diff --git a/.annotaterb.yml b/.annotaterb.yml
deleted file mode 100644
index df8e92b247..0000000000
--- a/.annotaterb.yml
+++ /dev/null
@@ -1,59 +0,0 @@
----
-:position: before
-:position_in_additional_file_patterns: before
-:position_in_class: before
-:position_in_factory: before
-:position_in_fixture: before
-:position_in_routes: before
-:position_in_serializer: before
-:position_in_test: before
-:classified_sort: true
-:exclude_controllers: true
-:exclude_factories: true
-:exclude_fixtures: true
-:exclude_helpers: true
-:exclude_scaffolds: true
-:exclude_serializers: true
-:exclude_sti_subclasses: true
-:exclude_tests: true
-:force: false
-:format_markdown: false
-:format_rdoc: false
-:format_yard: false
-:frozen: false
-:ignore_model_sub_dir: false
-:ignore_unknown_models: false
-:include_version: false
-:show_complete_foreign_keys: false
-:show_foreign_keys: false
-:show_indexes: false
-:simple_indexes: false
-:sort: false
-:timestamp: false
-:trace: false
-:with_comment: true
-:with_column_comments: true
-:with_table_comments: true
-:active_admin: false
-:command:
-:debug: false
-:hide_default_column_types: ''
-:hide_limit_column_types: 'integer,boolean'
-:ignore_columns:
-:ignore_routes:
-:models: true
-:routes: false
-:skip_on_db_migrate: false
-:target_action: :do_annotations
-:wrapper:
-:wrapper_close:
-:wrapper_open:
-:classes_default_to_s: []
-:additional_file_patterns: []
-:model_dir:
-  - app/models
-:require: []
-:root_dir:
-  - ''
-
-:show_check_constraints: false
diff --git a/.browserslistrc b/.browserslistrc
index 0135379d6e..6367e4d358 100644
--- a/.browserslistrc
+++ b/.browserslistrc
@@ -1,6 +1,10 @@
+[production]
 defaults
 > 0.2%
 firefox >= 78
 ios >= 15.6
 not dead
 not OperaMini all
+
+[development]
+supports es6-module
diff --git a/.bundler-audit.yml b/.bundler-audit.yml
new file mode 100644
index 0000000000..3d22165690
--- /dev/null
+++ b/.bundler-audit.yml
@@ -0,0 +1,6 @@
+ignore:
+  - CVE-2024-53985
+  - CVE-2024-53986
+  - CVE-2024-53987
+  - CVE-2024-53988
+  - CVE-2024-53989
diff --git a/.devcontainer/compose.yaml b/.devcontainer/compose.yaml
index 5da1ec3a24..5c7263c874 100644
--- a/.devcontainer/compose.yaml
+++ b/.devcontainer/compose.yaml
@@ -10,7 +10,6 @@ services:
       RAILS_ENV: development
       NODE_ENV: development
       BIND: 0.0.0.0
-      BOOTSNAP_CACHE_DIR: /tmp
       REDIS_HOST: redis
       REDIS_PORT: '6379'
       DB_HOST: db
@@ -21,13 +20,12 @@ services:
       ES_HOST: es
       ES_PORT: '9200'
       LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000
-      LOCAL_DOMAIN: ${LOCAL_DOMAIN:-localhost:3000}
     # Overrides default command so things don't shut down after the process ends.
     command: sleep infinity
     ports:
-      - '3000:3000'
-      - '3035:3035'
-      - '4000:4000'
+      - '127.0.0.1:3000:3000'
+      - '127.0.0.1:3035:3035'
+      - '127.0.0.1:4000:4000'
     networks:
       - external_network
       - internal_network
@@ -71,7 +69,7 @@ services:
         hard: -1
 
   libretranslate:
-    image: libretranslate/libretranslate:v1.6.2
+    image: libretranslate/libretranslate:v1.6.1
     restart: unless-stopped
     volumes:
       - lt-data:/home/libretranslate/.local
diff --git a/.dockerignore b/.dockerignore
index 9d990ab9ce..41da718049 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -20,9 +20,3 @@ postgres14
 redis
 elasticsearch
 chart
-.yarn/
-!.yarn/patches
-!.yarn/plugins
-!.yarn/releases
-!.yarn/sdks
-!.yarn/versions
diff --git a/.env.production.sample b/.env.production.sample
index a311ad5f8d..1faaf5b57c 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -79,9 +79,6 @@ AWS_ACCESS_KEY_ID=
 AWS_SECRET_ACCESS_KEY=
 S3_ALIAS_HOST=files.example.com
 
-# Optional list of hosts that are allowed to serve media for your instance
-# EXTRA_MEDIA_HOSTS=https://data.example1.com,https://data.example2.com
-
 # IP and session retention
 # -----------------------
 # Make sure to modify the scheduling of ip_cleanup_scheduler in config/sidekiq.yml
@@ -89,27 +86,3 @@ S3_ALIAS_HOST=files.example.com
 # -----------------------
 IP_RETENTION_PERIOD=31556952
 SESSION_RETENTION_PERIOD=31556952
-
-# Fetch All Replies Behavior
-# --------------------------
-# When a user expands a post (DetailedStatus view), fetch all of its replies
-# (default: false)
-FETCH_REPLIES_ENABLED=false
-
-# Period to wait between fetching replies (in minutes)
-FETCH_REPLIES_COOLDOWN_MINUTES=15
-
-# Period to wait after a post is first created before fetching its replies (in minutes)
-FETCH_REPLIES_INITIAL_WAIT_MINUTES=5
-
-# Max number of replies to fetch - total, recursively through a whole reply tree
-FETCH_REPLIES_MAX_GLOBAL=1000
-
-# Max number of replies to fetch - for a single post
-FETCH_REPLIES_MAX_SINGLE=500
-
-# Max number of replies Collection pages to fetch - total
-FETCH_REPLIES_MAX_PAGES=500
-
-# Maximum allowed character count
-MAX_CHARS=5555
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000000..d4930e1f52
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,13 @@
+/build/**
+/coverage/**
+/db/**
+/lib/**
+/log/**
+/node_modules/**
+/nonobox/**
+/public/**
+!/public/embed.js
+/spec/**
+/tmp/**
+/vendor/**
+!.eslintrc.js
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000000..b6e4253e61
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,368 @@
+// @ts-check
+const { defineConfig } = require('eslint-define-config');
+
+module.exports = defineConfig({
+  root: true,
+
+  extends: [
+    'eslint:recommended',
+    'plugin:react/recommended',
+    'plugin:react-hooks/recommended',
+    'plugin:jsx-a11y/recommended',
+    'plugin:import/recommended',
+    'plugin:promise/recommended',
+    'plugin:jsdoc/recommended',
+  ],
+
+  env: {
+    browser: true,
+    node: true,
+    es6: true,
+  },
+
+  parser: '@typescript-eslint/parser',
+
+  plugins: [
+    'react',
+    'jsx-a11y',
+    'import',
+    'promise',
+    '@typescript-eslint',
+    'formatjs',
+  ],
+
+  parserOptions: {
+    sourceType: 'module',
+    ecmaFeatures: {
+      jsx: true,
+    },
+    ecmaVersion: 2021,
+    requireConfigFile: false,
+    babelOptions: {
+      configFile: false,
+      presets: ['@babel/react', '@babel/env'],
+    },
+  },
+
+  settings: {
+    react: {
+      version: 'detect',
+    },
+    'import/ignore': [
+      'node_modules',
+      '\\.(css|scss|json)$',
+    ],
+    'import/resolver': {
+      typescript: {},
+    },
+  },
+
+  rules: {
+    'consistent-return': 'error',
+    'dot-notation': 'error',
+    eqeqeq: ['error', 'always', { 'null': 'ignore' }],
+    'indent': ['error', 2],
+    'jsx-quotes': ['error', 'prefer-single'],
+    'semi': ['error', 'always'],
+    'no-case-declarations': 'off',
+    'no-catch-shadow': 'error',
+    'no-console': [
+      'warn',
+      {
+        allow: [
+          'error',
+          'warn',
+        ],
+      },
+    ],
+    'no-empty': ['error', { "allowEmptyCatch": true }],
+    'no-restricted-properties': [
+      'error',
+      { property: 'substring', message: 'Use .slice instead of .substring.' },
+      { property: 'substr', message: 'Use .slice instead of .substr.' },
+    ],
+    'no-restricted-syntax': [
+      'error',
+      {
+        // eslint-disable-next-line no-restricted-syntax
+        selector: 'Literal[value=/•/], JSXText[value=/•/]',
+        // eslint-disable-next-line no-restricted-syntax
+        message: "Use '·' (middle dot) instead of '•' (bullet)",
+      },
+    ],
+    'no-unused-expressions': 'error',
+    'no-unused-vars': 'off',
+    '@typescript-eslint/no-unused-vars': [
+      'error',
+      {
+        vars: 'all',
+        args: 'after-used',
+        destructuredArrayIgnorePattern: '^_',
+        ignoreRestSiblings: true,
+      },
+    ],
+    'valid-typeof': 'error',
+
+    'react/jsx-filename-extension': ['error', { extensions: ['.jsx', 'tsx'] }],
+    'react/jsx-boolean-value': 'error',
+    'react/display-name': 'off',
+    'react/jsx-fragments': ['error', 'syntax'],
+    'react/jsx-equals-spacing': 'error',
+    'react/jsx-no-bind': 'error',
+    'react/jsx-no-useless-fragment': 'error',
+    'react/jsx-no-target-blank': 'off',
+    'react/jsx-tag-spacing': 'error',
+    'react/jsx-uses-react': 'off', // not needed with new JSX transform
+    'react/jsx-wrap-multilines': 'error',
+    'react/react-in-jsx-scope': 'off', // not needed with new JSX transform
+    'react/self-closing-comp': 'error',
+
+    // recommended values found in https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/v6.8.0/src/index.js#L46
+    'jsx-a11y/click-events-have-key-events': 'off',
+    'jsx-a11y/label-has-associated-control': 'off',
+    'jsx-a11y/media-has-caption': 'off',
+    'jsx-a11y/no-autofocus': 'off',
+    // recommended rule is:
+    // 'jsx-a11y/no-interactive-element-to-noninteractive-role': [
+    //   'error',
+    //   {
+    //     tr: ['none', 'presentation'],
+    //     canvas: ['img'],
+    //   },
+    // ],
+    'jsx-a11y/no-interactive-element-to-noninteractive-role': 'off',
+    // recommended rule is:
+    // 'jsx-a11y/no-noninteractive-tabindex': [
+    //   'error',
+    //   {
+    //     tags: [],
+    //     roles: ['tabpanel'],
+    //     allowExpressionValues: true,
+    //   },
+    // ],
+    'jsx-a11y/no-noninteractive-tabindex': 'off',
+    // recommended is full 'error'
+    'jsx-a11y/no-static-element-interactions': [
+      'warn',
+      {
+        handlers: [
+          'onClick',
+        ],
+      },
+    ],
+
+    // See https://github.com/import-js/eslint-plugin-import/blob/v2.29.1/config/recommended.js
+    'import/extensions': [
+      'error',
+      'always',
+      {
+        js: 'never',
+        jsx: 'never',
+        mjs: 'never',
+        ts: 'never',
+        tsx: 'never',
+      },
+    ],
+    'import/first': 'error',
+    'import/newline-after-import': 'error',
+    'import/no-anonymous-default-export': 'error',
+    'import/no-extraneous-dependencies': [
+      'error',
+      {
+        devDependencies: [
+          '.eslintrc.js',
+          'config/webpack/**',
+          'app/javascript/mastodon/performance.js',
+          'app/javascript/mastodon/test_setup.js',
+          'app/javascript/**/__tests__/**',
+        ],
+      },
+    ],
+    'import/no-amd': 'error',
+    'import/no-commonjs': 'error',
+    'import/no-import-module-exports': 'error',
+    'import/no-relative-packages': 'error',
+    'import/no-self-import': 'error',
+    'import/no-useless-path-segments': 'error',
+    'import/no-webpack-loader-syntax': 'error',
+
+    'import/order': [
+      'error',
+      {
+        alphabetize: { order: 'asc' },
+        'newlines-between': 'always',
+        groups: [
+          'builtin',
+          'external',
+          'internal',
+          'parent',
+          ['index', 'sibling'],
+          'object',
+        ],
+        pathGroups: [
+          // React core packages
+          {
+            pattern: '{react,react-dom,react-dom/client,prop-types}',
+            group: 'builtin',
+            position: 'after',
+          },
+          // I18n
+          {
+            pattern: '{react-intl,intl-messageformat}',
+            group: 'builtin',
+            position: 'after',
+          },
+          // Common React utilities
+          {
+            pattern: '{classnames,react-helmet,react-router,react-router-dom}',
+            group: 'external',
+            position: 'before',
+          },
+          // Immutable / Redux / data store
+          {
+            pattern: '{immutable,@reduxjs/toolkit,react-redux,react-immutable-proptypes,react-immutable-pure-component}',
+            group: 'external',
+            position: 'before',
+          },
+          // Internal packages
+          {
+            pattern: '{mastodon/**}',
+            group: 'internal',
+            position: 'after',
+          },
+        ],
+        pathGroupsExcludedImportTypes: [],
+      },
+    ],
+
+    'promise/always-return': 'off',
+    'promise/catch-or-return': [
+      'error',
+      {
+        allowFinally: true,
+      },
+    ],
+    'promise/no-callback-in-promise': 'off',
+    'promise/no-nesting': 'off',
+    'promise/no-promise-in-callback': 'off',
+
+    'formatjs/blocklist-elements': 'error',
+    'formatjs/enforce-default-message': ['error', 'literal'],
+    'formatjs/enforce-description': 'off', // description values not currently used
+    'formatjs/enforce-id': 'off', // Explicit IDs are used in the project
+    'formatjs/enforce-placeholders': 'off', // Issues in short_number.jsx
+    'formatjs/enforce-plural-rules': 'error',
+    'formatjs/no-camel-case': 'off', // disabledAccount is only non-conforming
+    'formatjs/no-complex-selectors': 'error',
+    'formatjs/no-emoji': 'error',
+    'formatjs/no-id': 'off', // IDs are used for translation keys
+    'formatjs/no-invalid-icu': 'error',
+    'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
+    'formatjs/no-multiple-whitespaces': 'error',
+    'formatjs/no-offset': 'error',
+    'formatjs/no-useless-message': 'error',
+    'formatjs/prefer-formatted-message': 'error',
+    'formatjs/prefer-pound-in-plural': 'error',
+
+    'jsdoc/check-types': 'off',
+    'jsdoc/no-undefined-types': 'off',
+    'jsdoc/require-jsdoc': 'off',
+    'jsdoc/require-param-description': 'off',
+    'jsdoc/require-property-description': 'off',
+    'jsdoc/require-returns-description': 'off',
+    'jsdoc/require-returns': 'off',
+  },
+
+  overrides: [
+    {
+      files: [
+        '.eslintrc.js',
+        '*.config.js',
+        '.*rc.js',
+        'ide-helper.js',
+        'config/webpack/**/*',
+        'config/formatjs-formatter.js',
+      ],
+
+      env: {
+        commonjs: true,
+      },
+
+      parserOptions: {
+        sourceType: 'script',
+      },
+
+      rules: {
+        'import/no-commonjs': 'off',
+      },
+    },
+    {
+      files: [
+        '**/*.ts',
+        '**/*.tsx',
+      ],
+
+      extends: [
+        'eslint:recommended',
+        'plugin:@typescript-eslint/strict-type-checked',
+        'plugin:@typescript-eslint/stylistic-type-checked',
+        'plugin:react/recommended',
+        'plugin:react-hooks/recommended',
+        'plugin:jsx-a11y/recommended',
+        'plugin:import/recommended',
+        'plugin:import/typescript',
+        'plugin:promise/recommended',
+        'plugin:jsdoc/recommended-typescript',
+      ],
+
+      parserOptions: {
+        projectService: true,
+        tsconfigRootDir: __dirname,
+      },
+
+      rules: {
+        // Disable formatting rules that have been enabled in the base config
+        'indent': 'off',
+
+        // This is not needed as we use noImplicitReturns, which handles this in addition to understanding types
+        'consistent-return': 'off',
+
+        'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
+
+        '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
+        '@typescript-eslint/consistent-type-exports': 'error',
+        '@typescript-eslint/consistent-type-imports': 'error',
+        "@typescript-eslint/prefer-nullish-coalescing": ['error', { ignorePrimitives: { boolean: true } }],
+        "@typescript-eslint/no-restricted-imports": [
+          "warn",
+          {
+            "name": "react-redux",
+            "importNames": ["useSelector", "useDispatch"],
+            "message": "Use typed hooks `useAppDispatch` and `useAppSelector` instead."
+          }
+        ],
+        "@typescript-eslint/restrict-template-expressions": ['warn', { allowNumber: true }],
+        'jsdoc/require-jsdoc': 'off',
+
+        // Those rules set stricter rules for TS files
+        // to enforce better practices when converting from JS
+        'import/no-default-export': 'warn',
+        'react/prefer-stateless-function': 'warn',
+        'react/function-component-definition': ['error', { namedComponents: 'arrow-function' }],
+        'react/jsx-uses-react': 'off', // not needed with new JSX transform
+        'react/react-in-jsx-scope': 'off', // not needed with new JSX transform
+        'react/prop-types': 'off',
+      },
+    },
+    {
+      files: [
+        '**/__tests__/*.js',
+        '**/__tests__/*.jsx',
+      ],
+
+      env: {
+        jest: true,
+      },
+    }
+  ],
+});
diff --git a/.github/ISSUE_TEMPLATE/3.troubleshooting.yml b/.github/ISSUE_TEMPLATE/3.troubleshooting.yml
deleted file mode 100644
index fa9bfc7c80..0000000000
--- a/.github/ISSUE_TEMPLATE/3.troubleshooting.yml
+++ /dev/null
@@ -1,74 +0,0 @@
-name: Deployment troubleshooting
-description: |
-  You are a server administrator and you are encountering a technical issue during installation, upgrade or operations of Mastodon.
-labels: ['status/to triage']
-type: 'Troubleshooting'
-body:
-  - type: markdown
-    attributes:
-      value: |
-        Make sure that you are submitting a new bug that was not previously reported or already fixed.
-
-        Please use a concise and distinct title for the issue.
-  - type: textarea
-    attributes:
-      label: Steps to reproduce the problem
-      description: What were you trying to do?
-      value: |
-        1.
-        2.
-        3.
-        ...
-    validations:
-      required: true
-  - type: input
-    attributes:
-      label: Expected behaviour
-      description: What should have happened?
-    validations:
-      required: true
-  - type: input
-    attributes:
-      label: Actual behaviour
-      description: What happened?
-    validations:
-      required: true
-  - type: textarea
-    attributes:
-      label: Detailed description
-    validations:
-      required: false
-  - type: input
-    attributes:
-      label: Mastodon instance
-      description: The address of the Mastodon instance where you experienced the issue
-      placeholder: mastodon.social
-    validations:
-      required: true
-  - type: input
-    attributes:
-      label: Mastodon version
-      description: |
-        This is displayed at the bottom of the About page, eg. `v4.4.0-alpha.1`
-      placeholder: v4.3.0
-    validations:
-      required: false
-  - type: textarea
-    attributes:
-      label: Environment
-      description: |
-        Details about your environment, like how Mastodon is deployed, if containers are used, version numbers, etc.
-      value: |
-        Please at least include those informations:
-        - Operating system: (eg. Ubuntu 22.04)
-        - Ruby version: (from `ruby --version`, eg. v3.4.1)
-        - Node.js version: (from `node --version`, eg. v20.18.0)
-    validations:
-      required: false
-  - type: textarea
-    attributes:
-      label: Technical details
-      description: |
-        Any additional technical details you may have, like logs or error traces
-    validations:
-      required: false
diff --git a/.github/actions/setup-ruby/action.yml b/.github/actions/setup-ruby/action.yml
index 3e232f134c..672a06f9ea 100644
--- a/.github/actions/setup-ruby/action.yml
+++ b/.github/actions/setup-ruby/action.yml
@@ -21,3 +21,4 @@ runs:
       with:
         ruby-version: ${{ inputs.ruby-version }}
         bundler-cache: true
+        cache-version: 4.3
diff --git a/.github/renovate.json5 b/.github/renovate.json5
index e638b9c548..8a10676283 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -15,8 +15,6 @@
   // to `null` after any other rule set it to something.
   dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).',
   postUpdateOptions: ['yarnDedupeHighest'],
-  // The types are now included in recent versions,we ignore them here until we upgrade and remove the dependency
-  ignoreDeps: ['@types/emoji-mart'],
   packageRules: [
     {
       // Require Dependency Dashboard Approval for major version bumps of these node packages
@@ -99,13 +97,7 @@
     {
       // Group all eslint-related packages with `eslint` in the same PR
       matchManagers: ['npm'],
-      matchPackageNames: [
-        'eslint',
-        'eslint-*',
-        'typescript-eslint',
-        '@eslint/*',
-        'globals',
-      ],
+      matchPackageNames: ['eslint', 'eslint-*', '@typescript-eslint/*'],
       matchUpdateTypes: ['patch', 'minor'],
       groupName: 'eslint (non-major)',
     },
diff --git a/.github/workflows/bundler-audit.yml b/.github/workflows/bundler-audit.yml
index c96e4429af..f80c5837b2 100644
--- a/.github/workflows/bundler-audit.yml
+++ b/.github/workflows/bundler-audit.yml
@@ -39,4 +39,4 @@ jobs:
           bundler-cache: true
 
       - name: Run bundler-audit
-        run: bin/bundler-audit check --update
+        run: bundle exec bundler-audit check --update
diff --git a/.github/workflows/check-i18n.yml b/.github/workflows/check-i18n.yml
index 63529e4f16..7cb0c0dd98 100644
--- a/.github/workflows/check-i18n.yml
+++ b/.github/workflows/check-i18n.yml
@@ -24,7 +24,7 @@ permissions:
 
 jobs:
   check-i18n:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
 
     steps:
       - uses: actions/checkout@v4
@@ -41,18 +41,18 @@ jobs:
           git diff --exit-code
 
       - name: Check locale file normalization
-        run: bin/i18n-tasks check-normalized
+        run: bundle exec i18n-tasks check-normalized
 
       - name: Check for unused strings
-        run: bin/i18n-tasks unused
+        run: bundle exec i18n-tasks unused
 
       - name: Check for missing strings in English YML
         run: |
-          bin/i18n-tasks add-missing -l en
+          bundle exec i18n-tasks add-missing -l en
           git diff --exit-code
 
       - name: Check for wrong string interpolations
-        run: bin/i18n-tasks check-consistent-interpolations
+        run: bundle exec i18n-tasks check-consistent-interpolations
 
       - name: Check that all required locale files exist
-        run: bin/rake repo:check_locales_files
+        run: bundle exec rake repo:check_locales_files
diff --git a/.github/workflows/crowdin-download-stable.yml b/.github/workflows/crowdin-download-stable.yml
index 6d9a058629..de21e2e58f 100644
--- a/.github/workflows/crowdin-download-stable.yml
+++ b/.github/workflows/crowdin-download-stable.yml
@@ -46,11 +46,11 @@ jobs:
         uses: ./.github/actions/setup-ruby
 
       - name: Run i18n normalize task
-        run: bin/i18n-tasks normalize
+        run: bundle exec i18n-tasks normalize
 
       # Create or update the pull request
       - name: Create Pull Request
-        uses: peter-evans/create-pull-request@v7.0.6
+        uses: peter-evans/create-pull-request@v7.0.5
         with:
           commit-message: 'New Crowdin translations'
           title: 'New Crowdin Translations for ${{ github.base_ref || github.ref_name }} (automated)'
diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml
index ffab4880e1..f379c56112 100644
--- a/.github/workflows/lint-css.yml
+++ b/.github/workflows/lint-css.yml
@@ -43,4 +43,4 @@ jobs:
         uses: ./.github/actions/setup-javascript
 
       - name: Stylelint
-        run: yarn lint:css --custom-formatter @csstools/stylelint-formatter-github
+        run: yarn lint:css -f github
diff --git a/.github/workflows/lint-haml.yml b/.github/workflows/lint-haml.yml
index c596261eb0..fb40585c37 100644
--- a/.github/workflows/lint-haml.yml
+++ b/.github/workflows/lint-haml.yml
@@ -46,4 +46,4 @@ jobs:
       - name: Run haml-lint
         run: |
           echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
-          bin/haml-lint --reporter github
+          bundle exec haml-lint --reporter github
diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml
index 13468e7799..621a662387 100644
--- a/.github/workflows/lint-js.yml
+++ b/.github/workflows/lint-js.yml
@@ -14,7 +14,7 @@ on:
       - 'tsconfig.json'
       - '.nvmrc'
       - '.prettier*'
-      - 'eslint.config.mjs'
+      - '.eslint*'
       - '**/*.js'
       - '**/*.jsx'
       - '**/*.ts'
@@ -28,7 +28,7 @@ on:
       - 'tsconfig.json'
       - '.nvmrc'
       - '.prettier*'
-      - 'eslint.config.mjs'
+      - '.eslint*'
       - '**/*.js'
       - '**/*.jsx'
       - '**/*.ts'
@@ -47,7 +47,7 @@ jobs:
         uses: ./.github/actions/setup-javascript
 
       - name: ESLint
-        run: yarn workspaces foreach --all --parallel run lint:js --max-warnings 0
+        run: yarn lint:js --max-warnings 0
 
       - name: Typecheck
         run: yarn typecheck
diff --git a/.github/workflows/lint-ruby.yml b/.github/workflows/lint-ruby.yml
index 5bb67b108c..1d4395e9ac 100644
--- a/.github/workflows/lint-ruby.yml
+++ b/.github/workflows/lint-ruby.yml
@@ -12,7 +12,6 @@ on:
       - 'Gemfile*'
       - '.rubocop*.yml'
       - '.ruby-version'
-      - 'bin/rubocop'
       - 'config/brakeman.ignore'
       - '**/*.rb'
       - '**/*.rake'
@@ -23,7 +22,6 @@ on:
       - 'Gemfile*'
       - '.rubocop*.yml'
       - '.ruby-version'
-      - 'bin/rubocop'
       - 'config/brakeman.ignore'
       - '**/*.rb'
       - '**/*.rake'
diff --git a/.github/workflows/test-migrations.yml b/.github/workflows/test-migrations.yml
index c4a716e8f9..892f59f941 100644
--- a/.github/workflows/test-migrations.yml
+++ b/.github/workflows/test-migrations.yml
@@ -15,7 +15,6 @@ on:
       - '**/*.rb'
       - '.github/workflows/test-migrations.yml'
       - 'lib/tasks/tests.rake'
-      - 'lib/tasks/db.rake'
 
   pull_request:
     paths:
@@ -67,6 +66,7 @@ jobs:
       DB_HOST: localhost
       DB_USER: postgres
       DB_PASS: postgres
+      DISABLE_SIMPLECOV: true
       RAILS_ENV: test
       BUNDLE_CLEAN: true
       BUNDLE_FROZEN: true
@@ -80,18 +80,6 @@ jobs:
       - name: Set up Ruby environment
         uses: ./.github/actions/setup-ruby
 
-      - name: Ensure no errors with `db:prepare`
-        run: |
-          bin/rails db:drop
-          bin/rails db:prepare
-          bin/rails db:migrate
-
-      - name: Ensure no errors with `db:prepare` and SKIP_POST_DEPLOYMENT_MIGRATIONS
-        run: |
-          bin/rails db:drop
-          SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails db:prepare
-          bin/rails db:migrate
-
       - name: Test "one step migration" flow
         run: |
           bin/rails db:drop
@@ -105,11 +93,6 @@ jobs:
           bin/rails db:drop
           bin/rails db:create
           SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails tests:migrations:prepare_database
-
-          # Migrate up to v4.2.0 breakpoint
-          bin/rails db:migrate VERSION=20230907150100
-
-          # Migrate the rest
           SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails db:migrate
           bin/rails db:migrate
           bin/rails tests:migrations:check_database
diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml
index fd4c666059..ee22e19bee 100644
--- a/.github/workflows/test-ruby.yml
+++ b/.github/workflows/test-ruby.yml
@@ -110,7 +110,7 @@ jobs:
       DB_HOST: localhost
       DB_USER: postgres
       DB_PASS: postgres
-      COVERAGE: ${{ matrix.ruby-version == '.ruby-version' }}
+      DISABLE_SIMPLECOV: ${{ matrix.ruby-version != '.ruby-version' }}
       RAILS_ENV: test
       ALLOW_NOPAM: true
       PAM_ENABLED: true
@@ -128,9 +128,10 @@ jobs:
       fail-fast: false
       matrix:
         ruby-version:
+          - '3.1'
           - '3.2'
-          - '3.3'
           - '.ruby-version'
+          - '3.4'
     steps:
       - uses: actions/checkout@v4
 
@@ -171,7 +172,7 @@ jobs:
 
       - name: Upload coverage reports to Codecov
         if: matrix.ruby-version == '.ruby-version'
-        uses: codecov/codecov-action@v5
+        uses: codecov/codecov-action@v4
         with:
           files: coverage/lcov/*.lcov
         env:
@@ -179,7 +180,7 @@ jobs:
 
   test-libvips:
     name: Libvips tests
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-24.04
 
     needs:
       - build
@@ -212,7 +213,7 @@ jobs:
       DB_HOST: localhost
       DB_USER: postgres
       DB_PASS: postgres
-      COVERAGE: ${{ matrix.ruby-version == '.ruby-version' }}
+      DISABLE_SIMPLECOV: ${{ matrix.ruby-version != '.ruby-version' }}
       RAILS_ENV: test
       ALLOW_NOPAM: true
       PAM_ENABLED: true
@@ -230,9 +231,10 @@ jobs:
       fail-fast: false
       matrix:
         ruby-version:
+          - '3.1'
           - '3.2'
-          - '3.3'
           - '.ruby-version'
+          - '3.4'
     steps:
       - uses: actions/checkout@v4
 
@@ -258,7 +260,7 @@ jobs:
 
       - name: Upload coverage reports to Codecov
         if: matrix.ruby-version == '.ruby-version'
-        uses: codecov/codecov-action@v5
+        uses: codecov/codecov-action@v4
         with:
           files: coverage/lcov/mastodon.lcov
         env:
@@ -299,6 +301,7 @@ jobs:
       DB_HOST: localhost
       DB_USER: postgres
       DB_PASS: postgres
+      DISABLE_SIMPLECOV: true
       RAILS_ENV: test
       BUNDLE_WITH: test
       ES_ENABLED: false
@@ -309,9 +312,10 @@ jobs:
       fail-fast: false
       matrix:
         ruby-version:
+          - '3.1'
           - '3.2'
-          - '3.3'
           - '.ruby-version'
+          - '3.4'
 
     steps:
       - uses: actions/checkout@v4
@@ -415,6 +419,7 @@ jobs:
       DB_HOST: localhost
       DB_USER: postgres
       DB_PASS: postgres
+      DISABLE_SIMPLECOV: true
       RAILS_ENV: test
       BUNDLE_WITH: test
       ES_ENABLED: true
@@ -425,9 +430,10 @@ jobs:
       fail-fast: false
       matrix:
         ruby-version:
+          - '3.1'
           - '3.2'
-          - '3.3'
           - '.ruby-version'
+          - '3.4'
         search-image:
           - docker.elastic.co/elasticsearch/elasticsearch:7.17.13
         include:
diff --git a/.nvmrc b/.nvmrc
index 744ca17ec0..65da8ce391 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-22.14
+20.17
diff --git a/.prettierignore b/.prettierignore
index 80b4c0159e..6b2f0c1889 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -63,7 +63,6 @@ docker-compose.override.yml
 
 # Ignore emoji map file
 /app/javascript/mastodon/features/emoji/emoji_map.json
-/app/javascript/mastodon/features/emoji/emoji_sheet.json
 
 # Ignore locale files
 /app/javascript/mastodon/locales/*.json
diff --git a/.prettierrc.js b/.prettierrc.js
index 65ec869c33..af39b253f6 100644
--- a/.prettierrc.js
+++ b/.prettierrc.js
@@ -1,4 +1,4 @@
 module.exports = {
   singleQuote: true,
   jsxSingleQuote: true
-};
+}
diff --git a/.rubocop.yml b/.rubocop.yml
index 1bbba515af..5bfa331d27 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -8,7 +8,7 @@ AllCops:
     - lib/mastodon/migration_helpers.rb
   ExtraDetails: true
   NewCops: enable
-  TargetRubyVersion: 3.2 # Oldest supported ruby version
+  TargetRubyVersion: 3.1 # Oldest supported ruby version
 
 inherit_from:
   - .rubocop/layout.yml
@@ -18,7 +18,6 @@ inherit_from:
   - .rubocop/rspec_rails.yml
   - .rubocop/rspec.yml
   - .rubocop/style.yml
-  - .rubocop/i18n.yml
   - .rubocop/custom.yml
   - .rubocop_todo.yml
   - .rubocop/strict.yml
@@ -27,10 +26,10 @@ inherit_mode:
   merge:
     - Exclude
 
-plugins:
-  - rubocop-capybara
-  - rubocop-i18n
-  - rubocop-performance
+require:
   - rubocop-rails
   - rubocop-rspec
   - rubocop-rspec_rails
+  - rubocop-performance
+  - rubocop-capybara
+  - ./lib/linter/rubocop_middle_dot
diff --git a/.rubocop/i18n.yml b/.rubocop/i18n.yml
deleted file mode 100644
index de395d3a79..0000000000
--- a/.rubocop/i18n.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-I18n/RailsI18n:
-  Enabled: true
-  Exclude:
-    - 'config/**/*'
-    - 'db/**/*'
-    - 'lib/**/*'
-    - 'spec/**/*'
-I18n/GetText:
-  Enabled: false
-
-I18n/RailsI18n/DecorateStringFormattingUsingInterpolation:
-  Enabled: false
diff --git a/.rubocop/rails.yml b/.rubocop/rails.yml
index bbd172e656..ae31c1f266 100644
--- a/.rubocop/rails.yml
+++ b/.rubocop/rails.yml
@@ -2,9 +2,6 @@
 Rails/BulkChangeTable:
   Enabled: false # Conflicts with strong_migrations features
 
-Rails/Delegate:
-  Enabled: false
-
 Rails/FilePath:
   EnforcedStyle: arguments
 
diff --git a/.rubocop/style.yml b/.rubocop/style.yml
index f59340d452..03e35a70ac 100644
--- a/.rubocop/style.yml
+++ b/.rubocop/style.yml
@@ -1,7 +1,4 @@
 ---
-Style/ArrayIntersect:
-  Enabled: false
-
 Style/ClassAndModuleChildren:
   Enabled: false
 
@@ -22,13 +19,6 @@ Style/HashSyntax:
   EnforcedShorthandSyntax: either
   EnforcedStyle: ruby19_no_mixed_keys
 
-Style/IfUnlessModifier:
-  Exclude:
-    - '**/*.haml'
-
-Style/KeywordArgumentsMerging:
-  Enabled: false
-
 Style/NumericLiterals:
   AllowedPatterns:
     - \d{4}_\d{2}_\d{2}_\d{6}
@@ -47,9 +37,6 @@ Style/RedundantFetchBlock:
 Style/RescueStandardError:
   EnforcedStyle: implicit
 
-Style/SafeNavigationChainLength:
-  Enabled: false
-
 Style/SymbolArray:
   Enabled: false
 
@@ -58,6 +45,3 @@ Style/TrailingCommaInArrayLiteral:
 
 Style/TrailingCommaInHashLiteral:
   EnforcedStyleForMultiline: comma
-
-Style/WordArray:
-  MinSize: 3 # Override default of 2
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 13fb25d333..cd5d365c67 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,6 +1,6 @@
 # This configuration was generated by
 # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
-# using RuboCop version 1.75.2.
+# using RuboCop version 1.66.1.
 # The point is for the user to remove these configuration records
 # one by one as the offenses are removed from the code base.
 # Note that changes in the inspected code, or installation of new
@@ -8,7 +8,7 @@
 
 Lint/NonLocalExitFromIterator:
   Exclude:
-    - 'app/helpers/json_ld_helper.rb'
+    - 'app/helpers/jsonld_helper.rb'
 
 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
 Metrics/AbcSize:
@@ -39,6 +39,7 @@ Rails/OutputSafety:
 # Configuration parameters: AllowedVars.
 Style/FetchEnvVar:
   Exclude:
+    - 'app/lib/translation_service.rb'
     - 'config/environments/production.rb'
     - 'config/initializers/2_limited_federation_mode.rb'
     - 'config/initializers/3_omniauth.rb'
@@ -49,7 +50,7 @@ Style/FetchEnvVar:
     - 'lib/tasks/repo.rake'
 
 # This cop supports safe autocorrection (--autocorrect).
-# Configuration parameters: EnforcedStyle, MaxUnannotatedPlaceholdersAllowed, Mode, AllowedMethods, AllowedPatterns.
+# Configuration parameters: EnforcedStyle, MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns.
 # SupportedStyles: annotated, template, unannotated
 # AllowedMethods: redirect
 Style/FormatStringToken:
@@ -62,10 +63,31 @@ Style/FormatStringToken:
 Style/GuardClause:
   Enabled: false
 
+# This cop supports unsafe autocorrection (--autocorrect-all).
+Style/HashTransformValues:
+  Exclude:
+    - 'app/serializers/rest/web_push_subscription_serializer.rb'
+    - 'app/services/import_service.rb'
+
+# This cop supports unsafe autocorrection (--autocorrect-all).
+Style/MapToHash:
+  Exclude:
+    - 'app/models/status.rb'
+
+# This cop supports unsafe autocorrection (--autocorrect-all).
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: literals, strict
+Style/MutableConstant:
+  Exclude:
+    - 'app/models/tag.rb'
+    - 'app/services/delete_account_service.rb'
+    - 'lib/mastodon/migration_warning.rb'
+
 # Configuration parameters: AllowedMethods.
 # AllowedMethods: respond_to_missing?
 Style/OptionalBooleanParameter:
   Exclude:
+    - 'app/helpers/jsonld_helper.rb'
     - 'app/lib/admin/system_check/message.rb'
     - 'app/lib/request.rb'
     - 'app/lib/webfinger.rb'
@@ -86,3 +108,10 @@ Style/RedundantConstantBase:
   Exclude:
     - 'config/environments/production.rb'
     - 'config/initializers/sidekiq.rb'
+
+# This cop supports safe autocorrection (--autocorrect).
+# Configuration parameters: WordRegex.
+# SupportedStyles: percent, brackets
+Style/WordArray:
+  EnforcedStyle: percent
+  MinSize: 3
diff --git a/.ruby-version b/.ruby-version
index 6cb9d3dd0d..fa7adc7ac7 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-3.4.3
+3.3.5
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4dd4783597..c05fdd679f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,29 +2,6 @@
 
 All notable changes to this project will be documented in this file.
 
-## [4.3.7] - 2025-04-02
-
-### Add
-
-- Add delay to profile updates to debounce them (#34137 by @ClearlyClaire)
-- Add support for paginating partial collections in `SynchronizeFollowersService` (#34272 and #34277 by @ClearlyClaire)
-
-### Changed
-
-- Change account suspensions to be federated to recently-followed accounts as well (#34294 by @ClearlyClaire)
-- Change `AccountReachFinder` to consider statuses based on suspension date (#32805 and #34291 by @ClearlyClaire and @mjankowski)
-- Change user archive signed URL TTL from 10 seconds to 1 hour (#34254 by @ClearlyClaire)
-
-### Fixed
-
-- Fix static version of animated PNG emojis not being properly extracted (#34337 by @ClearlyClaire)
-- Fix filters not applying in detailed view, favourites and bookmarks (#34259 and #34260 by @ClearlyClaire)
-- Fix handling of malformed/unusual HTML (#34201 by @ClearlyClaire)
-- Fix `CacheBuster` being queued for missing media attachments (#34253 by @ClearlyClaire)
-- Fix incorrect URL being used when cache busting (#34189 by @ClearlyClaire)
-- Fix streaming server refusing unix socket path in `DATABASE_URL` (#34091 by @ClearlyClaire)
-- Fix “x” hotkey not working on boosted filtered posts (#33758 by @ClearlyClaire)
-
 ## [4.3.6] - 2025-03-13
 
 ### Security
@@ -210,7 +187,7 @@ The following changelog entries focus on changes visible to users, administrator
   - `GET /api/v2/notifications`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-grouped
   - `GET /api/v2/notifications/:group_key`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-notification-group
   - `GET /api/v2/notifications/:group_key/accounts`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-group-accounts
-  - `POST /api/v2/notifications/:group_key/dismiss`: https://docs.joinmastodon.org/methods/grouped_notifications/#dismiss-group
+  - `POST /api/v2/notifications/:group_key/dimsiss`: https://docs.joinmastodon.org/methods/grouped_notifications/#dismiss-group
   - `GET /api/v2/notifications/:unread_count`: https://docs.joinmastodon.org/methods/grouped_notifications/#unread-group-count
 - **Add notification policies, filtered notifications and notification requests** (#29366, #29529, #29433, #29565, #29567, #29572, #29575, #29588, #29646, #29652, #29658, #29666, #29693, #29699, #29737, #29706, #29570, #29752, #29810, #29826, #30114, #30251, #30559, #29868, #31008, #31011, #30996, #31149, #31220, #31222, #31225, #31242, #31262, #31250, #31273, #31310, #31316, #31322, #31329, #31324, #31331, #31343, #31342, #31309, #31358, #31378, #31406, #31256, #31456, #31419, #31457, #31508, #31540, #31541, #31723, #32062 and #32281 by @ClearlyClaire, @Gargron, @TheEssem, @mgmn, @oneiros, and @renchap)\
   The old “Block notifications from non-followers”, “Block notifications from people you don't follow” and “Block direct messages from people you don't follow” notification settings have been replaced by a new set of settings found directly in the notification column.\
@@ -541,7 +518,7 @@ The following changelog entries focus on changes visible to users, administrator
 - Fix empty environment variables not using default nil value (#27400 by @renchap)
 - Fix language sorting in settings (#27158 by @gunchleoc)
 
-## [4.2.11] - 2024-08-16
+## |4.2.11] - 2024-08-16
 
 ### Added
 
diff --git a/Dockerfile b/Dockerfile
index 6620f4c096..bb4e8947aa 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
-# syntax=docker/dockerfile:1.12
+# syntax=docker/dockerfile:1.9
 
 # This file is designed for production server deployment, not local development work
-# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/docs/DEVELOPMENT.md#docker
+# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker
 
 # Please see https://docs.docker.com/engine/reference/builder for information about
 # the extended buildx capabilities used in this file.
@@ -9,20 +9,19 @@
 # See: https://docs.docker.com/build/building/multi-platform/
 ARG TARGETPLATFORM=${TARGETPLATFORM}
 ARG BUILDPLATFORM=${BUILDPLATFORM}
-ARG BASE_REGISTRY="docker.io"
 
-# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"]
+# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"]
 # renovate: datasource=docker depName=docker.io/ruby
-ARG RUBY_VERSION="3.4.2"
-# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
+ARG RUBY_VERSION="3.3.5"
+# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
 # renovate: datasource=node-version depName=node
-ARG NODE_MAJOR_VERSION="22"
+ARG NODE_MAJOR_VERSION="20"
 # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
 ARG DEBIAN_VERSION="bookworm"
-# Node.js image to use for base image based on combined variables (ex: 20-bookworm-slim)
-FROM ${BASE_REGISTRY}/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node
-# Ruby image to use for base image based on combined variables (ex: 3.4.x-slim-bookworm)
-FROM ${BASE_REGISTRY}/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
+# Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
+FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node
+# Ruby image to use for base image based on combined variables (ex: 3.3.x-slim-bookworm)
+FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
 
 # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA
 # Example: v4.3.0-nightly.2023.11.09+pr-123456
@@ -30,8 +29,6 @@ FROM ${BASE_REGISTRY}/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
 ARG MASTODON_VERSION_PRERELEASE=""
 # Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="pr-123456"]
 ARG MASTODON_VERSION_METADATA=""
-# Will be available as Mastodon::Version.source_commit
-ARG SOURCE_COMMIT=""
 
 # Allow Ruby on Rails to serve static files
 # See: https://docs.joinmastodon.org/admin/config/#rails_serve_static_files
@@ -48,31 +45,30 @@ ARG GID="991"
 
 # Apply Mastodon build options based on options above
 ENV \
-  # Apply Mastodon version information
+# Apply Mastodon version information
   MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
   MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \
-  SOURCE_COMMIT="${SOURCE_COMMIT}" \
-  # Apply Mastodon static files and YJIT options
+# Apply Mastodon static files and YJIT options
   RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \
   RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \
-  # Apply timezone
+# Apply timezone
   TZ=${TZ}
 
 ENV \
-  # Configure the IP to bind Mastodon to when serving traffic
+# Configure the IP to bind Mastodon to when serving traffic
   BIND="0.0.0.0" \
-  # Use production settings for Yarn, Node.js and related tools
+# Use production settings for Yarn, Node and related nodejs based tools
   NODE_ENV="production" \
-  # Use production settings for Ruby on Rails
+# Use production settings for Ruby on Rails
   RAILS_ENV="production" \
-  # Add Ruby and Mastodon installation to the PATH
+# Add Ruby and Mastodon installation to the PATH
   DEBIAN_FRONTEND="noninteractive" \
   PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" \
-  # Optimize jemalloc 5.x performance
+# Optimize jemalloc 5.x performance
   MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0" \
-  # Enable libvips, should not be changed
+# Enable libvips, should not be changed
   MASTODON_USE_LIBVIPS=true \
-  # Sidekiq will touch tmp/sidekiq_process_has_started_and_will_begin_processing_jobs to indicate it is ready. This can be used for a readiness check in Kubernetes
+# Sidekiq will touch tmp/sidekiq_process_has_started_and_will_begin_processing_jobs to indicate it is ready. This can be used for a readiness check in Kubernetes
   MASTODON_SIDEKIQ_READY_FILENAME=sidekiq_process_has_started_and_will_begin_processing_jobs
 
 # Set default shell used for running commands
@@ -83,14 +79,14 @@ ARG TARGETPLATFORM
 RUN echo "Target platform is $TARGETPLATFORM"
 
 RUN \
-  # Remove automatic apt cache Docker cleanup scripts
+# Remove automatic apt cache Docker cleanup scripts
   rm -f /etc/apt/apt.conf.d/docker-clean; \
-  # Sets timezone
+# Sets timezone
   echo "${TZ}" > /etc/localtime; \
-  # Creates mastodon user/group and sets home directory
+# Creates mastodon user/group and sets home directory
   groupadd -g "${GID}" mastodon; \
   useradd -l -u "${UID}" -g "${GID}" -m -d /opt/mastodon mastodon; \
-  # Creates /mastodon symlink to /opt/mastodon
+# Creates /mastodon symlink to /opt/mastodon
   ln -s /opt/mastodon /mastodon;
 
 # Set /opt/mastodon as working directory
@@ -101,92 +97,105 @@ RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/
 
 # hadolint ignore=DL3008,DL3005
 RUN \
-  # Mount Apt cache and lib directories from Docker buildx caches
-  --mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
-  --mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
-  # Apt update & upgrade to check for security updates to Debian image
+# Mount Apt cache and lib directories from Docker buildx caches
+--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
+--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
+# Apt update & upgrade to check for security updates to Debian image
   apt-get update; \
   apt-get dist-upgrade -yq; \
-  # Install jemalloc, curl and other necessary components
+# Install jemalloc, curl and other necessary components
   apt-get install -y --no-install-recommends \
-  curl \
-  file \
-  libjemalloc2 \
-  patchelf \
-  procps \
-  tini \
-  tzdata \
-  wget \
+    curl \
+    file \
+    libjemalloc2 \
+    patchelf \
+    procps \
+    tini \
+    tzdata \
+    wget \
   ; \
-  # Patch Ruby to use jemalloc
+# Patch Ruby to use jemalloc
   patchelf --add-needed libjemalloc.so.2 /usr/local/bin/ruby; \
-  # Discard patchelf after use
+# Discard patchelf after use
   apt-get purge -y \
-  patchelf \
+    patchelf \
   ;
 
 # Create temporary build layer from base image
 FROM ruby AS build
 
+# Copy Node package configuration files into working directory
+COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
+COPY .yarn /opt/mastodon/.yarn
+
+COPY --from=node /usr/local/bin /usr/local/bin
+COPY --from=node /usr/local/lib /usr/local/lib
+
 ARG TARGETPLATFORM
 
 # hadolint ignore=DL3008
 RUN \
-  # Mount Apt cache and lib directories from Docker buildx caches
-  --mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
-  --mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
-  # Install build tools and bundler dependencies from APT
+# Mount Apt cache and lib directories from Docker buildx caches
+--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
+--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
+# Install build tools and bundler dependencies from APT
   apt-get install -y --no-install-recommends \
-  autoconf \
-  automake \
-  build-essential \
-  cmake \
-  git \
-  libgdbm-dev \
-  libglib2.0-dev \
-  libgmp-dev \
-  libicu-dev \
-  libidn-dev \
-  libpq-dev \
-  libssl-dev \
-  libtool \
-  libyaml-dev \
-  meson \
-  nasm \
-  pkg-config \
-  shared-mime-info \
-  xz-utils \
-  # libvips components
-  libcgif-dev \
-  libexif-dev \
-  libexpat1-dev \
-  libgirepository1.0-dev \
-  libheif-dev/bookworm-backports \
-  libimagequant-dev \
-  libjpeg62-turbo-dev \
-  liblcms2-dev \
-  liborc-dev \
-  libspng-dev \
-  libtiff-dev \
-  libwebp-dev \
+    autoconf \
+    automake \
+    build-essential \
+    cmake \
+    git \
+    libgdbm-dev \
+    libglib2.0-dev \
+    libgmp-dev \
+    libicu-dev \
+    libidn-dev \
+    libpq-dev \
+    libssl-dev \
+    libtool \
+    libyaml-dev \
+    meson \
+    nasm \
+    pkg-config \
+    shared-mime-info \
+    xz-utils \
+	# libvips components
+    libcgif-dev \
+    libexif-dev \
+    libexpat1-dev \
+    libgirepository1.0-dev \
+    libheif-dev/bookworm-backports \
+    libimagequant-dev \
+    libjpeg62-turbo-dev \
+    liblcms2-dev \
+    liborc-dev \
+    libspng-dev \
+    libtiff-dev \
+    libwebp-dev \
   # ffmpeg components
-  libdav1d-dev \
-  liblzma-dev \
-  libmp3lame-dev \
-  libopus-dev \
-  libsnappy-dev \
-  libvorbis-dev \
-  libvpx-dev \
-  libx264-dev \
-  libx265-dev \
+    libdav1d-dev \
+    liblzma-dev \
+    libmp3lame-dev \
+    libopus-dev \
+    libsnappy-dev \
+    libvorbis-dev \
+    libvpx-dev \
+    libx264-dev \
+    libx265-dev \
   ;
 
+RUN \
+# Configure Corepack
+  rm /usr/local/bin/yarn*; \
+  corepack enable; \
+  corepack prepare --activate;
+
 # Create temporary libvips specific build layer from build layer
 FROM build AS libvips
 
 # libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
 # renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
-ARG VIPS_VERSION=8.16.1
+ARG VIPS_VERSION=8.15.3
 # libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
 ARG VIPS_URL=https://github.com/libvips/libvips/releases/download
 
@@ -209,7 +218,7 @@ FROM build AS ffmpeg
 
 # ffmpeg version to compile, change with [--build-arg FFMPEG_VERSION="7.0.x"]
 # renovate: datasource=repology depName=ffmpeg packageName=openpkg_current/ffmpeg
-ARG FFMPEG_VERSION=7.1
+ARG FFMPEG_VERSION=7.0.2
 # ffmpeg download URL, change with [--build-arg FFMPEG_URL="https://ffmpeg.org/releases"]
 ARG FFMPEG_URL=https://ffmpeg.org/releases
 
@@ -223,28 +232,28 @@ WORKDIR /usr/local/ffmpeg/src/ffmpeg-${FFMPEG_VERSION}
 # Configure and compile ffmpeg
 RUN \
   ./configure \
-  --prefix=/usr/local/ffmpeg \
-  --toolchain=hardened \
-  --disable-debug \
-  --disable-devices \
-  --disable-doc \
-  --disable-ffplay \
-  --disable-network \
-  --disable-static \
-  --enable-ffmpeg \
-  --enable-ffprobe \
-  --enable-gpl \
-  --enable-libdav1d \
-  --enable-libmp3lame \
-  --enable-libopus \
-  --enable-libsnappy \
-  --enable-libvorbis \
-  --enable-libvpx \
-  --enable-libwebp \
-  --enable-libx264 \
-  --enable-libx265 \
-  --enable-shared \
-  --enable-version3 \
+    --prefix=/usr/local/ffmpeg \
+    --toolchain=hardened \
+    --disable-debug \
+    --disable-devices \
+    --disable-doc \
+    --disable-ffplay \
+    --disable-network \
+    --disable-static \
+    --enable-ffmpeg \
+    --enable-ffprobe \
+    --enable-gpl \
+    --enable-libdav1d \
+    --enable-libmp3lame \
+    --enable-libopus \
+    --enable-libsnappy \
+    --enable-libvorbis \
+    --enable-libvpx \
+    --enable-libwebp \
+    --enable-libx264 \
+    --enable-libx265 \
+    --enable-shared \
+    --enable-version3 \
   ; \
   make -j$(nproc); \
   make install;
@@ -258,57 +267,58 @@ ARG TARGETPLATFORM
 COPY Gemfile* /opt/mastodon/
 
 RUN \
-  # Mount Ruby Gem caches
-  --mount=type=cache,id=gem-cache-${TARGETPLATFORM},target=/usr/local/bundle/cache/,sharing=locked \
-  # Configure bundle to prevent changes to Gemfile and Gemfile.lock
+# Mount Ruby Gem caches
+--mount=type=cache,id=gem-cache-${TARGETPLATFORM},target=/usr/local/bundle/cache/,sharing=locked \
+# Configure bundle to prevent changes to Gemfile and Gemfile.lock
   bundle config set --global frozen "true"; \
-  # Configure bundle to not cache downloaded Gems
+# Configure bundle to not cache downloaded Gems
   bundle config set --global cache_all "false"; \
-  # Configure bundle to only process production Gems
+# Configure bundle to only process production Gems
   bundle config set --local without "development test"; \
-  # Configure bundle to not warn about root user
+# Configure bundle to not warn about root user
   bundle config set silence_root_warning "true"; \
-  # Download and install required Gems
+# Download and install required Gems
   bundle install -j"$(nproc)";
 
+# Create temporary node specific build layer from build layer
+FROM build AS yarn
+
+ARG TARGETPLATFORM
+
+# Copy Node package configuration files into working directory
+COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
+COPY streaming/package.json /opt/mastodon/streaming/
+COPY .yarn /opt/mastodon/.yarn
+
+# hadolint ignore=DL3008
+RUN \
+--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
+--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
+# Install Node packages
+  yarn workspaces focus --production @mastodon/mastodon;
+
 # Create temporary assets build layer from build layer
 FROM build AS precompiler
 
-ARG TARGETPLATFORM
-
-# Copy Mastodon sources into layer
+# Copy Mastodon sources into precompiler layer
 COPY . /opt/mastodon/
 
-# Copy Node.js binaries/libraries into layer
-COPY --from=node /usr/local/bin /usr/local/bin
-COPY --from=node /usr/local/lib /usr/local/lib
-
-RUN \
-  # Configure Corepack
-  rm /usr/local/bin/yarn*; \
-  corepack enable; \
-  corepack prepare --activate;
-
-# hadolint ignore=DL3008
-RUN \
-  --mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
-  --mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
-  # Install Node.js packages
-  yarn workspaces focus --production @mastodon/mastodon;
-
-# Copy libvips components into layer for precompiler
-COPY --from=libvips /usr/local/libvips/bin /usr/local/bin
-COPY --from=libvips /usr/local/libvips/lib /usr/local/lib
-# Copy bundler packages into layer for precompiler
+# Copy bundler and node packages from build layer to container
+COPY --from=yarn /opt/mastodon /opt/mastodon/
 COPY --from=bundler /opt/mastodon /opt/mastodon/
 COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
+# Copy libvips components to layer for precompiler
+COPY --from=libvips /usr/local/libvips/bin /usr/local/bin
+COPY --from=libvips /usr/local/libvips/lib /usr/local/lib
+
+ARG TARGETPLATFORM
 
 RUN \
   ldconfig; \
-  # Use Ruby on Rails to create Mastodon assets
+# Use Ruby on Rails to create Mastodon assets
   SECRET_KEY_BASE_DUMMY=1 \
   bundle exec rails assets:precompile; \
-  # Cleanup temporary files
+# Cleanup temporary files
   rm -fr /opt/mastodon/tmp;
 
 # Prep final Mastodon Ruby layer
@@ -318,49 +328,49 @@ ARG TARGETPLATFORM
 
 # hadolint ignore=DL3008
 RUN \
-  # Mount Apt cache and lib directories from Docker buildx caches
-  --mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
-  --mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
-  # Mount Corepack and Yarn caches from Docker buildx caches
-  --mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
-  --mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
-  # Apt update install non-dev versions of necessary components
+# Mount Apt cache and lib directories from Docker buildx caches
+--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
+--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
+# Mount Corepack and Yarn caches from Docker buildx caches
+--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
+--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
+# Apt update install non-dev versions of necessary components
   apt-get install -y --no-install-recommends \
-  libexpat1 \
-  libglib2.0-0 \
-  libicu72 \
-  libidn12 \
-  libpq5 \
-  libreadline8 \
-  libssl3 \
-  libyaml-0-2 \
+    libexpat1 \
+    libglib2.0-0 \
+    libicu72 \
+    libidn12 \
+    libpq5 \
+    libreadline8 \
+    libssl3 \
+    libyaml-0-2 \
   # libvips components
-  libcgif0 \
-  libexif12 \
-  libheif1/bookworm-backports \
-  libimagequant0 \
-  libjpeg62-turbo \
-  liblcms2-2 \
-  liborc-0.4-0 \
-  libspng0 \
-  libtiff6 \
-  libwebp7 \
-  libwebpdemux2 \
-  libwebpmux3 \
+    libcgif0 \
+    libexif12 \
+    libheif1/bookworm-backports \
+    libimagequant0 \
+    libjpeg62-turbo \
+    liblcms2-2 \
+    liborc-0.4-0 \
+    libspng0 \
+    libtiff6 \
+    libwebp7 \
+    libwebpdemux2 \
+    libwebpmux3 \
   # ffmpeg components
-  libdav1d6 \
-  libmp3lame0 \
-  libopencore-amrnb0 \
-  libopencore-amrwb0 \
-  libopus0 \
-  libsnappy1v5 \
-  libtheora0 \
-  libvorbis0a \
-  libvorbisenc2 \
-  libvorbisfile3 \
-  libvpx7 \
-  libx264-164 \
-  libx265-199 \
+    libdav1d6 \
+    libmp3lame0 \
+    libopencore-amrnb0 \
+    libopencore-amrwb0 \
+    libopus0 \
+    libsnappy1v5 \
+    libtheora0 \
+    libvorbis0a \
+    libvorbisenc2 \
+    libvorbisfile3 \
+    libvpx7 \
+    libx264-164 \
+    libx265-199 \
   ;
 
 # Copy Mastodon sources into final layer
@@ -380,7 +390,7 @@ COPY --from=ffmpeg /usr/local/ffmpeg/lib /usr/local/lib
 
 RUN \
   ldconfig; \
-  # Smoketest media processors
+# Smoketest media processors
   vips -v; \
   ffmpeg -version; \
   ffprobe -version;
@@ -390,10 +400,10 @@ RUN \
   bundle exec bootsnap precompile --gemfile app/ lib/;
 
 RUN \
-  # Pre-create and chown system volume to Mastodon user
+# Pre-create and chown system volume to Mastodon user
   mkdir -p /opt/mastodon/public/system; \
   chown mastodon:mastodon /opt/mastodon/public/system; \
-  # Set Mastodon user as owner of tmp folder
+# Set Mastodon user as owner of tmp folder
   chown -R mastodon:mastodon /opt/mastodon/tmp;
 
 # Set the running user for resulting container
diff --git a/Gemfile b/Gemfile
index 9e5955e0b8..bcb19421ab 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,12 +1,12 @@
 # frozen_string_literal: true
 
 source 'https://rubygems.org'
-ruby '>= 3.2.0', '< 3.5.0'
+ruby '>= 3.1.0'
 
 gem 'propshaft'
 gem 'puma', '~> 6.3'
 gem 'rack', '~> 2.2.7'
-gem 'rails', '~> 8.0'
+gem 'rails', '~> 7.1.1'
 gem 'thor', '~> 1.2'
 
 gem 'dotenv'
@@ -14,19 +14,18 @@ gem 'haml-rails', '~>2.0'
 gem 'pg', '~> 1.5'
 gem 'pghero'
 
-gem 'aws-sdk-core', '< 3.216.0', require: false # TODO: https://github.com/mastodon/mastodon/pull/34173#issuecomment-2733378873
 gem 'aws-sdk-s3', '~> 1.123', require: false
 gem 'blurhash', '~> 0.1'
-gem 'fog-core', '<= 2.6.0'
+gem 'fog-core', '<= 2.5.0'
 gem 'fog-openstack', '~> 1.0', require: false
-gem 'jd-paperclip-azure', '~> 3.0', require: false
 gem 'kt-paperclip', '~> 7.2'
+gem 'md-paperclip-azure', '~> 2.2', require: false
 gem 'ruby-vips', '~> 2.2', require: false
 
 gem 'active_model_serializers', '~> 0.10'
 gem 'addressable', '~> 2.8'
 gem 'bootsnap', '~> 1.18.0', require: false
-gem 'browser'
+gem 'browser', '< 6' # https://github.com/fnando/browser/issues/543
 gem 'charlock_holmes', '~> 0.7.7'
 gem 'chewy', '~> 7.3'
 gem 'devise', '~> 4.9'
@@ -40,7 +39,7 @@ gem 'net-ldap', '~> 0.18'
 
 gem 'omniauth', '~> 2.0'
 gem 'omniauth-cas', '~> 3.0.0.beta.1'
-gem 'omniauth_openid_connect', '~> 0.8.0'
+gem 'omniauth_openid_connect', '~> 0.6.1'
 gem 'omniauth-rails_csrf_protection', '~> 1.0'
 gem 'omniauth-saml', '~> 2.0'
 
@@ -48,24 +47,21 @@ gem 'color_diff', '~> 0.1'
 gem 'csv', '~> 3.2'
 gem 'discard', '~> 1.2'
 gem 'doorkeeper', '~> 5.6'
-gem 'faraday-httpclient'
 gem 'fast_blank', '~> 1.0'
 gem 'fastimage'
 gem 'hiredis', '~> 0.6'
 gem 'htmlentities', '~> 4.3'
 gem 'http', '~> 5.2.0'
 gem 'http_accept_language', '~> 2.1'
-gem 'httplog', '~> 1.7.0', require: false
+gem 'httplog', '~> 1.7.0'
 gem 'i18n'
 gem 'idn-ruby', require: 'idn'
 gem 'inline_svg'
 gem 'irb', '~> 1.8'
 gem 'kaminari', '~> 1.2'
 gem 'link_header', '~> 0.0'
-gem 'linzer', '~> 0.6.1'
 gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
-gem 'mime-types', '~> 3.6.0', require: 'mime/types/columnar'
-gem 'mutex_m'
+gem 'mime-types', '~> 3.5.0', require: 'mime/types/columnar'
 gem 'nokogiri', '~> 1.15'
 gem 'oj', '~> 3.14'
 gem 'ox', '~> 2.14'
@@ -75,13 +71,13 @@ gem 'public_suffix', '~> 6.0'
 gem 'pundit', '~> 2.3'
 gem 'rack-attack', '~> 6.6'
 gem 'rack-cors', '~> 2.0', require: 'rack/cors'
-gem 'rails-i18n', '~> 8.0'
+gem 'rails-i18n', '~> 7.0'
 gem 'redcarpet', '~> 3.6'
 gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
 gem 'redis-namespace', '~> 1.10'
 gem 'rqrcode', '~> 2.2'
 gem 'ruby-progressbar', '~> 1.13'
-gem 'sanitize', '~> 7.0'
+gem 'sanitize', '~> 6.0'
 gem 'scenic', '~> 1.7'
 gem 'sidekiq', '~> 6.5'
 gem 'sidekiq-bulk', '~> 0.2.0'
@@ -96,31 +92,29 @@ gem 'twitter-text', '~> 3.1.0'
 gem 'tzinfo-data', '~> 1.2023'
 gem 'webauthn', '~> 3.0'
 gem 'webpacker', '~> 5.4'
-gem 'webpush', github: 'mastodon/webpush', ref: '9631ac63045cfabddacc69fc06e919b4c13eb913'
+gem 'webpush', github: 'ClearlyClaire/webpush', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9'
 
 gem 'json-ld'
 gem 'json-ld-preloaded', '~> 3.2'
 gem 'rdf-normalize', '~> 0.5'
 
-gem 'prometheus_exporter', '~> 2.2', require: false
-
-gem 'opentelemetry-api', '~> 1.5.0'
+gem 'opentelemetry-api', '~> 1.4.0'
 
 group :opentelemetry do
-  gem 'opentelemetry-exporter-otlp', '~> 0.30.0', require: false
-  gem 'opentelemetry-instrumentation-active_job', '~> 0.8.0', require: false
-  gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.22.0', require: false
-  gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.22.0', require: false
-  gem 'opentelemetry-instrumentation-excon', '~> 0.23.0', require: false
-  gem 'opentelemetry-instrumentation-faraday', '~> 0.26.0', require: false
-  gem 'opentelemetry-instrumentation-http', '~> 0.24.0', require: false
-  gem 'opentelemetry-instrumentation-http_client', '~> 0.23.0', require: false
-  gem 'opentelemetry-instrumentation-net_http', '~> 0.23.0', require: false
-  gem 'opentelemetry-instrumentation-pg', '~> 0.30.0', require: false
-  gem 'opentelemetry-instrumentation-rack', '~> 0.26.0', require: false
-  gem 'opentelemetry-instrumentation-rails', '~> 0.36.0', require: false
-  gem 'opentelemetry-instrumentation-redis', '~> 0.26.0', require: false
-  gem 'opentelemetry-instrumentation-sidekiq', '~> 0.26.0', require: false
+  gem 'opentelemetry-exporter-otlp', '~> 0.29.0', require: false
+  gem 'opentelemetry-instrumentation-active_job', '~> 0.7.1', require: false
+  gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.20.1', require: false
+  gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false
+  gem 'opentelemetry-instrumentation-excon', '~> 0.22.0', require: false
+  gem 'opentelemetry-instrumentation-faraday', '~> 0.24.1', require: false
+  gem 'opentelemetry-instrumentation-http', '~> 0.23.2', require: false
+  gem 'opentelemetry-instrumentation-http_client', '~> 0.22.3', require: false
+  gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false
+  gem 'opentelemetry-instrumentation-pg', '~> 0.29.0', require: false
+  gem 'opentelemetry-instrumentation-rack', '~> 0.24.1', require: false
+  gem 'opentelemetry-instrumentation-rails', '~> 0.31.0', require: false
+  gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false
+  gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false
   gem 'opentelemetry-sdk', '~> 1.4', require: false
 end
 
@@ -129,7 +123,7 @@ group :test do
   gem 'flatware-rspec'
 
   # Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab
-  gem 'rspec-github', '~> 3.0', require: false
+  gem 'rspec-github', '~> 2.4', require: false
 
   # RSpec helpers for email specs
   gem 'email_spec'
@@ -147,6 +141,9 @@ group :test do
   # Used to mock environment variables
   gem 'climate_control'
 
+  # Add back helpers functions removed in Rails 5.1
+  gem 'rails-controller-testing', '~> 1.0'
+
   # Validate schemas in specs
   gem 'json-schema', '~> 5.0'
 
@@ -155,7 +152,7 @@ group :test do
 
   gem 'shoulda-matchers'
 
-  # Coverage formatter for RSpec
+  # Coverage formatter for RSpec test if DISABLE_SIMPLECOV is false
   gem 'simplecov', '~> 0.22', require: false
   gem 'simplecov-lcov', '~> 0.8', require: false
 
@@ -167,14 +164,13 @@ group :development do
   # Code linting CLI and plugins
   gem 'rubocop', require: false
   gem 'rubocop-capybara', require: false
-  gem 'rubocop-i18n', require: false
   gem 'rubocop-performance', require: false
   gem 'rubocop-rails', require: false
   gem 'rubocop-rspec', require: false
   gem 'rubocop-rspec_rails', require: false
 
   # Annotates modules with schema
-  gem 'annotaterb', '~> 4.13', require: false
+  gem 'annotate', '~> 3.2'
 
   # Enhanced error message pages for development
   gem 'better_errors', '~> 2.9'
@@ -185,7 +181,7 @@ group :development do
   gem 'letter_opener_web', '~> 3.0'
 
   # Security analysis CLI tools
-  gem 'brakeman', '~> 7.0', require: false
+  gem 'brakeman', '~> 6.0', require: false
   gem 'bundler-audit', '~> 0.9', require: false
 
   # Linter CLI for HAML files
@@ -197,7 +193,7 @@ end
 
 group :development, :test do
   # Interactive Debugging tools
-  gem 'debug', '~> 1.8', require: false
+  gem 'debug', '~> 1.8'
 
   # Generate fake data values
   gem 'faker', '~> 3.2'
@@ -209,7 +205,7 @@ group :development, :test do
   gem 'memory_profiler', require: false
   gem 'ruby-prof', require: false
   gem 'stackprof', require: false
-  gem 'test-prof', require: false
+  gem 'test-prof'
 
   # RSpec runner for rails
   gem 'rspec-rails', '~> 7.0'
@@ -224,7 +220,7 @@ gem 'concurrent-ruby', require: false
 gem 'connection_pool', require: false
 gem 'xorcist', '~> 1.1'
 
-gem 'net-http', '~> 0.6.0'
+gem 'net-http', '~> 0.4.0'
 gem 'rubyzip', '~> 2.3'
 
 gem 'hcaptcha', '~> 7.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index f13df0c43f..cdd6b15dcf 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,116 +1,131 @@
 GIT
-  remote: https://github.com/mastodon/webpush.git
-  revision: 9631ac63045cfabddacc69fc06e919b4c13eb913
-  ref: 9631ac63045cfabddacc69fc06e919b4c13eb913
+  remote: https://github.com/ClearlyClaire/webpush.git
+  revision: f14a4d52e201128b1b00245d11b6de80d6cfdcd9
+  ref: f14a4d52e201128b1b00245d11b6de80d6cfdcd9
   specs:
-    webpush (1.1.0)
+    webpush (0.3.8)
       hkdf (~> 0.2)
       jwt (~> 2.0)
 
 GEM
   remote: https://rubygems.org/
   specs:
-    actioncable (8.0.2)
-      actionpack (= 8.0.2)
-      activesupport (= 8.0.2)
+    actioncable (7.1.5.1)
+      actionpack (= 7.1.5.1)
+      activesupport (= 7.1.5.1)
       nio4r (~> 2.0)
       websocket-driver (>= 0.6.1)
       zeitwerk (~> 2.6)
-    actionmailbox (8.0.2)
-      actionpack (= 8.0.2)
-      activejob (= 8.0.2)
-      activerecord (= 8.0.2)
-      activestorage (= 8.0.2)
-      activesupport (= 8.0.2)
-      mail (>= 2.8.0)
-    actionmailer (8.0.2)
-      actionpack (= 8.0.2)
-      actionview (= 8.0.2)
-      activejob (= 8.0.2)
-      activesupport (= 8.0.2)
-      mail (>= 2.8.0)
+    actionmailbox (7.1.5.1)
+      actionpack (= 7.1.5.1)
+      activejob (= 7.1.5.1)
+      activerecord (= 7.1.5.1)
+      activestorage (= 7.1.5.1)
+      activesupport (= 7.1.5.1)
+      mail (>= 2.7.1)
+      net-imap
+      net-pop
+      net-smtp
+    actionmailer (7.1.5.1)
+      actionpack (= 7.1.5.1)
+      actionview (= 7.1.5.1)
+      activejob (= 7.1.5.1)
+      activesupport (= 7.1.5.1)
+      mail (~> 2.5, >= 2.5.4)
+      net-imap
+      net-pop
+      net-smtp
       rails-dom-testing (~> 2.2)
-    actionpack (8.0.2)
-      actionview (= 8.0.2)
-      activesupport (= 8.0.2)
+    actionpack (7.1.5.1)
+      actionview (= 7.1.5.1)
+      activesupport (= 7.1.5.1)
       nokogiri (>= 1.8.5)
+      racc
       rack (>= 2.2.4)
       rack-session (>= 1.0.1)
       rack-test (>= 0.6.3)
       rails-dom-testing (~> 2.2)
       rails-html-sanitizer (~> 1.6)
-      useragent (~> 0.16)
-    actiontext (8.0.2)
-      actionpack (= 8.0.2)
-      activerecord (= 8.0.2)
-      activestorage (= 8.0.2)
-      activesupport (= 8.0.2)
+    actiontext (7.1.5.1)
+      actionpack (= 7.1.5.1)
+      activerecord (= 7.1.5.1)
+      activestorage (= 7.1.5.1)
+      activesupport (= 7.1.5.1)
       globalid (>= 0.6.0)
       nokogiri (>= 1.8.5)
-    actionview (8.0.2)
-      activesupport (= 8.0.2)
+    actionview (7.1.5.1)
+      activesupport (= 7.1.5.1)
       builder (~> 3.1)
       erubi (~> 1.11)
       rails-dom-testing (~> 2.2)
       rails-html-sanitizer (~> 1.6)
-    active_model_serializers (0.10.15)
+    active_model_serializers (0.10.14)
       actionpack (>= 4.1)
       activemodel (>= 4.1)
       case_transform (>= 0.2)
       jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
-    activejob (8.0.2)
-      activesupport (= 8.0.2)
+    activejob (7.1.5.1)
+      activesupport (= 7.1.5.1)
       globalid (>= 0.3.6)
-    activemodel (8.0.2)
-      activesupport (= 8.0.2)
-    activerecord (8.0.2)
-      activemodel (= 8.0.2)
-      activesupport (= 8.0.2)
+    activemodel (7.1.5.1)
+      activesupport (= 7.1.5.1)
+    activerecord (7.1.5.1)
+      activemodel (= 7.1.5.1)
+      activesupport (= 7.1.5.1)
       timeout (>= 0.4.0)
-    activestorage (8.0.2)
-      actionpack (= 8.0.2)
-      activejob (= 8.0.2)
-      activerecord (= 8.0.2)
-      activesupport (= 8.0.2)
+    activestorage (7.1.5.1)
+      actionpack (= 7.1.5.1)
+      activejob (= 7.1.5.1)
+      activerecord (= 7.1.5.1)
+      activesupport (= 7.1.5.1)
       marcel (~> 1.0)
-    activesupport (8.0.2)
+    activesupport (7.1.5.1)
       base64
       benchmark (>= 0.3)
       bigdecimal
-      concurrent-ruby (~> 1.0, >= 1.3.1)
+      concurrent-ruby (~> 1.0, >= 1.0.2)
       connection_pool (>= 2.2.5)
       drb
       i18n (>= 1.6, < 2)
       logger (>= 1.4.2)
       minitest (>= 5.1)
+      mutex_m
       securerandom (>= 0.3)
-      tzinfo (~> 2.0, >= 2.0.5)
-      uri (>= 0.13.1)
+      tzinfo (~> 2.0)
     addressable (2.8.7)
       public_suffix (>= 2.0.2, < 7.0)
     aes_key_wrap (1.1.0)
     android_key_attestation (0.3.0)
-    annotaterb (4.14.0)
-    ast (2.4.3)
+    annotate (3.2.0)
+      activerecord (>= 3.2, < 8.0)
+      rake (>= 10.4, < 14.0)
+    ast (2.4.2)
     attr_required (1.0.2)
-    aws-eventstream (1.3.2)
-    aws-partitions (1.1087.0)
-    aws-sdk-core (3.215.1)
+    awrence (1.2.1)
+    aws-eventstream (1.3.0)
+    aws-partitions (1.978.0)
+    aws-sdk-core (3.209.0)
       aws-eventstream (~> 1, >= 1.3.0)
-      aws-partitions (~> 1, >= 1.992.0)
+      aws-partitions (~> 1, >= 1.651.0)
       aws-sigv4 (~> 1.9)
       jmespath (~> 1, >= 1.6.1)
-    aws-sdk-kms (1.96.0)
-      aws-sdk-core (~> 3, >= 3.210.0)
+    aws-sdk-kms (1.94.0)
+      aws-sdk-core (~> 3, >= 3.207.0)
       aws-sigv4 (~> 1.5)
-    aws-sdk-s3 (1.177.0)
-      aws-sdk-core (~> 3, >= 3.210.0)
+    aws-sdk-s3 (1.166.0)
+      aws-sdk-core (~> 3, >= 3.207.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.5)
-    aws-sigv4 (1.11.0)
+    aws-sigv4 (1.10.0)
       aws-eventstream (~> 1, >= 1.0.2)
-    azure-blob (0.5.7)
-      rexml
+    azure-storage-blob (2.0.3)
+      azure-storage-common (~> 2.0)
+      nokogiri (~> 1, >= 1.10.8)
+    azure-storage-common (2.0.4)
+      faraday (~> 1.0)
+      faraday_middleware (~> 1.0, >= 1.0.0.rc1)
+      net-http-persistent (~> 4.0)
+      nokogiri (~> 1, >= 1.10.8)
     base64 (0.2.0)
     bcp47_spec (0.2.1)
     bcrypt (3.1.20)
@@ -119,16 +134,16 @@ GEM
       erubi (>= 1.0.0)
       rack (>= 0.9.0)
       rouge (>= 1.0.0)
-    bigdecimal (3.1.9)
-    bindata (2.5.1)
+    bigdecimal (3.1.8)
+    bindata (2.5.0)
     binding_of_caller (1.0.1)
       debug_inspector (>= 1.2.0)
     blurhash (0.1.8)
     bootsnap (1.18.4)
       msgpack (~> 1.2)
-    brakeman (7.0.2)
+    brakeman (6.2.1)
       racc
-    browser (6.2.0)
+    browser (5.3.1)
     brpoplpush-redis_script (0.1.3)
       concurrent-ruby (~> 1.0, >= 1.0.5)
       redis (>= 1.0, < 6)
@@ -159,8 +174,8 @@ GEM
     climate_control (1.2.0)
     cocoon (1.2.15)
     color_diff (0.1)
-    concurrent-ruby (1.3.5)
-    connection_pool (2.5.0)
+    concurrent-ruby (1.3.4)
+    connection_pool (2.4.1)
     cose (1.3.1)
       cbor (~> 0.5.9)
       openssl-signature_algorithm (~> 1.0)
@@ -168,15 +183,15 @@ GEM
       bigdecimal
       rexml
     crass (1.0.6)
-    css_parser (1.21.1)
+    css_parser (1.19.0)
       addressable
-    csv (3.3.4)
+    csv (3.3.0)
     database_cleaner-active_record (2.2.0)
       activerecord (>= 5.a)
       database_cleaner-core (~> 2.0.0)
     database_cleaner-core (2.0.1)
-    date (3.4.1)
-    debug (1.10.0)
+    date (3.3.4)
+    debug (1.9.2)
       irb (~> 1.10)
       reline (>= 0.3.8)
     debug_inspector (1.2.0)
@@ -186,22 +201,22 @@ GEM
       railties (>= 4.1.0)
       responders
       warden (~> 1.2.3)
-    devise-two-factor (6.1.0)
-      activesupport (>= 7.0, < 8.1)
+    devise-two-factor (6.0.0)
+      activesupport (~> 7.0)
       devise (~> 4.0)
-      railties (>= 7.0, < 8.1)
+      railties (~> 7.0)
       rotp (~> 6.0)
     devise_pam_authenticatable2 (9.2.0)
       devise (>= 4.0.0)
       rpam2 (~> 4.0)
-    diff-lcs (1.6.1)
-    discard (1.4.0)
-      activerecord (>= 4.2, < 9.0)
+    diff-lcs (1.5.1)
+    discard (1.3.0)
+      activerecord (>= 4.2, < 8)
     docile (1.4.1)
     domain_name (0.6.20240107)
-    doorkeeper (5.8.2)
+    doorkeeper (5.7.1)
       railties (>= 5)
-    dotenv (3.1.8)
+    dotenv (3.1.4)
     drb (2.2.1)
     elasticsearch (7.17.11)
       elasticsearch-api (= 7.17.11)
@@ -217,29 +232,41 @@ GEM
       htmlentities (~> 4.3.3)
       launchy (>= 2.1, < 4.0)
       mail (~> 2.7)
-    email_validator (2.2.4)
-      activemodel
-    erubi (1.13.1)
+    erubi (1.13.0)
     et-orbi (1.2.11)
       tzinfo
-    excon (1.2.5)
-      logger
+    excon (0.111.0)
     fabrication (2.31.0)
-    faker (3.5.1)
+    faker (3.4.2)
       i18n (>= 1.8.11, < 2)
-    faraday (2.13.0)
-      faraday-net_http (>= 2.0, < 3.5)
-      json
-      logger
-    faraday-follow_redirects (0.3.0)
-      faraday (>= 1, < 3)
-    faraday-httpclient (2.0.1)
-      httpclient (>= 2.2)
-    faraday-net_http (3.4.0)
-      net-http (>= 0.5.0)
+    faraday (1.10.3)
+      faraday-em_http (~> 1.0)
+      faraday-em_synchrony (~> 1.0)
+      faraday-excon (~> 1.1)
+      faraday-httpclient (~> 1.0)
+      faraday-multipart (~> 1.0)
+      faraday-net_http (~> 1.0)
+      faraday-net_http_persistent (~> 1.0)
+      faraday-patron (~> 1.0)
+      faraday-rack (~> 1.0)
+      faraday-retry (~> 1.0)
+      ruby2_keywords (>= 0.0.4)
+    faraday-em_http (1.0.0)
+    faraday-em_synchrony (1.0.0)
+    faraday-excon (1.1.0)
+    faraday-httpclient (1.0.1)
+    faraday-multipart (1.0.4)
+      multipart-post (~> 2)
+    faraday-net_http (1.0.2)
+    faraday-net_http_persistent (1.2.0)
+    faraday-patron (1.0.0)
+    faraday-rack (1.0.0)
+    faraday-retry (1.0.3)
+    faraday_middleware (1.2.0)
+      faraday (~> 1.0)
     fast_blank (1.0.1)
-    fastimage (2.4.0)
-    ffi (1.17.2)
+    fastimage (2.3.1)
+    ffi (1.17.1)
     ffi-compiler (1.3.2)
       ffi (>= 1.15.5)
       rake
@@ -249,15 +276,15 @@ GEM
     flatware-rspec (2.3.4)
       flatware (= 2.3.4)
       rspec (>= 3.6)
-    fog-core (2.6.0)
+    fog-core (2.5.0)
       builder
-      excon (~> 1.0)
+      excon (~> 0.71)
       formatador (>= 0.2, < 2.0)
       mime-types
     fog-json (1.2.0)
       fog-core
       multi_json (~> 1.10)
-    fog-openstack (1.1.5)
+    fog-openstack (1.1.3)
       fog-core (~> 2.1)
       fog-json (>= 1.0)
     formatador (1.1.0)
@@ -266,10 +293,8 @@ GEM
       raabro (~> 1.4)
     globalid (1.2.1)
       activesupport (>= 6.1)
-    google-protobuf (4.30.2)
-      bigdecimal
-      rake (>= 13)
-    googleapis-common-protos-types (1.19.0)
+    google-protobuf (3.25.5)
+    googleapis-common-protos-types (1.15.0)
       google-protobuf (>= 3.18, < 5.a)
     haml (6.3.0)
       temple (>= 0.8.2)
@@ -280,17 +305,17 @@ GEM
       activesupport (>= 5.1)
       haml (>= 4.0.6)
       railties (>= 5.1)
-    haml_lint (0.62.0)
+    haml_lint (0.58.0)
       haml (>= 5.0)
       parallel (~> 1.10)
       rainbow
       rubocop (>= 1.0)
       sysexits (~> 1.1)
-    hashdiff (1.1.2)
+    hashdiff (1.1.1)
     hashie (5.0.0)
     hcaptcha (7.1.0)
       json
-    highline (3.1.2)
+    highline (3.1.1)
       reline
     hiredis (0.6.3)
     hkdf (0.3.0)
@@ -301,18 +326,17 @@ GEM
       http-cookie (~> 1.0)
       http-form_data (~> 2.2)
       llhttp-ffi (~> 0.5.0)
-    http-cookie (1.0.8)
+    http-cookie (1.0.5)
       domain_name (~> 0.5)
     http-form_data (2.3.0)
     http_accept_language (2.1.1)
-    httpclient (2.9.0)
-      mutex_m
+    httpclient (2.8.3)
     httplog (1.7.0)
       rack (>= 2.0)
       rainbow (>= 2.0.0)
-    i18n (1.14.7)
+    i18n (1.14.6)
       concurrent-ruby (~> 1.0)
-    i18n-tasks (1.0.15)
+    i18n-tasks (1.0.14)
       activesupport (>= 4.0.2)
       ast (>= 2.1.0)
       erubi
@@ -321,31 +345,23 @@ GEM
       parser (>= 3.2.2.1)
       rails-i18n
       rainbow (>= 2.2.2, < 4.0)
-      ruby-progressbar (~> 1.8, >= 1.8.1)
       terminal-table (>= 1.5.1)
     idn-ruby (0.1.5)
     inline_svg (1.10.0)
       activesupport (>= 3.0)
       nokogiri (>= 1.6)
-    io-console (0.8.0)
-    irb (1.15.2)
-      pp (>= 0.6.0)
+    io-console (0.7.2)
+    irb (1.14.1)
       rdoc (>= 4.0.0)
       reline (>= 0.4.2)
-    jd-paperclip-azure (3.0.0)
-      addressable (~> 2.5)
-      azure-blob (~> 0.5.2)
-      hashie (~> 5.0)
     jmespath (1.6.2)
-    json (2.10.2)
+    json (2.7.2)
     json-canonicalization (1.0.0)
-    json-jwt (1.16.7)
+    json-jwt (1.15.3.1)
       activesupport (>= 4.2)
       aes_key_wrap
-      base64
       bindata
-      faraday (~> 2.0)
-      faraday-follow_redirects
+      httpclient
     json-ld (3.3.2)
       htmlentities (~> 4.3)
       json-canonicalization (~> 1.0)
@@ -354,15 +370,13 @@ GEM
       rack (>= 2.2, < 4)
       rdf (~> 3.3)
       rexml (~> 3.2)
-    json-ld-preloaded (3.3.1)
+    json-ld-preloaded (3.3.0)
       json-ld (~> 3.3)
       rdf (~> 3.3)
-    json-schema (5.1.1)
+    json-schema (5.0.0)
       addressable (~> 2.8)
-      bigdecimal (~> 3.1)
     jsonapi-renderer (0.2.2)
-    jwt (2.10.1)
-      base64
+    jwt (2.7.1)
     kaminari (1.2.2)
       activesupport (>= 4.1.0)
       kaminari-actionview (= 1.2.2)
@@ -381,11 +395,10 @@ GEM
       marcel (~> 1.0.1)
       mime-types
       terrapin (>= 0.6.0, < 2.0)
-    language_server-protocol (3.17.0.4)
-    launchy (3.1.1)
+    language_server-protocol (3.17.0.3)
+    launchy (3.0.1)
       addressable (~> 2.8)
       childprocess (~> 5.0)
-      logger (~> 1.6)
     letter_opener (1.10.0)
       launchy (>= 2.2, < 4)
     letter_opener_web (3.0.0)
@@ -394,23 +407,16 @@ GEM
       railties (>= 6.1)
       rexml
     link_header (0.0.8)
-    lint_roller (1.1.0)
-    linzer (0.6.5)
-      openssl (~> 3.0, >= 3.0.0)
-      rack (>= 2.2, < 4.0)
-      starry (~> 0.2)
-      stringio (~> 3.1, >= 3.1.2)
-      uri (~> 1.0, >= 1.0.2)
-    llhttp-ffi (0.5.1)
+    llhttp-ffi (0.5.0)
       ffi-compiler (~> 1.0)
       rake (~> 13.0)
-    logger (1.7.0)
+    logger (1.6.6)
     lograge (0.14.0)
       actionpack (>= 4)
       activesupport (>= 4)
       railties (>= 4)
       request_store (~> 1.0)
-    loofah (2.24.0)
+    loofah (2.22.0)
       crass (~> 1.0.2)
       nokogiri (>= 1.12.0)
     mail (2.8.1)
@@ -422,20 +428,26 @@ GEM
     mario-redis-lock (1.2.1)
       redis (>= 3.0.5)
     matrix (0.4.2)
+    md-paperclip-azure (2.2.0)
+      addressable (~> 2.5)
+      azure-storage-blob (~> 2.0.1)
+      hashie (~> 5.0)
     memory_profiler (1.1.0)
-    mime-types (3.6.2)
-      logger
+    mime-types (3.5.2)
       mime-types-data (~> 3.2015)
-    mime-types-data (3.2025.0408)
+    mime-types-data (3.2024.0820)
     mini_mime (1.1.5)
     mini_portile2 (2.8.8)
-    minitest (5.25.5)
-    msgpack (1.8.0)
+    minitest (5.25.1)
+    msgpack (1.7.2)
     multi_json (1.15.0)
-    mutex_m (0.3.0)
-    net-http (0.6.0)
+    multipart-post (2.4.1)
+    mutex_m (0.2.0)
+    net-http (0.4.1)
       uri
-    net-imap (0.5.6)
+    net-http-persistent (4.0.2)
+      connection_pool (~> 2.2)
+    net-imap (0.4.19)
       date
       net-protocol
     net-ldap (0.19.0)
@@ -445,18 +457,18 @@ GEM
       timeout
     net-smtp (0.5.1)
       net-protocol
-    nio4r (2.7.4)
-    nokogiri (1.18.7)
+    nio4r (2.7.3)
+    nokogiri (1.18.3)
       mini_portile2 (~> 2.8.2)
       racc (~> 1.4)
-    oj (3.16.10)
+    oj (3.16.6)
       bigdecimal (>= 3.0)
       ostruct (>= 0.2)
     omniauth (2.1.3)
       hashie (>= 3.4.6)
       rack (>= 2.2.3)
       rack-protection
-    omniauth-cas (3.0.1)
+    omniauth-cas (3.0.0)
       addressable (~> 2.8)
       nokogiri (~> 1.12)
       omniauth (~> 2.1)
@@ -466,137 +478,125 @@ GEM
     omniauth-saml (2.2.3)
       omniauth (~> 2.1)
       ruby-saml (~> 1.18)
-    omniauth_openid_connect (0.8.0)
+    omniauth_openid_connect (0.6.1)
       omniauth (>= 1.9, < 3)
-      openid_connect (~> 2.2)
-    openid_connect (2.3.1)
+      openid_connect (~> 1.1)
+    openid_connect (1.4.2)
       activemodel
       attr_required (>= 1.0.0)
-      email_validator
-      faraday (~> 2.0)
-      faraday-follow_redirects
-      json-jwt (>= 1.16)
-      mail
-      rack-oauth2 (~> 2.2)
-      swd (~> 2.0)
+      json-jwt (>= 1.15.0)
+      net-smtp
+      rack-oauth2 (~> 1.21)
+      swd (~> 1.3)
       tzinfo
+      validate_email
       validate_url
-      webfinger (~> 2.0)
-    openssl (3.3.0)
+      webfinger (~> 1.2)
+    openssl (3.2.0)
     openssl-signature_algorithm (1.3.0)
       openssl (> 2.0)
-    opentelemetry-api (1.5.0)
-    opentelemetry-common (0.22.0)
+    opentelemetry-api (1.4.0)
+    opentelemetry-common (0.21.0)
       opentelemetry-api (~> 1.0)
-    opentelemetry-exporter-otlp (0.30.0)
+    opentelemetry-exporter-otlp (0.29.0)
       google-protobuf (>= 3.18)
       googleapis-common-protos-types (~> 1.3)
       opentelemetry-api (~> 1.1)
       opentelemetry-common (~> 0.20)
       opentelemetry-sdk (~> 1.2)
       opentelemetry-semantic_conventions
-    opentelemetry-helpers-sql-obfuscation (0.3.0)
+    opentelemetry-helpers-sql-obfuscation (0.2.0)
       opentelemetry-common (~> 0.21)
-    opentelemetry-instrumentation-action_mailer (0.4.0)
+    opentelemetry-instrumentation-action_mailer (0.1.0)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-active_support (~> 0.7)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-action_pack (0.12.0)
+      opentelemetry-instrumentation-active_support (~> 0.1)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-action_pack (0.9.0)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
       opentelemetry-instrumentation-rack (~> 0.21)
-    opentelemetry-instrumentation-action_view (0.9.0)
+    opentelemetry-instrumentation-action_view (0.7.2)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-active_support (~> 0.7)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-active_job (0.8.0)
+      opentelemetry-instrumentation-active_support (~> 0.1)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-active_job (0.7.7)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-active_model_serializers (0.22.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-active_model_serializers (0.20.2)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-active_support (>= 0.7.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-active_record (0.9.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-active_record (0.7.3)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-active_storage (0.1.1)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-active_support (0.6.0)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-active_support (~> 0.7)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-active_support (0.8.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-base (0.23.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-base (0.22.6)
       opentelemetry-api (~> 1.0)
       opentelemetry-common (~> 0.21)
       opentelemetry-registry (~> 0.1)
-    opentelemetry-instrumentation-concurrent_ruby (0.22.0)
+    opentelemetry-instrumentation-concurrent_ruby (0.21.4)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-excon (0.23.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-excon (0.22.4)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-faraday (0.26.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-faraday (0.24.6)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-http (0.24.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-http (0.23.4)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-http_client (0.23.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-http_client (0.22.7)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-net_http (0.23.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-net_http (0.22.7)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-pg (0.30.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-pg (0.29.0)
       opentelemetry-api (~> 1.0)
       opentelemetry-helpers-sql-obfuscation
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-rack (0.26.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-rack (0.24.6)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-rails (0.36.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-rails (0.31.2)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-action_mailer (~> 0.4.0)
-      opentelemetry-instrumentation-action_pack (~> 0.12.0)
-      opentelemetry-instrumentation-action_view (~> 0.9.0)
-      opentelemetry-instrumentation-active_job (~> 0.8.0)
-      opentelemetry-instrumentation-active_record (~> 0.9.0)
-      opentelemetry-instrumentation-active_storage (~> 0.1.0)
-      opentelemetry-instrumentation-active_support (~> 0.8.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-      opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0)
-    opentelemetry-instrumentation-redis (0.26.1)
+      opentelemetry-instrumentation-action_mailer (~> 0.1.0)
+      opentelemetry-instrumentation-action_pack (~> 0.9.0)
+      opentelemetry-instrumentation-action_view (~> 0.7.0)
+      opentelemetry-instrumentation-active_job (~> 0.7.0)
+      opentelemetry-instrumentation-active_record (~> 0.7.0)
+      opentelemetry-instrumentation-active_support (~> 0.6.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-redis (0.25.7)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-instrumentation-sidekiq (0.26.1)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-instrumentation-sidekiq (0.25.7)
       opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.23.0)
-    opentelemetry-registry (0.4.0)
+      opentelemetry-instrumentation-base (~> 0.22.1)
+    opentelemetry-registry (0.3.1)
       opentelemetry-api (~> 1.1)
-    opentelemetry-sdk (1.8.0)
+    opentelemetry-sdk (1.5.0)
       opentelemetry-api (~> 1.1)
       opentelemetry-common (~> 0.20)
       opentelemetry-registry (~> 0.2)
       opentelemetry-semantic_conventions
-    opentelemetry-semantic_conventions (1.11.0)
+    opentelemetry-semantic_conventions (1.10.1)
       opentelemetry-api (~> 1.0)
     orm_adapter (0.5.0)
-    ostruct (0.6.1)
-    ox (2.14.22)
-      bigdecimal (>= 3.0)
-    parallel (1.27.0)
-    parser (3.3.8.0)
+    ostruct (0.6.0)
+    ox (2.14.18)
+    parallel (1.26.3)
+    parser (3.3.5.0)
       ast (~> 2.4.1)
       racc
     parslet (2.0.0)
     pastel (0.8.0)
       tty-color (~> 0.5)
-    pg (1.5.9)
-    pghero (3.6.2)
+    pg (1.5.8)
+    pghero (3.6.0)
       activerecord (>= 6.1)
-    pp (0.6.2)
-      prettyprint
     premailer (1.27.0)
       addressable
       css_parser (>= 1.19.0)
@@ -605,22 +605,17 @@ GEM
       actionmailer (>= 3)
       net-smtp
       premailer (~> 1.7, >= 1.7.9)
-    prettyprint (0.2.0)
-    prism (1.4.0)
-    prometheus_exporter (2.2.0)
-      webrick
     propshaft (1.1.0)
       actionpack (>= 7.0.0)
       activesupport (>= 7.0.0)
       rack
       railties (>= 7.0.0)
-    psych (5.2.3)
-      date
+    psych (5.1.2)
       stringio
     public_suffix (6.0.1)
-    puma (6.6.0)
+    puma (6.4.3)
       nio4r (~> 2.0)
-    pundit (2.5.0)
+    pundit (2.4.0)
       activesupport (>= 3.0.0)
     raabro (1.4.0)
     racc (1.8.1)
@@ -629,11 +624,10 @@ GEM
       rack (>= 1.0, < 4)
     rack-cors (2.0.2)
       rack (>= 2.0.0)
-    rack-oauth2 (2.2.1)
+    rack-oauth2 (1.21.3)
       activesupport
       attr_required
-      faraday (~> 2.0)
-      faraday-follow_redirects
+      httpclient
       json-jwt (>= 1.11.0)
       rack (>= 2.1.0)
     rack-protection (3.2.0)
@@ -643,25 +637,29 @@ GEM
       rack
     rack-session (1.0.2)
       rack (< 3)
-    rack-test (2.2.0)
+    rack-test (2.1.0)
       rack (>= 1.3)
-    rackup (1.0.1)
+    rackup (1.0.0)
       rack (< 3)
       webrick
-    rails (8.0.2)
-      actioncable (= 8.0.2)
-      actionmailbox (= 8.0.2)
-      actionmailer (= 8.0.2)
-      actionpack (= 8.0.2)
-      actiontext (= 8.0.2)
-      actionview (= 8.0.2)
-      activejob (= 8.0.2)
-      activemodel (= 8.0.2)
-      activerecord (= 8.0.2)
-      activestorage (= 8.0.2)
-      activesupport (= 8.0.2)
+    rails (7.1.5.1)
+      actioncable (= 7.1.5.1)
+      actionmailbox (= 7.1.5.1)
+      actionmailer (= 7.1.5.1)
+      actionpack (= 7.1.5.1)
+      actiontext (= 7.1.5.1)
+      actionview (= 7.1.5.1)
+      activejob (= 7.1.5.1)
+      activemodel (= 7.1.5.1)
+      activerecord (= 7.1.5.1)
+      activestorage (= 7.1.5.1)
+      activesupport (= 7.1.5.1)
       bundler (>= 1.15.0)
-      railties (= 8.0.2)
+      railties (= 7.1.5.1)
+    rails-controller-testing (1.0.5)
+      actionpack (>= 5.0.1.rc1)
+      actionview (>= 5.0.1.rc1)
+      activesupport (>= 5.0.1.rc1)
     rails-dom-testing (2.2.0)
       activesupport (>= 5.0.0)
       minitest
@@ -669,13 +667,13 @@ GEM
     rails-html-sanitizer (1.6.2)
       loofah (~> 2.21)
       nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
-    rails-i18n (8.0.1)
+    rails-i18n (7.0.9)
       i18n (>= 0.7, < 2)
-      railties (>= 8.0.0, < 9)
-    railties (8.0.2)
-      actionpack (= 8.0.2)
-      activesupport (= 8.0.2)
-      irb (~> 1.13)
+      railties (>= 6.0.0, < 8)
+    railties (7.1.5.1)
+      actionpack (= 7.1.5.1)
+      activesupport (= 7.1.5.1)
+      irb
       rackup (>= 1.0.0)
       rake (>= 12.2)
       thor (~> 1.0, >= 1.2.2)
@@ -688,25 +686,25 @@ GEM
       link_header (~> 0.0, >= 0.0.8)
     rdf-normalize (0.7.0)
       rdf (~> 3.3)
-    rdoc (6.13.1)
+    rdoc (6.7.0)
       psych (>= 4.0.0)
-    redcarpet (3.6.1)
+    redcarpet (3.6.0)
     redis (4.8.1)
     redis-namespace (1.11.0)
       redis (>= 4)
     redlock (1.3.2)
       redis (>= 3.0.0, < 6.0)
-    regexp_parser (2.10.0)
-    reline (0.6.1)
+    regexp_parser (2.9.2)
+    reline (0.5.10)
       io-console (~> 0.5)
-    request_store (1.7.0)
+    request_store (1.6.0)
       rack (>= 1.4)
     responders (3.1.1)
       actionpack (>= 5.2)
       railties (>= 5.2)
-    rexml (3.4.1)
+    rexml (3.3.9)
     rotp (6.3.0)
-    rouge (4.5.1)
+    rouge (4.3.0)
     rpam2 (4.0.2)
     rqrcode (2.2.0)
       chunky_png (~> 1.0)
@@ -716,17 +714,17 @@ GEM
       rspec-core (~> 3.13.0)
       rspec-expectations (~> 3.13.0)
       rspec-mocks (~> 3.13.0)
-    rspec-core (3.13.3)
+    rspec-core (3.13.1)
       rspec-support (~> 3.13.0)
-    rspec-expectations (3.13.3)
+    rspec-expectations (3.13.2)
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.13.0)
-    rspec-github (3.0.0)
+    rspec-github (2.4.0)
       rspec-core (~> 3.0)
-    rspec-mocks (3.13.2)
+    rspec-mocks (3.13.1)
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.13.0)
-    rspec-rails (7.1.1)
+    rspec-rails (7.0.1)
       actionpack (>= 7.0)
       activesupport (>= 7.0)
       railties (>= 7.0)
@@ -734,50 +732,40 @@ GEM
       rspec-expectations (~> 3.13)
       rspec-mocks (~> 3.13)
       rspec-support (~> 3.13)
-    rspec-sidekiq (5.1.0)
+    rspec-sidekiq (5.0.0)
       rspec-core (~> 3.0)
       rspec-expectations (~> 3.0)
       rspec-mocks (~> 3.0)
-      sidekiq (>= 5, < 9)
-    rspec-support (3.13.2)
-    rubocop (1.75.2)
+      sidekiq (>= 5, < 8)
+    rspec-support (3.13.1)
+    rubocop (1.66.1)
       json (~> 2.3)
-      language_server-protocol (~> 3.17.0.2)
-      lint_roller (~> 1.1.0)
+      language_server-protocol (>= 3.17.0)
       parallel (~> 1.10)
       parser (>= 3.3.0.2)
       rainbow (>= 2.2.2, < 4.0)
-      regexp_parser (>= 2.9.3, < 3.0)
-      rubocop-ast (>= 1.44.0, < 2.0)
+      regexp_parser (>= 2.4, < 3.0)
+      rubocop-ast (>= 1.32.2, < 2.0)
       ruby-progressbar (~> 1.7)
-      unicode-display_width (>= 2.4.0, < 4.0)
-    rubocop-ast (1.44.1)
-      parser (>= 3.3.7.2)
-      prism (~> 1.4)
-    rubocop-capybara (2.22.1)
-      lint_roller (~> 1.1)
-      rubocop (~> 1.72, >= 1.72.1)
-    rubocop-i18n (3.2.3)
-      lint_roller (~> 1.1)
-      rubocop (>= 1.72.1)
-    rubocop-performance (1.25.0)
-      lint_roller (~> 1.1)
-      rubocop (>= 1.75.0, < 2.0)
-      rubocop-ast (>= 1.38.0, < 2.0)
-    rubocop-rails (2.31.0)
+      unicode-display_width (>= 2.4.0, < 3.0)
+    rubocop-ast (1.32.3)
+      parser (>= 3.3.1.0)
+    rubocop-capybara (2.21.0)
+      rubocop (~> 1.41)
+    rubocop-performance (1.22.1)
+      rubocop (>= 1.48.1, < 2.0)
+      rubocop-ast (>= 1.31.1, < 2.0)
+    rubocop-rails (2.26.2)
       activesupport (>= 4.2.0)
-      lint_roller (~> 1.1)
       rack (>= 1.1)
-      rubocop (>= 1.75.0, < 2.0)
-      rubocop-ast (>= 1.38.0, < 2.0)
-    rubocop-rspec (3.5.0)
-      lint_roller (~> 1.1)
-      rubocop (~> 1.72, >= 1.72.1)
-    rubocop-rspec_rails (2.31.0)
-      lint_roller (~> 1.1)
-      rubocop (~> 1.72, >= 1.72.1)
-      rubocop-rspec (~> 3.5)
-    ruby-prof (1.7.1)
+      rubocop (>= 1.52.0, < 2.0)
+      rubocop-ast (>= 1.31.1, < 2.0)
+    rubocop-rspec (3.0.5)
+      rubocop (~> 1.61)
+    rubocop-rspec_rails (2.30.0)
+      rubocop (~> 1.61)
+      rubocop-rspec (~> 3, >= 3.0.1)
+    ruby-prof (1.7.0)
     ruby-progressbar (1.13.0)
     ruby-saml (1.18.0)
       nokogiri (>= 1.13.10)
@@ -785,25 +773,26 @@ GEM
     ruby-vips (2.2.3)
       ffi (~> 1.12)
       logger
-    rubyzip (2.4.1)
-    rufus-scheduler (3.9.2)
-      fugit (~> 1.1, >= 1.11.1)
+    ruby2_keywords (0.0.5)
+    rubyzip (2.3.2)
+    rufus-scheduler (3.9.1)
+      fugit (~> 1.1, >= 1.1.6)
     safety_net_attestation (0.4.0)
       jwt (~> 2.0)
-    sanitize (7.0.0)
+    sanitize (6.1.3)
       crass (~> 1.0.2)
-      nokogiri (>= 1.16.8)
+      nokogiri (>= 1.12.0)
     scenic (1.8.0)
       activerecord (>= 4.0.0)
       railties (>= 4.0.0)
     securerandom (0.4.1)
-    selenium-webdriver (4.31.0)
+    selenium-webdriver (4.25.0)
       base64 (~> 0.2)
       logger (~> 1.4)
       rexml (~> 3.2, >= 3.2.5)
       rubyzip (>= 1.2.2, < 3.0)
       websocket (~> 1.0)
-    semantic_range (3.1.0)
+    semantic_range (3.0.0)
     shoulda-matchers (6.4.0)
       activesupport (>= 5.2.0)
     sidekiq (6.5.12)
@@ -831,33 +820,30 @@ GEM
       docile (~> 1.1)
       simplecov-html (~> 0.11)
       simplecov_json_formatter (~> 0.1)
-    simplecov-html (0.13.1)
+    simplecov-html (0.12.3)
     simplecov-lcov (0.8.0)
     simplecov_json_formatter (0.1.4)
-    stackprof (0.2.27)
-    starry (0.2.0)
-      base64
-    stoplight (4.1.1)
+    stackprof (0.2.26)
+    stoplight (4.1.0)
       redlock (~> 1.0)
-    stringio (3.1.6)
-    strong_migrations (2.3.0)
-      activerecord (>= 7)
-    swd (2.0.3)
+    stringio (3.1.1)
+    strong_migrations (2.0.0)
+      activerecord (>= 6.1)
+    swd (1.3.0)
       activesupport (>= 3)
       attr_required (>= 0.0.5)
-      faraday (~> 2.0)
-      faraday-follow_redirects
+      httpclient (>= 2.4)
     sysexits (1.2.0)
     temple (0.10.3)
-    terminal-table (4.0.0)
-      unicode-display_width (>= 1.1.1, < 4)
-    terrapin (1.1.0)
+    terminal-table (3.0.2)
+      unicode-display_width (>= 1.1.1, < 3)
+    terrapin (1.0.1)
       climate_control
-    test-prof (1.4.4)
+    test-prof (1.4.2)
     thor (1.3.2)
-    tilt (2.6.0)
+    tilt (2.4.0)
     timeout (0.4.3)
-    tpm-key_attestation (0.14.0)
+    tpm-key_attestation (0.12.1)
       bindata (~> 2.4)
       openssl (> 2.0)
       openssl-signature_algorithm (~> 1.0)
@@ -876,34 +862,34 @@ GEM
       unf (~> 0.1.0)
     tzinfo (2.0.6)
       concurrent-ruby (~> 1.0)
-    tzinfo-data (1.2025.2)
+    tzinfo-data (1.2024.2)
       tzinfo (>= 1.0.0)
     unf (0.1.4)
       unf_ext
     unf_ext (0.0.9.1)
-    unicode-display_width (3.1.4)
-      unicode-emoji (~> 4.0, >= 4.0.4)
-    unicode-emoji (4.0.4)
-    uri (1.0.3)
-    useragent (0.16.11)
+    unicode-display_width (2.5.0)
+    uri (0.13.2)
+    validate_email (0.1.6)
+      activemodel (>= 3.0)
+      mail (>= 2.2.5)
     validate_url (1.0.15)
       activemodel (>= 3.0.0)
       public_suffix
     warden (1.2.9)
       rack (>= 2.0.9)
-    webauthn (3.4.0)
+    webauthn (3.1.0)
       android_key_attestation (~> 0.3.0)
+      awrence (~> 1.1)
       bindata (~> 2.4)
       cbor (~> 0.5.9)
       cose (~> 1.1)
       openssl (>= 2.2)
       safety_net_attestation (~> 0.4.0)
-      tpm-key_attestation (~> 0.14.0)
-    webfinger (2.1.3)
+      tpm-key_attestation (~> 0.12.0)
+    webfinger (1.2.0)
       activesupport
-      faraday (~> 2.0)
-      faraday-follow_redirects
-    webmock (3.25.1)
+      httpclient (>= 2.4)
+    webmock (3.24.0)
       addressable (>= 2.8.0)
       crack (>= 0.3.2)
       hashdiff (>= 0.4.0, < 2.0.0)
@@ -912,17 +898,16 @@ GEM
       rack-proxy (>= 0.6.1)
       railties (>= 5.2)
       semantic_range (>= 2.3.0)
-    webrick (1.9.1)
+    webrick (1.8.2)
     websocket (1.2.11)
-    websocket-driver (0.7.7)
-      base64
+    websocket-driver (0.7.6)
       websocket-extensions (>= 0.1.0)
     websocket-extensions (0.1.5)
     wisper (2.0.1)
     xorcist (1.1.3)
     xpath (3.2.0)
       nokogiri (~> 1.8)
-    zeitwerk (2.7.2)
+    zeitwerk (2.6.18)
 
 PLATFORMS
   ruby
@@ -930,15 +915,14 @@ PLATFORMS
 DEPENDENCIES
   active_model_serializers (~> 0.10)
   addressable (~> 2.8)
-  annotaterb (~> 4.13)
-  aws-sdk-core (< 3.216.0)
+  annotate (~> 3.2)
   aws-sdk-s3 (~> 1.123)
   better_errors (~> 2.9)
   binding_of_caller (~> 1.0)
   blurhash (~> 0.1)
   bootsnap (~> 1.18.0)
-  brakeman (~> 7.0)
-  browser
+  brakeman (~> 6.0)
+  browser (< 6)
   bundler-audit (~> 0.9)
   capybara (~> 3.39)
   charlock_holmes (~> 0.7.7)
@@ -960,11 +944,10 @@ DEPENDENCIES
   email_spec
   fabrication (~> 2.30)
   faker (~> 3.2)
-  faraday-httpclient
   fast_blank (~> 1.0)
   fastimage
   flatware-rspec
-  fog-core (<= 2.6.0)
+  fog-core (<= 2.5.0)
   fog-openstack (~> 1.0)
   haml-rails (~> 2.0)
   haml_lint
@@ -979,7 +962,6 @@ DEPENDENCIES
   idn-ruby
   inline_svg
   irb (~> 1.8)
-  jd-paperclip-azure (~> 3.0)
   json-ld
   json-ld-preloaded (~> 3.2)
   json-schema (~> 5.0)
@@ -988,14 +970,13 @@ DEPENDENCIES
   letter_opener (~> 1.8)
   letter_opener_web (~> 3.0)
   link_header (~> 0.0)
-  linzer (~> 0.6.1)
   lograge (~> 0.12)
   mail (~> 2.8)
   mario-redis-lock (~> 1.2)
+  md-paperclip-azure (~> 2.2)
   memory_profiler
-  mime-types (~> 3.6.0)
-  mutex_m
-  net-http (~> 0.6.0)
+  mime-types (~> 3.5.0)
+  net-http (~> 0.4.0)
   net-ldap (~> 0.18)
   nokogiri (~> 1.15)
   oj (~> 3.14)
@@ -1003,29 +984,28 @@ DEPENDENCIES
   omniauth-cas (~> 3.0.0.beta.1)
   omniauth-rails_csrf_protection (~> 1.0)
   omniauth-saml (~> 2.0)
-  omniauth_openid_connect (~> 0.8.0)
-  opentelemetry-api (~> 1.5.0)
-  opentelemetry-exporter-otlp (~> 0.30.0)
-  opentelemetry-instrumentation-active_job (~> 0.8.0)
-  opentelemetry-instrumentation-active_model_serializers (~> 0.22.0)
-  opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0)
-  opentelemetry-instrumentation-excon (~> 0.23.0)
-  opentelemetry-instrumentation-faraday (~> 0.26.0)
-  opentelemetry-instrumentation-http (~> 0.24.0)
-  opentelemetry-instrumentation-http_client (~> 0.23.0)
-  opentelemetry-instrumentation-net_http (~> 0.23.0)
-  opentelemetry-instrumentation-pg (~> 0.30.0)
-  opentelemetry-instrumentation-rack (~> 0.26.0)
-  opentelemetry-instrumentation-rails (~> 0.36.0)
-  opentelemetry-instrumentation-redis (~> 0.26.0)
-  opentelemetry-instrumentation-sidekiq (~> 0.26.0)
+  omniauth_openid_connect (~> 0.6.1)
+  opentelemetry-api (~> 1.4.0)
+  opentelemetry-exporter-otlp (~> 0.29.0)
+  opentelemetry-instrumentation-active_job (~> 0.7.1)
+  opentelemetry-instrumentation-active_model_serializers (~> 0.20.1)
+  opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2)
+  opentelemetry-instrumentation-excon (~> 0.22.0)
+  opentelemetry-instrumentation-faraday (~> 0.24.1)
+  opentelemetry-instrumentation-http (~> 0.23.2)
+  opentelemetry-instrumentation-http_client (~> 0.22.3)
+  opentelemetry-instrumentation-net_http (~> 0.22.4)
+  opentelemetry-instrumentation-pg (~> 0.29.0)
+  opentelemetry-instrumentation-rack (~> 0.24.1)
+  opentelemetry-instrumentation-rails (~> 0.31.0)
+  opentelemetry-instrumentation-redis (~> 0.25.3)
+  opentelemetry-instrumentation-sidekiq (~> 0.25.2)
   opentelemetry-sdk (~> 1.4)
   ox (~> 2.14)
   parslet
   pg (~> 1.5)
   pghero
   premailer-rails
-  prometheus_exporter (~> 2.2)
   propshaft
   public_suffix (~> 6.0)
   puma (~> 6.3)
@@ -1034,19 +1014,19 @@ DEPENDENCIES
   rack-attack (~> 6.6)
   rack-cors (~> 2.0)
   rack-test (~> 2.1)
-  rails (~> 8.0)
-  rails-i18n (~> 8.0)
+  rails (~> 7.1.1)
+  rails-controller-testing (~> 1.0)
+  rails-i18n (~> 7.0)
   rdf-normalize (~> 0.5)
   redcarpet (~> 3.6)
   redis (~> 4.5)
   redis-namespace (~> 1.10)
   rqrcode (~> 2.2)
-  rspec-github (~> 3.0)
+  rspec-github (~> 2.4)
   rspec-rails (~> 7.0)
   rspec-sidekiq (~> 5.0)
   rubocop
   rubocop-capybara
-  rubocop-i18n
   rubocop-performance
   rubocop-rails
   rubocop-rspec
@@ -1055,7 +1035,7 @@ DEPENDENCIES
   ruby-progressbar (~> 1.13)
   ruby-vips (~> 2.2)
   rubyzip (~> 2.3)
-  sanitize (~> 7.0)
+  sanitize (~> 6.0)
   scenic (~> 1.7)
   selenium-webdriver
   shoulda-matchers
@@ -1082,7 +1062,7 @@ DEPENDENCIES
   xorcist (~> 1.1)
 
 RUBY VERSION
-   ruby 3.4.1p0
+   ruby 3.3.4p94
 
 BUNDLED WITH
-   2.6.8
+   2.6.5
diff --git a/README.md b/README.md
index 854e8ac3d9..200d58d8c4 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,123 @@
-NAS is an KMY & Mastodon Fork
+# ![kmyblue icon](https://raw.githubusercontent.com/kmycode/mastodon/kb_development/app/javascript/icons/favicon-32x32.png) kmyblue
 
-The following are just a few of the most common features. There are many other minor changes to the specifications.
+[![Ruby Testing](https://github.com/kmycode/mastodon/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/kmycode/mastodon/actions/workflows/test-ruby.yml)
 
-Emoji reactions
+! FOR ENGLISH USER ! We do not provide English documentation for kmyblue; we assume that you will use automatic translation software, such as Google, to translate the site.
 
-Local Public (Does not appear on the federated timeline of remote servers, but does appear on followers' home timelines. This is different from local only)
+kmyblueは、ActivityPubに接続するSNSの1つである[Mastodon](https://github.com/mastodon/mastodon)のフォークです。創作作家のためのMastodonを目指して開発しました。
 
-Bookmark classification
+kmyblueはフォーク名であり、同時に[サーバー名](https://kmy.blue)でもあります。以下は特に記述がない限り、フォークとしてのkmyblueをさします。
 
-Set who can search your posts for each post (Searchability)
+kmyblueは AGPL ライセンスで公開されているため、どなたでも自由にフォークし、このソースコードを元に自分でサーバーを立てて公開することができます。確かにサーバーkmyblueは創作作家向けの利用規約が設定されていますが、フォークとしてのkmyblueのルールは全くの別物です。いかなるコミュニティにも平等にお使いいただけます。  
+kmyblueは、閉鎖的なコミュニティ、あまり目立ちたくないコミュニティには特に強力な機能を提供します。kmyblueはプライバシーを考慮したうえで強力な独自機能を提供するため、汎用サーバーとして利用するにもある程度十分な機能が揃っています。
 
-Quote posts, modest quotes (references)
+テストコード、Lint どちらも動いています。
 
-Record posts that meet certain conditions such as domains, accounts, and keywords (Subscriptions/Antennas)
+### アジェンダ
 
-Send posts to a designated set of followers (Circles) (different from direct messages)
+- 利用方法
+- kmyblueの開発方針
+- kmyblueは何でないか
+- kmyblueの独自機能
+- 英語のサポートについて
 
-Notification of new posts on lists
+## 利用方法
 
-Exclude posts from people you follow when filtering posts
+### インストール方法
 
-Hide number of followers and followings
+[Wiki](https://github.com/kmycode/mastodon/wiki/Installation)を参照してください。
 
-Automatically delete posts after a specified time has passed
+### 開発への参加方法
 
-Expanding moderation functions
+CONTRIBUTING.mdを参照してください。
+
+### テスト
+
+```
+# デバッグ実行(以下のいずれか)
+foreman start
+DB_USER=postgres DB_PASS=password foreman start
+
+# 一部を除く全てのテストを行う
+RAILS_ENV=test bundle exec rspec spec
+
+# ElasticSearch連携テストを行う
+新
+RAILS_ENV=test ES_ENABLED=true bundle exec rspec --tag search
+旧
+RAILS_ENV=test ES_ENABLED=true RUN_SEARCH_SPECS=true bundle exec rspec spec/search
+```
+
+## kmyblueの開発方針
+
+### 本家Mastodonへの積極的追従
+
+kmyblueは、追加機能を控えめにする代わりに本家Mastodonに積極的に追従を行います。kmyblueの追加機能そのままに、Mastodonの新機能も利用できるよう調整を行います。
+
+### ゆるやかな内輪での運用
+
+kmyblueは同人向けサーバーとして出発したため、同人作家に需要のある「内輪ノリを外部にできるだけもらさない」という部分に特化しています。
+
+「ローカル公開」は、投稿を見せたくない人に見つかりにくくする効果があります。「サークル」は、フォロワーの中でも特に見せたい人だけに見せる効果があります。  
+「検索許可」という独自の検索オプションを利用することで、公開投稿の一部だけを検索されにくくするだけでなく、非収載投稿が誰でも自由に検索できるようになります。
+
+内輪とは自分のサーバーに限ったものではありません。内輪同士で複数のサーバーを運営するとき、お互いが深く繋がれる「フレンドサーバー」というシステムも用意しています。
+
+### 少人数サーバーでの運用
+
+kmyblueは、人の少ないサーバーでの運用を考慮して設計しています。そのため、Fedibirdにあるような、人の多いサーバー向けの機能はあまり作っていません。
+
+サーバーの負荷については一部度外視している部分があります。たとえば絵文字リアクション機能はサーバーへ著しい負荷をかける場合があります。ただしkmyblueでは、絵文字リアクション機能そのものを無効にしたり、負荷の高いストリーミング処理を無効にする管理者オプションも存在します。
+
+もちろん人の多いサーバーでの運用が不便になるような修正は行っていません。人数にかかわらず、そのままお使いいただけます。
+
+### 比較的高い防御力
+
+kmyblueでは、「Fediverseは将来的に荒むのではないか」「Fediverseは将来的にスパムに溢れるのではないか」を念頭に設計している部分があります。投稿だけでなく絵文字リアクションも対象にした防衛策があります。
+
+管理者は「NGワード」「NGルール」機能の利用が可能です。設定を変更することで、一部のモデレーターもこの機能を利用できます。  
+利用者は、独自拡張されたフィルター機能、絵文字リアクションのブロックなどを利用できます。
+
+ただし防御力の高さは自由を犠牲にします。例えばNGワードが多すぎると、他のサーバーからの投稿が制限され、かつそれに気づきにくくなります。
+
+## kmyblueは何でないか
+
+kmyblueは、企業・政府機関向けに開発されたものではありません。開発者はセキュリティに関する専門知識を有しておらず、高度なセキュリティを求められる機関向けのソフトウェアを制作する能力はありません。また、kmyblueのメンテナは現在1人のみであり、そのメンテナが飽きたら開発がストップするリスクも高いです。Mastodonのような高い信頼性・安全性を保証することはできないので、導入の際はご自身で安全を十分に確認してからお使いになることを強くおすすめします。  
+個人サーバーであっても、安定性を強く求める方にはおすすめできません。glitch-socがよりよい選択肢になるでしょう。
+
+kmyblueは、Misskeyではありません。Misskeyは「楽しむ」をコンセプトにしていますが、kmyblueはMastodonの思想を受け継ぎ、炎上や喧騒を避けることのできる落ち着いた場所を目指しています。そのため、思想に合わない機能は実装しないか、大幅に弱体化しています。
+
+kmyblueは、Fedibirdではありません。Fedibirdは大規模サーバー向けに設定していると思われる機能があり、例えば購読機能がその代表例です。Fedibirdの購読は擬似的なフォロー体験を与えるものですが、本物のフォローではないため、購読対象の投稿が配送されることを確約したものではありません。小規模サーバーだとかえって不便になる機能を、kmyblueは避けています。
+
+## kmyblueの独自機能
+
+以下に列挙したものはあくまで代表的なものです。これ以外にも、細かい仕様変更などが多数含まれます。
+
+- 絵文字リアクション
+- ローカル公開(Local Public)(リモートサーバーの連合タイムラインには流れませんが、フォロワーのホームタイムラインには流れます。**ローカル限定とは異なります**)
+- ブックマークの分類
+- 自分の投稿を検索できる人を投稿ごとに設定(検索許可・Searchability)
+- 投稿の引用、ひかえめな引用(参照)
+- ドメイン・アカウント・キーワードなど特定条件を満たした投稿を記録する機能(購読・アンテナ)
+- フォロワーの一部を指名して投稿を送る機能(サークル)(ダイレクトメッセージとは異なります)
+- リスト新着投稿の通知
+- 投稿のフィルタリングにおいて、自分がフォローしている相手の投稿を除外
+- フォロー・フォロワー数を隠す機能
+- 指定した時間が経過したあとに投稿を自動削除する機能
+- モデレーション機能の拡張
+
+## 英語のサポートについて
+
+kmyblueのメイン開発者である[雪あすか](https://kmy.blue/@askyq)は、英語の読み書きがほとんどできません。そのため、ドキュメントの英語化、海外向け公式アカウントの新設などを行う予定はありません。
+
+要望やバグ報告はIssueに書いて構いませんが、Issue画面内の説明やテンプレートはすべて日本語になっています。投稿が難しければ、Discussionに投稿してください。こちらで必要と判断したものは、改めてIssueとして起票します。
+
+そのほか開発者へ質問があれば、[@askyq@kmy.blue](https://kmy.blue/@askyq)へ英語のまま送ってください。
+
+ただしkmyblueのドキュメント、[@askyq@kmy.blue](https://kmy.blue/@askyq)内のkmyblueフォークに関係する投稿を、許可なく翻訳して公開することは問題ありません。
+
+## 開発者のアカウントについて
+
+kmyblueのメイン開発者である[雪あすか](https://kmy.blue/@askyq)は、用途別にアカウントを分けるようなことはせず、すべての発言を1つのアカウントで行っています。そのため、kmyblueの開発だけでなく、成人向け同人作品の話も混ざっています。
+
+このうち、公開範囲「公開」「ローカル公開」「非収載」であるkmyblueフォークの開発に関係する投稿に限り抽出し、翻訳の有無に関係なく公開することを許可します。これはkmyblueフォークの利用者にとって公共性の高いコンテンツであると思われます。これは、日本と欧米では一般的に考えられている児童ポルノの基準が異なり、欧米のサーバーの中にはこのアカウントをフォローしづらいものもあるという懸念を考慮したものです。
diff --git a/Rakefile b/Rakefile
index 488c551fee..e51cf0e17e 100644
--- a/Rakefile
+++ b/Rakefile
@@ -3,6 +3,6 @@
 # Add your own tasks in files placed in lib/tasks ending in .rake,
 # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
 
-require_relative 'config/application'
+require File.expand_path('config/application', __dir__)
 
 Rails.application.load_tasks
diff --git a/Vagrantfile b/Vagrantfile
index ce456060cd..89f5536edc 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -174,7 +174,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
   if config.vm.networks.any? { |type, options| type == :private_network }
     config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'actimeo=1']
   else
-    config.vm.synced_folder ".", "/vagrant", type: "rsync", create: true, rsync__args: ["--verbose", "--archive", "--delete", "-z"]
+    config.vm.synced_folder ".", "/vagrant"
   end
 
   # Otherwise, you can access the site at http://localhost:3000 and http://localhost:4000 , http://localhost:8080
diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb
index c80db3500d..ab1b98e646 100644
--- a/app/controllers/activitypub/collections_controller.rb
+++ b/app/controllers/activitypub/collections_controller.rb
@@ -49,7 +49,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
 
   def collection_presenter
     ActivityPub::CollectionPresenter.new(
-      id: ActivityPub::TagManager.instance.collection_uri_for(@account, params[:id]),
+      id: account_collection_url(@account, params[:id]),
       type: @type,
       size: @size,
       items: @items
diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb
index 171161d491..82b1830941 100644
--- a/app/controllers/activitypub/outboxes_controller.rb
+++ b/app/controllers/activitypub/outboxes_controller.rb
@@ -41,8 +41,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
     end
   end
 
-  def outbox_url(...)
-    ActivityPub::TagManager.instance.outbox_uri_for(@account, ...)
+  def outbox_url(**kwargs)
+    if params[:account_username].present?
+      account_outbox_url(@account, **kwargs)
+    else
+      instance_actor_outbox_url(**kwargs)
+    end
   end
 
   def next_page
diff --git a/app/controllers/admin/account_actions_controller.rb b/app/controllers/admin/account_actions_controller.rb
index 91849811e3..e674bf55a0 100644
--- a/app/controllers/admin/account_actions_controller.rb
+++ b/app/controllers/admin/account_actions_controller.rb
@@ -34,8 +34,7 @@ module Admin
     end
 
     def resource_params
-      params
-        .expect(admin_account_action: [:type, :report_id, :warning_preset_id, :text, :send_email_notification, :include_statuses])
+      params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification, :include_statuses)
     end
   end
 end
diff --git a/app/controllers/admin/account_moderation_notes_controller.rb b/app/controllers/admin/account_moderation_notes_controller.rb
index 7f65ced517..a3c4adf59a 100644
--- a/app/controllers/admin/account_moderation_notes_controller.rb
+++ b/app/controllers/admin/account_moderation_notes_controller.rb
@@ -29,8 +29,10 @@ module Admin
     private
 
     def resource_params
-      params
-        .expect(account_moderation_note: [:content, :target_account_id])
+      params.require(:account_moderation_note).permit(
+        :content,
+        :target_account_id
+      )
     end
 
     def set_account_moderation_note
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index a779a0cf51..a4d5018d0a 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -172,8 +172,7 @@ module Admin
     end
 
     def form_account_batch_params
-      params
-        .expect(form_account_batch: [:action, account_ids: []])
+      params.require(:form_account_batch).permit(:action, account_ids: [])
     end
 
     def action_from_button
diff --git a/app/controllers/admin/announcements/distributions_controller.rb b/app/controllers/admin/announcements/distributions_controller.rb
deleted file mode 100644
index 4bd8769834..0000000000
--- a/app/controllers/admin/announcements/distributions_controller.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::Announcements::DistributionsController < Admin::BaseController
-  before_action :set_announcement
-
-  def create
-    authorize @announcement, :distribute?
-    @announcement.touch(:notification_sent_at)
-    Admin::DistributeAnnouncementNotificationWorker.perform_async(@announcement.id)
-    redirect_to admin_announcements_path
-  end
-
-  private
-
-  def set_announcement
-    @announcement = Announcement.find(params[:announcement_id])
-  end
-end
diff --git a/app/controllers/admin/announcements/previews_controller.rb b/app/controllers/admin/announcements/previews_controller.rb
deleted file mode 100644
index d77f931a7f..0000000000
--- a/app/controllers/admin/announcements/previews_controller.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::Announcements::PreviewsController < Admin::BaseController
-  before_action :set_announcement
-
-  def show
-    authorize @announcement, :distribute?
-    @user_count = @announcement.scope_for_notification.count
-  end
-
-  private
-
-  def set_announcement
-    @announcement = Announcement.find(params[:announcement_id])
-  end
-end
diff --git a/app/controllers/admin/announcements/tests_controller.rb b/app/controllers/admin/announcements/tests_controller.rb
deleted file mode 100644
index f2457eb23a..0000000000
--- a/app/controllers/admin/announcements/tests_controller.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::Announcements::TestsController < Admin::BaseController
-  before_action :set_announcement
-
-  def create
-    authorize @announcement, :distribute?
-    UserMailer.announcement_published(current_user, @announcement).deliver_later!
-    redirect_to admin_announcements_path
-  end
-
-  private
-
-  def set_announcement
-    @announcement = Announcement.find(params[:announcement_id])
-  end
-end
diff --git a/app/controllers/admin/announcements_controller.rb b/app/controllers/admin/announcements_controller.rb
index eaf84aab25..12230a6506 100644
--- a/app/controllers/admin/announcements_controller.rb
+++ b/app/controllers/admin/announcements_controller.rb
@@ -84,7 +84,6 @@ class Admin::AnnouncementsController < Admin::BaseController
   end
 
   def resource_params
-    params
-      .expect(announcement: [:text, :scheduled_at, :starts_at, :ends_at, :all_day])
+    params.require(:announcement).permit(:text, :scheduled_at, :starts_at, :ends_at, :all_day)
   end
 end
diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb
index 14338dd293..48685db17a 100644
--- a/app/controllers/admin/base_controller.rb
+++ b/app/controllers/admin/base_controller.rb
@@ -7,14 +7,14 @@ module Admin
 
     layout 'admin'
 
-    before_action :set_referrer_policy_header
+    before_action :set_cache_headers
 
     after_action :verify_authorized
 
     private
 
-    def set_referrer_policy_header
-      response.headers['Referrer-Policy'] = 'same-origin'
+    def set_cache_headers
+      response.cache_control.replace(private: true, no_store: true)
     end
 
     def set_user
diff --git a/app/controllers/admin/change_emails_controller.rb b/app/controllers/admin/change_emails_controller.rb
index c923b94b1a..a689d3a530 100644
--- a/app/controllers/admin/change_emails_controller.rb
+++ b/app/controllers/admin/change_emails_controller.rb
@@ -41,8 +41,9 @@ module Admin
     end
 
     def resource_params
-      params
-        .expect(user: [:unconfirmed_email])
+      params.require(:user).permit(
+        :unconfirmed_email
+      )
     end
   end
 end
diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb
index 596b167249..34368f08a2 100644
--- a/app/controllers/admin/custom_emojis_controller.rb
+++ b/app/controllers/admin/custom_emojis_controller.rb
@@ -67,13 +67,11 @@ module Admin
     end
 
     def resource_params
-      params
-        .expect(custom_emoji: [:shortcode, :image, :category_id, :visible_in_picker, :aliases_raw, :license])
+      params.require(:custom_emoji).permit(:shortcode, :image, :category_id, :visible_in_picker, :aliases_raw, :license)
     end
 
     def update_params
-      params
-        .expect(custom_emoji: [:category_id, :visible_in_picker, :aliases_raw, :license])
+      params.require(:custom_emoji).permit(:category_id, :visible_in_picker, :aliases_raw, :license)
     end
 
     def filtered_custom_emojis
@@ -103,8 +101,7 @@ module Admin
     end
 
     def form_custom_emoji_batch_params
-      params
-        .expect(form_custom_emoji_batch: [:action, :category_id, :category_name, custom_emoji_ids: []])
+      params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
     end
   end
 end
diff --git a/app/controllers/admin/domain_allows_controller.rb b/app/controllers/admin/domain_allows_controller.rb
index 913c1a8246..b0f139e3a8 100644
--- a/app/controllers/admin/domain_allows_controller.rb
+++ b/app/controllers/admin/domain_allows_controller.rb
@@ -37,7 +37,6 @@ class Admin::DomainAllowsController < Admin::BaseController
   end
 
   def resource_params
-    params
-      .expect(domain_allow: [:domain])
+    params.require(:domain_allow).permit(:domain)
   end
 end
diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb
index 520db814f2..78d2a2da28 100644
--- a/app/controllers/admin/domain_blocks_controller.rb
+++ b/app/controllers/admin/domain_blocks_controller.rb
@@ -35,9 +35,7 @@ module Admin
     rescue Mastodon::NotPermittedError
       flash[:alert] = I18n.t('admin.domain_blocks.not_permitted')
     else
-      flash[:notice] = I18n.t('admin.domain_blocks.created_msg')
-    ensure
-      redirect_to admin_instances_path(limited: '1')
+      redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
     end
 
     def new
@@ -126,14 +124,9 @@ module Admin
     end
 
     def form_domain_block_batch_params
-      params
-        .expect(
-          form_domain_block_batch: [
-            domain_blocks_attributes: [[:enabled, :domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate,
-                                        :reject_favourite, :reject_reply_exclude_followers, :reject_send_sensitive, :reject_hashtag,
-                                        :reject_straight_follow, :reject_new_follow, :reject_friend, :block_trends, :detect_invalid_subscription, :hidden]],
-          ]
-        )
+      params.require(:form_domain_block_batch).permit(domain_blocks_attributes: [:enabled, :domain, :severity, :reject_media, :reject_favourite, :reject_reply_exclude_followers,
+                                                                                 :reject_send_sensitive, :reject_hashtag, :reject_straight_follow, :reject_new_follow, :reject_friend, :block_trends, :detect_invalid_subscription,
+                                                                                 :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden])
     end
 
     def action_from_button
diff --git a/app/controllers/admin/email_domain_blocks_controller.rb b/app/controllers/admin/email_domain_blocks_controller.rb
index 12f221164f..faa0a061a6 100644
--- a/app/controllers/admin/email_domain_blocks_controller.rb
+++ b/app/controllers/admin/email_domain_blocks_controller.rb
@@ -5,7 +5,7 @@ module Admin
     def index
       authorize :email_domain_block, :index?
 
-      @email_domain_blocks = EmailDomainBlock.parents.includes(:children).order(id: :desc).page(params[:page])
+      @email_domain_blocks = EmailDomainBlock.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page])
       @form                = Form::EmailDomainBlockBatch.new
     end
 
@@ -58,17 +58,18 @@ module Admin
     private
 
     def set_resolved_records
-      @resolved_records = DomainResource.new(@email_domain_block.domain).mx
+      Resolv::DNS.open do |dns|
+        dns.timeouts = 5
+        @resolved_records = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a
+      end
     end
 
     def resource_params
-      params
-        .expect(email_domain_block: [:domain, :allow_with_approval, other_domains: []])
+      params.require(:email_domain_block).permit(:domain, :allow_with_approval, other_domains: [])
     end
 
     def form_email_domain_block_batch_params
-      params
-        .expect(form_email_domain_block_batch: [email_domain_block_ids: []])
+      params.require(:form_email_domain_block_batch).permit(email_domain_block_ids: [])
     end
 
     def action_from_button
diff --git a/app/controllers/admin/fasp/debug/callbacks_controller.rb b/app/controllers/admin/fasp/debug/callbacks_controller.rb
deleted file mode 100644
index 28aba5e489..0000000000
--- a/app/controllers/admin/fasp/debug/callbacks_controller.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::Fasp::Debug::CallbacksController < Admin::BaseController
-  def index
-    authorize [:admin, :fasp, :provider], :update?
-
-    @callbacks = Fasp::DebugCallback
-                 .includes(:fasp_provider)
-                 .order(created_at: :desc)
-  end
-
-  def destroy
-    authorize [:admin, :fasp, :provider], :update?
-
-    callback = Fasp::DebugCallback.find(params[:id])
-    callback.destroy
-
-    redirect_to admin_fasp_debug_callbacks_path
-  end
-end
diff --git a/app/controllers/admin/fasp/debug_calls_controller.rb b/app/controllers/admin/fasp/debug_calls_controller.rb
deleted file mode 100644
index 1e1b6dbf3c..0000000000
--- a/app/controllers/admin/fasp/debug_calls_controller.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::Fasp::DebugCallsController < Admin::BaseController
-  before_action :set_provider
-
-  def create
-    authorize [:admin, @provider], :update?
-
-    @provider.perform_debug_call
-
-    redirect_to admin_fasp_providers_path
-  end
-
-  private
-
-  def set_provider
-    @provider = Fasp::Provider.find(params[:provider_id])
-  end
-end
diff --git a/app/controllers/admin/fasp/providers_controller.rb b/app/controllers/admin/fasp/providers_controller.rb
deleted file mode 100644
index 4f1f1271bf..0000000000
--- a/app/controllers/admin/fasp/providers_controller.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::Fasp::ProvidersController < Admin::BaseController
-  before_action :set_provider, only: [:show, :edit, :update, :destroy]
-
-  def index
-    authorize [:admin, :fasp, :provider], :index?
-
-    @providers = Fasp::Provider.order(confirmed: :asc, created_at: :desc)
-  end
-
-  def show
-    authorize [:admin, @provider], :show?
-  end
-
-  def edit
-    authorize [:admin, @provider], :update?
-  end
-
-  def update
-    authorize [:admin, @provider], :update?
-
-    if @provider.update(provider_params)
-      redirect_to admin_fasp_providers_path
-    else
-      render :edit
-    end
-  end
-
-  def destroy
-    authorize [:admin, @provider], :destroy?
-
-    @provider.destroy
-
-    redirect_to admin_fasp_providers_path
-  end
-
-  private
-
-  def provider_params
-    params.expect(fasp_provider: [capabilities_attributes: {}])
-  end
-
-  def set_provider
-    @provider = Fasp::Provider.find(params[:id])
-  end
-end
diff --git a/app/controllers/admin/fasp/registrations_controller.rb b/app/controllers/admin/fasp/registrations_controller.rb
deleted file mode 100644
index 52c46c2eb6..0000000000
--- a/app/controllers/admin/fasp/registrations_controller.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::Fasp::RegistrationsController < Admin::BaseController
-  before_action :set_provider
-
-  def new
-    authorize [:admin, @provider], :create?
-  end
-
-  def create
-    authorize [:admin, @provider], :create?
-
-    @provider.update_info!(confirm: true)
-
-    redirect_to edit_admin_fasp_provider_path(@provider)
-  end
-
-  private
-
-  def set_provider
-    @provider = Fasp::Provider.find(params[:provider_id])
-  end
-end
diff --git a/app/controllers/admin/follow_recommendations_controller.rb b/app/controllers/admin/follow_recommendations_controller.rb
index b060cfbe94..a54e41bd8c 100644
--- a/app/controllers/admin/follow_recommendations_controller.rb
+++ b/app/controllers/admin/follow_recommendations_controller.rb
@@ -37,8 +37,7 @@ module Admin
     end
 
     def form_account_batch_params
-      params
-        .expect(form_account_batch: [:action, account_ids: []])
+      params.require(:form_account_batch).permit(:action, account_ids: [])
     end
 
     def filter_params
diff --git a/app/controllers/admin/friend_servers_controller.rb b/app/controllers/admin/friend_servers_controller.rb
index ec41ba672c..729d3b3912 100644
--- a/app/controllers/admin/friend_servers_controller.rb
+++ b/app/controllers/admin/friend_servers_controller.rb
@@ -79,11 +79,11 @@ module Admin
     end
 
     def resource_params
-      params.expect(friend_domain: [:domain, :inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts])
+      params.require(:friend_domain).permit(:domain, :inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts)
     end
 
     def update_resource_params
-      params.expect(friend_domain: [:inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts])
+      params.require(:friend_domain).permit(:inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts)
     end
 
     def warn_signatures_not_enabled!
diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb
index a48c4773ed..d7f88a71f3 100644
--- a/app/controllers/admin/instances_controller.rb
+++ b/app/controllers/admin/instances_controller.rb
@@ -5,8 +5,6 @@ module Admin
     before_action :set_instances, only: :index
     before_action :set_instance, except: :index
 
-    LOGS_LIMIT = 5
-
     def index
       authorize :instance, :index?
       preload_delivery_failures!
@@ -15,7 +13,7 @@ module Admin
     def show
       authorize :instance, :show?
       @time_period = (6.days.ago.to_date...Time.now.utc.to_date)
-      @action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(LOGS_LIMIT)
+      @action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(5)
     end
 
     def destroy
diff --git a/app/controllers/admin/invites_controller.rb b/app/controllers/admin/invites_controller.rb
index ac4ee35271..dabfe97655 100644
--- a/app/controllers/admin/invites_controller.rb
+++ b/app/controllers/admin/invites_controller.rb
@@ -32,15 +32,14 @@ module Admin
 
     def deactivate_all
       authorize :invite, :deactivate_all?
-      Invite.available.in_batches.touch_all(:expires_at)
+      Invite.available.in_batches.update_all(expires_at: Time.now.utc)
       redirect_to admin_invites_path
     end
 
     private
 
     def resource_params
-      params
-        .expect(invite: [:max_uses, :expires_in])
+      params.require(:invite).permit(:max_uses, :expires_in)
     end
 
     def filtered_invites
diff --git a/app/controllers/admin/ip_blocks_controller.rb b/app/controllers/admin/ip_blocks_controller.rb
index afabda1b88..1bd7ec8059 100644
--- a/app/controllers/admin/ip_blocks_controller.rb
+++ b/app/controllers/admin/ip_blocks_controller.rb
@@ -44,8 +44,7 @@ module Admin
     private
 
     def resource_params
-      params
-        .expect(ip_block: [:ip, :severity, :comment, :expires_in])
+      params.require(:ip_block).permit(:ip, :severity, :comment, :expires_in)
     end
 
     def action_from_button
@@ -53,8 +52,7 @@ module Admin
     end
 
     def form_ip_block_batch_params
-      params
-        .expect(form_ip_block_batch: [ip_block_ids: []])
+      params.require(:form_ip_block_batch).permit(ip_block_ids: [])
     end
   end
 end
diff --git a/app/controllers/admin/ng_rules_controller.rb b/app/controllers/admin/ng_rules_controller.rb
index 0bdda41c0c..f37424cced 100644
--- a/app/controllers/admin/ng_rules_controller.rb
+++ b/app/controllers/admin/ng_rules_controller.rb
@@ -82,16 +82,16 @@ module Admin
     end
 
     def resource_params
-      params.expect(ng_rule: [:title, :expires_in, :available, :account_domain, :account_username, :account_display_name,
-                              :account_note, :account_field_name, :account_field_value, :account_avatar_state,
-                              :account_header_state, :account_include_local, :status_spoiler_text, :status_text, :status_tag,
-                              :status_sensitive_state, :status_cw_state, :status_media_state, :status_poll_state,
-                              :status_mention_state, :status_reference_state,
-                              :status_quote_state, :status_reply_state, :status_media_threshold, :status_poll_threshold,
-                              :status_mention_threshold, :status_allow_follower_mention,
-                              :reaction_allow_follower, :emoji_reaction_name, :emoji_reaction_origin_domain,
-                              :status_reference_threshold, :account_allow_followed_by_local, :record_history_also_local,
-                              status_visibility: [], status_searchability: [], reaction_type: []])
+      params.require(:ng_rule).permit(:title, :expires_in, :available, :account_domain, :account_username, :account_display_name,
+                                      :account_note, :account_field_name, :account_field_value, :account_avatar_state,
+                                      :account_header_state, :account_include_local, :status_spoiler_text, :status_text, :status_tag,
+                                      :status_sensitive_state, :status_cw_state, :status_media_state, :status_poll_state,
+                                      :status_mention_state, :status_reference_state,
+                                      :status_quote_state, :status_reply_state, :status_media_threshold, :status_poll_threshold,
+                                      :status_mention_threshold, :status_allow_follower_mention,
+                                      :reaction_allow_follower, :emoji_reaction_name, :emoji_reaction_origin_domain,
+                                      :status_reference_threshold, :account_allow_followed_by_local, :record_history_also_local,
+                                      status_visibility: [], status_searchability: [], reaction_type: [])
     end
 
     def test_words!
diff --git a/app/controllers/admin/ng_words/keywords_controller.rb b/app/controllers/admin/ng_words/keywords_controller.rb
index 10969204e8..9af38fab7b 100644
--- a/app/controllers/admin/ng_words/keywords_controller.rb
+++ b/app/controllers/admin/ng_words/keywords_controller.rb
@@ -21,10 +21,6 @@ module Admin
       false
     end
 
-    def avoid_save?
-      true
-    end
-
     private
 
     def after_update_redirect_path
diff --git a/app/controllers/admin/ng_words_controller.rb b/app/controllers/admin/ng_words_controller.rb
index 9e437f8c8b..a70a435fa4 100644
--- a/app/controllers/admin/ng_words_controller.rb
+++ b/app/controllers/admin/ng_words_controller.rb
@@ -13,12 +13,6 @@ module Admin
 
       return unless validate
 
-      if avoid_save?
-        flash[:notice] = I18n.t('generic.changes_saved_msg')
-        redirect_to after_update_redirect_path
-        return
-      end
-
       @admin_settings = Form::AdminSettings.new(settings_params)
 
       if @admin_settings.save
@@ -39,18 +33,14 @@ module Admin
       admin_ng_words_path
     end
 
-    def avoid_save?
-      false
-    end
-
     private
 
     def settings_params
-      params.expect(form_admin_settings: [*Form::AdminSettings::KEYS])
+      params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
     end
 
     def settings_params_test
-      params.expect(form_admin_settings: [ng_words_test: [keywords: [], regexps: [], strangers: [], temporary_ids: []]])['ng_words_test']
+      params.require(:form_admin_settings)[:ng_words_test]
     end
   end
 end
diff --git a/app/controllers/admin/relays_controller.rb b/app/controllers/admin/relays_controller.rb
index 9a796949de..c893802159 100644
--- a/app/controllers/admin/relays_controller.rb
+++ b/app/controllers/admin/relays_controller.rb
@@ -21,7 +21,6 @@ module Admin
       @relay = Relay.new(resource_params)
 
       if @relay.save
-        log_action :create, @relay
         @relay.enable!
         redirect_to admin_relays_path
       else
@@ -32,21 +31,18 @@ module Admin
     def destroy
       authorize :relay, :update?
       @relay.destroy
-      log_action :destroy, @relay
       redirect_to admin_relays_path
     end
 
     def enable
       authorize :relay, :update?
       @relay.enable!
-      log_action :enable, @relay
       redirect_to admin_relays_path
     end
 
     def disable
       authorize :relay, :update?
       @relay.disable!
-      log_action :disable, @relay
       redirect_to admin_relays_path
     end
 
@@ -57,8 +53,7 @@ module Admin
     end
 
     def resource_params
-      params
-        .expect(relay: [:inbox_url])
+      params.require(:relay).permit(:inbox_url)
     end
 
     def warn_signatures_not_enabled!
diff --git a/app/controllers/admin/report_notes_controller.rb b/app/controllers/admin/report_notes_controller.rb
index 10dbe846e4..6b16c29fc7 100644
--- a/app/controllers/admin/report_notes_controller.rb
+++ b/app/controllers/admin/report_notes_controller.rb
@@ -47,8 +47,10 @@ module Admin
     end
 
     def resource_params
-      params
-        .expect(report_note: [:content, :report_id])
+      params.require(:report_note).permit(
+        :content,
+        :report_id
+      )
     end
 
     def set_report_note
diff --git a/app/controllers/admin/roles_controller.rb b/app/controllers/admin/roles_controller.rb
index 2f9af8a6fc..bcfc11159c 100644
--- a/app/controllers/admin/roles_controller.rb
+++ b/app/controllers/admin/roles_controller.rb
@@ -61,8 +61,7 @@ module Admin
     end
 
     def resource_params
-      params
-        .expect(user_role: [:name, :color, :highlighted, :position, permissions_as_keys: []])
+      params.require(:user_role).permit(:name, :color, :highlighted, :position, permissions_as_keys: [])
     end
   end
 end
diff --git a/app/controllers/admin/rules_controller.rb b/app/controllers/admin/rules_controller.rb
index 289b6a98c3..b8def22ba3 100644
--- a/app/controllers/admin/rules_controller.rb
+++ b/app/controllers/admin/rules_controller.rb
@@ -53,8 +53,7 @@ module Admin
     end
 
     def resource_params
-      params
-        .expect(rule: [:text, :hint, :priority])
+      params.require(:rule).permit(:text, :hint, :priority)
     end
   end
 end
diff --git a/app/controllers/admin/sensitive_words_controller.rb b/app/controllers/admin/sensitive_words_controller.rb
index 716dcc708a..24cdd4efcb 100644
--- a/app/controllers/admin/sensitive_words_controller.rb
+++ b/app/controllers/admin/sensitive_words_controller.rb
@@ -37,7 +37,7 @@ module Admin
     end
 
     def settings_params
-      params.expect(form_admin_settings: [*Form::AdminSettings::KEYS])
+      params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
     end
 
     def settings_params_test
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index 2ae5ec8255..338a3638c4 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -28,8 +28,7 @@ module Admin
     end
 
     def settings_params
-      params
-        .expect(form_admin_settings: [*Form::AdminSettings::KEYS])
+      params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
     end
   end
 end
diff --git a/app/controllers/admin/software_updates_controller.rb b/app/controllers/admin/software_updates_controller.rb
index c9be97eb71..52d8cb41e6 100644
--- a/app/controllers/admin/software_updates_controller.rb
+++ b/app/controllers/admin/software_updates_controller.rb
@@ -6,7 +6,7 @@ module Admin
 
     def index
       authorize :software_update, :index?
-      @software_updates = SoftwareUpdate.by_version.filter(&:pending?)
+      @software_updates = SoftwareUpdate.all.sort_by(&:gem_version)
     end
 
     private
diff --git a/app/controllers/admin/special_domains_controller.rb b/app/controllers/admin/special_domains_controller.rb
index b36fe28d6e..0ddbf26786 100644
--- a/app/controllers/admin/special_domains_controller.rb
+++ b/app/controllers/admin/special_domains_controller.rb
@@ -28,7 +28,7 @@ module Admin
     end
 
     def settings_params
-      params.expect(form_admin_settings: [*Form::AdminSettings::KEYS])
+      params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
     end
   end
 end
diff --git a/app/controllers/admin/special_instances_controller.rb b/app/controllers/admin/special_instances_controller.rb
index a16bae13ef..3fd35d474e 100644
--- a/app/controllers/admin/special_instances_controller.rb
+++ b/app/controllers/admin/special_instances_controller.rb
@@ -28,7 +28,7 @@ module Admin
     end
 
     def settings_params
-      params.expect(form_admin_settings: [*Form::AdminSettings::KEYS])
+      params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
     end
   end
 end
diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb
index 956950fe0d..2070a7c70c 100644
--- a/app/controllers/admin/statuses_controller.rb
+++ b/app/controllers/admin/statuses_controller.rb
@@ -16,8 +16,6 @@ module Admin
 
     def show
       authorize [:admin, @status], :show?
-
-      @status_batch_action = Admin::StatusBatchAction.new
     end
 
     def batch
@@ -98,8 +96,7 @@ module Admin
     helper_method :batched_ordered_status_edits
 
     def admin_status_batch_action_params
-      params
-        .expect(admin_status_batch_action: [status_ids: []])
+      params.require(:admin_status_batch_action).permit(status_ids: [])
     end
 
     def after_create_redirect_path
diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb
index a7bfd64794..4759d15bc4 100644
--- a/app/controllers/admin/tags_controller.rb
+++ b/app/controllers/admin/tags_controller.rb
@@ -37,8 +37,7 @@ module Admin
     end
 
     def tag_params
-      params
-        .expect(tag: [:name, :display_name, :trendable, :usable, :listable])
+      params.require(:tag).permit(:name, :display_name, :trendable, :usable, :listable)
     end
 
     def filtered_tags
diff --git a/app/controllers/admin/terms_of_service/distributions_controller.rb b/app/controllers/admin/terms_of_service/distributions_controller.rb
deleted file mode 100644
index c639b083dd..0000000000
--- a/app/controllers/admin/terms_of_service/distributions_controller.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::TermsOfService::DistributionsController < Admin::BaseController
-  before_action :set_terms_of_service
-
-  def create
-    authorize @terms_of_service, :distribute?
-    @terms_of_service.touch(:notification_sent_at)
-    Admin::DistributeTermsOfServiceNotificationWorker.perform_async(@terms_of_service.id)
-    redirect_to admin_terms_of_service_index_path
-  end
-
-  private
-
-  def set_terms_of_service
-    @terms_of_service = TermsOfService.find(params[:terms_of_service_id])
-  end
-end
diff --git a/app/controllers/admin/terms_of_service/drafts_controller.rb b/app/controllers/admin/terms_of_service/drafts_controller.rb
deleted file mode 100644
index 0c67eb9df8..0000000000
--- a/app/controllers/admin/terms_of_service/drafts_controller.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::TermsOfService::DraftsController < Admin::BaseController
-  before_action :set_terms_of_service
-
-  def show
-    authorize :terms_of_service, :create?
-  end
-
-  def update
-    authorize @terms_of_service, :update?
-
-    @terms_of_service.published_at = Time.now.utc if params[:action_type] == 'publish'
-
-    if @terms_of_service.update(resource_params)
-      log_action(:publish, @terms_of_service) if @terms_of_service.published?
-      redirect_to @terms_of_service.published? ? admin_terms_of_service_index_path : admin_terms_of_service_draft_path
-    else
-      render :show
-    end
-  end
-
-  private
-
-  def set_terms_of_service
-    @terms_of_service = TermsOfService.draft.first || TermsOfService.new(text: current_terms_of_service&.text, effective_date: 10.days.from_now)
-  end
-
-  def current_terms_of_service
-    TermsOfService.live.first
-  end
-
-  def resource_params
-    params
-      .expect(terms_of_service: [:text, :changelog, :effective_date])
-  end
-end
diff --git a/app/controllers/admin/terms_of_service/generates_controller.rb b/app/controllers/admin/terms_of_service/generates_controller.rb
deleted file mode 100644
index 0edc87893e..0000000000
--- a/app/controllers/admin/terms_of_service/generates_controller.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::TermsOfService::GeneratesController < Admin::BaseController
-  before_action :set_instance_presenter
-
-  def show
-    authorize :terms_of_service, :create?
-
-    @generator = TermsOfService::Generator.new(
-      domain: @instance_presenter.domain,
-      admin_email: @instance_presenter.contact.email
-    )
-  end
-
-  def create
-    authorize :terms_of_service, :create?
-
-    @generator = TermsOfService::Generator.new(resource_params)
-
-    if @generator.valid?
-      TermsOfService.create!(text: @generator.render)
-      redirect_to admin_terms_of_service_draft_path
-    else
-      render :show
-    end
-  end
-
-  private
-
-  def set_instance_presenter
-    @instance_presenter = InstancePresenter.new
-  end
-
-  def resource_params
-    params
-      .expect(terms_of_service_generator: [*TermsOfService::Generator::VARIABLES])
-  end
-end
diff --git a/app/controllers/admin/terms_of_service/histories_controller.rb b/app/controllers/admin/terms_of_service/histories_controller.rb
deleted file mode 100644
index 8f12341aea..0000000000
--- a/app/controllers/admin/terms_of_service/histories_controller.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::TermsOfService::HistoriesController < Admin::BaseController
-  def show
-    authorize :terms_of_service, :index?
-    @terms_of_service = TermsOfService.published.all
-  end
-end
diff --git a/app/controllers/admin/terms_of_service/previews_controller.rb b/app/controllers/admin/terms_of_service/previews_controller.rb
deleted file mode 100644
index 0a1a966751..0000000000
--- a/app/controllers/admin/terms_of_service/previews_controller.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::TermsOfService::PreviewsController < Admin::BaseController
-  before_action :set_terms_of_service
-
-  def show
-    authorize @terms_of_service, :distribute?
-    @user_count = @terms_of_service.scope_for_notification.count
-  end
-
-  private
-
-  def set_terms_of_service
-    @terms_of_service = TermsOfService.find(params[:terms_of_service_id])
-  end
-end
diff --git a/app/controllers/admin/terms_of_service/tests_controller.rb b/app/controllers/admin/terms_of_service/tests_controller.rb
deleted file mode 100644
index e2483c1005..0000000000
--- a/app/controllers/admin/terms_of_service/tests_controller.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::TermsOfService::TestsController < Admin::BaseController
-  before_action :set_terms_of_service
-
-  def create
-    authorize @terms_of_service, :distribute?
-    UserMailer.terms_of_service_changed(current_user, @terms_of_service).deliver_later!
-    redirect_to admin_terms_of_service_preview_path(@terms_of_service)
-  end
-
-  private
-
-  def set_terms_of_service
-    @terms_of_service = TermsOfService.find(params[:terms_of_service_id])
-  end
-end
diff --git a/app/controllers/admin/terms_of_service_controller.rb b/app/controllers/admin/terms_of_service_controller.rb
deleted file mode 100644
index 10aa5c66ca..0000000000
--- a/app/controllers/admin/terms_of_service_controller.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::TermsOfServiceController < Admin::BaseController
-  def index
-    authorize :terms_of_service, :index?
-    @terms_of_service = TermsOfService.published.first
-  end
-end
diff --git a/app/controllers/admin/trends/links/preview_card_providers_controller.rb b/app/controllers/admin/trends/links/preview_card_providers_controller.rb
index 5a650d5d8c..5e4b4084f8 100644
--- a/app/controllers/admin/trends/links/preview_card_providers_controller.rb
+++ b/app/controllers/admin/trends/links/preview_card_providers_controller.rb
@@ -31,8 +31,7 @@ class Admin::Trends::Links::PreviewCardProvidersController < Admin::BaseControll
   end
 
   def trends_preview_card_provider_batch_params
-    params
-      .expect(trends_preview_card_provider_batch: [:action, preview_card_provider_ids: []])
+    params.require(:trends_preview_card_provider_batch).permit(:action, preview_card_provider_ids: [])
   end
 
   def action_from_button
diff --git a/app/controllers/admin/trends/links_controller.rb b/app/controllers/admin/trends/links_controller.rb
index 68aa73c992..83d68eba63 100644
--- a/app/controllers/admin/trends/links_controller.rb
+++ b/app/controllers/admin/trends/links_controller.rb
@@ -4,7 +4,7 @@ class Admin::Trends::LinksController < Admin::BaseController
   def index
     authorize :preview_card, :review?
 
-    @locales       = PreviewCardTrend.locales
+    @locales       = PreviewCardTrend.pluck('distinct language')
     @preview_cards = filtered_preview_cards.page(params[:page])
     @form          = Trends::PreviewCardBatch.new
   end
@@ -31,8 +31,7 @@ class Admin::Trends::LinksController < Admin::BaseController
   end
 
   def trends_preview_card_batch_params
-    params
-      .expect(trends_preview_card_batch: [:action, preview_card_ids: []])
+    params.require(:trends_preview_card_batch).permit(:action, preview_card_ids: [])
   end
 
   def action_from_button
diff --git a/app/controllers/admin/trends/statuses_controller.rb b/app/controllers/admin/trends/statuses_controller.rb
index 873d777fe3..3d8b53ea8a 100644
--- a/app/controllers/admin/trends/statuses_controller.rb
+++ b/app/controllers/admin/trends/statuses_controller.rb
@@ -4,7 +4,7 @@ class Admin::Trends::StatusesController < Admin::BaseController
   def index
     authorize [:admin, :status], :review?
 
-    @locales  = StatusTrend.locales
+    @locales  = StatusTrend.pluck('distinct language')
     @statuses = filtered_statuses.page(params[:page])
     @form     = Trends::StatusBatch.new
   end
@@ -31,8 +31,7 @@ class Admin::Trends::StatusesController < Admin::BaseController
   end
 
   def trends_status_batch_params
-    params
-      .expect(trends_status_batch: [:action, status_ids: []])
+    params.require(:trends_status_batch).permit(:action, status_ids: [])
   end
 
   def action_from_button
diff --git a/app/controllers/admin/trends/tags_controller.rb b/app/controllers/admin/trends/tags_controller.rb
index 1ccd740686..fcd23fbf66 100644
--- a/app/controllers/admin/trends/tags_controller.rb
+++ b/app/controllers/admin/trends/tags_controller.rb
@@ -31,8 +31,7 @@ class Admin::Trends::TagsController < Admin::BaseController
   end
 
   def trends_tag_batch_params
-    params
-      .expect(trends_tag_batch: [:action, tag_ids: []])
+    params.require(:trends_tag_batch).permit(:action, tag_ids: [])
   end
 
   def action_from_button
diff --git a/app/controllers/admin/users/roles_controller.rb b/app/controllers/admin/users/roles_controller.rb
index e8b58de504..f5dfc643d4 100644
--- a/app/controllers/admin/users/roles_controller.rb
+++ b/app/controllers/admin/users/roles_controller.rb
@@ -28,8 +28,7 @@ module Admin
     end
 
     def resource_params
-      params
-        .expect(user: [:role_id])
+      params.require(:user).permit(:role_id)
     end
   end
 end
diff --git a/app/controllers/admin/warning_presets_controller.rb b/app/controllers/admin/warning_presets_controller.rb
index dcf88294ee..efbf65b119 100644
--- a/app/controllers/admin/warning_presets_controller.rb
+++ b/app/controllers/admin/warning_presets_controller.rb
@@ -52,8 +52,7 @@ module Admin
     end
 
     def warning_preset_params
-      params
-        .expect(account_warning_preset: [:title, :text])
+      params.require(:account_warning_preset).permit(:title, :text)
     end
   end
 end
diff --git a/app/controllers/admin/webhooks_controller.rb b/app/controllers/admin/webhooks_controller.rb
index 31db369637..f1aad7c4b5 100644
--- a/app/controllers/admin/webhooks_controller.rb
+++ b/app/controllers/admin/webhooks_controller.rb
@@ -74,8 +74,7 @@ module Admin
     end
 
     def resource_params
-      params
-        .expect(webhook: [:url, :template, events: []])
+      params.require(:webhook).permit(:url, :template, events: [])
     end
   end
 end
diff --git a/app/controllers/antennas_controller.rb b/app/controllers/antennas_controller.rb
new file mode 100644
index 0000000000..ca7ee5d2a2
--- /dev/null
+++ b/app/controllers/antennas_controller.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+class AntennasController < ApplicationController
+  layout 'admin'
+
+  before_action :authenticate_user!
+  before_action :set_antenna, only: [:edit, :update, :destroy]
+  before_action :set_body_classes
+  before_action :set_cache_headers
+
+  def index
+    @antennas = current_account.antennas.includes(:antenna_domains).includes(:antenna_tags).includes(:antenna_accounts)
+  end
+
+  def edit; end
+
+  def update
+    if @antenna.update(resource_params)
+      redirect_to antennas_path
+    else
+      render action: :edit
+    end
+  end
+
+  def destroy
+    @antenna.destroy
+    redirect_to antennas_path
+  end
+
+  private
+
+  def set_antenna
+    @antenna = current_account.antennas.find(params[:id])
+  end
+
+  def resource_params
+    params.require(:antenna).permit(:title, :available, :expires_in)
+  end
+
+  def thin_resource_params
+    params.require(:antenna).permit(:title)
+  end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
+end
diff --git a/app/controllers/api/fasp/base_controller.rb b/app/controllers/api/fasp/base_controller.rb
deleted file mode 100644
index 690f7e419a..0000000000
--- a/app/controllers/api/fasp/base_controller.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-# frozen_string_literal: true
-
-class Api::Fasp::BaseController < ApplicationController
-  class Error < ::StandardError; end
-
-  DIGEST_PATTERN = /sha-256=:(.*?):/
-  KEYID_PATTERN = /keyid="(.*?)"/
-
-  attr_reader :current_provider
-
-  skip_forgery_protection
-
-  before_action :check_fasp_enabled
-  before_action :require_authentication
-  after_action :sign_response
-
-  private
-
-  def require_authentication
-    validate_content_digest!
-    validate_signature!
-  rescue Error, Linzer::Error, ActiveRecord::RecordNotFound => e
-    logger.debug("FASP Authentication error: #{e}")
-    authentication_error
-  end
-
-  def authentication_error
-    respond_to do |format|
-      format.json { head 401 }
-    end
-  end
-
-  def validate_content_digest!
-    content_digest_header = request.headers['content-digest']
-    raise Error, 'content-digest missing' if content_digest_header.blank?
-
-    digest_received = content_digest_header.match(DIGEST_PATTERN)[1]
-
-    digest_computed = OpenSSL::Digest.base64digest('sha256', request.body&.string || '')
-
-    raise Error, 'content-digest does not match' if digest_received != digest_computed
-  end
-
-  def validate_signature!
-    signature_input = request.headers['signature-input']&.encode('UTF-8')
-    raise Error, 'signature-input is missing' if signature_input.blank?
-
-    keyid = signature_input.match(KEYID_PATTERN)[1]
-    provider = Fasp::Provider.find(keyid)
-    linzer_request = Linzer.new_request(
-      request.method,
-      request.original_url,
-      {},
-      {
-        'content-digest' => request.headers['content-digest'],
-        'signature-input' => signature_input,
-        'signature' => request.headers['signature'],
-      }
-    )
-    message = Linzer::Message.new(linzer_request)
-    key = Linzer.new_ed25519_public_key(provider.provider_public_key_pem, keyid)
-    signature = Linzer::Signature.build(message.headers)
-    Linzer.verify(key, message, signature)
-    @current_provider = provider
-  end
-
-  def sign_response
-    response.headers['content-digest'] = "sha-256=:#{OpenSSL::Digest.base64digest('sha256', response.body || '')}:"
-
-    linzer_response = Linzer.new_response(response.body, response.status, { 'content-digest' => response.headers['content-digest'] })
-    message = Linzer::Message.new(linzer_response)
-    key = Linzer.new_ed25519_key(current_provider.server_private_key_pem)
-    signature = Linzer.sign(key, message, %w(@status content-digest))
-
-    response.headers.merge!(signature.to_h)
-  end
-
-  def check_fasp_enabled
-    raise ActionController::RoutingError unless Mastodon::Feature.fasp_enabled?
-  end
-end
diff --git a/app/controllers/api/fasp/debug/v0/callback/responses_controller.rb b/app/controllers/api/fasp/debug/v0/callback/responses_controller.rb
deleted file mode 100644
index 794e53f095..0000000000
--- a/app/controllers/api/fasp/debug/v0/callback/responses_controller.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-class Api::Fasp::Debug::V0::Callback::ResponsesController < Api::Fasp::BaseController
-  def create
-    Fasp::DebugCallback.create(
-      fasp_provider: current_provider,
-      ip: request.remote_ip,
-      request_body: request.raw_post
-    )
-
-    respond_to do |format|
-      format.json { head 201 }
-    end
-  end
-end
diff --git a/app/controllers/api/fasp/registrations_controller.rb b/app/controllers/api/fasp/registrations_controller.rb
deleted file mode 100644
index fecc992fec..0000000000
--- a/app/controllers/api/fasp/registrations_controller.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-class Api::Fasp::RegistrationsController < Api::Fasp::BaseController
-  skip_before_action :require_authentication
-
-  def create
-    @current_provider = Fasp::Provider.create!(
-      name: params[:name],
-      base_url: params[:baseUrl],
-      remote_identifier: params[:serverId],
-      provider_public_key_base64: params[:publicKey]
-    )
-
-    render json: registration_confirmation
-  end
-
-  private
-
-  def registration_confirmation
-    {
-      faspId: current_provider.id.to_s,
-      publicKey: current_provider.server_public_key_base64,
-      registrationCompletionUri: new_admin_fasp_provider_registration_url(current_provider),
-    }
-  end
-end
diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb
index bdd7732b87..7488fdec7c 100644
--- a/app/controllers/api/v1/accounts/credentials_controller.rb
+++ b/app/controllers/api/v1/accounts/credentials_controller.rb
@@ -14,7 +14,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
     @account = current_account
     UpdateAccountService.new.call(@account, account_params, raise_error: true)
     current_user.update(user_params) if user_params
-    ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
+    ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
     render json: @account, serializer: REST::CredentialAccountSerializer
   rescue ActiveRecord::RecordInvalid => e
     render json: ValidationErrorFormatter.new(e).as_json, status: 422
@@ -34,7 +34,6 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
       :searchability,
       :hide_collections,
       :indexable,
-      attribution_domains: [],
       fields_attributes: [:name, :value]
     )
   end
diff --git a/app/controllers/api/v1/accounts/endorsements_controller.rb b/app/controllers/api/v1/accounts/endorsements_controller.rb
deleted file mode 100644
index 1e21994a90..0000000000
--- a/app/controllers/api/v1/accounts/endorsements_controller.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-class Api::V1::Accounts::EndorsementsController < Api::BaseController
-  include Authorization
-
-  before_action -> { authorize_if_got_token! :read, :'read:accounts' }, only: :index
-  before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index
-  before_action :require_user!, except: :index
-  before_action :set_account
-  before_action :set_endorsed_accounts, only: :index
-  after_action :insert_pagination_headers, only: :index
-
-  def index
-    cache_if_unauthenticated!
-    render json: @endorsed_accounts, each_serializer: REST::AccountSerializer
-  end
-
-  def create
-    AccountPin.find_or_create_by!(account: current_account, target_account: @account)
-    render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
-  end
-
-  def destroy
-    pin = AccountPin.find_by(account: current_account, target_account: @account)
-    pin&.destroy!
-    render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
-  end
-
-  private
-
-  def set_account
-    @account = Account.find(params[:account_id])
-  end
-
-  def set_endorsed_accounts
-    @endorsed_accounts = @account.unavailable? ? [] : paginated_endorsed_accounts
-  end
-
-  def paginated_endorsed_accounts
-    @account.endorsed_accounts.without_suspended.includes(:account_stat, :user).paginate_by_max_id(
-      limit_param(DEFAULT_ACCOUNTS_LIMIT),
-      params[:max_id],
-      params[:since_id]
-    )
-  end
-
-  def relationships_presenter
-    AccountRelationshipsPresenter.new([@account], current_user.account_id)
-  end
-
-  def next_path
-    api_v1_account_endorsements_url pagination_params(max_id: pagination_max_id) if records_continue?
-  end
-
-  def prev_path
-    api_v1_account_endorsements_url pagination_params(since_id: pagination_since_id) unless @endorsed_accounts.empty?
-  end
-
-  def pagination_collection
-    @endorsed_accounts
-  end
-
-  def records_continue?
-    @endorsed_accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
-  end
-end
diff --git a/app/controllers/api/v1/accounts/familiar_followers_controller.rb b/app/controllers/api/v1/accounts/familiar_followers_controller.rb
index 81f0a9ed0f..a49eb2eb27 100644
--- a/app/controllers/api/v1/accounts/familiar_followers_controller.rb
+++ b/app/controllers/api/v1/accounts/familiar_followers_controller.rb
@@ -12,7 +12,7 @@ class Api::V1::Accounts::FamiliarFollowersController < Api::BaseController
   private
 
   def set_accounts
-    @accounts = Account.without_suspended.where(id: account_ids).select(:id, :hide_collections)
+    @accounts = Account.without_suspended.where(id: account_ids).select('id, hide_collections')
   end
 
   def familiar_followers
diff --git a/app/controllers/api/v1/accounts/featured_tags_controller.rb b/app/controllers/api/v1/accounts/featured_tags_controller.rb
index f95846366c..0101fb469b 100644
--- a/app/controllers/api/v1/accounts/featured_tags_controller.rb
+++ b/app/controllers/api/v1/accounts/featured_tags_controller.rb
@@ -17,6 +17,6 @@ class Api::V1::Accounts::FeaturedTagsController < Api::BaseController
   end
 
   def set_featured_tags
-    @featured_tags = @account.unavailable? ? [] : @account.featured_tags
+    @featured_tags = @account.suspended? ? [] : @account.featured_tags
   end
 end
diff --git a/app/controllers/api/v1/accounts/identity_proofs_controller.rb b/app/controllers/api/v1/accounts/identity_proofs_controller.rb
index 02a45e8758..48f293f47a 100644
--- a/app/controllers/api/v1/accounts/identity_proofs_controller.rb
+++ b/app/controllers/api/v1/accounts/identity_proofs_controller.rb
@@ -1,10 +1,6 @@
 # frozen_string_literal: true
 
 class Api::V1::Accounts::IdentityProofsController < Api::BaseController
-  include DeprecationConcern
-
-  deprecate_api '2022-03-30'
-
   before_action :require_user!
   before_action :set_account
 
diff --git a/app/controllers/api/v1/accounts/pins_controller.rb b/app/controllers/api/v1/accounts/pins_controller.rb
new file mode 100644
index 0000000000..0eb13c048c
--- /dev/null
+++ b/app/controllers/api/v1/accounts/pins_controller.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class Api::V1::Accounts::PinsController < Api::BaseController
+  include Authorization
+
+  before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
+  before_action :require_user!
+  before_action :set_account
+
+  def create
+    AccountPin.find_or_create_by!(account: current_account, target_account: @account)
+    render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
+  end
+
+  def destroy
+    pin = AccountPin.find_by(account: current_account, target_account: @account)
+    pin&.destroy!
+    render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
+  end
+
+  private
+
+  def set_account
+    @account = Account.find(params[:account_id])
+  end
+
+  def relationships_presenter
+    AccountRelationshipsPresenter.new([@account], current_user.account_id)
+  end
+end
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index 46838aeb66..050d13b0c8 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -111,8 +111,8 @@ class Api::V1::AccountsController < Api::BaseController
     render json: { error: I18n.t('accounts.self_follow_error') }, status: 403 if current_user.account.id == @account.id
   end
 
-  def relationships(**)
-    AccountRelationshipsPresenter.new([@account], current_user.account_id, **)
+  def relationships(**options)
+    AccountRelationshipsPresenter.new([@account], current_user.account_id, **options)
   end
 
   def account_ids
@@ -124,7 +124,7 @@ class Api::V1::AccountsController < Api::BaseController
   end
 
   def account_params
-    params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone, :invite_code, :date_of_birth)
+    params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone, :invite_code)
   end
 
   def invite
diff --git a/app/controllers/api/v1/annual_reports_controller.rb b/app/controllers/api/v1/annual_reports_controller.rb
index b1aee288dd..9bc8e68ac2 100644
--- a/app/controllers/api/v1/annual_reports_controller.rb
+++ b/app/controllers/api/v1/annual_reports_controller.rb
@@ -17,17 +17,6 @@ class Api::V1::AnnualReportsController < Api::BaseController
            relationships: @relationships
   end
 
-  def show
-    with_read_replica do
-      @presenter = AnnualReportsPresenter.new([@annual_report])
-      @relationships = StatusRelationshipsPresenter.new(@presenter.statuses, current_account.id)
-    end
-
-    render json: @presenter,
-           serializer: REST::AnnualReportsSerializer,
-           relationships: @relationships
-  end
-
   def read
     @annual_report.view!
     render_empty
diff --git a/app/controllers/api/v1/antennas_controller.rb b/app/controllers/api/v1/antennas_controller.rb
index 4040263c00..7cb0c5f093 100644
--- a/app/controllers/api/v1/antennas_controller.rb
+++ b/app/controllers/api/v1/antennas_controller.rb
@@ -21,7 +21,7 @@ class Api::V1::AntennasController < Api::BaseController
   end
 
   def create
-    @antenna = Antenna.create!(antenna_params.merge(account: current_account))
+    @antenna = Antenna.create!({ list_id: 0 }.merge(antenna_params.merge(account: current_account)))
     render json: @antenna, serializer: REST::AntennaSerializer
   end
 
@@ -42,6 +42,6 @@ class Api::V1::AntennasController < Api::BaseController
   end
 
   def antenna_params
-    params.permit(:title, :list_id, :insert_feeds, :stl, :ltl, :with_media_only, :ignore_reblog, :favourite)
+    params.permit(:title, :list_id, :insert_feeds, :stl, :ltl, :with_media_only, :ignore_reblog)
   end
 end
diff --git a/app/controllers/api/v1/featured_tags/suggestions_controller.rb b/app/controllers/api/v1/featured_tags/suggestions_controller.rb
index d533b1af7b..9c72e4380d 100644
--- a/app/controllers/api/v1/featured_tags/suggestions_controller.rb
+++ b/app/controllers/api/v1/featured_tags/suggestions_controller.rb
@@ -5,8 +5,6 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
   before_action :require_user!
   before_action :set_recently_used_tags, only: :index
 
-  RECENT_TAGS_LIMIT = 10
-
   def index
     render json: @recently_used_tags, each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@recently_used_tags, current_user&.account_id)
   end
@@ -14,6 +12,6 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
   private
 
   def set_recently_used_tags
-    @recently_used_tags = Tag.suggestions_for_account(current_account).limit(RECENT_TAGS_LIMIT)
+    @recently_used_tags = Tag.suggestions_for_account(current_account).limit(10)
   end
 end
diff --git a/app/controllers/api/v1/filters_controller.rb b/app/controllers/api/v1/filters_controller.rb
index f8d91c5f7f..c97e9720ad 100644
--- a/app/controllers/api/v1/filters_controller.rb
+++ b/app/controllers/api/v1/filters_controller.rb
@@ -1,10 +1,6 @@
 # frozen_string_literal: true
 
 class Api::V1::FiltersController < Api::BaseController
-  include DeprecationConcern
-
-  deprecate_api '2022-11-14'
-
   before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show]
   before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show]
   before_action :require_user!
diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb
index 4b44cfe531..29a09fceef 100644
--- a/app/controllers/api/v1/follow_requests_controller.rb
+++ b/app/controllers/api/v1/follow_requests_controller.rb
@@ -28,8 +28,8 @@ class Api::V1::FollowRequestsController < Api::BaseController
     @account ||= Account.find(params[:id])
   end
 
-  def relationships(**)
-    AccountRelationshipsPresenter.new([account], current_user.account_id, **)
+  def relationships(**options)
+    AccountRelationshipsPresenter.new([account], current_user.account_id, **options)
   end
 
   def load_accounts
diff --git a/app/controllers/api/v1/instances/terms_of_services_controller.rb b/app/controllers/api/v1/instances/terms_of_services_controller.rb
deleted file mode 100644
index 0a861dd7bb..0000000000
--- a/app/controllers/api/v1/instances/terms_of_services_controller.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-class Api::V1::Instances::TermsOfServicesController < Api::V1::Instances::BaseController
-  before_action :set_terms_of_service
-
-  def show
-    cache_even_if_authenticated!
-    render json: @terms_of_service, serializer: REST::TermsOfServiceSerializer
-  end
-
-  private
-
-  def set_terms_of_service
-    @terms_of_service = begin
-      if params[:date].present?
-        TermsOfService.published.find_by!(effective_date: params[:date])
-      else
-        TermsOfService.live.first || TermsOfService.published.first! # For the case when none of the published terms have become effective yet
-      end
-    end
-  end
-end
diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb
index e01267c000..49da75ed28 100644
--- a/app/controllers/api/v1/instances_controller.rb
+++ b/app/controllers/api/v1/instances_controller.rb
@@ -1,9 +1,15 @@
 # frozen_string_literal: true
 
-class Api::V1::InstancesController < Api::V2::InstancesController
-  include DeprecationConcern
+class Api::V1::InstancesController < Api::BaseController
+  skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
+  skip_around_action :set_locale
 
-  deprecate_api '2022-11-14'
+  vary_by ''
+
+  # Override `current_user` to avoid reading session cookies unless in limited federation mode
+  def current_user
+    super if limited_federation_mode?
+  end
 
   def show
     cache_even_if_authenticated!
diff --git a/app/controllers/api/v1/lists/accounts_controller.rb b/app/controllers/api/v1/lists/accounts_controller.rb
index 616159f05f..b1c0e609d0 100644
--- a/app/controllers/api/v1/lists/accounts_controller.rb
+++ b/app/controllers/api/v1/lists/accounts_controller.rb
@@ -15,12 +15,17 @@ class Api::V1::Lists::AccountsController < Api::BaseController
   end
 
   def create
-    AddAccountsToListService.new.call(@list, Account.find(account_ids))
+    ApplicationRecord.transaction do
+      list_accounts.each do |account|
+        @list.accounts << account
+      end
+    end
+
     render_empty
   end
 
   def destroy
-    RemoveAccountsFromListService.new.call(@list, Account.where(id: account_ids))
+    ListAccount.where(list: @list, account_id: account_ids).destroy_all
     render_empty
   end
 
@@ -38,6 +43,10 @@ class Api::V1::Lists::AccountsController < Api::BaseController
     end
   end
 
+  def list_accounts
+    Account.find(account_ids)
+  end
+
   def account_ids
     Array(resource_params[:account_ids])
   end
diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb
index b019ab6018..0bacd7fdb0 100644
--- a/app/controllers/api/v1/lists_controller.rb
+++ b/app/controllers/api/v1/lists_controller.rb
@@ -7,6 +7,10 @@ class Api::V1::ListsController < Api::BaseController
   before_action :require_user!
   before_action :set_list, except: [:index, :create]
 
+  rescue_from ArgumentError do |e|
+    render json: { error: e.to_s }, status: 422
+  end
+
   def index
     @lists = List.where(account: current_account).all
     render json: @lists, each_serializer: REST::ListSerializer
@@ -34,16 +38,6 @@ class Api::V1::ListsController < Api::BaseController
     render_empty
   end
 
-  def favourite
-    @list.favourite!
-    render json: @list, serializer: REST::ListSerializer
-  end
-
-  def unfavourite
-    @list.unfavourite!
-    render json: @list, serializer: REST::ListSerializer
-  end
-
   private
 
   def set_list
@@ -51,6 +45,6 @@ class Api::V1::ListsController < Api::BaseController
   end
 
   def list_params
-    params.permit(:title, :replies_policy, :exclusive, :notify, :favourite)
+    params.permit(:title, :replies_policy, :exclusive, :notify)
   end
 end
diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb
index c427e055ea..5ea26d55bd 100644
--- a/app/controllers/api/v1/media_controller.rb
+++ b/app/controllers/api/v1/media_controller.rb
@@ -3,8 +3,8 @@
 class Api::V1::MediaController < Api::BaseController
   before_action -> { doorkeeper_authorize! :write, :'write:media' }
   before_action :require_user!
-  before_action :set_media_attachment, except: [:create, :destroy]
-  before_action :check_processing, except: [:create, :destroy]
+  before_action :set_media_attachment, except: [:create]
+  before_action :check_processing, except: [:create]
 
   def show
     render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
@@ -25,15 +25,6 @@ class Api::V1::MediaController < Api::BaseController
     render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
   end
 
-  def destroy
-    @media_attachment = current_account.media_attachments.find(params[:id])
-
-    return render json: in_usage_error, status: 422 unless @media_attachment.status_id.nil?
-
-    @media_attachment.destroy
-    render_empty
-  end
-
   private
 
   def status_code_for_media_attachment
@@ -63,8 +54,4 @@ class Api::V1::MediaController < Api::BaseController
   def processing_error
     { error: 'Error processing thumbnail for uploaded media' }
   end
-
-  def in_usage_error
-    { error: 'Media attachment is currently used by a status' }
-  end
 end
diff --git a/app/controllers/api/v1/polls/votes_controller.rb b/app/controllers/api/v1/polls/votes_controller.rb
index 2833687a38..ad1b82cb52 100644
--- a/app/controllers/api/v1/polls/votes_controller.rb
+++ b/app/controllers/api/v1/polls/votes_controller.rb
@@ -15,7 +15,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
   private
 
   def set_poll
-    @poll = Poll.find(params[:poll_id])
+    @poll = Poll.attached.find(params[:poll_id])
     authorize @poll.status, :show?
   rescue Mastodon::NotPermittedError
     not_found
diff --git a/app/controllers/api/v1/polls_controller.rb b/app/controllers/api/v1/polls_controller.rb
index b4c25476e8..ffc70a8496 100644
--- a/app/controllers/api/v1/polls_controller.rb
+++ b/app/controllers/api/v1/polls_controller.rb
@@ -15,7 +15,7 @@ class Api::V1::PollsController < Api::BaseController
   private
 
   def set_poll
-    @poll = Poll.find(params[:id])
+    @poll = Poll.attached.find(params[:id])
     authorize @poll.status, :show?
   rescue Mastodon::NotPermittedError
     not_found
diff --git a/app/controllers/api/v1/profile/avatars_controller.rb b/app/controllers/api/v1/profile/avatars_controller.rb
index e6c954ed63..bc4d01a597 100644
--- a/app/controllers/api/v1/profile/avatars_controller.rb
+++ b/app/controllers/api/v1/profile/avatars_controller.rb
@@ -7,7 +7,7 @@ class Api::V1::Profile::AvatarsController < Api::BaseController
   def destroy
     @account = current_account
     UpdateAccountService.new.call(@account, { avatar: nil }, raise_error: true)
-    ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
+    ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
     render json: @account, serializer: REST::CredentialAccountSerializer
   end
 end
diff --git a/app/controllers/api/v1/profile/headers_controller.rb b/app/controllers/api/v1/profile/headers_controller.rb
index 4472a01b05..9f4daa2f77 100644
--- a/app/controllers/api/v1/profile/headers_controller.rb
+++ b/app/controllers/api/v1/profile/headers_controller.rb
@@ -7,7 +7,7 @@ class Api::V1::Profile::HeadersController < Api::BaseController
   def destroy
     @account = current_account
     UpdateAccountService.new.call(@account, { header: nil }, raise_error: true)
-    ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
+    ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
     render json: @account, serializer: REST::CredentialAccountSerializer
   end
 end
diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb
index f2c52f2846..e1ad89ee3e 100644
--- a/app/controllers/api/v1/push/subscriptions_controller.rb
+++ b/app/controllers/api/v1/push/subscriptions_controller.rb
@@ -21,7 +21,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
         endpoint: subscription_params[:endpoint],
         key_p256dh: subscription_params[:keys][:p256dh],
         key_auth: subscription_params[:keys][:auth],
-        standard: subscription_params[:standard] || false,
         data: data_params,
         user_id: current_user.id,
         access_token_id: doorkeeper_token.id
@@ -56,12 +55,12 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
   end
 
   def subscription_params
-    params.expect(subscription: [:endpoint, :standard, keys: [:auth, :p256dh]])
+    params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
   end
 
   def data_params
     return {} if params[:data].blank?
 
-    params.expect(data: [:policy, alerts: Notification::TYPES])
+    params.require(:data).permit(:policy, alerts: Notification::TYPES)
   end
 end
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 1217b70752..534347d019 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -67,8 +67,6 @@ class Api::V1::StatusesController < Api::BaseController
     statuses = [@status] + @context.ancestors + @context.descendants + @context.references
 
     render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
-
-    ActivityPub::FetchAllRepliesWorker.perform_async(@status.id) if !current_account.nil? && @status.should_fetch_replies?
   end
 
   def create
@@ -127,7 +125,7 @@ class Api::V1::StatusesController < Api::BaseController
     @status.account.statuses_count = @status.account.statuses_count - 1
     json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true
 
-    RemovalWorker.perform_async(@status.id, { 'redraft' => !truthy_param?(:delete_media) })
+    RemovalWorker.perform_async(@status.id, { 'redraft' => true })
 
     render json: json
   end
diff --git a/app/controllers/api/v1/suggestions_controller.rb b/app/controllers/api/v1/suggestions_controller.rb
index df9346832f..9ba1cef63c 100644
--- a/app/controllers/api/v1/suggestions_controller.rb
+++ b/app/controllers/api/v1/suggestions_controller.rb
@@ -2,9 +2,6 @@
 
 class Api::V1::SuggestionsController < Api::BaseController
   include Authorization
-  include DeprecationConcern
-
-  deprecate_api '2021-05-16', only: [:index]
 
   before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
   before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index
diff --git a/app/controllers/api/v1/trends/tags_controller.rb b/app/controllers/api/v1/trends/tags_controller.rb
index ecac3579fc..b15dd50131 100644
--- a/app/controllers/api/v1/trends/tags_controller.rb
+++ b/app/controllers/api/v1/trends/tags_controller.rb
@@ -1,15 +1,11 @@
 # frozen_string_literal: true
 
 class Api::V1::Trends::TagsController < Api::BaseController
-  include DeprecationConcern
-
   before_action :set_tags
 
   after_action :insert_pagination_headers
 
-  DEFAULT_TAGS_LIMIT = (ENV['MAX_TRENDING_TAGS'] || 10).to_i
-
-  deprecate_api '2022-03-30', only: :index, if: -> { request.path == '/api/v1/trends' }
+  DEFAULT_TAGS_LIMIT = 10
 
   def index
     cache_if_unauthenticated!
@@ -31,9 +27,7 @@ class Api::V1::Trends::TagsController < Api::BaseController
   end
 
   def tags_from_trends
-    scope = Trends.tags.query.allowed.in_locale(content_locale)
-    scope = scope.filtered_for(current_account) if user_signed_in?
-    scope
+    Trends.tags.query.allowed
   end
 
   def next_path
diff --git a/app/controllers/api/v2/instances_controller.rb b/app/controllers/api/v2/instances_controller.rb
index 62adf95260..8346e28830 100644
--- a/app/controllers/api/v2/instances_controller.rb
+++ b/app/controllers/api/v2/instances_controller.rb
@@ -1,16 +1,6 @@
 # frozen_string_literal: true
 
-class Api::V2::InstancesController < Api::BaseController
-  skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
-  skip_around_action :set_locale
-
-  vary_by ''
-
-  # Override `current_user` to avoid reading session cookies unless in limited federation mode
-  def current_user
-    super if limited_federation_mode?
-  end
-
+class Api::V2::InstancesController < Api::V1::InstancesController
   def show
     cache_even_if_authenticated!
     render_with_cache json: InstancePresenter.new, serializer: REST::InstanceSerializer, root: 'instance'
diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb
index 2711071b4a..167d16fc4d 100644
--- a/app/controllers/api/web/push_subscriptions_controller.rb
+++ b/app/controllers/api/web/push_subscriptions_controller.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class Api::Web::PushSubscriptionsController < Api::Web::BaseController
-  before_action :require_user!, except: :destroy
+  before_action :require_user!
   before_action :set_push_subscription, only: :update
   before_action :destroy_previous_subscriptions, only: :create, if: :prior_subscriptions?
   after_action :update_session_with_subscription, only: :create
@@ -17,13 +17,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
     render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
   end
 
-  def destroy
-    push_subscription = ::Web::PushSubscription.find_by_token_for(:unsubscribe, params[:id])
-    push_subscription&.destroy
-
-    head 200
-  end
-
   private
 
   def active_session
@@ -66,7 +59,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
   end
 
   def subscription_params
-    @subscription_params ||= params.expect(subscription: [:standard, :endpoint, keys: [:auth, :p256dh]])
+    @subscription_params ||= params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
   end
 
   def web_push_subscription_params
@@ -76,12 +69,11 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
       endpoint: subscription_params[:endpoint],
       key_auth: subscription_params[:keys][:auth],
       key_p256dh: subscription_params[:keys][:p256dh],
-      standard: subscription_params[:standard] || false,
       user_id: active_session.user_id,
     }
   end
 
   def data_params
-    @data_params ||= params.expect(data: [:policy, alerts: Notification::TYPES])
+    @data_params ||= params.require(:data).permit(:policy, alerts: Notification::TYPES)
   end
 end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1b071e8655..62e3355ae6 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -22,6 +22,7 @@ class ApplicationController < ActionController::Base
   helper_method :use_seamless_external_login?
   helper_method :sso_account_settings
   helper_method :limited_federation_mode?
+  helper_method :body_class_string
   helper_method :skip_csrf_meta_tags?
 
   rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
@@ -31,7 +32,7 @@ class ApplicationController < ActionController::Base
   rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
   rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
 
-  rescue_from(*Mastodon::HTTP_CONNECTION_ERRORS, with: :internal_server_error)
+  rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
   rescue_from Mastodon::RaceConditionError, Stoplight::Error::RedLight, ActiveRecord::SerializationFailure, with: :service_unavailable
 
   rescue_from Seahorse::Client::NetworkingError do |e|
@@ -70,13 +71,7 @@ class ApplicationController < ActionController::Base
   end
 
   def require_functional!
-    return if current_user.functional?
-
-    if current_user.confirmed?
-      redirect_to edit_user_registration_path
-    else
-      redirect_to auth_setup_path
-    end
+    redirect_to edit_user_registration_path unless current_user.functional?
   end
 
   def skip_csrf_meta_tags?
@@ -163,6 +158,10 @@ class ApplicationController < ActionController::Base
     current_user.setting_theme
   end
 
+  def body_class_string
+    @body_classes || ''
+  end
+
   def respond_with_error(code)
     respond_to do |format|
       format.any  { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index 0b6f5b3af4..4d94c80158 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -12,6 +12,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
   before_action :set_sessions, only: [:edit, :update]
   before_action :set_strikes, only: [:edit, :update]
   before_action :require_not_suspended!, only: [:update]
+  before_action :set_cache_headers, only: [:edit, :update]
   before_action :set_rules, only: :new
   before_action :require_rules_acceptance!, only: :new
   before_action :set_registration_form_time, only: :new
@@ -62,7 +63,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
 
   def configure_sign_up_params
     devise_parameter_sanitizer.permit(:sign_up) do |user_params|
-      user_params.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password, :date_of_birth)
+      user_params.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
     end
   end
 
@@ -138,11 +139,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
     set_locale { render :rules }
   end
 
-  def is_flashing_format? # rubocop:disable Naming/PredicateName
-    if params[:action] == 'create'
-      false # Disable flash messages for sign-up
-    else
-      super
-    end
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
   end
 end
diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb
index 5f9f133659..18603a32f2 100644
--- a/app/controllers/auth/sessions_controller.rb
+++ b/app/controllers/auth/sessions_controller.rb
@@ -73,7 +73,7 @@ class Auth::SessionsController < Devise::SessionsController
   end
 
   def user_params
-    params.expect(user: [:email, :password, :otp_attempt, :disable_css, credential: {}])
+    params.require(:user).permit(:email, :password, :otp_attempt, :disable_css, credential: {})
   end
 
   def login_page_params
@@ -174,7 +174,7 @@ class Auth::SessionsController < Devise::SessionsController
   end
 
   def disable_custom_css?
-    user_params[:disable_css].present? && user_params[:disable_css] == '1'
+    user_params[:disable_css].present? && user_params[:disable_css] != '0'
   end
 
   def disable_custom_css!(user)
diff --git a/app/controllers/auth/setup_controller.rb b/app/controllers/auth/setup_controller.rb
index 5e7b14646a..ad872dc607 100644
--- a/app/controllers/auth/setup_controller.rb
+++ b/app/controllers/auth/setup_controller.rb
@@ -18,7 +18,7 @@ class Auth::SetupController < ApplicationController
 
     if @user.update(user_params)
       @user.resend_confirmation_instructions unless @user.confirmed?
-      redirect_to auth_setup_path, notice: t('auth.setup.new_confirmation_instructions_sent')
+      redirect_to auth_setup_path, notice: I18n.t('auth.setup.new_confirmation_instructions_sent')
     else
       render :show
     end
@@ -35,6 +35,6 @@ class Auth::SetupController < ApplicationController
   end
 
   def user_params
-    params.expect(user: [:email])
+    params.require(:user).permit(:email)
   end
 end
diff --git a/app/controllers/backups_controller.rb b/app/controllers/backups_controller.rb
index 076d19874b..5df1af5f2f 100644
--- a/app/controllers/backups_controller.rb
+++ b/app/controllers/backups_controller.rb
@@ -9,15 +9,13 @@ class BackupsController < ApplicationController
   before_action :authenticate_user!
   before_action :set_backup
 
-  BACKUP_LINK_TIMEOUT = 1.hour.freeze
-
   def download
     case Paperclip::Attachment.default_options[:storage]
     when :s3, :azure
-      redirect_to @backup.dump.expiring_url(BACKUP_LINK_TIMEOUT.to_i), allow_other_host: true
+      redirect_to @backup.dump.expiring_url(10), allow_other_host: true
     when :fog
       if Paperclip::Attachment.default_options.dig(:fog_credentials, :openstack_temp_url_key).present?
-        redirect_to @backup.dump.expiring_url(BACKUP_LINK_TIMEOUT.from_now), allow_other_host: true
+        redirect_to @backup.dump.expiring_url(Time.now.utc + 10), allow_other_host: true
       else
         redirect_to full_asset_url(@backup.dump.url), allow_other_host: true
       end
diff --git a/app/controllers/concerns/admin/export_controller_concern.rb b/app/controllers/concerns/admin/export_controller_concern.rb
index ce03b2a24a..6228ae67fe 100644
--- a/app/controllers/concerns/admin/export_controller_concern.rb
+++ b/app/controllers/concerns/admin/export_controller_concern.rb
@@ -24,6 +24,6 @@ module Admin::ExportControllerConcern
   end
 
   def import_params
-    params.expect(admin_import: [:data])
+    params.require(:admin_import).permit(:data)
   end
 end
diff --git a/app/controllers/concerns/api/error_handling.rb b/app/controllers/concerns/api/error_handling.rb
index 9ce4795b02..ad559fe2d7 100644
--- a/app/controllers/concerns/api/error_handling.rb
+++ b/app/controllers/concerns/api/error_handling.rb
@@ -20,7 +20,7 @@ module Api::ErrorHandling
       render json: { error: 'Record not found' }, status: 404
     end
 
-    rescue_from(*Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::UnexpectedResponseError) do
+    rescue_from HTTP::Error, Mastodon::UnexpectedResponseError do
       render json: { error: 'Remote data could not be fetched' }, status: 503
     end
 
diff --git a/app/controllers/concerns/auth/captcha_concern.rb b/app/controllers/concerns/auth/captcha_concern.rb
index c01da21249..cfd93978ce 100644
--- a/app/controllers/concerns/auth/captcha_concern.rb
+++ b/app/controllers/concerns/auth/captcha_concern.rb
@@ -10,7 +10,7 @@ module Auth::CaptchaConcern
   end
 
   def captcha_available?
-    Rails.configuration.x.captcha.secret_key.present? && Rails.configuration.x.captcha.site_key.present?
+    ENV['HCAPTCHA_SECRET_KEY'].present? && ENV['HCAPTCHA_SITE_KEY'].present?
   end
 
   def captcha_enabled?
diff --git a/app/controllers/concerns/cache_concern.rb b/app/controllers/concerns/cache_concern.rb
index 1cdd4d7cf0..3dc0ea840a 100644
--- a/app/controllers/concerns/cache_concern.rb
+++ b/app/controllers/concerns/cache_concern.rb
@@ -38,7 +38,7 @@ module CacheConcern
       return render(options)
     end
 
-    key        = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields]&.join(',')].compact.join(':')
+    key        = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
     expires_in = options.delete(:expires_in) || 3.minutes
     body       = Rails.cache.read(key, raw: true)
 
diff --git a/app/controllers/concerns/challengable_concern.rb b/app/controllers/concerns/challengable_concern.rb
index 7fbc469bdf..c8d1a0bef7 100644
--- a/app/controllers/concerns/challengable_concern.rb
+++ b/app/controllers/concerns/challengable_concern.rb
@@ -58,6 +58,6 @@ module ChallengableConcern
   end
 
   def challenge_params
-    params.expect(form_challenge: [:current_password, :return_to])
+    params.require(:form_challenge).permit(:current_password, :return_to)
   end
 end
diff --git a/app/controllers/concerns/deprecation_concern.rb b/app/controllers/concerns/deprecation_concern.rb
deleted file mode 100644
index ad8de724a1..0000000000
--- a/app/controllers/concerns/deprecation_concern.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module DeprecationConcern
-  extend ActiveSupport::Concern
-
-  class_methods do
-    def deprecate_api(date, sunset: nil, **kwargs)
-      deprecation_timestamp = "@#{date.to_datetime.to_i}"
-      sunset = sunset&.to_date&.httpdate
-
-      before_action(**kwargs) do
-        response.headers['Deprecation'] = deprecation_timestamp
-        response.headers['Sunset'] = sunset if sunset
-      end
-    end
-  end
-end
diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb
index 14742e3b5c..ede299d5a4 100644
--- a/app/controllers/concerns/localized.rb
+++ b/app/controllers/concerns/localized.rb
@@ -25,7 +25,7 @@ module Localized
   end
 
   def available_locale_or_nil(locale_name)
-    locale_name.to_sym if locale_name.respond_to?(:to_sym) && I18n.available_locales.include?(locale_name.to_sym)
+    locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
   end
 
   def content_locale
diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb
index ffe612f468..deabacbc80 100644
--- a/app/controllers/concerns/signature_verification.rb
+++ b/app/controllers/concerns/signature_verification.rb
@@ -10,6 +10,8 @@ module SignatureVerification
   EXPIRATION_WINDOW_LIMIT = 12.hours
   CLOCK_SKEW_MARGIN       = 1.hour
 
+  class SignatureVerificationError < StandardError; end
+
   def require_account_signature!
     render json: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
   end
@@ -32,7 +34,7 @@ module SignatureVerification
 
   def signature_key_id
     signature_params['keyId']
-  rescue Mastodon::SignatureVerificationError
+  rescue SignatureVerificationError
     nil
   end
 
@@ -43,17 +45,17 @@ module SignatureVerification
   def signed_request_actor
     return @signed_request_actor if defined?(@signed_request_actor)
 
-    raise Mastodon::SignatureVerificationError, 'Request not signed' unless signed_request?
-    raise Mastodon::SignatureVerificationError, 'Incompatible request signature. keyId and signature are required' if missing_required_signature_parameters?
-    raise Mastodon::SignatureVerificationError, 'Unsupported signature algorithm (only rsa-sha256 and hs2019 are supported)' unless %w(rsa-sha256 hs2019).include?(signature_algorithm)
-    raise Mastodon::SignatureVerificationError, 'Signed request date outside acceptable time window' unless matches_time_window?
+    raise SignatureVerificationError, 'Request not signed' unless signed_request?
+    raise SignatureVerificationError, 'Incompatible request signature. keyId and signature are required' if missing_required_signature_parameters?
+    raise SignatureVerificationError, 'Unsupported signature algorithm (only rsa-sha256 and hs2019 are supported)' unless %w(rsa-sha256 hs2019).include?(signature_algorithm)
+    raise SignatureVerificationError, 'Signed request date outside acceptable time window' unless matches_time_window?
 
     verify_signature_strength!
     verify_body_digest!
 
     actor = actor_from_key_id(signature_params['keyId'])
 
-    raise Mastodon::SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
+    raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
 
     signature             = Base64.decode64(signature_params['signature'])
     compare_signed_string = build_signed_string(include_query_string: true)
@@ -66,7 +68,7 @@ module SignatureVerification
 
     actor = stoplight_wrapper.run { actor_refresh_key!(actor) }
 
-    raise Mastodon::SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil?
+    raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil?
 
     compare_signed_string = build_signed_string(include_query_string: true)
     return actor unless verify_signature(actor, signature, compare_signed_string).nil?
@@ -76,9 +78,9 @@ module SignatureVerification
     return actor unless verify_signature(actor, signature, compare_signed_string).nil?
 
     fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
-  rescue Mastodon::SignatureVerificationError => e
+  rescue SignatureVerificationError => e
     fail_with! e.message
-  rescue *Mastodon::HTTP_CONNECTION_ERRORS => e
+  rescue HTTP::Error, OpenSSL::SSL::SSLError => e
     fail_with! "Failed to fetch remote data: #{e.message}"
   rescue Mastodon::UnexpectedResponseError
     fail_with! 'Failed to fetch remote data (got unexpected reply from server)'
@@ -102,7 +104,7 @@ module SignatureVerification
   def signature_params
     @signature_params ||= SignatureParser.parse(request.headers['Signature'])
   rescue SignatureParser::ParsingError
-    raise Mastodon::SignatureVerificationError, 'Error parsing signature parameters'
+    raise SignatureVerificationError, 'Error parsing signature parameters'
   end
 
   def signature_algorithm
@@ -114,31 +116,31 @@ module SignatureVerification
   end
 
   def verify_signature_strength!
-    raise Mastodon::SignatureVerificationError, 'Mastodon requires the Date header or (created) pseudo-header to be signed' unless signed_headers.include?('date') || signed_headers.include?('(created)')
-    raise Mastodon::SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(HttpSignatureDraft::REQUEST_TARGET) || signed_headers.include?('digest')
-    raise Mastodon::SignatureVerificationError, 'Mastodon requires the Host header to be signed when doing a GET request' if request.get? && !signed_headers.include?('host')
-    raise Mastodon::SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest')
+    raise SignatureVerificationError, 'Mastodon requires the Date header or (created) pseudo-header to be signed' unless signed_headers.include?('date') || signed_headers.include?('(created)')
+    raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(HttpSignatureDraft::REQUEST_TARGET) || signed_headers.include?('digest')
+    raise SignatureVerificationError, 'Mastodon requires the Host header to be signed when doing a GET request' if request.get? && !signed_headers.include?('host')
+    raise SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest')
   end
 
   def verify_body_digest!
     return unless signed_headers.include?('digest')
-    raise Mastodon::SignatureVerificationError, 'Digest header missing' unless request.headers.key?('Digest')
+    raise SignatureVerificationError, 'Digest header missing' unless request.headers.key?('Digest')
 
     digests = request.headers['Digest'].split(',').map { |digest| digest.split('=', 2) }.map { |key, value| [key.downcase, value] }
     sha256  = digests.assoc('sha-256')
-    raise Mastodon::SignatureVerificationError, "Mastodon only supports SHA-256 in Digest header. Offered algorithms: #{digests.map(&:first).join(', ')}" if sha256.nil?
+    raise SignatureVerificationError, "Mastodon only supports SHA-256 in Digest header. Offered algorithms: #{digests.map(&:first).join(', ')}" if sha256.nil?
 
     return if body_digest == sha256[1]
 
     digest_size = begin
       Base64.strict_decode64(sha256[1].strip).length
     rescue ArgumentError
-      raise Mastodon::SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a valid base64 string. Given digest: #{sha256[1]}"
+      raise SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a valid base64 string. Given digest: #{sha256[1]}"
     end
 
-    raise Mastodon::SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a SHA-256 digest. Given digest: #{sha256[1]}" if digest_size != 32
+    raise SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a SHA-256 digest. Given digest: #{sha256[1]}" if digest_size != 32
 
-    raise Mastodon::SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}"
+    raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}"
   end
 
   def verify_signature(actor, signature, compare_signed_string)
@@ -163,13 +165,13 @@ module SignatureVerification
           "#{HttpSignatureDraft::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
         end
       when '(created)'
-        raise Mastodon::SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'
-        raise Mastodon::SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank?
+        raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'
+        raise SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank?
 
         "(created): #{signature_params['created']}"
       when '(expires)'
-        raise Mastodon::SignatureVerificationError, 'Invalid pseudo-header (expires) for rsa-sha256' unless signature_algorithm == 'hs2019'
-        raise Mastodon::SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank?
+        raise SignatureVerificationError, 'Invalid pseudo-header (expires) for rsa-sha256' unless signature_algorithm == 'hs2019'
+        raise SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank?
 
         "(expires): #{signature_params['expires']}"
       else
@@ -191,7 +193,7 @@ module SignatureVerification
 
       expires_time = Time.at(signature_params['expires'].to_i).utc if signature_params['expires'].present?
     rescue ArgumentError => e
-      raise Mastodon::SignatureVerificationError, "Invalid Date header: #{e.message}"
+      raise SignatureVerificationError, "Invalid Date header: #{e.message}"
     end
 
     expires_time ||= created_time + 5.minutes unless created_time.nil?
@@ -231,9 +233,9 @@ module SignatureVerification
       account
     end
   rescue Mastodon::PrivateNetworkAddressError => e
-    raise Mastodon::SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
+    raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
   rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, ActivityPub::FetchRemoteKeyService::Error, Webfinger::Error => e
-    raise Mastodon::SignatureVerificationError, e.message
+    raise SignatureVerificationError, e.message
   end
 
   def stoplight_wrapper
@@ -249,8 +251,8 @@ module SignatureVerification
 
     ActivityPub::FetchRemoteActorService.new.call(actor.uri, only_key: true, suppress_errors: false)
   rescue Mastodon::PrivateNetworkAddressError => e
-    raise Mastodon::SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
+    raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
   rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, Webfinger::Error => e
-    raise Mastodon::SignatureVerificationError, e.message
+    raise SignatureVerificationError, e.message
   end
 end
diff --git a/app/controllers/concerns/web_app_controller_concern.rb b/app/controllers/concerns/web_app_controller_concern.rb
index ec2256aa9c..9485ecda49 100644
--- a/app/controllers/concerns/web_app_controller_concern.rb
+++ b/app/controllers/concerns/web_app_controller_concern.rb
@@ -7,7 +7,7 @@ module WebAppControllerConcern
     vary_by 'Accept, Accept-Language, Cookie'
 
     before_action :redirect_unauthenticated_to_permalinks!
-    before_action :set_referer_header
+    before_action :set_app_body_class
 
     content_security_policy do |p|
       policy = ContentSecurityPolicy.new
@@ -24,6 +24,10 @@ module WebAppControllerConcern
     !(ENV['ONE_CLICK_SSO_LOGIN'] == 'true' && ENV['OMNIAUTH_ONLY'] == 'true' && Devise.omniauth_providers.length == 1) && current_user.nil?
   end
 
+  def set_app_body_class
+    @body_classes = 'app-body'
+  end
+
   def redirect_unauthenticated_to_permalinks!
     return if user_signed_in? && current_account.moved_to_account_id.nil?
 
@@ -42,10 +46,4 @@ module WebAppControllerConcern
       end
     end
   end
-
-  protected
-
-  def set_referer_header
-    response.set_header('Referrer-Policy', Setting.allow_referrer_origin ? 'strict-origin-when-cross-origin' : 'same-origin')
-  end
 end
diff --git a/app/controllers/custom_css_controller.rb b/app/controllers/custom_css_controller.rb
index 5b98914114..f0ff6f8ca7 100644
--- a/app/controllers/custom_css_controller.rb
+++ b/app/controllers/custom_css_controller.rb
@@ -1,8 +1,10 @@
 # frozen_string_literal: true
 
 class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController
+  before_action :set_user_roles
+
   def show
-    expires_in 1.month, public: true
+    expires_in 3.minutes, public: true
     render content_type: 'text/css'
   end
 
@@ -11,5 +13,19 @@ class CustomCssController < ActionController::Base # rubocop:disable Rails/Appli
   def custom_css_styles
     Setting.custom_css
   end
-  helper_method :custom_css_styles
+
+  def user_custom_css?
+    return false if current_user.nil?
+
+    current_user.setting_use_custom_css && current_user.custom_css_text.present?
+  end
+
+  def user_custom_css
+    current_user.custom_css_text
+  end
+  helper_method :custom_css_styles, :user_custom_css?, :user_custom_css
+
+  def set_user_roles
+    @user_roles = UserRole.providing_styles
+  end
 end
diff --git a/app/controllers/disputes/appeals_controller.rb b/app/controllers/disputes/appeals_controller.rb
index 797f31cf78..98b58d2117 100644
--- a/app/controllers/disputes/appeals_controller.rb
+++ b/app/controllers/disputes/appeals_controller.rb
@@ -21,6 +21,6 @@ class Disputes::AppealsController < Disputes::BaseController
   end
 
   def appeal_params
-    params.expect(appeal: [:text])
+    params.require(:appeal).permit(:text)
   end
 end
diff --git a/app/controllers/disputes/base_controller.rb b/app/controllers/disputes/base_controller.rb
index 07677fd3f3..dd24a1b740 100644
--- a/app/controllers/disputes/base_controller.rb
+++ b/app/controllers/disputes/base_controller.rb
@@ -8,4 +8,11 @@ class Disputes::BaseController < ApplicationController
   skip_before_action :require_functional!
 
   before_action :authenticate_user!
+  before_action :set_cache_headers
+
+  private
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/filters/statuses_controller.rb b/app/controllers/filters/statuses_controller.rb
index d85b017aaa..7ada13f680 100644
--- a/app/controllers/filters/statuses_controller.rb
+++ b/app/controllers/filters/statuses_controller.rb
@@ -6,6 +6,7 @@ class Filters::StatusesController < ApplicationController
   before_action :authenticate_user!
   before_action :set_filter
   before_action :set_status_filters
+  before_action :set_cache_headers
 
   PER_PAGE = 20
 
@@ -33,10 +34,14 @@ class Filters::StatusesController < ApplicationController
   end
 
   def status_filter_batch_action_params
-    params.expect(form_status_filter_batch_action: [status_filter_ids: []])
+    params.require(:form_status_filter_batch_action).permit(status_filter_ids: [])
   end
 
   def action_from_button
     'remove' if params[:remove]
   end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb
index 20b8135908..7746db049f 100644
--- a/app/controllers/filters_controller.rb
+++ b/app/controllers/filters_controller.rb
@@ -5,6 +5,7 @@ class FiltersController < ApplicationController
 
   before_action :authenticate_user!
   before_action :set_filter, only: [:edit, :update, :destroy]
+  before_action :set_cache_headers
 
   def index
     @filters = current_account.custom_filters.includes(:keywords, :statuses).order(:phrase)
@@ -47,6 +48,10 @@ class FiltersController < ApplicationController
   end
 
   def resource_params
-    params.expect(custom_filter: [:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :exclude_quote, :exclude_profile, context: [], keywords_attributes: [[:id, :keyword, :whole_word, :_destroy]]])
+    params.require(:custom_filter).permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :exclude_quote, :exclude_profile, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
+  end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
   end
 end
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index 85f6ccc5e4..44d90ec671 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -46,7 +46,7 @@ class FollowerAccountsController < ApplicationController
   end
 
   def page_url(page)
-    ActivityPub::TagManager.instance.followers_uri_for(@account, page: page) unless page.nil?
+    account_followers_url(@account, page: page) unless page.nil?
   end
 
   def next_page_url
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index fc65333ac4..070852695e 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -6,6 +6,7 @@ class InvitesController < ApplicationController
   layout 'admin'
 
   before_action :authenticate_user!
+  before_action :set_cache_headers
 
   def index
     authorize :invite, :create?
@@ -42,6 +43,10 @@ class InvitesController < ApplicationController
   end
 
   def resource_params
-    params.expect(invite: [:max_uses, :expires_in, :autofollow, :comment])
+    params.require(:invite).permit(:max_uses, :expires_in, :autofollow, :comment)
+  end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
   end
 end
diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb
index f68d85e44e..c4230d62c3 100644
--- a/app/controllers/media_proxy_controller.rb
+++ b/app/controllers/media_proxy_controller.rb
@@ -13,7 +13,7 @@ class MediaProxyController < ApplicationController
   rescue_from ActiveRecord::RecordInvalid, with: :not_found
   rescue_from Mastodon::UnexpectedResponseError, with: :not_found
   rescue_from Mastodon::NotPermittedError, with: :not_found
-  rescue_from(*Mastodon::HTTP_CONNECTION_ERRORS, with: :internal_server_error)
+  rescue_from HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, with: :internal_server_error
 
   def show
     with_redis_lock("media_download:#{params[:id]}") do
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index deafedeaef..66e774425d 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -5,6 +5,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
 
   before_action :store_current_location
   before_action :authenticate_resource_owner!
+  before_action :set_cache_headers
 
   content_security_policy do |p|
     p.form_action(false)
@@ -31,4 +32,8 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
   def truthy_param?(key)
     ActiveModel::Type::Boolean.new.cast(params[key])
   end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
index 8b11a519ea..267409a9ce 100644
--- a/app/controllers/oauth/authorized_applications_controller.rb
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -6,6 +6,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
   before_action :store_current_location
   before_action :authenticate_resource_owner!
   before_action :require_not_suspended!, only: :destroy
+  before_action :set_cache_headers
 
   before_action :set_last_used_at_by_app, only: :index, unless: -> { request.format == :json }
 
@@ -29,7 +30,17 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
     forbidden if current_account.unavailable?
   end
 
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
+
   def set_last_used_at_by_app
-    @last_used_at_by_app = current_resource_owner.applications_last_used
+    @last_used_at_by_app = Doorkeeper::AccessToken
+                           .select('DISTINCT ON (application_id) application_id, last_used_at')
+                           .where(resource_owner_id: current_resource_owner.id)
+                           .where.not(last_used_at: nil)
+                           .order(application_id: :desc, last_used_at: :desc)
+                           .pluck(:application_id, :last_used_at)
+                           .to_h
   end
 end
diff --git a/app/controllers/oauth/userinfo_controller.rb b/app/controllers/oauth/userinfo_controller.rb
deleted file mode 100644
index e268b70dcc..0000000000
--- a/app/controllers/oauth/userinfo_controller.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-class Oauth::UserinfoController < Api::BaseController
-  before_action -> { doorkeeper_authorize! :profile }, only: [:show]
-  before_action :require_user!
-
-  def show
-    @account = current_account
-    render json: @account, serializer: OauthUserinfoSerializer
-  end
-end
diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb
index 7e793fc734..d351afcfb7 100644
--- a/app/controllers/relationships_controller.rb
+++ b/app/controllers/relationships_controller.rb
@@ -6,6 +6,7 @@ class RelationshipsController < ApplicationController
   before_action :authenticate_user!
   before_action :set_accounts, only: :show
   before_action :set_relationships, only: :show
+  before_action :set_cache_headers
 
   helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
 
@@ -35,7 +36,7 @@ class RelationshipsController < ApplicationController
   end
 
   def form_account_batch_params
-    params.expect(form_account_batch: [:action, account_ids: []])
+    params.require(:form_account_batch).permit(:action, account_ids: [])
   end
 
   def following_relationship?
@@ -65,4 +66,8 @@ class RelationshipsController < ApplicationController
       'remove_domains_from_followers'
     end
   end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/settings/aliases_controller.rb b/app/controllers/settings/aliases_controller.rb
index c21d43eeb3..a421b8ede3 100644
--- a/app/controllers/settings/aliases_controller.rb
+++ b/app/controllers/settings/aliases_controller.rb
@@ -30,7 +30,7 @@ class Settings::AliasesController < Settings::BaseController
   private
 
   def resource_params
-    params.expect(account_alias: [:acct])
+    params.require(:account_alias).permit(:acct)
   end
 
   def set_alias
diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb
index 8e39741f89..d6573f9b49 100644
--- a/app/controllers/settings/applications_controller.rb
+++ b/app/controllers/settings/applications_controller.rb
@@ -2,6 +2,7 @@
 
 class Settings::ApplicationsController < Settings::BaseController
   before_action :set_application, only: [:show, :update, :destroy, :regenerate]
+  before_action :prepare_scopes, only: [:create, :update]
 
   def index
     @applications = current_user.applications.order(id: :desc).page(params[:page])
@@ -59,6 +60,16 @@ class Settings::ApplicationsController < Settings::BaseController
   end
 
   def application_params
-    params.expect(doorkeeper_application: [:name, :redirect_uri, :website, scopes: []])
+    params.require(:doorkeeper_application).permit(
+      :name,
+      :redirect_uri,
+      :scopes,
+      :website
+    )
+  end
+
+  def prepare_scopes
+    scopes = params.fetch(:doorkeeper_application, {}).fetch(:scopes, nil)
+    params[:doorkeeper_application][:scopes] = scopes.join(' ') if scopes.is_a? Array
   end
 end
diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb
index 7f2279aa8f..188334ac23 100644
--- a/app/controllers/settings/base_controller.rb
+++ b/app/controllers/settings/base_controller.rb
@@ -4,9 +4,14 @@ class Settings::BaseController < ApplicationController
   layout 'admin'
 
   before_action :authenticate_user!
+  before_action :set_cache_headers
 
   private
 
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
+
   def require_not_suspended!
     forbidden if current_account.unavailable?
   end
diff --git a/app/controllers/settings/deletes_controller.rb b/app/controllers/settings/deletes_controller.rb
index 815d95ad83..16c201b6b3 100644
--- a/app/controllers/settings/deletes_controller.rb
+++ b/app/controllers/settings/deletes_controller.rb
@@ -21,7 +21,7 @@ class Settings::DeletesController < Settings::BaseController
   private
 
   def resource_params
-    params.expect(form_delete_confirmation: [:password, :username])
+    params.require(:form_delete_confirmation).permit(:password, :username)
   end
 
   def require_not_suspended!
diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb
index 263d20eaea..076ed5dadb 100644
--- a/app/controllers/settings/exports_controller.rb
+++ b/app/controllers/settings/exports_controller.rb
@@ -9,7 +9,7 @@ class Settings::ExportsController < Settings::BaseController
   skip_before_action :require_functional!
 
   def show
-    @export_summary = ExportSummary.new(preloaded_account)
+    @export  = Export.new(current_account)
     @backups = current_user.backups
   end
 
@@ -25,15 +25,4 @@ class Settings::ExportsController < Settings::BaseController
 
     redirect_to settings_export_path
   end
-
-  private
-
-  def preloaded_account
-    current_account.tap do |account|
-      ActiveRecord::Associations::Preloader.new(
-        records: [account],
-        associations: :account_stat
-      ).call
-    end
-  end
 end
diff --git a/app/controllers/settings/featured_tags_controller.rb b/app/controllers/settings/featured_tags_controller.rb
index 0f352e1913..90c112e219 100644
--- a/app/controllers/settings/featured_tags_controller.rb
+++ b/app/controllers/settings/featured_tags_controller.rb
@@ -5,8 +5,6 @@ class Settings::FeaturedTagsController < Settings::BaseController
   before_action :set_featured_tag, except: [:index, :create]
   before_action :set_recently_used_tags, only: :index
 
-  RECENT_TAGS_LIMIT = 10
-
   def index
     @featured_tag = FeaturedTag.new
   end
@@ -40,10 +38,10 @@ class Settings::FeaturedTagsController < Settings::BaseController
   end
 
   def set_recently_used_tags
-    @recently_used_tags = Tag.suggestions_for_account(current_account).limit(RECENT_TAGS_LIMIT)
+    @recently_used_tags = Tag.suggestions_for_account(current_account).limit(10)
   end
 
   def featured_tag_params
-    params.expect(featured_tag: [:name])
+    params.require(:featured_tag).permit(:name)
   end
 end
diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb
index be1699315f..569aa07c53 100644
--- a/app/controllers/settings/imports_controller.rb
+++ b/app/controllers/settings/imports_controller.rb
@@ -24,8 +24,6 @@ class Settings::ImportsController < Settings::BaseController
     lists: false,
   }.freeze
 
-  RECENT_IMPORTS_LIMIT = 10
-
   def index
     @import = Form::Import.new(current_account: current_account)
   end
@@ -90,7 +88,7 @@ class Settings::ImportsController < Settings::BaseController
   private
 
   def import_params
-    params.expect(form_import: [:data, :type, :mode])
+    params.require(:form_import).permit(:data, :type, :mode)
   end
 
   def set_bulk_import
@@ -98,6 +96,6 @@ class Settings::ImportsController < Settings::BaseController
   end
 
   def set_recent_imports
-    @recent_imports = current_account.bulk_imports.reorder(id: :desc).limit(RECENT_IMPORTS_LIMIT)
+    @recent_imports = current_account.bulk_imports.reorder(id: :desc).limit(10)
   end
 end
diff --git a/app/controllers/settings/migration/redirects_controller.rb b/app/controllers/settings/migration/redirects_controller.rb
index d850e05e94..6d469f3842 100644
--- a/app/controllers/settings/migration/redirects_controller.rb
+++ b/app/controllers/settings/migration/redirects_controller.rb
@@ -33,6 +33,6 @@ class Settings::Migration::RedirectsController < Settings::BaseController
   private
 
   def resource_params
-    params.expect(form_redirect: [:acct, :current_password, :current_username])
+    params.require(:form_redirect).permit(:acct, :current_password, :current_username)
   end
 end
diff --git a/app/controllers/settings/migrations_controller.rb b/app/controllers/settings/migrations_controller.rb
index 92e3611fd9..62603aba81 100644
--- a/app/controllers/settings/migrations_controller.rb
+++ b/app/controllers/settings/migrations_controller.rb
@@ -27,7 +27,7 @@ class Settings::MigrationsController < Settings::BaseController
   private
 
   def resource_params
-    params.expect(account_migration: [:acct, :current_password, :current_username])
+    params.require(:account_migration).permit(:acct, :current_password, :current_username)
   end
 
   def set_migrations
diff --git a/app/controllers/settings/pictures_controller.rb b/app/controllers/settings/pictures_controller.rb
index 7e61e6d580..58a4325307 100644
--- a/app/controllers/settings/pictures_controller.rb
+++ b/app/controllers/settings/pictures_controller.rb
@@ -8,7 +8,7 @@ module Settings
     def destroy
       if valid_picture?
         if UpdateAccountService.new.call(@account, { @picture => nil, "#{@picture}_remote_url" => '' })
-          ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
+          ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
           redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg'), status: 303
         else
           redirect_to settings_profile_path
diff --git a/app/controllers/settings/preferences/base_controller.rb b/app/controllers/settings/preferences/base_controller.rb
index c2e705da3c..d3e62fb5d9 100644
--- a/app/controllers/settings/preferences/base_controller.rb
+++ b/app/controllers/settings/preferences/base_controller.rb
@@ -25,10 +25,10 @@ class Settings::Preferences::BaseController < Settings::BaseController
   end
 
   def original_user_params
-    params.expect(user: [:locale, :time_zone, :custom_css_text, chosen_languages: [], settings_attributes: UserSettings.keys])
+    params.require(:user).permit(:locale, :time_zone, :custom_css_text, chosen_languages: [], settings_attributes: UserSettings.keys)
   end
 
   def disabled_visibilities_params
-    params.expect(user: [settings_attributes: { enabled_visibilities: [] }])
+    params.require(:user).permit(settings_attributes: { enabled_visibilities: [] })
   end
 end
diff --git a/app/controllers/settings/privacy_controller.rb b/app/controllers/settings/privacy_controller.rb
index 96efa03ccf..1102c89fad 100644
--- a/app/controllers/settings/privacy_controller.rb
+++ b/app/controllers/settings/privacy_controller.rb
@@ -8,7 +8,7 @@ class Settings::PrivacyController < Settings::BaseController
   def update
     if UpdateAccountService.new.call(@account, account_params.except(:settings))
       current_user.update!(settings_attributes: account_params[:settings])
-      ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
+      ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
       redirect_to settings_privacy_path, notice: I18n.t('generic.changes_saved_msg')
     else
       render :show
@@ -18,7 +18,7 @@ class Settings::PrivacyController < Settings::BaseController
   private
 
   def account_params
-    params.expect(account: [:discoverable, :unlocked, :indexable, :show_collections, settings: UserSettings.keys])
+    params.require(:account).permit(:discoverable, :unlocked, :indexable, :show_collections, settings: UserSettings.keys)
   end
 
   def set_account
diff --git a/app/controllers/settings/privacy_extra_controller.rb b/app/controllers/settings/privacy_extra_controller.rb
index f1292e644c..54cedf2c4b 100644
--- a/app/controllers/settings/privacy_extra_controller.rb
+++ b/app/controllers/settings/privacy_extra_controller.rb
@@ -18,7 +18,7 @@ class Settings::PrivacyExtraController < Settings::BaseController
   private
 
   def account_params
-    params.expect(account: [settings: UserSettings.keys])
+    params.require(:account).permit(settings: UserSettings.keys)
   end
 
   def set_account
diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb
index 04a10fbfb9..dc759a060b 100644
--- a/app/controllers/settings/profiles_controller.rb
+++ b/app/controllers/settings/profiles_controller.rb
@@ -9,7 +9,7 @@ class Settings::ProfilesController < Settings::BaseController
 
   def update
     if UpdateAccountService.new.call(@account, account_params)
-      ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
+      ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
       redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
     else
       @account.build_fields
@@ -20,7 +20,7 @@ class Settings::ProfilesController < Settings::BaseController
   private
 
   def account_params
-    params.expect(account: [:display_name, :note, :bio_markdown, :avatar, :header, :bot, :my_actor_type, fields_attributes: [[:name, :value]]])
+    params.require(:account).permit(:display_name, :note, :bio_markdown, :avatar, :header, :bot, :my_actor_type, fields_attributes: [:name, :value])
   end
 
   def set_account
diff --git a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
index eae990e79b..1a0afe58b0 100644
--- a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
+++ b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
@@ -38,7 +38,7 @@ module Settings
       private
 
       def confirmation_params
-        params.expect(form_two_factor_confirmation: [:otp_attempt])
+        params.require(:form_two_factor_confirmation).permit(:otp_attempt)
       end
 
       def prepare_two_factor_form
diff --git a/app/controllers/settings/verifications_controller.rb b/app/controllers/settings/verifications_controller.rb
index 4b949ca72d..4e0663253c 100644
--- a/app/controllers/settings/verifications_controller.rb
+++ b/app/controllers/settings/verifications_controller.rb
@@ -8,7 +8,7 @@ class Settings::VerificationsController < Settings::BaseController
 
   def update
     if UpdateAccountService.new.call(@account, account_params)
-      ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
+      ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
       redirect_to settings_verification_path, notice: I18n.t('generic.changes_saved_msg')
     else
       render :show
@@ -18,9 +18,7 @@ class Settings::VerificationsController < Settings::BaseController
   private
 
   def account_params
-    params.expect(account: [:attribution_domains]).tap do |params|
-      params[:attribution_domains] = params[:attribution_domains].split if params[:attribution_domains]
-    end
+    params.require(:account).permit(:attribution_domains_as_text)
   end
 
   def set_account
diff --git a/app/controllers/severed_relationships_controller.rb b/app/controllers/severed_relationships_controller.rb
index 817abebf62..965753a26f 100644
--- a/app/controllers/severed_relationships_controller.rb
+++ b/app/controllers/severed_relationships_controller.rb
@@ -4,6 +4,7 @@ class SeveredRelationshipsController < ApplicationController
   layout 'admin'
 
   before_action :authenticate_user!
+  before_action :set_cache_headers
 
   before_action :set_event, only: [:following, :followers]
 
@@ -48,4 +49,8 @@ class SeveredRelationshipsController < ApplicationController
   def acct(account)
     account.local? ? account.local_username_and_domain : account.acct
   end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/statuses_cleanup_controller.rb b/app/controllers/statuses_cleanup_controller.rb
index a25e544392..4db02051cc 100644
--- a/app/controllers/statuses_cleanup_controller.rb
+++ b/app/controllers/statuses_cleanup_controller.rb
@@ -5,6 +5,7 @@ class StatusesCleanupController < ApplicationController
 
   before_action :authenticate_user!
   before_action :set_policy
+  before_action :set_cache_headers
 
   def show; end
 
@@ -14,6 +15,8 @@ class StatusesCleanupController < ApplicationController
     else
       render :show
     end
+  rescue ActionController::ParameterMissing
+    # Do nothing
   end
 
   def require_functional!
@@ -27,6 +30,10 @@ class StatusesCleanupController < ApplicationController
   end
 
   def resource_params
-    params.expect(account_statuses_cleanup_policy: [:enabled, :min_status_age, :keep_direct, :keep_pinned, :keep_polls, :keep_media, :keep_self_fav, :keep_self_bookmark, :keep_self_emoji, :min_favs, :min_reblogs, :min_emojis])
+    params.require(:account_statuses_cleanup_policy).permit(:enabled, :min_status_age, :keep_direct, :keep_pinned, :keep_polls, :keep_media, :keep_self_fav, :keep_self_bookmark, :keep_self_emoji, :min_favs, :min_reblogs, :min_emojis)
+  end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
   end
 end
diff --git a/app/controllers/system_css_controller.rb b/app/controllers/system_css_controller.rb
deleted file mode 100644
index dd90491894..0000000000
--- a/app/controllers/system_css_controller.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-class SystemCssController < ActionController::Base # rubocop:disable Rails/ApplicationController
-  def show
-    expires_in 3.minutes, public: true
-    render content_type: 'text/css'
-  end
-end
diff --git a/app/controllers/terms_of_service_controller.rb b/app/controllers/terms_of_service_controller.rb
deleted file mode 100644
index 672fb07915..0000000000
--- a/app/controllers/terms_of_service_controller.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-class TermsOfServiceController < ApplicationController
-  include WebAppControllerConcern
-
-  skip_before_action :require_functional!
-
-  def show
-    expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
-  end
-end
diff --git a/app/helpers/admin/account_moderation_notes_helper.rb b/app/helpers/admin/account_moderation_notes_helper.rb
index 7c931c1157..2a3d954a35 100644
--- a/app/helpers/admin/account_moderation_notes_helper.rb
+++ b/app/helpers/admin/account_moderation_notes_helper.rb
@@ -12,12 +12,12 @@ module Admin::AccountModerationNotesHelper
     )
   end
 
-  def admin_account_inline_link_to(account, path: nil)
+  def admin_account_inline_link_to(account)
     return if account.nil?
 
     link_to(
       account_inline_text(account),
-      path || admin_account_path(account.id),
+      admin_account_path(account.id),
       class: class_names('inline-name-tag', suspended: suspended_account?(account)),
       title: account.acct
     )
diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index 07381cc6ad..54a99dec29 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -35,8 +35,6 @@ module Admin::ActionLogsHelper
       else
         I18n.t('admin.action_logs.deleted_account')
       end
-    when 'Relay'
-      link_to log.human_identifier, admin_relays_path
     end
   end
 
diff --git a/app/helpers/admin/settings_helper.rb b/app/helpers/admin/settings_helper.rb
index 9b950d5a63..6937331e1a 100644
--- a/app/helpers/admin/settings_helper.rb
+++ b/app/helpers/admin/settings_helper.rb
@@ -2,7 +2,7 @@
 
 module Admin::SettingsHelper
   def captcha_available?
-    Rails.configuration.x.captcha.secret_key.present? && Rails.configuration.x.captcha.site_key.present?
+    ENV['HCAPTCHA_SECRET_KEY'].present? && ENV['HCAPTCHA_SITE_KEY'].present?
   end
 
   def login_activity_title(activity)
diff --git a/app/helpers/admin/trends/statuses_helper.rb b/app/helpers/admin/trends/statuses_helper.rb
index 33da1f7216..c7a59660cf 100644
--- a/app/helpers/admin/trends/statuses_helper.rb
+++ b/app/helpers/admin/trends/statuses_helper.rb
@@ -2,18 +2,11 @@
 
 module Admin::Trends::StatusesHelper
   def one_line_preview(status)
-    text = begin
-      if status.local?
-        status.text.split("\n").first
-      else
-        Nokogiri::HTML5(status.text).css('html > body > *').first&.text
-      end
-    rescue ArgumentError
-      # This can happen if one of the Nokogumbo limits is encountered
-      # Unfortunately, it does not use a more precise error class
-      # nor allows more graceful handling
-      ''
-    end
+    text = if status.local?
+             status.text.split("\n").first
+           else
+             Nokogiri::HTML5(status.text).css('html > body > *').first&.text
+           end
 
     return '' if text.blank?
 
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 0d7d0e8117..8b7fbde207 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -122,6 +122,22 @@ module ApplicationHelper
     inline_svg_tag 'check.svg'
   end
 
+  def visibility_icon(status)
+    if status.public_visibility?
+      material_symbol('globe', title: I18n.t('statuses.visibilities.public'))
+    elsif status.unlisted_visibility?
+      material_symbol('lock_open', title: I18n.t('statuses.visibilities.unlisted'))
+    elsif status.public_unlisted_visibility?
+      material_symbol('cloud', title: I18n.t('statuses.visibilities.public_unlisted'))
+    elsif status.login_visibility?
+      material_symbol('key', title: I18n.t('statuses.visibilities.login'))
+    elsif status.private_visibility? || status.limited_visibility?
+      material_symbol('lock', title: I18n.t('statuses.visibilities.private'))
+    elsif status.direct_visibility?
+      material_symbol('alternate_email', title: I18n.t('statuses.visibilities.direct'))
+    end
+  end
+
   def interrelationships_icon(relationships, account_id)
     if relationships.following[account_id] && relationships.followed_by[account_id]
       material_symbol('sync_alt', title: I18n.t('relationships.mutual'), class: 'active passive')
@@ -145,11 +161,10 @@ module ApplicationHelper
   end
 
   def body_classes
-    output = []
+    output = body_class_string.split
     output << content_for(:body_classes)
     output << "theme-#{current_theme.parameterize}"
     output << 'system-font' if current_account&.user&.setting_system_font_ui
-    output << 'custom-scrollbars' unless current_account&.user&.setting_system_scrollbars_ui
     output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion')
     output << 'rtl' if locale_direction == 'rtl'
     output << "content-font-size__#{current_account&.user&.setting_content_font_size}"
@@ -236,12 +251,6 @@ module ApplicationHelper
     full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
   end
 
-  def server_css?
-    return true if current_account&.user.nil?
-
-    current_account.user.setting_use_server_css
-  end
-
   def user_custom_css?
     return false if current_account&.user.nil?
 
@@ -263,14 +272,6 @@ module ApplicationHelper
     I18n.t 'user_mailer.welcome.hashtags_recent_count', people: number_with_delimiter(people), count: people
   end
 
-  def app_store_url_ios
-    'https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974'
-  end
-
-  def app_store_url_android
-    'https://play.google.com/store/apps/details?id=org.joinmastodon.android'
-  end
-
   private
 
   def storage_host_var
diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb
index 077c5272a5..03ca88670f 100644
--- a/app/helpers/context_helper.rb
+++ b/app/helpers/context_helper.rb
@@ -34,7 +34,6 @@ module ContextHelper
     license: { 'schema' => 'http://schema.org#', 'license' => 'schema:license' },
     suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
     attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } },
-    misskey_license: { 'misskey' => 'https://misskey-hub.net/ns#', '_misskey_license' => 'misskey:_misskey_license' },
   }.freeze
 
   def full_context
diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb
index dc7442ac33..f0faf69b43 100644
--- a/app/helpers/formatting_helper.rb
+++ b/app/helpers/formatting_helper.rb
@@ -1,14 +1,6 @@
 # frozen_string_literal: true
 
 module FormattingHelper
-  SYNDICATED_EMOJI_STYLES = <<~CSS.squish
-    height: 1.1em;
-    margin: -.2ex .15em .2ex;
-    object-fit: contain;
-    vertical-align: middle;
-    width: 1.1em;
-  CSS
-
   def html_aware_format(text, local, options = {})
     HtmlAwareFormatter.new(text, local, options).to_s
   end
@@ -31,33 +23,46 @@ module FormattingHelper
   end
 
   def status_content_format(status)
-    MastodonOTELTracer.in_span('HtmlAwareFormatter rendering') do |span|
-      span.add_attributes(
-        'app.formatter.content.type' => 'status',
-        'app.formatter.content.origin' => status.local? ? 'local' : 'remote'
-      )
+    html_aware_format(status.text, status.local?, markdown: status.markdown, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []))
+  end
 
-      html_aware_format(status.text, status.local?, markdown: status.markdown, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []))
-    end
+  def emoji_name_format(emoji_reaction, status)
+    html_aware_format(emoji_reaction['url'].present? ? ":#{emoji_reaction['name']}:" : emoji_reaction['name'], status.local?, markdown: status.markdown)
   end
 
   def rss_status_content_format(status)
+    html = status_content_format(status)
+
+    before_html = if status.spoiler_text?
+                    tag.p do
+                      tag.strong do
+                        I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale)
+                      end
+
+                      status.spoiler_text
+                    end + tag.hr
+                  end
+
+    after_html = if status.preloadable_poll
+                   tag.p do
+                     safe_join(
+                       status.preloadable_poll.options.map do |o|
+                         tag.send(status.preloadable_poll.multiple? ? 'checkbox' : 'radio', o, disabled: true)
+                       end,
+                       tag.br
+                     )
+                   end
+                 end
+
     prerender_custom_emojis(
-      wrapped_status_content_format(status),
+      safe_join([before_html, html, after_html]),
       status.emojis,
-      style: SYNDICATED_EMOJI_STYLES
+      style: 'min-width: 1.1em; height: 1.1em; object-fit: contain; vertical-align: middle; margin: -.2ex .15em .2ex'
     ).to_str
   end
 
   def account_bio_format(account)
-    MastodonOTELTracer.in_span('HtmlAwareFormatter rendering') do |span|
-      span.add_attributes(
-        'app.formatter.content.type' => 'account_bio',
-        'app.formatter.content.origin' => account.local? ? 'local' : 'remote'
-      )
-
-      html_aware_format(account.note, account.local?, markdown: account.user&.setting_bio_markdown)
-    end
+    html_aware_format(account.note, account.local?, markdown: account.user&.setting_bio_markdown)
   end
 
   def account_field_value_format(field, with_rel_me: true)
@@ -67,51 +72,4 @@ module FormattingHelper
       html_aware_format(field.value, field.account.local?, markdown: false, with_rel_me: with_rel_me, with_domains: true, multiline: false)
     end
   end
-
-  def markdown(text)
-    Redcarpet::Markdown.new(Redcarpet::Render::HTML, escape_html: true, no_images: true).render(text).html_safe # rubocop:disable Rails/OutputSafety
-  end
-
-  private
-
-  def wrapped_status_content_format(status)
-    safe_join [
-      rss_content_preroll(status),
-      status_content_format(status),
-      rss_content_postroll(status),
-    ]
-  end
-
-  def rss_content_preroll(status)
-    if status.spoiler_text?
-      safe_join [
-        tag.p { spoiler_with_warning(status) },
-        tag.hr,
-      ]
-    end
-  end
-
-  def spoiler_with_warning(status)
-    safe_join [
-      tag.strong { I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale) },
-      status.spoiler_text,
-    ]
-  end
-
-  def rss_content_postroll(status)
-    if status.preloadable_poll
-      tag.p do
-        poll_option_tags(status)
-      end
-    end
-  end
-
-  def poll_option_tags(status)
-    safe_join(
-      status.preloadable_poll.options.map do |option|
-        tag.send(status.preloadable_poll.multiple? ? 'checkbox' : 'radio', option, disabled: true)
-      end,
-      tag.br
-    )
-  end
 end
diff --git a/app/helpers/json_ld_helper.rb b/app/helpers/jsonld_helper.rb
similarity index 84%
rename from app/helpers/json_ld_helper.rb
rename to app/helpers/jsonld_helper.rb
index 693cdf730f..2a5c2d8826 100644
--- a/app/helpers/json_ld_helper.rb
+++ b/app/helpers/jsonld_helper.rb
@@ -163,49 +163,24 @@ module JsonLdHelper
     end
   end
 
-  # Fetch the resource given by uri.
-  # @param uri [String]
-  # @param id_is_known [Boolean]
-  # @param on_behalf_of [nil, Account]
-  # @param raise_on_error [Symbol<:all, :temporary, :none>] See {#fetch_resource_without_id_validation} for possible values
-  def fetch_resource(uri, id_is_known, on_behalf_of = nil, raise_on_error: :none, request_options: {})
+  def fetch_resource(uri, id_is_known, on_behalf_of = nil, request_options: {})
     unless id_is_known
-      json = fetch_resource_without_id_validation(uri, on_behalf_of, raise_on_error: raise_on_error)
+      json = fetch_resource_without_id_validation(uri, on_behalf_of)
 
       return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])
 
       uri = json['id']
     end
 
-    json = fetch_resource_without_id_validation(uri, on_behalf_of, raise_on_error: raise_on_error, request_options: request_options)
+    json = fetch_resource_without_id_validation(uri, on_behalf_of, request_options: request_options)
     json.present? && json['id'] == uri ? json : nil
   end
 
-  # Fetch the resource given by uri
-  #
-  # If an error is raised, it contains the response and can be captured for handling like
-  #
-  #     begin
-  #       fetch_resource_without_id_validation(uri, nil, true)
-  #     rescue Mastodon::UnexpectedResponseError => e
-  #       e.response
-  #     end
-  #
-  # @param uri [String]
-  # @param on_behalf_of [nil, Account]
-  # @param raise_on_error [Symbol<:all, :temporary, :none>]
-  #   - +:all+ - raise if response code is not in the 2xx range
-  #   - +:temporary+ - raise if the response code is not an "unsalvageable error" like a 404
-  #     (see {#response_error_unsalvageable} )
-  #   - +:none+ - do not raise, return +nil+
-  def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_error: :none, request_options: {})
+  def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false, request_options: {})
     on_behalf_of ||= Account.representative
 
     build_request(uri, on_behalf_of, options: request_options).perform do |response|
-      raise Mastodon::UnexpectedResponseError, response if !response_successful?(response) && (
-        raise_on_error == :all ||
-        (!response_error_unsalvageable?(response) && raise_on_error == :temporary)
-      )
+      raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
 
       body_to_json(response.body_with_limit) if response.code == 200 && valid_activitypub_content_type?(response)
     end
diff --git a/app/helpers/kmyblue_capabilities_helper.rb b/app/helpers/kmyblue_capabilities_helper.rb
index 279505bec8..075869ee60 100644
--- a/app/helpers/kmyblue_capabilities_helper.rb
+++ b/app/helpers/kmyblue_capabilities_helper.rb
@@ -20,8 +20,6 @@ module KmyblueCapabilitiesHelper
       kmyblue_circle_history
       kmyblue_list_notification
       kmyblue_server_features
-      favourite_list
-      kmyblue_favourite_antenna
     )
 
     capabilities << :full_text_search if Chewy.enabled?
diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb
index 0a8ebcde54..c0c8001eaa 100644
--- a/app/helpers/languages_helper.rb
+++ b/app/helpers/languages_helper.rb
@@ -193,7 +193,6 @@ module LanguagesHelper
     ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
     cnr: ['Montenegrin', 'crnogorski'].freeze,
     csb: ['Kashubian', 'Kaszëbsczi'].freeze,
-    gsw: ['Swiss German', 'Schwiizertütsch'].freeze,
     jbo: ['Lojban', 'la .lojban.'].freeze,
     kab: ['Kabyle', 'Taqbaylit'].freeze,
     ldn: ['Láadan', 'Láadan'].freeze,
diff --git a/app/helpers/media_component_helper.rb b/app/helpers/media_component_helper.rb
index 269566528a..60ccdd0835 100644
--- a/app/helpers/media_component_helper.rb
+++ b/app/helpers/media_component_helper.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 module MediaComponentHelper
-  def render_video_component(status, **)
+  def render_video_component(status, **options)
     video = status.ordered_media_attachments.first
 
     meta = video.file.meta || {}
@@ -18,14 +18,14 @@ module MediaComponentHelper
       media: [
         serialize_media_attachment(video),
       ].as_json,
-    }.merge(**)
+    }.merge(**options)
 
     react_component :video, component_params do
       render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
     end
   end
 
-  def render_audio_component(status, **)
+  def render_audio_component(status, **options)
     audio = status.ordered_media_attachments.first
 
     meta = audio.file.meta || {}
@@ -38,19 +38,19 @@ module MediaComponentHelper
       foregroundColor: meta.dig('colors', 'foreground'),
       accentColor: meta.dig('colors', 'accent'),
       duration: meta.dig('original', 'duration'),
-    }.merge(**)
+    }.merge(**options)
 
     react_component :audio, component_params do
       render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
     end
   end
 
-  def render_media_gallery_component(status, **)
+  def render_media_gallery_component(status, **options)
     component_params = {
       sensitive: sensitive_viewer?(status, current_account),
       autoplay: prefers_autoplay?,
       media: status.ordered_media_attachments.map { |a| serialize_media_attachment(a).as_json },
-    }.merge(**)
+    }.merge(**options)
 
     react_component :media_gallery, component_params do
       render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
diff --git a/app/helpers/registration_helper.rb b/app/helpers/registration_helper.rb
index f387a48309..c3db46c027 100644
--- a/app/helpers/registration_helper.rb
+++ b/app/helpers/registration_helper.rb
@@ -18,6 +18,6 @@ module RegistrationHelper
   end
 
   def ip_blocked?(remote_ip)
-    IpBlock.severity_sign_up_block.containing(remote_ip.to_s).exists?
+    IpBlock.where(severity: :sign_up_block).exists?(['ip >>= ?', remote_ip.to_s])
   end
 end
diff --git a/app/helpers/routing_helper.rb b/app/helpers/routing_helper.rb
index 22efc5f092..15d988f64d 100644
--- a/app/helpers/routing_helper.rb
+++ b/app/helpers/routing_helper.rb
@@ -14,8 +14,8 @@ module RoutingHelper
     end
   end
 
-  def full_asset_url(source, **)
-    source = ActionController::Base.helpers.asset_url(source, **) unless use_storage?
+  def full_asset_url(source, **options)
+    source = ActionController::Base.helpers.asset_url(source, **options) unless use_storage?
 
     URI.join(asset_host, source).to_s
   end
@@ -24,12 +24,12 @@ module RoutingHelper
     Rails.configuration.action_controller.asset_host || root_url
   end
 
-  def frontend_asset_path(source, **)
-    asset_pack_path("media/#{source}", **)
+  def frontend_asset_path(source, **options)
+    asset_pack_path("media/#{source}", **options)
   end
 
-  def frontend_asset_url(source, **)
-    full_asset_url(frontend_asset_path(source, **))
+  def frontend_asset_url(source, **options)
+    full_asset_url(frontend_asset_path(source, **options))
   end
 
   def use_storage?
diff --git a/app/helpers/self_destruct_helper.rb b/app/helpers/self_destruct_helper.rb
index f1927b1e04..78557c25e5 100644
--- a/app/helpers/self_destruct_helper.rb
+++ b/app/helpers/self_destruct_helper.rb
@@ -1,11 +1,9 @@
 # frozen_string_literal: true
 
 module SelfDestructHelper
-  VERIFY_PURPOSE = 'self-destruct'
-
   def self.self_destruct?
-    value = Rails.configuration.x.mastodon.self_destruct_value
-    value.present? && Rails.application.message_verifier(VERIFY_PURPOSE).verify(value) == ENV['LOCAL_DOMAIN']
+    value = ENV.fetch('SELF_DESTRUCT', nil)
+    value.present? && Rails.application.message_verifier('self-destruct').verify(value) == ENV['LOCAL_DOMAIN']
   rescue ActiveSupport::MessageVerifier::InvalidSignature
     false
   end
diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb
index 7e42dd4623..938fcf5b53 100644
--- a/app/helpers/statuses_helper.rb
+++ b/app/helpers/statuses_helper.rb
@@ -1,6 +1,9 @@
 # frozen_string_literal: true
 
 module StatusesHelper
+  EMBEDDED_CONTROLLER = 'statuses'
+  EMBEDDED_ACTION = 'embed'
+
   VISIBLITY_ICONS = {
     public: 'globe',
     unlisted: 'lock_open',
@@ -12,7 +15,7 @@ module StatusesHelper
   }.freeze
 
   def nothing_here(extra_classes = '')
-    tag.div(class: ['nothing-here', extra_classes]) do
+    content_tag(:div, class: "nothing-here #{extra_classes}") do
       t('accounts.nothing_here')
     end
   end
@@ -60,10 +63,18 @@ module StatusesHelper
     components.compact_blank.join("\n\n")
   end
 
+  def stream_link_target
+    embedded_view? ? '_blank' : nil
+  end
+
   def visibility_icon(status)
     VISIBLITY_ICONS[status.visibility.to_sym]
   end
 
+  def embedded_view?
+    params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
+  end
+
   def prefers_autoplay?
     ActiveModel::Type::Boolean.new.cast(params[:autoplay]) || current_user&.setting_auto_play_gif
   end
diff --git a/app/helpers/theme_helper.rb b/app/helpers/theme_helper.rb
index f4d88a1ef0..fab899a533 100644
--- a/app/helpers/theme_helper.rb
+++ b/app/helpers/theme_helper.rb
@@ -23,51 +23,8 @@ module ThemeHelper
     end
   end
 
-  def custom_stylesheet
-    if active_custom_stylesheet.present?
-      stylesheet_link_tag(
-        custom_css_path(active_custom_stylesheet),
-        host: root_url,
-        media: :all,
-        skip_pipeline: true
-      )
-    end
-  end
-
-  def system_stylesheet
-    stylesheet_link_tag(
-      system_css_path,
-      host: root_url,
-      media: :all,
-      skip_pipeline: true
-    )
-  end
-
-  def user_custom_stylesheet
-    stylesheet_link_tag(
-      user_custom_css_path({ version: user_custom_css_version }),
-      host: root_url,
-      media: :all,
-      skip_pipeline: true
-    )
-  end
-
   private
 
-  def active_custom_stylesheet
-    if cached_custom_css_digest.present?
-      [:custom, cached_custom_css_digest.to_s.first(8)]
-        .compact_blank
-        .join('-')
-    end
-  end
-
-  def cached_custom_css_digest
-    Rails.cache.fetch(:setting_digest_custom_css) do
-      Setting.custom_css&.then { |content| Digest::SHA256.hexdigest(content) }
-    end
-  end
-
   def theme_color_for(theme)
     theme == 'mastodon-light' ? Themes::THEME_COLORS[:light] : Themes::THEME_COLORS[:dark]
   end
diff --git a/app/inputs/date_of_birth_input.rb b/app/inputs/date_of_birth_input.rb
deleted file mode 100644
index 131234b02e..0000000000
--- a/app/inputs/date_of_birth_input.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-class DateOfBirthInput < SimpleForm::Inputs::Base
-  OPTIONS = [
-    { autocomplete: 'bday-day', maxlength: 2, pattern: '[0-9]+', placeholder: 'DD' }.freeze,
-    { autocomplete: 'bday-month', maxlength: 2, pattern: '[0-9]+', placeholder: 'MM' }.freeze,
-    { autocomplete: 'bday-year', maxlength: 4, pattern: '[0-9]+', placeholder: 'YYYY' }.freeze,
-  ].freeze
-
-  def input(wrapper_options = nil)
-    merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
-    merged_input_options[:inputmode] = 'numeric'
-
-    values = (object.public_send(attribute_name) || '').split('.')
-
-    safe_join(Array.new(3) do |index|
-      options = merged_input_options.merge(OPTIONS[index]).merge id: generate_id(index), 'aria-label': I18n.t("simple_form.labels.user.date_of_birth_#{index + 1}i"), value: values[index]
-      @builder.text_field("#{attribute_name}(#{index + 1}i)", options)
-    end)
-  end
-
-  def label_target
-    "#{attribute_name}_1i"
-  end
-
-  private
-
-  def generate_id(index)
-    "#{object_name}_#{attribute_name}_#{index + 1}i"
-  end
-end
diff --git a/app/javascript/entrypoints/embed.tsx b/app/javascript/entrypoints/embed.tsx
index 6c091e4d07..f8c824d287 100644
--- a/app/javascript/entrypoints/embed.tsx
+++ b/app/javascript/entrypoints/embed.tsx
@@ -1,7 +1,7 @@
 import './public-path';
 import { createRoot } from 'react-dom/client';
 
-import { afterInitialRender } from 'mastodon/hooks/useRenderSignal';
+import { afterInitialRender } from 'mastodon/../hooks/useRenderSignal';
 
 import { start } from '../mastodon/common';
 import { Status } from '../mastodon/features/standalone/status';
@@ -60,10 +60,6 @@ window.addEventListener('message', (e) => {
 
   const data = e.data;
 
-  // Only set overflow to `hidden` once we got the expected `message` so the post can still be scrolled if
-  // embedded without parent Javascript support
-  document.body.style.overflow = 'hidden';
-
   // We use a timeout to allow for the React page to render before calculating the height
   afterInitialRender(() => {
     window.parent.postMessage(
diff --git a/app/javascript/entrypoints/public.tsx b/app/javascript/entrypoints/public.tsx
index 9374d6b2d1..d33e00d5da 100644
--- a/app/javascript/entrypoints/public.tsx
+++ b/app/javascript/entrypoints/public.tsx
@@ -68,7 +68,7 @@ function loaded() {
 
     if (id) message = localeData[id];
 
-    message ??= defaultMessage as string;
+    if (!message) message = defaultMessage as string;
 
     const messageFormat = new IntlMessageFormat(message, locale);
     return messageFormat.format(values) as string;
@@ -119,11 +119,7 @@ function loaded() {
         formattedContent = dateFormat.format(datetime);
       }
 
-      const timeGiven = content.dateTime.includes('T');
-      content.title = timeGiven
-        ? dateTimeFormat.format(datetime)
-        : dateFormat.format(datetime);
-
+      content.title = formattedContent;
       content.textContent = formattedContent;
     });
 
@@ -234,6 +230,62 @@ function loaded() {
       }
     },
   );
+
+  Rails.delegate(
+    document,
+    'button.status__content__spoiler-link',
+    'click',
+    function () {
+      if (!(this instanceof HTMLButtonElement)) return;
+
+      const statusEl = this.parentNode?.parentNode;
+
+      if (
+        !(
+          statusEl instanceof HTMLDivElement &&
+          statusEl.classList.contains('.status__content')
+        )
+      )
+        return;
+
+      if (statusEl.dataset.spoiler === 'expanded') {
+        statusEl.dataset.spoiler = 'folded';
+        this.textContent = new IntlMessageFormat(
+          localeData['status.show_more'] ?? 'Show more',
+          locale,
+        ).format() as string;
+      } else {
+        statusEl.dataset.spoiler = 'expanded';
+        this.textContent = new IntlMessageFormat(
+          localeData['status.show_less'] ?? 'Show less',
+          locale,
+        ).format() as string;
+      }
+    },
+  );
+
+  document
+    .querySelectorAll<HTMLButtonElement>('button.status__content__spoiler-link')
+    .forEach((spoilerLink) => {
+      const statusEl = spoilerLink.parentNode?.parentNode;
+
+      if (
+        !(
+          statusEl instanceof HTMLDivElement &&
+          statusEl.classList.contains('.status__content')
+        )
+      )
+        return;
+
+      const message =
+        statusEl.dataset.spoiler === 'expanded'
+          ? (localeData['status.show_less'] ?? 'Show less')
+          : (localeData['status.show_more'] ?? 'Show more');
+      spoilerLink.textContent = new IntlMessageFormat(
+        message,
+        locale,
+      ).format() as string;
+    });
 }
 
 Rails.delegate(
@@ -275,24 +327,31 @@ Rails.delegate(document, '.input-copy button', 'click', ({ target }) => {
 
   if (!input) return;
 
-  navigator.clipboard
-    .writeText(input.value)
-    .then(() => {
+  const oldReadOnly = input.readOnly;
+
+  input.readOnly = false;
+  input.focus();
+  input.select();
+  input.setSelectionRange(0, input.value.length);
+
+  try {
+    if (document.execCommand('copy')) {
+      input.blur();
+
       const parent = target.parentElement;
 
-      if (parent) {
-        parent.classList.add('copied');
+      if (!parent) return;
+      parent.classList.add('copied');
 
-        setTimeout(() => {
-          parent.classList.remove('copied');
-        }, 700);
-      }
+      setTimeout(() => {
+        parent.classList.remove('copied');
+      }, 700);
+    }
+  } catch (err) {
+    console.error(err);
+  }
 
-      return true;
-    })
-    .catch((error: unknown) => {
-      console.error(error);
-    });
+  input.readOnly = oldReadOnly;
 });
 
 const toggleSidebar = () => {
@@ -387,24 +446,6 @@ Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => {
   });
 });
 
-Rails.delegate(document, '.rules-list button', 'click', ({ target }) => {
-  if (!(target instanceof HTMLElement)) {
-    return;
-  }
-
-  const button = target.closest('button');
-
-  if (!button) {
-    return;
-  }
-
-  if (button.ariaExpanded === 'true') {
-    button.ariaExpanded = 'false';
-  } else {
-    button.ariaExpanded = 'true';
-  }
-});
-
 function main() {
   ready(loaded).catch((error: unknown) => {
     console.error(error);
diff --git a/app/javascript/mastodon/hooks/useHovering.ts b/app/javascript/hooks/useHovering.ts
similarity index 100%
rename from app/javascript/mastodon/hooks/useHovering.ts
rename to app/javascript/hooks/useHovering.ts
diff --git a/app/javascript/mastodon/hooks/useLinks.ts b/app/javascript/hooks/useLinks.ts
similarity index 53%
rename from app/javascript/mastodon/hooks/useLinks.ts
rename to app/javascript/hooks/useLinks.ts
index abaa108f6b..f08b9500da 100644
--- a/app/javascript/mastodon/hooks/useLinks.ts
+++ b/app/javascript/hooks/useLinks.ts
@@ -2,8 +2,6 @@ import { useCallback } from 'react';
 
 import { useHistory } from 'react-router-dom';
 
-import { isFulfilled, isRejected } from '@reduxjs/toolkit';
-
 import { openURL } from 'mastodon/actions/search';
 import { useAppDispatch } from 'mastodon/store';
 
@@ -14,9 +12,6 @@ const isHashtagClick = (element: HTMLAnchorElement) =>
   element.textContent?.[0] === '#' ||
   element.previousSibling?.textContent?.endsWith('#');
 
-const isFeaturedHashtagClick = (element: HTMLAnchorElement) =>
-  isHashtagClick(element) && element.href.includes('/tagged/');
-
 export const useLinks = () => {
   const history = useHistory();
   const dispatch = useAppDispatch();
@@ -32,36 +27,13 @@ export const useLinks = () => {
     [history],
   );
 
-  const handleFeaturedHashtagClick = useCallback(
-    (element: HTMLAnchorElement) => {
-      const { textContent, href } = element;
-
-      if (!textContent) return;
-
-      const url = new URL(href);
-
-      history.push(url.pathname);
-    },
-    [history],
-  );
-
   const handleMentionClick = useCallback(
-    async (element: HTMLAnchorElement) => {
-      const result = await dispatch(openURL({ url: element.href }));
-
-      if (isFulfilled(result)) {
-        if (result.payload.accounts[0]) {
-          history.push(`/@${result.payload.accounts[0].acct}`);
-        } else if (result.payload.statuses[0]) {
-          history.push(
-            `/@${result.payload.statuses[0].account.acct}/${result.payload.statuses[0].id}`,
-          );
-        } else {
+    (element: HTMLAnchorElement) => {
+      dispatch(
+        openURL(element.href, history, () => {
           window.location.href = element.href;
-        }
-      } else if (isRejected(result)) {
-        window.location.href = element.href;
-      }
+        }),
+      );
     },
     [dispatch, history],
   );
@@ -76,16 +48,13 @@ export const useLinks = () => {
 
       if (isMentionClick(target)) {
         e.preventDefault();
-        void handleMentionClick(target);
-      } else if (isFeaturedHashtagClick(target)) {
-        e.preventDefault();
-        handleFeaturedHashtagClick(target);
+        handleMentionClick(target);
       } else if (isHashtagClick(target)) {
         e.preventDefault();
         handleHashtagClick(target);
       }
     },
-    [handleMentionClick, handleFeaturedHashtagClick, handleHashtagClick],
+    [handleMentionClick, handleHashtagClick],
   );
 
   return handleClick;
diff --git a/app/javascript/mastodon/hooks/useRenderSignal.ts b/app/javascript/hooks/useRenderSignal.ts
similarity index 100%
rename from app/javascript/mastodon/hooks/useRenderSignal.ts
rename to app/javascript/hooks/useRenderSignal.ts
diff --git a/app/javascript/mastodon/hooks/useSearchParam.ts b/app/javascript/hooks/useSearchParam.ts
similarity index 100%
rename from app/javascript/mastodon/hooks/useSearchParam.ts
rename to app/javascript/hooks/useSearchParam.ts
diff --git a/app/javascript/mastodon/hooks/useTimeout.ts b/app/javascript/hooks/useTimeout.ts
similarity index 100%
rename from app/javascript/mastodon/hooks/useTimeout.ts
rename to app/javascript/hooks/useTimeout.ts
diff --git a/app/javascript/icons/android-chrome-144x144.png b/app/javascript/icons/android-chrome-144x144.png
old mode 100644
new mode 100755
index 698fb4a260..d636e94c43
Binary files a/app/javascript/icons/android-chrome-144x144.png and b/app/javascript/icons/android-chrome-144x144.png differ
diff --git a/app/javascript/icons/android-chrome-192x192.png b/app/javascript/icons/android-chrome-192x192.png
old mode 100644
new mode 100755
index 2b6b632648..4a2681ffb9
Binary files a/app/javascript/icons/android-chrome-192x192.png and b/app/javascript/icons/android-chrome-192x192.png differ
diff --git a/app/javascript/icons/android-chrome-256x256.png b/app/javascript/icons/android-chrome-256x256.png
old mode 100644
new mode 100755
index 51e3849a26..8fab493ede
Binary files a/app/javascript/icons/android-chrome-256x256.png and b/app/javascript/icons/android-chrome-256x256.png differ
diff --git a/app/javascript/icons/android-chrome-36x36.png b/app/javascript/icons/android-chrome-36x36.png
old mode 100644
new mode 100755
index 925f69c4fc..335d012db1
Binary files a/app/javascript/icons/android-chrome-36x36.png and b/app/javascript/icons/android-chrome-36x36.png differ
diff --git a/app/javascript/icons/android-chrome-384x384.png b/app/javascript/icons/android-chrome-384x384.png
old mode 100644
new mode 100755
index 9d256a83cb..02b1e6fced
Binary files a/app/javascript/icons/android-chrome-384x384.png and b/app/javascript/icons/android-chrome-384x384.png differ
diff --git a/app/javascript/icons/android-chrome-48x48.png b/app/javascript/icons/android-chrome-48x48.png
old mode 100644
new mode 100755
index bcfe7475d0..43cf411b8c
Binary files a/app/javascript/icons/android-chrome-48x48.png and b/app/javascript/icons/android-chrome-48x48.png differ
diff --git a/app/javascript/icons/android-chrome-512x512.png b/app/javascript/icons/android-chrome-512x512.png
old mode 100644
new mode 100755
index bffacfb699..1856b80c7c
Binary files a/app/javascript/icons/android-chrome-512x512.png and b/app/javascript/icons/android-chrome-512x512.png differ
diff --git a/app/javascript/icons/android-chrome-72x72.png b/app/javascript/icons/android-chrome-72x72.png
old mode 100644
new mode 100755
index 16679d5731..335008bf85
Binary files a/app/javascript/icons/android-chrome-72x72.png and b/app/javascript/icons/android-chrome-72x72.png differ
diff --git a/app/javascript/icons/android-chrome-96x96.png b/app/javascript/icons/android-chrome-96x96.png
old mode 100644
new mode 100755
index 9ade87cf32..d1cb095822
Binary files a/app/javascript/icons/android-chrome-96x96.png and b/app/javascript/icons/android-chrome-96x96.png differ
diff --git a/app/javascript/icons/apple-touch-icon-1024x1024.png b/app/javascript/icons/apple-touch-icon-1024x1024.png
old mode 100644
new mode 100755
index 8ec371eb27..c2a2d516ef
Binary files a/app/javascript/icons/apple-touch-icon-1024x1024.png and b/app/javascript/icons/apple-touch-icon-1024x1024.png differ
diff --git a/app/javascript/icons/apple-touch-icon-114x114.png b/app/javascript/icons/apple-touch-icon-114x114.png
old mode 100644
new mode 100755
index e1563f51e5..218b415439
Binary files a/app/javascript/icons/apple-touch-icon-114x114.png and b/app/javascript/icons/apple-touch-icon-114x114.png differ
diff --git a/app/javascript/icons/apple-touch-icon-120x120.png b/app/javascript/icons/apple-touch-icon-120x120.png
old mode 100644
new mode 100755
index e9a5f5b0e5..be53bc7c10
Binary files a/app/javascript/icons/apple-touch-icon-120x120.png and b/app/javascript/icons/apple-touch-icon-120x120.png differ
diff --git a/app/javascript/icons/apple-touch-icon-144x144.png b/app/javascript/icons/apple-touch-icon-144x144.png
old mode 100644
new mode 100755
index 698fb4a260..cbb055732f
Binary files a/app/javascript/icons/apple-touch-icon-144x144.png and b/app/javascript/icons/apple-touch-icon-144x144.png differ
diff --git a/app/javascript/icons/apple-touch-icon-152x152.png b/app/javascript/icons/apple-touch-icon-152x152.png
old mode 100644
new mode 100755
index 0cc93cc288..3a7975c054
Binary files a/app/javascript/icons/apple-touch-icon-152x152.png and b/app/javascript/icons/apple-touch-icon-152x152.png differ
diff --git a/app/javascript/icons/apple-touch-icon-167x167.png b/app/javascript/icons/apple-touch-icon-167x167.png
old mode 100644
new mode 100755
index 9bbbf53120..25be4eb5f5
Binary files a/app/javascript/icons/apple-touch-icon-167x167.png and b/app/javascript/icons/apple-touch-icon-167x167.png differ
diff --git a/app/javascript/icons/apple-touch-icon-180x180.png b/app/javascript/icons/apple-touch-icon-180x180.png
old mode 100644
new mode 100755
index 329b803b91..dc0e9bc20b
Binary files a/app/javascript/icons/apple-touch-icon-180x180.png and b/app/javascript/icons/apple-touch-icon-180x180.png differ
diff --git a/app/javascript/icons/apple-touch-icon-192x192.png b/app/javascript/icons/apple-touch-icon-192x192.png
deleted file mode 100644
index 2b6b632648..0000000000
Binary files a/app/javascript/icons/apple-touch-icon-192x192.png and /dev/null differ
diff --git a/app/javascript/icons/apple-touch-icon-256x256.png b/app/javascript/icons/apple-touch-icon-256x256.png
deleted file mode 100644
index 51e3849a26..0000000000
Binary files a/app/javascript/icons/apple-touch-icon-256x256.png and /dev/null differ
diff --git a/app/javascript/icons/apple-touch-icon-36x36.png b/app/javascript/icons/apple-touch-icon-36x36.png
deleted file mode 100644
index 925f69c4fc..0000000000
Binary files a/app/javascript/icons/apple-touch-icon-36x36.png and /dev/null differ
diff --git a/app/javascript/icons/apple-touch-icon-384x384.png b/app/javascript/icons/apple-touch-icon-384x384.png
deleted file mode 100644
index 9d256a83cb..0000000000
Binary files a/app/javascript/icons/apple-touch-icon-384x384.png and /dev/null differ
diff --git a/app/javascript/icons/apple-touch-icon-48x48.png b/app/javascript/icons/apple-touch-icon-48x48.png
deleted file mode 100644
index bcfe7475d0..0000000000
Binary files a/app/javascript/icons/apple-touch-icon-48x48.png and /dev/null differ
diff --git a/app/javascript/icons/apple-touch-icon-512x512.png b/app/javascript/icons/apple-touch-icon-512x512.png
deleted file mode 100644
index bffacfb699..0000000000
Binary files a/app/javascript/icons/apple-touch-icon-512x512.png and /dev/null differ
diff --git a/app/javascript/icons/apple-touch-icon-57x57.png b/app/javascript/icons/apple-touch-icon-57x57.png
old mode 100644
new mode 100755
index e00e142c64..bb0dc957cd
Binary files a/app/javascript/icons/apple-touch-icon-57x57.png and b/app/javascript/icons/apple-touch-icon-57x57.png differ
diff --git a/app/javascript/icons/apple-touch-icon-60x60.png b/app/javascript/icons/apple-touch-icon-60x60.png
old mode 100644
new mode 100755
index 011285b564..9143a0bf07
Binary files a/app/javascript/icons/apple-touch-icon-60x60.png and b/app/javascript/icons/apple-touch-icon-60x60.png differ
diff --git a/app/javascript/icons/apple-touch-icon-72x72.png b/app/javascript/icons/apple-touch-icon-72x72.png
old mode 100644
new mode 100755
index 16679d5731..2b7d19484c
Binary files a/app/javascript/icons/apple-touch-icon-72x72.png and b/app/javascript/icons/apple-touch-icon-72x72.png differ
diff --git a/app/javascript/icons/apple-touch-icon-76x76.png b/app/javascript/icons/apple-touch-icon-76x76.png
old mode 100644
new mode 100755
index 83c8748876..0985e33bcb
Binary files a/app/javascript/icons/apple-touch-icon-76x76.png and b/app/javascript/icons/apple-touch-icon-76x76.png differ
diff --git a/app/javascript/icons/apple-touch-icon-96x96.png b/app/javascript/icons/apple-touch-icon-96x96.png
deleted file mode 100644
index 9ade87cf32..0000000000
Binary files a/app/javascript/icons/apple-touch-icon-96x96.png and /dev/null differ
diff --git a/app/javascript/icons/favicon-16x16.png b/app/javascript/icons/favicon-16x16.png
old mode 100644
new mode 100755
index 7f865cfe96..1326ba0462
Binary files a/app/javascript/icons/favicon-16x16.png and b/app/javascript/icons/favicon-16x16.png differ
diff --git a/app/javascript/icons/favicon-32x32.png b/app/javascript/icons/favicon-32x32.png
old mode 100644
new mode 100755
index 7f865cfe96..f5058cb0a5
Binary files a/app/javascript/icons/favicon-32x32.png and b/app/javascript/icons/favicon-32x32.png differ
diff --git a/app/javascript/icons/favicon-48x48.png b/app/javascript/icons/favicon-48x48.png
old mode 100644
new mode 100755
index 7f865cfe96..6253d054c7
Binary files a/app/javascript/icons/favicon-48x48.png and b/app/javascript/icons/favicon-48x48.png differ
diff --git a/app/javascript/images/archetypes/booster.png b/app/javascript/images/archetypes/booster.png
deleted file mode 100755
index df2a0226f8..0000000000
Binary files a/app/javascript/images/archetypes/booster.png and /dev/null differ
diff --git a/app/javascript/images/archetypes/lurker.png b/app/javascript/images/archetypes/lurker.png
deleted file mode 100755
index e37f98aab2..0000000000
Binary files a/app/javascript/images/archetypes/lurker.png and /dev/null differ
diff --git a/app/javascript/images/archetypes/oracle.png b/app/javascript/images/archetypes/oracle.png
deleted file mode 100755
index 9d4e2177c5..0000000000
Binary files a/app/javascript/images/archetypes/oracle.png and /dev/null differ
diff --git a/app/javascript/images/archetypes/pollster.png b/app/javascript/images/archetypes/pollster.png
deleted file mode 100755
index 9fe6281af0..0000000000
Binary files a/app/javascript/images/archetypes/pollster.png and /dev/null differ
diff --git a/app/javascript/images/archetypes/replier.png b/app/javascript/images/archetypes/replier.png
deleted file mode 100755
index 6c6325b9f1..0000000000
Binary files a/app/javascript/images/archetypes/replier.png and /dev/null differ
diff --git a/app/javascript/images/logo_full.svg b/app/javascript/images/logo_full.svg
new file mode 100644
index 0000000000..03bcf93e39
--- /dev/null
+++ b/app/javascript/images/logo_full.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><symbol id="mastodon-svg-logo-full" viewBox="0 0 713.35878 175.8678"><path d="M160.55476 105.43125c-2.4125 12.40625-21.5975 25.9825-43.63375 28.61375-11.49125 1.3725-22.80375 2.63125-34.8675 2.07875-19.73-.90375-35.2975-4.71-35.2975-4.71 0 1.92125.11875 3.75.355 5.46 2.565 19.47 19.3075 20.6375 35.16625 21.18125 16.00625.5475 30.2575-3.9475 30.2575-3.9475l.65875 14.4725s-11.19625 6.01125-31.14 7.11625c-10.99875.605-24.65375-.27625-40.56-4.485C6.99851 162.08 1.06601 125.31.15851 88-.11899 76.9225.05226 66.47625.05226 57.74125c0-38.1525 24.99625-49.335 24.99625-49.335C37.65226 2.6175 59.27976.18375 81.76351 0h.5525c22.48375.18375 44.125 2.6175 56.72875 8.40625 0 0 24.99625 11.1825 24.99625 49.335 0 0 .3125 28.1475-3.48625 47.69" fill="#3088d4"/><path d="M34.65751 48.494c0-5.55375 4.5025-10.055 10.055-10.055 5.55375 0 10.055 4.50125 10.055 10.055 0 5.5525-4.50125 10.055-10.055 10.055-5.5525 0-10.055-4.5025-10.055-10.055M178.86476 60.69975v46.195h-18.30125v-44.8375c0-9.4525-3.9775-14.24875-11.9325-14.24875-8.79375 0-13.2025 5.69125-13.2025 16.94375V89.2935h-18.19375V64.75225c0-11.2525-4.40875-16.94375-13.2025-16.94375-7.955 0-11.9325 4.79625-11.9325 14.24875v44.8375H73.79851v-46.195c0-9.44125 2.40375-16.94375 7.2325-22.495 4.98-5.55 11.50125-8.395 19.595-8.395 9.36625 0 16.45875 3.59875 21.14625 10.79875l4.56 7.6425 4.55875-7.6425c4.68875-7.2 11.78-10.79875 21.1475-10.79875 8.09375 0 14.61375 2.845 19.59375 8.395 4.82875 5.55125 7.2325 13.05375 7.2325 22.495M241.91276 83.663625c3.77625-3.99 5.595-9.015 5.595-15.075 0-6.06-1.81875-11.085-5.595-14.9275-3.63625-3.99125-8.25375-5.91125-13.84875-5.91125-5.59625 0-10.2125 1.92-13.84875 5.91125-3.6375 3.8425-5.45625 8.8675-5.45625 14.9275 0 6.06 1.81875 11.085 5.45625 15.075 3.63625 3.8425 8.2525 5.76375 13.84875 5.76375 5.595 0 10.2125-1.92125 13.84875-5.76375m5.595-52.025h18.04625v73.9h-18.04625v-8.72125c-5.455 7.2425-13.01 10.79-22.80125 10.79-9.3725 0-17.34625-3.695-24.06125-11.23375-6.57375-7.5375-9.93125-16.84875-9.93125-27.785 0-10.78875 3.3575-20.10125 9.93125-27.63875 6.715-7.5375 14.68875-11.38 24.06125-11.38 9.79125 0 17.34625 3.5475 22.80125 10.78875v-8.72zM326.26951 67.258625c5.315 3.99 7.97375 9.60625 7.83375 16.7 0 7.53875-2.65875 13.45-8.11375 17.58875-5.45625 3.99125-12.03 6.06-20.00375 6.06-14.40875 0-24.20125-5.9125-29.3775-17.58875l15.66875-9.31c2.0975 6.35375 6.71375 9.60625 13.70875 9.60625 6.43375 0 9.6525-2.07 9.6525-6.35625 0-3.10375-4.1975-5.91125-12.73-8.1275-3.21875-.8875-5.87625-1.77375-7.97375-2.51375-2.9375-1.18125-5.455-2.5125-7.55375-4.1375-5.17625-3.99-7.83375-9.3125-7.83375-16.11 0-7.2425 2.5175-13.00625 7.55375-17.145 5.17625-4.28625 11.47-6.355 19.025-6.355 12.03 0 20.84375 5.1725 26.5775 15.66625l-15.38625 8.8675c-2.23875-5.02375-6.015-7.53625-11.19125-7.53625-5.45625 0-8.11375 2.06875-8.11375 6.05875 0 3.10375 4.19625 5.91125 12.73 8.12875 6.575 1.4775 11.75 3.695 15.5275 6.50375M383.626635 49.966125h-15.8075v30.7425c0 3.695 1.4 5.91125 4.0575 6.945 1.95875.74 5.875.8875 11.75.59125v17.29375c-12.16875 1.4775-20.9825.295-26.15875-3.69625-5.175-3.8425-7.69375-10.93625-7.69375-21.13375v-30.7425h-12.17v-18.3275h12.17v-14.9275l18.045-5.76375v20.69125h15.8075v18.3275zM441.124885 83.2205c3.6375-3.84375 5.455-8.72125 5.455-14.6325 0-5.91125-1.8175-10.78875-5.455-14.63125-3.6375-3.84375-8.11375-5.76375-13.57-5.76375-5.455 0-9.93125 1.92-13.56875 5.76375-3.4975 3.99-5.31625 8.8675-5.31625 14.63125 0 5.765 1.81875 10.6425 5.31625 14.6325 3.6375 3.8425 8.11375 5.76375 13.56875 5.76375 5.45625 0 9.9325-1.92125 13.57-5.76375m-39.86875 13.15375c-7.13375-7.5375-10.63125-16.70125-10.63125-27.78625 0-10.9375 3.4975-20.1 10.63125-27.6375 7.13375-7.5375 15.9475-11.38 26.29875-11.38 10.3525 0 19.165 3.8425 26.3 11.38 7.135 7.5375 10.77125 16.84875 10.77125 27.6375 0 10.9375-3.63625 20.24875-10.77125 27.78625-7.135 7.53875-15.8075 11.2325-26.3 11.2325-10.49125 0-19.165-3.69375-26.29875-11.2325M524.92126 83.663625c3.6375-3.99 5.455-9.015 5.455-15.075 0-6.06-1.8175-11.085-5.455-14.9275-3.63625-3.99125-8.25375-5.91125-13.84875-5.91125-5.59625 0-10.2125 1.92-13.98875 5.91125-3.63625 3.8425-5.45625 8.8675-5.45625 14.9275 0 6.06 1.82 11.085 5.45625 15.075 3.77625 3.8425 8.5325 5.76375 13.98875 5.76375 5.595 0 10.2125-1.92125 13.84875-5.76375m5.455-81.585h18.04625v103.46h-18.04625v-8.72125c-5.315 7.2425-12.87 10.79-22.66125 10.79-9.3725 0-17.485-3.695-24.2-11.23375-6.575-7.5375-9.9325-16.84875-9.9325-27.785 0-10.78875 3.3575-20.10125 9.9325-27.63875 6.715-7.5375 14.8275-11.38 24.2-11.38 9.79125 0 17.34625 3.5475 22.66125 10.78875v-38.28zM611.79626 83.2205c3.63625-3.84375 5.455-8.72125 5.455-14.6325 0-5.91125-1.81875-10.78875-5.455-14.63125-3.6375-3.84375-8.11375-5.76375-13.57-5.76375-5.455 0-9.9325 1.92-13.56875 5.76375-3.49875 3.99-5.31625 8.8675-5.31625 14.63125 0 5.765 1.8175 10.6425 5.31625 14.6325 3.63625 3.8425 8.11375 5.76375 13.56875 5.76375 5.45625 0 9.9325-1.92125 13.57-5.76375m-39.86875 13.15375c-7.135-7.5375-10.63125-16.70125-10.63125-27.78625 0-10.9375 3.49625-20.1 10.63125-27.6375 7.135-7.5375 15.9475-11.38 26.29875-11.38 10.3525 0 19.165 3.8425 26.3 11.38 7.135 7.5375 10.77125 16.84875 10.77125 27.6375 0 10.9375-3.63625 20.24875-10.77125 27.78625-7.135 7.53875-15.8075 11.2325-26.3 11.2325-10.49125 0-19.16375-3.69375-26.29875-11.2325M713.35876 60.163875v45.37375h-18.04625v-43.00875c0-4.8775-1.25875-8.5725-3.77625-11.38-2.37875-2.5125-5.73625-3.84375-10.0725-3.84375-10.2125 0-15.3875 6.06-15.3875 18.3275v39.905h-18.04625v-73.89875h18.04625v8.27625c4.33625-6.94625 11.19-10.345 20.84375-10.345 7.69375 0 13.98875 2.66 18.885 8.12875 5.035 5.46875 7.55375 12.85875 7.55375 22.465"/></symbol></svg>
diff --git a/app/javascript/images/logo_transparent.svg b/app/javascript/images/logo_transparent.svg
new file mode 100644
index 0000000000..a1e7b403e0
--- /dev/null
+++ b/app/javascript/images/logo_transparent.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><symbol id="mastodon-svg-logo" viewBox="0 0 216.4144 232.00976"><path d="M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z" /></symbol></svg>
diff --git a/app/javascript/images/quote.svg b/app/javascript/images/quote.svg
deleted file mode 100644
index ae6fbbe04a..0000000000
--- a/app/javascript/images/quote.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-<svg width="24" height="20" viewBox="0 0 24 20" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M23.933 2.82414C22.324 4.07931 21.0726 5.3569 20.1788 6.6569C19.3296 7.91207 18.905 9.1 18.905 10.2207C19.0838 10.131 19.3073 10.0862 19.5754 10.0862C19.8883 10.0414 20.1564 10.019 20.3799 10.019C21.4078 10.019 22.257 10.4448 22.9274 11.2966C23.6425 12.1034 24 13.1121 24 14.3224C24 15.8017 23.5084 17.0345 22.5251 18.0207C21.5419 19.0069 20.3129 19.5 18.838 19.5C17.2737 19.5 16.0447 18.9397 15.1508 17.819C14.257 16.6535 13.8101 15.1069 13.8101 13.1793C13.8101 10.8931 14.5028 8.62931 15.8883 6.38793C17.2737 4.14655 19.3073 2.01724 21.9888 0L23.933 2.82414ZM10.1229 2.82414C8.51397 4.07931 7.26257 5.3569 6.36872 6.6569C5.51955 7.91207 5.09497 9.1 5.09497 10.2207C5.27374 10.131 5.49721 10.0862 5.76536 10.0862C6.07821 10.0414 6.34637 10.019 6.56983 10.019C7.59777 10.019 8.44693 10.4448 9.11732 11.2966C9.8324 12.1034 10.1899 13.1121 10.1899 14.3224C10.1899 15.8017 9.69832 17.0345 8.71508 18.0207C7.73184 19.0069 6.50279 19.5 5.02793 19.5C3.46369 19.5 2.23464 18.9397 1.34078 17.819C0.446927 16.6535 0 15.1069 0 13.1793C0 10.8931 0.692738 8.62931 2.07821 6.38793C3.46369 4.14655 5.49721 2.01724 8.17877 0L10.1229 2.82414Z" fill="currentColor" />
-</svg>
diff --git a/app/javascript/images/reticle.png b/app/javascript/images/reticle.png
new file mode 100644
index 0000000000..a724ac0bcd
Binary files /dev/null and b/app/javascript/images/reticle.png differ
diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js
index d821381ce0..3d0e8b8c90 100644
--- a/app/javascript/mastodon/actions/accounts.js
+++ b/app/javascript/mastodon/actions/accounts.js
@@ -142,13 +142,6 @@ export function fetchAccountFail(id, error) {
   };
 }
 
-/**
- * @param {string} id
- * @param {Object} options
- * @param {boolean} [options.reblogs]
- * @param {boolean} [options.notify]
- * @returns {function(): void}
- */
 export function followAccount(id, options = { reblogs: true }) {
   return (dispatch, getState) => {
     const alreadyFollowing = getState().getIn(['relationships', id, 'following']);
diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js
new file mode 100644
index 0000000000..48dee2587f
--- /dev/null
+++ b/app/javascript/mastodon/actions/alerts.js
@@ -0,0 +1,66 @@
+import { defineMessages } from 'react-intl';
+
+import { AxiosError } from 'axios';
+
+const messages = defineMessages({
+  unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
+  unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' },
+  rateLimitedTitle: { id: 'alert.rate_limited.title', defaultMessage: 'Rate limited' },
+  rateLimitedMessage: { id: 'alert.rate_limited.message', defaultMessage: 'Please retry after {retry_time, time, medium}.' },
+});
+
+export const ALERT_SHOW    = 'ALERT_SHOW';
+export const ALERT_DISMISS = 'ALERT_DISMISS';
+export const ALERT_CLEAR   = 'ALERT_CLEAR';
+export const ALERT_NOOP    = 'ALERT_NOOP';
+
+export const dismissAlert = alert => ({
+  type: ALERT_DISMISS,
+  alert,
+});
+
+export const clearAlert = () => ({
+  type: ALERT_CLEAR,
+});
+
+export const showAlert = alert => ({
+  type: ALERT_SHOW,
+  alert,
+});
+
+export const showAlertForError = (error, skipNotFound = false) => {
+  if (error.response) {
+    const { data, status, statusText, headers } = error.response;
+
+    // Skip these errors as they are reflected in the UI
+    if (skipNotFound && (status === 404 || status === 410)) {
+      return { type: ALERT_NOOP };
+    }
+
+    // Rate limit errors
+    if (status === 429 && headers['x-ratelimit-reset']) {
+      return showAlert({
+        title: messages.rateLimitedTitle,
+        message: messages.rateLimitedMessage,
+        values: { 'retry_time': new Date(headers['x-ratelimit-reset']) },
+      });
+    }
+
+    return showAlert({
+      title: `${status}`,
+      message: data.error || statusText,
+    });
+  }
+
+  // An aborted request, e.g. due to reloading the browser window, it not really error
+  if (error.code === AxiosError.ECONNABORTED) {
+    return { type: ALERT_NOOP };
+  }
+
+  console.error(error);
+
+  return showAlert({
+    title: messages.unexpectedTitle,
+    message: messages.unexpectedMessage,
+  });
+};
diff --git a/app/javascript/mastodon/actions/alerts.ts b/app/javascript/mastodon/actions/alerts.ts
deleted file mode 100644
index 4fd293e252..0000000000
--- a/app/javascript/mastodon/actions/alerts.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { defineMessages } from 'react-intl';
-
-import { createAction } from '@reduxjs/toolkit';
-
-import { AxiosError } from 'axios';
-import type { AxiosResponse } from 'axios';
-
-import type { Alert } from 'mastodon/models/alert';
-
-interface ApiErrorResponse {
-  error?: string;
-}
-
-const messages = defineMessages({
-  unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
-  unexpectedMessage: {
-    id: 'alert.unexpected.message',
-    defaultMessage: 'An unexpected error occurred.',
-  },
-  rateLimitedTitle: {
-    id: 'alert.rate_limited.title',
-    defaultMessage: 'Rate limited',
-  },
-  rateLimitedMessage: {
-    id: 'alert.rate_limited.message',
-    defaultMessage: 'Please retry after {retry_time, time, medium}.',
-  },
-});
-
-export const dismissAlert = createAction<{ key: number }>('alerts/dismiss');
-
-export const clearAlerts = createAction('alerts/clear');
-
-export const showAlert = createAction<Omit<Alert, 'key'>>('alerts/show');
-
-const ignoreAlert = createAction('alerts/ignore');
-
-export const showAlertForError = (error: unknown, skipNotFound = false) => {
-  if (error instanceof AxiosError && error.response) {
-    const { status, statusText, headers } = error.response;
-    const { data } = error.response as AxiosResponse<ApiErrorResponse>;
-
-    // Skip these errors as they are reflected in the UI
-    if (skipNotFound && (status === 404 || status === 410)) {
-      return ignoreAlert();
-    }
-
-    // Rate limit errors
-    if (status === 429 && headers['x-ratelimit-reset']) {
-      return showAlert({
-        title: messages.rateLimitedTitle,
-        message: messages.rateLimitedMessage,
-        values: {
-          retry_time: new Date(headers['x-ratelimit-reset'] as string),
-        },
-      });
-    }
-
-    return showAlert({
-      title: `${status}`,
-      message: data.error ?? statusText,
-    });
-  }
-
-  // An aborted request, e.g. due to reloading the browser window, is not really an error
-  if (error instanceof AxiosError && error.code === AxiosError.ECONNABORTED) {
-    return ignoreAlert();
-  }
-
-  console.error(error);
-
-  return showAlert({
-    title: messages.unexpectedTitle,
-    message: messages.unexpectedMessage,
-  });
-};
diff --git a/app/javascript/mastodon/actions/antennas.js b/app/javascript/mastodon/actions/antennas.js
index 30dc2d42c6..4716897586 100644
--- a/app/javascript/mastodon/actions/antennas.js
+++ b/app/javascript/mastodon/actions/antennas.js
@@ -1,5 +1,8 @@
 import api from '../api';
 
+import { showAlertForError } from './alerts';
+import { importFetchedAccounts } from './importer';
+
 export const ANTENNA_FETCH_REQUEST = 'ANTENNA_FETCH_REQUEST';
 export const ANTENNA_FETCH_SUCCESS = 'ANTENNA_FETCH_SUCCESS';
 export const ANTENNA_FETCH_FAIL    = 'ANTENNA_FETCH_FAIL';
@@ -8,10 +11,121 @@ export const ANTENNAS_FETCH_REQUEST = 'ANTENNAS_FETCH_REQUEST';
 export const ANTENNAS_FETCH_SUCCESS = 'ANTENNAS_FETCH_SUCCESS';
 export const ANTENNAS_FETCH_FAIL    = 'ANTENNAS_FETCH_FAIL';
 
+export const ANTENNA_EDITOR_TITLE_CHANGE = 'ANTENNA_EDITOR_TITLE_CHANGE';
+export const ANTENNA_EDITOR_RESET        = 'ANTENNA_EDITOR_RESET';
+export const ANTENNA_EDITOR_SETUP        = 'ANTENNA_EDITOR_SETUP';
+
+export const ANTENNA_CREATE_REQUEST = 'ANTENNA_CREATE_REQUEST';
+export const ANTENNA_CREATE_SUCCESS = 'ANTENNA_CREATE_SUCCESS';
+export const ANTENNA_CREATE_FAIL    = 'ANTENNA_CREATE_FAIL';
+
+export const ANTENNA_UPDATE_REQUEST = 'ANTENNA_UPDATE_REQUEST';
+export const ANTENNA_UPDATE_SUCCESS = 'ANTENNA_UPDATE_SUCCESS';
+export const ANTENNA_UPDATE_FAIL    = 'ANTENNA_UPDATE_FAIL';
+
 export const ANTENNA_DELETE_REQUEST = 'ANTENNA_DELETE_REQUEST';
 export const ANTENNA_DELETE_SUCCESS = 'ANTENNA_DELETE_SUCCESS';
 export const ANTENNA_DELETE_FAIL    = 'ANTENNA_DELETE_FAIL';
 
+export const ANTENNA_ACCOUNTS_FETCH_REQUEST = 'ANTENNA_ACCOUNTS_FETCH_REQUEST';
+export const ANTENNA_ACCOUNTS_FETCH_SUCCESS = 'ANTENNA_ACCOUNTS_FETCH_SUCCESS';
+export const ANTENNA_ACCOUNTS_FETCH_FAIL    = 'ANTENNA_ACCOUNTS_FETCH_FAIL';
+
+export const ANTENNA_EDITOR_SUGGESTIONS_CHANGE = 'ANTENNA_EDITOR_SUGGESTIONS_CHANGE';
+export const ANTENNA_EDITOR_SUGGESTIONS_READY  = 'ANTENNA_EDITOR_SUGGESTIONS_READY';
+export const ANTENNA_EDITOR_SUGGESTIONS_CLEAR  = 'ANTENNA_EDITOR_SUGGESTIONS_CLEAR';
+
+export const ANTENNA_EDITOR_ADD_REQUEST = 'ANTENNA_EDITOR_ADD_REQUEST';
+export const ANTENNA_EDITOR_ADD_SUCCESS = 'ANTENNA_EDITOR_ADD_SUCCESS';
+export const ANTENNA_EDITOR_ADD_FAIL    = 'ANTENNA_EDITOR_ADD_FAIL';
+
+export const ANTENNA_EDITOR_REMOVE_REQUEST = 'ANTENNA_EDITOR_REMOVE_REQUEST';
+export const ANTENNA_EDITOR_REMOVE_SUCCESS = 'ANTENNA_EDITOR_REMOVE_SUCCESS';
+export const ANTENNA_EDITOR_REMOVE_FAIL    = 'ANTENNA_EDITOR_REMOVE_FAIL';
+
+export const ANTENNA_EXCLUDE_ACCOUNTS_FETCH_REQUEST = 'ANTENNA_EXCLUDE_ACCOUNTS_FETCH_REQUEST';
+export const ANTENNA_EXCLUDE_ACCOUNTS_FETCH_SUCCESS = 'ANTENNA_EXCLUDE_ACCOUNTS_FETCH_SUCCESS';
+export const ANTENNA_EXCLUDE_ACCOUNTS_FETCH_FAIL    = 'ANTENNA_EXCLUDE_ACCOUNTS_FETCH_FAIL';
+
+export const ANTENNA_EDITOR_ADD_EXCLUDE_REQUEST = 'ANTENNA_EDITOR_ADD_EXCLUDE_REQUEST';
+export const ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS = 'ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS';
+export const ANTENNA_EDITOR_ADD_EXCLUDE_FAIL    = 'ANTENNA_EDITOR_ADD_EXCLUDE_FAIL';
+
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_REQUEST = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_REQUEST';
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS';
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_FAIL    = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_FAIL';
+
+export const ANTENNA_EDITOR_FETCH_DOMAINS_REQUEST = 'ANTENNA_EDITOR_FETCH_DOMAINS_REQUEST';
+export const ANTENNA_EDITOR_FETCH_DOMAINS_SUCCESS = 'ANTENNA_EDITOR_FETCH_DOMAINS_SUCCESS';
+export const ANTENNA_EDITOR_FETCH_DOMAINS_FAIL    = 'ANTENNA_EDITOR_FETCH_DOMAINS_FAIL';
+
+export const ANTENNA_EDITOR_ADD_DOMAIN_REQUEST = 'ANTENNA_EDITOR_ADD_DOMAIN_REQUEST';
+export const ANTENNA_EDITOR_ADD_DOMAIN_SUCCESS = 'ANTENNA_EDITOR_ADD_DOMAIN_SUCCESS';
+export const ANTENNA_EDITOR_ADD_DOMAIN_FAIL    = 'ANTENNA_EDITOR_ADD_DOMAIN_FAIL';
+
+export const ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_REQUEST = 'ANTENNA_EDITOR_ADD_EXCLUDEDOMAIN_REQUEST';
+export const ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_SUCCESS = 'ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_SUCCESS';
+export const ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_FAIL    = 'ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_FAIL';
+
+export const ANTENNA_EDITOR_REMOVE_DOMAIN_REQUEST = 'ANTENNA_EDITOR_REMOVE_DOMAIN_REQUEST';
+export const ANTENNA_EDITOR_REMOVE_DOMAIN_SUCCESS = 'ANTENNA_EDITOR_REMOVE_DOMAIN_SUCCESS';
+export const ANTENNA_EDITOR_REMOVE_DOMAIN_FAIL    = 'ANTENNA_EDITOR_REMOVE_DOMAIN_FAIL';
+
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_REQUEST = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_REQUEST';
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_SUCCESS = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_SUCCESS';
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_FAIL    = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_FAIL';
+
+export const ANTENNA_EDITOR_FETCH_KEYWORDS_REQUEST = 'ANTENNA_EDITOR_FETCH_KEYWORDS_REQUEST';
+export const ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS = 'ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS';
+export const ANTENNA_EDITOR_FETCH_KEYWORDS_FAIL    = 'ANTENNA_EDITOR_FETCH_KEYWORDS_FAIL';
+
+export const ANTENNA_EDITOR_ADD_KEYWORD_REQUEST = 'ANTENNA_EDITOR_ADD_KEYWORD_REQUEST';
+export const ANTENNA_EDITOR_ADD_KEYWORD_SUCCESS = 'ANTENNA_EDITOR_ADD_KEYWORD_SUCCESS';
+export const ANTENNA_EDITOR_ADD_KEYWORD_FAIL    = 'ANTENNA_EDITOR_ADD_KEYWORD_FAIL';
+
+export const ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_REQUEST = 'ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_REQUEST';
+export const ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_SUCCESS = 'ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_SUCCESS';
+export const ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_FAIL    = 'ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_FAIL';
+
+export const ANTENNA_EDITOR_REMOVE_KEYWORD_REQUEST = 'ANTENNA_EDITOR_REMOVE_KEYWORD_REQUEST';
+export const ANTENNA_EDITOR_REMOVE_KEYWORD_SUCCESS = 'ANTENNA_EDITOR_REMOVE_KEYWORD_SUCCESS';
+export const ANTENNA_EDITOR_REMOVE_KEYWORD_FAIL    = 'ANTENNA_EDITOR_REMOVE_KEYWORD_FAIL';
+
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_REQUEST = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_REQUEST';
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS';
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_FAIL    = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_FAIL';
+
+export const ANTENNA_EDITOR_FETCH_TAGS_REQUEST = 'ANTENNA_EDITOR_FETCH_TAGS_REQUEST';
+export const ANTENNA_EDITOR_FETCH_TAGS_SUCCESS = 'ANTENNA_EDITOR_FETCH_TAGS_SUCCESS';
+export const ANTENNA_EDITOR_FETCH_TAGS_FAIL    = 'ANTENNA_EDITOR_FETCH_TAGS_FAIL';
+
+export const ANTENNA_EDITOR_ADD_TAG_REQUEST = 'ANTENNA_EDITOR_ADD_TAG_REQUEST';
+export const ANTENNA_EDITOR_ADD_TAG_SUCCESS = 'ANTENNA_EDITOR_ADD_TAG_SUCCESS';
+export const ANTENNA_EDITOR_ADD_TAG_FAIL    = 'ANTENNA_EDITOR_ADD_TAG_FAIL';
+
+export const ANTENNA_EDITOR_ADD_EXCLUDE_TAG_REQUEST = 'ANTENNA_EDITOR_ADD_EXCLUDE_TAG_REQUEST';
+export const ANTENNA_EDITOR_ADD_EXCLUDE_TAG_SUCCESS = 'ANTENNA_EDITOR_ADD_EXCLUDE_TAG_SUCCESS';
+export const ANTENNA_EDITOR_ADD_EXCLUDE_TAG_FAIL    = 'ANTENNA_EDITOR_ADD_EXCLUDE_TAG_FAIL';
+
+export const ANTENNA_EDITOR_REMOVE_TAG_REQUEST = 'ANTENNA_EDITOR_REMOVE_TAG_REQUEST';
+export const ANTENNA_EDITOR_REMOVE_TAG_SUCCESS = 'ANTENNA_EDITOR_REMOVE_TAG_SUCCESS';
+export const ANTENNA_EDITOR_REMOVE_TAG_FAIL    = 'ANTENNA_EDITOR_REMOVE_TAG_FAIL';
+
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_REQUEST = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_REQUEST';
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_SUCCESS = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_SUCCESS';
+export const ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_FAIL    = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_FAIL';
+
+export const ANTENNA_ADDER_RESET = 'ANTENNA_ADDER_RESET';
+export const ANTENNA_ADDER_SETUP = 'ANTENNA_ADDER_SETUP';
+
+export const ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST = 'ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST';
+export const ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS = 'ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS';
+export const ANTENNA_ADDER_ANTENNAS_FETCH_FAIL    = 'ANTENNA_ADDER_ANTENNAS_FETCH_FAIL';
+
+export const ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST = 'ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST';
+export const ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS = 'ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS';
+export const ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL    = 'ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL';
+
 export const fetchAntenna = id => (dispatch, getState) => {
   if (getState().getIn(['antennas', id])) {
     return;
@@ -62,6 +176,98 @@ export const fetchAntennasFail = error => ({
   error,
 });
 
+export const submitAntennaEditor = shouldReset => (dispatch, getState) => {
+  const antennaId = getState().getIn(['antennaEditor', 'antennaId']);
+  const title  = getState().getIn(['antennaEditor', 'title']);
+
+  if (antennaId === null) {
+    dispatch(createAntenna(title, shouldReset));
+  } else {
+    dispatch(updateAntenna(antennaId, title, shouldReset));
+  }
+};
+
+export const setupAntennaEditor = antennaId => (dispatch, getState) => {
+  dispatch({
+    type: ANTENNA_EDITOR_SETUP,
+    antenna: getState().getIn(['antennas', antennaId]),
+  });
+
+  dispatch(fetchAntennaAccounts(antennaId));
+};
+
+export const setupExcludeAntennaEditor = antennaId => (dispatch, getState) => {
+  dispatch({
+    type: ANTENNA_EDITOR_SETUP,
+    antenna: getState().getIn(['antennas', antennaId]),
+  });
+
+  dispatch(fetchAntennaExcludeAccounts(antennaId));
+};
+
+export const changeAntennaEditorTitle = value => ({
+  type: ANTENNA_EDITOR_TITLE_CHANGE,
+  value,
+});
+
+export const createAntenna = (title, shouldReset) => (dispatch, getState) => {
+  dispatch(createAntennaRequest());
+
+  api(getState).post('/api/v1/antennas', { title }).then(({ data }) => {
+    dispatch(createAntennaSuccess(data));
+
+    if (shouldReset) {
+      dispatch(resetAntennaEditor());
+    }
+  }).catch(err => dispatch(createAntennaFail(err)));
+};
+
+export const createAntennaRequest = () => ({
+  type: ANTENNA_CREATE_REQUEST,
+});
+
+export const createAntennaSuccess = antenna => ({
+  type: ANTENNA_CREATE_SUCCESS,
+  antenna,
+});
+
+export const createAntennaFail = error => ({
+  type: ANTENNA_CREATE_FAIL,
+  error,
+});
+
+export const updateAntenna = (id, title, shouldReset, list_id, stl, ltl, with_media_only, ignore_reblog, insert_feeds) => (dispatch, getState) => {
+  dispatch(updateAntennaRequest(id));
+
+  api(getState).put(`/api/v1/antennas/${id}`, { title, list_id, stl, ltl, with_media_only, ignore_reblog, insert_feeds }).then(({ data }) => {
+    dispatch(updateAntennaSuccess(data));
+
+    if (shouldReset) {
+      dispatch(resetAntennaEditor());
+    }
+  }).catch(err => dispatch(updateAntennaFail(id, err)));
+};
+
+export const updateAntennaRequest = id => ({
+  type: ANTENNA_UPDATE_REQUEST,
+  id,
+});
+
+export const updateAntennaSuccess = antenna => ({
+  type: ANTENNA_UPDATE_SUCCESS,
+  antenna,
+});
+
+export const updateAntennaFail = (id, error) => ({
+  type: ANTENNA_UPDATE_FAIL,
+  id,
+  error,
+});
+
+export const resetAntennaEditor = () => ({
+  type: ANTENNA_EDITOR_RESET,
+});
+
 export const deleteAntenna = id => (dispatch, getState) => {
   dispatch(deleteAntennaRequest(id));
 
@@ -85,3 +291,696 @@ export const deleteAntennaFail = (id, error) => ({
   id,
   error,
 });
+
+export const fetchAntennaAccounts = antennaId => (dispatch, getState) => {
+  dispatch(fetchAntennaAccountsRequest(antennaId));
+
+  api(getState).get(`/api/v1/antennas/${antennaId}/accounts`, { params: { limit: 0 } }).then(({ data }) => {
+    dispatch(importFetchedAccounts(data));
+    dispatch(fetchAntennaAccountsSuccess(antennaId, data));
+  }).catch(err => dispatch(fetchAntennaAccountsFail(antennaId, err)));
+};
+
+export const fetchAntennaAccountsRequest = id => ({
+  type: ANTENNA_ACCOUNTS_FETCH_REQUEST,
+  id,
+});
+
+export const fetchAntennaAccountsSuccess = (id, accounts, next) => ({
+  type: ANTENNA_ACCOUNTS_FETCH_SUCCESS,
+  id,
+  accounts,
+  next,
+});
+
+export const fetchAntennaAccountsFail = (id, error) => ({
+  type: ANTENNA_ACCOUNTS_FETCH_FAIL,
+  id,
+  error,
+});
+
+export const fetchAntennaExcludeAccounts = antennaId => (dispatch, getState) => {
+  dispatch(fetchAntennaExcludeAccountsRequest(antennaId));
+
+  api(getState).get(`/api/v1/antennas/${antennaId}/exclude_accounts`, { params: { limit: 0 } }).then(({ data }) => {
+    dispatch(importFetchedAccounts(data));
+    dispatch(fetchAntennaExcludeAccountsSuccess(antennaId, data));
+  }).catch(err => dispatch(fetchAntennaExcludeAccountsFail(antennaId, err)));
+};
+
+export const fetchAntennaExcludeAccountsRequest = id => ({
+  type: ANTENNA_EXCLUDE_ACCOUNTS_FETCH_REQUEST,
+  id,
+});
+
+export const fetchAntennaExcludeAccountsSuccess = (id, accounts, next) => ({
+  type: ANTENNA_EXCLUDE_ACCOUNTS_FETCH_SUCCESS,
+  id,
+  accounts,
+  next,
+});
+
+export const fetchAntennaExcludeAccountsFail = (id, error) => ({
+  type: ANTENNA_EXCLUDE_ACCOUNTS_FETCH_FAIL,
+  id,
+  error,
+});
+
+export const fetchAntennaSuggestions = q => (dispatch, getState) => {
+  const params = {
+    q,
+    resolve: false,
+  };
+
+  api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => {
+    dispatch(importFetchedAccounts(data));
+    dispatch(fetchAntennaSuggestionsReady(q, data));
+  }).catch(error => dispatch(showAlertForError(error)));
+};
+
+export const fetchAntennaSuggestionsReady = (query, accounts) => ({
+  type: ANTENNA_EDITOR_SUGGESTIONS_READY,
+  query,
+  accounts,
+});
+
+export const clearAntennaSuggestions = () => ({
+  type: ANTENNA_EDITOR_SUGGESTIONS_CLEAR,
+});
+
+export const changeAntennaSuggestions = value => ({
+  type: ANTENNA_EDITOR_SUGGESTIONS_CHANGE,
+  value,
+});
+
+export const addToAntennaEditor = accountId => (dispatch, getState) => {
+  dispatch(addToAntenna(getState().getIn(['antennaEditor', 'antennaId']), accountId));
+};
+
+export const addToAntenna = (antennaId, accountId) => (dispatch, getState) => {
+  dispatch(addToAntennaRequest(antennaId, accountId));
+
+  api(getState).post(`/api/v1/antennas/${antennaId}/accounts`, { account_ids: [accountId] })
+    .then(() => dispatch(addToAntennaSuccess(antennaId, accountId)))
+    .catch(err => dispatch(addToAntennaFail(antennaId, accountId, err)));
+};
+
+export const addToAntennaRequest = (antennaId, accountId) => ({
+  type: ANTENNA_EDITOR_ADD_REQUEST,
+  antennaId,
+  accountId,
+});
+
+export const addToAntennaSuccess = (antennaId, accountId) => ({
+  type: ANTENNA_EDITOR_ADD_SUCCESS,
+  antennaId,
+  accountId,
+});
+
+export const addToAntennaFail = (antennaId, accountId, error) => ({
+  type: ANTENNA_EDITOR_ADD_FAIL,
+  antennaId,
+  accountId,
+  error,
+});
+
+export const addExcludeToAntennaEditor = accountId => (dispatch, getState) => {
+  dispatch(addExcludeToAntenna(getState().getIn(['antennaEditor', 'antennaId']), accountId));
+};
+
+export const addExcludeToAntenna = (antennaId, accountId) => (dispatch, getState) => {
+  dispatch(addExcludeToAntennaRequest(antennaId, accountId));
+
+  api(getState).post(`/api/v1/antennas/${antennaId}/exclude_accounts`, { account_ids: [accountId] })
+    .then(() => dispatch(addExcludeToAntennaSuccess(antennaId, accountId)))
+    .catch(err => dispatch(addExcludeToAntennaFail(antennaId, accountId, err)));
+};
+
+export const addExcludeToAntennaRequest = (antennaId, accountId) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_REQUEST,
+  antennaId,
+  accountId,
+});
+
+export const addExcludeToAntennaSuccess = (antennaId, accountId) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS,
+  antennaId,
+  accountId,
+});
+
+export const addExcludeToAntennaFail = (antennaId, accountId, error) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_FAIL,
+  antennaId,
+  accountId,
+  error,
+});
+
+export const removeFromAntennaEditor = accountId => (dispatch, getState) => {
+  dispatch(removeFromAntenna(getState().getIn(['antennaEditor', 'antennaId']), accountId));
+};
+
+export const removeFromAntenna = (antennaId, accountId) => (dispatch, getState) => {
+  dispatch(removeFromAntennaRequest(antennaId, accountId));
+
+  api(getState).delete(`/api/v1/antennas/${antennaId}/accounts`, { params: { account_ids: [accountId] } })
+    .then(() => dispatch(removeFromAntennaSuccess(antennaId, accountId)))
+    .catch(err => dispatch(removeFromAntennaFail(antennaId, accountId, err)));
+};
+
+export const removeFromAntennaRequest = (antennaId, accountId) => ({
+  type: ANTENNA_EDITOR_REMOVE_REQUEST,
+  antennaId,
+  accountId,
+});
+
+export const removeFromAntennaSuccess = (antennaId, accountId) => ({
+  type: ANTENNA_EDITOR_REMOVE_SUCCESS,
+  antennaId,
+  accountId,
+});
+
+export const removeFromAntennaFail = (antennaId, accountId, error) => ({
+  type: ANTENNA_EDITOR_REMOVE_FAIL,
+  antennaId,
+  accountId,
+  error,
+});
+
+export const removeExcludeFromAntennaEditor = accountId => (dispatch, getState) => {
+  dispatch(removeExcludeFromAntenna(getState().getIn(['antennaEditor', 'antennaId']), accountId));
+};
+
+export const removeExcludeFromAntenna = (antennaId, accountId) => (dispatch, getState) => {
+  dispatch(removeExcludeFromAntennaRequest(antennaId, accountId));
+
+  api(getState).delete(`/api/v1/antennas/${antennaId}/exclude_accounts`, { params: { account_ids: [accountId] } })
+    .then(() => dispatch(removeExcludeFromAntennaSuccess(antennaId, accountId)))
+    .catch(err => dispatch(removeExcludeFromAntennaFail(antennaId, accountId, err)));
+};
+
+export const removeExcludeFromAntennaRequest = (antennaId, accountId) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_REQUEST,
+  antennaId,
+  accountId,
+});
+
+export const removeExcludeFromAntennaSuccess = (antennaId, accountId) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS,
+  antennaId,
+  accountId,
+});
+
+export const removeExcludeFromAntennaFail = (antennaId, accountId, error) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_FAIL,
+  antennaId,
+  accountId,
+  error,
+});
+
+export const fetchAntennaDomains = antennaId => (dispatch, getState) => {
+  dispatch(fetchAntennaDomainsRequest(antennaId));
+
+  api(getState).get(`/api/v1/antennas/${antennaId}/domains`, { params: { limit: 0 } }).then(({ data }) => {
+    dispatch(fetchAntennaDomainsSuccess(antennaId, data));
+  }).catch(err => dispatch(fetchAntennaDomainsFail(antennaId, err)));
+};
+
+export const fetchAntennaDomainsRequest = id => ({
+  type: ANTENNA_EDITOR_FETCH_DOMAINS_REQUEST,
+  id,
+});
+
+export const fetchAntennaDomainsSuccess = (id, domains) => ({
+  type: ANTENNA_EDITOR_FETCH_DOMAINS_SUCCESS,
+  id,
+  domains,
+});
+
+export const fetchAntennaDomainsFail = (id, error) => ({
+  type: ANTENNA_EDITOR_FETCH_DOMAINS_FAIL,
+  id,
+  error,
+});
+
+export const addDomainToAntenna = (antennaId, domain) => (dispatch, getState) => {
+  dispatch(addDomainToAntennaRequest(antennaId, domain));
+
+  api(getState).post(`/api/v1/antennas/${antennaId}/domains`, { domains: [domain] })
+    .then(() => dispatch(addDomainToAntennaSuccess(antennaId, domain)))
+    .catch(err => dispatch(addDomainToAntennaFail(antennaId, domain, err)));
+};
+
+export const addDomainToAntennaRequest = (antennaId, domain) => ({
+  type: ANTENNA_EDITOR_ADD_DOMAIN_REQUEST,
+  antennaId,
+  domain,
+});
+
+export const addDomainToAntennaSuccess = (antennaId, domain) => ({
+  type: ANTENNA_EDITOR_ADD_DOMAIN_SUCCESS,
+  antennaId,
+  domain,
+});
+
+export const addDomainToAntennaFail = (antennaId, domain, error) => ({
+  type: ANTENNA_EDITOR_ADD_DOMAIN_FAIL,
+  antennaId,
+  domain,
+  error,
+});
+
+export const removeDomainFromAntenna = (antennaId, domain) => (dispatch, getState) => {
+  dispatch(removeDomainFromAntennaRequest(antennaId, domain));
+
+  api(getState).delete(`/api/v1/antennas/${antennaId}/domains`, { params: { domains: [domain] } })
+    .then(() => dispatch(removeDomainFromAntennaSuccess(antennaId, domain)))
+    .catch(err => dispatch(removeDomainFromAntennaFail(antennaId, domain, err)));
+};
+
+export const removeDomainFromAntennaRequest = (antennaId, domain) => ({
+  type: ANTENNA_EDITOR_REMOVE_DOMAIN_REQUEST,
+  antennaId,
+  domain,
+});
+
+export const removeDomainFromAntennaSuccess = (antennaId, domain) => ({
+  type: ANTENNA_EDITOR_REMOVE_DOMAIN_SUCCESS,
+  antennaId,
+  domain,
+});
+
+export const removeDomainFromAntennaFail = (antennaId, domain, error) => ({
+  type: ANTENNA_EDITOR_REMOVE_DOMAIN_FAIL,
+  antennaId,
+  domain,
+  error,
+});
+
+export const addExcludeDomainToAntenna = (antennaId, domain) => (dispatch, getState) => {
+  dispatch(addExcludeDomainToAntennaRequest(antennaId, domain));
+
+  api(getState).post(`/api/v1/antennas/${antennaId}/exclude_domains`, { domains: [domain] })
+    .then(() => dispatch(addExcludeDomainToAntennaSuccess(antennaId, domain)))
+    .catch(err => dispatch(addExcludeDomainToAntennaFail(antennaId, domain, err)));
+};
+
+export const addExcludeDomainToAntennaRequest = (antennaId, domain) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_REQUEST,
+  antennaId,
+  domain,
+});
+
+export const addExcludeDomainToAntennaSuccess = (antennaId, domain) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_SUCCESS,
+  antennaId,
+  domain,
+});
+
+export const addExcludeDomainToAntennaFail = (antennaId, domain, error) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_FAIL,
+  antennaId,
+  domain,
+  error,
+});
+
+export const removeExcludeDomainFromAntenna = (antennaId, domain) => (dispatch, getState) => {
+  dispatch(removeExcludeDomainFromAntennaRequest(antennaId, domain));
+
+  api(getState).delete(`/api/v1/antennas/${antennaId}/exclude_domains`, { params: { domains: [domain] } })
+    .then(() => dispatch(removeExcludeDomainFromAntennaSuccess(antennaId, domain)))
+    .catch(err => dispatch(removeExcludeDomainFromAntennaFail(antennaId, domain, err)));
+};
+
+export const removeExcludeDomainFromAntennaRequest = (antennaId, domain) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_REQUEST,
+  antennaId,
+  domain,
+});
+
+export const removeExcludeDomainFromAntennaSuccess = (antennaId, domain) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_SUCCESS,
+  antennaId,
+  domain,
+});
+
+export const removeExcludeDomainFromAntennaFail = (antennaId, domain, error) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_FAIL,
+  antennaId,
+  domain,
+  error,
+});
+
+export const fetchAntennaKeywords = antennaId => (dispatch, getState) => {
+  dispatch(fetchAntennaKeywordsRequest(antennaId));
+
+  api(getState).get(`/api/v1/antennas/${antennaId}/keywords`, { params: { limit: 0 } }).then(({ data }) => {
+    dispatch(fetchAntennaKeywordsSuccess(antennaId, data));
+  }).catch(err => dispatch(fetchAntennaKeywordsFail(antennaId, err)));
+};
+
+export const fetchAntennaKeywordsRequest = id => ({
+  type: ANTENNA_EDITOR_FETCH_KEYWORDS_REQUEST,
+  id,
+});
+
+export const fetchAntennaKeywordsSuccess = (id, keywords) => ({
+  type: ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS,
+  id,
+  keywords,
+});
+
+export const fetchAntennaKeywordsFail = (id, error) => ({
+  type: ANTENNA_EDITOR_FETCH_KEYWORDS_FAIL,
+  id,
+  error,
+});
+
+export const addKeywordToAntenna = (antennaId, keyword) => (dispatch, getState) => {
+  dispatch(addKeywordToAntennaRequest(antennaId, keyword));
+
+  api(getState).post(`/api/v1/antennas/${antennaId}/keywords`, { keywords: [keyword] })
+    .then(() => dispatch(addKeywordToAntennaSuccess(antennaId, keyword)))
+    .catch(err => dispatch(addKeywordToAntennaFail(antennaId, keyword, err)));
+};
+
+export const addKeywordToAntennaRequest = (antennaId, keyword) => ({
+  type: ANTENNA_EDITOR_ADD_KEYWORD_REQUEST,
+  antennaId,
+  keyword,
+});
+
+export const addKeywordToAntennaSuccess = (antennaId, keyword) => ({
+  type: ANTENNA_EDITOR_ADD_KEYWORD_SUCCESS,
+  antennaId,
+  keyword,
+});
+
+export const addKeywordToAntennaFail = (antennaId, keyword, error) => ({
+  type: ANTENNA_EDITOR_ADD_KEYWORD_FAIL,
+  antennaId,
+  keyword,
+  error,
+});
+
+export const removeKeywordFromAntenna = (antennaId, keyword) => (dispatch, getState) => {
+  dispatch(removeKeywordFromAntennaRequest(antennaId, keyword));
+
+  api(getState).delete(`/api/v1/antennas/${antennaId}/keywords`, { params: { keywords: [keyword] } })
+    .then(() => dispatch(removeKeywordFromAntennaSuccess(antennaId, keyword)))
+    .catch(err => dispatch(removeKeywordFromAntennaFail(antennaId, keyword, err)));
+};
+
+export const removeKeywordFromAntennaRequest = (antennaId, keyword) => ({
+  type: ANTENNA_EDITOR_REMOVE_KEYWORD_REQUEST,
+  antennaId,
+  keyword,
+});
+
+export const removeKeywordFromAntennaSuccess = (antennaId, keyword) => ({
+  type: ANTENNA_EDITOR_REMOVE_KEYWORD_SUCCESS,
+  antennaId,
+  keyword,
+});
+
+export const removeKeywordFromAntennaFail = (antennaId, keyword, error) => ({
+  type: ANTENNA_EDITOR_REMOVE_KEYWORD_FAIL,
+  antennaId,
+  keyword,
+  error,
+});
+
+export const addExcludeKeywordToAntenna = (antennaId, keyword) => (dispatch, getState) => {
+  dispatch(addExcludeKeywordToAntennaRequest(antennaId, keyword));
+
+  api(getState).post(`/api/v1/antennas/${antennaId}/exclude_keywords`, { keywords: [keyword] })
+    .then(() => dispatch(addExcludeKeywordToAntennaSuccess(antennaId, keyword)))
+    .catch(err => dispatch(addExcludeKeywordToAntennaFail(antennaId, keyword, err)));
+};
+
+export const addExcludeKeywordToAntennaRequest = (antennaId, keyword) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_REQUEST,
+  antennaId,
+  keyword,
+});
+
+export const addExcludeKeywordToAntennaSuccess = (antennaId, keyword) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_SUCCESS,
+  antennaId,
+  keyword,
+});
+
+export const addExcludeKeywordToAntennaFail = (antennaId, keyword, error) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_FAIL,
+  antennaId,
+  keyword,
+  error,
+});
+
+export const removeExcludeKeywordFromAntenna = (antennaId, keyword) => (dispatch, getState) => {
+  dispatch(removeExcludeKeywordFromAntennaRequest(antennaId, keyword));
+
+  api(getState).delete(`/api/v1/antennas/${antennaId}/exclude_keywords`, { params: { keywords: [keyword] } })
+    .then(() => dispatch(removeExcludeKeywordFromAntennaSuccess(antennaId, keyword)))
+    .catch(err => dispatch(removeExcludeKeywordFromAntennaFail(antennaId, keyword, err)));
+};
+
+export const removeExcludeKeywordFromAntennaRequest = (antennaId, keyword) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_REQUEST,
+  antennaId,
+  keyword,
+});
+
+export const removeExcludeKeywordFromAntennaSuccess = (antennaId, keyword) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS,
+  antennaId,
+  keyword,
+});
+
+export const removeExcludeKeywordFromAntennaFail = (antennaId, keyword, error) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_FAIL,
+  antennaId,
+  keyword,
+  error,
+});
+
+export const fetchAntennaTags = antennaId => (dispatch, getState) => {
+  dispatch(fetchAntennaTagsRequest(antennaId));
+
+  api(getState).get(`/api/v1/antennas/${antennaId}/tags`, { params: { limit: 0 } }).then(({ data }) => {
+    dispatch(fetchAntennaTagsSuccess(antennaId, data));
+  }).catch(err => dispatch(fetchAntennaTagsFail(antennaId, err)));
+};
+
+export const fetchAntennaTagsRequest = id => ({
+  type: ANTENNA_EDITOR_FETCH_TAGS_REQUEST,
+  id,
+});
+
+export const fetchAntennaTagsSuccess = (id, tags) => ({
+  type: ANTENNA_EDITOR_FETCH_TAGS_SUCCESS,
+  id,
+  tags,
+});
+
+export const fetchAntennaTagsFail = (id, error) => ({
+  type: ANTENNA_EDITOR_FETCH_TAGS_FAIL,
+  id,
+  error,
+});
+
+export const addTagToAntenna = (antennaId, tag) => (dispatch, getState) => {
+  dispatch(addTagToAntennaRequest(antennaId, tag));
+
+  api(getState).post(`/api/v1/antennas/${antennaId}/tags`, { tags: [tag] })
+    .then(() => dispatch(addTagToAntennaSuccess(antennaId, tag)))
+    .catch(err => dispatch(addTagToAntennaFail(antennaId, tag, err)));
+};
+
+export const addTagToAntennaRequest = (antennaId, tag) => ({
+  type: ANTENNA_EDITOR_ADD_TAG_REQUEST,
+  antennaId,
+  tag,
+});
+
+export const addTagToAntennaSuccess = (antennaId, tag) => ({
+  type: ANTENNA_EDITOR_ADD_TAG_SUCCESS,
+  antennaId,
+  tag,
+});
+
+export const addTagToAntennaFail = (antennaId, tag, error) => ({
+  type: ANTENNA_EDITOR_ADD_TAG_FAIL,
+  antennaId,
+  tag,
+  error,
+});
+
+export const removeTagFromAntenna = (antennaId, tag) => (dispatch, getState) => {
+  dispatch(removeTagFromAntennaRequest(antennaId, tag));
+
+  api(getState).delete(`/api/v1/antennas/${antennaId}/tags`, { params: { tags: [tag] } })
+    .then(() => dispatch(removeTagFromAntennaSuccess(antennaId, tag)))
+    .catch(err => dispatch(removeTagFromAntennaFail(antennaId, tag, err)));
+};
+
+export const removeTagFromAntennaRequest = (antennaId, tag) => ({
+  type: ANTENNA_EDITOR_REMOVE_TAG_REQUEST,
+  antennaId,
+  tag,
+});
+
+export const removeTagFromAntennaSuccess = (antennaId, tag) => ({
+  type: ANTENNA_EDITOR_REMOVE_TAG_SUCCESS,
+  antennaId,
+  tag,
+});
+
+export const removeTagFromAntennaFail = (antennaId, tag, error) => ({
+  type: ANTENNA_EDITOR_REMOVE_TAG_FAIL,
+  antennaId,
+  tag,
+  error,
+});
+
+export const addExcludeTagToAntenna = (antennaId, tag) => (dispatch, getState) => {
+  dispatch(addExcludeTagToAntennaRequest(antennaId, tag));
+
+  api(getState).post(`/api/v1/antennas/${antennaId}/exclude_tags`, { tags: [tag] })
+    .then(() => dispatch(addExcludeTagToAntennaSuccess(antennaId, tag)))
+    .catch(err => dispatch(addExcludeTagToAntennaFail(antennaId, tag, err)));
+};
+
+export const addExcludeTagToAntennaRequest = (antennaId, tag) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_TAG_REQUEST,
+  antennaId,
+  tag,
+});
+
+export const addExcludeTagToAntennaSuccess = (antennaId, tag) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_TAG_SUCCESS,
+  antennaId,
+  tag,
+});
+
+export const addExcludeTagToAntennaFail = (antennaId, tag, error) => ({
+  type: ANTENNA_EDITOR_ADD_EXCLUDE_TAG_FAIL,
+  antennaId,
+  tag,
+  error,
+});
+
+export const removeExcludeTagFromAntenna = (antennaId, tag) => (dispatch, getState) => {
+  dispatch(removeExcludeTagFromAntennaRequest(antennaId, tag));
+
+  api(getState).delete(`/api/v1/antennas/${antennaId}/exclude_tags`, { params: { tags: [tag] } })
+    .then(() => dispatch(removeExcludeTagFromAntennaSuccess(antennaId, tag)))
+    .catch(err => dispatch(removeExcludeTagFromAntennaFail(antennaId, tag, err)));
+};
+
+export const removeExcludeTagFromAntennaRequest = (antennaId, tag) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_REQUEST,
+  antennaId,
+  tag,
+});
+
+export const removeExcludeTagFromAntennaSuccess = (antennaId, tag) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_SUCCESS,
+  antennaId,
+  tag,
+});
+
+export const removeExcludeTagFromAntennaFail = (antennaId, tag, error) => ({
+  type: ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_FAIL,
+  antennaId,
+  tag,
+  error,
+});
+
+export const resetAntennaAdder = () => ({
+  type: ANTENNA_ADDER_RESET,
+});
+
+export const setupAntennaAdder = accountId => (dispatch, getState) => {
+  dispatch({
+    type: ANTENNA_ADDER_SETUP,
+    account: getState().getIn(['accounts', accountId]),
+  });
+  dispatch(fetchAntennas());
+  dispatch(fetchAccountAntennas(accountId));
+};
+
+export const setupExcludeAntennaAdder = accountId => (dispatch, getState) => {
+  dispatch({
+    type: ANTENNA_ADDER_SETUP,
+    account: getState().getIn(['accounts', accountId]),
+  });
+  dispatch(fetchAntennas());
+  dispatch(fetchExcludeAccountAntennas(accountId));
+};
+
+export const fetchAccountAntennas = accountId => (dispatch, getState) => {
+  dispatch(fetchAccountAntennasRequest(accountId));
+
+  api(getState).get(`/api/v1/accounts/${accountId}/antennas`)
+    .then(({ data }) => dispatch(fetchAccountAntennasSuccess(accountId, data)))
+    .catch(err => dispatch(fetchAccountAntennasFail(accountId, err)));
+};
+
+export const fetchAccountAntennasRequest = id => ({
+  type:ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST,
+  id,
+});
+
+export const fetchAccountAntennasSuccess = (id, antennas) => ({
+  type: ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS,
+  id,
+  antennas,
+});
+
+export const fetchAccountAntennasFail = (id, err) => ({
+  type: ANTENNA_ADDER_ANTENNAS_FETCH_FAIL,
+  id,
+  err,
+});
+
+export const fetchExcludeAccountAntennas = accountId => (dispatch, getState) => {
+  dispatch(fetchExcludeAccountAntennasRequest(accountId));
+
+  api(getState).get(`/api/v1/accounts/${accountId}/exclude_antennas`)
+    .then(({ data }) => dispatch(fetchExcludeAccountAntennasSuccess(accountId, data)))
+    .catch(err => dispatch(fetchExcludeAccountAntennasFail(accountId, err)));
+};
+
+export const fetchExcludeAccountAntennasRequest = id => ({
+  type:ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST,
+  id,
+});
+
+export const fetchExcludeAccountAntennasSuccess = (id, antennas) => ({
+  type: ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS,
+  id,
+  antennas,
+});
+
+export const fetchExcludeAccountAntennasFail = (id, err) => ({
+  type: ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL,
+  id,
+  err,
+});
+
+export const addToAntennaAdder = antennaId => (dispatch, getState) => {
+  dispatch(addToAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId'])));
+};
+
+export const removeFromAntennaAdder = antennaId => (dispatch, getState) => {
+  dispatch(removeFromAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId'])));
+};
+
+export const addExcludeToAntennaAdder = antennaId => (dispatch, getState) => {
+  dispatch(addExcludeToAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId'])));
+};
+
+export const removeExcludeFromAntennaAdder = antennaId => (dispatch, getState) => {
+  dispatch(removeExcludeFromAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId'])));
+};
+
diff --git a/app/javascript/mastodon/actions/antennas_typed.ts b/app/javascript/mastodon/actions/antennas_typed.ts
deleted file mode 100644
index f385b37d6e..0000000000
--- a/app/javascript/mastodon/actions/antennas_typed.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { apiCreate, apiUpdate } from 'mastodon/api/antennas';
-import type { Antenna } from 'mastodon/models/antenna';
-import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
-
-export const createAntenna = createDataLoadingThunk(
-  'antenna/create',
-  (antenna: Partial<Antenna>) => apiCreate(antenna),
-);
-
-export const updateAntenna = createDataLoadingThunk(
-  'antenna/update',
-  (antenna: Partial<Antenna>) => apiUpdate(antenna),
-);
diff --git a/app/javascript/mastodon/actions/bookmark_categories.js b/app/javascript/mastodon/actions/bookmark_categories.js
index 313d5de8f2..7d458b85ec 100644
--- a/app/javascript/mastodon/actions/bookmark_categories.js
+++ b/app/javascript/mastodon/actions/bookmark_categories.js
@@ -1,6 +1,10 @@
+import { bookmarkCategoryNeeded } from 'mastodon/initial_state';
+import { makeGetStatus } from 'mastodon/selectors';
+
 import api, { getLinks } from '../api';
 
 import { importFetchedStatuses } from './importer';
+import { unbookmark } from './interactions';
 
 export const BOOKMARK_CATEGORY_FETCH_REQUEST = 'BOOKMARK_CATEGORY_FETCH_REQUEST';
 export const BOOKMARK_CATEGORY_FETCH_SUCCESS = 'BOOKMARK_CATEGORY_FETCH_SUCCESS';
@@ -10,6 +14,18 @@ export const BOOKMARK_CATEGORIES_FETCH_REQUEST = 'BOOKMARK_CATEGORIES_FETCH_REQU
 export const BOOKMARK_CATEGORIES_FETCH_SUCCESS = 'BOOKMARK_CATEGORIES_FETCH_SUCCESS';
 export const BOOKMARK_CATEGORIES_FETCH_FAIL    = 'BOOKMARK_CATEGORIES_FETCH_FAIL';
 
+export const BOOKMARK_CATEGORY_EDITOR_TITLE_CHANGE = 'BOOKMARK_CATEGORY_EDITOR_TITLE_CHANGE';
+export const BOOKMARK_CATEGORY_EDITOR_RESET        = 'BOOKMARK_CATEGORY_EDITOR_RESET';
+export const BOOKMARK_CATEGORY_EDITOR_SETUP        = 'BOOKMARK_CATEGORY_EDITOR_SETUP';
+
+export const BOOKMARK_CATEGORY_CREATE_REQUEST = 'BOOKMARK_CATEGORY_CREATE_REQUEST';
+export const BOOKMARK_CATEGORY_CREATE_SUCCESS = 'BOOKMARK_CATEGORY_CREATE_SUCCESS';
+export const BOOKMARK_CATEGORY_CREATE_FAIL    = 'BOOKMARK_CATEGORY_CREATE_FAIL';
+
+export const BOOKMARK_CATEGORY_UPDATE_REQUEST = 'BOOKMARK_CATEGORY_UPDATE_REQUEST';
+export const BOOKMARK_CATEGORY_UPDATE_SUCCESS = 'BOOKMARK_CATEGORY_UPDATE_SUCCESS';
+export const BOOKMARK_CATEGORY_UPDATE_FAIL    = 'BOOKMARK_CATEGORY_UPDATE_FAIL';
+
 export const BOOKMARK_CATEGORY_DELETE_REQUEST = 'BOOKMARK_CATEGORY_DELETE_REQUEST';
 export const BOOKMARK_CATEGORY_DELETE_SUCCESS = 'BOOKMARK_CATEGORY_DELETE_SUCCESS';
 export const BOOKMARK_CATEGORY_DELETE_FAIL    = 'BOOKMARK_CATEGORY_DELETE_FAIL';
@@ -18,13 +34,25 @@ export const BOOKMARK_CATEGORY_STATUSES_FETCH_REQUEST = 'BOOKMARK_CATEGORY_STATU
 export const BOOKMARK_CATEGORY_STATUSES_FETCH_SUCCESS = 'BOOKMARK_CATEGORY_STATUSES_FETCH_SUCCESS';
 export const BOOKMARK_CATEGORY_STATUSES_FETCH_FAIL    = 'BOOKMARK_CATEGORY_STATUSES_FETCH_FAIL';
 
+export const BOOKMARK_CATEGORY_EDITOR_ADD_REQUEST = 'BOOKMARK_CATEGORY_EDITOR_ADD_REQUEST';
+export const BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS = 'BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS';
+export const BOOKMARK_CATEGORY_EDITOR_ADD_FAIL    = 'BOOKMARK_CATEGORY_EDITOR_ADD_FAIL';
+
+export const BOOKMARK_CATEGORY_EDITOR_REMOVE_REQUEST = 'BOOKMARK_CATEGORY_EDITOR_REMOVE_REQUEST';
+export const BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS = 'BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS';
+export const BOOKMARK_CATEGORY_EDITOR_REMOVE_FAIL    = 'BOOKMARK_CATEGORY_EDITOR_REMOVE_FAIL';
+
+export const BOOKMARK_CATEGORY_ADDER_RESET = 'BOOKMARK_CATEGORY_ADDER_RESET';
+export const BOOKMARK_CATEGORY_ADDER_SETUP = 'BOOKMARK_CATEGORY_ADDER_SETUP';
+
+export const BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_REQUEST = 'BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_REQUEST';
+export const BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_SUCCESS = 'BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_SUCCESS';
+export const BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_FAIL    = 'BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_FAIL';
+
 export const BOOKMARK_CATEGORY_STATUSES_EXPAND_REQUEST = 'BOOKMARK_CATEGORY_STATUSES_EXPAND_REQUEST';
 export const BOOKMARK_CATEGORY_STATUSES_EXPAND_SUCCESS = 'BOOKMARK_CATEGORY_STATUSES_EXPAND_SUCCESS';
 export const BOOKMARK_CATEGORY_STATUSES_EXPAND_FAIL    = 'BOOKMARK_CATEGORY_STATUSES_EXPAND_FAIL';
 
-export const BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS = 'BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS';
-export const BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS = 'BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS';
-
 export const fetchBookmarkCategory = id => (dispatch, getState) => {
   if (getState().getIn(['bookmark_categories', id])) {
     return;
@@ -75,6 +103,89 @@ export const fetchBookmarkCategoriesFail = error => ({
   error,
 });
 
+export const submitBookmarkCategoryEditor = shouldReset => (dispatch, getState) => {
+  const bookmarkCategoryId = getState().getIn(['bookmarkCategoryEditor', 'bookmarkCategoryId']);
+  const title  = getState().getIn(['bookmarkCategoryEditor', 'title']);
+
+  if (bookmarkCategoryId === null) {
+    dispatch(createBookmarkCategory(title, shouldReset));
+  } else {
+    dispatch(updateBookmarkCategory(bookmarkCategoryId, title, shouldReset));
+  }
+};
+
+export const setupBookmarkCategoryEditor = bookmarkCategoryId => (dispatch, getState) => {
+  dispatch({
+    type: BOOKMARK_CATEGORY_EDITOR_SETUP,
+    bookmarkCategory: getState().getIn(['bookmark_categories', bookmarkCategoryId]),
+  });
+
+  dispatch(fetchBookmarkCategoryStatuses(bookmarkCategoryId));
+};
+
+export const changeBookmarkCategoryEditorTitle = value => ({
+  type: BOOKMARK_CATEGORY_EDITOR_TITLE_CHANGE,
+  value,
+});
+
+export const createBookmarkCategory = (title, shouldReset) => (dispatch, getState) => {
+  dispatch(createBookmarkCategoryRequest());
+
+  api(getState).post('/api/v1/bookmark_categories', { title }).then(({ data }) => {
+    dispatch(createBookmarkCategorySuccess(data));
+
+    if (shouldReset) {
+      dispatch(resetBookmarkCategoryEditor());
+    }
+  }).catch(err => dispatch(createBookmarkCategoryFail(err)));
+};
+
+export const createBookmarkCategoryRequest = () => ({
+  type: BOOKMARK_CATEGORY_CREATE_REQUEST,
+});
+
+export const createBookmarkCategorySuccess = bookmarkCategory => ({
+  type: BOOKMARK_CATEGORY_CREATE_SUCCESS,
+  bookmarkCategory,
+});
+
+export const createBookmarkCategoryFail = error => ({
+  type: BOOKMARK_CATEGORY_CREATE_FAIL,
+  error,
+});
+
+export const updateBookmarkCategory = (id, title, shouldReset) => (dispatch, getState) => {
+  dispatch(updateBookmarkCategoryRequest(id));
+
+  api(getState).put(`/api/v1/bookmark_categories/${id}`, { title }).then(({ data }) => {
+    dispatch(updateBookmarkCategorySuccess(data));
+
+    if (shouldReset) {
+      dispatch(resetBookmarkCategoryEditor());
+    }
+  }).catch(err => dispatch(updateBookmarkCategoryFail(id, err)));
+};
+
+export const updateBookmarkCategoryRequest = id => ({
+  type: BOOKMARK_CATEGORY_UPDATE_REQUEST,
+  id,
+});
+
+export const updateBookmarkCategorySuccess = bookmarkCategory => ({
+  type: BOOKMARK_CATEGORY_UPDATE_SUCCESS,
+  bookmarkCategory,
+});
+
+export const updateBookmarkCategoryFail = (id, error) => ({
+  type: BOOKMARK_CATEGORY_UPDATE_FAIL,
+  id,
+  error,
+});
+
+export const resetBookmarkCategoryEditor = () => ({
+  type: BOOKMARK_CATEGORY_EDITOR_RESET,
+});
+
 export const deleteBookmarkCategory = id => (dispatch, getState) => {
   dispatch(deleteBookmarkCategoryRequest(id));
 
@@ -127,11 +238,121 @@ export const fetchBookmarkCategoryStatusesFail = (id, error) => ({
   error,
 });
 
+export const addToBookmarkCategory = (bookmarkCategoryId, statusId) => (dispatch, getState) => {
+  dispatch(addToBookmarkCategoryRequest(bookmarkCategoryId, statusId));
+
+  api(getState).post(`/api/v1/bookmark_categories/${bookmarkCategoryId}/statuses`, { status_ids: [statusId] })
+    .then(() => dispatch(addToBookmarkCategorySuccess(bookmarkCategoryId, statusId)))
+    .catch(err => dispatch(addToBookmarkCategoryFail(bookmarkCategoryId, statusId, err)));
+};
+
+export const addToBookmarkCategoryRequest = (bookmarkCategoryId, statusId) => ({
+  type: BOOKMARK_CATEGORY_EDITOR_ADD_REQUEST,
+  bookmarkCategoryId,
+  statusId,
+});
+
+export const addToBookmarkCategorySuccess = (bookmarkCategoryId, statusId) => ({
+  type: BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS,
+  bookmarkCategoryId,
+  statusId,
+});
+
+export const addToBookmarkCategoryFail = (bookmarkCategoryId, statusId, error) => ({
+  type: BOOKMARK_CATEGORY_EDITOR_ADD_FAIL,
+  bookmarkCategoryId,
+  statusId,
+  error,
+});
+
+export const removeFromBookmarkCategory = (bookmarkCategoryId, statusId) => (dispatch, getState) => {
+  dispatch(removeFromBookmarkCategoryRequest(bookmarkCategoryId, statusId));
+
+  api(getState).delete(`/api/v1/bookmark_categories/${bookmarkCategoryId}/statuses`, { params: { status_ids: [statusId] } })
+    .then(() => dispatch(removeFromBookmarkCategorySuccess(bookmarkCategoryId, statusId)))
+    .catch(err => dispatch(removeFromBookmarkCategoryFail(bookmarkCategoryId, statusId, err)));
+};
+
+export const removeFromBookmarkCategoryRequest = (bookmarkCategoryId, statusId) => ({
+  type: BOOKMARK_CATEGORY_EDITOR_REMOVE_REQUEST,
+  bookmarkCategoryId,
+  statusId,
+});
+
+export const removeFromBookmarkCategorySuccess = (bookmarkCategoryId, statusId) => ({
+  type: BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS,
+  bookmarkCategoryId,
+  statusId,
+});
+
+export const removeFromBookmarkCategoryFail = (bookmarkCategoryId, statusId, error) => ({
+  type: BOOKMARK_CATEGORY_EDITOR_REMOVE_FAIL,
+  bookmarkCategoryId,
+  statusId,
+  error,
+});
+
+export const resetBookmarkCategoryAdder = () => ({
+  type: BOOKMARK_CATEGORY_ADDER_RESET,
+});
+
+export const setupBookmarkCategoryAdder = statusId => (dispatch, getState) => {
+  dispatch({
+    type: BOOKMARK_CATEGORY_ADDER_SETUP,
+    status: getState().getIn(['statuses', statusId]),
+  });
+  dispatch(fetchBookmarkCategories());
+  dispatch(fetchStatusBookmarkCategories(statusId));
+};
+
+export const fetchStatusBookmarkCategories = statusId => (dispatch, getState) => {
+  dispatch(fetchStatusBookmarkCategoriesRequest(statusId));
+
+  api(getState).get(`/api/v1/statuses/${statusId}/bookmark_categories`)
+    .then(({ data }) => dispatch(fetchStatusBookmarkCategoriesSuccess(statusId, data)))
+    .catch(err => dispatch(fetchStatusBookmarkCategoriesFail(statusId, err)));
+};
+
+export const fetchStatusBookmarkCategoriesRequest = id => ({
+  type:BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_REQUEST,
+  id,
+});
+
+export const fetchStatusBookmarkCategoriesSuccess = (id, bookmarkCategories) => ({
+  type: BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_SUCCESS,
+  id,
+  bookmarkCategories,
+});
+
+export const fetchStatusBookmarkCategoriesFail = (id, err) => ({
+  type: BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_FAIL,
+  id,
+  err,
+});
+
+export const addToBookmarkCategoryAdder = bookmarkCategoryId => (dispatch, getState) => {
+  dispatch(addToBookmarkCategory(bookmarkCategoryId, getState().getIn(['bookmarkCategoryAdder', 'statusId'])));
+};
+
+export const removeFromBookmarkCategoryAdder = bookmarkCategoryId => (dispatch, getState) => {
+  if (bookmarkCategoryNeeded) {
+    const categories = getState().getIn(['bookmarkCategoryAdder', 'bookmarkCategories', 'items']);
+    if (categories && categories.count() <= 1) {
+      const status = makeGetStatus()(getState(), { id: getState().getIn(['bookmarkCategoryAdder', 'statusId']) });
+      dispatch(unbookmark(status));
+    } else {
+      dispatch(removeFromBookmarkCategory(bookmarkCategoryId, getState().getIn(['bookmarkCategoryAdder', 'statusId'])));
+    }
+  } else {
+    dispatch(removeFromBookmarkCategory(bookmarkCategoryId, getState().getIn(['bookmarkCategoryAdder', 'statusId'])));
+  }
+};
+
 export function expandBookmarkCategoryStatuses(bookmarkCategoryId) {
   return (dispatch, getState) => {
-    const url = getState().getIn(['status_lists', 'bookmark_category_statuses', bookmarkCategoryId, 'next'], null);
+    const url = getState().getIn(['bookmark_categories', bookmarkCategoryId, 'next'], null);
 
-    if (url === null || getState().getIn(['status_lists', 'bookmark_category_statuses', bookmarkCategoryId, 'isLoading'])) {
+    if (url === null || getState().getIn(['bookmark_categories', bookmarkCategoryId, 'isLoading'])) {
       return;
     }
 
@@ -171,19 +392,3 @@ export function expandBookmarkCategoryStatusesFail(id, error) {
   };
 }
 
-export function bookmarkCategoryEditorAddSuccess(id, statusId) {
-  return {
-    type: BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS,
-    id,
-    statusId,
-  };
-}
-
-export function bookmarkCategoryEditorRemoveSuccess(id, statusId) {
-  return {
-    type: BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS,
-    id,
-    statusId,
-  };
-}
-
diff --git a/app/javascript/mastodon/actions/bookmark_categories_typed.ts b/app/javascript/mastodon/actions/bookmark_categories_typed.ts
deleted file mode 100644
index 3570ce5991..0000000000
--- a/app/javascript/mastodon/actions/bookmark_categories_typed.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { apiCreate, apiUpdate } from 'mastodon/api/bookmark_categories';
-import type { BookmarkCategory } from 'mastodon/models/bookmark_category';
-import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
-
-export const createBookmarkCategory = createDataLoadingThunk(
-  'bookmark_category/create',
-  (bookmarkCategory: Partial<BookmarkCategory>) => apiCreate(bookmarkCategory),
-);
-
-export const updateBookmarkCategory = createDataLoadingThunk(
-  'bookmark_category/update',
-  (bookmarkCategory: Partial<BookmarkCategory>) => apiUpdate(bookmarkCategory),
-);
diff --git a/app/javascript/mastodon/actions/circles.js b/app/javascript/mastodon/actions/circles.js
index 221c0c683a..7ed41b4045 100644
--- a/app/javascript/mastodon/actions/circles.js
+++ b/app/javascript/mastodon/actions/circles.js
@@ -1,6 +1,7 @@
 import api, { getLinks } from '../api';
 
-import { importFetchedStatuses } from './importer';
+import { showAlertForError } from './alerts';
+import { importFetchedAccounts, importFetchedStatuses } from './importer';
 
 export const CIRCLE_FETCH_REQUEST = 'CIRCLE_FETCH_REQUEST';
 export const CIRCLE_FETCH_SUCCESS = 'CIRCLE_FETCH_SUCCESS';
@@ -10,6 +11,10 @@ export const CIRCLES_FETCH_REQUEST = 'CIRCLES_FETCH_REQUEST';
 export const CIRCLES_FETCH_SUCCESS = 'CIRCLES_FETCH_SUCCESS';
 export const CIRCLES_FETCH_FAIL    = 'CIRCLES_FETCH_FAIL';
 
+export const CIRCLE_EDITOR_TITLE_CHANGE = 'CIRCLE_EDITOR_TITLE_CHANGE';
+export const CIRCLE_EDITOR_RESET        = 'CIRCLE_EDITOR_RESET';
+export const CIRCLE_EDITOR_SETUP        = 'CIRCLE_EDITOR_SETUP';
+
 export const CIRCLE_CREATE_REQUEST = 'CIRCLE_CREATE_REQUEST';
 export const CIRCLE_CREATE_SUCCESS = 'CIRCLE_CREATE_SUCCESS';
 export const CIRCLE_CREATE_FAIL    = 'CIRCLE_CREATE_FAIL';
@@ -22,6 +27,29 @@ export const CIRCLE_DELETE_REQUEST = 'CIRCLE_DELETE_REQUEST';
 export const CIRCLE_DELETE_SUCCESS = 'CIRCLE_DELETE_SUCCESS';
 export const CIRCLE_DELETE_FAIL    = 'CIRCLE_DELETE_FAIL';
 
+export const CIRCLE_ACCOUNTS_FETCH_REQUEST = 'CIRCLE_ACCOUNTS_FETCH_REQUEST';
+export const CIRCLE_ACCOUNTS_FETCH_SUCCESS = 'CIRCLE_ACCOUNTS_FETCH_SUCCESS';
+export const CIRCLE_ACCOUNTS_FETCH_FAIL    = 'CIRCLE_ACCOUNTS_FETCH_FAIL';
+
+export const CIRCLE_EDITOR_SUGGESTIONS_CHANGE = 'CIRCLE_EDITOR_SUGGESTIONS_CHANGE';
+export const CIRCLE_EDITOR_SUGGESTIONS_READY  = 'CIRCLE_EDITOR_SUGGESTIONS_READY';
+export const CIRCLE_EDITOR_SUGGESTIONS_CLEAR  = 'CIRCLE_EDITOR_SUGGESTIONS_CLEAR';
+
+export const CIRCLE_EDITOR_ADD_REQUEST = 'CIRCLE_EDITOR_ADD_REQUEST';
+export const CIRCLE_EDITOR_ADD_SUCCESS = 'CIRCLE_EDITOR_ADD_SUCCESS';
+export const CIRCLE_EDITOR_ADD_FAIL    = 'CIRCLE_EDITOR_ADD_FAIL';
+
+export const CIRCLE_EDITOR_REMOVE_REQUEST = 'CIRCLE_EDITOR_REMOVE_REQUEST';
+export const CIRCLE_EDITOR_REMOVE_SUCCESS = 'CIRCLE_EDITOR_REMOVE_SUCCESS';
+export const CIRCLE_EDITOR_REMOVE_FAIL    = 'CIRCLE_EDITOR_REMOVE_FAIL';
+
+export const CIRCLE_ADDER_RESET = 'CIRCLE_ADDER_RESET';
+export const CIRCLE_ADDER_SETUP = 'CIRCLE_ADDER_SETUP';
+
+export const CIRCLE_ADDER_CIRCLES_FETCH_REQUEST = 'CIRCLE_ADDER_CIRCLES_FETCH_REQUEST';
+export const CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS = 'CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS';
+export const CIRCLE_ADDER_CIRCLES_FETCH_FAIL    = 'CIRCLE_ADDER_CIRCLES_FETCH_FAIL';
+
 export const CIRCLE_STATUSES_FETCH_REQUEST = 'CIRCLE_STATUSES_FETCH_REQUEST';
 export const CIRCLE_STATUSES_FETCH_SUCCESS = 'CIRCLE_STATUSES_FETCH_SUCCESS';
 export const CIRCLE_STATUSES_FETCH_FAIL    = 'CIRCLE_STATUSES_FETCH_FAIL';
@@ -80,6 +108,89 @@ export const fetchCirclesFail = error => ({
   error,
 });
 
+export const submitCircleEditor = shouldReset => (dispatch, getState) => {
+  const circleId = getState().getIn(['circleEditor', 'circleId']);
+  const title  = getState().getIn(['circleEditor', 'title']);
+
+  if (circleId === null) {
+    dispatch(createCircle(title, shouldReset));
+  } else {
+    dispatch(updateCircle(circleId, title, shouldReset));
+  }
+};
+
+export const setupCircleEditor = circleId => (dispatch, getState) => {
+  dispatch({
+    type: CIRCLE_EDITOR_SETUP,
+    circle: getState().getIn(['circles', circleId]),
+  });
+
+  dispatch(fetchCircleAccounts(circleId));
+};
+
+export const changeCircleEditorTitle = value => ({
+  type: CIRCLE_EDITOR_TITLE_CHANGE,
+  value,
+});
+
+export const createCircle = (title, shouldReset) => (dispatch, getState) => {
+  dispatch(createCircleRequest());
+
+  api(getState).post('/api/v1/circles', { title }).then(({ data }) => {
+    dispatch(createCircleSuccess(data));
+
+    if (shouldReset) {
+      dispatch(resetCircleEditor());
+    }
+  }).catch(err => dispatch(createCircleFail(err)));
+};
+
+export const createCircleRequest = () => ({
+  type: CIRCLE_CREATE_REQUEST,
+});
+
+export const createCircleSuccess = circle => ({
+  type: CIRCLE_CREATE_SUCCESS,
+  circle,
+});
+
+export const createCircleFail = error => ({
+  type: CIRCLE_CREATE_FAIL,
+  error,
+});
+
+export const updateCircle = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch, getState) => {
+  dispatch(updateCircleRequest(id));
+
+  api(getState).put(`/api/v1/circles/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => {
+    dispatch(updateCircleSuccess(data));
+
+    if (shouldReset) {
+      dispatch(resetCircleEditor());
+    }
+  }).catch(err => dispatch(updateCircleFail(id, err)));
+};
+
+export const updateCircleRequest = id => ({
+  type: CIRCLE_UPDATE_REQUEST,
+  id,
+});
+
+export const updateCircleSuccess = circle => ({
+  type: CIRCLE_UPDATE_SUCCESS,
+  circle,
+});
+
+export const updateCircleFail = (id, error) => ({
+  type: CIRCLE_UPDATE_FAIL,
+  id,
+  error,
+});
+
+export const resetCircleEditor = () => ({
+  type: CIRCLE_EDITOR_RESET,
+});
+
 export const deleteCircle = id => (dispatch, getState) => {
   dispatch(deleteCircleRequest(id));
 
@@ -104,6 +215,169 @@ export const deleteCircleFail = (id, error) => ({
   error,
 });
 
+export const fetchCircleAccounts = circleId => (dispatch, getState) => {
+  dispatch(fetchCircleAccountsRequest(circleId));
+
+  api(getState).get(`/api/v1/circles/${circleId}/accounts`, { params: { limit: 0 } }).then(({ data }) => {
+    dispatch(importFetchedAccounts(data));
+    dispatch(fetchCircleAccountsSuccess(circleId, data));
+  }).catch(err => dispatch(fetchCircleAccountsFail(circleId, err)));
+};
+
+export const fetchCircleAccountsRequest = id => ({
+  type: CIRCLE_ACCOUNTS_FETCH_REQUEST,
+  id,
+});
+
+export const fetchCircleAccountsSuccess = (id, accounts, next) => ({
+  type: CIRCLE_ACCOUNTS_FETCH_SUCCESS,
+  id,
+  accounts,
+  next,
+});
+
+export const fetchCircleAccountsFail = (id, error) => ({
+  type: CIRCLE_ACCOUNTS_FETCH_FAIL,
+  id,
+  error,
+});
+
+export const fetchCircleSuggestions = q => (dispatch, getState) => {
+  const params = {
+    q,
+    resolve: false,
+    follower: true,
+  };
+
+  api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => {
+    dispatch(importFetchedAccounts(data));
+    dispatch(fetchCircleSuggestionsReady(q, data));
+  }).catch(error => dispatch(showAlertForError(error)));
+};
+
+export const fetchCircleSuggestionsReady = (query, accounts) => ({
+  type: CIRCLE_EDITOR_SUGGESTIONS_READY,
+  query,
+  accounts,
+});
+
+export const clearCircleSuggestions = () => ({
+  type: CIRCLE_EDITOR_SUGGESTIONS_CLEAR,
+});
+
+export const changeCircleSuggestions = value => ({
+  type: CIRCLE_EDITOR_SUGGESTIONS_CHANGE,
+  value,
+});
+
+export const addToCircleEditor = accountId => (dispatch, getState) => {
+  dispatch(addToCircle(getState().getIn(['circleEditor', 'circleId']), accountId));
+};
+
+export const addToCircle = (circleId, accountId) => (dispatch, getState) => {
+  dispatch(addToCircleRequest(circleId, accountId));
+
+  api(getState).post(`/api/v1/circles/${circleId}/accounts`, { account_ids: [accountId] })
+    .then(() => dispatch(addToCircleSuccess(circleId, accountId)))
+    .catch(err => dispatch(addToCircleFail(circleId, accountId, err)));
+};
+
+export const addToCircleRequest = (circleId, accountId) => ({
+  type: CIRCLE_EDITOR_ADD_REQUEST,
+  circleId,
+  accountId,
+});
+
+export const addToCircleSuccess = (circleId, accountId) => ({
+  type: CIRCLE_EDITOR_ADD_SUCCESS,
+  circleId,
+  accountId,
+});
+
+export const addToCircleFail = (circleId, accountId, error) => ({
+  type: CIRCLE_EDITOR_ADD_FAIL,
+  circleId,
+  accountId,
+  error,
+});
+
+export const removeFromCircleEditor = accountId => (dispatch, getState) => {
+  dispatch(removeFromCircle(getState().getIn(['circleEditor', 'circleId']), accountId));
+};
+
+export const removeFromCircle = (circleId, accountId) => (dispatch, getState) => {
+  dispatch(removeFromCircleRequest(circleId, accountId));
+
+  api(getState).delete(`/api/v1/circles/${circleId}/accounts`, { params: { account_ids: [accountId] } })
+    .then(() => dispatch(removeFromCircleSuccess(circleId, accountId)))
+    .catch(err => dispatch(removeFromCircleFail(circleId, accountId, err)));
+};
+
+export const removeFromCircleRequest = (circleId, accountId) => ({
+  type: CIRCLE_EDITOR_REMOVE_REQUEST,
+  circleId,
+  accountId,
+});
+
+export const removeFromCircleSuccess = (circleId, accountId) => ({
+  type: CIRCLE_EDITOR_REMOVE_SUCCESS,
+  circleId,
+  accountId,
+});
+
+export const removeFromCircleFail = (circleId, accountId, error) => ({
+  type: CIRCLE_EDITOR_REMOVE_FAIL,
+  circleId,
+  accountId,
+  error,
+});
+
+export const resetCircleAdder = () => ({
+  type: CIRCLE_ADDER_RESET,
+});
+
+export const setupCircleAdder = accountId => (dispatch, getState) => {
+  dispatch({
+    type: CIRCLE_ADDER_SETUP,
+    account: getState().getIn(['accounts', accountId]),
+  });
+  dispatch(fetchCircles());
+  dispatch(fetchAccountCircles(accountId));
+};
+
+export const fetchAccountCircles = accountId => (dispatch, getState) => {
+  dispatch(fetchAccountCirclesRequest(accountId));
+
+  api(getState).get(`/api/v1/accounts/${accountId}/circles`)
+    .then(({ data }) => dispatch(fetchAccountCirclesSuccess(accountId, data)))
+    .catch(err => dispatch(fetchAccountCirclesFail(accountId, err)));
+};
+
+export const fetchAccountCirclesRequest = id => ({
+  type:CIRCLE_ADDER_CIRCLES_FETCH_REQUEST,
+  id,
+});
+
+export const fetchAccountCirclesSuccess = (id, circles) => ({
+  type: CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS,
+  id,
+  circles,
+});
+
+export const fetchAccountCirclesFail = (id, err) => ({
+  type: CIRCLE_ADDER_CIRCLES_FETCH_FAIL,
+  id,
+  err,
+});
+
+export const addToCircleAdder = circleId => (dispatch, getState) => {
+  dispatch(addToCircle(circleId, getState().getIn(['circleAdder', 'accountId'])));
+};
+
+export const removeFromCircleAdder = circleId => (dispatch, getState) => {
+  dispatch(removeFromCircle(circleId, getState().getIn(['circleAdder', 'accountId'])));
+};
+
 export function fetchCircleStatuses(circleId) {
   return (dispatch, getState) => {
     if (getState().getIn(['circles', circleId, 'isLoading'])) {
@@ -152,9 +426,9 @@ export function fetchCircleStatusesFail(id, error) {
 
 export function expandCircleStatuses(circleId) {
   return (dispatch, getState) => {
-    const url = getState().getIn(['status_lists', 'circle_statuses', circleId, 'next'], null);
+    const url = getState().getIn(['circles', circleId, 'next'], null);
 
-    if (url === null || getState().getIn(['status_lists', 'circle_statuses', circleId, 'isLoading'])) {
+    if (url === null || getState().getIn(['circles', circleId, 'isLoading'])) {
       return;
     }
 
diff --git a/app/javascript/mastodon/actions/circles_typed.ts b/app/javascript/mastodon/actions/circles_typed.ts
deleted file mode 100644
index 6f4117ff81..0000000000
--- a/app/javascript/mastodon/actions/circles_typed.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { apiCreate, apiUpdate } from 'mastodon/api/circles';
-import type { Circle } from 'mastodon/models/circle';
-import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
-
-export const createCircle = createDataLoadingThunk(
-  'circle/create',
-  (circle: Partial<Circle>) => apiCreate(circle),
-);
-
-export const updateCircle = createDataLoadingThunk(
-  'circle/update',
-  (circle: Partial<Circle>) => apiUpdate(circle),
-);
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index 9a92528f3a..459847e37b 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -272,14 +272,14 @@ export function submitCompose() {
         insertIfOnline('home');
       }
 
-      if (statusId === null && response.data.in_reply_to_id === null && ['public', 'public_unlisted', 'login'].includes(response.data.visibility_ex)) {
+      if (statusId === null && response.data.in_reply_to_id === null && response.data.visibility_ex === 'public') {
         insertIfOnline('community');
         insertIfOnline('public');
         insertIfOnline(`account:${response.data.account.id}`);
       }
 
       if (statusId === null && privacy === 'circle' && circleId !== null && circleId !== 0) {
-        dispatch(submitComposeWithCircleSuccess({ ...response.data }, `${circleId}`));
+        dispatch(submitComposeWithCircleSuccess({ ...response.data }, circleId));
       }
 
       dispatch(showAlert({
@@ -310,7 +310,7 @@ export function submitComposeSuccess(status) {
 export function submitComposeWithCircleSuccess(status, circleId) {
   return {
     type: COMPOSE_WITH_CIRCLE_SUCCESS,
-    statusId: status.id,
+    status,
     circleId,
   };
 }
@@ -441,7 +441,7 @@ export function initMediaEditModal(id) {
 
     dispatch(openModal({
       modalType: 'FOCAL_POINT',
-      modalProps: { mediaId: id },
+      modalProps: { id },
     }));
   };
 }
diff --git a/app/javascript/mastodon/actions/compose_typed.ts b/app/javascript/mastodon/actions/compose_typed.ts
deleted file mode 100644
index 97f0d68c51..0000000000
--- a/app/javascript/mastodon/actions/compose_typed.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
-
-import { apiUpdateMedia } from 'mastodon/api/compose';
-import type { ApiMediaAttachmentJSON } from 'mastodon/api_types/media_attachments';
-import type { MediaAttachment } from 'mastodon/models/media_attachment';
-import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
-
-type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & {
-  unattached?: boolean;
-};
-
-const simulateModifiedApiResponse = (
-  media: MediaAttachment,
-  params: { description?: string; focus?: string },
-): SimulatedMediaAttachmentJSON => {
-  const [x, y] = (params.focus ?? '').split(',');
-
-  const data = {
-    ...media.toJS(),
-    ...params,
-    meta: {
-      focus: {
-        x: parseFloat(x ?? '0'),
-        y: parseFloat(y ?? '0'),
-      },
-    },
-  } as unknown as SimulatedMediaAttachmentJSON;
-
-  return data;
-};
-
-export const changeUploadCompose = createDataLoadingThunk(
-  'compose/changeUpload',
-  async (
-    {
-      id,
-      ...params
-    }: {
-      id: string;
-      description: string;
-      focus: string;
-    },
-    { getState },
-  ) => {
-    const media = (
-      (getState().compose as ImmutableMap<string, unknown>).get(
-        'media_attachments',
-      ) as ImmutableList<MediaAttachment>
-    ).find((item) => item.get('id') === id);
-
-    // Editing already-attached media is deferred to editing the post itself.
-    // For simplicity's sake, fake an API reply.
-    if (media && !media.get('unattached')) {
-      return new Promise<SimulatedMediaAttachmentJSON>((resolve) => {
-        resolve(simulateModifiedApiResponse(media, params));
-      });
-    }
-
-    return apiUpdateMedia(id, params);
-  },
-  (media: SimulatedMediaAttachmentJSON) => {
-    return {
-      media,
-      attached: typeof media.unattached !== 'undefined' && !media.unattached,
-    };
-  },
-  {
-    useLoadingBar: false,
-  },
-);
diff --git a/app/javascript/mastodon/actions/domain_blocks.js b/app/javascript/mastodon/actions/domain_blocks.js
index 279ec1bef7..727f800af3 100644
--- a/app/javascript/mastodon/actions/domain_blocks.js
+++ b/app/javascript/mastodon/actions/domain_blocks.js
@@ -12,6 +12,14 @@ export const DOMAIN_BLOCK_FAIL    = 'DOMAIN_BLOCK_FAIL';
 export const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST';
 export const DOMAIN_UNBLOCK_FAIL    = 'DOMAIN_UNBLOCK_FAIL';
 
+export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST';
+export const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS';
+export const DOMAIN_BLOCKS_FETCH_FAIL    = 'DOMAIN_BLOCKS_FETCH_FAIL';
+
+export const DOMAIN_BLOCKS_EXPAND_REQUEST = 'DOMAIN_BLOCKS_EXPAND_REQUEST';
+export const DOMAIN_BLOCKS_EXPAND_SUCCESS = 'DOMAIN_BLOCKS_EXPAND_SUCCESS';
+export const DOMAIN_BLOCKS_EXPAND_FAIL    = 'DOMAIN_BLOCKS_EXPAND_FAIL';
+
 export function blockDomain(domain) {
   return (dispatch, getState) => {
     dispatch(blockDomainRequest(domain));
@@ -71,6 +79,80 @@ export function unblockDomainFail(domain, error) {
   };
 }
 
+export function fetchDomainBlocks() {
+  return (dispatch) => {
+    dispatch(fetchDomainBlocksRequest());
+
+    api().get('/api/v1/domain_blocks').then(response => {
+      const next = getLinks(response).refs.find(link => link.rel === 'next');
+      dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null));
+    }).catch(err => {
+      dispatch(fetchDomainBlocksFail(err));
+    });
+  };
+}
+
+export function fetchDomainBlocksRequest() {
+  return {
+    type: DOMAIN_BLOCKS_FETCH_REQUEST,
+  };
+}
+
+export function fetchDomainBlocksSuccess(domains, next) {
+  return {
+    type: DOMAIN_BLOCKS_FETCH_SUCCESS,
+    domains,
+    next,
+  };
+}
+
+export function fetchDomainBlocksFail(error) {
+  return {
+    type: DOMAIN_BLOCKS_FETCH_FAIL,
+    error,
+  };
+}
+
+export function expandDomainBlocks() {
+  return (dispatch, getState) => {
+    const url = getState().getIn(['domain_lists', 'blocks', 'next']);
+
+    if (!url) {
+      return;
+    }
+
+    dispatch(expandDomainBlocksRequest());
+
+    api().get(url).then(response => {
+      const next = getLinks(response).refs.find(link => link.rel === 'next');
+      dispatch(expandDomainBlocksSuccess(response.data, next ? next.uri : null));
+    }).catch(err => {
+      dispatch(expandDomainBlocksFail(err));
+    });
+  };
+}
+
+export function expandDomainBlocksRequest() {
+  return {
+    type: DOMAIN_BLOCKS_EXPAND_REQUEST,
+  };
+}
+
+export function expandDomainBlocksSuccess(domains, next) {
+  return {
+    type: DOMAIN_BLOCKS_EXPAND_SUCCESS,
+    domains,
+    next,
+  };
+}
+
+export function expandDomainBlocksFail(error) {
+  return {
+    type: DOMAIN_BLOCKS_EXPAND_FAIL,
+    error,
+  };
+}
+
 export const initDomainBlockModal = account => dispatch => dispatch(openModal({
   modalType: 'DOMAIN_BLOCK',
   modalProps: {
diff --git a/app/javascript/mastodon/actions/dropdown_menu.ts b/app/javascript/mastodon/actions/dropdown_menu.ts
index d9d395ba33..3694df1ae0 100644
--- a/app/javascript/mastodon/actions/dropdown_menu.ts
+++ b/app/javascript/mastodon/actions/dropdown_menu.ts
@@ -1,11 +1,11 @@
 import { createAction } from '@reduxjs/toolkit';
 
 export const openDropdownMenu = createAction<{
-  id: number;
+  id: string;
   keyboard: boolean;
-  scrollKey?: string;
+  scrollKey: string;
 }>('dropdownMenu/open');
 
-export const closeDropdownMenu = createAction<{ id: number }>(
+export const closeDropdownMenu = createAction<{ id: string }>(
   'dropdownMenu/close',
 );
diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js
index fc165b1a1f..ebf58b761a 100644
--- a/app/javascript/mastodon/actions/importer/index.js
+++ b/app/javascript/mastodon/actions/importer/index.js
@@ -1,12 +1,10 @@
-import { createPollFromServerJSON } from 'mastodon/models/poll';
-
 import { importAccounts } from '../accounts_typed';
 
-import { normalizeStatus } from './normalizer';
-import { importPolls } from './polls';
+import { normalizeStatus, normalizePoll } from './normalizer';
 
 export const STATUS_IMPORT   = 'STATUS_IMPORT';
 export const STATUSES_IMPORT = 'STATUSES_IMPORT';
+export const POLLS_IMPORT    = 'POLLS_IMPORT';
 export const FILTERS_IMPORT  = 'FILTERS_IMPORT';
 
 function pushUnique(array, object) {
@@ -27,6 +25,10 @@ export function importFilters(filters) {
   return { type: FILTERS_IMPORT, filters };
 }
 
+export function importPolls(polls) {
+  return { type: POLLS_IMPORT, polls };
+}
+
 export function importFetchedAccount(account) {
   return importFetchedAccounts([account]);
 }
@@ -75,7 +77,7 @@ export function importFetchedStatuses(statuses) {
       }
 
       if (status.poll?.id) {
-        pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls[status.poll.id]));
+        pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id])));
       }
 
       if (status.card) {
@@ -85,9 +87,15 @@ export function importFetchedStatuses(statuses) {
 
     statuses.forEach(processStatus);
 
-    dispatch(importPolls({ polls }));
+    dispatch(importPolls(polls));
     dispatch(importFetchedAccounts(accounts));
     dispatch(importStatuses(normalStatuses));
     dispatch(importFilters(filters));
   };
 }
+
+export function importFetchedPoll(poll) {
+  return (dispatch, getState) => {
+    dispatch(importPolls([normalizePoll(poll, getState().getIn(['polls', poll.id]))]));
+  };
+}
diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js
index b643cf5613..d9e9fef0c6 100644
--- a/app/javascript/mastodon/actions/importer/normalizer.js
+++ b/app/javascript/mastodon/actions/importer/normalizer.js
@@ -1,12 +1,15 @@
 import escapeTextContentForBrowser from 'escape-html';
 
-import { makeEmojiMap } from 'mastodon/models/custom_emoji';
-
 import emojify from '../../features/emoji/emoji';
 import { expandSpoilers, me } from '../../initial_state';
 
 const domParser = new DOMParser();
 
+const makeEmojiMap = emojis => emojis.reduce((obj, emoji) => {
+  obj[`:${emoji.shortcode}:`] = emoji;
+  return obj;
+}, {});
+
 export function searchTextFromRawStatus (status) {
   const spoilerText   = status.spoiler_text || '';
   const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
@@ -137,6 +140,38 @@ export function normalizeStatusTranslation(translation, status) {
   return normalTranslation;
 }
 
+export function normalizePoll(poll, normalOldPoll) {
+  const normalPoll = { ...poll };
+  const emojiMap = makeEmojiMap(poll.emojis);
+
+  normalPoll.options = poll.options.map((option, index) => {
+    const normalOption = {
+      ...option,
+      voted: poll.own_votes && poll.own_votes.includes(index),
+      titleHtml: emojify(escapeTextContentForBrowser(option.title), emojiMap),
+    };
+
+    if (normalOldPoll && normalOldPoll.getIn(['options', index, 'title']) === option.title) {
+      normalOption.translation = normalOldPoll.getIn(['options', index, 'translation']);
+    }
+
+    return normalOption;
+  });
+
+  return normalPoll;
+}
+
+export function normalizePollOptionTranslation(translation, poll) {
+  const emojiMap = makeEmojiMap(poll.get('emojis').toJS());
+
+  const normalTranslation = {
+    ...translation,
+    titleHtml: emojify(escapeTextContentForBrowser(translation.title), emojiMap),
+  };
+
+  return normalTranslation;
+}
+
 export function normalizeAnnouncement(announcement) {
   const normalAnnouncement = { ...announcement };
   const emojiMap = makeEmojiMap(normalAnnouncement.emojis);
diff --git a/app/javascript/mastodon/actions/importer/polls.ts b/app/javascript/mastodon/actions/importer/polls.ts
deleted file mode 100644
index 5bbe7d57d6..0000000000
--- a/app/javascript/mastodon/actions/importer/polls.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { createAction } from '@reduxjs/toolkit';
-
-import type { Poll } from 'mastodon/models/poll';
-
-export const importPolls = createAction<{ polls: Poll[] }>(
-  'poll/importMultiple',
-);
diff --git a/app/javascript/mastodon/actions/lists.js b/app/javascript/mastodon/actions/lists.js
index 7b6dd22041..a7cc1a9329 100644
--- a/app/javascript/mastodon/actions/lists.js
+++ b/app/javascript/mastodon/actions/lists.js
@@ -2,6 +2,9 @@
 
 import api from '../api';
 
+import { showAlertForError } from './alerts';
+import { importFetchedAccounts } from './importer';
+
 export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST';
 export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS';
 export const LIST_FETCH_FAIL    = 'LIST_FETCH_FAIL';
@@ -10,10 +13,45 @@ export const LISTS_FETCH_REQUEST = 'LISTS_FETCH_REQUEST';
 export const LISTS_FETCH_SUCCESS = 'LISTS_FETCH_SUCCESS';
 export const LISTS_FETCH_FAIL    = 'LISTS_FETCH_FAIL';
 
+export const LIST_EDITOR_TITLE_CHANGE = 'LIST_EDITOR_TITLE_CHANGE';
+export const LIST_EDITOR_RESET        = 'LIST_EDITOR_RESET';
+export const LIST_EDITOR_SETUP        = 'LIST_EDITOR_SETUP';
+
+export const LIST_CREATE_REQUEST = 'LIST_CREATE_REQUEST';
+export const LIST_CREATE_SUCCESS = 'LIST_CREATE_SUCCESS';
+export const LIST_CREATE_FAIL    = 'LIST_CREATE_FAIL';
+
+export const LIST_UPDATE_REQUEST = 'LIST_UPDATE_REQUEST';
+export const LIST_UPDATE_SUCCESS = 'LIST_UPDATE_SUCCESS';
+export const LIST_UPDATE_FAIL    = 'LIST_UPDATE_FAIL';
+
 export const LIST_DELETE_REQUEST = 'LIST_DELETE_REQUEST';
 export const LIST_DELETE_SUCCESS = 'LIST_DELETE_SUCCESS';
 export const LIST_DELETE_FAIL    = 'LIST_DELETE_FAIL';
 
+export const LIST_ACCOUNTS_FETCH_REQUEST = 'LIST_ACCOUNTS_FETCH_REQUEST';
+export const LIST_ACCOUNTS_FETCH_SUCCESS = 'LIST_ACCOUNTS_FETCH_SUCCESS';
+export const LIST_ACCOUNTS_FETCH_FAIL    = 'LIST_ACCOUNTS_FETCH_FAIL';
+
+export const LIST_EDITOR_SUGGESTIONS_CHANGE = 'LIST_EDITOR_SUGGESTIONS_CHANGE';
+export const LIST_EDITOR_SUGGESTIONS_READY  = 'LIST_EDITOR_SUGGESTIONS_READY';
+export const LIST_EDITOR_SUGGESTIONS_CLEAR  = 'LIST_EDITOR_SUGGESTIONS_CLEAR';
+
+export const LIST_EDITOR_ADD_REQUEST = 'LIST_EDITOR_ADD_REQUEST';
+export const LIST_EDITOR_ADD_SUCCESS = 'LIST_EDITOR_ADD_SUCCESS';
+export const LIST_EDITOR_ADD_FAIL    = 'LIST_EDITOR_ADD_FAIL';
+
+export const LIST_EDITOR_REMOVE_REQUEST = 'LIST_EDITOR_REMOVE_REQUEST';
+export const LIST_EDITOR_REMOVE_SUCCESS = 'LIST_EDITOR_REMOVE_SUCCESS';
+export const LIST_EDITOR_REMOVE_FAIL    = 'LIST_EDITOR_REMOVE_FAIL';
+
+export const LIST_ADDER_RESET = 'LIST_ADDER_RESET';
+export const LIST_ADDER_SETUP = 'LIST_ADDER_SETUP';
+
+export const LIST_ADDER_LISTS_FETCH_REQUEST = 'LIST_ADDER_LISTS_FETCH_REQUEST';
+export const LIST_ADDER_LISTS_FETCH_SUCCESS = 'LIST_ADDER_LISTS_FETCH_SUCCESS';
+export const LIST_ADDER_LISTS_FETCH_FAIL    = 'LIST_ADDER_LISTS_FETCH_FAIL';
+
 export const fetchList = id => (dispatch, getState) => {
   if (getState().getIn(['lists', id])) {
     return;
@@ -64,6 +102,94 @@ export const fetchListsFail = error => ({
   error,
 });
 
+export const submitListEditor = shouldReset => (dispatch, getState) => {
+  const listId = getState().getIn(['listEditor', 'listId']);
+  const title  = getState().getIn(['listEditor', 'title']);
+
+  if (listId === null) {
+    dispatch(createList(title, shouldReset));
+  } else {
+    dispatch(updateList(listId, title, shouldReset));
+  }
+};
+
+export const setupListEditor = listId => (dispatch, getState) => {
+  dispatch({
+    type: LIST_EDITOR_SETUP,
+    list: getState().getIn(['lists', listId]),
+  });
+
+  dispatch(fetchListAccounts(listId));
+};
+
+export const changeListEditorTitle = value => ({
+  type: LIST_EDITOR_TITLE_CHANGE,
+  value,
+});
+
+export const createList = (title, shouldReset) => (dispatch) => {
+  dispatch(createListRequest());
+
+  api().post('/api/v1/lists', { title }).then(({ data }) => {
+    dispatch(createListSuccess(data));
+
+    if (shouldReset) {
+      dispatch(resetListEditor());
+    }
+  }).catch(err => dispatch(createListFail(err)));
+};
+
+export const createListRequest = () => ({
+  type: LIST_CREATE_REQUEST,
+});
+
+export const createListSuccess = list => ({
+  type: LIST_CREATE_SUCCESS,
+  list,
+});
+
+export const createListFail = error => ({
+  type: LIST_CREATE_FAIL,
+  error,
+});
+
+export const updateList = (id, title, shouldReset, isExclusive, replies_policy, notify) => (dispatch) => {
+  dispatch(updateListRequest(id));
+
+  api().put(`/api/v1/lists/${id}`, {
+    title,
+    replies_policy,
+    exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive,
+    notify: typeof notify === 'undefined' ? undefined : !!notify,
+  }).then(({ data }) => {
+    dispatch(updateListSuccess(data));
+
+    if (shouldReset) {
+      dispatch(resetListEditor());
+    }
+  }).catch(err => dispatch(updateListFail(id, err)));
+};
+
+export const updateListRequest = id => ({
+  type: LIST_UPDATE_REQUEST,
+  id,
+});
+
+export const updateListSuccess = list => ({
+  type: LIST_UPDATE_SUCCESS,
+  list,
+});
+
+export const updateListFail = (id, error) => ({
+  type: LIST_UPDATE_FAIL,
+  id,
+  error,
+});
+
+export const resetListEditor = () => ({
+  type: LIST_EDITOR_RESET,
+});
+
 export const deleteList = id => (dispatch) => {
   dispatch(deleteListRequest(id));
 
@@ -87,3 +213,166 @@ export const deleteListFail = (id, error) => ({
   id,
   error,
 });
+
+export const fetchListAccounts = listId => (dispatch) => {
+  dispatch(fetchListAccountsRequest(listId));
+
+  api().get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ data }) => {
+    dispatch(importFetchedAccounts(data));
+    dispatch(fetchListAccountsSuccess(listId, data));
+  }).catch(err => dispatch(fetchListAccountsFail(listId, err)));
+};
+
+export const fetchListAccountsRequest = id => ({
+  type: LIST_ACCOUNTS_FETCH_REQUEST,
+  id,
+});
+
+export const fetchListAccountsSuccess = (id, accounts, next) => ({
+  type: LIST_ACCOUNTS_FETCH_SUCCESS,
+  id,
+  accounts,
+  next,
+});
+
+export const fetchListAccountsFail = (id, error) => ({
+  type: LIST_ACCOUNTS_FETCH_FAIL,
+  id,
+  error,
+});
+
+export const fetchListSuggestions = q => (dispatch) => {
+  const params = {
+    q,
+    resolve: false,
+    following: true,
+  };
+
+  api().get('/api/v1/accounts/search', { params }).then(({ data }) => {
+    dispatch(importFetchedAccounts(data));
+    dispatch(fetchListSuggestionsReady(q, data));
+  }).catch(error => dispatch(showAlertForError(error)));
+};
+
+export const fetchListSuggestionsReady = (query, accounts) => ({
+  type: LIST_EDITOR_SUGGESTIONS_READY,
+  query,
+  accounts,
+});
+
+export const clearListSuggestions = () => ({
+  type: LIST_EDITOR_SUGGESTIONS_CLEAR,
+});
+
+export const changeListSuggestions = value => ({
+  type: LIST_EDITOR_SUGGESTIONS_CHANGE,
+  value,
+});
+
+export const addToListEditor = accountId => (dispatch, getState) => {
+  dispatch(addToList(getState().getIn(['listEditor', 'listId']), accountId));
+};
+
+export const addToList = (listId, accountId) => (dispatch) => {
+  dispatch(addToListRequest(listId, accountId));
+
+  api().post(`/api/v1/lists/${listId}/accounts`, { account_ids: [accountId] })
+    .then(() => dispatch(addToListSuccess(listId, accountId)))
+    .catch(err => dispatch(addToListFail(listId, accountId, err)));
+};
+
+export const addToListRequest = (listId, accountId) => ({
+  type: LIST_EDITOR_ADD_REQUEST,
+  listId,
+  accountId,
+});
+
+export const addToListSuccess = (listId, accountId) => ({
+  type: LIST_EDITOR_ADD_SUCCESS,
+  listId,
+  accountId,
+});
+
+export const addToListFail = (listId, accountId, error) => ({
+  type: LIST_EDITOR_ADD_FAIL,
+  listId,
+  accountId,
+  error,
+});
+
+export const removeFromListEditor = accountId => (dispatch, getState) => {
+  dispatch(removeFromList(getState().getIn(['listEditor', 'listId']), accountId));
+};
+
+export const removeFromList = (listId, accountId) => (dispatch) => {
+  dispatch(removeFromListRequest(listId, accountId));
+
+  api().delete(`/api/v1/lists/${listId}/accounts`, { params: { account_ids: [accountId] } })
+    .then(() => dispatch(removeFromListSuccess(listId, accountId)))
+    .catch(err => dispatch(removeFromListFail(listId, accountId, err)));
+};
+
+export const removeFromListRequest = (listId, accountId) => ({
+  type: LIST_EDITOR_REMOVE_REQUEST,
+  listId,
+  accountId,
+});
+
+export const removeFromListSuccess = (listId, accountId) => ({
+  type: LIST_EDITOR_REMOVE_SUCCESS,
+  listId,
+  accountId,
+});
+
+export const removeFromListFail = (listId, accountId, error) => ({
+  type: LIST_EDITOR_REMOVE_FAIL,
+  listId,
+  accountId,
+  error,
+});
+
+export const resetListAdder = () => ({
+  type: LIST_ADDER_RESET,
+});
+
+export const setupListAdder = accountId => (dispatch, getState) => {
+  dispatch({
+    type: LIST_ADDER_SETUP,
+    account: getState().getIn(['accounts', accountId]),
+  });
+  dispatch(fetchLists());
+  dispatch(fetchAccountLists(accountId));
+};
+
+export const fetchAccountLists = accountId => (dispatch) => {
+  dispatch(fetchAccountListsRequest(accountId));
+
+  api().get(`/api/v1/accounts/${accountId}/lists`)
+    .then(({ data }) => dispatch(fetchAccountListsSuccess(accountId, data)))
+    .catch(err => dispatch(fetchAccountListsFail(accountId, err)));
+};
+
+export const fetchAccountListsRequest = id => ({
+  type:LIST_ADDER_LISTS_FETCH_REQUEST,
+  id,
+});
+
+export const fetchAccountListsSuccess = (id, lists) => ({
+  type: LIST_ADDER_LISTS_FETCH_SUCCESS,
+  id,
+  lists,
+});
+
+export const fetchAccountListsFail = (id, err) => ({
+  type: LIST_ADDER_LISTS_FETCH_FAIL,
+  id,
+  err,
+});
+
+export const addToListAdder = listId => (dispatch, getState) => {
+  dispatch(addToList(listId, getState().getIn(['listAdder', 'accountId'])));
+};
+
+export const removeFromListAdder = listId => (dispatch, getState) => {
+  dispatch(removeFromList(listId, getState().getIn(['listAdder', 'accountId'])));
+};
diff --git a/app/javascript/mastodon/actions/lists_typed.ts b/app/javascript/mastodon/actions/lists_typed.ts
deleted file mode 100644
index eca051f52c..0000000000
--- a/app/javascript/mastodon/actions/lists_typed.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { apiCreate, apiUpdate } from 'mastodon/api/lists';
-import type { List } from 'mastodon/models/list';
-import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
-
-export const createList = createDataLoadingThunk(
-  'list/create',
-  (list: Partial<List>) => apiCreate(list),
-);
-
-// Kmyblue tracking marker: copied antenna, circle, bookmark_category
-
-export const updateList = createDataLoadingThunk(
-  'list/update',
-  (list: Partial<List>) => apiUpdate(list),
-);
-
-// Kmyblue tracking marker: copied antenna, circle, bookmark_category
diff --git a/app/javascript/mastodon/actions/modal.ts b/app/javascript/mastodon/actions/modal.ts
index 49af176a11..ab03e46765 100644
--- a/app/javascript/mastodon/actions/modal.ts
+++ b/app/javascript/mastodon/actions/modal.ts
@@ -9,7 +9,6 @@ export type ModalType = keyof typeof MODAL_COMPONENTS;
 interface OpenModalPayload {
   modalType: ModalType;
   modalProps: ModalProps;
-  previousModalProps?: ModalProps;
 }
 export const openModal = createAction<OpenModalPayload>('MODAL_OPEN');
 
diff --git a/app/javascript/mastodon/actions/notification_groups.ts b/app/javascript/mastodon/actions/notification_groups.ts
index c7b192accc..77fbc198e8 100644
--- a/app/javascript/mastodon/actions/notification_groups.ts
+++ b/app/javascript/mastodon/actions/notification_groups.ts
@@ -12,7 +12,7 @@ import type {
 } from 'mastodon/api_types/notifications';
 import { allNotificationTypes } from 'mastodon/api_types/notifications';
 import type { ApiStatusJSON } from 'mastodon/api_types/statuses';
-import { enableEmojiReaction, usePendingItems } from 'mastodon/initial_state';
+import { usePendingItems } from 'mastodon/initial_state';
 import type { NotificationGap } from 'mastodon/reducers/notification_groups';
 import {
   selectSettingsNotificationsExcludedTypes,
@@ -37,15 +37,9 @@ function excludeAllTypesExcept(filter: string) {
 function getExcludedTypes(state: RootState) {
   const activeFilter = selectSettingsNotificationsQuickFilterActive(state);
 
-  const types =
-    activeFilter === 'all'
-      ? selectSettingsNotificationsExcludedTypes(state)
-      : excludeAllTypesExcept(activeFilter);
-  if (!enableEmojiReaction && !types.includes('emoji_reaction')) {
-    types.push('emoji_reaction');
-  }
-
-  return types;
+  return activeFilter === 'all'
+    ? selectSettingsNotificationsExcludedTypes(state)
+    : excludeAllTypesExcept(activeFilter);
 }
 
 function dispatchAssociatedRecords(
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index 87b842e51f..f8b2aa13a4 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -1,25 +1,57 @@
 import { IntlMessageFormat } from 'intl-messageformat';
 import { defineMessages } from 'react-intl';
 
+import { List as ImmutableList } from 'immutable';
+
+import { compareId } from 'mastodon/compare_id';
+import { enableEmojiReaction, usePendingItems as preferPendingItems } from 'mastodon/initial_state';
+
+import api, { getLinks } from '../api';
 import { unescapeHTML } from '../utils/html';
 import { requestNotificationPermission } from '../utils/notifications';
 
 import { fetchFollowRequests } from './accounts';
 import {
   importFetchedAccount,
+  importFetchedAccounts,
+  importFetchedStatus,
+  importFetchedStatuses,
 } from './importer';
 import { submitMarkers } from './markers';
 import { notificationsUpdate } from "./notifications_typed";
 import { register as registerPushNotifications } from './push_notifications';
+import { saveSettings } from './settings';
 import { STATUS_EMOJI_REACTION_UPDATE } from './statuses';
 
 export * from "./notifications_typed";
 
+export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
+
+export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
+export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
+export const NOTIFICATIONS_EXPAND_FAIL    = 'NOTIFICATIONS_EXPAND_FAIL';
+
 export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
 
+export const NOTIFICATIONS_SCROLL_TOP   = 'NOTIFICATIONS_SCROLL_TOP';
+export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING';
+
+export const NOTIFICATIONS_MOUNT   = 'NOTIFICATIONS_MOUNT';
+export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
+
+export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
+
 export const NOTIFICATIONS_SET_BROWSER_SUPPORT    = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
 export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION';
 
+export const NOTIFICATION_REQUESTS_ACCEPT_REQUEST = 'NOTIFICATION_REQUESTS_ACCEPT_REQUEST';
+export const NOTIFICATION_REQUESTS_ACCEPT_SUCCESS = 'NOTIFICATION_REQUESTS_ACCEPT_SUCCESS';
+export const NOTIFICATION_REQUESTS_ACCEPT_FAIL    = 'NOTIFICATION_REQUESTS_ACCEPT_FAIL';
+
+export const NOTIFICATION_REQUESTS_DISMISS_REQUEST = 'NOTIFICATION_REQUESTS_DISMISS_REQUEST';
+export const NOTIFICATION_REQUESTS_DISMISS_SUCCESS = 'NOTIFICATION_REQUESTS_DISMISS_SUCCESS';
+export const NOTIFICATION_REQUESTS_DISMISS_FAIL    = 'NOTIFICATION_REQUESTS_DISMISS_FAIL';
+
 const messages = defineMessages({
   // mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
   group: { id: 'notifications.group', defaultMessage: '{count} notifications' },
@@ -37,6 +69,10 @@ const messages = defineMessages({
   message_update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
 });
 
+export const loadPending = () => ({
+  type: NOTIFICATIONS_LOAD_PENDING,
+});
+
 export function updateEmojiReactions(emoji_reaction) {
   return (dispatch) =>
     dispatch({
@@ -47,6 +83,8 @@ export function updateEmojiReactions(emoji_reaction) {
 
 export function updateNotifications(notification, intlMessages, intlLocale) {
   return (dispatch, getState) => {
+    const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']);
+    const showInColumn = activeFilter === 'all' ? getState().getIn(['settings', 'notifications', 'shows', notification.type], true) : activeFilter === notification.type;
     const showAlert    = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
     const playSound    = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true);
 
@@ -55,7 +93,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
     if (['mention', 'status'].includes(notification.type) && notification.status.filtered) {
       const filters = notification.status.filtered.filter(result => result.filter.context.includes('notifications'));
 
-      if (filters.some(result => result.filter.filter_action === 'hide')) {
+      if (filters.some(result => result.filter.filter_action_ex === 'hide')) {
         return;
       }
 
@@ -68,9 +106,25 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
 
     dispatch(submitMarkers());
 
-    // `notificationsUpdate` is still used in `user_lists` and `relationships` reducers
-    dispatch(importFetchedAccount(notification.account));
-    dispatch(notificationsUpdate({ notification, playSound: playSound && !filtered}));
+    if (showInColumn) {
+      dispatch(importFetchedAccount(notification.account));
+
+      if (notification.status) {
+        dispatch(importFetchedStatus(notification.status));
+      }
+
+      if (notification.report) {
+        dispatch(importFetchedAccount(notification.report.target_account));
+      }
+
+
+      dispatch(notificationsUpdate({ notification, preferPendingItems, playSound: playSound && !filtered}));
+    } else if (playSound && !filtered) {
+      dispatch({
+        type: NOTIFICATIONS_UPDATE_NOOP,
+        meta: { sound: 'boop' },
+      });
+    }
 
     // Desktop notifications
     if (typeof window.Notification !== 'undefined' && showAlert && !filtered) {
@@ -91,8 +145,149 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
   };
 }
 
+const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
+
+const excludeTypesFromFilter = filter => {
+  const allTypes = ImmutableList([
+    'follow',
+    'follow_request',
+    'favourite',
+    'emoji_reaction',
+    'reblog',
+    'status_reference',
+    'mention',
+    'poll',
+    'status',
+    'list_status',
+    'update',
+    'admin.sign_up',
+    'admin.report',
+  ]);
+
+  return allTypes.filterNot(item => item === filter).toJS();
+};
+
 const noOp = () => {};
 
+let expandNotificationsController = new AbortController();
+
+export function expandNotifications({ maxId = undefined, forceLoad = false }) {
+  return async (dispatch, getState) => {
+    const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']);
+    const notifications = getState().get('notifications');
+    const isLoadingMore = !!maxId;
+
+    if (notifications.get('isLoading')) {
+      if (forceLoad) {
+        expandNotificationsController.abort();
+        expandNotificationsController = new AbortController();
+      } else {
+        return;
+      }
+    }
+
+    let exclude_types = activeFilter === 'all'
+      ? excludeTypesFromSettings(getState())
+      : excludeTypesFromFilter(activeFilter);
+    if (!enableEmojiReaction && !exclude_types.includes('emoji_reaction')) {
+      exclude_types.push('emoji_reaction');
+    }
+
+    const params = {
+      max_id: maxId,
+      exclude_types,
+    };
+
+    if (!params.max_id && (notifications.get('items', ImmutableList()).size + notifications.get('pendingItems', ImmutableList()).size) > 0) {
+      const a = notifications.getIn(['pendingItems', 0, 'id']);
+      const b = notifications.getIn(['items', 0, 'id']);
+
+      if (a && b && compareId(a, b) > 0) {
+        params.since_id = a;
+      } else {
+        params.since_id = b || a;
+      }
+    }
+
+    const isLoadingRecent = !!params.since_id;
+
+    dispatch(expandNotificationsRequest(isLoadingMore));
+
+    try {
+      const response = await api().get('/api/v1/notifications', { params, signal: expandNotificationsController.signal });
+      const next = getLinks(response).refs.find(link => link.rel === 'next');
+
+      dispatch(importFetchedAccounts(response.data.map(item => item.account)));
+      dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));
+      dispatch(importFetchedAccounts(response.data.filter(item => item.report).map(item => item.report.target_account)));
+
+      dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems));
+      dispatch(submitMarkers());
+    } catch(error) {
+      dispatch(expandNotificationsFail(error, isLoadingMore));
+    }
+  };
+}
+
+export function expandNotificationsRequest(isLoadingMore) {
+  return {
+    type: NOTIFICATIONS_EXPAND_REQUEST,
+    skipLoading: !isLoadingMore,
+  };
+}
+
+export function expandNotificationsSuccess(notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) {
+  return {
+    type: NOTIFICATIONS_EXPAND_SUCCESS,
+    notifications,
+    next,
+    isLoadingRecent: isLoadingRecent,
+    usePendingItems,
+    skipLoading: !isLoadingMore,
+  };
+}
+
+export function expandNotificationsFail(error, isLoadingMore) {
+  return {
+    type: NOTIFICATIONS_EXPAND_FAIL,
+    error,
+    skipLoading: !isLoadingMore,
+    skipAlert: !isLoadingMore || error.name === 'AbortError',
+  };
+}
+
+export function scrollTopNotifications(top) {
+  return {
+    type: NOTIFICATIONS_SCROLL_TOP,
+    top,
+  };
+}
+
+export function setFilter (filterType) {
+  return dispatch => {
+    dispatch({
+      type: NOTIFICATIONS_FILTER_SET,
+      path: ['notifications', 'quickFilter', 'active'],
+      value: filterType,
+    });
+    dispatch(expandNotifications({ forceLoad: true }));
+    dispatch(saveSettings());
+  };
+}
+
+export const mountNotifications = () => ({
+  type: NOTIFICATIONS_MOUNT,
+});
+
+export const unmountNotifications = () => ({
+  type: NOTIFICATIONS_UNMOUNT,
+});
+
+
+export const markNotificationsAsRead = () => ({
+  type: NOTIFICATIONS_MARK_AS_READ,
+});
+
 // Browser support
 export function setupBrowserNotifications() {
   return dispatch => {
diff --git a/app/javascript/mastodon/actions/notifications_migration.tsx b/app/javascript/mastodon/actions/notifications_migration.tsx
new file mode 100644
index 0000000000..cd9f5ca3d6
--- /dev/null
+++ b/app/javascript/mastodon/actions/notifications_migration.tsx
@@ -0,0 +1,10 @@
+import { createAppAsyncThunk } from 'mastodon/store';
+
+import { fetchNotifications } from './notification_groups';
+
+export const initializeNotifications = createAppAsyncThunk(
+  'notifications/initialize',
+  (_, { dispatch }) => {
+    void dispatch(fetchNotifications());
+  },
+);
diff --git a/app/javascript/mastodon/actions/notifications_typed.ts b/app/javascript/mastodon/actions/notifications_typed.ts
index 3eb1230666..88d942d45e 100644
--- a/app/javascript/mastodon/actions/notifications_typed.ts
+++ b/app/javascript/mastodon/actions/notifications_typed.ts
@@ -9,6 +9,7 @@ export const notificationsUpdate = createAction(
     ...args
   }: {
     notification: ApiNotificationJSON;
+    usePendingItems: boolean;
     playSound: boolean;
   }) => ({
     payload: args,
diff --git a/app/javascript/mastodon/actions/polls.js b/app/javascript/mastodon/actions/polls.js
new file mode 100644
index 0000000000..aa49341444
--- /dev/null
+++ b/app/javascript/mastodon/actions/polls.js
@@ -0,0 +1,61 @@
+import api from '../api';
+
+import { importFetchedPoll } from './importer';
+
+export const POLL_VOTE_REQUEST = 'POLL_VOTE_REQUEST';
+export const POLL_VOTE_SUCCESS = 'POLL_VOTE_SUCCESS';
+export const POLL_VOTE_FAIL    = 'POLL_VOTE_FAIL';
+
+export const POLL_FETCH_REQUEST = 'POLL_FETCH_REQUEST';
+export const POLL_FETCH_SUCCESS = 'POLL_FETCH_SUCCESS';
+export const POLL_FETCH_FAIL    = 'POLL_FETCH_FAIL';
+
+export const vote = (pollId, choices) => (dispatch) => {
+  dispatch(voteRequest());
+
+  api().post(`/api/v1/polls/${pollId}/votes`, { choices })
+    .then(({ data }) => {
+      dispatch(importFetchedPoll(data));
+      dispatch(voteSuccess(data));
+    })
+    .catch(err => dispatch(voteFail(err)));
+};
+
+export const fetchPoll = pollId => (dispatch) => {
+  dispatch(fetchPollRequest());
+
+  api().get(`/api/v1/polls/${pollId}`)
+    .then(({ data }) => {
+      dispatch(importFetchedPoll(data));
+      dispatch(fetchPollSuccess(data));
+    })
+    .catch(err => dispatch(fetchPollFail(err)));
+};
+
+export const voteRequest = () => ({
+  type: POLL_VOTE_REQUEST,
+});
+
+export const voteSuccess = poll => ({
+  type: POLL_VOTE_SUCCESS,
+  poll,
+});
+
+export const voteFail = error => ({
+  type: POLL_VOTE_FAIL,
+  error,
+});
+
+export const fetchPollRequest = () => ({
+  type: POLL_FETCH_REQUEST,
+});
+
+export const fetchPollSuccess = poll => ({
+  type: POLL_FETCH_SUCCESS,
+  poll,
+});
+
+export const fetchPollFail = error => ({
+  type: POLL_FETCH_FAIL,
+  error,
+});
diff --git a/app/javascript/mastodon/actions/polls.ts b/app/javascript/mastodon/actions/polls.ts
deleted file mode 100644
index 65a96e8f62..0000000000
--- a/app/javascript/mastodon/actions/polls.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { apiGetPoll, apiPollVote } from 'mastodon/api/polls';
-import type { ApiPollJSON } from 'mastodon/api_types/polls';
-import { createPollFromServerJSON } from 'mastodon/models/poll';
-import {
-  createAppAsyncThunk,
-  createDataLoadingThunk,
-} from 'mastodon/store/typed_functions';
-
-import { importPolls } from './importer/polls';
-
-export const importFetchedPoll = createAppAsyncThunk(
-  'poll/importFetched',
-  (args: { poll: ApiPollJSON }, { dispatch, getState }) => {
-    const { poll } = args;
-
-    dispatch(
-      importPolls({
-        polls: [createPollFromServerJSON(poll, getState().polls[poll.id])],
-      }),
-    );
-  },
-);
-
-export const vote = createDataLoadingThunk(
-  'poll/vote',
-  ({ pollId, choices }: { pollId: string; choices: string[] }) =>
-    apiPollVote(pollId, choices),
-  async (poll, { dispatch, discardLoadData }) => {
-    await dispatch(importFetchedPoll({ poll }));
-    return discardLoadData;
-  },
-);
-
-export const fetchPoll = createDataLoadingThunk(
-  'poll/fetch',
-  ({ pollId }: { pollId: string }) => apiGetPoll(pollId),
-  async (poll, { dispatch }) => {
-    await dispatch(importFetchedPoll({ poll }));
-  },
-);
diff --git a/app/javascript/mastodon/actions/push_notifications/registerer.js b/app/javascript/mastodon/actions/push_notifications/registerer.js
index 647a6bd9fb..b3d3850e31 100644
--- a/app/javascript/mastodon/actions/push_notifications/registerer.js
+++ b/app/javascript/mastodon/actions/push_notifications/registerer.js
@@ -33,7 +33,7 @@ const unsubscribe = ({ registration, subscription }) =>
   subscription ? subscription.unsubscribe().then(() => registration) : registration;
 
 const sendSubscriptionToBackend = (subscription) => {
-  const params = { subscription: { ...subscription.toJSON(), standard: true } };
+  const params = { subscription };
 
   if (me) {
     const data = pushNotificationsSetting.get(me);
diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js
new file mode 100644
index 0000000000..bde17ae0db
--- /dev/null
+++ b/app/javascript/mastodon/actions/search.js
@@ -0,0 +1,215 @@
+import { fromJS } from 'immutable';
+
+import { searchHistory } from 'mastodon/settings';
+
+import api from '../api';
+
+import { fetchRelationships } from './accounts';
+import { importFetchedAccounts, importFetchedStatuses } from './importer';
+
+export const SEARCH_CHANGE = 'SEARCH_CHANGE';
+export const SEARCH_CLEAR  = 'SEARCH_CLEAR';
+export const SEARCH_SHOW   = 'SEARCH_SHOW';
+
+export const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST';
+export const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS';
+export const SEARCH_FETCH_FAIL    = 'SEARCH_FETCH_FAIL';
+
+export const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST';
+export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS';
+export const SEARCH_EXPAND_FAIL    = 'SEARCH_EXPAND_FAIL';
+
+export const SEARCH_HISTORY_UPDATE  = 'SEARCH_HISTORY_UPDATE';
+
+export function changeSearch(value) {
+  return {
+    type: SEARCH_CHANGE,
+    value,
+  };
+}
+
+export function clearSearch() {
+  return {
+    type: SEARCH_CLEAR,
+  };
+}
+
+export function submitSearch(type) {
+  return (dispatch, getState) => {
+    const value    = getState().getIn(['search', 'value']);
+    const signedIn = !!getState().getIn(['meta', 'me']);
+
+    if (value.length === 0) {
+      dispatch(fetchSearchSuccess({ accounts: [], statuses: [], hashtags: [] }, '', type));
+      return;
+    }
+
+    dispatch(fetchSearchRequest(type));
+
+    api().get('/api/v2/search', {
+      params: {
+        q: value,
+        resolve: signedIn,
+        limit: 11,
+        type,
+      },
+    }).then(response => {
+      if (response.data.accounts) {
+        dispatch(importFetchedAccounts(response.data.accounts));
+      }
+
+      if (response.data.statuses) {
+        dispatch(importFetchedStatuses(response.data.statuses));
+      }
+
+      dispatch(fetchSearchSuccess(response.data, value, type));
+      dispatch(fetchRelationships(response.data.accounts.map(item => item.id)));
+    }).catch(error => {
+      dispatch(fetchSearchFail(error));
+    });
+  };
+}
+
+export function fetchSearchRequest(searchType) {
+  return {
+    type: SEARCH_FETCH_REQUEST,
+    searchType,
+  };
+}
+
+export function fetchSearchSuccess(results, searchTerm, searchType) {
+  return {
+    type: SEARCH_FETCH_SUCCESS,
+    results,
+    searchType,
+    searchTerm,
+  };
+}
+
+export function fetchSearchFail(error) {
+  return {
+    type: SEARCH_FETCH_FAIL,
+    error,
+  };
+}
+
+export const expandSearch = type => (dispatch, getState) => {
+  const value  = getState().getIn(['search', 'value']);
+  const offset = getState().getIn(['search', 'results', type]).size - 1;
+
+  dispatch(expandSearchRequest(type));
+
+  api().get('/api/v2/search', {
+    params: {
+      q: value,
+      type,
+      offset,
+      limit: 11,
+    },
+  }).then(({ data }) => {
+    if (data.accounts) {
+      dispatch(importFetchedAccounts(data.accounts));
+    }
+
+    if (data.statuses) {
+      dispatch(importFetchedStatuses(data.statuses));
+    }
+
+    dispatch(expandSearchSuccess(data, value, type));
+    dispatch(fetchRelationships(data.accounts.map(item => item.id)));
+  }).catch(error => {
+    dispatch(expandSearchFail(error));
+  });
+};
+
+export const expandSearchRequest = (searchType) => ({
+  type: SEARCH_EXPAND_REQUEST,
+  searchType,
+});
+
+export const expandSearchSuccess = (results, searchTerm, searchType) => ({
+  type: SEARCH_EXPAND_SUCCESS,
+  results,
+  searchTerm,
+  searchType,
+});
+
+export const expandSearchFail = error => ({
+  type: SEARCH_EXPAND_FAIL,
+  error,
+});
+
+export const showSearch = () => ({
+  type: SEARCH_SHOW,
+});
+
+export const openURL = (value, history, onFailure) => (dispatch, getState) => {
+  const signedIn = !!getState().getIn(['meta', 'me']);
+
+  if (!signedIn) {
+    if (onFailure) {
+      onFailure();
+    }
+
+    return;
+  }
+
+  dispatch(fetchSearchRequest());
+
+  api().get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => {
+    if (response.data.accounts?.length > 0) {
+      dispatch(importFetchedAccounts(response.data.accounts));
+      history.push(`/@${response.data.accounts[0].acct}`);
+    } else if (response.data.statuses?.length > 0) {
+      dispatch(importFetchedStatuses(response.data.statuses));
+      history.push(`/@${response.data.statuses[0].account.acct}/${response.data.statuses[0].id}`);
+    } else if (onFailure) {
+      onFailure();
+    }
+
+    dispatch(fetchSearchSuccess(response.data, value));
+  }).catch(err => {
+    dispatch(fetchSearchFail(err));
+
+    if (onFailure) {
+      onFailure();
+    }
+  });
+};
+
+export const clickSearchResult = (q, type) => (dispatch, getState) => {
+  const previous = getState().getIn(['search', 'recent']);
+
+  if (previous.some(x => x.get('q') === q && x.get('type') === type)) {
+    return;
+  }
+
+  const me = getState().getIn(['meta', 'me']);
+  const current = previous.add(fromJS({ type, q })).takeLast(4);
+
+  searchHistory.set(me, current.toJS());
+  dispatch(updateSearchHistory(current));
+};
+
+export const forgetSearchResult = q => (dispatch, getState) => {
+  const previous = getState().getIn(['search', 'recent']);
+  const me = getState().getIn(['meta', 'me']);
+  const current = previous.filterNot(result => result.get('q') === q);
+
+  searchHistory.set(me, current.toJS());
+  dispatch(updateSearchHistory(current));
+};
+
+export const updateSearchHistory = recent => ({
+  type: SEARCH_HISTORY_UPDATE,
+  recent,
+});
+
+export const hydrateSearch = () => (dispatch, getState) => {
+  const me = getState().getIn(['meta', 'me']);
+  const history = searchHistory.get(me);
+
+  if (history !== null) {
+    dispatch(updateSearchHistory(history));
+  }
+};
diff --git a/app/javascript/mastodon/actions/search.ts b/app/javascript/mastodon/actions/search.ts
deleted file mode 100644
index 13a4ee4432..0000000000
--- a/app/javascript/mastodon/actions/search.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import { createAction } from '@reduxjs/toolkit';
-
-import { apiGetSearch } from 'mastodon/api/search';
-import type { ApiSearchType } from 'mastodon/api_types/search';
-import type {
-  RecentSearch,
-  SearchType as RecentSearchType,
-} from 'mastodon/models/search';
-import { searchHistory } from 'mastodon/settings';
-import {
-  createDataLoadingThunk,
-  createAppAsyncThunk,
-} from 'mastodon/store/typed_functions';
-
-import { fetchRelationships } from './accounts';
-import { importFetchedAccounts, importFetchedStatuses } from './importer';
-
-export const SEARCH_HISTORY_UPDATE = 'SEARCH_HISTORY_UPDATE';
-
-export const submitSearch = createDataLoadingThunk(
-  'search/submit',
-  async ({ q, type }: { q: string; type?: ApiSearchType }, { getState }) => {
-    const signedIn = !!getState().meta.get('me');
-
-    return apiGetSearch({
-      q,
-      type,
-      resolve: signedIn,
-      limit: 11,
-    });
-  },
-  (data, { dispatch }) => {
-    if (data.accounts.length > 0) {
-      dispatch(importFetchedAccounts(data.accounts));
-      dispatch(fetchRelationships(data.accounts.map((account) => account.id)));
-    }
-
-    if (data.statuses.length > 0) {
-      dispatch(importFetchedStatuses(data.statuses));
-    }
-
-    return data;
-  },
-  {
-    useLoadingBar: false,
-  },
-);
-
-export const expandSearch = createDataLoadingThunk(
-  'search/expand',
-  async ({ type }: { type: ApiSearchType }, { getState }) => {
-    const q = getState().search.q;
-    const results = getState().search.results;
-    const offset = results?.[type].length;
-
-    return apiGetSearch({
-      q,
-      type,
-      limit: 10,
-      offset,
-    });
-  },
-  (data, { dispatch }) => {
-    if (data.accounts.length > 0) {
-      dispatch(importFetchedAccounts(data.accounts));
-      dispatch(fetchRelationships(data.accounts.map((account) => account.id)));
-    }
-
-    if (data.statuses.length > 0) {
-      dispatch(importFetchedStatuses(data.statuses));
-    }
-
-    return data;
-  },
-  {
-    useLoadingBar: true,
-  },
-);
-
-export const openURL = createDataLoadingThunk(
-  'search/openURL',
-  ({ url }: { url: string }) =>
-    apiGetSearch({
-      q: url,
-      resolve: true,
-      limit: 1,
-    }),
-  (data, { dispatch }) => {
-    if (data.accounts.length > 0) {
-      dispatch(importFetchedAccounts(data.accounts));
-    } else if (data.statuses.length > 0) {
-      dispatch(importFetchedStatuses(data.statuses));
-    }
-
-    return data;
-  },
-  {
-    useLoadingBar: true,
-  },
-);
-
-export const clickSearchResult = createAppAsyncThunk(
-  'search/clickResult',
-  (
-    { q, type }: { q: string; type?: RecentSearchType },
-    { dispatch, getState },
-  ) => {
-    const previous = getState().search.recent;
-
-    if (previous.some((x) => x.q === q && x.type === type)) {
-      return;
-    }
-
-    const me = getState().meta.get('me') as string;
-    const current = [{ type, q }, ...previous].slice(0, 4);
-
-    searchHistory.set(me, current);
-    dispatch(updateSearchHistory(current));
-  },
-);
-
-export const forgetSearchResult = createAppAsyncThunk(
-  'search/forgetResult',
-  (q: string, { dispatch, getState }) => {
-    const previous = getState().search.recent;
-    const me = getState().meta.get('me') as string;
-    const current = previous.filter((result) => result.q !== q);
-
-    searchHistory.set(me, current);
-    dispatch(updateSearchHistory(current));
-  },
-);
-
-export const updateSearchHistory = createAction<RecentSearch[]>(
-  'search/updateHistory',
-);
-
-export const hydrateSearch = createAppAsyncThunk(
-  'search/hydrate',
-  (_args, { dispatch, getState }) => {
-    const me = getState().meta.get('me') as string;
-    const history = searchHistory.get(me) as RecentSearch[] | null;
-
-    if (history !== null) {
-      dispatch(updateSearchHistory(history));
-    }
-  },
-);
diff --git a/app/javascript/mastodon/actions/settings.js b/app/javascript/mastodon/actions/settings.js
index 7659fb5f98..fbd89f9d4b 100644
--- a/app/javascript/mastodon/actions/settings.js
+++ b/app/javascript/mastodon/actions/settings.js
@@ -29,7 +29,7 @@ const debouncedSave = debounce((dispatch, getState) => {
   api().put('/api/web/settings', { data })
     .then(() => dispatch({ type: SETTING_SAVE }))
     .catch(error => dispatch(showAlertForError(error)));
-}, 2000, { leading: true, trailing: true });
+}, 5000, { trailing: true });
 
 export function saveSettings() {
   return (dispatch, getState) => debouncedSave(dispatch, getState);
diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js
index 5064e65e7b..40ead34782 100644
--- a/app/javascript/mastodon/actions/statuses.js
+++ b/app/javascript/mastodon/actions/statuses.js
@@ -148,7 +148,7 @@ export function deleteStatus(id, withRedraft = false) {
 
     dispatch(deleteStatusRequest(id));
 
-    api().delete(`/api/v1/statuses/${id}`, { params: { delete_media: !withRedraft } }).then(response => {
+    api().delete(`/api/v1/statuses/${id}`).then(response => {
       dispatch(deleteStatusSuccess(id));
       dispatch(deleteFromTimelines(id));
       dispatch(importFetchedAccount(response.data.account));
diff --git a/app/javascript/mastodon/actions/store.js b/app/javascript/mastodon/actions/store.js
index e8fec13453..8ab75cdc44 100644
--- a/app/javascript/mastodon/actions/store.js
+++ b/app/javascript/mastodon/actions/store.js
@@ -1,4 +1,4 @@
-import { fromJS, isIndexed } from 'immutable';
+import { Iterable, fromJS } from 'immutable';
 
 import { hydrateCompose } from './compose';
 import { importFetchedAccounts } from './importer';
@@ -9,7 +9,8 @@ export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
 
 const convertState = rawState =>
   fromJS(rawState, (k, v) =>
-    isIndexed(v) ? v.toList() : v.toMap());
+    Iterable.isIndexed(v) ? v.toList() : v.toMap());
+
 
 export function hydrateStore(rawState) {
   return dispatch => {
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index f9d784c2b4..a828900ec9 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -11,7 +11,7 @@ import {
 } from './announcements';
 import { updateConversations } from './conversations';
 import { processNewNotificationForGroups, refreshStaleNotificationGroups, pollRecentNotifications as pollRecentGroupNotifications } from './notification_groups';
-import { updateNotifications, updateEmojiReactions } from './notifications';
+import { updateNotifications, expandNotifications, updateEmojiReactions } from './notifications';
 import { updateStatus } from './statuses';
 import {
   updateTimeline,
@@ -111,10 +111,12 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
           // @ts-expect-error
           dispatch(updateEmojiReactions(JSON.parse(data.payload)));
           break;
-        case 'notifications_merged': {
+        case 'notifications_merged':
+          const state = getState();
+          if (state.notifications.top || !state.notifications.mounted)
+            dispatch(expandNotifications({ forceLoad: true, maxId: undefined }));
           dispatch(refreshStaleNotificationGroups());
           break;
-        }
         case 'conversation':
           // @ts-expect-error
           dispatch(updateConversations(JSON.parse(data.payload)));
diff --git a/app/javascript/mastodon/actions/suggestions.js b/app/javascript/mastodon/actions/suggestions.js
new file mode 100644
index 0000000000..258ffa901d
--- /dev/null
+++ b/app/javascript/mastodon/actions/suggestions.js
@@ -0,0 +1,58 @@
+import api from '../api';
+
+import { fetchRelationships } from './accounts';
+import { importFetchedAccounts } from './importer';
+
+export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST';
+export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS';
+export const SUGGESTIONS_FETCH_FAIL    = 'SUGGESTIONS_FETCH_FAIL';
+
+export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS';
+
+export function fetchSuggestions(withRelationships = false) {
+  return (dispatch) => {
+    dispatch(fetchSuggestionsRequest());
+
+    api().get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => {
+      dispatch(importFetchedAccounts(response.data.map(x => x.account)));
+      dispatch(fetchSuggestionsSuccess(response.data));
+
+      if (withRelationships) {
+        dispatch(fetchRelationships(response.data.map(item => item.account.id)));
+      }
+    }).catch(error => dispatch(fetchSuggestionsFail(error)));
+  };
+}
+
+export function fetchSuggestionsRequest() {
+  return {
+    type: SUGGESTIONS_FETCH_REQUEST,
+    skipLoading: true,
+  };
+}
+
+export function fetchSuggestionsSuccess(suggestions) {
+  return {
+    type: SUGGESTIONS_FETCH_SUCCESS,
+    suggestions,
+    skipLoading: true,
+  };
+}
+
+export function fetchSuggestionsFail(error) {
+  return {
+    type: SUGGESTIONS_FETCH_FAIL,
+    error,
+    skipLoading: true,
+    skipAlert: true,
+  };
+}
+
+export const dismissSuggestion = accountId => (dispatch) => {
+  dispatch({
+    type: SUGGESTIONS_DISMISS,
+    id: accountId,
+  });
+
+  api().delete(`/api/v1/suggestions/${accountId}`).catch(() => {});
+};
diff --git a/app/javascript/mastodon/actions/suggestions.ts b/app/javascript/mastodon/actions/suggestions.ts
deleted file mode 100644
index 0eadfa6b47..0000000000
--- a/app/javascript/mastodon/actions/suggestions.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import {
-  apiGetSuggestions,
-  apiDeleteSuggestion,
-} from 'mastodon/api/suggestions';
-import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
-
-import { fetchRelationships } from './accounts';
-import { importFetchedAccounts } from './importer';
-
-export const fetchSuggestions = createDataLoadingThunk(
-  'suggestions/fetch',
-  () => apiGetSuggestions(20),
-  (data, { dispatch }) => {
-    dispatch(importFetchedAccounts(data.map((x) => x.account)));
-    dispatch(fetchRelationships(data.map((x) => x.account.id)));
-
-    return data;
-  },
-);
-
-export const dismissSuggestion = createDataLoadingThunk(
-  'suggestions/dismiss',
-  ({ accountId }: { accountId: string }) => apiDeleteSuggestion(accountId),
-);
diff --git a/app/javascript/mastodon/actions/tags.js b/app/javascript/mastodon/actions/tags.js
new file mode 100644
index 0000000000..d18d7e514f
--- /dev/null
+++ b/app/javascript/mastodon/actions/tags.js
@@ -0,0 +1,172 @@
+import api, { getLinks } from '../api';
+
+export const HASHTAG_FETCH_REQUEST = 'HASHTAG_FETCH_REQUEST';
+export const HASHTAG_FETCH_SUCCESS = 'HASHTAG_FETCH_SUCCESS';
+export const HASHTAG_FETCH_FAIL    = 'HASHTAG_FETCH_FAIL';
+
+export const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST';
+export const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS';
+export const FOLLOWED_HASHTAGS_FETCH_FAIL    = 'FOLLOWED_HASHTAGS_FETCH_FAIL';
+
+export const FOLLOWED_HASHTAGS_EXPAND_REQUEST = 'FOLLOWED_HASHTAGS_EXPAND_REQUEST';
+export const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS';
+export const FOLLOWED_HASHTAGS_EXPAND_FAIL    = 'FOLLOWED_HASHTAGS_EXPAND_FAIL';
+
+export const HASHTAG_FOLLOW_REQUEST = 'HASHTAG_FOLLOW_REQUEST';
+export const HASHTAG_FOLLOW_SUCCESS = 'HASHTAG_FOLLOW_SUCCESS';
+export const HASHTAG_FOLLOW_FAIL    = 'HASHTAG_FOLLOW_FAIL';
+
+export const HASHTAG_UNFOLLOW_REQUEST = 'HASHTAG_UNFOLLOW_REQUEST';
+export const HASHTAG_UNFOLLOW_SUCCESS = 'HASHTAG_UNFOLLOW_SUCCESS';
+export const HASHTAG_UNFOLLOW_FAIL    = 'HASHTAG_UNFOLLOW_FAIL';
+
+export const fetchHashtag = name => (dispatch) => {
+  dispatch(fetchHashtagRequest());
+
+  api().get(`/api/v1/tags/${name}`).then(({ data }) => {
+    dispatch(fetchHashtagSuccess(name, data));
+  }).catch(err => {
+    dispatch(fetchHashtagFail(err));
+  });
+};
+
+export const fetchHashtagRequest = () => ({
+  type: HASHTAG_FETCH_REQUEST,
+});
+
+export const fetchHashtagSuccess = (name, tag) => ({
+  type: HASHTAG_FETCH_SUCCESS,
+  name,
+  tag,
+});
+
+export const fetchHashtagFail = error => ({
+  type: HASHTAG_FETCH_FAIL,
+  error,
+});
+
+export const fetchFollowedHashtags = () => (dispatch) => {
+  dispatch(fetchFollowedHashtagsRequest());
+
+  api().get('/api/v1/followed_tags').then(response => {
+    const next = getLinks(response).refs.find(link => link.rel === 'next');
+    dispatch(fetchFollowedHashtagsSuccess(response.data, next ? next.uri : null));
+  }).catch(err => {
+    dispatch(fetchFollowedHashtagsFail(err));
+  });
+};
+
+export function fetchFollowedHashtagsRequest() {
+  return {
+    type: FOLLOWED_HASHTAGS_FETCH_REQUEST,
+  };
+}
+
+export function fetchFollowedHashtagsSuccess(followed_tags, next) {
+  return {
+    type: FOLLOWED_HASHTAGS_FETCH_SUCCESS,
+    followed_tags,
+    next,
+  };
+}
+
+export function fetchFollowedHashtagsFail(error) {
+  return {
+    type: FOLLOWED_HASHTAGS_FETCH_FAIL,
+    error,
+  };
+}
+
+export function expandFollowedHashtags() {
+  return (dispatch, getState) => {
+    const url = getState().getIn(['followed_tags', 'next']);
+
+    if (url === null) {
+      return;
+    }
+
+    dispatch(expandFollowedHashtagsRequest());
+
+    api().get(url).then(response => {
+      const next = getLinks(response).refs.find(link => link.rel === 'next');
+      dispatch(expandFollowedHashtagsSuccess(response.data, next ? next.uri : null));
+    }).catch(error => {
+      dispatch(expandFollowedHashtagsFail(error));
+    });
+  };
+}
+
+export function expandFollowedHashtagsRequest() {
+  return {
+    type: FOLLOWED_HASHTAGS_EXPAND_REQUEST,
+  };
+}
+
+export function expandFollowedHashtagsSuccess(followed_tags, next) {
+  return {
+    type: FOLLOWED_HASHTAGS_EXPAND_SUCCESS,
+    followed_tags,
+    next,
+  };
+}
+
+export function expandFollowedHashtagsFail(error) {
+  return {
+    type: FOLLOWED_HASHTAGS_EXPAND_FAIL,
+    error,
+  };
+}
+
+export const followHashtag = name => (dispatch) => {
+  dispatch(followHashtagRequest(name));
+
+  api().post(`/api/v1/tags/${name}/follow`).then(({ data }) => {
+    dispatch(followHashtagSuccess(name, data));
+  }).catch(err => {
+    dispatch(followHashtagFail(name, err));
+  });
+};
+
+export const followHashtagRequest = name => ({
+  type: HASHTAG_FOLLOW_REQUEST,
+  name,
+});
+
+export const followHashtagSuccess = (name, tag) => ({
+  type: HASHTAG_FOLLOW_SUCCESS,
+  name,
+  tag,
+});
+
+export const followHashtagFail = (name, error) => ({
+  type: HASHTAG_FOLLOW_FAIL,
+  name,
+  error,
+});
+
+export const unfollowHashtag = name => (dispatch) => {
+  dispatch(unfollowHashtagRequest(name));
+
+  api().post(`/api/v1/tags/${name}/unfollow`).then(({ data }) => {
+    dispatch(unfollowHashtagSuccess(name, data));
+  }).catch(err => {
+    dispatch(unfollowHashtagFail(name, err));
+  });
+};
+
+export const unfollowHashtagRequest = name => ({
+  type: HASHTAG_UNFOLLOW_REQUEST,
+  name,
+});
+
+export const unfollowHashtagSuccess = (name, tag) => ({
+  type: HASHTAG_UNFOLLOW_SUCCESS,
+  name,
+  tag,
+});
+
+export const unfollowHashtagFail = (name, error) => ({
+  type: HASHTAG_UNFOLLOW_FAIL,
+  name,
+  error,
+});
diff --git a/app/javascript/mastodon/actions/tags_typed.ts b/app/javascript/mastodon/actions/tags_typed.ts
deleted file mode 100644
index 6dca32fd84..0000000000
--- a/app/javascript/mastodon/actions/tags_typed.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { apiGetTag, apiFollowTag, apiUnfollowTag } from 'mastodon/api/tags';
-import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
-
-export const fetchHashtag = createDataLoadingThunk(
-  'tags/fetch',
-  ({ tagId }: { tagId: string }) => apiGetTag(tagId),
-);
-
-export const followHashtag = createDataLoadingThunk(
-  'tags/follow',
-  ({ tagId }: { tagId: string }) => apiFollowTag(tagId),
-);
-
-export const unfollowHashtag = createDataLoadingThunk(
-  'tags/unfollow',
-  ({ tagId }: { tagId: string }) => apiUnfollowTag(tagId),
-);
diff --git a/app/javascript/mastodon/api.ts b/app/javascript/mastodon/api.ts
index a41b058d2c..51cbe0b695 100644
--- a/app/javascript/mastodon/api.ts
+++ b/app/javascript/mastodon/api.ts
@@ -1,9 +1,4 @@
-import type {
-  AxiosError,
-  AxiosResponse,
-  Method,
-  RawAxiosRequestHeaders,
-} from 'axios';
+import type { AxiosResponse, Method, RawAxiosRequestHeaders } from 'axios';
 import axios from 'axios';
 import LinkHeader from 'http-link-header';
 
@@ -46,7 +41,7 @@ const authorizationTokenFromInitialState = (): RawAxiosRequestHeaders => {
 
 // eslint-disable-next-line import/no-default-export
 export default function api(withAuthorization = true) {
-  const instance = axios.create({
+  return axios.create({
     transitional: {
       clarifyTimeoutError: true,
     },
@@ -65,22 +60,6 @@ export default function api(withAuthorization = true) {
       },
     ],
   });
-
-  instance.interceptors.response.use(
-    (response: AxiosResponse) => {
-      if (response.headers.deprecation) {
-        console.warn(
-          `Deprecated request: ${response.config.method} ${response.config.url}`,
-        );
-      }
-      return response;
-    },
-    (error: AxiosError) => {
-      return Promise.reject(error);
-    },
-  );
-
-  return instance;
 }
 
 type RequestParamsOrData = Record<string, unknown>;
@@ -89,7 +68,6 @@ export async function apiRequest<ApiResponse = unknown>(
   method: Method,
   url: string,
   args: {
-    signal?: AbortSignal;
     params?: RequestParamsOrData;
     data?: RequestParamsOrData;
     timeout?: number;
diff --git a/app/javascript/mastodon/api/accounts.ts b/app/javascript/mastodon/api/accounts.ts
index 717010ba74..bd1757e827 100644
--- a/app/javascript/mastodon/api/accounts.ts
+++ b/app/javascript/mastodon/api/accounts.ts
@@ -5,16 +5,3 @@ export const apiSubmitAccountNote = (id: string, value: string) =>
   apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, {
     comment: value,
   });
-
-export const apiFollowAccount = (
-  id: string,
-  params?: {
-    reblogs: boolean;
-  },
-) =>
-  apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/follow`, {
-    ...params,
-  });
-
-export const apiUnfollowAccount = (id: string) =>
-  apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/unfollow`);
diff --git a/app/javascript/mastodon/api/antennas.ts b/app/javascript/mastodon/api/antennas.ts
deleted file mode 100644
index 61fd84185d..0000000000
--- a/app/javascript/mastodon/api/antennas.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import {
-  apiRequestPost,
-  apiRequestPut,
-  apiRequestGet,
-  apiRequestDelete,
-} from 'mastodon/api';
-import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
-import type { ApiAntennaJSON } from 'mastodon/api_types/antennas';
-
-export const apiCreate = (antenna: Partial<ApiAntennaJSON>) =>
-  apiRequestPost<ApiAntennaJSON>('v1/antennas', antenna);
-
-export const apiUpdate = (antenna: Partial<ApiAntennaJSON>) =>
-  apiRequestPut<ApiAntennaJSON>(`v1/antennas/${antenna.id}`, antenna);
-
-export const apiGetAccounts = (antennaId: string) =>
-  apiRequestGet<ApiAccountJSON[]>(`v1/antennas/${antennaId}/accounts`, {
-    limit: 0,
-  });
-
-export const apiGetExcludeAccounts = (antennaId: string) =>
-  apiRequestGet<ApiAccountJSON[]>(`v1/antennas/${antennaId}/exclude_accounts`, {
-    limit: 0,
-  });
-
-export const apiGetDomains = (antennaId: string) =>
-  apiRequestGet<{ domains: string[]; exclude_domains: string[] }>(
-    `v1/antennas/${antennaId}/domains`,
-    {
-      limit: 0,
-    },
-  );
-
-export const apiAddDomain = (antennaId: string, domain: string) =>
-  apiRequestPost(`v1/antennas/${antennaId}/domains`, {
-    domains: [domain],
-  });
-
-export const apiRemoveDomain = (antennaId: string, domain: string) =>
-  apiRequestDelete(`v1/antennas/${antennaId}/domains`, {
-    domains: [domain],
-  });
-
-export const apiAddExcludeDomain = (antennaId: string, domain: string) =>
-  apiRequestPost(`v1/antennas/${antennaId}/exclude_domains`, {
-    domains: [domain],
-  });
-
-export const apiRemoveExcludeDomain = (antennaId: string, domain: string) =>
-  apiRequestDelete(`v1/antennas/${antennaId}/exclude_domains`, {
-    domains: [domain],
-  });
-
-export const apiGetTags = (antennaId: string) =>
-  apiRequestGet<{ tags: string[]; exclude_tags: string[] }>(
-    `v1/antennas/${antennaId}/tags`,
-    {
-      limit: 0,
-    },
-  );
-
-export const apiAddTag = (antennaId: string, tag: string) =>
-  apiRequestPost(`v1/antennas/${antennaId}/tags`, {
-    tags: [tag],
-  });
-
-export const apiRemoveTag = (antennaId: string, tag: string) =>
-  apiRequestDelete(`v1/antennas/${antennaId}/tags`, {
-    tags: [tag],
-  });
-
-export const apiAddExcludeTag = (antennaId: string, tag: string) =>
-  apiRequestPost(`v1/antennas/${antennaId}/exclude_tags`, {
-    tags: [tag],
-  });
-
-export const apiRemoveExcludeTag = (antennaId: string, tag: string) =>
-  apiRequestDelete(`v1/antennas/${antennaId}/exclude_tags`, {
-    tags: [tag],
-  });
-
-export const apiGetKeywords = (antennaId: string) =>
-  apiRequestGet<{ keywords: string[]; exclude_keywords: string[] }>(
-    `v1/antennas/${antennaId}/keywords`,
-    {
-      limit: 0,
-    },
-  );
-
-export const apiAddKeyword = (antennaId: string, keyword: string) =>
-  apiRequestPost(`v1/antennas/${antennaId}/keywords`, {
-    keywords: [keyword],
-  });
-
-export const apiRemoveKeyword = (antennaId: string, keyword: string) =>
-  apiRequestDelete(`v1/antennas/${antennaId}/keywords`, {
-    keywords: [keyword],
-  });
-
-export const apiAddExcludeKeyword = (antennaId: string, keyword: string) =>
-  apiRequestPost(`v1/antennas/${antennaId}/exclude_keywords`, {
-    keywords: [keyword],
-  });
-
-export const apiRemoveExcludeKeyword = (antennaId: string, keyword: string) =>
-  apiRequestDelete(`v1/antennas/${antennaId}/exclude_keywords`, {
-    keywords: [keyword],
-  });
-
-export const apiGetAccountAntennas = (accountId: string) =>
-  apiRequestGet<ApiAntennaJSON[]>(`v1/accounts/${accountId}/antennas`);
-
-export const apiAddAccountToAntenna = (antennaId: string, accountId: string) =>
-  apiRequestPost(`v1/antennas/${antennaId}/accounts`, {
-    account_ids: [accountId],
-  });
-
-export const apiRemoveAccountFromAntenna = (
-  antennaId: string,
-  accountId: string,
-) =>
-  apiRequestDelete(`v1/antennas/${antennaId}/accounts`, {
-    account_ids: [accountId],
-  });
-
-export const apiGetExcludeAccountAntennas = (accountId: string) =>
-  apiRequestGet<ApiAntennaJSON[]>(`v1/accounts/${accountId}/exclude_antennas`);
-
-export const apiAddExcludeAccountToAntenna = (
-  antennaId: string,
-  accountId: string,
-) =>
-  apiRequestPost(`v1/antennas/${antennaId}/exclude_accounts`, {
-    account_ids: [accountId],
-  });
-
-export const apiRemoveExcludeAccountFromAntenna = (
-  antennaId: string,
-  accountId: string,
-) =>
-  apiRequestDelete(`v1/antennas/${antennaId}/exclude_accounts`, {
-    account_ids: [accountId],
-  });
diff --git a/app/javascript/mastodon/api/bookmark_categories.ts b/app/javascript/mastodon/api/bookmark_categories.ts
deleted file mode 100644
index d6d3394b3a..0000000000
--- a/app/javascript/mastodon/api/bookmark_categories.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import {
-  apiRequestPost,
-  apiRequestPut,
-  apiRequestGet,
-  apiRequestDelete,
-} from 'mastodon/api';
-import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
-import type { ApiBookmarkCategoryJSON } from 'mastodon/api_types/bookmark_categories';
-
-export const apiCreate = (bookmarkCategory: Partial<ApiBookmarkCategoryJSON>) =>
-  apiRequestPost<ApiBookmarkCategoryJSON>(
-    'v1/bookmark_categories',
-    bookmarkCategory,
-  );
-
-export const apiUpdate = (bookmarkCategory: Partial<ApiBookmarkCategoryJSON>) =>
-  apiRequestPut<ApiBookmarkCategoryJSON>(
-    `v1/bookmark_categories/${bookmarkCategory.id}`,
-    bookmarkCategory,
-  );
-
-export const apiGetStatuses = (bookmarkCategoryId: string) =>
-  apiRequestGet<ApiAccountJSON[]>(
-    `v1/bookmark_categories/${bookmarkCategoryId}/statuses`,
-    {
-      limit: 0,
-    },
-  );
-
-export const apiGetStatusBookmarkCategories = (accountId: string) =>
-  apiRequestGet<ApiBookmarkCategoryJSON[]>(
-    `v1/statuses/${accountId}/bookmark_categories`,
-  );
-
-export const apiAddStatusToBookmarkCategory = (
-  bookmarkCategoryId: string,
-  statusId: string,
-) =>
-  apiRequestPost(`v1/bookmark_categories/${bookmarkCategoryId}/statuses`, {
-    status_ids: [statusId],
-  });
-
-export const apiRemoveStatusFromBookmarkCategory = (
-  bookmarkCategoryId: string,
-  statusId: string,
-) =>
-  apiRequestDelete(`v1/bookmark_categories/${bookmarkCategoryId}/statuses`, {
-    status_ids: [statusId],
-  });
diff --git a/app/javascript/mastodon/api/circles.ts b/app/javascript/mastodon/api/circles.ts
deleted file mode 100644
index 04971e1e6b..0000000000
--- a/app/javascript/mastodon/api/circles.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import {
-  apiRequestPost,
-  apiRequestPut,
-  apiRequestGet,
-  apiRequestDelete,
-} from 'mastodon/api';
-import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
-import type { ApiCircleJSON } from 'mastodon/api_types/circles';
-
-export const apiCreate = (circle: Partial<ApiCircleJSON>) =>
-  apiRequestPost<ApiCircleJSON>('v1/circles', circle);
-
-export const apiUpdate = (circle: Partial<ApiCircleJSON>) =>
-  apiRequestPut<ApiCircleJSON>(`v1/circles/${circle.id}`, circle);
-
-export const apiGetAccounts = (circleId: string) =>
-  apiRequestGet<ApiAccountJSON[]>(`v1/circles/${circleId}/accounts`, {
-    limit: 0,
-  });
-
-export const apiGetAccountCircles = (accountId: string) =>
-  apiRequestGet<ApiCircleJSON[]>(`v1/accounts/${accountId}/circles`);
-
-export const apiAddAccountToCircle = (circleId: string, accountId: string) =>
-  apiRequestPost(`v1/circles/${circleId}/accounts`, {
-    account_ids: [accountId],
-  });
-
-export const apiRemoveAccountFromCircle = (
-  circleId: string,
-  accountId: string,
-) =>
-  apiRequestDelete(`v1/circles/${circleId}/accounts`, {
-    account_ids: [accountId],
-  });
diff --git a/app/javascript/mastodon/api/compose.ts b/app/javascript/mastodon/api/compose.ts
deleted file mode 100644
index 757e9961c9..0000000000
--- a/app/javascript/mastodon/api/compose.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { apiRequestPut } from 'mastodon/api';
-import type { ApiMediaAttachmentJSON } from 'mastodon/api_types/media_attachments';
-
-export const apiUpdateMedia = (
-  id: string,
-  params?: { description?: string; focus?: string },
-) => apiRequestPut<ApiMediaAttachmentJSON>(`v1/media/${id}`, params);
diff --git a/app/javascript/mastodon/api/domain_blocks.ts b/app/javascript/mastodon/api/domain_blocks.ts
deleted file mode 100644
index 4e153b0ee9..0000000000
--- a/app/javascript/mastodon/api/domain_blocks.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import api, { getLinks } from 'mastodon/api';
-
-export const apiGetDomainBlocks = async (url?: string) => {
-  const response = await api().request<string[]>({
-    method: 'GET',
-    url: url ?? '/api/v1/domain_blocks',
-  });
-
-  return {
-    domains: response.data,
-    links: getLinks(response),
-  };
-};
diff --git a/app/javascript/mastodon/api/instance.ts b/app/javascript/mastodon/api/instance.ts
deleted file mode 100644
index 764e8daab2..0000000000
--- a/app/javascript/mastodon/api/instance.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { apiRequestGet } from 'mastodon/api';
-import type {
-  ApiTermsOfServiceJSON,
-  ApiPrivacyPolicyJSON,
-} from 'mastodon/api_types/instance';
-
-export const apiGetTermsOfService = (version?: string) =>
-  apiRequestGet<ApiTermsOfServiceJSON>(
-    version
-      ? `v1/instance/terms_of_service/${version}`
-      : 'v1/instance/terms_of_service',
-  );
-
-export const apiGetPrivacyPolicy = () =>
-  apiRequestGet<ApiPrivacyPolicyJSON>('v1/instance/privacy_policy');
diff --git a/app/javascript/mastodon/api/lists.ts b/app/javascript/mastodon/api/lists.ts
deleted file mode 100644
index a5586eb6d4..0000000000
--- a/app/javascript/mastodon/api/lists.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import {
-  apiRequestPost,
-  apiRequestPut,
-  apiRequestGet,
-  apiRequestDelete,
-} from 'mastodon/api';
-import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
-import type { ApiListJSON } from 'mastodon/api_types/lists';
-
-export const apiCreate = (list: Partial<ApiListJSON>) =>
-  apiRequestPost<ApiListJSON>('v1/lists', list);
-
-export const apiUpdate = (list: Partial<ApiListJSON>) =>
-  apiRequestPut<ApiListJSON>(`v1/lists/${list.id}`, list);
-
-export const apiGetAccounts = (listId: string) =>
-  apiRequestGet<ApiAccountJSON[]>(`v1/lists/${listId}/accounts`, {
-    limit: 0,
-  });
-
-export const apiGetAccountLists = (accountId: string) =>
-  apiRequestGet<ApiListJSON[]>(`v1/accounts/${accountId}/lists`);
-
-export const apiAddAccountToList = (listId: string, accountId: string) =>
-  apiRequestPost(`v1/lists/${listId}/accounts`, {
-    account_ids: [accountId],
-  });
-
-export const apiRemoveAccountFromList = (listId: string, accountId: string) =>
-  apiRequestDelete(`v1/lists/${listId}/accounts`, {
-    account_ids: [accountId],
-  });
diff --git a/app/javascript/mastodon/api/polls.ts b/app/javascript/mastodon/api/polls.ts
deleted file mode 100644
index cb659986f5..0000000000
--- a/app/javascript/mastodon/api/polls.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { apiRequestGet, apiRequestPost } from 'mastodon/api';
-import type { ApiPollJSON } from 'mastodon/api_types/polls';
-
-export const apiGetPoll = (pollId: string) =>
-  apiRequestGet<ApiPollJSON>(`/v1/polls/${pollId}`);
-
-export const apiPollVote = (pollId: string, choices: string[]) =>
-  apiRequestPost<ApiPollJSON>(`/v1/polls/${pollId}/votes`, {
-    choices,
-  });
diff --git a/app/javascript/mastodon/api/search.ts b/app/javascript/mastodon/api/search.ts
deleted file mode 100644
index 79b0385fe8..0000000000
--- a/app/javascript/mastodon/api/search.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { apiRequestGet } from 'mastodon/api';
-import type {
-  ApiSearchType,
-  ApiSearchResultsJSON,
-} from 'mastodon/api_types/search';
-
-export const apiGetSearch = (params: {
-  q: string;
-  resolve?: boolean;
-  type?: ApiSearchType;
-  limit?: number;
-  offset?: number;
-}) =>
-  apiRequestGet<ApiSearchResultsJSON>('v2/search', {
-    ...params,
-  });
diff --git a/app/javascript/mastodon/api/suggestions.ts b/app/javascript/mastodon/api/suggestions.ts
deleted file mode 100644
index d4817698cc..0000000000
--- a/app/javascript/mastodon/api/suggestions.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { apiRequestGet, apiRequestDelete } from 'mastodon/api';
-import type { ApiSuggestionJSON } from 'mastodon/api_types/suggestions';
-
-export const apiGetSuggestions = (limit: number) =>
-  apiRequestGet<ApiSuggestionJSON[]>('v2/suggestions', { limit });
-
-export const apiDeleteSuggestion = (accountId: string) =>
-  apiRequestDelete(`v1/suggestions/${accountId}`);
diff --git a/app/javascript/mastodon/api/tags.ts b/app/javascript/mastodon/api/tags.ts
deleted file mode 100644
index 4b111def81..0000000000
--- a/app/javascript/mastodon/api/tags.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import api, { getLinks, apiRequestPost, apiRequestGet } from 'mastodon/api';
-import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
-
-export const apiGetTag = (tagId: string) =>
-  apiRequestGet<ApiHashtagJSON>(`v1/tags/${tagId}`);
-
-export const apiFollowTag = (tagId: string) =>
-  apiRequestPost<ApiHashtagJSON>(`v1/tags/${tagId}/follow`);
-
-export const apiUnfollowTag = (tagId: string) =>
-  apiRequestPost<ApiHashtagJSON>(`v1/tags/${tagId}/unfollow`);
-
-export const apiGetFollowedTags = async (url?: string) => {
-  const response = await api().request<ApiHashtagJSON[]>({
-    method: 'GET',
-    url: url ?? '/api/v1/followed_tags',
-  });
-
-  return {
-    tags: response.data,
-    links: getLinks(response),
-  };
-};
diff --git a/app/javascript/mastodon/api_types/accounts.ts b/app/javascript/mastodon/api_types/accounts.ts
index 9d7974eda0..80d575cad6 100644
--- a/app/javascript/mastodon/api_types/accounts.ts
+++ b/app/javascript/mastodon/api_types/accounts.ts
@@ -45,7 +45,7 @@ export interface BaseApiAccountJSON {
   avatar_static: string;
   bot: boolean;
   created_at: string;
-  discoverable?: boolean;
+  discoverable: boolean;
   indexable: boolean;
   display_name: string;
   emojis: ApiCustomEmojiJSON[];
diff --git a/app/javascript/mastodon/api_types/antennas.ts b/app/javascript/mastodon/api_types/antennas.ts
deleted file mode 100644
index a2a8a997ba..0000000000
--- a/app/javascript/mastodon/api_types/antennas.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-// See app/serializers/rest/antenna_serializer.rb
-
-import type { ApiListJSON } from './lists';
-
-export interface ApiAntennaJSON {
-  id: string;
-  title: string;
-  stl: boolean;
-  ltl: boolean;
-  insert_feeds: boolean;
-  with_media_only: boolean;
-  ignore_reblog: boolean;
-  favourite: boolean;
-  list: ApiListJSON | null;
-
-  list_id: string | undefined;
-}
diff --git a/app/javascript/mastodon/api_types/bookmark_categories.ts b/app/javascript/mastodon/api_types/bookmark_categories.ts
deleted file mode 100644
index 5407b6b125..0000000000
--- a/app/javascript/mastodon/api_types/bookmark_categories.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-// See app/serializers/rest/bookmark_category_serializer.rb
-
-export interface ApiBookmarkCategoryJSON {
-  id: string;
-  title: string;
-}
diff --git a/app/javascript/mastodon/api_types/circles.ts b/app/javascript/mastodon/api_types/circles.ts
deleted file mode 100644
index 9905d480b8..0000000000
--- a/app/javascript/mastodon/api_types/circles.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-// See app/serializers/rest/circle_serializer.rb
-
-export interface ApiCircleJSON {
-  id: string;
-  title: string;
-}
diff --git a/app/javascript/mastodon/api_types/dummy_types.ts b/app/javascript/mastodon/api_types/dummy_types.ts
new file mode 100644
index 0000000000..1f8c4db6f2
--- /dev/null
+++ b/app/javascript/mastodon/api_types/dummy_types.ts
@@ -0,0 +1,11 @@
+// A similar definition will eventually be added in the main house. These definitions will replace it.
+
+export interface ApiListJSON_KmyDummy {
+  id: string;
+  title: string;
+  exclusive: boolean;
+  notify: boolean;
+
+  // replies_policy
+  // antennas
+}
diff --git a/app/javascript/mastodon/api_types/instance.ts b/app/javascript/mastodon/api_types/instance.ts
deleted file mode 100644
index 3a29684b70..0000000000
--- a/app/javascript/mastodon/api_types/instance.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export interface ApiTermsOfServiceJSON {
-  effective_date: string;
-  effective: boolean;
-  succeeded_by: string | null;
-  content: string;
-}
-
-export interface ApiPrivacyPolicyJSON {
-  updated_at: string;
-  content: string;
-}
diff --git a/app/javascript/mastodon/api_types/lists.ts b/app/javascript/mastodon/api_types/lists.ts
deleted file mode 100644
index bc32b33883..0000000000
--- a/app/javascript/mastodon/api_types/lists.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-// See app/serializers/rest/list_serializer.rb
-
-import type { ApiAntennaJSON } from './antennas';
-
-export type RepliesPolicyType = 'list' | 'followed' | 'none';
-
-export interface ApiListJSON {
-  id: string;
-  title: string;
-  exclusive: boolean;
-  replies_policy: RepliesPolicyType;
-  notify: boolean;
-  favourite: boolean;
-  antennas?: ApiAntennaJSON[];
-}
diff --git a/app/javascript/mastodon/api_types/notifications.ts b/app/javascript/mastodon/api_types/notifications.ts
index 41daed25ad..89ce9ee497 100644
--- a/app/javascript/mastodon/api_types/notifications.ts
+++ b/app/javascript/mastodon/api_types/notifications.ts
@@ -3,7 +3,7 @@
 import type { AccountWarningAction } from 'mastodon/models/notification_group';
 
 import type { ApiAccountJSON } from './accounts';
-import type { ApiListJSON } from './lists';
+import type { ApiListJSON_KmyDummy } from './dummy_types';
 import type { ApiReportJSON } from './reports';
 import type { ApiStatusJSON } from './statuses';
 
@@ -24,7 +24,6 @@ export const allNotificationTypes = [
   'admin.report',
   'moderation_warning',
   'severed_relationships',
-  'annual_report',
 ];
 
 export type NotificationWithStatusType =
@@ -45,8 +44,7 @@ export type NotificationType =
   | 'moderation_warning'
   | 'severed_relationships'
   | 'admin.sign_up'
-  | 'admin.report'
-  | 'annual_report';
+  | 'admin.report';
 
 export interface NotifyEmojiReactionJSON {
   name: string;
@@ -71,7 +69,7 @@ export interface BaseNotificationJSON {
   group_key: string;
   account: ApiAccountJSON;
   emoji_reaction?: NotifyEmojiReactionJSON;
-  list?: ApiListJSON;
+  list?: ApiListJSON_KmyDummy;
 }
 
 export interface BaseNotificationGroupJSON {
@@ -84,7 +82,7 @@ export interface BaseNotificationGroupJSON {
   page_min_id?: string;
   page_max_id?: string;
   emoji_reaction_groups?: NotificationEmojiReactionGroupJSON[];
-  list?: ApiListJSON;
+  list?: ApiListJSON_KmyDummy;
 }
 
 interface NotificationGroupWithStatusJSON extends BaseNotificationGroupJSON {
@@ -160,15 +158,6 @@ interface AccountRelationshipSeveranceNotificationJSON
   event: ApiAccountRelationshipSeveranceEventJSON;
 }
 
-export interface ApiAnnualReportEventJSON {
-  year: string;
-}
-
-interface AnnualReportNotificationGroupJSON extends BaseNotificationGroupJSON {
-  type: 'annual_report';
-  annual_report: ApiAnnualReportEventJSON;
-}
-
 export type ApiNotificationJSON =
   | SimpleNotificationJSON
   | ReportNotificationJSON
@@ -181,8 +170,7 @@ export type ApiNotificationGroupJSON =
   | ReportNotificationGroupJSON
   | AccountRelationshipSeveranceNotificationGroupJSON
   | NotificationGroupWithStatusJSON
-  | ModerationWarningNotificationGroupJSON
-  | AnnualReportNotificationGroupJSON;
+  | ModerationWarningNotificationGroupJSON;
 
 export interface ApiNotificationGroupsResultJSON {
   accounts: ApiAccountJSON[];
diff --git a/app/javascript/mastodon/api_types/polls.ts b/app/javascript/mastodon/api_types/polls.ts
index 891a2faba7..8181f7b813 100644
--- a/app/javascript/mastodon/api_types/polls.ts
+++ b/app/javascript/mastodon/api_types/polls.ts
@@ -13,11 +13,11 @@ export interface ApiPollJSON {
   expired: boolean;
   multiple: boolean;
   votes_count: number;
-  voters_count: number | null;
+  voters_count: number;
 
   options: ApiPollOptionJSON[];
   emojis: ApiCustomEmojiJSON[];
 
-  voted?: boolean;
-  own_votes?: number[];
+  voted: boolean;
+  own_votes: number[];
 }
diff --git a/app/javascript/mastodon/api_types/search.ts b/app/javascript/mastodon/api_types/search.ts
deleted file mode 100644
index 795cbb2b41..0000000000
--- a/app/javascript/mastodon/api_types/search.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { ApiAccountJSON } from './accounts';
-import type { ApiStatusJSON } from './statuses';
-import type { ApiHashtagJSON } from './tags';
-
-export type ApiSearchType = 'accounts' | 'statuses' | 'hashtags';
-
-export interface ApiSearchResultsJSON {
-  accounts: ApiAccountJSON[];
-  statuses: ApiStatusJSON[];
-  hashtags: ApiHashtagJSON[];
-}
diff --git a/app/javascript/mastodon/api_types/suggestions.ts b/app/javascript/mastodon/api_types/suggestions.ts
deleted file mode 100644
index 7d91daf901..0000000000
--- a/app/javascript/mastodon/api_types/suggestions.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
-
-export type ApiSuggestionSourceJSON =
-  | 'featured'
-  | 'most_followed'
-  | 'most_interactions'
-  | 'similar_to_recently_followed'
-  | 'friends_of_friends';
-
-export interface ApiSuggestionJSON {
-  sources: [ApiSuggestionSourceJSON, ...ApiSuggestionSourceJSON[]];
-  account: ApiAccountJSON;
-}
diff --git a/app/javascript/mastodon/api_types/tags.ts b/app/javascript/mastodon/api_types/tags.ts
deleted file mode 100644
index 0c16c8bd28..0000000000
--- a/app/javascript/mastodon/api_types/tags.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-interface ApiHistoryJSON {
-  day: string;
-  accounts: string;
-  uses: string;
-}
-
-export interface ApiHashtagJSON {
-  id: string;
-  name: string;
-  url: string;
-  history: [ApiHistoryJSON, ...ApiHistoryJSON[]];
-  following?: boolean;
-}
diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx
new file mode 100644
index 0000000000..6204dcdf35
--- /dev/null
+++ b/app/javascript/mastodon/components/account.jsx
@@ -0,0 +1,194 @@
+import PropTypes from 'prop-types';
+import { useCallback } from 'react';
+
+import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
+
+import classNames from 'classnames';
+import { Link } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+
+import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
+import { EmptyAccount } from 'mastodon/components/empty_account';
+import { ShortNumber } from 'mastodon/components/short_number';
+import { VerifiedBadge } from 'mastodon/components/verified_badge';
+
+import DropdownMenuContainer from '../containers/dropdown_menu_container';
+import { me } from '../initial_state';
+
+import { Avatar } from './avatar';
+import { Button } from './button';
+import { FollowersCounter } from './counters';
+import { DisplayName } from './display_name';
+import { RelativeTimestamp } from './relative_timestamp';
+
+const messages = defineMessages({
+  follow: { id: 'account.follow', defaultMessage: 'Follow' },
+  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
+  cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
+  unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
+  unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
+  mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' },
+  unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' },
+  mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
+  block: { id: 'account.block_short', defaultMessage: 'Block' },
+  more: { id: 'status.more', defaultMessage: 'More' },
+});
+
+const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifications, hidden, hideButtons, minimal, defaultAction, children, withBio }) => {
+  const intl = useIntl();
+
+  const handleFollow = useCallback(() => {
+    onFollow(account);
+  }, [onFollow, account]);
+
+  const handleBlock = useCallback(() => {
+    onBlock(account);
+  }, [onBlock, account]);
+
+  const handleMute = useCallback(() => {
+    onMute(account);
+  }, [onMute, account]);
+
+  const handleMuteNotifications = useCallback(() => {
+    onMuteNotifications(account, true);
+  }, [onMuteNotifications, account]);
+
+  const handleUnmuteNotifications = useCallback(() => {
+    onMuteNotifications(account, false);
+  }, [onMuteNotifications, account]);
+
+  if (!account) {
+    return <EmptyAccount size={size} minimal={minimal} />;
+  }
+
+  if (hidden) {
+    return (
+      <>
+        {account.get('display_name')}
+        {account.get('username')}
+      </>
+    );
+  }
+
+  let buttons;
+
+  if (!hideButtons && account.get('id') !== me && account.get('relationship', null) !== null) {
+    const following = account.getIn(['relationship', 'following']);
+    const requested = account.getIn(['relationship', 'requested']);
+    const blocking  = account.getIn(['relationship', 'blocking']);
+    const muting  = account.getIn(['relationship', 'muting']);
+
+    if (requested) {
+      buttons = <Button text={intl.formatMessage(messages.cancel_follow_request)} onClick={handleFollow} />;
+    } else if (blocking) {
+      buttons = <Button text={intl.formatMessage(messages.unblock)} onClick={handleBlock} />;
+    } else if (muting) {
+      let menu;
+
+      if (account.getIn(['relationship', 'muting_notifications'])) {
+        menu = [{ text: intl.formatMessage(messages.unmute_notifications), action: handleUnmuteNotifications }];
+      } else {
+        menu = [{ text: intl.formatMessage(messages.mute_notifications), action: handleMuteNotifications }];
+      }
+
+      buttons = (
+        <>
+          <DropdownMenuContainer
+            items={menu}
+            icon='ellipsis-h'
+            iconComponent={MoreHorizIcon}
+            direction='right'
+            title={intl.formatMessage(messages.more)}
+          />
+
+          <Button text={intl.formatMessage(messages.unmute)} onClick={handleMute} />
+        </>
+      );
+    } else if (defaultAction === 'mute') {
+      buttons = <Button text={intl.formatMessage(messages.mute)} onClick={handleMute} />;
+    } else if (defaultAction === 'block') {
+      buttons = <Button text={intl.formatMessage(messages.block)} onClick={handleBlock} />;
+    } else if (!account.get('suspended') && !account.get('moved') || following) {
+      buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={handleFollow} />;
+    }
+  }
+
+  let muteTimeRemaining;
+
+  if (account.get('mute_expires_at')) {
+    muteTimeRemaining = <>· <RelativeTimestamp timestamp={account.get('mute_expires_at')} futureDate /></>;
+  }
+
+  let verification;
+
+  const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at'));
+
+  if (firstVerifiedField) {
+    verification = <VerifiedBadge link={firstVerifiedField.get('value')} />;
+  }
+
+  return (
+    <div className={classNames('account', { 'account--minimal': minimal })}>
+      <div className='account__wrapper'>
+        <Link key={account.get('id')} className='account__display-name' title={account.get('acct')} to={`/@${account.get('acct')}`} data-hover-card-account={account.get('id')}>
+          <div className='account__avatar-wrapper'>
+            <Avatar account={account} size={size} />
+          </div>
+
+          <div className='account__contents'>
+            <DisplayName account={account} />
+            {!minimal && (
+              <div className='account__details'>
+                <ShortNumber value={account.get('followers_count')} renderer={FollowersCounter}
+                  isHide={account.getIn(['other_settings', 'hide_followers_count'])} /> {verification} {muteTimeRemaining}
+              </div>
+            )}
+          </div>
+        </Link>
+
+        {!minimal && children && (
+          <div>
+            <div>
+              {children}
+            </div>
+            <div className='account__relationship'>
+              {buttons}
+            </div>
+          </div>
+        )}
+        {!minimal && !children && (
+          <div className='account__relationship'>
+            {buttons}
+          </div>
+        )}
+      </div>
+
+      {withBio && (account.get('note').length > 0 ? (
+        <div
+          className='account__note translate'
+          dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
+        />
+      ) : (
+        <div className='account__note account__note--missing'><FormattedMessage id='account.no_bio' defaultMessage='No description provided.' /></div>
+      ))}
+    </div>
+  );
+};
+
+Account.propTypes = {
+  size: PropTypes.number,
+  account: ImmutablePropTypes.record,
+  onFollow: PropTypes.func,
+  onBlock: PropTypes.func,
+  onMute: PropTypes.func,
+  onMuteNotifications: PropTypes.func,
+  hidden: PropTypes.bool,
+  hideButtons: PropTypes.bool,
+  minimal: PropTypes.bool,
+  defaultAction: PropTypes.string,
+  withBio: PropTypes.bool,
+  children: PropTypes.any,
+};
+
+export default Account;
diff --git a/app/javascript/mastodon/components/account.tsx b/app/javascript/mastodon/components/account.tsx
deleted file mode 100644
index c6c2204085..0000000000
--- a/app/javascript/mastodon/components/account.tsx
+++ /dev/null
@@ -1,299 +0,0 @@
-import type { ReactNode } from 'react';
-import type React from 'react';
-import { useCallback, useMemo } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import classNames from 'classnames';
-import { Link } from 'react-router-dom';
-
-import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
-import {
-  blockAccount,
-  unblockAccount,
-  muteAccount,
-  unmuteAccount,
-} from 'mastodon/actions/accounts';
-import { openModal } from 'mastodon/actions/modal';
-import { initMuteModal } from 'mastodon/actions/mutes';
-import { Avatar } from 'mastodon/components/avatar';
-import { Button } from 'mastodon/components/button';
-import { FollowersCounter } from 'mastodon/components/counters';
-import { DisplayName } from 'mastodon/components/display_name';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
-import { FollowButton } from 'mastodon/components/follow_button';
-import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
-import { ShortNumber } from 'mastodon/components/short_number';
-import { Skeleton } from 'mastodon/components/skeleton';
-import { VerifiedBadge } from 'mastodon/components/verified_badge';
-import type { MenuItem } from 'mastodon/models/dropdown_menu';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-const messages = defineMessages({
-  follow: { id: 'account.follow', defaultMessage: 'Follow' },
-  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
-  cancel_follow_request: {
-    id: 'account.cancel_follow_request',
-    defaultMessage: 'Withdraw follow request',
-  },
-  unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
-  unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
-  mute_notifications: {
-    id: 'account.mute_notifications_short',
-    defaultMessage: 'Mute notifications',
-  },
-  unmute_notifications: {
-    id: 'account.unmute_notifications_short',
-    defaultMessage: 'Unmute notifications',
-  },
-  mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
-  block: { id: 'account.block_short', defaultMessage: 'Block' },
-  more: { id: 'status.more', defaultMessage: 'More' },
-  addToLists: {
-    id: 'account.add_or_remove_from_list',
-    defaultMessage: 'Add or Remove from lists',
-  },
-  openOriginalPage: {
-    id: 'account.open_original_page',
-    defaultMessage: 'Open original page',
-  },
-});
-
-export const Account: React.FC<{
-  size?: number;
-  id: string;
-  hidden?: boolean;
-  minimal?: boolean;
-  defaultAction?: 'block' | 'mute';
-  withBio?: boolean;
-  hideButtons?: boolean;
-  children?: ReactNode;
-}> = ({
-  id,
-  size = 46,
-  hidden,
-  minimal,
-  defaultAction,
-  withBio,
-  hideButtons,
-  children,
-}) => {
-  const intl = useIntl();
-  const account = useAppSelector((state) => state.accounts.get(id));
-  const relationship = useAppSelector((state) => state.relationships.get(id));
-  const dispatch = useAppDispatch();
-  const accountUrl = account?.url;
-
-  const handleBlock = useCallback(() => {
-    if (relationship?.blocking) {
-      dispatch(unblockAccount(id));
-    } else {
-      dispatch(blockAccount(id));
-    }
-  }, [dispatch, id, relationship]);
-
-  const handleMute = useCallback(() => {
-    if (relationship?.muting) {
-      dispatch(unmuteAccount(id));
-    } else {
-      dispatch(initMuteModal(account));
-    }
-  }, [dispatch, id, account, relationship]);
-
-  const menu = useMemo(() => {
-    let arr: MenuItem[] = [];
-
-    if (defaultAction === 'mute') {
-      const handleMuteNotifications = () => {
-        dispatch(muteAccount(id, true));
-      };
-
-      const handleUnmuteNotifications = () => {
-        dispatch(muteAccount(id, false));
-      };
-
-      arr = [
-        {
-          text: intl.formatMessage(
-            relationship?.muting_notifications
-              ? messages.unmute_notifications
-              : messages.mute_notifications,
-          ),
-          action: relationship?.muting_notifications
-            ? handleUnmuteNotifications
-            : handleMuteNotifications,
-        },
-      ];
-    } else if (defaultAction !== 'block') {
-      const handleAddToLists = () => {
-        dispatch(
-          openModal({
-            modalType: 'LIST_ADDER',
-            modalProps: {
-              accountId: id,
-            },
-          }),
-        );
-      };
-
-      arr = [
-        {
-          text: intl.formatMessage(messages.addToLists),
-          action: handleAddToLists,
-        },
-      ];
-
-      if (accountUrl) {
-        arr.unshift(
-          {
-            text: intl.formatMessage(messages.openOriginalPage),
-            href: accountUrl,
-          },
-          null,
-        );
-      }
-    }
-
-    return arr;
-  }, [dispatch, intl, id, accountUrl, relationship, defaultAction]);
-
-  if (hidden) {
-    return (
-      <>
-        {account?.display_name}
-        {account?.username}
-      </>
-    );
-  }
-
-  let button: React.ReactNode, dropdown: React.ReactNode;
-
-  if (menu.length > 0) {
-    dropdown = (
-      <Dropdown
-        items={menu}
-        icon='ellipsis-h'
-        iconComponent={MoreHorizIcon}
-        title={intl.formatMessage(messages.more)}
-      />
-    );
-  }
-
-  if (defaultAction === 'block') {
-    button = (
-      <Button
-        text={intl.formatMessage(
-          relationship?.blocking ? messages.unblock : messages.block,
-        )}
-        onClick={handleBlock}
-      />
-    );
-  } else if (defaultAction === 'mute') {
-    button = (
-      <Button
-        text={intl.formatMessage(
-          relationship?.muting ? messages.unmute : messages.mute,
-        )}
-        onClick={handleMute}
-      />
-    );
-  } else {
-    button = <FollowButton accountId={id} />;
-  }
-
-  if (hideButtons) {
-    button = null;
-  }
-
-  let muteTimeRemaining: React.ReactNode;
-
-  if (account?.mute_expires_at) {
-    muteTimeRemaining = (
-      <>
-        · <RelativeTimestamp timestamp={account.mute_expires_at} futureDate />
-      </>
-    );
-  }
-
-  let verification: React.ReactNode;
-
-  const firstVerifiedField = account?.fields.find((item) => !!item.verified_at);
-
-  if (firstVerifiedField) {
-    verification = <VerifiedBadge link={firstVerifiedField.value} />;
-  }
-
-  return (
-    <div className={classNames('account', { 'account--minimal': minimal })}>
-      <div className='account__wrapper'>
-        <Link
-          className='account__display-name'
-          title={account?.acct}
-          to={`/@${account?.acct}`}
-          data-hover-card-account={id}
-        >
-          <div className='account__avatar-wrapper'>
-            {account ? (
-              <Avatar account={account} size={size} />
-            ) : (
-              <Skeleton width={size} height={size} />
-            )}
-          </div>
-
-          <div className='account__contents'>
-            <DisplayName account={account} />
-
-            {!minimal && (
-              <div className='account__details'>
-                {account ? (
-                  <>
-                    <ShortNumber
-                      value={account.followers_count}
-                      renderer={FollowersCounter}
-                      isHide={account.other_settings.hide_followers_count}
-                    />{' '}
-                    {verification} {muteTimeRemaining}
-                  </>
-                ) : (
-                  <Skeleton width='7ch' />
-                )}
-              </div>
-            )}
-          </div>
-        </Link>
-
-        {!minimal && children && (
-          <div>
-            <div>{children}</div>
-            <div className='account__relationship'>
-              {dropdown}
-              {button}
-            </div>
-          </div>
-        )}
-        {!minimal && !children && (
-          <div className='account__relationship'>
-            {dropdown}
-            {button}
-          </div>
-        )}
-      </div>
-
-      {account &&
-        withBio &&
-        (account.note.length > 0 ? (
-          <div
-            className='account__note translate'
-            dangerouslySetInnerHTML={{ __html: account.note_emojified }}
-          />
-        ) : (
-          <div className='account__note account__note--missing'>
-            <FormattedMessage
-              id='account.no_bio'
-              defaultMessage='No description provided.'
-            />
-          </div>
-        ))}
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/components/account_bio.tsx b/app/javascript/mastodon/components/account_bio.tsx
index 301ffcbb24..9d523c7402 100644
--- a/app/javascript/mastodon/components/account_bio.tsx
+++ b/app/javascript/mastodon/components/account_bio.tsx
@@ -1,4 +1,4 @@
-import { useLinks } from 'mastodon/hooks/useLinks';
+import { useLinks } from 'mastodon/../hooks/useLinks';
 
 export const AccountBio: React.FC<{
   note: string;
diff --git a/app/javascript/mastodon/components/account_fields.tsx b/app/javascript/mastodon/components/account_fields.tsx
index 4ce55f7896..e297f99e3a 100644
--- a/app/javascript/mastodon/components/account_fields.tsx
+++ b/app/javascript/mastodon/components/account_fields.tsx
@@ -1,8 +1,8 @@
 import classNames from 'classnames';
 
 import CheckIcon from '@/material-icons/400-24px/check.svg?react';
+import { useLinks } from 'mastodon/../hooks/useLinks';
 import { Icon } from 'mastodon/components/icon';
-import { useLinks } from 'mastodon/hooks/useLinks';
 import type { Account } from 'mastodon/models/account';
 
 export const AccountFields: React.FC<{
diff --git a/app/javascript/mastodon/components/alerts_controller.tsx b/app/javascript/mastodon/components/alerts_controller.tsx
deleted file mode 100644
index 26749fa103..0000000000
--- a/app/javascript/mastodon/components/alerts_controller.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import { useState, useEffect } from 'react';
-
-import { useIntl } from 'react-intl';
-import type { IntlShape } from 'react-intl';
-
-import classNames from 'classnames';
-
-import { dismissAlert } from 'mastodon/actions/alerts';
-import type {
-  Alert,
-  TranslatableString,
-  TranslatableValues,
-} from 'mastodon/models/alert';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-const formatIfNeeded = (
-  intl: IntlShape,
-  message: TranslatableString,
-  values?: TranslatableValues,
-) => {
-  if (typeof message === 'object') {
-    return intl.formatMessage(message, values);
-  }
-
-  return message;
-};
-
-const Alert: React.FC<{
-  alert: Alert;
-  dismissAfter: number;
-}> = ({
-  alert: { key, title, message, values, action, onClick },
-  dismissAfter,
-}) => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-  const [active, setActive] = useState(false);
-
-  useEffect(() => {
-    const setActiveTimeout = setTimeout(() => {
-      setActive(true);
-    }, 1);
-
-    return () => {
-      clearTimeout(setActiveTimeout);
-    };
-  }, []);
-
-  useEffect(() => {
-    const dismissTimeout = setTimeout(() => {
-      setActive(false);
-
-      // Allow CSS transition to finish before removing from the DOM
-      setTimeout(() => {
-        dispatch(dismissAlert({ key }));
-      }, 500);
-    }, dismissAfter);
-
-    return () => {
-      clearTimeout(dismissTimeout);
-    };
-  }, [dispatch, setActive, key, dismissAfter]);
-
-  return (
-    <div
-      className={classNames('notification-bar', {
-        'notification-bar-active': active,
-      })}
-    >
-      <div className='notification-bar-wrapper'>
-        {title && (
-          <span className='notification-bar-title'>
-            {formatIfNeeded(intl, title, values)}
-          </span>
-        )}
-
-        <span className='notification-bar-message'>
-          {formatIfNeeded(intl, message, values)}
-        </span>
-
-        {action && (
-          <button className='notification-bar-action' onClick={onClick}>
-            {formatIfNeeded(intl, action, values)}
-          </button>
-        )}
-      </div>
-    </div>
-  );
-};
-
-export const AlertsController: React.FC = () => {
-  const alerts = useAppSelector((state) => state.alerts);
-
-  if (alerts.length === 0) {
-    return null;
-  }
-
-  return (
-    <div className='notification-list'>
-      {alerts.map((alert, idx) => (
-        <Alert key={alert.key} alert={alert} dismissAfter={5000 + idx * 1000} />
-      ))}
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/components/alt_text_badge.tsx b/app/javascript/mastodon/components/alt_text_badge.tsx
index 701cfbe8b4..99bec1ee51 100644
--- a/app/javascript/mastodon/components/alt_text_badge.tsx
+++ b/app/javascript/mastodon/components/alt_text_badge.tsx
@@ -1,4 +1,4 @@
-import { useState, useCallback, useRef, useId } from 'react';
+import { useState, useCallback, useRef } from 'react';
 
 import { FormattedMessage } from 'react-intl';
 
@@ -8,15 +8,12 @@ import type {
   UsePopperOptions,
 } from 'react-overlays/esm/usePopper';
 
-import { useSelectableClick } from 'mastodon/hooks/useSelectableClick';
-
 const offset = [0, 4] as OffsetValue;
 const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
 
 export const AltTextBadge: React.FC<{
   description: string;
 }> = ({ description }) => {
-  const accessibilityId = useId();
   const anchorRef = useRef<HTMLButtonElement>(null);
   const [open, setOpen] = useState(false);
 
@@ -28,16 +25,12 @@ export const AltTextBadge: React.FC<{
     setOpen(false);
   }, [setOpen]);
 
-  const [handleMouseDown, handleMouseUp] = useSelectableClick(handleClose);
-
   return (
     <>
       <button
         ref={anchorRef}
         className='media-gallery__alt__label'
         onClick={handleClick}
-        aria-expanded={open}
-        aria-controls={accessibilityId}
       >
         ALT
       </button>
@@ -54,12 +47,9 @@ export const AltTextBadge: React.FC<{
       >
         {({ props }) => (
           <div {...props} className='hover-card-controller'>
-            <div // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
+            <div
               className='media-gallery__alt__popover dropdown-animation'
-              role='region'
-              id={accessibilityId}
-              onMouseDown={handleMouseDown}
-              onMouseUp={handleMouseUp}
+              role='tooltip'
             >
               <h4>
                 <FormattedMessage
diff --git a/app/javascript/mastodon/components/animated_number.tsx b/app/javascript/mastodon/components/animated_number.tsx
index db422f47ce..6c1e0aaec1 100644
--- a/app/javascript/mastodon/components/animated_number.tsx
+++ b/app/javascript/mastodon/components/animated_number.tsx
@@ -1,6 +1,6 @@
-import { useEffect, useState } from 'react';
+import { useCallback, useState } from 'react';
 
-import { animated, useSpring, config } from '@react-spring/web';
+import { TransitionMotion, spring } from 'react-motion';
 
 import { reduceMotion } from '../initial_state';
 
@@ -11,49 +11,53 @@ interface Props {
 }
 export const AnimatedNumber: React.FC<Props> = ({ value }) => {
   const [previousValue, setPreviousValue] = useState(value);
-  const direction = value > previousValue ? -1 : 1;
+  const [direction, setDirection] = useState<1 | -1>(1);
 
-  const [styles, api] = useSpring(
-    () => ({
-      from: { transform: `translateY(${100 * direction}%)` },
-      to: { transform: 'translateY(0%)' },
-      onRest() {
-        setPreviousValue(value);
-      },
-      config: { ...config.gentle, duration: 200 },
-      immediate: true, // This ensures that the animation is not played when the component is first rendered
-    }),
-    [value, previousValue],
+  if (previousValue !== value) {
+    setPreviousValue(value);
+    setDirection(value > previousValue ? 1 : -1);
+  }
+
+  const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]);
+  const willLeave = useCallback(
+    () => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }),
+    [direction],
   );
 
-  // When the value changes, start the animation
-  useEffect(() => {
-    if (value !== previousValue) {
-      void api.start({ reset: true });
-    }
-  }, [api, previousValue, value]);
-
   if (reduceMotion) {
     return <ShortNumber value={value} />;
   }
 
+  const styles = [
+    {
+      key: `${value}`,
+      data: value,
+      style: { y: spring(0, { damping: 35, stiffness: 400 }) },
+    },
+  ];
+
   return (
-    <span className='animated-number'>
-      <animated.span style={styles}>
-        <ShortNumber value={value} />
-      </animated.span>
-      {value !== previousValue && (
-        <animated.span
-          style={{
-            ...styles,
-            position: 'absolute',
-            top: `${-100 * direction}%`, // Adds extra space on top of translateY
-          }}
-          role='presentation'
-        >
-          <ShortNumber value={previousValue} />
-        </animated.span>
+    <TransitionMotion
+      styles={styles}
+      willEnter={willEnter}
+      willLeave={willLeave}
+    >
+      {(items) => (
+        <span className='animated-number'>
+          {items.map(({ key, data, style }) => (
+            <span
+              key={key}
+              style={{
+                position:
+                  direction * (style.y ?? 0) > 0 ? 'absolute' : 'static',
+                transform: `translateY(${(style.y ?? 0) * 100}%)`,
+              }}
+            >
+              <ShortNumber value={data as number} />
+            </span>
+          ))}
+        </span>
       )}
-    </span>
+    </TransitionMotion>
   );
 };
diff --git a/app/javascript/mastodon/components/attachment_list.jsx b/app/javascript/mastodon/components/attachment_list.jsx
index f97e22f2d4..c5ac046751 100644
--- a/app/javascript/mastodon/components/attachment_list.jsx
+++ b/app/javascript/mastodon/components/attachment_list.jsx
@@ -36,7 +36,7 @@ export default class AttachmentList extends ImmutablePureComponent {
 
             return (
               <li key={attachment.get('id')}>
-                <a href={displayUrl} target='_blank' rel='noopener'>
+                <a href={displayUrl} target='_blank' rel='noopener noreferrer'>
                   {compact && <Icon id='link' icon={LinkIcon} />}
                   {compact && ' ' }
                   {displayUrl ? filename(displayUrl) : <FormattedMessage id='attachments_list.unprocessed' defaultMessage='(unprocessed)' />}
diff --git a/app/javascript/mastodon/components/avatar.tsx b/app/javascript/mastodon/components/avatar.tsx
index a2dc0b782e..f61d9676de 100644
--- a/app/javascript/mastodon/components/avatar.tsx
+++ b/app/javascript/mastodon/components/avatar.tsx
@@ -2,7 +2,7 @@ import { useState, useCallback } from 'react';
 
 import classNames from 'classnames';
 
-import { useHovering } from 'mastodon/hooks/useHovering';
+import { useHovering } from 'mastodon/../hooks/useHovering';
 import { autoPlayGif } from 'mastodon/initial_state';
 import type { Account } from 'mastodon/models/account';
 
diff --git a/app/javascript/mastodon/components/avatar_overlay.tsx b/app/javascript/mastodon/components/avatar_overlay.tsx
index 0bd33fea69..f98cfcc38b 100644
--- a/app/javascript/mastodon/components/avatar_overlay.tsx
+++ b/app/javascript/mastodon/components/avatar_overlay.tsx
@@ -1,7 +1,8 @@
-import { useHovering } from 'mastodon/hooks/useHovering';
-import { autoPlayGif } from 'mastodon/initial_state';
 import type { Account } from 'mastodon/models/account';
 
+import { useHovering } from '../../hooks/useHovering';
+import { autoPlayGif } from '../initial_state';
+
 interface Props {
   account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
   friend: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
diff --git a/app/javascript/mastodon/components/button.tsx b/app/javascript/mastodon/components/button.tsx
index a527468f65..3e720f7cee 100644
--- a/app/javascript/mastodon/components/button.tsx
+++ b/app/javascript/mastodon/components/button.tsx
@@ -1,4 +1,4 @@
-import type { PropsWithChildren, JSX } from 'react';
+import type { PropsWithChildren } from 'react';
 import { useCallback } from 'react';
 
 import classNames from 'classnames';
@@ -7,7 +7,6 @@ interface BaseProps
   extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
   block?: boolean;
   secondary?: boolean;
-  compact?: boolean;
   dangerous?: boolean;
 }
 
@@ -28,7 +27,6 @@ export const Button: React.FC<Props> = ({
   disabled,
   block,
   secondary,
-  compact,
   dangerous,
   className,
   title,
@@ -49,7 +47,6 @@ export const Button: React.FC<Props> = ({
     <button
       className={classNames('button', className, {
         'button-secondary': secondary,
-        'button--compact': compact,
         'button--block': block,
         'button--dangerous': dangerous,
       })}
diff --git a/app/javascript/mastodon/components/check_box.tsx b/app/javascript/mastodon/components/check_box.tsx
index 73fdb2f97b..9bd137abf5 100644
--- a/app/javascript/mastodon/components/check_box.tsx
+++ b/app/javascript/mastodon/components/check_box.tsx
@@ -7,11 +7,11 @@ import { Icon } from './icon';
 
 interface Props {
   value: string;
-  checked?: boolean;
-  indeterminate?: boolean;
-  name?: string;
-  onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
-  label?: React.ReactNode;
+  checked: boolean;
+  indeterminate: boolean;
+  name: string;
+  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
+  label: React.ReactNode;
 }
 
 export const CheckBox: React.FC<Props> = ({
@@ -30,7 +30,6 @@ export const CheckBox: React.FC<Props> = ({
         value={value}
         checked={checked}
         onChange={onChange}
-        readOnly={!onChange}
       />
 
       <span
@@ -43,7 +42,7 @@ export const CheckBox: React.FC<Props> = ({
         )}
       </span>
 
-      {label && <span>{label}</span>}
+      <span>{label}</span>
     </label>
   );
 };
diff --git a/app/javascript/mastodon/components/column.jsx b/app/javascript/mastodon/components/column.jsx
new file mode 100644
index 0000000000..abc87a57e5
--- /dev/null
+++ b/app/javascript/mastodon/components/column.jsx
@@ -0,0 +1,72 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { supportsPassiveEvents } from 'detect-passive-events';
+
+import { scrollTop } from '../scroll';
+
+const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
+
+export default class Column extends PureComponent {
+
+  static propTypes = {
+    children: PropTypes.node,
+    label: PropTypes.string,
+    bindToDocument: PropTypes.bool,
+  };
+
+  scrollTop () {
+    let scrollable = null;
+
+    if (this.props.bindToDocument) {
+      scrollable = document.scrollingElement;
+    } else {
+      scrollable = this.node.querySelector('.scrollable');
+    }
+
+    if (!scrollable) {
+      return;
+    }
+
+    this._interruptScrollAnimation = scrollTop(scrollable);
+  }
+
+  handleWheel = () => {
+    if (typeof this._interruptScrollAnimation !== 'function') {
+      return;
+    }
+
+    this._interruptScrollAnimation();
+  };
+
+  setRef = c => {
+    this.node = c;
+  };
+
+  componentDidMount () {
+    if (this.props.bindToDocument) {
+      document.addEventListener('wheel', this.handleWheel, listenerOptions);
+    } else {
+      this.node.addEventListener('wheel', this.handleWheel, listenerOptions);
+    }
+  }
+
+  componentWillUnmount () {
+    if (this.props.bindToDocument) {
+      document.removeEventListener('wheel', this.handleWheel, listenerOptions);
+    } else {
+      this.node.removeEventListener('wheel', this.handleWheel, listenerOptions);
+    }
+  }
+
+  render () {
+    const { label, children } = this.props;
+
+    return (
+      <div role='region' aria-label={label} className='column' ref={this.setRef}>
+        {children}
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/components/column.tsx b/app/javascript/mastodon/components/column.tsx
deleted file mode 100644
index 01c75d85c0..0000000000
--- a/app/javascript/mastodon/components/column.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { forwardRef, useRef, useImperativeHandle } from 'react';
-import type { Ref } from 'react';
-
-import { scrollTop } from 'mastodon/scroll';
-
-export interface ColumnRef {
-  scrollTop: () => void;
-  node: HTMLDivElement | null;
-}
-
-interface ColumnProps {
-  children?: React.ReactNode;
-  label?: string;
-  bindToDocument?: boolean;
-}
-
-export const Column = forwardRef<ColumnRef, ColumnProps>(
-  ({ children, label, bindToDocument }, ref: Ref<ColumnRef>) => {
-    const nodeRef = useRef<HTMLDivElement>(null);
-
-    useImperativeHandle(ref, () => ({
-      node: nodeRef.current,
-
-      scrollTop() {
-        let scrollable = null;
-
-        if (bindToDocument) {
-          scrollable = document.scrollingElement;
-        } else {
-          scrollable = nodeRef.current?.querySelector('.scrollable');
-        }
-
-        if (!scrollable) {
-          return;
-        }
-
-        scrollTop(scrollable);
-      },
-    }));
-
-    return (
-      <div role='region' aria-label={label} className='column' ref={nodeRef}>
-        {children}
-      </div>
-    );
-  },
-);
-
-Column.displayName = 'Column';
-
-// eslint-disable-next-line import/no-default-export
-export default Column;
diff --git a/app/javascript/mastodon/components/column_back_button.tsx b/app/javascript/mastodon/components/column_back_button.tsx
index aad355d710..af38c1e110 100644
--- a/app/javascript/mastodon/components/column_back_button.tsx
+++ b/app/javascript/mastodon/components/column_back_button.tsx
@@ -24,7 +24,7 @@ function useHandleClick(onClick?: OnClickCallback) {
   }, [history, onClick]);
 }
 
-export const ColumnBackButton: React.FC<{ onClick?: OnClickCallback }> = ({
+export const ColumnBackButton: React.FC<{ onClick: OnClickCallback }> = ({
   onClick,
 }) => {
   const handleClick = useHandleClick(onClick);
diff --git a/app/javascript/mastodon/components/column_search_header.tsx b/app/javascript/mastodon/components/column_search_header.tsx
deleted file mode 100644
index 90b6c4d89f..0000000000
--- a/app/javascript/mastodon/components/column_search_header.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { useCallback, useState, useEffect, useRef } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-export const ColumnSearchHeader: React.FC<{
-  onBack: () => void;
-  onSubmit: (value: string) => void;
-  onActivate: () => void;
-  placeholder: string;
-  active: boolean;
-}> = ({ onBack, onActivate, onSubmit, placeholder, active }) => {
-  const inputRef = useRef<HTMLInputElement>(null);
-  const [value, setValue] = useState('');
-
-  useEffect(() => {
-    if (!active) {
-      setValue('');
-    }
-  }, [active]);
-
-  const handleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setValue(value);
-      onSubmit(value);
-    },
-    [setValue, onSubmit],
-  );
-
-  const handleKeyUp = useCallback(
-    (e: React.KeyboardEvent<HTMLInputElement>) => {
-      if (e.key === 'Escape') {
-        e.preventDefault();
-        onBack();
-        inputRef.current?.blur();
-      }
-    },
-    [onBack],
-  );
-
-  const handleFocus = useCallback(() => {
-    onActivate();
-  }, [onActivate]);
-
-  const handleSubmit = useCallback(() => {
-    onSubmit(value);
-  }, [onSubmit, value]);
-
-  return (
-    <form className='column-search-header' onSubmit={handleSubmit}>
-      <input
-        ref={inputRef}
-        type='search'
-        value={value}
-        onChange={handleChange}
-        onKeyUp={handleKeyUp}
-        placeholder={placeholder}
-        onFocus={handleFocus}
-      />
-
-      {active && (
-        <button type='button' className='link-button' onClick={onBack}>
-          <FormattedMessage id='column_search.cancel' defaultMessage='Cancel' />
-        </button>
-      )}
-    </form>
-  );
-};
diff --git a/app/javascript/mastodon/components/copy_icon_button.tsx b/app/javascript/mastodon/components/copy_icon_button.jsx
similarity index 62%
rename from app/javascript/mastodon/components/copy_icon_button.tsx
rename to app/javascript/mastodon/components/copy_icon_button.jsx
index 29f5f34430..0c3c6c290b 100644
--- a/app/javascript/mastodon/components/copy_icon_button.tsx
+++ b/app/javascript/mastodon/components/copy_icon_button.jsx
@@ -1,36 +1,29 @@
+import PropTypes from 'prop-types';
 import { useState, useCallback } from 'react';
 
 import { defineMessages } from 'react-intl';
 
 import classNames from 'classnames';
 
+import { useDispatch } from 'react-redux';
+
 import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
 import { showAlert } from 'mastodon/actions/alerts';
 import { IconButton } from 'mastodon/components/icon_button';
-import { useAppDispatch } from 'mastodon/store';
 
 const messages = defineMessages({
-  copied: {
-    id: 'copy_icon_button.copied',
-    defaultMessage: 'Copied to clipboard',
-  },
+  copied: { id: 'copy_icon_button.copied', defaultMessage: 'Copied to clipboard' },
 });
 
-export const CopyIconButton: React.FC<{
-  title: string;
-  value: string;
-  className: string;
-}> = ({ title, value, className }) => {
+export const CopyIconButton = ({ title, value, className }) => {
   const [copied, setCopied] = useState(false);
-  const dispatch = useAppDispatch();
+  const dispatch = useDispatch();
 
   const handleClick = useCallback(() => {
-    void navigator.clipboard.writeText(value);
+    navigator.clipboard.writeText(value);
     setCopied(true);
     dispatch(showAlert({ message: messages.copied }));
-    setTimeout(() => {
-      setCopied(false);
-    }, 700);
+    setTimeout(() => setCopied(false), 700);
   }, [setCopied, value, dispatch]);
 
   return (
@@ -38,8 +31,13 @@ export const CopyIconButton: React.FC<{
       className={classNames(className, copied ? 'copied' : 'copyable')}
       title={title}
       onClick={handleClick}
-      icon=''
       iconComponent={ContentCopyIcon}
     />
   );
 };
+
+CopyIconButton.propTypes = {
+  title: PropTypes.string,
+  value: PropTypes.string,
+  className: PropTypes.string,
+};
diff --git a/app/javascript/mastodon/components/copy_paste_text.tsx b/app/javascript/mastodon/components/copy_paste_text.tsx
index e6eba765ab..f888acd0f7 100644
--- a/app/javascript/mastodon/components/copy_paste_text.tsx
+++ b/app/javascript/mastodon/components/copy_paste_text.tsx
@@ -5,8 +5,8 @@ import { FormattedMessage } from 'react-intl';
 import classNames from 'classnames';
 
 import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
+import { useTimeout } from 'mastodon/../hooks/useTimeout';
 import { Icon } from 'mastodon/components/icon';
-import { useTimeout } from 'mastodon/hooks/useTimeout';
 
 export const CopyPasteText: React.FC<{ value: string }> = ({ value }) => {
   const inputRef = useRef<HTMLTextAreaElement>(null);
diff --git a/app/javascript/mastodon/components/counters.tsx b/app/javascript/mastodon/components/counters.tsx
index 151b25a3f7..35b0ad8d60 100644
--- a/app/javascript/mastodon/components/counters.tsx
+++ b/app/javascript/mastodon/components/counters.tsx
@@ -1,4 +1,4 @@
-import type React from 'react';
+import React from 'react';
 
 import { FormattedMessage } from 'react-intl';
 
diff --git a/app/javascript/mastodon/components/domain.tsx b/app/javascript/mastodon/components/domain.tsx
index 0ccffac482..aa64f0f8c3 100644
--- a/app/javascript/mastodon/components/domain.tsx
+++ b/app/javascript/mastodon/components/domain.tsx
@@ -1,15 +1,24 @@
 import { useCallback } from 'react';
 
-import { FormattedMessage } from 'react-intl';
+import { defineMessages, useIntl } from 'react-intl';
 
+import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react';
 import { unblockDomain } from 'mastodon/actions/domain_blocks';
 import { useAppDispatch } from 'mastodon/store';
 
-import { Button } from './button';
+import { IconButton } from './icon_button';
+
+const messages = defineMessages({
+  unblockDomain: {
+    id: 'account.unblock_domain',
+    defaultMessage: 'Unblock domain {domain}',
+  },
+});
 
 export const Domain: React.FC<{
   domain: string;
 }> = ({ domain }) => {
+  const intl = useIntl();
   const dispatch = useAppDispatch();
 
   const handleDomainUnblock = useCallback(() => {
@@ -18,17 +27,20 @@ export const Domain: React.FC<{
 
   return (
     <div className='domain'>
-      <div className='domain__domain-name'>
-        <strong>{domain}</strong>
-      </div>
+      <div className='domain__wrapper'>
+        <span className='domain__domain-name'>
+          <strong>{domain}</strong>
+        </span>
 
-      <div className='domain__buttons'>
-        <Button onClick={handleDomainUnblock}>
-          <FormattedMessage
-            id='account.unblock_domain_short'
-            defaultMessage='Unblock'
+        <div className='domain__buttons'>
+          <IconButton
+            active
+            icon='unlock'
+            iconComponent={LockOpenIcon}
+            title={intl.formatMessage(messages.unblockDomain, { domain })}
+            onClick={handleDomainUnblock}
           />
-        </Button>
+        </div>
       </div>
     </div>
   );
diff --git a/app/javascript/mastodon/components/dropdown_menu.jsx b/app/javascript/mastodon/components/dropdown_menu.jsx
new file mode 100644
index 0000000000..4d1a47a2f5
--- /dev/null
+++ b/app/javascript/mastodon/components/dropdown_menu.jsx
@@ -0,0 +1,345 @@
+import PropTypes from 'prop-types';
+import { PureComponent, cloneElement, Children } from 'react';
+
+import classNames from 'classnames';
+import { withRouter } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+
+import { supportsPassiveEvents } from 'detect-passive-events';
+import Overlay from 'react-overlays/Overlay';
+
+import { CircularProgress } from 'mastodon/components/circular_progress';
+import { WithRouterPropTypes } from 'mastodon/utils/react_router';
+
+import { IconButton } from './icon_button';
+
+const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
+let id = 0;
+
+class DropdownMenu extends PureComponent {
+
+  static propTypes = {
+    items: PropTypes.array.isRequired,
+    loading: PropTypes.bool,
+    scrollable: PropTypes.bool,
+    onClose: PropTypes.func.isRequired,
+    style: PropTypes.object,
+    openedViaKeyboard: PropTypes.bool,
+    renderItem: PropTypes.func,
+    renderHeader: PropTypes.func,
+    onItemClick: PropTypes.func.isRequired,
+  };
+
+  static defaultProps = {
+    style: {},
+  };
+
+  handleDocumentClick = e => {
+    if (this.node && !this.node.contains(e.target)) {
+      this.props.onClose();
+      e.stopPropagation();
+      e.preventDefault();
+    }
+  };
+
+  componentDidMount () {
+    document.addEventListener('click', this.handleDocumentClick, { capture: true });
+    document.addEventListener('keydown', this.handleKeyDown, { capture: true });
+    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
+
+    if (this.focusedItem && this.props.openedViaKeyboard) {
+      this.focusedItem.focus({ preventScroll: true });
+    }
+  }
+
+  componentWillUnmount () {
+    document.removeEventListener('click', this.handleDocumentClick, { capture: true });
+    document.removeEventListener('keydown', this.handleKeyDown, { capture: true });
+    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
+  }
+
+  setRef = c => {
+    this.node = c;
+  };
+
+  setFocusRef = c => {
+    this.focusedItem = c;
+  };
+
+  handleKeyDown = e => {
+    const items = Array.from(this.node.querySelectorAll('a, button'));
+    const index = items.indexOf(document.activeElement);
+    let element = null;
+
+    switch(e.key) {
+    case 'ArrowDown':
+      element = items[index+1] || items[0];
+      break;
+    case 'ArrowUp':
+      element = items[index-1] || items[items.length-1];
+      break;
+    case 'Tab':
+      if (e.shiftKey) {
+        element = items[index-1] || items[items.length-1];
+      } else {
+        element = items[index+1] || items[0];
+      }
+      break;
+    case 'Home':
+      element = items[0];
+      break;
+    case 'End':
+      element = items[items.length-1];
+      break;
+    case 'Escape':
+      this.props.onClose();
+      break;
+    }
+
+    if (element) {
+      element.focus();
+      e.preventDefault();
+      e.stopPropagation();
+    }
+  };
+
+  handleItemKeyPress = e => {
+    if (e.key === 'Enter' || e.key === ' ') {
+      this.handleClick(e);
+    }
+  };
+
+  handleClick = e => {
+    const { onItemClick } = this.props;
+    onItemClick(e);
+  };
+
+  renderItem = (option, i) => {
+    if (option === null) {
+      return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
+    }
+
+    const { text, href = '#', target = '_blank', method, dangerous } = option;
+
+    return (
+      <li className={classNames('dropdown-menu__item', { 'dropdown-menu__item--dangerous': dangerous })} key={`${text}-${i}`}>
+        <a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex={0} ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
+          {text}
+        </a>
+      </li>
+    );
+  };
+
+  render () {
+    const { items, scrollable, renderHeader, loading } = this.props;
+
+    let renderItem = this.props.renderItem || this.renderItem;
+
+    return (
+      <div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })} ref={this.setRef}>
+        {loading && (
+          <CircularProgress size={30} strokeWidth={3.5} />
+        )}
+
+        {!loading && renderHeader && (
+          <div className='dropdown-menu__container__header'>
+            {renderHeader(items)}
+          </div>
+        )}
+
+        {!loading && (
+          <ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
+            {items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
+          </ul>
+        )}
+      </div>
+    );
+  }
+
+}
+
+class Dropdown extends PureComponent {
+
+  static propTypes = {
+    children: PropTypes.node,
+    icon: PropTypes.string,
+    iconComponent: PropTypes.func,
+    items: PropTypes.array.isRequired,
+    loading: PropTypes.bool,
+    size: PropTypes.number,
+    title: PropTypes.string,
+    disabled: PropTypes.bool,
+    scrollable: PropTypes.bool,
+    active: PropTypes.bool,
+    status: ImmutablePropTypes.map,
+    isUserTouching: PropTypes.func,
+    onOpen: PropTypes.func.isRequired,
+    onClose: PropTypes.func.isRequired,
+    openDropdownId: PropTypes.number,
+    openedViaKeyboard: PropTypes.bool,
+    renderItem: PropTypes.func,
+    renderHeader: PropTypes.func,
+    onItemClick: PropTypes.func,
+    ...WithRouterPropTypes
+  };
+
+  static defaultProps = {
+    title: 'Menu',
+  };
+
+  state = {
+    id: id++,
+  };
+
+  handleClick = ({ type }) => {
+    if (this.state.id === this.props.openDropdownId) {
+      this.handleClose();
+    } else {
+      this.props.onOpen(this.state.id, this.handleItemClick, type !== 'click');
+    }
+  };
+
+  handleClose = () => {
+    if (this.activeElement) {
+      this.activeElement.focus({ preventScroll: true });
+      this.activeElement = null;
+    }
+    this.props.onClose(this.state.id);
+  };
+
+  handleMouseDown = () => {
+    if (!this.state.open) {
+      this.activeElement = document.activeElement;
+    }
+  };
+
+  handleButtonKeyDown = (e) => {
+    switch(e.key) {
+    case ' ':
+    case 'Enter':
+      this.handleMouseDown();
+      break;
+    }
+  };
+
+  handleKeyPress = (e) => {
+    switch(e.key) {
+    case ' ':
+    case 'Enter':
+      this.handleClick(e);
+      e.stopPropagation();
+      e.preventDefault();
+      break;
+    }
+  };
+
+  handleItemClick = e => {
+    const { onItemClick } = this.props;
+    const i = Number(e.currentTarget.getAttribute('data-index'));
+    const item = this.props.items[i];
+
+    this.handleClose();
+
+    if (typeof onItemClick === 'function') {
+      e.preventDefault();
+      onItemClick(item, i);
+    } else if (item && typeof item.action === 'function') {
+      e.preventDefault();
+      item.action();
+    } else if (item && item.to) {
+      e.preventDefault();
+      this.props.history.push(item.to);
+    }
+  };
+
+  setTargetRef = c => {
+    this.target = c;
+  };
+
+  findTarget = () => {
+    return this.target?.buttonRef?.current ?? this.target;
+  };
+
+  componentWillUnmount = () => {
+    if (this.state.id === this.props.openDropdownId) {
+      this.handleClose();
+    }
+  };
+
+  close = () => {
+    this.handleClose();
+  };
+
+  render () {
+    const {
+      icon,
+      iconComponent,
+      items,
+      size,
+      title,
+      disabled,
+      loading,
+      scrollable,
+      openDropdownId,
+      openedViaKeyboard,
+      children,
+      renderItem,
+      renderHeader,
+      active,
+    } = this.props;
+
+    const open = this.state.id === openDropdownId;
+
+    const button = children ? cloneElement(Children.only(children), {
+      onClick: this.handleClick,
+      onMouseDown: this.handleMouseDown,
+      onKeyDown: this.handleButtonKeyDown,
+      onKeyPress: this.handleKeyPress,
+      ref: this.setTargetRef,
+    }) : (
+      <IconButton
+        icon={!open ? icon : 'close'}
+        iconComponent={iconComponent}
+        title={title}
+        active={open || active}
+        disabled={disabled}
+        size={size}
+        onClick={this.handleClick}
+        onMouseDown={this.handleMouseDown}
+        onKeyDown={this.handleButtonKeyDown}
+        onKeyPress={this.handleKeyPress}
+        ref={this.setTargetRef}
+      />
+    );
+
+    return (
+      <>
+        {button}
+
+        <Overlay show={open} offset={[5, 5]} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
+          {({ props, arrowProps, placement }) => (
+            <div {...props}>
+              <div className={`dropdown-animation dropdown-menu ${placement}`}>
+                <div className={`dropdown-menu__arrow ${placement}`} {...arrowProps} />
+                <DropdownMenu
+                  items={items}
+                  loading={loading}
+                  scrollable={scrollable}
+                  onClose={this.handleClose}
+                  openedViaKeyboard={openedViaKeyboard}
+                  renderItem={renderItem}
+                  renderHeader={renderHeader}
+                  onItemClick={this.handleItemClick}
+                />
+              </div>
+            </div>
+          )}
+        </Overlay>
+      </>
+    );
+  }
+
+}
+
+export default withRouter(Dropdown);
diff --git a/app/javascript/mastodon/components/dropdown_menu.tsx b/app/javascript/mastodon/components/dropdown_menu.tsx
deleted file mode 100644
index 0f9ab5b1cc..0000000000
--- a/app/javascript/mastodon/components/dropdown_menu.tsx
+++ /dev/null
@@ -1,551 +0,0 @@
-import {
-  useState,
-  useEffect,
-  useRef,
-  useCallback,
-  cloneElement,
-  Children,
-} from 'react';
-
-import classNames from 'classnames';
-import { Link } from 'react-router-dom';
-
-import type { Map as ImmutableMap } from 'immutable';
-
-import Overlay from 'react-overlays/Overlay';
-import type {
-  OffsetValue,
-  UsePopperOptions,
-} from 'react-overlays/esm/usePopper';
-
-import { fetchRelationships } from 'mastodon/actions/accounts';
-import {
-  openDropdownMenu,
-  closeDropdownMenu,
-} from 'mastodon/actions/dropdown_menu';
-import { openModal, closeModal } from 'mastodon/actions/modal';
-import { CircularProgress } from 'mastodon/components/circular_progress';
-import { isUserTouching } from 'mastodon/is_mobile';
-import type {
-  MenuItem,
-  ActionMenuItem,
-  ExternalLinkMenuItem,
-} from 'mastodon/models/dropdown_menu';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-import type { IconProp } from './icon';
-import { IconButton } from './icon_button';
-
-let id = 0;
-
-const isMenuItem = (item: unknown): item is MenuItem => {
-  if (item === null) {
-    return true;
-  }
-
-  return typeof item === 'object' && 'text' in item;
-};
-
-const isActionItem = (item: unknown): item is ActionMenuItem => {
-  if (!item || !isMenuItem(item)) {
-    return false;
-  }
-
-  return 'action' in item;
-};
-
-const isExternalLinkItem = (item: unknown): item is ExternalLinkMenuItem => {
-  if (!item || !isMenuItem(item)) {
-    return false;
-  }
-
-  return 'href' in item;
-};
-
-type RenderItemFn<Item = MenuItem> = (
-  item: Item,
-  index: number,
-  handlers: {
-    onClick: (e: React.MouseEvent) => void;
-    onKeyUp: (e: React.KeyboardEvent) => void;
-  },
-) => React.ReactNode;
-
-type ItemClickFn<Item = MenuItem> = (item: Item, index: number) => void;
-
-type RenderHeaderFn<Item = MenuItem> = (items: Item[]) => React.ReactNode;
-
-interface DropdownMenuProps<Item = MenuItem> {
-  items?: Item[];
-  loading?: boolean;
-  scrollable?: boolean;
-  onClose: () => void;
-  openedViaKeyboard: boolean;
-  renderItem?: RenderItemFn<Item>;
-  renderHeader?: RenderHeaderFn<Item>;
-  onItemClick?: ItemClickFn<Item>;
-}
-
-export const DropdownMenu = <Item = MenuItem,>({
-  items,
-  loading,
-  scrollable,
-  onClose,
-  openedViaKeyboard,
-  renderItem,
-  renderHeader,
-  onItemClick,
-}: DropdownMenuProps<Item>) => {
-  const nodeRef = useRef<HTMLDivElement>(null);
-  const focusedItemRef = useRef<HTMLElement | null>(null);
-
-  useEffect(() => {
-    const handleDocumentClick = (e: MouseEvent) => {
-      if (
-        e.target instanceof Node &&
-        nodeRef.current &&
-        !nodeRef.current.contains(e.target)
-      ) {
-        onClose();
-        e.stopPropagation();
-        e.preventDefault();
-      }
-    };
-
-    const handleKeyDown = (e: KeyboardEvent) => {
-      if (!nodeRef.current) {
-        return;
-      }
-
-      const items = Array.from(nodeRef.current.querySelectorAll('a, button'));
-      const index = document.activeElement
-        ? items.indexOf(document.activeElement)
-        : -1;
-
-      let element: Element | undefined;
-
-      switch (e.key) {
-        case 'ArrowDown':
-          element = items[index + 1] ?? items[0];
-          break;
-        case 'ArrowUp':
-          element = items[index - 1] ?? items[items.length - 1];
-          break;
-        case 'Tab':
-          if (e.shiftKey) {
-            element = items[index - 1] ?? items[items.length - 1];
-          } else {
-            element = items[index + 1] ?? items[0];
-          }
-          break;
-        case 'Home':
-          element = items[0];
-          break;
-        case 'End':
-          element = items[items.length - 1];
-          break;
-        case 'Escape':
-          onClose();
-          break;
-      }
-
-      if (element && element instanceof HTMLElement) {
-        element.focus();
-        e.preventDefault();
-        e.stopPropagation();
-      }
-    };
-
-    document.addEventListener('click', handleDocumentClick, { capture: true });
-    document.addEventListener('keydown', handleKeyDown, { capture: true });
-
-    if (focusedItemRef.current && openedViaKeyboard) {
-      focusedItemRef.current.focus({ preventScroll: true });
-    }
-
-    return () => {
-      document.removeEventListener('click', handleDocumentClick, {
-        capture: true,
-      });
-      document.removeEventListener('keydown', handleKeyDown, { capture: true });
-    };
-  }, [onClose, openedViaKeyboard]);
-
-  const handleFocusedItemRef = useCallback(
-    (c: HTMLAnchorElement | HTMLButtonElement | null) => {
-      focusedItemRef.current = c as HTMLElement;
-    },
-    [],
-  );
-
-  const handleItemClick = useCallback(
-    (e: React.MouseEvent | React.KeyboardEvent) => {
-      const i = Number(e.currentTarget.getAttribute('data-index'));
-      const item = items?.[i];
-
-      onClose();
-
-      if (!item) {
-        return;
-      }
-
-      if (typeof onItemClick === 'function') {
-        e.preventDefault();
-        onItemClick(item, i);
-      } else if (isActionItem(item)) {
-        e.preventDefault();
-        item.action();
-      }
-    },
-    [onClose, onItemClick, items],
-  );
-
-  const handleItemKeyUp = useCallback(
-    (e: React.KeyboardEvent) => {
-      if (e.key === 'Enter' || e.key === ' ') {
-        handleItemClick(e);
-      }
-    },
-    [handleItemClick],
-  );
-
-  const nativeRenderItem = (option: Item, i: number) => {
-    if (!isMenuItem(option)) {
-      return null;
-    }
-
-    if (option === null) {
-      return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
-    }
-
-    const { text, dangerous } = option;
-
-    let element: React.ReactElement;
-
-    if (isActionItem(option)) {
-      element = (
-        <button
-          ref={i === 0 ? handleFocusedItemRef : undefined}
-          onClick={handleItemClick}
-          onKeyUp={handleItemKeyUp}
-          data-index={i}
-        >
-          {text}
-        </button>
-      );
-    } else if (isExternalLinkItem(option)) {
-      element = (
-        <a
-          href={option.href}
-          target={option.target ?? '_target'}
-          data-method={option.method}
-          rel='noopener'
-          ref={i === 0 ? handleFocusedItemRef : undefined}
-          onClick={handleItemClick}
-          onKeyUp={handleItemKeyUp}
-          data-index={i}
-        >
-          {text}
-        </a>
-      );
-    } else {
-      element = (
-        <Link
-          to={option.to}
-          ref={i === 0 ? handleFocusedItemRef : undefined}
-          onClick={handleItemClick}
-          onKeyUp={handleItemKeyUp}
-          data-index={i}
-        >
-          {text}
-        </Link>
-      );
-    }
-
-    return (
-      <li
-        className={classNames('dropdown-menu__item', {
-          'dropdown-menu__item--dangerous': dangerous,
-        })}
-        key={`${text}-${i}`}
-      >
-        {element}
-      </li>
-    );
-  };
-
-  const renderItemMethod = renderItem ?? nativeRenderItem;
-
-  return (
-    <div
-      className={classNames('dropdown-menu__container', {
-        'dropdown-menu__container--loading': loading,
-      })}
-      ref={nodeRef}
-    >
-      {(loading || !items) && <CircularProgress size={30} strokeWidth={3.5} />}
-
-      {!loading && renderHeader && items && (
-        <div className='dropdown-menu__container__header'>
-          {renderHeader(items)}
-        </div>
-      )}
-
-      {!loading && items && (
-        <ul
-          className={classNames('dropdown-menu__container__list', {
-            'dropdown-menu__container__list--scrollable': scrollable,
-          })}
-        >
-          {items.map((option, i) =>
-            renderItemMethod(option, i, {
-              onClick: handleItemClick,
-              onKeyUp: handleItemKeyUp,
-            }),
-          )}
-        </ul>
-      )}
-    </div>
-  );
-};
-
-interface DropdownProps<Item = MenuItem> {
-  children?: React.ReactElement;
-  icon?: string;
-  iconComponent?: IconProp;
-  items?: Item[];
-  loading?: boolean;
-  title?: string;
-  disabled?: boolean;
-  scrollable?: boolean;
-  active?: boolean;
-  scrollKey?: string;
-  status?: ImmutableMap<string, unknown>;
-  renderItem?: RenderItemFn<Item>;
-  renderHeader?: RenderHeaderFn<Item>;
-  onOpen?: () => void;
-  onItemClick?: ItemClickFn<Item>;
-}
-
-const offset = [5, 5] as OffsetValue;
-const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
-
-export const Dropdown = <Item = MenuItem,>({
-  children,
-  icon,
-  iconComponent,
-  items,
-  loading,
-  title = 'Menu',
-  disabled,
-  scrollable,
-  active,
-  status,
-  renderItem,
-  renderHeader,
-  onOpen,
-  onItemClick,
-  scrollKey,
-}: DropdownProps<Item>) => {
-  const dispatch = useAppDispatch();
-  const openDropdownId = useAppSelector((state) => state.dropdownMenu.openId);
-  const openedViaKeyboard = useAppSelector(
-    (state) => state.dropdownMenu.keyboard,
-  );
-  const [currentId] = useState(id++);
-  const open = currentId === openDropdownId;
-  const activeElement = useRef<HTMLElement | null>(null);
-  const targetRef = useRef<HTMLButtonElement | null>(null);
-
-  const handleClose = useCallback(() => {
-    if (activeElement.current) {
-      activeElement.current.focus({ preventScroll: true });
-      activeElement.current = null;
-    }
-
-    dispatch(
-      closeModal({
-        modalType: 'ACTIONS',
-        ignoreFocus: false,
-      }),
-    );
-
-    dispatch(closeDropdownMenu({ id: currentId }));
-  }, [dispatch, currentId]);
-
-  const handleItemClick = useCallback(
-    (e: React.MouseEvent | React.KeyboardEvent) => {
-      const i = Number(e.currentTarget.getAttribute('data-index'));
-      const item = items?.[i];
-
-      handleClose();
-
-      if (!item) {
-        return;
-      }
-
-      if (typeof onItemClick === 'function') {
-        e.preventDefault();
-        onItemClick(item, i);
-      } else if (isActionItem(item)) {
-        e.preventDefault();
-        item.action();
-      }
-    },
-    [handleClose, onItemClick, items],
-  );
-
-  const handleClick = useCallback(
-    (e: React.MouseEvent | React.KeyboardEvent) => {
-      const { type } = e;
-
-      if (open) {
-        handleClose();
-      } else {
-        onOpen?.();
-
-        if (status) {
-          dispatch(fetchRelationships([status.getIn(['account', 'id'])]));
-        }
-
-        if (isUserTouching()) {
-          dispatch(
-            openModal({
-              modalType: 'ACTIONS',
-              modalProps: {
-                status,
-                actions: items,
-                onClick: handleItemClick,
-              },
-            }),
-          );
-        } else {
-          dispatch(
-            openDropdownMenu({
-              id: currentId,
-              keyboard: type !== 'click',
-              scrollKey,
-            }),
-          );
-        }
-      }
-    },
-    [
-      dispatch,
-      currentId,
-      scrollKey,
-      onOpen,
-      handleItemClick,
-      open,
-      status,
-      items,
-      handleClose,
-    ],
-  );
-
-  const handleMouseDown = useCallback(() => {
-    if (!open && document.activeElement instanceof HTMLElement) {
-      activeElement.current = document.activeElement;
-    }
-  }, [open]);
-
-  const handleButtonKeyDown = useCallback(
-    (e: React.KeyboardEvent) => {
-      switch (e.key) {
-        case ' ':
-        case 'Enter':
-          handleMouseDown();
-          break;
-      }
-    },
-    [handleMouseDown],
-  );
-
-  const handleKeyPress = useCallback(
-    (e: React.KeyboardEvent) => {
-      switch (e.key) {
-        case ' ':
-        case 'Enter':
-          handleClick(e);
-          e.stopPropagation();
-          e.preventDefault();
-          break;
-      }
-    },
-    [handleClick],
-  );
-
-  useEffect(() => {
-    return () => {
-      if (currentId === openDropdownId) {
-        handleClose();
-      }
-    };
-  }, [currentId, openDropdownId, handleClose]);
-
-  let button: React.ReactElement;
-
-  if (children) {
-    button = cloneElement(Children.only(children), {
-      onClick: handleClick,
-      onMouseDown: handleMouseDown,
-      onKeyDown: handleButtonKeyDown,
-      onKeyPress: handleKeyPress,
-      ref: targetRef,
-    });
-  } else if (icon && iconComponent) {
-    button = (
-      <IconButton
-        icon={!open ? icon : 'close'}
-        iconComponent={iconComponent}
-        title={title}
-        active={open || active}
-        disabled={disabled}
-        onClick={handleClick}
-        onMouseDown={handleMouseDown}
-        onKeyDown={handleButtonKeyDown}
-        onKeyPress={handleKeyPress}
-        ref={targetRef}
-      />
-    );
-  } else {
-    return null;
-  }
-
-  return (
-    <>
-      {button}
-
-      <Overlay
-        show={open}
-        offset={offset}
-        placement='bottom'
-        flip
-        target={targetRef}
-        popperConfig={popperConfig}
-      >
-        {({ props, arrowProps, placement }) => (
-          <div {...props}>
-            <div className={`dropdown-animation dropdown-menu ${placement}`}>
-              <div
-                className={`dropdown-menu__arrow ${placement}`}
-                {...arrowProps}
-              />
-
-              <DropdownMenu
-                items={items}
-                loading={loading}
-                scrollable={scrollable}
-                onClose={handleClose}
-                openedViaKeyboard={openedViaKeyboard}
-                renderItem={renderItem}
-                renderHeader={renderHeader}
-                onItemClick={onItemClick}
-              />
-            </div>
-          </div>
-        )}
-      </Overlay>
-    </>
-  );
-};
diff --git a/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js b/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js
new file mode 100644
index 0000000000..726fee9076
--- /dev/null
+++ b/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js
@@ -0,0 +1,32 @@
+import { connect } from 'react-redux';
+
+import { openDropdownMenu, closeDropdownMenu } from 'mastodon/actions/dropdown_menu';
+import { fetchHistory } from 'mastodon/actions/history';
+import DropdownMenu from 'mastodon/components/dropdown_menu';
+
+/**
+ *
+ * @param {import('mastodon/store').RootState} state
+ * @param {*} props
+ */
+const mapStateToProps = (state, { statusId }) => ({
+  openDropdownId: state.dropdownMenu.openId,
+  openedViaKeyboard: state.dropdownMenu.keyboard,
+  items: state.getIn(['history', statusId, 'items']),
+  loading: state.getIn(['history', statusId, 'loading']),
+});
+
+const mapDispatchToProps = (dispatch, { statusId }) => ({
+
+  onOpen (id, onItemClick, keyboard) {
+    dispatch(fetchHistory(statusId));
+    dispatch(openDropdownMenu({ id, keyboard }));
+  },
+
+  onClose (id) {
+    dispatch(closeDropdownMenu({ id }));
+  },
+
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu);
diff --git a/app/javascript/mastodon/components/edited_timestamp/index.jsx b/app/javascript/mastodon/components/edited_timestamp/index.jsx
new file mode 100644
index 0000000000..fbf14ec4bd
--- /dev/null
+++ b/app/javascript/mastodon/components/edited_timestamp/index.jsx
@@ -0,0 +1,76 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { FormattedMessage, injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import { openModal } from 'mastodon/actions/modal';
+import InlineAccount from 'mastodon/components/inline_account';
+import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
+
+import DropdownMenu from './containers/dropdown_menu_container';
+
+const mapDispatchToProps = (dispatch, { statusId }) => ({
+
+  onItemClick (index) {
+    dispatch(openModal({
+      modalType: 'COMPARE_HISTORY',
+      modalProps: { index, statusId },
+    }));
+  },
+
+});
+
+class EditedTimestamp extends PureComponent {
+
+  static propTypes = {
+    statusId: PropTypes.string.isRequired,
+    timestamp: PropTypes.string.isRequired,
+    intl: PropTypes.object.isRequired,
+    onItemClick: PropTypes.func.isRequired,
+  };
+
+  handleItemClick = (item, i) => {
+    const { onItemClick } = this.props;
+    onItemClick(i);
+  };
+
+  renderHeader = items => {
+    return (
+      <FormattedMessage id='status.edited_x_times' defaultMessage='Edited {count, plural, one {# time} other {# times}}' values={{ count: items.size - 1 }} />
+    );
+  };
+
+  renderItem = (item, index, { onClick, onKeyPress }) => {
+    const formattedDate = <RelativeTimestamp timestamp={item.get('created_at')} short={false} />;
+    const formattedName = <InlineAccount accountId={item.get('account')} />;
+
+    const label = item.get('original') ? (
+      <FormattedMessage id='status.history.created' defaultMessage='{name} created {date}' values={{ name: formattedName, date: formattedDate }} />
+    ) : (
+      <FormattedMessage id='status.history.edited' defaultMessage='{name} edited {date}' values={{ name: formattedName, date: formattedDate }} />
+    );
+
+    return (
+      <li className='dropdown-menu__item edited-timestamp__history__item' key={item.get('created_at')}>
+        <button data-index={index} onClick={onClick} onKeyPress={onKeyPress}>{label}</button>
+      </li>
+    );
+  };
+
+  render () {
+    const { timestamp, intl, statusId } = this.props;
+
+    return (
+      <DropdownMenu statusId={statusId} renderItem={this.renderItem} scrollable renderHeader={this.renderHeader} onItemClick={this.handleItemClick}>
+        <button className='dropdown-menu__text-button'>
+          <FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: <span className='animated-number'>{intl.formatDate(timestamp, { month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' })}</span> }} />
+        </button>
+      </DropdownMenu>
+    );
+  }
+
+}
+
+export default connect(null, mapDispatchToProps)(injectIntl(EditedTimestamp));
diff --git a/app/javascript/mastodon/components/edited_timestamp/index.tsx b/app/javascript/mastodon/components/edited_timestamp/index.tsx
deleted file mode 100644
index 4a33210199..0000000000
--- a/app/javascript/mastodon/components/edited_timestamp/index.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import { useCallback } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import type { Map as ImmutableMap, List as ImmutableList } from 'immutable';
-
-import { fetchHistory } from 'mastodon/actions/history';
-import { openModal } from 'mastodon/actions/modal';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
-import { FormattedDateWrapper } from 'mastodon/components/formatted_date';
-import InlineAccount from 'mastodon/components/inline_account';
-import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-type HistoryItem = ImmutableMap<string, unknown>;
-
-export const EditedTimestamp: React.FC<{
-  statusId: string;
-  timestamp: string;
-}> = ({ statusId, timestamp }) => {
-  const dispatch = useAppDispatch();
-  const items = useAppSelector(
-    (state) =>
-      (
-        state.history.getIn([statusId, 'items']) as
-          | ImmutableList<unknown>
-          | undefined
-      )?.toArray() as HistoryItem[],
-  );
-  const loading = useAppSelector(
-    (state) => state.history.getIn([statusId, 'loading']) as boolean,
-  );
-
-  const handleOpen = useCallback(() => {
-    dispatch(fetchHistory(statusId));
-  }, [dispatch, statusId]);
-
-  const handleItemClick = useCallback(
-    (_item: HistoryItem, index: number) => {
-      dispatch(
-        openModal({
-          modalType: 'COMPARE_HISTORY',
-          modalProps: { index, statusId },
-        }),
-      );
-    },
-    [dispatch, statusId],
-  );
-
-  const renderHeader = useCallback((items: HistoryItem[]) => {
-    return (
-      <FormattedMessage
-        id='status.edited_x_times'
-        defaultMessage='Edited {count, plural, one {# time} other {# times}}'
-        values={{ count: items.length - 1 }}
-      />
-    );
-  }, []);
-
-  const renderItem = useCallback(
-    (
-      item: HistoryItem,
-      index: number,
-      {
-        onClick,
-        onKeyUp,
-      }: {
-        onClick: React.MouseEventHandler;
-        onKeyUp: React.KeyboardEventHandler;
-      },
-    ) => {
-      const formattedDate = (
-        <RelativeTimestamp
-          timestamp={item.get('created_at') as string}
-          short={false}
-        />
-      );
-      const formattedName = (
-        <InlineAccount accountId={item.get('account') as string} />
-      );
-
-      const label = (item.get('original') as boolean) ? (
-        <FormattedMessage
-          id='status.history.created'
-          defaultMessage='{name} created {date}'
-          values={{ name: formattedName, date: formattedDate }}
-        />
-      ) : (
-        <FormattedMessage
-          id='status.history.edited'
-          defaultMessage='{name} edited {date}'
-          values={{ name: formattedName, date: formattedDate }}
-        />
-      );
-
-      return (
-        <li
-          className='dropdown-menu__item edited-timestamp__history__item'
-          key={item.get('created_at') as string}
-        >
-          <button data-index={index} onClick={onClick} onKeyUp={onKeyUp}>
-            {label}
-          </button>
-        </li>
-      );
-    },
-    [],
-  );
-
-  return (
-    <Dropdown<HistoryItem>
-      items={items}
-      loading={loading}
-      renderItem={renderItem}
-      scrollable
-      renderHeader={renderHeader}
-      onOpen={handleOpen}
-      onItemClick={handleItemClick}
-    >
-      <button className='dropdown-menu__text-button'>
-        <FormattedMessage
-          id='status.edited'
-          defaultMessage='Edited {date}'
-          values={{
-            date: (
-              <FormattedDateWrapper
-                className='animated-number'
-                value={timestamp}
-                month='short'
-                day='2-digit'
-                hour='2-digit'
-                minute='2-digit'
-              />
-            ),
-          }}
-        />
-      </button>
-    </Dropdown>
-  );
-};
diff --git a/app/javascript/mastodon/components/empty_account.tsx b/app/javascript/mastodon/components/empty_account.tsx
new file mode 100644
index 0000000000..a4a6b7f823
--- /dev/null
+++ b/app/javascript/mastodon/components/empty_account.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+
+import classNames from 'classnames';
+
+import { DisplayName } from 'mastodon/components/display_name';
+import { Skeleton } from 'mastodon/components/skeleton';
+
+interface Props {
+  size?: number;
+  minimal?: boolean;
+}
+
+export const EmptyAccount: React.FC<Props> = ({
+  size = 46,
+  minimal = false,
+}) => {
+  return (
+    <div className={classNames('account', { 'account--minimal': minimal })}>
+      <div className='account__wrapper'>
+        <div className='account__display-name'>
+          <div className='account__avatar-wrapper'>
+            <Skeleton width={size} height={size} />
+          </div>
+
+          <div>
+            <DisplayName />
+            <Skeleton width='7ch' />
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
diff --git a/app/javascript/mastodon/components/error_boundary.jsx b/app/javascript/mastodon/components/error_boundary.jsx
index ca2f017f3b..392a3ad61e 100644
--- a/app/javascript/mastodon/components/error_boundary.jsx
+++ b/app/javascript/mastodon/components/error_boundary.jsx
@@ -98,7 +98,7 @@ export default class ErrorBoundary extends PureComponent {
             )}
           </p>
 
-          <p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied ? 'copied' : ''}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
+          <p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied ? 'copied' : ''}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
         </div>
 
         <Helmet>
diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx
index f21ad60240..4414c75a84 100644
--- a/app/javascript/mastodon/components/follow_button.tsx
+++ b/app/javascript/mastodon/components/follow_button.tsx
@@ -2,8 +2,6 @@ import { useCallback, useEffect } from 'react';
 
 import { useIntl, defineMessages } from 'react-intl';
 
-import classNames from 'classnames';
-
 import { useIdentity } from '@/mastodon/identity_context';
 import { fetchRelationships, followAccount } from 'mastodon/actions/accounts';
 import { openModal } from 'mastodon/actions/modal';
@@ -16,13 +14,13 @@ const messages = defineMessages({
   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
   follow: { id: 'account.follow', defaultMessage: 'Follow' },
   followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' },
-  editProfile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
+  mutual: { id: 'account.mutual', defaultMessage: 'Mutual' },
+  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
 });
 
 export const FollowButton: React.FC<{
   accountId?: string;
-  compact?: boolean;
-}> = ({ accountId, compact }) => {
+}> = ({ accountId }) => {
   const intl = useIntl();
   const dispatch = useAppDispatch();
   const { signedIn } = useIdentity();
@@ -54,7 +52,7 @@ export const FollowButton: React.FC<{
       );
     }
 
-    if (!relationship || !accountId) return;
+    if (!relationship) return;
 
     if (accountId === me) {
       return;
@@ -72,9 +70,15 @@ export const FollowButton: React.FC<{
   if (!signedIn) {
     label = intl.formatMessage(messages.follow);
   } else if (accountId === me) {
-    label = intl.formatMessage(messages.editProfile);
+    label = intl.formatMessage(messages.edit_profile);
   } else if (!relationship) {
     label = <LoadingIndicator />;
+  } else if (
+    relationship.following &&
+    isShowItem('relationships') &&
+    relationship.followed_by
+  ) {
+    label = intl.formatMessage(messages.mutual);
   } else if (relationship.following || relationship.requested) {
     label = intl.formatMessage(messages.unfollow);
   } else if (relationship.followed_by && isShowItem('relationships')) {
@@ -88,10 +92,8 @@ export const FollowButton: React.FC<{
       <a
         href='/settings/profile'
         target='_blank'
-        rel='noopener'
-        className={classNames('button button-secondary', {
-          'button--compact': compact,
-        })}
+        rel='noreferrer noopener'
+        className='button button-secondary'
       >
         {label}
       </a>
@@ -101,14 +103,8 @@ export const FollowButton: React.FC<{
   return (
     <Button
       onClick={handleClick}
-      disabled={
-        relationship?.blocked_by ||
-        relationship?.blocking ||
-        (!(relationship?.following || relationship?.requested) &&
-          (account?.suspended || !!account?.moved))
-      }
+      disabled={relationship?.blocked_by || relationship?.blocking}
       secondary={following}
-      compact={compact}
       className={following ? 'button--destructive' : undefined}
     >
       {label}
diff --git a/app/javascript/mastodon/components/formatted_date.tsx b/app/javascript/mastodon/components/formatted_date.tsx
deleted file mode 100644
index cc927b0873..0000000000
--- a/app/javascript/mastodon/components/formatted_date.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import type { ComponentProps } from 'react';
-
-import { FormattedDate } from 'react-intl';
-
-export const FormattedDateWrapper = (
-  props: ComponentProps<typeof FormattedDate> & { className?: string },
-) => (
-  <FormattedDate {...props}>
-    {(date) => (
-      <time dateTime={tryIsoString(props.value)} className={props.className}>
-        {date}
-      </time>
-    )}
-  </FormattedDate>
-);
-
-const tryIsoString = (date?: string | number | Date): string => {
-  if (!date) {
-    return '';
-  }
-  try {
-    return new Date(date).toISOString();
-  } catch {
-    return date.toString();
-  }
-};
diff --git a/app/javascript/mastodon/components/gif.tsx b/app/javascript/mastodon/components/gif.tsx
deleted file mode 100644
index 1cc0881a5a..0000000000
--- a/app/javascript/mastodon/components/gif.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useHovering } from 'mastodon/hooks/useHovering';
-import { autoPlayGif } from 'mastodon/initial_state';
-
-export const GIF: React.FC<{
-  src: string;
-  staticSrc: string;
-  className: string;
-  animate?: boolean;
-}> = ({ src, staticSrc, className, animate = autoPlayGif }) => {
-  const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(animate);
-
-  return (
-    <img
-      className={className}
-      src={hovering || animate ? src : staticSrc}
-      alt=''
-      role='presentation'
-      onMouseEnter={handleMouseEnter}
-      onMouseLeave={handleMouseLeave}
-    />
-  );
-};
diff --git a/app/javascript/mastodon/components/gifv.tsx b/app/javascript/mastodon/components/gifv.tsx
index 8e3a434c14..c2be591128 100644
--- a/app/javascript/mastodon/components/gifv.tsx
+++ b/app/javascript/mastodon/components/gifv.tsx
@@ -1,70 +1,70 @@
-import { useCallback, useState, forwardRef } from 'react';
+import { useCallback, useState } from 'react';
 
 interface Props {
   src: string;
+  key: string;
   alt?: string;
   lang?: string;
-  width?: number;
-  height?: number;
-  onClick?: React.MouseEventHandler;
-  onMouseDown?: React.MouseEventHandler;
-  onTouchStart?: React.TouchEventHandler;
+  width: number;
+  height: number;
+  onClick?: () => void;
 }
 
-export const GIFV = forwardRef<HTMLVideoElement, Props>(
-  (
-    { src, alt, lang, width, height, onClick, onMouseDown, onTouchStart },
-    ref,
-  ) => {
-    const [loading, setLoading] = useState(true);
+export const GIFV: React.FC<Props> = ({
+  src,
+  alt,
+  lang,
+  width,
+  height,
+  onClick,
+}) => {
+  const [loading, setLoading] = useState(true);
 
-    const handleLoadedData = useCallback(() => {
+  const handleLoadedData: React.ReactEventHandler<HTMLVideoElement> =
+    useCallback(() => {
       setLoading(false);
     }, [setLoading]);
 
-    const handleClick = useCallback(
-      (e: React.MouseEvent) => {
+  const handleClick: React.MouseEventHandler = useCallback(
+    (e) => {
+      if (onClick) {
         e.stopPropagation();
-        onClick?.(e);
-      },
-      [onClick],
-    );
+        onClick();
+      }
+    },
+    [onClick],
+  );
 
-    return (
-      <div className='gifv'>
-        {loading && (
-          <canvas
-            role='button'
-            tabIndex={0}
-            aria-label={alt}
-            title={alt}
-            lang={lang}
-            onClick={handleClick}
-          />
-        )}
-
-        <video
-          ref={ref}
-          src={src}
+  return (
+    <div className='gifv' style={{ position: 'relative' }}>
+      {loading && (
+        <canvas
+          width={width}
+          height={height}
           role='button'
           tabIndex={0}
           aria-label={alt}
           title={alt}
           lang={lang}
-          width={width}
-          height={height}
-          muted
-          loop
-          autoPlay
-          playsInline
           onClick={handleClick}
-          onLoadedData={handleLoadedData}
-          onMouseDown={onMouseDown}
-          onTouchStart={onTouchStart}
         />
-      </div>
-    );
-  },
-);
+      )}
 
-GIFV.displayName = 'GIFV';
+      <video
+        src={src}
+        role='button'
+        tabIndex={0}
+        aria-label={alt}
+        title={alt}
+        lang={lang}
+        muted
+        loop
+        autoPlay
+        playsInline
+        onClick={handleClick}
+        onLoadedData={handleLoadedData}
+        style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
+      />
+    </div>
+  );
+};
diff --git a/app/javascript/mastodon/components/hashtag.tsx b/app/javascript/mastodon/components/hashtag.tsx
index 346c95183f..8963e4a40d 100644
--- a/app/javascript/mastodon/components/hashtag.tsx
+++ b/app/javascript/mastodon/components/hashtag.tsx
@@ -12,7 +12,6 @@ import { Sparklines, SparklinesCurve } from 'react-sparklines';
 
 import { ShortNumber } from 'mastodon/components/short_number';
 import { Skeleton } from 'mastodon/components/skeleton';
-import type { Hashtag as HashtagType } from 'mastodon/models/tags';
 
 interface SilentErrorBoundaryProps {
   children: React.ReactNode;
@@ -81,32 +80,15 @@ export const ImmutableHashtag = ({ hashtag }: ImmutableHashtagProps) => (
   />
 );
 
-export const CompatibilityHashtag: React.FC<{
-  hashtag: HashtagType;
-}> = ({ hashtag }) => (
-  <Hashtag
-    name={hashtag.name}
-    to={`/tags/${hashtag.name}`}
-    people={
-      (hashtag.history[0].accounts as unknown as number) * 1 +
-      ((hashtag.history[1]?.accounts ?? 0) as unknown as number) * 1
-    }
-    history={hashtag.history
-      .map((day) => (day.uses as unknown as number) * 1)
-      .reverse()}
-  />
-);
-
 export interface HashtagProps {
   className?: string;
   description?: React.ReactNode;
   history?: number[];
   name: string;
-  people?: number;
+  people: number;
   to: string;
   uses?: number;
   withGraph?: boolean;
-  children?: React.ReactNode;
 }
 
 export const Hashtag: React.FC<HashtagProps> = ({
@@ -118,7 +100,6 @@ export const Hashtag: React.FC<HashtagProps> = ({
   className,
   description,
   withGraph = true,
-  children,
 }) => (
   <div className={classNames('trends__item', className)}>
     <div className='trends__item__name'>
@@ -153,14 +134,12 @@ export const Hashtag: React.FC<HashtagProps> = ({
           <Sparklines
             width={50}
             height={28}
-            data={history ?? Array.from(Array(7)).map(() => 0)}
+            data={history ? history : Array.from(Array(7)).map(() => 0)}
           >
             <SparklinesCurve style={{ fill: 'none' }} />
           </Sparklines>
         </SilentErrorBoundary>
       </div>
     )}
-
-    {children && <div className='trends__item__buttons'>{children}</div>}
   </div>
 );
diff --git a/app/javascript/mastodon/components/hashtag_bar.tsx b/app/javascript/mastodon/components/hashtag_bar.tsx
index ce8f17ddb9..9e1d74bb74 100644
--- a/app/javascript/mastodon/components/hashtag_bar.tsx
+++ b/app/javascript/mastodon/components/hashtag_bar.tsx
@@ -20,7 +20,6 @@ export type StatusLike = Record<{
   contentHTML: string;
   media_attachments: List<unknown>;
   spoiler_text?: string;
-  account: Record<{ id: string }>;
 }>;
 
 function normalizeHashtag(hashtag: string) {
@@ -196,36 +195,19 @@ export function getHashtagBarForStatus(status: StatusLike) {
 
   return {
     statusContentProps,
-    hashtagBar: (
-      <HashtagBar
-        hashtags={hashtagsInBar}
-        accountId={status.getIn(['account', 'id']) as string}
-      />
-    ),
+    hashtagBar: <HashtagBar hashtags={hashtagsInBar} />,
   };
 }
 
-export function getFeaturedHashtagBar(
-  accountId: string,
-  acct: string,
-  tags: string[],
-) {
-  return (
-    <HashtagBar
-      acct={acct}
-      hashtags={tags}
-      accountId={accountId}
-      defaultExpanded
-    />
-  );
+export function getFeaturedHashtagBar(acct: string, tags: string[]) {
+  return <HashtagBar acct={acct} hashtags={tags} defaultExpanded />;
 }
 
 const HashtagBar: React.FC<{
   hashtags: string[];
-  accountId: string;
   acct?: string;
   defaultExpanded?: boolean;
-}> = ({ hashtags, accountId, acct, defaultExpanded }) => {
+}> = ({ hashtags, acct, defaultExpanded }) => {
   const [expanded, setExpanded] = useState(false);
   const handleClick = useCallback(() => {
     setExpanded(true);
@@ -246,7 +228,6 @@ const HashtagBar: React.FC<{
         <Link
           key={hashtag}
           to={acct ? `/@${acct}/tagged/${hashtag}` : `/tags/${hashtag}`}
-          data-menu-hashtag={accountId}
         >
           #<span>{hashtag}</span>
         </Link>
diff --git a/app/javascript/mastodon/components/hover_card_controller.tsx b/app/javascript/mastodon/components/hover_card_controller.tsx
index 38c3306f30..057ef1aaed 100644
--- a/app/javascript/mastodon/components/hover_card_controller.tsx
+++ b/app/javascript/mastodon/components/hover_card_controller.tsx
@@ -8,8 +8,8 @@ import type {
   UsePopperOptions,
 } from 'react-overlays/esm/usePopper';
 
+import { useTimeout } from 'mastodon/../hooks/useTimeout';
 import { HoverCardAccount } from 'mastodon/components/hover_card_account';
-import { useTimeout } from 'mastodon/hooks/useTimeout';
 
 const offset = [-12, 4] as OffsetValue;
 const enterDelay = 750;
diff --git a/app/javascript/mastodon/components/icon_button.tsx b/app/javascript/mastodon/components/icon_button.tsx
index 7e0b3e7a22..b7cac35960 100644
--- a/app/javascript/mastodon/components/icon_button.tsx
+++ b/app/javascript/mastodon/components/icon_button.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useCallback, forwardRef } from 'react';
+import { PureComponent, createRef } from 'react';
 
 import classNames from 'classnames';
 
@@ -15,110 +15,101 @@ interface Props {
   onMouseDown?: React.MouseEventHandler<HTMLButtonElement>;
   onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
   onKeyPress?: React.KeyboardEventHandler<HTMLButtonElement>;
-  active?: boolean;
+  active: boolean;
   expanded?: boolean;
   style?: React.CSSProperties;
   activeStyle?: React.CSSProperties;
-  disabled?: boolean;
+  disabled: boolean;
   inverted?: boolean;
-  animate?: boolean;
-  overlay?: boolean;
-  tabIndex?: number;
+  animate: boolean;
+  overlay: boolean;
+  tabIndex: number;
   counter?: number;
   href?: string;
-  ariaHidden?: boolean;
+  ariaHidden: boolean;
   data_id?: string;
 }
+interface States {
+  activate: boolean;
+  deactivate: boolean;
+}
+export class IconButton extends PureComponent<Props, States> {
+  buttonRef = createRef<HTMLButtonElement>();
 
-export const IconButton = forwardRef<HTMLButtonElement, Props>(
-  (
-    {
+  static defaultProps = {
+    active: false,
+    disabled: false,
+    animate: false,
+    overlay: false,
+    tabIndex: 0,
+    ariaHidden: false,
+  };
+
+  state = {
+    activate: false,
+    deactivate: false,
+  };
+
+  UNSAFE_componentWillReceiveProps(nextProps: Props) {
+    if (!nextProps.animate) return;
+
+    if (this.props.active && !nextProps.active) {
+      this.setState({ activate: false, deactivate: true });
+    } else if (!this.props.active && nextProps.active) {
+      this.setState({ activate: true, deactivate: false });
+    }
+  }
+
+  handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
+    e.preventDefault();
+
+    if (!this.props.disabled && this.props.onClick != null) {
+      this.props.onClick(e);
+    }
+  };
+
+  handleKeyPress: React.KeyboardEventHandler<HTMLButtonElement> = (e) => {
+    if (this.props.onKeyPress && !this.props.disabled) {
+      this.props.onKeyPress(e);
+    }
+  };
+
+  handleMouseDown: React.MouseEventHandler<HTMLButtonElement> = (e) => {
+    if (!this.props.disabled && this.props.onMouseDown) {
+      this.props.onMouseDown(e);
+    }
+  };
+
+  handleKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (e) => {
+    if (!this.props.disabled && this.props.onKeyDown) {
+      this.props.onKeyDown(e);
+    }
+  };
+
+  render() {
+    const style = {
+      ...this.props.style,
+      ...(this.props.active ? this.props.activeStyle : {}),
+    };
+
+    const {
+      active,
       className,
+      disabled,
       expanded,
       icon,
       iconComponent,
       inverted,
+      overlay,
+      tabIndex,
       title,
       counter,
       href,
-      style,
-      activeStyle,
-      onClick,
-      onKeyDown,
-      onKeyPress,
-      onMouseDown,
-      active = false,
-      disabled = false,
-      animate = false,
-      overlay = false,
-      tabIndex = 0,
-      ariaHidden = false,
-      data_id = undefined,
-    },
-    buttonRef,
-  ) => {
-    const [activate, setActivate] = useState(false);
-    const [deactivate, setDeactivate] = useState(false);
+      ariaHidden,
+      data_id,
+    } = this.props;
 
-    useEffect(() => {
-      if (!animate) {
-        return;
-      }
-
-      if (activate && !active) {
-        setActivate(false);
-        setDeactivate(true);
-      } else if (!activate && active) {
-        setActivate(true);
-        setDeactivate(false);
-      }
-    }, [setActivate, setDeactivate, animate, active, activate]);
-
-    const handleClick: React.MouseEventHandler<HTMLButtonElement> = useCallback(
-      (e) => {
-        e.preventDefault();
-
-        if (!disabled) {
-          onClick?.(e);
-        }
-      },
-      [disabled, onClick],
-    );
-
-    const handleKeyPress: React.KeyboardEventHandler<HTMLButtonElement> =
-      useCallback(
-        (e) => {
-          if (!disabled) {
-            onKeyPress?.(e);
-          }
-        },
-        [disabled, onKeyPress],
-      );
-
-    const handleMouseDown: React.MouseEventHandler<HTMLButtonElement> =
-      useCallback(
-        (e) => {
-          if (!disabled) {
-            onMouseDown?.(e);
-          }
-        },
-        [disabled, onMouseDown],
-      );
-
-    const handleKeyDown: React.KeyboardEventHandler<HTMLButtonElement> =
-      useCallback(
-        (e) => {
-          if (!disabled) {
-            onKeyDown?.(e);
-          }
-        },
-        [disabled, onKeyDown],
-      );
-
-    const buttonStyle = {
-      ...style,
-      ...(active ? activeStyle : {}),
-    };
+    const { activate, deactivate } = this.state;
 
     const classes = classNames(className, 'icon-button', {
       active,
@@ -157,20 +148,18 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
         aria-hidden={ariaHidden}
         title={title}
         className={classes}
-        onClick={handleClick}
-        onMouseDown={handleMouseDown}
-        onKeyDown={handleKeyDown}
-        onKeyPress={handleKeyPress} // eslint-disable-line @typescript-eslint/no-deprecated
-        style={buttonStyle}
+        onClick={this.handleClick}
+        onMouseDown={this.handleMouseDown}
+        onKeyDown={this.handleKeyDown}
+        onKeyPress={this.handleKeyPress}
+        style={style}
         tabIndex={tabIndex}
         disabled={disabled}
         data-id={data_id}
-        ref={buttonRef}
+        ref={this.buttonRef}
       >
         {contents}
       </button>
     );
-  },
-);
-
-IconButton.displayName = 'IconButton';
+  }
+}
diff --git a/app/javascript/mastodon/components/load_gap.tsx b/app/javascript/mastodon/components/load_gap.tsx
index 6cbdee6ce5..544b5e1461 100644
--- a/app/javascript/mastodon/components/load_gap.tsx
+++ b/app/javascript/mastodon/components/load_gap.tsx
@@ -1,10 +1,9 @@
-import { useCallback, useState } from 'react';
+import { useCallback } from 'react';
 
 import { useIntl, defineMessages } from 'react-intl';
 
 import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
 import { Icon } from 'mastodon/components/icon';
-import { LoadingIndicator } from 'mastodon/components/loading_indicator';
 
 const messages = defineMessages({
   load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
@@ -18,12 +17,10 @@ interface Props<T> {
 
 export const LoadGap = <T,>({ disabled, param, onClick }: Props<T>) => {
   const intl = useIntl();
-  const [loading, setLoading] = useState(false);
 
   const handleClick = useCallback(() => {
-    setLoading(true);
     onClick(param);
-  }, [setLoading, param, onClick]);
+  }, [param, onClick]);
 
   return (
     <button
@@ -31,13 +28,8 @@ export const LoadGap = <T,>({ disabled, param, onClick }: Props<T>) => {
       disabled={disabled}
       onClick={handleClick}
       aria-label={intl.formatMessage(messages.load_more)}
-      title={intl.formatMessage(messages.load_more)}
     >
-      {loading ? (
-        <LoadingIndicator />
-      ) : (
-        <Icon id='ellipsis-h' icon={MoreHorizIcon} />
-      )}
+      <Icon id='ellipsis-h' icon={MoreHorizIcon} />
     </button>
   );
 };
diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx
index 12cf381e5e..e0641f62e3 100644
--- a/app/javascript/mastodon/components/media_gallery.jsx
+++ b/app/javascript/mastodon/components/media_gallery.jsx
@@ -12,7 +12,6 @@ import { debounce } from 'lodash';
 
 import { AltTextBadge } from 'mastodon/components/alt_text_badge';
 import { Blurhash } from 'mastodon/components/blurhash';
-import { SpoilerButton } from 'mastodon/components/spoiler_button';
 import { formatTime } from 'mastodon/features/video';
 
 import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state';
@@ -39,7 +38,6 @@ class Item extends PureComponent {
 
   state = {
     loaded: false,
-    error: false,
   };
 
   handleMouseEnter = (e) => {
@@ -83,10 +81,6 @@ class Item extends PureComponent {
     this.setState({ loaded: true });
   };
 
-  handleImageError = () => {
-    this.setState({ error: true });
-  };
-
   render () {
     const { attachment, lang, index, size, standalone, displayWidth, visible } = this.props;
 
@@ -128,7 +122,7 @@ class Item extends PureComponent {
     if (attachment.get('type') === 'unknown') {
       return (
         <div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
-          <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={description} lang={lang} target='_blank' rel='noopener'>
+          <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={description} lang={lang} target='_blank' rel='noopener noreferrer'>
             <Blurhash
               hash={attachment.get('blurhash')}
               className='media-gallery__preview'
@@ -160,17 +154,17 @@ class Item extends PureComponent {
           href={attachment.get('remote_url') || originalUrl}
           onClick={this.handleClick}
           target='_blank'
-          rel='noopener'
+          rel='noopener noreferrer'
         >
           <img
             src={previewUrl}
             srcSet={srcSet}
             sizes={sizes}
             alt={description}
+            title={description}
             lang={lang}
             style={{ objectPosition: `${x}% ${y}%` }}
             onLoad={this.handleImageLoad}
-            onError={this.handleImageError}
           />
         </a>
       );
@@ -189,6 +183,7 @@ class Item extends PureComponent {
           <video
             className='media-gallery__item-gifv-thumbnail'
             aria-label={description}
+            title={description}
             lang={lang}
             role='application'
             src={attachment.get('url')}
@@ -206,7 +201,7 @@ class Item extends PureComponent {
     }
 
     return (
-      <div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--error': this.state.error, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
+      <div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
         <Blurhash
           hash={attachment.get('blurhash')}
           dummy={!useBlurhash}
@@ -243,7 +238,6 @@ class MediaGallery extends PureComponent {
     autoplay: PropTypes.bool,
     onToggleVisibility: PropTypes.func,
     compact: PropTypes.bool,
-    matchedFilters: PropTypes.arrayOf(PropTypes.string),
   };
 
   state = {
@@ -314,11 +308,11 @@ class MediaGallery extends PureComponent {
   }
 
   render () {
-    const { media, lang, sensitive, defaultWidth, autoplay, compact, matchedFilters } = this.props;
+    const { media, lang, sensitive, defaultWidth, autoplay, compact } = this.props;
     const { visible } = this.state;
     const width = this.state.width || defaultWidth;
 
-    let children;
+    let children, spoilerButton;
 
     const style = {};
 
@@ -337,6 +331,26 @@ class MediaGallery extends PureComponent {
       children = media.map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} displayWidth={width} visible={visible || uncached} />);
     }
 
+    if (uncached) {
+      spoilerButton = (
+        <button type='button' disabled className='spoiler-button__overlay'>
+          <span className='spoiler-button__overlay__label'>
+            <FormattedMessage id='status.uncached_media_warning' defaultMessage='Preview not available' />
+            <span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.open' defaultMessage='Click to open' /></span>
+          </span>
+        </button>
+      );
+    } else if (!visible) {
+      spoilerButton = (
+        <button type='button' onClick={this.handleOpen} className='spoiler-button__overlay'>
+          <span className='spoiler-button__overlay__label'>
+            {sensitive ? <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /> : <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />}
+            <span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
+          </span>
+        </button>
+      );
+    }
+
     const rowClass = (size === 5 || size === 6 || size === 9 || size === 10 || size === 11 || size === 12) ? 'media-gallery--row3' :
       (size === 7 || size === 8 || size === 13 || size === 14 || size === 15 || size === 16) ? 'media-gallery--row4' :
         'media-gallery--row2';
@@ -353,8 +367,12 @@ class MediaGallery extends PureComponent {
     return (
       <div className={classNames(classList)} style={style} ref={this.handleRef}>
         {children}
-
-        {(!visible || uncached) && <SpoilerButton uncached={uncached} sensitive={sensitive} onClick={this.handleOpen} matchedFilters={matchedFilters} />}
+        
+        {(!visible || uncached) && (
+          <div className={classNames('spoiler-button', { 'spoiler-button--click-thru': uncached })}>
+            {spoilerButton}
+          </div>
+        )}
 
         {(visible && !uncached) && (
           <div className='media-gallery__actions'>
diff --git a/app/javascript/mastodon/components/modal_root.jsx b/app/javascript/mastodon/components/modal_root.jsx
index 1eae0819af..e7fa5e6f9a 100644
--- a/app/javascript/mastodon/components/modal_root.jsx
+++ b/app/javascript/mastodon/components/modal_root.jsx
@@ -13,14 +13,11 @@ class ModalRoot extends PureComponent {
   static propTypes = {
     children: PropTypes.node,
     onClose: PropTypes.func.isRequired,
-    backgroundColor: PropTypes.oneOfType([
-      PropTypes.string,
-      PropTypes.shape({
-        r: PropTypes.number,
-        g: PropTypes.number,
-        b: PropTypes.number,
-      }),
-    ]),
+    backgroundColor: PropTypes.shape({
+      r: PropTypes.number,
+      g: PropTypes.number,
+      b: PropTypes.number,
+    }),
     ignoreFocus: PropTypes.bool,
     ...WithOptionalRouterPropTypes,
   };
@@ -118,9 +115,9 @@ class ModalRoot extends PureComponent {
   }
 
   _ensureHistoryBuffer () {
-    const { pathname, search, hash, state } = this.history.location;
+    const { pathname, state } = this.history.location;
     if (!state || state.mastodonModalKey !== this._modalHistoryKey) {
-      this.history.push({ pathname, search, hash }, { ...state, mastodonModalKey: this._modalHistoryKey });
+      this.history.push(pathname, { ...state, mastodonModalKey: this._modalHistoryKey });
     }
   }
 
@@ -144,17 +141,14 @@ class ModalRoot extends PureComponent {
 
     let backgroundColor = null;
 
-    if (this.props.backgroundColor && typeof this.props.backgroundColor === 'string') {
-      backgroundColor = this.props.backgroundColor;
-    } else if (this.props.backgroundColor) {
-      const darkenedColor = multiply({ ...this.props.backgroundColor, a: 1 }, { r: 0, g: 0, b: 0, a: 0.7 });
-      backgroundColor = `rgb(${darkenedColor.r}, ${darkenedColor.g}, ${darkenedColor.b})`;
+    if (this.props.backgroundColor) {
+      backgroundColor = multiply({ ...this.props.backgroundColor, a: 1 }, { r: 0, g: 0, b: 0, a: 0.7 });
     }
 
     return (
       <div className='modal-root' ref={this.setRef}>
         <div style={{ pointerEvents: visible ? 'auto' : 'none' }}>
-          <div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor }} />
+          <div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor: backgroundColor ? `rgba(${backgroundColor.r}, ${backgroundColor.g}, ${backgroundColor.b}, 0.9)` : null }} />
           <div role='dialog' className='modal-root__container'>{children}</div>
         </div>
       </div>
diff --git a/app/javascript/mastodon/components/navigation_portal.tsx b/app/javascript/mastodon/components/navigation_portal.tsx
index d3ac8baa6e..08f91ce18a 100644
--- a/app/javascript/mastodon/components/navigation_portal.tsx
+++ b/app/javascript/mastodon/components/navigation_portal.tsx
@@ -1,6 +1,25 @@
+import { Switch, Route } from 'react-router-dom';
+
+import AccountNavigation from 'mastodon/features/account/navigation';
 import Trends from 'mastodon/features/getting_started/containers/trends_container';
 import { showTrends } from 'mastodon/initial_state';
 
+const DefaultNavigation: React.FC = () => (showTrends ? <Trends /> : null);
+
 export const NavigationPortal: React.FC = () => (
-  <div className='navigation-panel__portal'>{showTrends && <Trends />}</div>
+  <div className='navigation-panel__portal'>
+    <Switch>
+      <Route path='/@:acct' exact component={AccountNavigation} />
+      <Route
+        path='/@:acct/tagged/:tagged?'
+        exact
+        component={AccountNavigation}
+      />
+      <Route path='/@:acct/with_replies' exact component={AccountNavigation} />
+      <Route path='/@:acct/followers' exact component={AccountNavigation} />
+      <Route path='/@:acct/following' exact component={AccountNavigation} />
+      <Route path='/@:acct/media' exact component={AccountNavigation} />
+      <Route component={DefaultNavigation} />
+    </Switch>
+  </div>
 );
diff --git a/app/javascript/mastodon/components/poll.jsx b/app/javascript/mastodon/components/poll.jsx
new file mode 100644
index 0000000000..7b836f00b1
--- /dev/null
+++ b/app/javascript/mastodon/components/poll.jsx
@@ -0,0 +1,247 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import classNames from 'classnames';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+import escapeTextContentForBrowser from 'escape-html';
+import spring from 'react-motion/lib/spring';
+
+import CheckIcon from '@/material-icons/400-24px/check.svg?react';
+import { Icon }  from 'mastodon/components/icon';
+import emojify from 'mastodon/features/emoji/emoji';
+import Motion from 'mastodon/features/ui/util/optional_motion';
+import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
+
+import { RelativeTimestamp } from './relative_timestamp';
+
+const messages = defineMessages({
+  closed: {
+    id: 'poll.closed',
+    defaultMessage: 'Closed',
+  },
+  voted: {
+    id: 'poll.voted',
+    defaultMessage: 'You voted for this answer',
+  },
+  votes: {
+    id: 'poll.votes',
+    defaultMessage: '{votes, plural, one {# vote} other {# votes}}',
+  },
+});
+
+const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
+  obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
+  return obj;
+}, {});
+
+class Poll extends ImmutablePureComponent {
+  static propTypes = {
+    identity: identityContextPropShape,
+    poll: ImmutablePropTypes.map,
+    lang: PropTypes.string,
+    intl: PropTypes.object.isRequired,
+    disabled: PropTypes.bool,
+    refresh: PropTypes.func,
+    onVote: PropTypes.func,
+  };
+
+  state = {
+    selected: {},
+    expired: null,
+  };
+
+  static getDerivedStateFromProps (props, state) {
+    const { poll } = props;
+    const expires_at = poll.get('expires_at');
+    const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < Date.now();
+    return (expired === state.expired) ? null : { expired };
+  }
+
+  componentDidMount () {
+    this._setupTimer();
+  }
+
+  componentDidUpdate () {
+    this._setupTimer();
+  }
+
+  componentWillUnmount () {
+    clearTimeout(this._timer);
+  }
+
+  _setupTimer () {
+    const { poll } = this.props;
+    clearTimeout(this._timer);
+    if (!this.state.expired) {
+      const delay = (new Date(poll.get('expires_at'))).getTime() - Date.now();
+      this._timer = setTimeout(() => {
+        this.setState({ expired: true });
+      }, delay);
+    }
+  }
+
+  _toggleOption = value => {
+    if (this.props.poll.get('multiple')) {
+      const tmp = { ...this.state.selected };
+      if (tmp[value]) {
+        delete tmp[value];
+      } else {
+        tmp[value] = true;
+      }
+      this.setState({ selected: tmp });
+    } else {
+      const tmp = {};
+      tmp[value] = true;
+      this.setState({ selected: tmp });
+    }
+  };
+
+  handleOptionChange = ({ target: { value } }) => {
+    this._toggleOption(value);
+  };
+
+  handleOptionKeyPress = (e) => {
+    if (e.key === 'Enter' || e.key === ' ') {
+      this._toggleOption(e.target.getAttribute('data-index'));
+      e.stopPropagation();
+      e.preventDefault();
+    }
+  };
+
+  handleVote = () => {
+    if (this.props.disabled) {
+      return;
+    }
+
+    this.props.onVote(Object.keys(this.state.selected));
+  };
+
+  handleRefresh = () => {
+    if (this.props.disabled) {
+      return;
+    }
+
+    this.props.refresh();
+  };
+
+  handleReveal = () => {
+    this.setState({ revealed: true });
+  };
+
+  renderOption (option, optionIndex, showResults) {
+    const { poll, lang, disabled, intl } = this.props;
+    const pollVotesCount  = poll.get('voters_count') || poll.get('votes_count');
+    const percent         = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
+    const leading         = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
+    const active          = !!this.state.selected[`${optionIndex}`];
+    const voted           = option.get('voted') || (poll.get('own_votes') && poll.get('own_votes').includes(optionIndex));
+
+    const title = option.getIn(['translation', 'title']) || option.get('title');
+    let titleHtml = option.getIn(['translation', 'titleHtml']) || option.get('titleHtml');
+
+    if (!titleHtml) {
+      const emojiMap = makeEmojiMap(poll);
+      titleHtml = emojify(escapeTextContentForBrowser(title), emojiMap);
+    }
+
+    return (
+      <li key={option.get('title')}>
+        <label className={classNames('poll__option', { selectable: !showResults })}>
+          <input
+            name='vote-options'
+            type={poll.get('multiple') ? 'checkbox' : 'radio'}
+            value={optionIndex}
+            checked={active}
+            onChange={this.handleOptionChange}
+            disabled={disabled}
+          />
+
+          {!showResults && (
+            <span
+              className={classNames('poll__input', { checkbox: poll.get('multiple'), active })}
+              tabIndex={0}
+              role={poll.get('multiple') ? 'checkbox' : 'radio'}
+              onKeyPress={this.handleOptionKeyPress}
+              aria-checked={active}
+              aria-label={title}
+              lang={lang}
+              data-index={optionIndex}
+            />
+          )}
+          {showResults && (
+            <span
+              className='poll__number'
+              title={intl.formatMessage(messages.votes, {
+                votes: option.get('votes_count'),
+              })}
+            >
+              {Math.round(percent)}%
+            </span>
+          )}
+
+          <span
+            className='poll__option__text translate'
+            lang={lang}
+            dangerouslySetInnerHTML={{ __html: titleHtml }}
+          />
+
+          {!!voted && <span className='poll__voted'>
+            <Icon id='check' icon={CheckIcon} className='poll__voted__mark' title={intl.formatMessage(messages.voted)} />
+          </span>}
+        </label>
+
+        {showResults && (
+          <Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
+            {({ width }) =>
+              <span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} />
+            }
+          </Motion>
+        )}
+      </li>
+    );
+  }
+
+  render () {
+    const { poll, intl } = this.props;
+    const { revealed, expired } = this.state;
+
+    if (!poll) {
+      return null;
+    }
+
+    const timeRemaining = expired ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />;
+    const showResults   = poll.get('voted') || revealed || expired;
+    const disabled      = this.props.disabled || Object.entries(this.state.selected).every(item => !item);
+
+    let votesCount = null;
+
+    if (poll.get('voters_count') !== null && poll.get('voters_count') !== undefined) {
+      votesCount = <FormattedMessage id='poll.total_people' defaultMessage='{count, plural, one {# person} other {# people}}' values={{ count: poll.get('voters_count') }} />;
+    } else {
+      votesCount = <FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} />;
+    }
+
+    return (
+      <div className='poll'>
+        <ul>
+          {poll.get('options').map((option, i) => this.renderOption(option, i, showResults))}
+        </ul>
+
+        <div className='poll__footer'>
+          {!showResults && <button className='button button-secondary' disabled={disabled || !this.props.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
+          {!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>}
+          {showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
+          {votesCount}
+          {poll.get('expires_at') && <> · {timeRemaining}</>}
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default injectIntl(withIdentity(Poll));
diff --git a/app/javascript/mastodon/components/poll.tsx b/app/javascript/mastodon/components/poll.tsx
deleted file mode 100644
index 6692f674d4..0000000000
--- a/app/javascript/mastodon/components/poll.tsx
+++ /dev/null
@@ -1,337 +0,0 @@
-import type { KeyboardEventHandler } from 'react';
-import { useCallback, useMemo, useState } from 'react';
-
-import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
-
-import classNames from 'classnames';
-
-import { animated, useSpring } from '@react-spring/web';
-import escapeTextContentForBrowser from 'escape-html';
-
-import CheckIcon from '@/material-icons/400-24px/check.svg?react';
-import { openModal } from 'mastodon/actions/modal';
-import { fetchPoll, vote } from 'mastodon/actions/polls';
-import { Icon } from 'mastodon/components/icon';
-import emojify from 'mastodon/features/emoji/emoji';
-import { useIdentity } from 'mastodon/identity_context';
-import { reduceMotion } from 'mastodon/initial_state';
-import { makeEmojiMap } from 'mastodon/models/custom_emoji';
-import type * as Model from 'mastodon/models/poll';
-import type { Status } from 'mastodon/models/status';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-import { RelativeTimestamp } from './relative_timestamp';
-
-const messages = defineMessages({
-  closed: {
-    id: 'poll.closed',
-    defaultMessage: 'Closed',
-  },
-  voted: {
-    id: 'poll.voted',
-    defaultMessage: 'You voted for this answer',
-  },
-  votes: {
-    id: 'poll.votes',
-    defaultMessage: '{votes, plural, one {# vote} other {# votes}}',
-  },
-});
-
-interface PollProps {
-  pollId: string;
-  status: Status;
-  lang?: string;
-  disabled?: boolean;
-}
-
-export const Poll: React.FC<PollProps> = ({ pollId, disabled, status }) => {
-  // Third party hooks
-  const poll = useAppSelector((state) => state.polls[pollId]);
-  const identity = useIdentity();
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-
-  // State
-  const [revealed, setRevealed] = useState(false);
-  const [selected, setSelected] = useState<Record<string, boolean>>({});
-
-  // Derived values
-  const expired = useMemo(() => {
-    if (!poll) {
-      return false;
-    }
-    const expiresAt = poll.expires_at;
-    return poll.expired || new Date(expiresAt).getTime() < Date.now();
-  }, [poll]);
-  const timeRemaining = useMemo(() => {
-    if (!poll) {
-      return null;
-    }
-    if (expired) {
-      return intl.formatMessage(messages.closed);
-    }
-    return <RelativeTimestamp timestamp={poll.expires_at} futureDate />;
-  }, [expired, intl, poll]);
-  const votesCount = useMemo(() => {
-    if (!poll) {
-      return null;
-    }
-    if (poll.voters_count) {
-      return (
-        <FormattedMessage
-          id='poll.total_people'
-          defaultMessage='{count, plural, one {# person} other {# people}}'
-          values={{ count: poll.voters_count }}
-        />
-      );
-    }
-    return (
-      <FormattedMessage
-        id='poll.total_votes'
-        defaultMessage='{count, plural, one {# vote} other {# votes}}'
-        values={{ count: poll.votes_count }}
-      />
-    );
-  }, [poll]);
-
-  const voteDisabled =
-    disabled || Object.values(selected).every((item) => !item);
-
-  // Event handlers
-  const handleVote = useCallback(() => {
-    if (voteDisabled) {
-      return;
-    }
-
-    if (identity.signedIn) {
-      void dispatch(vote({ pollId, choices: Object.keys(selected) }));
-    } else {
-      dispatch(
-        openModal({
-          modalType: 'INTERACTION',
-          modalProps: {
-            type: 'vote',
-            accountId: status.getIn(['account', 'id']),
-            url: status.get('uri'),
-          },
-        }),
-      );
-    }
-  }, [voteDisabled, dispatch, identity, pollId, selected, status]);
-
-  const handleReveal = useCallback(() => {
-    setRevealed(true);
-  }, []);
-
-  const handleRefresh = useCallback(() => {
-    if (disabled) {
-      return;
-    }
-    void dispatch(fetchPoll({ pollId }));
-  }, [disabled, dispatch, pollId]);
-
-  const handleOptionChange = useCallback(
-    (choiceIndex: number) => {
-      if (!poll) {
-        return;
-      }
-      if (poll.multiple) {
-        setSelected((prev) => ({
-          ...prev,
-          [choiceIndex]: !prev[choiceIndex],
-        }));
-      } else {
-        setSelected({ [choiceIndex]: true });
-      }
-    },
-    [poll],
-  );
-
-  if (!poll) {
-    return null;
-  }
-  const showResults = poll.voted || revealed || expired;
-
-  return (
-    <div className='poll'>
-      <ul>
-        {poll.options.map((option, i) => (
-          <PollOption
-            key={option.title || i}
-            index={i}
-            poll={poll}
-            option={option}
-            showResults={showResults}
-            active={!!selected[i]}
-            onChange={handleOptionChange}
-          />
-        ))}
-      </ul>
-
-      <div className='poll__footer'>
-        {!showResults && (
-          <button
-            className='button button-secondary'
-            disabled={voteDisabled}
-            onClick={handleVote}
-          >
-            <FormattedMessage id='poll.vote' defaultMessage='Vote' />
-          </button>
-        )}
-        {!showResults && (
-          <>
-            <button className='poll__link' onClick={handleReveal}>
-              <FormattedMessage id='poll.reveal' defaultMessage='See results' />
-            </button>{' '}
-            ·{' '}
-          </>
-        )}
-        {showResults && !disabled && (
-          <>
-            <button className='poll__link' onClick={handleRefresh}>
-              <FormattedMessage id='poll.refresh' defaultMessage='Refresh' />
-            </button>{' '}
-            ·{' '}
-          </>
-        )}
-        {votesCount}
-        {poll.expires_at && <> · {timeRemaining}</>}
-      </div>
-    </div>
-  );
-};
-
-type PollOptionProps = Pick<PollProps, 'disabled' | 'lang'> & {
-  active: boolean;
-  onChange: (index: number) => void;
-  poll: Model.Poll;
-  option: Model.PollOption;
-  index: number;
-  showResults?: boolean;
-};
-
-const PollOption: React.FC<PollOptionProps> = (props) => {
-  const { active, lang, disabled, poll, option, index, showResults, onChange } =
-    props;
-  const voted = option.voted || poll.own_votes?.includes(index);
-  const title = option.translation?.title ?? option.title;
-
-  const intl = useIntl();
-
-  // Derived values
-  const percent = useMemo(() => {
-    const pollVotesCount = poll.voters_count ?? poll.votes_count;
-    return pollVotesCount === 0
-      ? 0
-      : (option.votes_count / pollVotesCount) * 100;
-  }, [option, poll]);
-  const isLeading = useMemo(
-    () =>
-      poll.options
-        .filter((other) => other.title !== option.title)
-        .every((other) => option.votes_count >= other.votes_count),
-    [poll, option],
-  );
-  const titleHtml = useMemo(() => {
-    let titleHtml = option.translation?.titleHtml ?? option.titleHtml;
-
-    if (!titleHtml) {
-      const emojiMap = makeEmojiMap(poll.emojis);
-      titleHtml = emojify(escapeTextContentForBrowser(title), emojiMap);
-    }
-
-    return titleHtml;
-  }, [option, poll, title]);
-
-  // Handlers
-  const handleOptionChange = useCallback(() => {
-    onChange(index);
-  }, [index, onChange]);
-  const handleOptionKeyPress: KeyboardEventHandler = useCallback(
-    (event) => {
-      if (event.key === 'Enter' || event.key === ' ') {
-        onChange(index);
-        event.stopPropagation();
-        event.preventDefault();
-      }
-    },
-    [index, onChange],
-  );
-
-  const widthSpring = useSpring({
-    from: {
-      width: '0%',
-    },
-    to: {
-      width: `${percent}%`,
-    },
-    immediate: reduceMotion,
-  });
-
-  return (
-    <li>
-      <label
-        className={classNames('poll__option', { selectable: !showResults })}
-      >
-        <input
-          name='vote-options'
-          type={poll.multiple ? 'checkbox' : 'radio'}
-          value={index}
-          checked={active}
-          onChange={handleOptionChange}
-          disabled={disabled}
-        />
-
-        {!showResults && (
-          <span
-            className={classNames('poll__input', {
-              checkbox: poll.multiple,
-              active,
-            })}
-            tabIndex={0}
-            role={poll.multiple ? 'checkbox' : 'radio'}
-            onKeyDown={handleOptionKeyPress}
-            aria-checked={active}
-            aria-label={title}
-            lang={lang}
-            data-index={index}
-          />
-        )}
-        {showResults && (
-          <span
-            className='poll__number'
-            title={intl.formatMessage(messages.votes, {
-              votes: option.votes_count,
-            })}
-          >
-            {Math.round(percent)}%
-          </span>
-        )}
-
-        <span
-          className='poll__option__text translate'
-          lang={lang}
-          dangerouslySetInnerHTML={{ __html: titleHtml }}
-        />
-
-        {!!voted && (
-          <span className='poll__voted'>
-            <Icon
-              id='check'
-              icon={CheckIcon}
-              className='poll__voted__mark'
-              title={intl.formatMessage(messages.voted)}
-            />
-          </span>
-        )}
-      </label>
-
-      {showResults && (
-        <animated.span
-          className={classNames('poll__chart', { leading: isLeading })}
-          style={widthSpring}
-        />
-      )}
-    </li>
-  );
-};
diff --git a/app/javascript/mastodon/components/regeneration_indicator.jsx b/app/javascript/mastodon/components/regeneration_indicator.jsx
new file mode 100644
index 0000000000..d42a7d7c72
--- /dev/null
+++ b/app/javascript/mastodon/components/regeneration_indicator.jsx
@@ -0,0 +1,18 @@
+import { FormattedMessage } from 'react-intl';
+
+import illustration from '@/images/elephant_ui_working.svg';
+
+const RegenerationIndicator = () => (
+  <div className='regeneration-indicator'>
+    <div className='regeneration-indicator__figure'>
+      <img src={illustration} alt='' />
+    </div>
+
+    <div className='regeneration-indicator__label'>
+      <FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading&hellip;' />
+      <FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
+    </div>
+  </div>
+);
+
+export default RegenerationIndicator;
diff --git a/app/javascript/mastodon/components/regeneration_indicator.tsx b/app/javascript/mastodon/components/regeneration_indicator.tsx
deleted file mode 100644
index e26b93eb4f..0000000000
--- a/app/javascript/mastodon/components/regeneration_indicator.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-import { GIF } from './gif';
-
-export const RegenerationIndicator: React.FC = () => (
-  <div className='regeneration-indicator'>
-    <GIF
-      src='/loading.gif'
-      staticSrc='/loading.png'
-      className='regeneration-indicator__figure'
-    />
-
-    <div className='regeneration-indicator__label'>
-      <strong>
-        <FormattedMessage
-          id='regeneration_indicator.preparing_your_home_feed'
-          defaultMessage='Preparing your home feed…'
-        />
-      </strong>
-      <FormattedMessage
-        id='regeneration_indicator.please_stand_by'
-        defaultMessage='Please stand by.'
-      />
-    </div>
-  </div>
-);
diff --git a/app/javascript/mastodon/components/relative_timestamp.tsx b/app/javascript/mastodon/components/relative_timestamp.tsx
index 6253525091..ac385e88c6 100644
--- a/app/javascript/mastodon/components/relative_timestamp.tsx
+++ b/app/javascript/mastodon/components/relative_timestamp.tsx
@@ -1,6 +1,6 @@
 import { Component } from 'react';
 
-import type { MessageDescriptor, PrimitiveType, IntlShape } from 'react-intl';
+import type { IntlShape } from 'react-intl';
 import { injectIntl, defineMessages } from 'react-intl';
 
 const messages = defineMessages({
@@ -102,13 +102,7 @@ const getUnitDelay = (units: string) => {
 };
 
 export const timeAgoString = (
-  intl: {
-    formatDate: IntlShape['formatDate'];
-    formatMessage: (
-      { id, defaultMessage }: MessageDescriptor,
-      values?: Record<string, PrimitiveType>,
-    ) => string;
-  },
+  intl: Pick<IntlShape, 'formatDate' | 'formatMessage'>,
   date: Date,
   now: number,
   year: number,
diff --git a/app/javascript/mastodon/components/remote_hint.tsx b/app/javascript/mastodon/components/remote_hint.tsx
deleted file mode 100644
index 772aa805db..0000000000
--- a/app/javascript/mastodon/components/remote_hint.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-import { useAppSelector } from 'mastodon/store';
-
-import { TimelineHint } from './timeline_hint';
-
-interface RemoteHintProps {
-  accountId?: string;
-}
-
-export const RemoteHint: React.FC<RemoteHintProps> = ({ accountId }) => {
-  const account = useAppSelector((state) =>
-    accountId ? state.accounts.get(accountId) : undefined,
-  );
-  const domain = account?.acct ? account.acct.split('@')[1] : undefined;
-  if (
-    !account ||
-    !account.url ||
-    account.acct !== account.username ||
-    !domain
-  ) {
-    return null;
-  }
-
-  return (
-    <TimelineHint
-      url={account.url}
-      message={
-        <FormattedMessage
-          id='hints.profiles.posts_may_be_missing'
-          defaultMessage='Some posts from this profile may be missing.'
-        />
-      }
-      label={
-        <FormattedMessage
-          id='hints.profiles.see_more_posts'
-          defaultMessage='See more posts on {domain}'
-          values={{ domain: <strong>{domain}</strong> }}
-        />
-      }
-    />
-  );
-};
diff --git a/app/javascript/mastodon/components/router.tsx b/app/javascript/mastodon/components/router.tsx
index 815b4b59ab..558d0307e7 100644
--- a/app/javascript/mastodon/components/router.tsx
+++ b/app/javascript/mastodon/components/router.tsx
@@ -1,5 +1,5 @@
 import type { PropsWithChildren } from 'react';
-import type React from 'react';
+import React from 'react';
 
 import { Router as OriginalRouter, useHistory } from 'react-router';
 
diff --git a/app/javascript/mastodon/components/scrollable_list.jsx b/app/javascript/mastodon/components/scrollable_list.jsx
index 93ed201a07..d766366869 100644
--- a/app/javascript/mastodon/components/scrollable_list.jsx
+++ b/app/javascript/mastodon/components/scrollable_list.jsx
@@ -80,8 +80,6 @@ class ScrollableList extends PureComponent {
     children: PropTypes.node,
     bindToDocument: PropTypes.bool,
     preventScroll: PropTypes.bool,
-    footer: PropTypes.node,
-    className: PropTypes.string,
   };
 
   static defaultProps = {
@@ -326,7 +324,7 @@ class ScrollableList extends PureComponent {
   };
 
   render () {
-    const { children, scrollKey, className, trackScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, footer, emptyMessage, onLoadMore } = this.props;
+    const { children, scrollKey, trackScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, emptyMessage, onLoadMore } = this.props;
     const { fullscreen } = this.state;
     const childrenCount = Children.count(children);
 
@@ -337,23 +335,21 @@ class ScrollableList extends PureComponent {
     if (showLoading) {
       scrollableArea = (
         <div className='scrollable scrollable--flex' ref={this.setRef}>
-          {prepend}
-
-          <div role='feed' className='item-list' />
+          <div role='feed' className='item-list'>
+            {prepend}
+          </div>
 
           <div className='scrollable__append'>
             <LoadingIndicator />
           </div>
-
-          {footer}
         </div>
       );
     } else if (isLoading || childrenCount > 0 || numPending > 0 || hasMore || !emptyMessage) {
       scrollableArea = (
-        <div className={classNames('scrollable scrollable--flex', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
-          {prepend}
+        <div className={classNames('scrollable', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
+          <div role='feed' className='item-list'>
+            {prepend}
 
-          <div role='feed' className={classNames('item-list', className)}>
             {loadPending}
 
             {Children.map(this.props.children, (child, index) => (
@@ -379,8 +375,6 @@ class ScrollableList extends PureComponent {
 
             {!hasMore && append}
           </div>
-
-          {footer}
         </div>
       );
     } else {
@@ -391,8 +385,6 @@ class ScrollableList extends PureComponent {
           <div className='empty-column-indicator'>
             {emptyMessage}
           </div>
-
-          {footer}
         </div>
       );
     }
diff --git a/app/javascript/mastodon/components/server_banner.jsx b/app/javascript/mastodon/components/server_banner.jsx
index 989ac7f006..baa220af5e 100644
--- a/app/javascript/mastodon/components/server_banner.jsx
+++ b/app/javascript/mastodon/components/server_banner.jsx
@@ -8,10 +8,10 @@ import { Link } from 'react-router-dom';
 import { connect } from 'react-redux';
 
 import { fetchServer } from 'mastodon/actions/server';
-import { Account } from 'mastodon/components/account';
 import { ServerHeroImage } from 'mastodon/components/server_hero_image';
 import { ShortNumber } from 'mastodon/components/short_number';
 import { Skeleton } from 'mastodon/components/skeleton';
+import Account from 'mastodon/containers/account_container';
 import { domain } from 'mastodon/initial_state';
 
 const messages = defineMessages({
@@ -42,7 +42,7 @@ class ServerBanner extends PureComponent {
     return (
       <div className='server-banner'>
         <div className='server-banner__introduction'>
-          <FormattedMessage id='server_banner.is_one_of_many' defaultMessage='{domain} is one of the many independent Mastodon servers you can use to participate in the fediverse.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank' rel='noopener'>Mastodon</a> }} />
+          <FormattedMessage id='server_banner.is_one_of_many' defaultMessage='{domain} is one of the many independent Mastodon servers you can use to participate in the fediverse.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank'>Mastodon</a> }} />
         </div>
 
         <Link to='/about'>
diff --git a/app/javascript/mastodon/components/short_number.tsx b/app/javascript/mastodon/components/short_number.tsx
index 5702d1a2e4..1a99e232b7 100644
--- a/app/javascript/mastodon/components/short_number.tsx
+++ b/app/javascript/mastodon/components/short_number.tsx
@@ -1,5 +1,4 @@
 import { memo } from 'react';
-import type { JSX } from 'react';
 
 import { FormattedMessage, FormattedNumber } from 'react-intl';
 
diff --git a/app/javascript/mastodon/components/spoiler_button.tsx b/app/javascript/mastodon/components/spoiler_button.tsx
deleted file mode 100644
index bf84ffd04d..0000000000
--- a/app/javascript/mastodon/components/spoiler_button.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-import classNames from 'classnames';
-
-interface Props {
-  hidden?: boolean;
-  sensitive: boolean;
-  uncached?: boolean;
-  matchedFilters?: string[];
-  onClick: React.MouseEventHandler<HTMLButtonElement>;
-}
-
-export const SpoilerButton: React.FC<Props> = ({
-  hidden = false,
-  sensitive,
-  uncached = false,
-  matchedFilters,
-  onClick,
-}) => {
-  let warning;
-  let action;
-
-  if (uncached) {
-    warning = (
-      <FormattedMessage
-        id='status.uncached_media_warning'
-        defaultMessage='Preview not available'
-      />
-    );
-    action = (
-      <FormattedMessage id='status.media.open' defaultMessage='Click to open' />
-    );
-  } else if (matchedFilters) {
-    warning = (
-      <FormattedMessage
-        id='filter_warning.matches_filter'
-        defaultMessage='Matches filter “<span>{title}</span>”'
-        values={{
-          title: matchedFilters.join(', '),
-          span: (chunks) => <span className='filter-name'>{chunks}</span>,
-        }}
-      />
-    );
-    action = (
-      <FormattedMessage id='status.media.show' defaultMessage='Click to show' />
-    );
-  } else if (sensitive) {
-    warning = (
-      <FormattedMessage
-        id='status.sensitive_warning'
-        defaultMessage='Sensitive content'
-      />
-    );
-    action = (
-      <FormattedMessage id='status.media.show' defaultMessage='Click to show' />
-    );
-  } else {
-    warning = (
-      <FormattedMessage
-        id='status.media_hidden'
-        defaultMessage='Media hidden'
-      />
-    );
-    action = (
-      <FormattedMessage id='status.media.show' defaultMessage='Click to show' />
-    );
-  }
-
-  return (
-    <div
-      className={classNames('spoiler-button', {
-        'spoiler-button--hidden': hidden,
-        'spoiler-button--click-thru': uncached,
-      })}
-    >
-      <button
-        type='button'
-        className='spoiler-button__overlay'
-        onClick={onClick}
-        disabled={uncached}
-      >
-        <span className='spoiler-button__overlay__label'>
-          {warning}
-          <span className='spoiler-button__overlay__action'>{action}</span>
-        </span>
-      </button>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx
index 0efea48f87..ad13229382 100644
--- a/app/javascript/mastodon/components/status.jsx
+++ b/app/javascript/mastodon/components/status.jsx
@@ -3,8 +3,6 @@ import PropTypes from 'prop-types';
 import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
 
 import classNames from 'classnames';
-import { Link } from 'react-router-dom';
-
 
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -77,7 +75,7 @@ export const defaultMediaVisibility = (status) => {
     status = status.get('reblog');
   }
 
-  return !status.get('matched_media_filters') && (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
+  return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
 };
 
 const messages = defineMessages({
@@ -94,6 +92,7 @@ class Status extends ImmutablePureComponent {
     account: ImmutablePropTypes.record,
     contextType: PropTypes.string,
     previousId: PropTypes.string,
+    nextInReplyToId: PropTypes.string,
     rootId: PropTypes.string,
     onClick: PropTypes.func,
     onReply: PropTypes.func,
@@ -175,23 +174,32 @@ class Status extends ImmutablePureComponent {
   };
 
   handleClick = e => {
-    e.preventDefault();
-
-    if (e?.button === 0 && !(e?.ctrlKey || e?.metaKey)) {
-      this._openStatus();
-    } else if (e?.button === 1 || (e?.button === 0 && (e?.ctrlKey || e?.metaKey))) {
-      this._openStatus(true);
-    }
-  };
-
-  handleMouseUp = e => {
-    // Only handle clicks on the empty space above the content
-
-    if (e.target !== e.currentTarget && e.detail >= 1) {
+    if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
       return;
     }
 
-    this.handleClick(e);
+    if (e) {
+      e.preventDefault();
+    }
+
+    this.handleHotkeyOpen();
+  };
+
+  handlePrependAccountClick = e => {
+    this.handleAccountClick(e, false);
+  };
+
+  handleAccountClick = (e, proper = true) => {
+    if (e && (e.button !== 0 || e.ctrlKey || e.metaKey))  {
+      return;
+    }
+
+    if (e) {
+      e.preventDefault();
+      e.stopPropagation();
+    }
+
+    this._openProfile(proper);
   };
 
   handleExpandedToggle = () => {
@@ -290,10 +298,6 @@ class Status extends ImmutablePureComponent {
   };
 
   handleHotkeyOpen = () => {
-    this._openStatus();
-  };
-
-  _openStatus = (newTab = false) => {
     if (this.props.onClick) {
       this.props.onClick();
       return;
@@ -306,13 +310,7 @@ class Status extends ImmutablePureComponent {
       return;
     }
 
-    const path = `/@${status.getIn(['account', 'acct'])}/${status.get('id')}`;
-
-    if (newTab) {
-      window.open(path, '_blank', 'noopener');
-    } else {
-      history.push(path);
-    }
+    history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
   };
 
   handleHotkeyOpenProfile = () => {
@@ -342,7 +340,7 @@ class Status extends ImmutablePureComponent {
     const { onToggleHidden } = this.props;
     const status = this._properStatus();
 
-    if (this.props.status.get('matched_filters')) {
+    if (status.get('matched_filters')) {
       const expandedBecauseOfCW = !status.get('hidden') || status.get('spoiler_text').length === 0;
       const expandedBecauseOfFilter = this.state.showDespiteFilter;
 
@@ -382,7 +380,7 @@ class Status extends ImmutablePureComponent {
   };
 
   render () {
-    const { intl, hidden, featured, unfocusable, unread, showThread, scrollKey, pictureInPicture, previousId, rootId, withoutQuote, skipPrepend, avatarSize = 46 } = this.props;
+    const { intl, hidden, featured, unfocusable, unread, showThread, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId, withoutQuote, skipPrepend, avatarSize = 46 } = this.props;
 
     let { status, account, ...other } = this.props;
     
@@ -404,13 +402,24 @@ class Status extends ImmutablePureComponent {
       toggleHidden: this.handleHotkeyToggleHidden,
       toggleSensitive: this.handleHotkeyToggleSensitive,
       openMedia: this.handleHotkeyOpenMedia,
-      onTranslate: this.handleTranslate,
     };
 
     let media, statusAvatar, prepend, rebloggedByText;
 
+    if (hidden) {
+      return (
+        <HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
+          <div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={unfocusable ? null : 0}>
+            <span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
+            <span>{status.get('content')}</span>
+          </div>
+        </HotKeys>
+      );
+    }
+
     const connectUp = previousId && previousId === status.get('in_reply_to_id');
     const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
+    const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
     const matchedFilters = status.get('matched_filters');
 
     let visibilityName = status.get('limited_scope') || status.get('visibility_ex') || status.get('visibility');
@@ -429,7 +438,7 @@ class Status extends ImmutablePureComponent {
         <div className='status__prepend'>
           <div className='status__prepend__icon'><Icon id='retweet' icon={RepeatIcon} /></div>
           <div className='status__prepend__icon'><VisibilityIcon visibility={visibilityName} /></div>
-          <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <Link data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} to={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></Link> }} />
+          <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
         </div>
       );
 
@@ -451,24 +460,10 @@ class Status extends ImmutablePureComponent {
     }
 
     if (account === undefined || account === null) {
-      statusAvatar = <Avatar account={status.get('account')} size={avatarSize} />;
+      statusAvatar = <Avatar account={status.get('account')} size={46} />;
     } else {
       statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
     }
-    
-    const expanded = (!matchedFilters || this.state.showDespiteFilter) && (!status.get('hidden') || status.get('spoiler_text').length === 0);
-
-    if (hidden) {
-      return (
-        <HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
-          <div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={unfocusable ? null : 0}>
-            <span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
-            {status.get('spoiler_text').length > 0 && (<span>{status.get('spoiler_text')}</span>)}
-            {expanded && <span>{status.get('content')}</span>}
-          </div>
-        </HotKeys>
-      );
-    }
 
     if (pictureInPicture.get('inUse')) {
       media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
@@ -496,7 +491,6 @@ class Status extends ImmutablePureComponent {
                 defaultWidth={this.props.cachedMediaWidth}
                 visible={this.state.showMedia}
                 onToggleVisibility={this.handleToggleMediaVisibility}
-                matchedFilters={status.get('matched_media_filters')}
               />
             )}
           </Bundle>
@@ -525,7 +519,6 @@ class Status extends ImmutablePureComponent {
                 blurhash={attachment.get('blurhash')}
                 visible={this.state.showMedia}
                 onToggleVisibility={this.handleToggleMediaVisibility}
-                matchedFilters={status.get('matched_media_filters')}
               />
             )}
           </Bundle>
@@ -550,7 +543,6 @@ class Status extends ImmutablePureComponent {
                 deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
                 visible={this.state.showMedia}
                 onToggleVisibility={this.handleToggleMediaVisibility}
-                matchedFilters={status.get('matched_media_filters')}
               />
             )}
           </Bundle>
@@ -579,7 +571,14 @@ class Status extends ImmutablePureComponent {
       }
     }
 
+    if (account === undefined || account === null) {
+      statusAvatar = <Avatar account={status.get('account')} size={avatarSize} />;
+    } else {
+      statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
+    }
+
     const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
+    const expanded = (!matchedFilters || this.state.showDespiteFilter) && (!status.get('hidden') || status.get('spoiler_text').length === 0);
 
     const withLimited = status.get('visibility_ex') === 'limited' && status.get('limited_scope') ? <span className='status__visibility-icon'><Icon id='get-pocket' icon={LimitedIcon} title={intl.formatMessage(messages.limited_short)} /></span> : null;
     const withQuote = status.get('quote_id') ? <span className='status__visibility-icon'><Icon id='quote-right' icon={QuoteIcon} title='Quote' /></span> : null;
@@ -595,25 +594,30 @@ class Status extends ImmutablePureComponent {
 
           <div className={classNames('status', `status-${status.get('visibility_ex')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
 
-            {(!matchedFilters || expanded || isShowItem('avatar_on_filter')) && (
-              <div onMouseUp={this.handleMouseUp} className='status__info'>
-                <Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time'>
-                  {withQuote}
-                  {withReference}
-                  {withExpiration}
-                  {withLimited}
-                  <span className='status__visibility-icon'><VisibilityIcon visibility={visibilityName} /></span>
-                  <RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
-                </Link>
+            {(!matchedFilters || this.state.showDespiteFilter || status.get('filter_action_ex') === 'half_warn') && (
+              <>
+                {(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
 
-                <Link to={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} data-hover-card-account={status.getIn(['account', 'id'])} className='status__display-name'>
-                  <div className='status__avatar'>
-                    {statusAvatar}
-                  </div>
+                {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
+                <div onClick={this.handleClick} className='status__info'>
+                  <a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
+                    {withQuote}
+                    {withReference}
+                    {withExpiration}
+                    {withLimited}
+                    <span className='status__visibility-icon'><VisibilityIcon visibility={visibilityName} /></span>
+                    <RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
+                  </a>
 
-                  <DisplayName account={status.get('account')} />
-                </Link>
-              </div>
+                  <a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} data-hover-card-account={status.getIn(['account', 'id'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
+                    <div className='status__avatar'>
+                      {statusAvatar}
+                    </div>
+
+                    <DisplayName account={status.get('account')} />
+                  </a>
+                </div>
+              </>
             )}
 
             {matchedFilters && <FilterWarning title={matchedFilters.join(', ')} expanded={this.state.showDespiteFilter} onClick={this.handleFilterToggle} />}
diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx
index 4721e9da93..2f636ddbd9 100644
--- a/app/javascript/mastodon/components/status_action_bar.jsx
+++ b/app/javascript/mastodon/components/status_action_bar.jsx
@@ -25,8 +25,9 @@ import { identityContextPropShape, withIdentity } from 'mastodon/identity_contex
 import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
 import { WithRouterPropTypes } from 'mastodon/utils/react_router';
 
+
+import DropdownMenuContainer from '../containers/dropdown_menu_container';
 import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dropdown_container';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
 import { enableEmojiReaction , bookmarkCategoryNeeded, simpleTimelineMenu, me, isHideItem, boostMenu, boostModal } from '../initial_state';
 
 import { IconButton } from './icon_button';
@@ -52,7 +53,6 @@ const messages = defineMessages({
   cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
   cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
   favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
-  removeFavourite: { id: 'status.remove_favourite', defaultMessage: 'Remove from favorites' },
   emojiReaction: { id: 'status.emoji_reaction', defaultMessage: 'Emoji reaction' },
   bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
   bookmarkCategory: { id: 'status.bookmark_category', defaultMessage: 'Bookmark category' },
@@ -335,22 +335,28 @@ class StatusActionBar extends ImmutablePureComponent {
       }
 
       if (!boostMenu) {
-        if (publicStatus && allowQuote && (account.getIn(['server_features', 'quote']) || !isHideItem('quote_unavailable_server'))) {
-          menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote, tag: 'reblog' });
-        }
+        menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancelReblog : messages.reblog), action: this.handleReblogForceModalClick, tag: 'reblog' });
 
-        if (account.getIn(['server_features', 'status_reference']) || !isHideItem('status_reference_unavailable_server')) {
-          menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference, tag: 'reblog' });
+        if (publicStatus) {
+          if (allowQuote && (account.getIn(['server_features', 'quote']) || !isHideItem('quote_unavailable_server'))) {
+            menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote, tag: 'reblog' });
+          }
+
+          if (account.getIn(['server_features', 'status_reference']) || !isHideItem('status_reference_unavailable_server')) {
+            menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference, tag: 'reblog' });
+          }
         }
       }
 
+      menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClickOriginal });
       menu.push({ text: intl.formatMessage(messages.bookmarkCategory), action: this.handleBookmarkCategoryAdderClick });
 
       if (writtenByMe && pinnableStatus) {
         menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
-        menu.push(null);
       }
 
+      menu.push(null);
+
       if (writtenByMe || withDismiss) {
         menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
         menu.push(null);
@@ -482,9 +488,6 @@ class StatusActionBar extends ImmutablePureComponent {
       <div className='status__action-bar__button-wrapper status__action-bar__button-wrapper__blank' />
     )) || null;
 
-
-    const bookmarkTitle = intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark);
-    const favouriteTitle = intl.formatMessage(status.get('favourited') ? messages.removeFavourite : messages.favourite);
     const isReply = status.get('in_reply_to_account_id') === status.getIn(['account', 'id']);
 
     const reblogButton = <IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />;
@@ -496,7 +499,7 @@ class StatusActionBar extends ImmutablePureComponent {
         </div>
         <div className='status__action-bar__button-wrapper'>
           {reblogMenu.length === 0 ? reblogButton : (
-            <Dropdown
+            <DropdownMenuContainer
               className={classNames('status__action-bar__button', { reblogPrivate })}
               scrollKey={scrollKey}
               status={status}
@@ -507,18 +510,20 @@ class StatusActionBar extends ImmutablePureComponent {
               title={reblogTitle}
               active={status.get('reblogged')}
               disabled={!publicStatus && !reblogPrivate}
-            />
+            >
+              {reblogButton}
+            </DropdownMenuContainer>
           )}
         </div>
         <div className='status__action-bar__button-wrapper'>
-          <IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={favouriteTitle} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
+          <IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
         </div>
         <div className='status__action-bar__button-wrapper'>
-          <IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={bookmarkTitle} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
+          <IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
         </div>
         {emojiPickerDropdown}
         <div className='status__action-bar__button-wrapper'>
-          <Dropdown
+          <DropdownMenuContainer
             scrollKey={scrollKey}
             status={status}
             items={menu}
diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx
index 69f222f6b4..d9b523722b 100644
--- a/app/javascript/mastodon/components/status_content.jsx
+++ b/app/javascript/mastodon/components/status_content.jsx
@@ -11,7 +11,7 @@ import { connect } from 'react-redux';
 
 import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
 import { Icon }  from 'mastodon/components/icon';
-import { Poll } from 'mastodon/components/poll';
+import PollContainer from 'mastodon/containers/poll_container';
 import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
 import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state';
 
@@ -38,7 +38,7 @@ class TranslateButton extends PureComponent {
 
     if (translation) {
       const language     = preloadedLanguages.find(lang => lang[0] === translation.get('detected_source_language'));
-      const languageName = language ? language[1] : translation.get('detected_source_language');
+      const languageName = language ? language[2] : translation.get('detected_source_language');
       const provider     = translation.get('provider');
 
       return (
@@ -115,7 +115,6 @@ class StatusContent extends PureComponent {
       } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
         link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
         link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`);
-        link.setAttribute('data-menu-hashtag', this.props.status.getIn(['account', 'id']));
       } else {
         link.setAttribute('title', link.href);
         link.classList.add('unhandled-link');
@@ -205,8 +204,8 @@ class StatusContent extends PureComponent {
       element = element.parentNode;
     }
 
-    if (deltaX + deltaY < 5 && (e.button === 0 || e.button === 1) && e.detail >= 1 && this.props.onClick) {
-      this.props.onClick(e);
+    if (deltaX + deltaY < 5 && e.button === 0 && this.props.onClick) {
+      this.props.onClick();
     }
 
     this.startXY = null;
@@ -251,7 +250,7 @@ class StatusContent extends PureComponent {
     );
 
     const poll = !!status.get('poll') && (
-      <Poll pollId={status.get('poll')} status={status} lang={language} />
+      <PollContainer pollId={status.get('poll')} lang={language} />
     );
 
     if (this.props.onClick) {
diff --git a/app/javascript/mastodon/components/status_list.jsx b/app/javascript/mastodon/components/status_list.jsx
index 3091e2a2a0..c6cacbd2b2 100644
--- a/app/javascript/mastodon/components/status_list.jsx
+++ b/app/javascript/mastodon/components/status_list.jsx
@@ -6,7 +6,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import { debounce } from 'lodash';
 
 import { TIMELINE_GAP, TIMELINE_SUGGESTIONS } from 'mastodon/actions/timelines';
-import { RegenerationIndicator } from 'mastodon/components/regeneration_indicator';
+import RegenerationIndicator from 'mastodon/components/regeneration_indicator';
 import { InlineFollowSuggestions } from 'mastodon/features/home_timeline/components/inline_follow_suggestions';
 
 import StatusContainer from '../containers/status_container';
diff --git a/app/javascript/mastodon/containers/account_container.jsx b/app/javascript/mastodon/containers/account_container.jsx
new file mode 100644
index 0000000000..d34962fa4a
--- /dev/null
+++ b/app/javascript/mastodon/containers/account_container.jsx
@@ -0,0 +1,60 @@
+import { injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import { openModal } from 'mastodon/actions/modal';
+
+import {
+  followAccount,
+  blockAccount,
+  unblockAccount,
+  muteAccount,
+  unmuteAccount,
+} from '../actions/accounts';
+import { initMuteModal } from '../actions/mutes';
+import Account from '../components/account';
+import { makeGetAccount } from '../selectors';
+
+const makeMapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  const mapStateToProps = (state, props) => ({
+    account: getAccount(state, props.id),
+  });
+
+  return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch) => ({
+
+  onFollow (account) {
+    if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
+      dispatch(openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }));
+    } else {
+      dispatch(followAccount(account.get('id')));
+    }
+  },
+
+  onBlock (account) {
+    if (account.getIn(['relationship', 'blocking'])) {
+      dispatch(unblockAccount(account.get('id')));
+    } else {
+      dispatch(blockAccount(account.get('id')));
+    }
+  },
+
+  onMute (account) {
+    if (account.getIn(['relationship', 'muting'])) {
+      dispatch(unmuteAccount(account.get('id')));
+    } else {
+      dispatch(initMuteModal(account));
+    }
+  },
+
+
+  onMuteNotifications (account, notifications) {
+    dispatch(muteAccount(account.get('id'), notifications));
+  },
+});
+
+export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Account));
diff --git a/app/javascript/mastodon/containers/compose_container.jsx b/app/javascript/mastodon/containers/compose_container.jsx
index 4c43adbd17..788c654316 100644
--- a/app/javascript/mastodon/containers/compose_container.jsx
+++ b/app/javascript/mastodon/containers/compose_container.jsx
@@ -2,7 +2,6 @@ import { Provider } from 'react-redux';
 
 import { fetchCircles } from 'mastodon/actions/circles';
 import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
-import { fetchServer } from 'mastodon/actions/server';
 import { hydrateStore } from 'mastodon/actions/store';
 import { Router } from 'mastodon/components/router';
 import Compose from 'mastodon/features/standalone/compose';
@@ -16,7 +15,6 @@ if (initialState) {
 
 store.dispatch(fetchCustomEmojis());
 store.dispatch(fetchCircles());
-store.dispatch(fetchServer());
 
 const ComposeContainer = () => (
   <IntlProvider>
diff --git a/app/javascript/mastodon/containers/dropdown_menu_container.js b/app/javascript/mastodon/containers/dropdown_menu_container.js
new file mode 100644
index 0000000000..bc9124c041
--- /dev/null
+++ b/app/javascript/mastodon/containers/dropdown_menu_container.js
@@ -0,0 +1,43 @@
+import { connect } from 'react-redux';
+
+import { fetchRelationships } from 'mastodon/actions/accounts';
+
+import { openDropdownMenu, closeDropdownMenu } from '../actions/dropdown_menu';
+import { openModal, closeModal } from '../actions/modal';
+import DropdownMenu from '../components/dropdown_menu';
+import { isUserTouching } from '../is_mobile';
+
+/**
+ * @param {import('mastodon/store').RootState} state
+ */
+const mapStateToProps = state => ({
+  openDropdownId: state.dropdownMenu.openId,
+  openedViaKeyboard: state.dropdownMenu.keyboard,
+});
+
+const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
+  onOpen(id, onItemClick, keyboard) {
+    if (status) {
+      dispatch(fetchRelationships([status.getIn(['account', 'id'])]));
+    }
+
+    dispatch(isUserTouching() ? openModal({
+      modalType: 'ACTIONS',
+      modalProps: {
+        status,
+        actions: items,
+        onClick: onItemClick,
+      },
+    }) : openDropdownMenu({ id, keyboard, scrollKey }));
+  },
+
+  onClose(id) {
+    dispatch(closeModal({
+      modalType: 'ACTIONS',
+      ignoreFocus: false,
+    }));
+    dispatch(closeDropdownMenu({ id }));
+  },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu);
diff --git a/app/javascript/mastodon/containers/media_container.jsx b/app/javascript/mastodon/containers/media_container.jsx
index 9c07341faa..d18602e3b5 100644
--- a/app/javascript/mastodon/containers/media_container.jsx
+++ b/app/javascript/mastodon/containers/media_container.jsx
@@ -7,13 +7,12 @@ import { fromJS } from 'immutable';
 import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
 import MediaGallery from 'mastodon/components/media_gallery';
 import ModalRoot from 'mastodon/components/modal_root';
-import { Poll } from 'mastodon/components/poll';
+import Poll from 'mastodon/components/poll';
 import Audio from 'mastodon/features/audio';
 import Card from 'mastodon/features/status/components/card';
 import MediaModal from 'mastodon/features/ui/components/media_modal';
-import { Video } from 'mastodon/features/video';
+import Video from 'mastodon/features/video';
 import { IntlProvider } from 'mastodon/locales';
-import { createPollFromServerJSON } from 'mastodon/models/poll';
 import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
 
 const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
@@ -89,7 +88,7 @@ export default class MediaContainer extends PureComponent {
             Object.assign(props, {
               ...(media   ? { media:   fromJS(media)   } : {}),
               ...(card    ? { card:    fromJS(card)    } : {}),
-              ...(poll    ? { poll:    createPollFromServerJSON(poll)    } : {}),
+              ...(poll    ? { poll:    fromJS(poll)    } : {}),
               ...(hashtag ? { hashtag: fromJS(hashtag) } : {}),
 
               ...(componentName === 'Video' ? {
diff --git a/app/javascript/mastodon/containers/poll_container.js b/app/javascript/mastodon/containers/poll_container.js
new file mode 100644
index 0000000000..8482345431
--- /dev/null
+++ b/app/javascript/mastodon/containers/poll_container.js
@@ -0,0 +1,26 @@
+import { connect } from 'react-redux';
+
+import { debounce } from 'lodash';
+
+import { fetchPoll, vote } from 'mastodon/actions/polls';
+import Poll from 'mastodon/components/poll';
+
+const mapDispatchToProps = (dispatch, { pollId }) => ({
+  refresh: debounce(
+    () => {
+      dispatch(fetchPoll(pollId));
+    },
+    1000,
+    { leading: true },
+  ),
+
+  onVote (choices) {
+    dispatch(vote(pollId, choices));
+  },
+});
+
+const mapStateToProps = (state, { pollId }) => ({
+  poll: state.getIn(['polls', pollId]),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Poll);
diff --git a/app/javascript/mastodon/features/about/index.jsx b/app/javascript/mastodon/features/about/index.jsx
index 8158f47c11..bd73a2fd58 100644
--- a/app/javascript/mastodon/features/about/index.jsx
+++ b/app/javascript/mastodon/features/about/index.jsx
@@ -15,12 +15,12 @@ import DisabledIcon from '@/material-icons/400-24px/close-fill.svg?react';
 import EnabledIcon from '@/material-icons/400-24px/done-fill.svg?react';
 import ExpandMoreIcon from '@/material-icons/400-24px/expand_more.svg?react';
 import { fetchServer, fetchExtendedDescription, fetchDomainBlocks  } from 'mastodon/actions/server';
-import { Account } from 'mastodon/components/account';
 import Column from 'mastodon/components/column';
 import { Icon  }  from 'mastodon/components/icon';
 import { ServerHeroImage } from 'mastodon/components/server_hero_image';
 import { Skeleton } from 'mastodon/components/skeleton';
-import { LinkFooter} from 'mastodon/features/ui/components/link_footer';
+import Account from 'mastodon/containers/account_container';
+import LinkFooter from 'mastodon/features/ui/components/link_footer';
 
 const messages = defineMessages({
   title: { id: 'column.about', defaultMessage: 'About' },
@@ -173,7 +173,7 @@ class About extends PureComponent {
           <div className='about__header'>
             <ServerHeroImage blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} srcSet={server.getIn(['thumbnail', 'versions'])?.map((value, key) => `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' />
             <h1>{isLoading ? <Skeleton width='10ch' /> : server.get('domain')}</h1>
-            <p><FormattedMessage id='about.powered_by' defaultMessage='Decentralized social media powered by {domain}' /></p>
+            <p><FormattedMessage id='about.powered_by' defaultMessage='Decentralized social media powered by {mastodon}' values={{ mastodon: <a href='https://joinmastodon.org' className='about__mail' target='_blank'>Mastodon</a> }} /></p>
           </div>
 
           <div className='about__meta'>
diff --git a/app/javascript/mastodon/features/account/components/account_note.jsx b/app/javascript/mastodon/features/account/components/account_note.jsx
index df7312eafc..e736e7ad64 100644
--- a/app/javascript/mastodon/features/account/components/account_note.jsx
+++ b/app/javascript/mastodon/features/account/components/account_note.jsx
@@ -4,6 +4,7 @@ import { PureComponent } from 'react';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 
 import { is } from 'immutable';
+import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 import Textarea from 'react-textarea-autosize';
@@ -48,7 +49,7 @@ class InlineAlert extends PureComponent {
 class AccountNote extends ImmutablePureComponent {
 
   static propTypes = {
-    accountId: PropTypes.string.isRequired,
+    account: ImmutablePropTypes.record.isRequired,
     value: PropTypes.string,
     onSave: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
@@ -65,7 +66,7 @@ class AccountNote extends ImmutablePureComponent {
   }
 
   UNSAFE_componentWillReceiveProps (nextProps) {
-    const accountWillChange = !is(this.props.accountId, nextProps.accountId);
+    const accountWillChange = !is(this.props.account, nextProps.account);
     const newState = {};
 
     if (accountWillChange && this._isDirty()) {
@@ -101,10 +102,10 @@ class AccountNote extends ImmutablePureComponent {
     if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
       e.preventDefault();
 
+      this._save();
+
       if (this.textarea) {
         this.textarea.blur();
-      } else {
-        this._save();
       }
     } else if (e.keyCode === 27) {
       e.preventDefault();
@@ -140,21 +141,21 @@ class AccountNote extends ImmutablePureComponent {
   }
 
   render () {
-    const { accountId, intl } = this.props;
+    const { account, intl } = this.props;
     const { value, saved } = this.state;
 
-    if (!accountId) {
+    if (!account) {
       return null;
     }
 
     return (
       <div className='account__header__account-note'>
-        <label htmlFor={`account-note-${accountId}`}>
+        <label htmlFor={`account-note-${account.get('id')}`}>
           <FormattedMessage id='account.account_note_header' defaultMessage='Personal note' /> <InlineAlert show={saved} />
         </label>
 
         <Textarea
-          id={`account-note-${accountId}`}
+          id={`account-note-${account.get('id')}`}
           className='account__header__account-note__content'
           disabled={this.props.value === null || value === null}
           placeholder={intl.formatMessage(messages.placeholder)}
diff --git a/app/javascript/mastodon/features/account/components/domain_pill.jsx b/app/javascript/mastodon/features/account/components/domain_pill.jsx
new file mode 100644
index 0000000000..0dadb947f9
--- /dev/null
+++ b/app/javascript/mastodon/features/account/components/domain_pill.jsx
@@ -0,0 +1,86 @@
+import PropTypes from 'prop-types';
+import { useState, useRef, useCallback } from 'react';
+
+import { FormattedMessage } from 'react-intl';
+
+import classNames from 'classnames';
+
+import Overlay from 'react-overlays/Overlay';
+
+
+
+import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
+import BadgeIcon from '@/material-icons/400-24px/badge.svg?react';
+import GlobeIcon from '@/material-icons/400-24px/globe.svg?react';
+import { Icon } from 'mastodon/components/icon';
+
+export const DomainPill = ({ domain, username, isSelf }) => {
+  const [open, setOpen] = useState(false);
+  const [expanded, setExpanded] = useState(false);
+  const triggerRef = useRef(null);
+
+  const handleClick = useCallback(() => {
+    setOpen(!open);
+  }, [open, setOpen]);
+
+  const handleExpandClick = useCallback(() => {
+    setExpanded(!expanded);
+  }, [expanded, setExpanded]);
+
+  return (
+    <>
+      <button className={classNames('account__domain-pill', { active: open })} ref={triggerRef} onClick={handleClick}>{domain}</button>
+
+      <Overlay show={open} rootClose onHide={handleClick} offset={[5, 5]} target={triggerRef}>
+        {({ props }) => (
+          <div {...props} className='account__domain-pill__popout dropdown-animation'>
+            <div className='account__domain-pill__popout__header'>
+              <div className='account__domain-pill__popout__header__icon'><Icon icon={BadgeIcon} /></div>
+              <h3><FormattedMessage id='domain_pill.whats_in_a_handle' defaultMessage="What's in a handle?" /></h3>
+            </div>
+
+            <div className='account__domain-pill__popout__handle'>
+              <div className='account__domain-pill__popout__handle__label'>{isSelf ? <FormattedMessage id='domain_pill.your_handle' defaultMessage='Your handle:' /> : <FormattedMessage id='domain_pill.their_handle' defaultMessage='Their handle:' />}</div>
+              <div className='account__domain-pill__popout__handle__handle'>@{username}@{domain}</div>
+            </div>
+
+            <div className='account__domain-pill__popout__parts'>
+              <div>
+                <div className='account__domain-pill__popout__parts__icon'><Icon icon={AlternateEmailIcon} /></div>
+
+                <div>
+                  <h6><FormattedMessage id='domain_pill.username' defaultMessage='Username' /></h6>
+                  <p>{isSelf ? <FormattedMessage id='domain_pill.your_username' defaultMessage='Your unique identifier on this server. It’s possible to find users with the same username on different servers.' /> : <FormattedMessage id='domain_pill.their_username' defaultMessage='Their unique identifier on their server. It’s possible to find users with the same username on different servers.' />}</p>
+                </div>
+              </div>
+
+              <div>
+                <div className='account__domain-pill__popout__parts__icon'><Icon icon={GlobeIcon} /></div>
+
+                <div>
+                  <h6><FormattedMessage id='domain_pill.server' defaultMessage='Server' /></h6>
+                  <p>{isSelf ? <FormattedMessage id='domain_pill.your_server' defaultMessage='Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers, too.' /> : <FormattedMessage id='domain_pill.their_server' defaultMessage='Their digital home, where all of their posts live.' />}</p>
+                </div>
+              </div>
+            </div>
+
+            <p>{isSelf ? <FormattedMessage id='domain_pill.who_you_are' defaultMessage='Because your handle says who you are and where you are, people can interact with you across the social web of <button>ActivityPub-powered platforms</button>.' values={{ button: x => <button onClick={handleExpandClick} className='link-button'>{x}</button> }} /> : <FormattedMessage id='domain_pill.who_they_are' defaultMessage='Since handles say who someone is and where they are, you can interact with people across the social web of <button>ActivityPub-powered platforms</button>.' values={{ button: x => <button onClick={handleExpandClick} className='link-button'>{x}</button> }} />}</p>
+
+            {expanded && (
+              <>
+                <p><FormattedMessage id='domain_pill.activitypub_like_language' defaultMessage='ActivityPub is like the language Mastodon speaks with other social networks.' /></p>
+                <p><FormattedMessage id='domain_pill.activitypub_lets_connect' defaultMessage='It lets you connect and interact with people not just on Mastodon, but across different social apps too.' /></p>
+              </>
+            )}
+          </div>
+        )}
+      </Overlay>
+    </>
+  );
+};
+
+DomainPill.propTypes = {
+  username: PropTypes.string.isRequired,
+  domain: PropTypes.string.isRequired,
+  isSelf: PropTypes.bool,
+};
diff --git a/app/javascript/mastodon/features/account/components/domain_pill.tsx b/app/javascript/mastodon/features/account/components/domain_pill.tsx
deleted file mode 100644
index c60397448b..0000000000
--- a/app/javascript/mastodon/features/account/components/domain_pill.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import { useState, useRef, useCallback, useId } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import classNames from 'classnames';
-
-import Overlay from 'react-overlays/Overlay';
-
-import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
-import BadgeIcon from '@/material-icons/400-24px/badge.svg?react';
-import GlobeIcon from '@/material-icons/400-24px/globe.svg?react';
-import { Icon } from 'mastodon/components/icon';
-
-export const DomainPill: React.FC<{
-  domain: string;
-  username: string;
-  isSelf: boolean;
-}> = ({ domain, username, isSelf }) => {
-  const accessibilityId = useId();
-  const [open, setOpen] = useState(false);
-  const [expanded, setExpanded] = useState(false);
-  const triggerRef = useRef(null);
-
-  const handleClick = useCallback(() => {
-    setOpen(!open);
-  }, [open, setOpen]);
-
-  const handleExpandClick = useCallback(() => {
-    setExpanded(!expanded);
-  }, [expanded, setExpanded]);
-
-  return (
-    <>
-      <button
-        className={classNames('account__domain-pill', { active: open })}
-        ref={triggerRef}
-        onClick={handleClick}
-        aria-expanded={open}
-        aria-controls={accessibilityId}
-      >
-        {domain}
-      </button>
-
-      <Overlay
-        show={open}
-        rootClose
-        onHide={handleClick}
-        offset={[5, 5]}
-        target={triggerRef}
-      >
-        {({ props }) => (
-          <div
-            {...props}
-            role='region'
-            id={accessibilityId}
-            className='account__domain-pill__popout dropdown-animation'
-          >
-            <div className='account__domain-pill__popout__header'>
-              <div className='account__domain-pill__popout__header__icon'>
-                <Icon id='' icon={BadgeIcon} />
-              </div>
-              <h3>
-                <FormattedMessage
-                  id='domain_pill.whats_in_a_handle'
-                  defaultMessage="What's in a handle?"
-                />
-              </h3>
-            </div>
-
-            <div className='account__domain-pill__popout__handle'>
-              <div className='account__domain-pill__popout__handle__label'>
-                {isSelf ? (
-                  <FormattedMessage
-                    id='domain_pill.your_handle'
-                    defaultMessage='Your handle:'
-                  />
-                ) : (
-                  <FormattedMessage
-                    id='domain_pill.their_handle'
-                    defaultMessage='Their handle:'
-                  />
-                )}
-              </div>
-              <div className='account__domain-pill__popout__handle__handle'>
-                @{username}@{domain}
-              </div>
-            </div>
-
-            <div className='account__domain-pill__popout__parts'>
-              <div>
-                <div className='account__domain-pill__popout__parts__icon'>
-                  <Icon id='' icon={AlternateEmailIcon} />
-                </div>
-
-                <div>
-                  <h6>
-                    <FormattedMessage
-                      id='domain_pill.username'
-                      defaultMessage='Username'
-                    />
-                  </h6>
-                  <p>
-                    {isSelf ? (
-                      <FormattedMessage
-                        id='domain_pill.your_username'
-                        defaultMessage='Your unique identifier on this server. It’s possible to find users with the same username on different servers.'
-                      />
-                    ) : (
-                      <FormattedMessage
-                        id='domain_pill.their_username'
-                        defaultMessage='Their unique identifier on their server. It’s possible to find users with the same username on different servers.'
-                      />
-                    )}
-                  </p>
-                </div>
-              </div>
-
-              <div>
-                <div className='account__domain-pill__popout__parts__icon'>
-                  <Icon id='' icon={GlobeIcon} />
-                </div>
-
-                <div>
-                  <h6>
-                    <FormattedMessage
-                      id='domain_pill.server'
-                      defaultMessage='Server'
-                    />
-                  </h6>
-                  <p>
-                    {isSelf ? (
-                      <FormattedMessage
-                        id='domain_pill.your_server'
-                        defaultMessage='Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers, too.'
-                      />
-                    ) : (
-                      <FormattedMessage
-                        id='domain_pill.their_server'
-                        defaultMessage='Their digital home, where all of their posts live.'
-                      />
-                    )}
-                  </p>
-                </div>
-              </div>
-            </div>
-
-            <p>
-              {isSelf ? (
-                <FormattedMessage
-                  id='domain_pill.who_you_are'
-                  defaultMessage='Because your handle says who you are and where you are, people can interact with you across the social web of <button>ActivityPub-powered platforms</button>.'
-                  values={{
-                    button: (x) => (
-                      <button
-                        onClick={handleExpandClick}
-                        className='link-button'
-                      >
-                        {x}
-                      </button>
-                    ),
-                  }}
-                />
-              ) : (
-                <FormattedMessage
-                  id='domain_pill.who_they_are'
-                  defaultMessage='Since handles say who someone is and where they are, you can interact with people across the social web of <button>ActivityPub-powered platforms</button>.'
-                  values={{
-                    button: (x) => (
-                      <button
-                        onClick={handleExpandClick}
-                        className='link-button'
-                      >
-                        {x}
-                      </button>
-                    ),
-                  }}
-                />
-              )}
-            </p>
-
-            {expanded && (
-              <>
-                <p>
-                  <FormattedMessage
-                    id='domain_pill.activitypub_like_language'
-                    defaultMessage='ActivityPub is like the language Mastodon speaks with other social networks.'
-                  />
-                </p>
-                <p>
-                  <FormattedMessage
-                    id='domain_pill.activitypub_lets_connect'
-                    defaultMessage='It lets you connect and interact with people not just on Mastodon, but across different social apps too.'
-                  />
-                </p>
-              </>
-            )}
-          </div>
-        )}
-      </Overlay>
-    </>
-  );
-};
diff --git a/app/javascript/mastodon/features/account/components/featured_tags.jsx b/app/javascript/mastodon/features/account/components/featured_tags.jsx
new file mode 100644
index 0000000000..56a9efac02
--- /dev/null
+++ b/app/javascript/mastodon/features/account/components/featured_tags.jsx
@@ -0,0 +1,51 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+import { Hashtag } from 'mastodon/components/hashtag';
+
+const messages = defineMessages({
+  lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' },
+  empty: { id: 'account.featured_tags.last_status_never', defaultMessage: 'No posts' },
+});
+
+class FeaturedTags extends ImmutablePureComponent {
+
+  static propTypes = {
+    account: ImmutablePropTypes.record,
+    featuredTags: ImmutablePropTypes.list,
+    tagged: PropTypes.string,
+    intl: PropTypes.object.isRequired,
+  };
+
+  render () {
+    const { account, featuredTags, intl } = this.props;
+
+    if (!account || account.get('suspended') || featuredTags.isEmpty()) {
+      return null;
+    }
+
+    return (
+      <div className='getting-started__trends'>
+        <h4><FormattedMessage id='account.featured_tags.title' defaultMessage="{name}'s featured hashtags" values={{ name: <bdi dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /> }} /></h4>
+
+        {featuredTags.take(3).map(featuredTag => (
+          <Hashtag
+            key={featuredTag.get('name')}
+            name={featuredTag.get('name')}
+            to={`/@${account.get('acct')}/tagged/${featuredTag.get('name')}`}
+            uses={featuredTag.get('statuses_count') * 1}
+            withGraph={false}
+            description={((featuredTag.get('statuses_count') * 1) > 0) ? intl.formatMessage(messages.lastStatusAt, { date: intl.formatDate(featuredTag.get('last_status_at'), { month: 'short', day: '2-digit' }) }) : intl.formatMessage(messages.empty)}
+          />
+        ))}
+      </div>
+    );
+  }
+
+}
+
+export default injectIntl(FeaturedTags);
diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx
new file mode 100644
index 0000000000..f2fb646df8
--- /dev/null
+++ b/app/javascript/mastodon/features/account/components/header.jsx
@@ -0,0 +1,553 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import classNames from 'classnames';
+import { Helmet } from 'react-helmet';
+import { NavLink, withRouter } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+import CheckIcon from '@/material-icons/400-24px/check.svg?react';
+import LockIcon from '@/material-icons/400-24px/lock.svg?react';
+import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
+import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react';
+import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react';
+import ShareIcon from '@/material-icons/400-24px/share.svg?react';
+import { Avatar } from 'mastodon/components/avatar';
+import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge';
+import { Button } from 'mastodon/components/button';
+import { CopyIconButton } from 'mastodon/components/copy_icon_button';
+import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters';
+import { getFeaturedHashtagBar } from 'mastodon/components/hashtag_bar';
+import { Icon }  from 'mastodon/components/icon';
+import { IconButton } from 'mastodon/components/icon_button';
+import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import { ShortNumber } from 'mastodon/components/short_number';
+import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
+import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
+import { autoPlayGif, me, domain as localDomain, isShowItem } from 'mastodon/initial_state';
+import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
+import { WithRouterPropTypes } from 'mastodon/utils/react_router';
+
+import AccountNoteContainer from '../containers/account_note_container';
+import FollowRequestNoteContainer from '../containers/follow_request_note_container';
+
+import { DomainPill } from './domain_pill';
+
+const messages = defineMessages({
+  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
+  follow: { id: 'account.follow', defaultMessage: 'Follow' },
+  followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' },
+  mutual: { id: 'account.mutual', defaultMessage: 'Mutual' },
+  cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
+  requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
+  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
+  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
+  linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
+  account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
+  mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
+  direct: { id: 'account.direct', defaultMessage: 'Privately mention @{name}' },
+  unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
+  block: { id: 'account.block', defaultMessage: 'Block @{name}' },
+  mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
+  report: { id: 'account.report', defaultMessage: 'Report @{name}' },
+  share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
+  copy: { id: 'account.copy', defaultMessage: 'Copy link to profile' },
+  media: { id: 'account.media', defaultMessage: 'Media' },
+  blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
+  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
+  hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
+  showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
+  enableNotifications: { id: 'account.enable_notifications', defaultMessage: 'Notify me when @{name} posts' },
+  disableNotifications: { id: 'account.disable_notifications', defaultMessage: 'Stop notifying me when @{name} posts' },
+  pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
+  preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+  follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
+  favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
+  lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
+  followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
+  blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
+  domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
+  mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
+  endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
+  unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
+  add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
+  add_or_remove_from_antenna: { id: 'account.add_or_remove_from_antenna', defaultMessage: 'Add or Remove from antennas' },
+  add_or_remove_from_exclude_antenna: { id: 'account.add_or_remove_from_exclude_antenna', defaultMessage: 'Add or Remove from antennas as exclusion' },
+  add_or_remove_from_circle: { id: 'account.add_or_remove_from_circle', defaultMessage: 'Add or Remove from circles' },
+  admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
+  admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
+  languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
+  openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
+});
+
+const titleFromAccount = account => {
+  const displayName = account.get('display_name');
+  const acct = account.get('acct') === account.get('username') ? `${account.get('username')}@${localDomain}` : account.get('acct');
+  const prefix = displayName.trim().length === 0 ? account.get('username') : displayName;
+
+  return `${prefix} (@${acct})`;
+};
+
+const messageForFollowButton = relationship => {
+  if(!relationship) return messages.follow;
+
+  if (relationship.get('following') && relationship.get('followed_by') && isShowItem('relationships')) {
+    return messages.mutual;
+  } else if (relationship.get('following') || relationship.get('requested')) {
+    return messages.unfollow;
+  } else if (relationship.get('followed_by') && isShowItem('relationships')) {
+    return messages.followBack;
+  } else {
+    return messages.follow;
+  }
+};
+
+const dateFormatOptions = {
+  month: 'short',
+  day: 'numeric',
+  year: 'numeric',
+  hour: '2-digit',
+  minute: '2-digit',
+};
+
+class Header extends ImmutablePureComponent {
+
+  static propTypes = {
+    identity: identityContextPropShape,
+    account: ImmutablePropTypes.record,
+    featuredTags: PropTypes.array,
+    identity_props: ImmutablePropTypes.list,
+    onFollow: PropTypes.func.isRequired,
+    onBlock: PropTypes.func.isRequired,
+    onMention: PropTypes.func.isRequired,
+    onDirect: PropTypes.func.isRequired,
+    onReblogToggle: PropTypes.func.isRequired,
+    onNotifyToggle: PropTypes.func.isRequired,
+    onReport: PropTypes.func.isRequired,
+    onMute: PropTypes.func.isRequired,
+    onBlockDomain: PropTypes.func.isRequired,
+    onUnblockDomain: PropTypes.func.isRequired,
+    onEndorseToggle: PropTypes.func.isRequired,
+    onAddToList: PropTypes.func.isRequired,
+    onAddToAntenna: PropTypes.func.isRequired,
+    onAddToExcludeAntenna: PropTypes.func.isRequired,
+    onAddToCircle: PropTypes.func.isRequired,
+    onEditAccountNote: PropTypes.func.isRequired,
+    onChangeLanguages: PropTypes.func.isRequired,
+    onInteractionModal: PropTypes.func.isRequired,
+    onOpenAvatar: PropTypes.func.isRequired,
+    onOpenURL: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    domain: PropTypes.string.isRequired,
+    hidden: PropTypes.bool,
+    ...WithRouterPropTypes,
+  };
+
+  setRef = c => {
+    this.node = c;
+  };
+
+  openEditProfile = () => {
+    window.open('/settings/profile', '_blank');
+  };
+
+  isStatusesPageActive = (match, location) => {
+    if (!match) {
+      return false;
+    }
+
+    return !location.pathname.match(/\/(followers|following)\/?$/);
+  };
+
+  handleMouseEnter = ({ currentTarget }) => {
+    if (autoPlayGif) {
+      return;
+    }
+
+    const emojis = currentTarget.querySelectorAll('.custom-emoji');
+
+    for (var i = 0; i < emojis.length; i++) {
+      let emoji = emojis[i];
+      emoji.src = emoji.getAttribute('data-original');
+    }
+  };
+
+  handleMouseLeave = ({ currentTarget }) => {
+    if (autoPlayGif) {
+      return;
+    }
+
+    const emojis = currentTarget.querySelectorAll('.custom-emoji');
+
+    for (var i = 0; i < emojis.length; i++) {
+      let emoji = emojis[i];
+      emoji.src = emoji.getAttribute('data-static');
+    }
+  };
+
+  handleAvatarClick = e => {
+    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      e.preventDefault();
+      this.props.onOpenAvatar();
+    }
+  };
+
+  handleShare = () => {
+    const { account } = this.props;
+
+    navigator.share({
+      url: account.get('url'),
+    }).catch((e) => {
+      if (e.name !== 'AbortError') console.error(e);
+    });
+  };
+
+  handleHashtagClick = e => {
+    const { history } = this.props;
+    const value = e.currentTarget.textContent.replace(/^#/, '');
+
+    if (history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      e.preventDefault();
+      history.push(`/tags/${value}`);
+    }
+  };
+
+  handleFeaturedHashtagClick = e => {
+    const { history, account } = this.props;
+    const value = e.currentTarget.textContent.replace(/^#/, '');
+
+    if (history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      e.preventDefault();
+      history.push(`/@${account.get('acct')}/tagged/${value}`);
+    }
+  };
+
+  handleMentionClick = e => {
+    const { history, onOpenURL } = this.props;
+
+    if (history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      e.preventDefault();
+
+      const link = e.currentTarget;
+
+      onOpenURL(link.href, history, () => {
+        window.location = link.href;
+      });
+    }
+  };
+
+  _attachLinkEvents () {
+    const node = this.node;
+
+    if (!node) {
+      return;
+    }
+
+    const links = node.querySelectorAll('a');
+
+    let link;
+
+    for (var i = 0; i < links.length; ++i) {
+      link = links[i];
+
+      if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
+        if (link.href && link.href.includes('/tagged/')) {
+          link.addEventListener('click', this.handleFeaturedHashtagClick, false);
+        } else {
+          link.addEventListener('click', this.handleHashtagClick, false);
+        }
+      } else if (link.classList.contains('mention')) {
+        link.addEventListener('click', this.handleMentionClick, false);
+      }
+    }
+  }
+
+  componentDidMount () {
+    this._attachLinkEvents();
+  }
+
+  componentDidUpdate () {
+    this._attachLinkEvents();
+  }
+
+  render () {
+    const { account, hidden, intl } = this.props;
+    const { signedIn, permissions } = this.props.identity;
+
+    if (!account) {
+      return null;
+    }
+
+    const suspended    = account.get('suspended');
+    const isRemote     = account.get('acct') !== account.get('username');
+    const remoteDomain = isRemote ? account.get('acct').split('@')[1] : null;
+
+    let actionBtn, bellBtn, lockedIcon, shareBtn;
+
+    let info = [];
+    let menu = [];
+
+    if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
+      info.push(<span key='blocked' className='relationship-tag'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>);
+    }
+
+    if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
+      info.push(<span key='muted' className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
+    } else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
+      info.push(<span key='domain_blocked' className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain blocked' /></span>);
+    }
+
+    if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) {
+      bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} iconComponent={account.getIn(['relationship', 'notifying']) ? NotificationsActiveIcon : NotificationsIcon} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
+    }
+
+    if ('share' in navigator) {
+      shareBtn = <IconButton className='optional' iconComponent={ShareIcon} title={intl.formatMessage(messages.share, { name: account.get('username') })} onClick={this.handleShare} />;
+    } else {
+      shareBtn = <CopyIconButton className='optional' title={intl.formatMessage(messages.copy)} value={account.get('url')} />;
+    }
+
+    if (me !== account.get('id')) {
+      if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded
+        actionBtn = <Button disabled><LoadingIndicator /></Button>;
+      } else if (!account.getIn(['relationship', 'blocking'])) {
+        actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) })} text={intl.formatMessage(messageForFollowButton(account.get('relationship')))} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />;
+      } else if (account.getIn(['relationship', 'blocking'])) {
+        actionBtn = <Button text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
+      }
+    } else {
+      actionBtn = <Button text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />;
+    }
+
+    if (account.get('moved') && !account.getIn(['relationship', 'following'])) {
+      actionBtn = '';
+    }
+
+    if (account.get('locked')) {
+      lockedIcon = <Icon id='lock' icon={LockIcon} title={intl.formatMessage(messages.account_locked)} />;
+    }
+
+    if (signedIn && account.get('id') !== me && !account.get('suspended')) {
+      menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
+      menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
+      menu.push(null);
+    }
+
+    if (isRemote) {
+      menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
+      menu.push(null);
+    }
+
+    if (account.get('id') === me) {
+      menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
+      menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
+      menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
+      menu.push(null);
+      menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
+      menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
+      menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
+      menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
+      menu.push(null);
+      menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
+      menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
+      menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
+    } else if (signedIn) {
+      if (account.getIn(['relationship', 'following'])) {
+        if (!account.getIn(['relationship', 'muting'])) {
+          if (account.getIn(['relationship', 'showing_reblogs'])) {
+            menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
+          } else {
+            menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
+          }
+
+          menu.push({ text: intl.formatMessage(messages.languages), action: this.props.onChangeLanguages });
+          menu.push(null);
+        }
+
+        menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
+        menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
+      }
+      menu.push({ text: intl.formatMessage(messages.add_or_remove_from_antenna), action: this.props.onAddToAntenna });
+      menu.push({ text: intl.formatMessage(messages.add_or_remove_from_exclude_antenna), action: this.props.onAddToExcludeAntenna });
+      if (account.getIn(['relationship', 'followed_by'])) {
+        menu.push({ text: intl.formatMessage(messages.add_or_remove_from_circle), action: this.props.onAddToCircle });
+      }
+      menu.push(null);
+
+      if (account.getIn(['relationship', 'muting'])) {
+        menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
+      } else {
+        menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute, dangerous: true });
+      }
+
+      if (account.getIn(['relationship', 'blocking'])) {
+        menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
+      } else {
+        menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock, dangerous: true });
+      }
+
+      if (!account.get('suspended')) {
+        menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
+      }
+    }
+
+    if (signedIn && isRemote) {
+      menu.push(null);
+
+      if (account.getIn(['relationship', 'domain_blocking'])) {
+        menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain: remoteDomain }), action: this.props.onUnblockDomain });
+      } else {
+        menu.push({ text: intl.formatMessage(messages.blockDomain, { domain: remoteDomain }), action: this.props.onBlockDomain, dangerous: true });
+      }
+    }
+
+    if ((account.get('id') !== me && (permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
+      menu.push(null);
+      if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
+        menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` });
+      }
+      if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
+        menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: remoteDomain }), href: `/admin/instances/${remoteDomain}` });
+      }
+    }
+
+    const content         = { __html: account.get('note_emojified') };
+    const displayNameHtml = { __html: account.get('display_name_html') };
+    const fields          = account.get('fields');
+    const isLocal         = account.get('acct').indexOf('@') === -1;
+    const username        = account.get('acct').split('@')[0];
+    const domain          = isLocal ? localDomain : account.get('acct').split('@')[1];
+    const isIndexable     = !account.get('noindex');
+    const featuredTagsArr = this.props.featuredTags?.map((tag) => tag.get('name')).toArray() || [];
+    const featuredTags    = getFeaturedHashtagBar(account.get('acct'), featuredTagsArr);
+
+    const badges = [];
+
+    if (account.get('bot')) {
+      badges.push(<AutomatedBadge key='bot-badge' />);
+    } else if (account.get('group')) {
+      badges.push(<GroupBadge key='group-badge' />);
+    }
+
+    account.get('roles', []).forEach((role) => {
+      badges.push(<Badge key={`role-badge-${role.get('id')}`} label={<span>{role.get('name')}</span>} domain={domain} roleId={role.get('id')} />);
+    });
+
+    return (
+      <div className={classNames('account__header', { inactive: !!account.get('moved') })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
+        {!(suspended || hidden || account.get('moved')) && account.getIn(['relationship', 'requested_by']) && <FollowRequestNoteContainer account={account} />}
+
+        <div className='account__header__image'>
+          <div className='account__header__info'>
+            {info}
+          </div>
+
+          {!(suspended || hidden) && <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />}
+        </div>
+
+        <div className='account__header__bar'>
+          <div className='account__header__tabs'>
+            <a className='avatar' href={account.get('avatar')} rel='noopener noreferrer' target='_blank' onClick={this.handleAvatarClick}>
+              <Avatar account={suspended || hidden ? undefined : account} size={90} />
+            </a>
+
+            <div className='account__header__tabs__buttons'>
+              {!hidden && bellBtn}
+              {!hidden && shareBtn}
+              <DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' />
+              {!hidden && actionBtn}
+            </div>
+          </div>
+
+          <div className='account__header__tabs__name'>
+            <h1>
+              <span dangerouslySetInnerHTML={displayNameHtml} />
+              <small>
+                <span>@{username}<span className='invisible'>@{domain}</span></span>
+                <DomainPill username={username} domain={domain} isSelf={me === account.get('id')} />
+                {lockedIcon}
+              </small>
+            </h1>
+          </div>
+
+          {badges.length > 0 && (
+            <div className='account__header__badges'>
+              {badges}
+            </div>
+          )}
+
+          {!(suspended || hidden) && (
+            <div className='account__header__extra'>
+              <div className='account__header__bio' ref={this.setRef}>
+                {(account.get('id') !== me && signedIn) && <AccountNoteContainer account={account} />}
+
+                {account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
+
+                {featuredTagsArr.length > 0 && (
+                  <div className='account__header__featured-tags'>
+                    {featuredTags}
+                  </div>
+                )}
+
+                <div className='account__header__fields'>
+                  <dl>
+                    <dt><FormattedMessage id='account.joined_short' defaultMessage='Joined' /></dt>
+                    <dd>{intl.formatDate(account.get('created_at'), { year: 'numeric', month: 'short', day: '2-digit' })}</dd>
+                  </dl>
+
+                  {fields.map((pair, i) => (
+                    <dl key={i} className={classNames({ verified: pair.get('verified_at') })}>
+                      <dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} className='translate' />
+
+                      <dd className='translate' title={pair.get('value_plain')}>
+                        {pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' icon={CheckIcon} className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
+                      </dd>
+                    </dl>
+                  ))}
+                </div>
+              </div>
+
+              <div className='account__header__extra__links'>
+                <NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/@${account.get('acct')}`} title={intl.formatNumber(account.get('statuses_count'))}>
+                  <ShortNumber
+                    value={account.get('statuses_count')}
+                    isHide={account.getIn(['other_settings', 'hide_statuses_count']) || false}
+                    renderer={StatusesCounter}
+                  />
+                </NavLink>
+
+                <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/following`} title={intl.formatNumber(account.get('following_count'))}>
+                  <ShortNumber
+                    value={account.get('following_count')}
+                    isHide={account.getIn(['other_settings', 'hide_following_count']) || false}
+                    renderer={FollowingCounter}
+                  />
+                </NavLink>
+
+                <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
+                  <ShortNumber
+                    value={account.get('followers_count')}
+                    isHide={account.getIn(['other_settings', 'hide_followers_count']) || false}
+                    renderer={FollowersCounter}
+                  />
+                </NavLink>
+              </div>
+            </div>
+          )}
+        </div>
+
+        <Helmet>
+          <title>{titleFromAccount(account)}</title>
+          <meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} />
+          <link rel='canonical' href={account.get('url')} />
+        </Helmet>
+      </div>
+    );
+  }
+
+}
+
+export default withRouter(withIdentity(injectIntl(Header)));
diff --git a/app/javascript/mastodon/features/account/containers/account_note_container.js b/app/javascript/mastodon/features/account/containers/account_note_container.js
index 77de964039..1530242d69 100644
--- a/app/javascript/mastodon/features/account/containers/account_note_container.js
+++ b/app/javascript/mastodon/features/account/containers/account_note_container.js
@@ -4,14 +4,14 @@ import { submitAccountNote } from 'mastodon/actions/account_notes';
 
 import AccountNote from '../components/account_note';
 
-const mapStateToProps = (state, { accountId }) => ({
-  value: state.relationships.getIn([accountId, 'note']),
+const mapStateToProps = (state, { account }) => ({
+  value: account.getIn(['relationship', 'note']),
 });
 
-const mapDispatchToProps = (dispatch, { accountId }) => ({
+const mapDispatchToProps = (dispatch, { account }) => ({
 
   onSave (value) {
-    dispatch(submitAccountNote({ accountId: accountId, note: value }));
+    dispatch(submitAccountNote({ accountId: account.get('id'), note: value }));
   },
 
 });
diff --git a/app/javascript/mastodon/features/account/containers/featured_tags_container.js b/app/javascript/mastodon/features/account/containers/featured_tags_container.js
new file mode 100644
index 0000000000..726c805f78
--- /dev/null
+++ b/app/javascript/mastodon/features/account/containers/featured_tags_container.js
@@ -0,0 +1,17 @@
+import { List as ImmutableList } from 'immutable';
+import { connect } from 'react-redux';
+
+import { makeGetAccount } from 'mastodon/selectors';
+
+import FeaturedTags from '../components/featured_tags';
+
+const mapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  return (state, { accountId }) => ({
+    account: getAccount(state, accountId),
+    featuredTags: state.getIn(['user_lists', 'featured_tags', accountId, 'items'], ImmutableList()),
+  });
+};
+
+export default connect(mapStateToProps)(FeaturedTags);
diff --git a/app/javascript/mastodon/features/account/navigation.jsx b/app/javascript/mastodon/features/account/navigation.jsx
new file mode 100644
index 0000000000..aa78135de2
--- /dev/null
+++ b/app/javascript/mastodon/features/account/navigation.jsx
@@ -0,0 +1,52 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { connect } from 'react-redux';
+
+import FeaturedTags from 'mastodon/features/account/containers/featured_tags_container';
+import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
+
+const mapStateToProps = (state, { match: { params: { acct } } }) => {
+  const accountId = state.getIn(['accounts_map', normalizeForLookup(acct)]);
+
+  if (!accountId) {
+    return {
+      isLoading: true,
+    };
+  }
+
+  return {
+    accountId,
+    isLoading: false,
+  };
+};
+
+class AccountNavigation extends PureComponent {
+
+  static propTypes = {
+    match: PropTypes.shape({
+      params: PropTypes.shape({
+        acct: PropTypes.string,
+        tagged: PropTypes.string,
+      }).isRequired,
+    }).isRequired,
+
+    accountId: PropTypes.string,
+    isLoading: PropTypes.bool,
+  };
+
+  render () {
+    const { accountId, isLoading, match: { params: { tagged } } } = this.props;
+
+    if (isLoading) {
+      return null;
+    }
+
+    return (
+      <FeaturedTags accountId={accountId} tagged={tagged} />
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(AccountNavigation);
diff --git a/app/javascript/mastodon/features/account_featured/components/empty_message.tsx b/app/javascript/mastodon/features/account_featured/components/empty_message.tsx
deleted file mode 100644
index 9dd8ffdfe0..0000000000
--- a/app/javascript/mastodon/features/account_featured/components/empty_message.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-import { LimitedAccountHint } from 'mastodon/features/account_timeline/components/limited_account_hint';
-
-interface EmptyMessageProps {
-  suspended: boolean;
-  hidden: boolean;
-  blockedBy: boolean;
-  accountId?: string;
-}
-
-export const EmptyMessage: React.FC<EmptyMessageProps> = ({
-  accountId,
-  suspended,
-  hidden,
-  blockedBy,
-}) => {
-  if (!accountId) {
-    return null;
-  }
-
-  let message: React.ReactNode = null;
-
-  if (suspended) {
-    message = (
-      <FormattedMessage
-        id='empty_column.account_suspended'
-        defaultMessage='Account suspended'
-      />
-    );
-  } else if (hidden) {
-    message = <LimitedAccountHint accountId={accountId} />;
-  } else if (blockedBy) {
-    message = (
-      <FormattedMessage
-        id='empty_column.account_unavailable'
-        defaultMessage='Profile unavailable'
-      />
-    );
-  } else {
-    message = (
-      <FormattedMessage
-        id='empty_column.account_featured'
-        defaultMessage='This list is empty'
-      />
-    );
-  }
-
-  return <div className='empty-column-indicator'>{message}</div>;
-};
diff --git a/app/javascript/mastodon/features/account_featured/components/featured_tag.tsx b/app/javascript/mastodon/features/account_featured/components/featured_tag.tsx
deleted file mode 100644
index 7b476ba01d..0000000000
--- a/app/javascript/mastodon/features/account_featured/components/featured_tag.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { defineMessages, useIntl } from 'react-intl';
-
-import type { Map as ImmutableMap } from 'immutable';
-
-import { Hashtag } from 'mastodon/components/hashtag';
-
-export type TagMap = ImmutableMap<
-  'id' | 'name' | 'url' | 'statuses_count' | 'last_status_at' | 'accountId',
-  string | null
->;
-
-interface FeaturedTagProps {
-  tag: TagMap;
-  account: string;
-}
-
-const messages = defineMessages({
-  lastStatusAt: {
-    id: 'account.featured_tags.last_status_at',
-    defaultMessage: 'Last post on {date}',
-  },
-  empty: {
-    id: 'account.featured_tags.last_status_never',
-    defaultMessage: 'No posts',
-  },
-});
-
-export const FeaturedTag: React.FC<FeaturedTagProps> = ({ tag, account }) => {
-  const intl = useIntl();
-  const name = tag.get('name') ?? '';
-  const count = Number.parseInt(tag.get('statuses_count') ?? '');
-  return (
-    <Hashtag
-      key={name}
-      name={name}
-      to={`/@${account}/tagged/${name}`}
-      uses={count}
-      withGraph={false}
-      description={
-        count > 0
-          ? intl.formatMessage(messages.lastStatusAt, {
-              date: intl.formatDate(tag.get('last_status_at') ?? '', {
-                month: 'short',
-                day: '2-digit',
-              }),
-            })
-          : intl.formatMessage(messages.empty)
-      }
-    />
-  );
-};
diff --git a/app/javascript/mastodon/features/account_featured/index.tsx b/app/javascript/mastodon/features/account_featured/index.tsx
deleted file mode 100644
index 70e411f61a..0000000000
--- a/app/javascript/mastodon/features/account_featured/index.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-import { useEffect } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import { useParams } from 'react-router';
-
-import type { Map as ImmutableMap } from 'immutable';
-import { List as ImmutableList } from 'immutable';
-
-import { fetchFeaturedTags } from 'mastodon/actions/featured_tags';
-import { expandAccountFeaturedTimeline } from 'mastodon/actions/timelines';
-import { ColumnBackButton } from 'mastodon/components/column_back_button';
-import { LoadingIndicator } from 'mastodon/components/loading_indicator';
-import { RemoteHint } from 'mastodon/components/remote_hint';
-import StatusContainer from 'mastodon/containers/status_container';
-import { useAccountId } from 'mastodon/hooks/useAccountId';
-import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-import { AccountHeader } from '../account_timeline/components/account_header';
-import Column from '../ui/components/column';
-
-import { EmptyMessage } from './components/empty_message';
-import { FeaturedTag } from './components/featured_tag';
-import type { TagMap } from './components/featured_tag';
-
-interface Params {
-  acct?: string;
-  id?: string;
-}
-
-const AccountFeatured = () => {
-  const accountId = useAccountId();
-  const { suspended, blockedBy, hidden } = useAccountVisibility(accountId);
-  const forceEmptyState = suspended || blockedBy || hidden;
-  const { acct = '' } = useParams<Params>();
-
-  const dispatch = useAppDispatch();
-
-  useEffect(() => {
-    if (accountId) {
-      void dispatch(expandAccountFeaturedTimeline(accountId));
-      dispatch(fetchFeaturedTags(accountId));
-    }
-  }, [accountId, dispatch]);
-
-  const isLoading = useAppSelector(
-    (state) =>
-      !accountId ||
-      !!(state.timelines as ImmutableMap<string, unknown>).getIn([
-        `account:${accountId}:pinned`,
-        'isLoading',
-      ]) ||
-      !!state.user_lists.getIn(['featured_tags', accountId, 'isLoading']),
-  );
-  const featuredTags = useAppSelector(
-    (state) =>
-      state.user_lists.getIn(
-        ['featured_tags', accountId, 'items'],
-        ImmutableList(),
-      ) as ImmutableList<TagMap>,
-  );
-  const featuredStatusIds = useAppSelector(
-    (state) =>
-      (state.timelines as ImmutableMap<string, unknown>).getIn(
-        [`account:${accountId}:pinned`, 'items'],
-        ImmutableList(),
-      ) as ImmutableList<string>,
-  );
-
-  if (isLoading) {
-    return (
-      <AccountFeaturedWrapper accountId={accountId}>
-        <div className='scrollable__append'>
-          <LoadingIndicator />
-        </div>
-      </AccountFeaturedWrapper>
-    );
-  }
-
-  if (featuredStatusIds.isEmpty() && featuredTags.isEmpty()) {
-    return (
-      <AccountFeaturedWrapper accountId={accountId}>
-        <EmptyMessage
-          blockedBy={blockedBy}
-          hidden={hidden}
-          suspended={suspended}
-          accountId={accountId}
-        />
-        <RemoteHint accountId={accountId} />
-      </AccountFeaturedWrapper>
-    );
-  }
-
-  return (
-    <Column>
-      <ColumnBackButton />
-
-      <div className='scrollable scrollable--flex'>
-        {accountId && (
-          <AccountHeader accountId={accountId} hideTabs={forceEmptyState} />
-        )}
-        {!featuredTags.isEmpty() && (
-          <>
-            <h4 className='column-subheading'>
-              <FormattedMessage
-                id='account.featured.hashtags'
-                defaultMessage='Hashtags'
-              />
-            </h4>
-            {featuredTags.map((tag) => (
-              <FeaturedTag key={tag.get('id')} tag={tag} account={acct} />
-            ))}
-          </>
-        )}
-        {!featuredStatusIds.isEmpty() && (
-          <>
-            <h4 className='column-subheading'>
-              <FormattedMessage
-                id='account.featured.posts'
-                defaultMessage='Posts'
-              />
-            </h4>
-            {featuredStatusIds.map((statusId) => (
-              <StatusContainer
-                key={`f-${statusId}`}
-                // @ts-expect-error inferred props are wrong
-                id={statusId}
-                contextType='account'
-              />
-            ))}
-          </>
-        )}
-        <RemoteHint accountId={accountId} />
-      </div>
-    </Column>
-  );
-};
-
-const AccountFeaturedWrapper = ({
-  children,
-  accountId,
-}: React.PropsWithChildren<{ accountId?: string }>) => {
-  return (
-    <Column>
-      <ColumnBackButton />
-      <div className='scrollable scrollable--flex'>
-        {accountId && <AccountHeader accountId={accountId} />}
-        {children}
-      </div>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default AccountFeatured;
diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.tsx b/app/javascript/mastodon/features/account_gallery/components/media_item.tsx
index 0d251ff99f..729e40a993 100644
--- a/app/javascript/mastodon/features/account_gallery/components/media_item.tsx
+++ b/app/javascript/mastodon/features/account_gallery/components/media_item.tsx
@@ -11,31 +11,22 @@ import { Icon } from 'mastodon/components/icon';
 import { formatTime } from 'mastodon/features/video';
 import { autoPlayGif, displayMedia, useBlurhash } from 'mastodon/initial_state';
 import type { Status, MediaAttachment } from 'mastodon/models/status';
-import { useAppSelector } from 'mastodon/store';
 
 export const MediaItem: React.FC<{
   attachment: MediaAttachment;
   onOpenMedia: (arg0: MediaAttachment) => void;
 }> = ({ attachment, onOpenMedia }) => {
-  const account = useAppSelector((state) =>
-    state.accounts.get(attachment.getIn(['status', 'account']) as string),
-  );
   const [visible, setVisible] = useState(
     (displayMedia !== 'hide_all' &&
       !attachment.getIn(['status', 'sensitive'])) ||
       displayMedia === 'show_all',
   );
   const [loaded, setLoaded] = useState(false);
-  const [error, setError] = useState(false);
 
   const handleImageLoad = useCallback(() => {
     setLoaded(true);
   }, [setLoaded]);
 
-  const handleImageError = useCallback(() => {
-    setError(true);
-  }, [setError]);
-
   const handleMouseEnter = useCallback(
     (e: React.MouseEvent<HTMLVideoElement>) => {
       if (e.target instanceof HTMLVideoElement) {
@@ -75,10 +66,11 @@ export const MediaItem: React.FC<{
     attachment.get('description')) as string | undefined;
   const previewUrl = attachment.get('preview_url') as string;
   const fullUrl = attachment.get('url') as string;
-  const avatarUrl = account?.avatar_static;
+  const avatarUrl = status.getIn(['account', 'avatar_static']) as string;
   const lang = status.get('language') as string;
   const blurhash = attachment.get('blurhash') as string;
   const statusId = status.get('id') as string;
+  const acct = status.getIn(['account', 'acct']) as string;
   const type = attachment.get('type') as string;
 
   let thumbnail;
@@ -101,9 +93,9 @@ export const MediaItem: React.FC<{
         <img
           src={previewUrl || avatarUrl}
           alt={description}
+          title={description}
           lang={lang}
           onLoad={handleImageLoad}
-          onError={handleImageError}
         />
 
         <div className='media-gallery__item__overlay media-gallery__item__overlay--corner'>
@@ -121,10 +113,10 @@ export const MediaItem: React.FC<{
       <img
         src={previewUrl}
         alt={description}
+        title={description}
         lang={lang}
         style={{ objectPosition: `${x}% ${y}%` }}
         onLoad={handleImageLoad}
-        onError={handleImageError}
       />
     );
   } else if (['video', 'gifv'].includes(type)) {
@@ -139,6 +131,7 @@ export const MediaItem: React.FC<{
         <video
           className='media-gallery__item-gifv-thumbnail'
           aria-label={description}
+          title={description}
           lang={lang}
           src={fullUrl}
           onMouseEnter={handleMouseEnter}
@@ -180,11 +173,7 @@ export const MediaItem: React.FC<{
   }
 
   return (
-    <div
-      className={classNames('media-gallery__item media-gallery__item--square', {
-        'media-gallery__item--error': error,
-      })}
-    >
+    <div className='media-gallery__item media-gallery__item--square'>
       <Blurhash
         hash={blurhash}
         className={classNames('media-gallery__preview', {
@@ -195,7 +184,7 @@ export const MediaItem: React.FC<{
 
       <a
         className='media-gallery__item-thumbnail'
-        href={`/@${account?.acct}/${statusId}`}
+        href={`/@${acct}/${statusId}`}
         onClick={handleClick}
         target='_blank'
         rel='noopener noreferrer'
diff --git a/app/javascript/mastodon/features/account_gallery/index.jsx b/app/javascript/mastodon/features/account_gallery/index.jsx
new file mode 100644
index 0000000000..35a0fbd2c6
--- /dev/null
+++ b/app/javascript/mastodon/features/account_gallery/index.jsx
@@ -0,0 +1,241 @@
+import PropTypes from 'prop-types';
+
+import { FormattedMessage } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
+import { openModal } from 'mastodon/actions/modal';
+import { ColumnBackButton } from 'mastodon/components/column_back_button';
+import { LoadMore } from 'mastodon/components/load_more';
+import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import ScrollContainer from 'mastodon/containers/scroll_container';
+import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
+import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
+import { getAccountGallery } from 'mastodon/selectors';
+
+import { expandAccountMediaTimeline } from '../../actions/timelines';
+import HeaderContainer from '../account_timeline/containers/header_container';
+import Column from '../ui/components/column';
+
+import { MediaItem } from './components/media_item';
+
+const mapStateToProps = (state, { params: { acct, id } }) => {
+  const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]);
+
+  if (!accountId) {
+    return {
+      isLoading: true,
+    };
+  }
+
+  return {
+    accountId,
+    isAccount: !!state.getIn(['accounts', accountId]),
+    attachments: getAccountGallery(state, accountId),
+    isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
+    hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
+    suspended: state.getIn(['accounts', accountId, 'suspended'], false),
+    blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
+  };
+};
+
+class LoadMoreMedia extends ImmutablePureComponent {
+
+  static propTypes = {
+    maxId: PropTypes.string,
+    onLoadMore: PropTypes.func.isRequired,
+  };
+
+  handleLoadMore = () => {
+    this.props.onLoadMore(this.props.maxId);
+  };
+
+  render () {
+    return (
+      <LoadMore
+        disabled={this.props.disabled}
+        onClick={this.handleLoadMore}
+      />
+    );
+  }
+
+}
+
+class AccountGallery extends ImmutablePureComponent {
+
+  static propTypes = {
+    params: PropTypes.shape({
+      acct: PropTypes.string,
+      id: PropTypes.string,
+    }).isRequired,
+    accountId: PropTypes.string,
+    dispatch: PropTypes.func.isRequired,
+    attachments: ImmutablePropTypes.list.isRequired,
+    isLoading: PropTypes.bool,
+    hasMore: PropTypes.bool,
+    isAccount: PropTypes.bool,
+    blockedBy: PropTypes.bool,
+    suspended: PropTypes.bool,
+    multiColumn: PropTypes.bool,
+  };
+
+  state = {
+    width: 323,
+  };
+
+  _load () {
+    const { accountId, isAccount, dispatch } = this.props;
+
+    if (!isAccount) dispatch(fetchAccount(accountId));
+    dispatch(expandAccountMediaTimeline(accountId));
+  }
+
+  componentDidMount () {
+    const { params: { acct }, accountId, dispatch } = this.props;
+
+    if (accountId) {
+      this._load();
+    } else {
+      dispatch(lookupAccount(acct));
+    }
+  }
+
+  componentDidUpdate (prevProps) {
+    const { params: { acct }, accountId, dispatch } = this.props;
+
+    if (prevProps.accountId !== accountId && accountId) {
+      this._load();
+    } else if (prevProps.params.acct !== acct) {
+      dispatch(lookupAccount(acct));
+    }
+  }
+
+  handleScrollToBottom = () => {
+    if (this.props.hasMore) {
+      this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined);
+    }
+  };
+
+  handleScroll = e => {
+    const { scrollTop, scrollHeight, clientHeight } = e.target;
+    const offset = scrollHeight - scrollTop - clientHeight;
+
+    if (150 > offset && !this.props.isLoading) {
+      this.handleScrollToBottom();
+    }
+  };
+
+  handleLoadMore = maxId => {
+    this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId }));
+  };
+
+  handleLoadOlder = e => {
+    e.preventDefault();
+    this.handleScrollToBottom();
+  };
+
+  handleOpenMedia = attachment => {
+    const { dispatch } = this.props;
+    const statusId = attachment.getIn(['status', 'id']);
+    const lang = attachment.getIn(['status', 'language']);
+
+    if (attachment.get('type') === 'video') {
+      dispatch(openModal({
+        modalType: 'VIDEO',
+        modalProps: { media: attachment, statusId, lang, options: { autoPlay: true } },
+      }));
+    } else if (attachment.get('type') === 'audio') {
+      dispatch(openModal({
+        modalType: 'AUDIO',
+        modalProps: { media: attachment, statusId, lang, options: { autoPlay: true } },
+      }));
+    } else {
+      const media = attachment.getIn(['status', 'media_attachments']);
+      const index = media.findIndex(x => x.get('id') === attachment.get('id'));
+
+      dispatch(openModal({
+        modalType: 'MEDIA',
+        modalProps: { media, index, statusId, lang },
+      }));
+    }
+  };
+
+  handleRef = c => {
+    if (c) {
+      this.setState({ width: c.offsetWidth });
+    }
+  };
+
+  render () {
+    const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended } = this.props;
+    const { width } = this.state;
+
+    if (!isAccount) {
+      return (
+        <BundleColumnError multiColumn={multiColumn} errorType='routing' />
+      );
+    }
+
+    if (!attachments && isLoading) {
+      return (
+        <Column>
+          <LoadingIndicator />
+        </Column>
+      );
+    }
+
+    let loadOlder = null;
+
+    if (hasMore && !(isLoading && attachments.size === 0)) {
+      loadOlder = <LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />;
+    }
+
+    let emptyMessage;
+
+    if (suspended) {
+      emptyMessage = <FormattedMessage id='empty_column.account_suspended' defaultMessage='Account suspended' />;
+    } else if (blockedBy) {
+      emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
+    }
+
+    return (
+      <Column>
+        <ColumnBackButton />
+
+        <ScrollContainer scrollKey='account_gallery'>
+          <div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
+            <HeaderContainer accountId={this.props.accountId} />
+
+            {(suspended || blockedBy) ? (
+              <div className='empty-column-indicator'>
+                {emptyMessage}
+              </div>
+            ) : (
+              <div role='feed' className='account-gallery__container' ref={this.handleRef}>
+                {attachments.map((attachment, index) => attachment === null ? (
+                  <LoadMoreMedia key={'more:' + attachments.getIn(index + 1, 'id')} maxId={index > 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
+                ) : (
+                  <MediaItem key={attachment.get('id')} attachment={attachment} displayWidth={width} onOpenMedia={this.handleOpenMedia} />
+                ))}
+
+                {loadOlder}
+              </div>
+            )}
+
+            {isLoading && attachments.size === 0 && (
+              <div className='scrollable__append'>
+                <LoadingIndicator />
+              </div>
+            )}
+          </div>
+        </ScrollContainer>
+      </Column>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(AccountGallery);
diff --git a/app/javascript/mastodon/features/account_gallery/index.tsx b/app/javascript/mastodon/features/account_gallery/index.tsx
deleted file mode 100644
index 0027329c93..0000000000
--- a/app/javascript/mastodon/features/account_gallery/index.tsx
+++ /dev/null
@@ -1,220 +0,0 @@
-import { useEffect, useCallback } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import { createSelector } from '@reduxjs/toolkit';
-import type { Map as ImmutableMap } from 'immutable';
-import { List as ImmutableList } from 'immutable';
-
-import { openModal } from 'mastodon/actions/modal';
-import { expandAccountMediaTimeline } from 'mastodon/actions/timelines';
-import { ColumnBackButton } from 'mastodon/components/column_back_button';
-import { RemoteHint } from 'mastodon/components/remote_hint';
-import ScrollableList from 'mastodon/components/scrollable_list';
-import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
-import { LimitedAccountHint } from 'mastodon/features/account_timeline/components/limited_account_hint';
-import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
-import Column from 'mastodon/features/ui/components/column';
-import { useAccountId } from 'mastodon/hooks/useAccountId';
-import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility';
-import type { MediaAttachment } from 'mastodon/models/media_attachment';
-import type { RootState } from 'mastodon/store';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-import { MediaItem } from './components/media_item';
-
-const getAccountGallery = createSelector(
-  [
-    (state: RootState, accountId: string) =>
-      (state.timelines as ImmutableMap<string, unknown>).getIn(
-        [`account:${accountId}:media`, 'items'],
-        ImmutableList(),
-      ) as ImmutableList<string>,
-    (state: RootState) => state.statuses,
-  ],
-  (statusIds, statuses) => {
-    let items = ImmutableList<MediaAttachment>();
-
-    statusIds.forEach((statusId) => {
-      const status = statuses.get(statusId) as
-        | ImmutableMap<string, unknown>
-        | undefined;
-
-      if (status) {
-        items = items.concat(
-          (
-            status.get('media_attachments') as ImmutableList<MediaAttachment>
-          ).map((media) => media.set('status', status)),
-        );
-      }
-    });
-
-    return items;
-  },
-);
-
-export const AccountGallery: React.FC<{
-  multiColumn: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const accountId = useAccountId();
-  const attachments = useAppSelector((state) =>
-    accountId
-      ? getAccountGallery(state, accountId)
-      : ImmutableList<MediaAttachment>(),
-  );
-  const isLoading = useAppSelector((state) =>
-    (state.timelines as ImmutableMap<string, unknown>).getIn([
-      `account:${accountId}:media`,
-      'isLoading',
-    ]),
-  );
-  const hasMore = useAppSelector((state) =>
-    (state.timelines as ImmutableMap<string, unknown>).getIn([
-      `account:${accountId}:media`,
-      'hasMore',
-    ]),
-  );
-  const account = useAppSelector((state) =>
-    accountId ? state.accounts.get(accountId) : undefined,
-  );
-  const isAccount = !!account;
-
-  const { suspended, blockedBy, hidden } = useAccountVisibility(accountId);
-
-  const maxId = attachments.last()?.getIn(['status', 'id']) as
-    | string
-    | undefined;
-
-  useEffect(() => {
-    if (accountId && isAccount) {
-      void dispatch(expandAccountMediaTimeline(accountId));
-    }
-  }, [dispatch, accountId, isAccount]);
-
-  const handleLoadMore = useCallback(() => {
-    if (maxId) {
-      void dispatch(expandAccountMediaTimeline(accountId, { maxId }));
-    }
-  }, [dispatch, accountId, maxId]);
-
-  const handleOpenMedia = useCallback(
-    (attachment: MediaAttachment) => {
-      const statusId = attachment.getIn(['status', 'id']);
-      const lang = attachment.getIn(['status', 'language']);
-
-      if (attachment.get('type') === 'video') {
-        dispatch(
-          openModal({
-            modalType: 'VIDEO',
-            modalProps: {
-              media: attachment,
-              statusId,
-              lang,
-              options: { autoPlay: true },
-            },
-          }),
-        );
-      } else if (attachment.get('type') === 'audio') {
-        dispatch(
-          openModal({
-            modalType: 'AUDIO',
-            modalProps: {
-              media: attachment,
-              statusId,
-              lang,
-              options: { autoPlay: true },
-            },
-          }),
-        );
-      } else {
-        const media = attachment.getIn([
-          'status',
-          'media_attachments',
-        ]) as ImmutableList<MediaAttachment>;
-        const index = media.findIndex(
-          (x) => x.get('id') === attachment.get('id'),
-        );
-
-        dispatch(
-          openModal({
-            modalType: 'MEDIA',
-            modalProps: { media, index, statusId, lang },
-          }),
-        );
-      }
-    },
-    [dispatch],
-  );
-
-  if (accountId && !isAccount) {
-    return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
-  }
-
-  let emptyMessage;
-
-  if (accountId) {
-    if (suspended) {
-      emptyMessage = (
-        <FormattedMessage
-          id='empty_column.account_suspended'
-          defaultMessage='Account suspended'
-        />
-      );
-    } else if (hidden) {
-      emptyMessage = <LimitedAccountHint accountId={accountId} />;
-    } else if (blockedBy) {
-      emptyMessage = (
-        <FormattedMessage
-          id='empty_column.account_unavailable'
-          defaultMessage='Profile unavailable'
-        />
-      );
-    } else if (attachments.isEmpty()) {
-      emptyMessage = <RemoteHint accountId={accountId} />;
-    } else {
-      emptyMessage = (
-        <FormattedMessage
-          id='empty_column.account_timeline'
-          defaultMessage='No posts found'
-        />
-      );
-    }
-  }
-
-  const forceEmptyState = suspended || blockedBy || hidden;
-
-  return (
-    <Column>
-      <ColumnBackButton />
-
-      <ScrollableList
-        className='account-gallery__container'
-        prepend={
-          accountId && (
-            <AccountHeader accountId={accountId} hideTabs={forceEmptyState} />
-          )
-        }
-        alwaysPrepend
-        append={accountId && <RemoteHint accountId={accountId} />}
-        scrollKey='account_gallery'
-        isLoading={isLoading}
-        hasMore={!forceEmptyState && hasMore}
-        onLoadMore={handleLoadMore}
-        emptyMessage={emptyMessage}
-        bindToDocument={!multiColumn}
-      >
-        {attachments.map((attachment) => (
-          <MediaItem
-            key={attachment.get('id') as string}
-            attachment={attachment}
-            onOpenMedia={handleOpenMedia}
-          />
-        ))}
-      </ScrollableList>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default AccountGallery;
diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx
deleted file mode 100644
index 3f9c7d843a..0000000000
--- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx
+++ /dev/null
@@ -1,1090 +0,0 @@
-/* eslint-disable @typescript-eslint/no-unsafe-call,
-                  @typescript-eslint/no-unsafe-return,
-                  @typescript-eslint/no-unsafe-assignment,
-                  @typescript-eslint/no-unsafe-argument,
-                  @typescript-eslint/no-unsafe-member-access,
-                  @typescript-eslint/no-explicit-any
-                  -- the settings store is not yet typed */
-import { useCallback, useMemo } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import classNames from 'classnames';
-import { Helmet } from 'react-helmet';
-import { NavLink } from 'react-router-dom';
-
-import CheckIcon from '@/material-icons/400-24px/check.svg?react';
-import LockIcon from '@/material-icons/400-24px/lock.svg?react';
-import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
-import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react';
-import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react';
-import ShareIcon from '@/material-icons/400-24px/share.svg?react';
-import {
-  followAccount,
-  unblockAccount,
-  unmuteAccount,
-  pinAccount,
-  unpinAccount,
-} from 'mastodon/actions/accounts';
-import { initBlockModal } from 'mastodon/actions/blocks';
-import { mentionCompose, directCompose } from 'mastodon/actions/compose';
-import {
-  initDomainBlockModal,
-  unblockDomain,
-} from 'mastodon/actions/domain_blocks';
-import { openModal } from 'mastodon/actions/modal';
-import { initMuteModal } from 'mastodon/actions/mutes';
-import { initReport } from 'mastodon/actions/reports';
-import { Avatar } from 'mastodon/components/avatar';
-import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge';
-import { Button } from 'mastodon/components/button';
-import { CopyIconButton } from 'mastodon/components/copy_icon_button';
-import {
-  FollowersCounter,
-  FollowingCounter,
-  StatusesCounter,
-} from 'mastodon/components/counters';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
-import { FollowButton } from 'mastodon/components/follow_button';
-import { FormattedDateWrapper } from 'mastodon/components/formatted_date';
-import { getFeaturedHashtagBar } from 'mastodon/components/hashtag_bar';
-import { Icon } from 'mastodon/components/icon';
-import { IconButton } from 'mastodon/components/icon_button';
-import { ShortNumber } from 'mastodon/components/short_number';
-import { DomainPill } from 'mastodon/features/account/components/domain_pill';
-import AccountNoteContainer from 'mastodon/features/account/containers/account_note_container';
-import FollowRequestNoteContainer from 'mastodon/features/account/containers/follow_request_note_container';
-import { useLinks } from 'mastodon/hooks/useLinks';
-import { useIdentity } from 'mastodon/identity_context';
-import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state';
-import type { Account } from 'mastodon/models/account';
-import type { MenuItem } from 'mastodon/models/dropdown_menu';
-import {
-  PERMISSION_MANAGE_USERS,
-  PERMISSION_MANAGE_FEDERATION,
-} from 'mastodon/permissions';
-import { getAccountHidden } from 'mastodon/selectors/accounts';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-import { MemorialNote } from './memorial_note';
-import { MovedNote } from './moved_note';
-
-const messages = defineMessages({
-  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
-  follow: { id: 'account.follow', defaultMessage: 'Follow' },
-  followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' },
-  mutual: { id: 'account.mutual', defaultMessage: 'Mutual' },
-  cancel_follow_request: {
-    id: 'account.cancel_follow_request',
-    defaultMessage: 'Withdraw follow request',
-  },
-  requested: {
-    id: 'account.requested',
-    defaultMessage: 'Awaiting approval. Click to cancel follow request',
-  },
-  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
-  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
-  linkVerifiedOn: {
-    id: 'account.link_verified_on',
-    defaultMessage: 'Ownership of this link was checked on {date}',
-  },
-  account_locked: {
-    id: 'account.locked_info',
-    defaultMessage:
-      'This account privacy status is set to locked. The owner manually reviews who can follow them.',
-  },
-  mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
-  direct: { id: 'account.direct', defaultMessage: 'Privately mention @{name}' },
-  unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
-  block: { id: 'account.block', defaultMessage: 'Block @{name}' },
-  mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
-  report: { id: 'account.report', defaultMessage: 'Report @{name}' },
-  share: { id: 'account.share', defaultMessage: "Share @{name}'s profile" },
-  copy: { id: 'account.copy', defaultMessage: 'Copy link to profile' },
-  media: { id: 'account.media', defaultMessage: 'Media' },
-  blockDomain: {
-    id: 'account.block_domain',
-    defaultMessage: 'Block domain {domain}',
-  },
-  unblockDomain: {
-    id: 'account.unblock_domain',
-    defaultMessage: 'Unblock domain {domain}',
-  },
-  hideReblogs: {
-    id: 'account.hide_reblogs',
-    defaultMessage: 'Hide boosts from @{name}',
-  },
-  showReblogs: {
-    id: 'account.show_reblogs',
-    defaultMessage: 'Show boosts from @{name}',
-  },
-  enableNotifications: {
-    id: 'account.enable_notifications',
-    defaultMessage: 'Notify me when @{name} posts',
-  },
-  disableNotifications: {
-    id: 'account.disable_notifications',
-    defaultMessage: 'Stop notifying me when @{name} posts',
-  },
-  pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
-  preferences: {
-    id: 'navigation_bar.preferences',
-    defaultMessage: 'Preferences',
-  },
-  follow_requests: {
-    id: 'navigation_bar.follow_requests',
-    defaultMessage: 'Follow requests',
-  },
-  favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
-  lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
-  followed_tags: {
-    id: 'navigation_bar.followed_tags',
-    defaultMessage: 'Followed hashtags',
-  },
-  blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
-  domain_blocks: {
-    id: 'navigation_bar.domain_blocks',
-    defaultMessage: 'Blocked domains',
-  },
-  mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
-  endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
-  unendorse: {
-    id: 'account.unendorse',
-    defaultMessage: "Don't feature on profile",
-  },
-  add_or_remove_from_list: {
-    id: 'account.add_or_remove_from_list',
-    defaultMessage: 'Add or Remove from lists',
-  },
-  add_or_remove_from_antenna: {
-    id: 'account.add_or_remove_from_antenna',
-    defaultMessage: 'Add or Remove from antennas',
-  },
-  add_or_remove_from_exclude_antenna: {
-    id: 'account.add_or_remove_from_exclude_antenna',
-    defaultMessage: 'Add or Remove from antennas as exclusion',
-  },
-  add_or_remove_from_circle: {
-    id: 'account.add_or_remove_from_circle',
-    defaultMessage: 'Add or Remove from circles',
-  },
-  admin_account: {
-    id: 'status.admin_account',
-    defaultMessage: 'Open moderation interface for @{name}',
-  },
-  admin_domain: {
-    id: 'status.admin_domain',
-    defaultMessage: 'Open moderation interface for {domain}',
-  },
-  languages: {
-    id: 'account.languages',
-    defaultMessage: 'Change subscribed languages',
-  },
-  openOriginalPage: {
-    id: 'account.open_original_page',
-    defaultMessage: 'Open original page',
-  },
-});
-
-const titleFromAccount = (account: Account) => {
-  const displayName = account.display_name;
-  const acct =
-    account.acct === account.username
-      ? `${account.username}@${localDomain}`
-      : account.acct;
-  const prefix =
-    displayName.trim().length === 0 ? account.username : displayName;
-
-  return `${prefix} (@${acct})`;
-};
-
-const dateFormatOptions: Intl.DateTimeFormatOptions = {
-  month: 'short',
-  day: 'numeric',
-  year: 'numeric',
-  hour: '2-digit',
-  minute: '2-digit',
-};
-
-export const AccountHeader: React.FC<{
-  accountId: string;
-  hideTabs?: boolean;
-}> = ({ accountId, hideTabs }) => {
-  const dispatch = useAppDispatch();
-
-  const intl = useIntl();
-  const { signedIn, permissions } = useIdentity();
-  const account = useAppSelector((state) => state.accounts.get(accountId));
-  const relationship = useAppSelector((state) =>
-    state.relationships.get(accountId),
-  );
-  const hidden = useAppSelector((state) => getAccountHidden(state, accountId));
-  const featuredTags = useAppSelector(
-    (state) =>
-      state.user_lists.getIn(['featured_tags', accountId, 'items']) as any,
-  );
-  const handleLinkClick = useLinks();
-
-  const handleBlock = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    if (relationship?.blocking) {
-      dispatch(unblockAccount(account.id));
-    } else {
-      dispatch(initBlockModal(account));
-    }
-  }, [dispatch, account, relationship]);
-
-  const handleMention = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    dispatch(mentionCompose(account));
-  }, [dispatch, account]);
-
-  const handleDirect = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    dispatch(directCompose(account));
-  }, [dispatch, account]);
-
-  const handleReport = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    dispatch(initReport(account));
-  }, [dispatch, account]);
-
-  const handleReblogToggle = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    if (relationship?.showing_reblogs) {
-      dispatch(followAccount(account.id, { reblogs: false }));
-    } else {
-      dispatch(followAccount(account.id, { reblogs: true }));
-    }
-  }, [dispatch, account, relationship]);
-
-  const handleNotifyToggle = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    if (relationship?.notifying) {
-      dispatch(followAccount(account.id, { notify: false }));
-    } else {
-      dispatch(followAccount(account.id, { notify: true }));
-    }
-  }, [dispatch, account, relationship]);
-
-  const handleMute = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    if (relationship?.muting) {
-      dispatch(unmuteAccount(account.id));
-    } else {
-      dispatch(initMuteModal(account));
-    }
-  }, [dispatch, account, relationship]);
-
-  const handleBlockDomain = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    dispatch(initDomainBlockModal(account));
-  }, [dispatch, account]);
-
-  const handleUnblockDomain = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    const domain = account.acct.split('@')[1];
-
-    if (!domain) {
-      return;
-    }
-
-    dispatch(unblockDomain(domain));
-  }, [dispatch, account]);
-
-  const handleEndorseToggle = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    if (relationship?.endorsed) {
-      dispatch(unpinAccount(account.id));
-    } else {
-      dispatch(pinAccount(account.id));
-    }
-  }, [dispatch, account, relationship]);
-
-  const handleAddToList = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    dispatch(
-      openModal({
-        modalType: 'LIST_ADDER',
-        modalProps: {
-          accountId: account.id,
-        },
-      }),
-    );
-  }, [dispatch, account]);
-
-  const handleAddToAntenna = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    dispatch(
-      openModal({
-        modalType: 'ANTENNA_ADDER',
-        modalProps: {
-          accountId: account.id,
-          isExclude: false,
-        },
-      }),
-    );
-  }, [dispatch, account]);
-
-  const handleAddToExcludeAntenna = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    dispatch(
-      openModal({
-        modalType: 'ANTENNA_ADDER',
-        modalProps: {
-          accountId: account.id,
-          isExclude: true,
-        },
-      }),
-    );
-  }, [dispatch, account]);
-
-  const handleAddToCircle = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    dispatch(
-      openModal({
-        modalType: 'CIRCLE_ADDER',
-        modalProps: {
-          accountId: account.id,
-        },
-      }),
-    );
-  }, [dispatch, account]);
-
-  const handleChangeLanguages = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    dispatch(
-      openModal({
-        modalType: 'SUBSCRIBED_LANGUAGES',
-        modalProps: {
-          accountId: account.id,
-        },
-      }),
-    );
-  }, [dispatch, account]);
-
-  const handleOpenAvatar = useCallback(
-    (e: React.MouseEvent) => {
-      if (e.button !== 0 || e.ctrlKey || e.metaKey) {
-        return;
-      }
-
-      e.preventDefault();
-
-      if (!account) {
-        return;
-      }
-
-      dispatch(
-        openModal({
-          modalType: 'IMAGE',
-          modalProps: {
-            src: account.avatar,
-            alt: '',
-          },
-        }),
-      );
-    },
-    [dispatch, account],
-  );
-
-  const handleShare = useCallback(() => {
-    if (!account) {
-      return;
-    }
-
-    void navigator.share({
-      url: account.url,
-    });
-  }, [account]);
-
-  const handleMouseEnter = useCallback(
-    ({ currentTarget }: React.MouseEvent) => {
-      if (autoPlayGif) {
-        return;
-      }
-
-      currentTarget
-        .querySelectorAll<HTMLImageElement>('.custom-emoji')
-        .forEach((emoji) => {
-          emoji.src = emoji.getAttribute('data-original') ?? '';
-        });
-    },
-    [],
-  );
-
-  const handleMouseLeave = useCallback(
-    ({ currentTarget }: React.MouseEvent) => {
-      if (autoPlayGif) {
-        return;
-      }
-
-      currentTarget
-        .querySelectorAll<HTMLImageElement>('.custom-emoji')
-        .forEach((emoji) => {
-          emoji.src = emoji.getAttribute('data-static') ?? '';
-        });
-    },
-    [],
-  );
-
-  const suspended = account?.suspended;
-  const isRemote = account?.acct !== account?.username;
-  const remoteDomain = isRemote ? account?.acct.split('@')[1] : null;
-
-  const menu = useMemo(() => {
-    const arr: MenuItem[] = [];
-
-    if (!account) {
-      return arr;
-    }
-
-    if (signedIn && account.id !== me && !account.suspended) {
-      arr.push({
-        text: intl.formatMessage(messages.mention, {
-          name: account.username,
-        }),
-        action: handleMention,
-      });
-      arr.push({
-        text: intl.formatMessage(messages.direct, {
-          name: account.username,
-        }),
-        action: handleDirect,
-      });
-      arr.push(null);
-    }
-
-    if (isRemote) {
-      arr.push({
-        text: intl.formatMessage(messages.openOriginalPage),
-        href: account.url,
-      });
-      arr.push(null);
-    }
-
-    if (account.id === me) {
-      arr.push({
-        text: intl.formatMessage(messages.edit_profile),
-        href: '/settings/profile',
-      });
-      arr.push({
-        text: intl.formatMessage(messages.preferences),
-        href: '/settings/preferences',
-      });
-      arr.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
-      arr.push(null);
-      arr.push({
-        text: intl.formatMessage(messages.follow_requests),
-        to: '/follow_requests',
-      });
-      arr.push({
-        text: intl.formatMessage(messages.favourites),
-        to: '/favourites',
-      });
-      arr.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
-      arr.push({
-        text: intl.formatMessage(messages.followed_tags),
-        to: '/followed_tags',
-      });
-      arr.push(null);
-      arr.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
-      arr.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
-      arr.push({
-        text: intl.formatMessage(messages.domain_blocks),
-        to: '/domain_blocks',
-      });
-    } else if (signedIn) {
-      if (relationship?.following) {
-        if (!relationship.muting) {
-          if (relationship.showing_reblogs) {
-            arr.push({
-              text: intl.formatMessage(messages.hideReblogs, {
-                name: account.username,
-              }),
-              action: handleReblogToggle,
-            });
-          } else {
-            arr.push({
-              text: intl.formatMessage(messages.showReblogs, {
-                name: account.username,
-              }),
-              action: handleReblogToggle,
-            });
-          }
-
-          arr.push({
-            text: intl.formatMessage(messages.languages),
-            action: handleChangeLanguages,
-          });
-          arr.push(null);
-        }
-
-        arr.push({
-          text: intl.formatMessage(
-            relationship.endorsed ? messages.unendorse : messages.endorse,
-          ),
-          action: handleEndorseToggle,
-        });
-        arr.push({
-          text: intl.formatMessage(messages.add_or_remove_from_list),
-          action: handleAddToList,
-        });
-      }
-      arr.push({
-        text: intl.formatMessage(messages.add_or_remove_from_antenna),
-        action: handleAddToAntenna,
-      });
-      arr.push({
-        text: intl.formatMessage(messages.add_or_remove_from_exclude_antenna),
-        action: handleAddToExcludeAntenna,
-      });
-      if (relationship?.followed_by) {
-        arr.push({
-          text: intl.formatMessage(messages.add_or_remove_from_circle),
-          action: handleAddToCircle,
-        });
-      }
-      arr.push(null);
-
-      if (relationship?.muting) {
-        arr.push({
-          text: intl.formatMessage(messages.unmute, {
-            name: account.username,
-          }),
-          action: handleMute,
-        });
-      } else {
-        arr.push({
-          text: intl.formatMessage(messages.mute, {
-            name: account.username,
-          }),
-          action: handleMute,
-          dangerous: true,
-        });
-      }
-
-      if (relationship?.blocking) {
-        arr.push({
-          text: intl.formatMessage(messages.unblock, {
-            name: account.username,
-          }),
-          action: handleBlock,
-        });
-      } else {
-        arr.push({
-          text: intl.formatMessage(messages.block, {
-            name: account.username,
-          }),
-          action: handleBlock,
-          dangerous: true,
-        });
-      }
-
-      if (!account.suspended) {
-        arr.push({
-          text: intl.formatMessage(messages.report, {
-            name: account.username,
-          }),
-          action: handleReport,
-          dangerous: true,
-        });
-      }
-    }
-
-    if (signedIn && isRemote) {
-      arr.push(null);
-
-      if (relationship?.domain_blocking) {
-        arr.push({
-          text: intl.formatMessage(messages.unblockDomain, {
-            domain: remoteDomain,
-          }),
-          action: handleUnblockDomain,
-        });
-      } else {
-        arr.push({
-          text: intl.formatMessage(messages.blockDomain, {
-            domain: remoteDomain,
-          }),
-          action: handleBlockDomain,
-          dangerous: true,
-        });
-      }
-    }
-
-    if (
-      (account.id !== me &&
-        (permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) ||
-      (isRemote &&
-        (permissions & PERMISSION_MANAGE_FEDERATION) ===
-          PERMISSION_MANAGE_FEDERATION)
-    ) {
-      arr.push(null);
-      if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
-        arr.push({
-          text: intl.formatMessage(messages.admin_account, {
-            name: account.username,
-          }),
-          href: `/admin/accounts/${account.id}`,
-        });
-      }
-      if (
-        isRemote &&
-        (permissions & PERMISSION_MANAGE_FEDERATION) ===
-          PERMISSION_MANAGE_FEDERATION
-      ) {
-        arr.push({
-          text: intl.formatMessage(messages.admin_domain, {
-            domain: remoteDomain,
-          }),
-          href: `/admin/instances/${remoteDomain}`,
-        });
-      }
-    }
-
-    return arr;
-  }, [
-    account,
-    relationship,
-    permissions,
-    isRemote,
-    remoteDomain,
-    intl,
-    signedIn,
-    handleAddToAntenna,
-    handleAddToExcludeAntenna,
-    handleAddToCircle,
-    handleAddToList,
-    handleBlock,
-    handleBlockDomain,
-    handleChangeLanguages,
-    handleDirect,
-    handleEndorseToggle,
-    handleMention,
-    handleMute,
-    handleReblogToggle,
-    handleReport,
-    handleUnblockDomain,
-  ]);
-
-  if (!account) {
-    return null;
-  }
-
-  let actionBtn: React.ReactNode,
-    bellBtn: React.ReactNode,
-    lockedIcon: React.ReactNode,
-    shareBtn: React.ReactNode;
-
-  const info: React.ReactNode[] = [];
-
-  if (me !== account.id && relationship?.blocking) {
-    info.push(
-      <span key='blocked' className='relationship-tag'>
-        <FormattedMessage id='account.blocked' defaultMessage='Blocked' />
-      </span>,
-    );
-  }
-
-  if (me !== account.id && relationship?.muting) {
-    info.push(
-      <span key='muted' className='relationship-tag'>
-        <FormattedMessage id='account.muted' defaultMessage='Muted' />
-      </span>,
-    );
-  } else if (me !== account.id && relationship?.domain_blocking) {
-    info.push(
-      <span key='domain_blocked' className='relationship-tag'>
-        <FormattedMessage
-          id='account.domain_blocked'
-          defaultMessage='Domain blocked'
-        />
-      </span>,
-    );
-  }
-
-  if (relationship?.requested || relationship?.following) {
-    bellBtn = (
-      <IconButton
-        icon={relationship.notifying ? 'bell' : 'bell-o'}
-        iconComponent={
-          relationship.notifying ? NotificationsActiveIcon : NotificationsIcon
-        }
-        active={relationship.notifying}
-        title={intl.formatMessage(
-          relationship.notifying
-            ? messages.disableNotifications
-            : messages.enableNotifications,
-          { name: account.username },
-        )}
-        onClick={handleNotifyToggle}
-      />
-    );
-  }
-
-  if ('share' in navigator) {
-    shareBtn = (
-      <IconButton
-        className='optional'
-        icon=''
-        iconComponent={ShareIcon}
-        title={intl.formatMessage(messages.share, {
-          name: account.username,
-        })}
-        onClick={handleShare}
-      />
-    );
-  } else {
-    shareBtn = (
-      <CopyIconButton
-        className='optional'
-        title={intl.formatMessage(messages.copy)}
-        value={account.url}
-      />
-    );
-  }
-
-  if (relationship?.blocking) {
-    actionBtn = (
-      <Button
-        text={intl.formatMessage(messages.unblock, {
-          name: account.username,
-        })}
-        onClick={handleBlock}
-      />
-    );
-  } else {
-    actionBtn = <FollowButton accountId={accountId} />;
-  }
-
-  if (account.moved && !relationship?.following) {
-    actionBtn = '';
-  }
-
-  if (account.locked) {
-    lockedIcon = (
-      <Icon
-        id='lock'
-        icon={LockIcon}
-        title={intl.formatMessage(messages.account_locked)}
-      />
-    );
-  }
-
-  const content = { __html: account.note_emojified };
-  const displayNameHtml = { __html: account.display_name_html };
-  const fields = account.fields;
-  const isLocal = !account.acct.includes('@');
-  const username = account.acct.split('@')[0];
-  const domain = isLocal ? localDomain : account.acct.split('@')[1];
-  const isIndexable = !account.noindex;
-  const featuredTagsArr =
-    featuredTags?.map((tag: any) => tag.get('name')).toArray() || [];
-  const featuredTagsBar = getFeaturedHashtagBar(
-    account.id,
-    account.acct,
-    featuredTagsArr,
-  );
-
-  const badges = [];
-
-  if (account.bot) {
-    badges.push(<AutomatedBadge key='bot-badge' />);
-  } else if (account.group) {
-    badges.push(<GroupBadge key='group-badge' />);
-  }
-
-  account.roles.forEach((role) => {
-    badges.push(
-      <Badge
-        key={`role-badge-${role.get('id')}`}
-        label={<span>{role.get('name')}</span>}
-        domain={domain}
-        roleId={role.get('id')}
-      />,
-    );
-  });
-
-  return (
-    <div className='account-timeline__header'>
-      {!hidden && account.memorial && <MemorialNote />}
-      {!hidden && account.moved && (
-        <MovedNote accountId={account.id} targetAccountId={account.moved} />
-      )}
-
-      <div
-        className={classNames('account__header', {
-          inactive: !!account.moved,
-        })}
-        onMouseEnter={handleMouseEnter}
-        onMouseLeave={handleMouseLeave}
-      >
-        {!(suspended || hidden || account.moved) &&
-          relationship?.requested_by && (
-            <FollowRequestNoteContainer account={account} />
-          )}
-
-        <div className='account__header__image'>
-          <div className='account__header__info'>{info}</div>
-
-          {!(suspended || hidden) && (
-            <img
-              src={autoPlayGif ? account.header : account.header_static}
-              alt=''
-              className='parallax'
-            />
-          )}
-        </div>
-
-        <div className='account__header__bar'>
-          <div className='account__header__tabs'>
-            <a
-              className='avatar'
-              href={account.avatar}
-              rel='noopener'
-              target='_blank'
-              onClick={handleOpenAvatar}
-            >
-              <Avatar
-                account={suspended || hidden ? undefined : account}
-                size={90}
-              />
-            </a>
-
-            <div className='account__header__tabs__buttons'>
-              {!hidden && bellBtn}
-              {!hidden && shareBtn}
-              <Dropdown
-                disabled={menu.length === 0}
-                items={menu}
-                icon='ellipsis-v'
-                iconComponent={MoreHorizIcon}
-              />
-              {!hidden && actionBtn}
-            </div>
-          </div>
-
-          <div className='account__header__tabs__name'>
-            <h1>
-              <span dangerouslySetInnerHTML={displayNameHtml} />
-              <small>
-                <span>
-                  @{username}
-                  <span className='invisible'>@{domain}</span>
-                </span>
-                <DomainPill
-                  username={username ?? ''}
-                  domain={domain ?? ''}
-                  isSelf={me === account.id}
-                />
-                {lockedIcon}
-              </small>
-            </h1>
-          </div>
-
-          {badges.length > 0 && (
-            <div className='account__header__badges'>{badges}</div>
-          )}
-
-          {!(suspended || hidden) && (
-            <div className='account__header__extra'>
-              <div
-                className='account__header__bio'
-                onClickCapture={handleLinkClick}
-              >
-                {account.id !== me && signedIn && (
-                  <AccountNoteContainer accountId={accountId} />
-                )}
-
-                {account.note.length > 0 && account.note !== '<p></p>' && (
-                  <div
-                    className='account__header__content translate'
-                    dangerouslySetInnerHTML={content}
-                  />
-                )}
-
-                {featuredTagsArr.length > 0 && (
-                  <div className='account__header__featured-tags'>
-                    {featuredTagsBar}
-                  </div>
-                )}
-
-                <div className='account__header__fields'>
-                  <dl>
-                    <dt>
-                      <FormattedMessage
-                        id='account.joined_short'
-                        defaultMessage='Joined'
-                      />
-                    </dt>
-                    <dd>
-                      <FormattedDateWrapper
-                        value={account.created_at}
-                        year='numeric'
-                        month='short'
-                        day='2-digit'
-                      />
-                    </dd>
-                  </dl>
-
-                  {fields.map((pair, i) => (
-                    <dl
-                      key={i}
-                      className={classNames({
-                        verified: pair.verified_at,
-                      })}
-                    >
-                      <dt
-                        dangerouslySetInnerHTML={{
-                          __html: pair.name_emojified,
-                        }}
-                        title={pair.name}
-                        className='translate'
-                      />
-
-                      <dd className='translate' title={pair.value_plain ?? ''}>
-                        {pair.verified_at && (
-                          <span
-                            title={intl.formatMessage(messages.linkVerifiedOn, {
-                              date: intl.formatDate(
-                                pair.verified_at,
-                                dateFormatOptions,
-                              ),
-                            })}
-                          >
-                            <Icon
-                              id='check'
-                              icon={CheckIcon}
-                              className='verified__mark'
-                            />
-                          </span>
-                        )}{' '}
-                        <span
-                          dangerouslySetInnerHTML={{
-                            __html: pair.value_emojified,
-                          }}
-                        />
-                      </dd>
-                    </dl>
-                  ))}
-                </div>
-              </div>
-
-              <div className='account__header__extra__links'>
-                <NavLink
-                  to={`/@${account.acct}`}
-                  title={intl.formatNumber(account.statuses_count)}
-                >
-                  <ShortNumber
-                    value={account.statuses_count}
-                    isHide={account.other_settings.hide_statuses_count}
-                    renderer={StatusesCounter}
-                  />
-                </NavLink>
-
-                <NavLink
-                  exact
-                  to={`/@${account.acct}/following`}
-                  title={intl.formatNumber(account.following_count)}
-                >
-                  <ShortNumber
-                    value={account.following_count}
-                    isHide={account.other_settings.hide_following_count}
-                    renderer={FollowingCounter}
-                  />
-                </NavLink>
-
-                <NavLink
-                  exact
-                  to={`/@${account.acct}/followers`}
-                  title={intl.formatNumber(account.followers_count)}
-                >
-                  <ShortNumber
-                    value={account.followers_count}
-                    isHide={account.other_settings.hide_followers_count}
-                    renderer={FollowersCounter}
-                  />
-                </NavLink>
-              </div>
-            </div>
-          )}
-        </div>
-      </div>
-
-      {!(hideTabs || hidden) && (
-        <div className='account__section-headline'>
-          <NavLink exact to={`/@${account.acct}/featured`}>
-            <FormattedMessage id='account.featured' defaultMessage='Featured' />
-          </NavLink>
-          <NavLink exact to={`/@${account.acct}`}>
-            <FormattedMessage id='account.posts' defaultMessage='Posts' />
-          </NavLink>
-          <NavLink exact to={`/@${account.acct}/with_replies`}>
-            <FormattedMessage
-              id='account.posts_with_replies'
-              defaultMessage='Posts and replies'
-            />
-          </NavLink>
-          <NavLink exact to={`/@${account.acct}/media`}>
-            <FormattedMessage id='account.media' defaultMessage='Media' />
-          </NavLink>
-        </div>
-      )}
-
-      <Helmet>
-        <title>{titleFromAccount(account)}</title>
-        <meta
-          name='robots'
-          content={isLocal && isIndexable ? 'all' : 'noindex'}
-        />
-        <link rel='canonical' href={account.url} />
-      </Helmet>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.jsx b/app/javascript/mastodon/features/account_timeline/components/header.jsx
new file mode 100644
index 0000000000..d496558788
--- /dev/null
+++ b/app/javascript/mastodon/features/account_timeline/components/header.jsx
@@ -0,0 +1,175 @@
+import PropTypes from 'prop-types';
+
+import { FormattedMessage } from 'react-intl';
+
+import { NavLink } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+import InnerHeader from '../../account/components/header';
+
+import MemorialNote from './memorial_note';
+import MovedNote from './moved_note';
+
+class Header extends ImmutablePureComponent {
+
+  static propTypes = {
+    account: ImmutablePropTypes.record,
+    featuredTags: PropTypes.array,
+    onFollow: PropTypes.func.isRequired,
+    onBlock: PropTypes.func.isRequired,
+    onMention: PropTypes.func.isRequired,
+    onDirect: PropTypes.func.isRequired,
+    onReblogToggle: PropTypes.func.isRequired,
+    onReport: PropTypes.func.isRequired,
+    onMute: PropTypes.func.isRequired,
+    onBlockDomain: PropTypes.func.isRequired,
+    onUnblockDomain: PropTypes.func.isRequired,
+    onEndorseToggle: PropTypes.func.isRequired,
+    onAddToList: PropTypes.func.isRequired,
+    onAddToAntenna: PropTypes.func.isRequired,
+    onAddToExcludeAntenna: PropTypes.func.isRequired,
+    onAddToCircle: PropTypes.func.isRequired,
+    onChangeLanguages: PropTypes.func.isRequired,
+    onInteractionModal: PropTypes.func.isRequired,
+    onOpenAvatar: PropTypes.func.isRequired,
+    onOpenURL: PropTypes.func.isRequired,
+    hideTabs: PropTypes.bool,
+    domain: PropTypes.string.isRequired,
+    hidden: PropTypes.bool,
+  };
+
+  handleFollow = () => {
+    this.props.onFollow(this.props.account);
+  };
+
+  handleBlock = () => {
+    this.props.onBlock(this.props.account);
+  };
+
+  handleMention = () => {
+    this.props.onMention(this.props.account);
+  };
+
+  handleDirect = () => {
+    this.props.onDirect(this.props.account);
+  };
+
+  handleReport = () => {
+    this.props.onReport(this.props.account);
+  };
+
+  handleReblogToggle = () => {
+    this.props.onReblogToggle(this.props.account);
+  };
+
+  handleNotifyToggle = () => {
+    this.props.onNotifyToggle(this.props.account);
+  };
+
+  handleMute = () => {
+    this.props.onMute(this.props.account);
+  };
+
+  handleBlockDomain = () => {
+    this.props.onBlockDomain(this.props.account);
+  };
+
+  handleUnblockDomain = () => {
+    const domain = this.props.account.get('acct').split('@')[1];
+
+    if (!domain) return;
+
+    this.props.onUnblockDomain(domain);
+  };
+
+  handleEndorseToggle = () => {
+    this.props.onEndorseToggle(this.props.account);
+  };
+
+  handleAddToList = () => {
+    this.props.onAddToList(this.props.account);
+  };
+
+  handleAddToAntenna = () => {
+    this.props.onAddToAntenna(this.props.account);
+  };
+
+  handleAddToExcludeAntenna = () => {
+    this.props.onAddToExcludeAntenna(this.props.account);
+  };
+
+  handleAddToCircle = () => {
+    this.props.onAddToCircle(this.props.account);
+  };
+
+  handleEditAccountNote = () => {
+    this.props.onEditAccountNote(this.props.account);
+  };
+
+  handleChangeLanguages = () => {
+    this.props.onChangeLanguages(this.props.account);
+  };
+
+  handleInteractionModal = () => {
+    this.props.onInteractionModal(this.props.account);
+  };
+
+  handleOpenAvatar = () => {
+    this.props.onOpenAvatar(this.props.account);
+  };
+
+  render () {
+    const { account, featuredTags, hidden, hideTabs } = this.props;
+
+    if (account === null) {
+      return null;
+    }
+
+    return (
+      <div className='account-timeline__header'>
+        {(!hidden && account.get('memorial')) && <MemorialNote />}
+        {(!hidden && account.get('moved')) && <MovedNote from={account} to={account.get('moved')} />}
+
+        <InnerHeader
+          account={account}
+          featuredTags={featuredTags}
+          onFollow={this.handleFollow}
+          onBlock={this.handleBlock}
+          onMention={this.handleMention}
+          onDirect={this.handleDirect}
+          onReblogToggle={this.handleReblogToggle}
+          onNotifyToggle={this.handleNotifyToggle}
+          onReport={this.handleReport}
+          onMute={this.handleMute}
+          onBlockDomain={this.handleBlockDomain}
+          onUnblockDomain={this.handleUnblockDomain}
+          onEndorseToggle={this.handleEndorseToggle}
+          onAddToList={this.handleAddToList}
+          onAddToAntenna={this.handleAddToAntenna}
+          onAddToExcludeAntenna={this.handleAddToExcludeAntenna}
+          onAddToCircle={this.handleAddToCircle}
+          onEditAccountNote={this.handleEditAccountNote}
+          onChangeLanguages={this.handleChangeLanguages}
+          onInteractionModal={this.handleInteractionModal}
+          onOpenAvatar={this.handleOpenAvatar}
+          onOpenURL={this.props.onOpenURL}
+          domain={this.props.domain}
+          hidden={hidden}
+        />
+
+        {!(hideTabs || hidden) && (
+          <div className='account__section-headline'>
+            <NavLink exact to={`/@${account.get('acct')}`}><FormattedMessage id='account.posts' defaultMessage='Posts' /></NavLink>
+            <NavLink exact to={`/@${account.get('acct')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Posts and replies' /></NavLink>
+            <NavLink exact to={`/@${account.get('acct')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
+          </div>
+        )}
+      </div>
+    );
+  }
+
+}
+
+export default Header;
diff --git a/app/javascript/mastodon/features/account_timeline/components/memorial_note.tsx b/app/javascript/mastodon/features/account_timeline/components/memorial_note.jsx
similarity index 53%
rename from app/javascript/mastodon/features/account_timeline/components/memorial_note.tsx
rename to app/javascript/mastodon/features/account_timeline/components/memorial_note.jsx
index 19e6f0ed22..a04808f1ca 100644
--- a/app/javascript/mastodon/features/account_timeline/components/memorial_note.tsx
+++ b/app/javascript/mastodon/features/account_timeline/components/memorial_note.jsx
@@ -1,12 +1,11 @@
 import { FormattedMessage } from 'react-intl';
 
-export const MemorialNote: React.FC = () => (
+const MemorialNote = () => (
   <div className='account-memorial-banner'>
     <div className='account-memorial-banner__message'>
-      <FormattedMessage
-        id='account.in_memoriam'
-        defaultMessage='In Memoriam.'
-      />
+      <FormattedMessage id='account.in_memoriam' defaultMessage='In Memoriam.' />
     </div>
   </div>
 );
+
+export default MemorialNote;
diff --git a/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx b/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx
new file mode 100644
index 0000000000..2c996ff769
--- /dev/null
+++ b/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx
@@ -0,0 +1,39 @@
+import { FormattedMessage } from 'react-intl';
+
+import { Link } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+import { AvatarOverlay } from '../../../components/avatar_overlay';
+import { DisplayName } from '../../../components/display_name';
+
+export default class MovedNote extends ImmutablePureComponent {
+
+  static propTypes = {
+    from: ImmutablePropTypes.map.isRequired,
+    to: ImmutablePropTypes.map.isRequired,
+  };
+
+  render () {
+    const { from, to } = this.props;
+
+    return (
+      <div className='moved-account-banner'>
+        <div className='moved-account-banner__message'>
+          <FormattedMessage id='account.moved_to' defaultMessage='{name} has indicated that their new account is now:' values={{ name: <bdi><strong dangerouslySetInnerHTML={{ __html: from.get('display_name_html') }} /></bdi> }} />
+        </div>
+
+        <div className='moved-account-banner__action'>
+          <Link to={`/@${to.get('acct')}`} className='detailed-status__display-name'>
+            <div className='detailed-status__display-avatar'><AvatarOverlay account={to} friend={from} /></div>
+            <DisplayName account={to} />
+          </Link>
+
+          <Link to={`/@${to.get('acct')}`} className='button'><FormattedMessage id='account.go_to_profile' defaultMessage='Go to profile' /></Link>
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/account_timeline/components/moved_note.tsx b/app/javascript/mastodon/features/account_timeline/components/moved_note.tsx
deleted file mode 100644
index 51dbb93c8b..0000000000
--- a/app/javascript/mastodon/features/account_timeline/components/moved_note.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-import { Link } from 'react-router-dom';
-
-import { AvatarOverlay } from 'mastodon/components/avatar_overlay';
-import { DisplayName } from 'mastodon/components/display_name';
-import { useAppSelector } from 'mastodon/store';
-
-export const MovedNote: React.FC<{
-  accountId: string;
-  targetAccountId: string;
-}> = ({ accountId, targetAccountId }) => {
-  const from = useAppSelector((state) => state.accounts.get(accountId));
-  const to = useAppSelector((state) => state.accounts.get(targetAccountId));
-
-  return (
-    <div className='moved-account-banner'>
-      <div className='moved-account-banner__message'>
-        <FormattedMessage
-          id='account.moved_to'
-          defaultMessage='{name} has indicated that their new account is now:'
-          values={{
-            name: (
-              <bdi>
-                <strong
-                  dangerouslySetInnerHTML={{
-                    __html: from?.display_name_html ?? '',
-                  }}
-                />
-              </bdi>
-            ),
-          }}
-        />
-      </div>
-
-      <div className='moved-account-banner__action'>
-        <Link to={`/@${to?.acct}`} className='detailed-status__display-name'>
-          <div className='detailed-status__display-avatar'>
-            <AvatarOverlay account={to} friend={from} />
-          </div>
-          <DisplayName account={to} />
-        </Link>
-
-        <Link to={`/@${to?.acct}`} className='button'>
-          <FormattedMessage
-            id='account.go_to_profile'
-            defaultMessage='Go to profile'
-          />
-        </Link>
-      </div>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx
new file mode 100644
index 0000000000..2535350208
--- /dev/null
+++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx
@@ -0,0 +1,183 @@
+import { injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import { openURL } from 'mastodon/actions/search';
+
+import {
+  followAccount,
+  unblockAccount,
+  unmuteAccount,
+  pinAccount,
+  unpinAccount,
+} from '../../../actions/accounts';
+import { initBlockModal } from '../../../actions/blocks';
+import {
+  mentionCompose,
+  directCompose,
+} from '../../../actions/compose';
+import { initDomainBlockModal, unblockDomain } from '../../../actions/domain_blocks';
+import { openModal } from '../../../actions/modal';
+import { initMuteModal } from '../../../actions/mutes';
+import { initReport } from '../../../actions/reports';
+import { makeGetAccount, getAccountHidden } from '../../../selectors';
+import Header from '../components/header';
+
+const makeMapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  const mapStateToProps = (state, { accountId }) => ({
+    account: getAccount(state, accountId),
+    domain: state.getIn(['meta', 'domain']),
+    hidden: getAccountHidden(state, accountId),
+    featuredTags: state.getIn(['user_lists', 'featured_tags', accountId, 'items']),
+  });
+
+  return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch) => ({
+
+  onFollow (account) {
+    if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
+      dispatch(openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }));
+    } else {
+      dispatch(followAccount(account.get('id')));
+    }
+  },
+
+  onInteractionModal (account) {
+    dispatch(openModal({
+      modalType: 'INTERACTION',
+      modalProps: {
+        type: 'follow',
+        accountId: account.get('id'),
+        url: account.get('uri'),
+      },
+    }));
+  },
+
+  onBlock (account) {
+    if (account.getIn(['relationship', 'blocking'])) {
+      dispatch(unblockAccount(account.get('id')));
+    } else {
+      dispatch(initBlockModal(account));
+    }
+  },
+
+  onMention (account) {
+    dispatch(mentionCompose(account));
+  },
+
+  onDirect (account) {
+    dispatch(directCompose(account));
+  },
+
+  onReblogToggle (account) {
+    if (account.getIn(['relationship', 'showing_reblogs'])) {
+      dispatch(followAccount(account.get('id'), { reblogs: false }));
+    } else {
+      dispatch(followAccount(account.get('id'), { reblogs: true }));
+    }
+  },
+
+  onEndorseToggle (account) {
+    if (account.getIn(['relationship', 'endorsed'])) {
+      dispatch(unpinAccount(account.get('id')));
+    } else {
+      dispatch(pinAccount(account.get('id')));
+    }
+  },
+
+  onNotifyToggle (account) {
+    if (account.getIn(['relationship', 'notifying'])) {
+      dispatch(followAccount(account.get('id'), { notify: false }));
+    } else {
+      dispatch(followAccount(account.get('id'), { notify: true }));
+    }
+  },
+
+  onReport (account) {
+    dispatch(initReport(account));
+  },
+
+  onMute (account) {
+    if (account.getIn(['relationship', 'muting'])) {
+      dispatch(unmuteAccount(account.get('id')));
+    } else {
+      dispatch(initMuteModal(account));
+    }
+  },
+
+  onBlockDomain (account) {
+    dispatch(initDomainBlockModal(account));
+  },
+
+  onUnblockDomain (domain) {
+    dispatch(unblockDomain(domain));
+  },
+
+  onAddToList (account) {
+    dispatch(openModal({
+      modalType: 'LIST_ADDER',
+      modalProps: {
+        accountId: account.get('id'),
+      },
+    }));
+  },
+
+  onAddToAntenna (account) {
+    dispatch(openModal({
+      modalType: 'ANTENNA_ADDER',
+      modalProps: {
+        accountId: account.get('id'),
+        isExclude: false,
+      },
+    }));
+  },
+
+  onAddToExcludeAntenna (account) {
+    dispatch(openModal({
+      modalType: 'ANTENNA_ADDER',
+      modalProps: {
+        accountId: account.get('id'),
+        isExclude: true,
+      },
+    }));
+  },
+
+  onAddToCircle (account) {
+    dispatch(openModal({
+      modalType: 'CIRCLE_ADDER',
+      modalProps: {
+        accountId: account.get('id'),
+      },
+    }));
+  },
+
+  onChangeLanguages (account) {
+    dispatch(openModal({
+      modalType: 'SUBSCRIBED_LANGUAGES',
+      modalProps: {
+        accountId: account.get('id'),
+      },
+    }));
+  },
+
+  onOpenAvatar (account) {
+    dispatch(openModal({
+      modalType: 'IMAGE',
+      modalProps: {
+        src: account.get('avatar'),
+        alt: '',
+      },
+    }));
+  },
+
+  onOpenURL (url, routerHistory, onFailure) {
+    dispatch(openURL(url, routerHistory, onFailure));
+  },
+
+});
+
+export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
diff --git a/app/javascript/mastodon/features/account_timeline/index.jsx b/app/javascript/mastodon/features/account_timeline/index.jsx
index a5223275b3..105c2e4e50 100644
--- a/app/javascript/mastodon/features/account_timeline/index.jsx
+++ b/app/javascript/mastodon/features/account_timeline/index.jsx
@@ -7,10 +7,12 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { connect } from 'react-redux';
 
+import { TimelineHint } from 'mastodon/components/timeline_hint';
 import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
 import { me } from 'mastodon/initial_state';
 import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
-import { getAccountHidden } from 'mastodon/selectors/accounts';
+import { getAccountHidden } from 'mastodon/selectors';
+import { useAppSelector } from 'mastodon/store';
 
 import { lookupAccount, fetchAccount } from '../../actions/accounts';
 import { fetchFeaturedTags } from '../../actions/featured_tags';
@@ -19,10 +21,9 @@ import { ColumnBackButton } from '../../components/column_back_button';
 import { LoadingIndicator } from '../../components/loading_indicator';
 import StatusList from '../../components/status_list';
 import Column from '../ui/components/column';
-import { RemoteHint } from 'mastodon/components/remote_hint';
 
-import { AccountHeader } from './components/account_header';
 import { LimitedAccountHint } from './components/limited_account_hint';
+import HeaderContainer from './containers/header_container';
 
 const emptyList = ImmutableList();
 
@@ -46,8 +47,11 @@ const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = fa
 
   return {
     accountId,
+    remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
+    remoteUrl: state.getIn(['accounts', accountId, 'url']),
     isAccount: !!state.getIn(['accounts', accountId]),
     statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
+    featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, 'items'], emptyList),
     isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
     hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
     suspended: state.getIn(['accounts', accountId, 'suspended'], false),
@@ -56,6 +60,24 @@ const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = fa
   };
 };
 
+const RemoteHint = ({ accountId, url }) => {
+  const acct = useAppSelector(state => state.accounts.get(accountId)?.acct);
+  const domain = acct ? acct.split('@')[1] : undefined;
+
+  return (
+    <TimelineHint
+      url={url}
+      message={<FormattedMessage id='hints.profiles.posts_may_be_missing' defaultMessage='Some posts from this profile may be missing.' />}
+      label={<FormattedMessage id='hints.profiles.see_more_posts' defaultMessage='See more posts on {domain}' values={{ domain: <strong>{domain}</strong> }} />}
+    />
+  );
+};
+
+RemoteHint.propTypes = {
+  url: PropTypes.string.isRequired,
+  accountId: PropTypes.string.isRequired,
+};
+
 class AccountTimeline extends ImmutablePureComponent {
 
   static propTypes = {
@@ -67,6 +89,7 @@ class AccountTimeline extends ImmutablePureComponent {
     accountId: PropTypes.string,
     dispatch: PropTypes.func.isRequired,
     statusIds: ImmutablePropTypes.list,
+    featuredStatusIds: ImmutablePropTypes.list,
     isLoading: PropTypes.bool,
     hasMore: PropTypes.bool,
     withReplies: PropTypes.bool,
@@ -74,6 +97,8 @@ class AccountTimeline extends ImmutablePureComponent {
     isAccount: PropTypes.bool,
     suspended: PropTypes.bool,
     hidden: PropTypes.bool,
+    remote: PropTypes.bool,
+    remoteUrl: PropTypes.string,
     multiColumn: PropTypes.bool,
   };
 
@@ -136,7 +161,7 @@ class AccountTimeline extends ImmutablePureComponent {
   };
 
   render () {
-    const { accountId, statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props;
+    const { accountId, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props;
 
     if (isLoading && statusIds.isEmpty()) {
       return (
@@ -166,16 +191,19 @@ class AccountTimeline extends ImmutablePureComponent {
       emptyMessage = <FormattedMessage id='empty_column.account_timeline' defaultMessage='No posts found' />;
     }
 
+    const remoteMessage = remote ? <RemoteHint accountId={accountId} url={remoteUrl} /> : null;
+
     return (
       <Column>
         <ColumnBackButton />
 
         <StatusList
-          prepend={<AccountHeader accountId={this.props.accountId} hideTabs={forceEmptyState} tagged={this.props.params.tagged} />}
+          prepend={<HeaderContainer accountId={this.props.accountId} hideTabs={forceEmptyState} tagged={this.props.params.tagged} />}
           alwaysPrepend
-          append={<RemoteHint accountId={accountId} />}
+          append={remoteMessage}
           scrollKey='account_timeline'
           statusIds={forceEmptyState ? emptyList : statusIds}
+          featuredStatusIds={featuredStatusIds}
           isLoading={isLoading}
           hasMore={!forceEmptyState && hasMore}
           onLoadMore={this.handleLoadMore}
diff --git a/app/javascript/mastodon/features/alt_text_modal/components/info_button.tsx b/app/javascript/mastodon/features/alt_text_modal/components/info_button.tsx
deleted file mode 100644
index aecf9cbc2f..0000000000
--- a/app/javascript/mastodon/features/alt_text_modal/components/info_button.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import { useState, useRef, useCallback, useId } from 'react';
-
-import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
-
-import classNames from 'classnames';
-
-import Overlay from 'react-overlays/Overlay';
-
-import QuestionMarkIcon from '@/material-icons/400-24px/question_mark.svg?react';
-import { Icon } from 'mastodon/components/icon';
-import { useSelectableClick } from 'mastodon/hooks/useSelectableClick';
-
-const messages = defineMessages({
-  help: { id: 'info_button.label', defaultMessage: 'Help' },
-});
-
-export const InfoButton: React.FC = () => {
-  const intl = useIntl();
-  const [open, setOpen] = useState(false);
-  const triggerRef = useRef<HTMLButtonElement>(null);
-  const accessibilityId = useId();
-
-  const handleClick = useCallback(() => {
-    setOpen(!open);
-  }, [open, setOpen]);
-
-  const [handleMouseDown, handleMouseUp] = useSelectableClick(handleClick);
-
-  return (
-    <>
-      <button
-        type='button'
-        className={classNames('help-button', { active: open })}
-        ref={triggerRef}
-        onClick={handleClick}
-        aria-expanded={open}
-        aria-controls={accessibilityId}
-        aria-label={intl.formatMessage(messages.help)}
-      >
-        <Icon id='' icon={QuestionMarkIcon} />
-      </button>
-
-      <Overlay
-        show={open}
-        rootClose
-        placement='top'
-        onHide={handleClick}
-        offset={[5, 5]}
-        target={triggerRef}
-      >
-        {({ props }) => (
-          <div // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
-            {...props}
-            className='dialog-modal__popout prose dropdown-animation'
-            role='region'
-            id={accessibilityId}
-            onMouseDown={handleMouseDown}
-            onMouseUp={handleMouseUp}
-          >
-            <FormattedMessage
-              id='info_button.what_is_alt_text'
-              defaultMessage='<h1>What is alt text?</h1>
-
-            <p>Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.</p>
-
-            <p>You can improve accessibility and understanding for everyone by writing clear, concise, and objective alt text.</p>
-
-            <ul>
-              <li>Capture important elements</li>
-              <li>Summarize text in images</li>
-              <li>Use regular sentence structure</li>
-              <li>Avoid redundant information</li>
-              <li>Focus on trends and key findings in complex visuals (like diagrams or maps)</li>
-            </ul>'
-              values={{
-                h1: (node) => <h1>{node}</h1>,
-                p: (node) => <p>{node}</p>,
-                ul: (node) => <ul>{node}</ul>,
-                li: (node) => <li>{node}</li>,
-              }}
-            />
-          </div>
-        )}
-      </Overlay>
-    </>
-  );
-};
diff --git a/app/javascript/mastodon/features/alt_text_modal/index.tsx b/app/javascript/mastodon/features/alt_text_modal/index.tsx
deleted file mode 100644
index e2d05a99ca..0000000000
--- a/app/javascript/mastodon/features/alt_text_modal/index.tsx
+++ /dev/null
@@ -1,505 +0,0 @@
-import {
-  useState,
-  useCallback,
-  useRef,
-  useImperativeHandle,
-  forwardRef,
-} from 'react';
-
-import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
-
-import classNames from 'classnames';
-
-import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
-
-import { useSpring, animated } from '@react-spring/web';
-import Textarea from 'react-textarea-autosize';
-import { length } from 'stringz';
-// eslint-disable-next-line import/extensions
-import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js';
-// eslint-disable-next-line import/no-extraneous-dependencies
-import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js';
-
-import { showAlertForError } from 'mastodon/actions/alerts';
-import { uploadThumbnail } from 'mastodon/actions/compose';
-import { changeUploadCompose } from 'mastodon/actions/compose_typed';
-import { Button } from 'mastodon/components/button';
-import { GIFV } from 'mastodon/components/gifv';
-import { LoadingIndicator } from 'mastodon/components/loading_indicator';
-import { Skeleton } from 'mastodon/components/skeleton';
-import Audio from 'mastodon/features/audio';
-import { CharacterCounter } from 'mastodon/features/compose/components/character_counter';
-import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
-import { Video, getPointerPosition } from 'mastodon/features/video';
-import { me, reduceMotion } from 'mastodon/initial_state';
-import type { MediaAttachment } from 'mastodon/models/media_attachment';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-import { assetHost } from 'mastodon/utils/config';
-
-import { InfoButton } from './components/info_button';
-
-const messages = defineMessages({
-  placeholderVisual: {
-    id: 'alt_text_modal.describe_for_people_with_visual_impairments',
-    defaultMessage: 'Describe this for people with visual impairments…',
-  },
-  placeholderHearing: {
-    id: 'alt_text_modal.describe_for_people_with_hearing_impairments',
-    defaultMessage: 'Describe this for people with hearing impairments…',
-  },
-  discardMessage: {
-    id: 'confirmations.discard_edit_media.message',
-    defaultMessage:
-      'You have unsaved changes to the media description or preview, discard them anyway?',
-  },
-  discardConfirm: {
-    id: 'confirmations.discard_edit_media.confirm',
-    defaultMessage: 'Discard',
-  },
-});
-
-const MAX_LENGTH = 1500;
-
-type FocalPoint = [number, number];
-
-const UploadButton: React.FC<{
-  children: React.ReactNode;
-  onSelectFile: (arg0: File) => void;
-  mimeTypes: string;
-}> = ({ children, onSelectFile, mimeTypes }) => {
-  const fileRef = useRef<HTMLInputElement>(null);
-
-  const handleClick = useCallback(() => {
-    fileRef.current?.click();
-  }, []);
-
-  const handleChange = useCallback(
-    (e: React.ChangeEvent<HTMLInputElement>) => {
-      const file = e.target.files?.[0];
-
-      if (file) {
-        onSelectFile(file);
-      }
-    },
-    [onSelectFile],
-  );
-
-  return (
-    <label>
-      <Button onClick={handleClick}>{children}</Button>
-
-      <input
-        id='upload-modal__thumbnail'
-        ref={fileRef}
-        type='file'
-        accept={mimeTypes}
-        onChange={handleChange}
-        style={{ display: 'none' }}
-      />
-    </label>
-  );
-};
-
-const Preview: React.FC<{
-  mediaId: string;
-  position: FocalPoint;
-  onPositionChange: (arg0: FocalPoint) => void;
-}> = ({ mediaId, position, onPositionChange }) => {
-  const draggingRef = useRef<boolean>(false);
-  const nodeRef = useRef<HTMLImageElement | HTMLVideoElement | null>(null);
-
-  const [x, y] = position;
-  const style = useSpring({
-    to: {
-      left: `${x * 100}%`,
-      top: `${y * 100}%`,
-    },
-    immediate: reduceMotion || draggingRef.current,
-  });
-  const media = useAppSelector((state) =>
-    (
-      (state.compose as ImmutableMap<string, unknown>).get(
-        'media_attachments',
-      ) as ImmutableList<MediaAttachment>
-    ).find((x) => x.get('id') === mediaId),
-  );
-  const account = useAppSelector((state) =>
-    me ? state.accounts.get(me) : undefined,
-  );
-
-  const [dragging, setDragging] = useState(false);
-
-  const setRef = useCallback(
-    (e: HTMLImageElement | HTMLVideoElement | null) => {
-      nodeRef.current = e;
-    },
-    [],
-  );
-
-  const handleMouseDown = useCallback(
-    (e: React.MouseEvent) => {
-      if (e.button !== 0) {
-        return;
-      }
-
-      const handleMouseMove = (e: MouseEvent) => {
-        const { x, y } = getPointerPosition(nodeRef.current, e);
-        draggingRef.current = true; // This will disable the animation for quicker feedback, only do this if the mouse actually moves
-        onPositionChange([x, y]);
-      };
-
-      const handleMouseUp = () => {
-        setDragging(false);
-        draggingRef.current = false;
-        document.removeEventListener('mouseup', handleMouseUp);
-        document.removeEventListener('mousemove', handleMouseMove);
-      };
-
-      const { x, y } = getPointerPosition(nodeRef.current, e.nativeEvent);
-
-      setDragging(true);
-      onPositionChange([x, y]);
-
-      document.addEventListener('mouseup', handleMouseUp);
-      document.addEventListener('mousemove', handleMouseMove);
-    },
-    [setDragging, onPositionChange],
-  );
-
-  if (!media) {
-    return null;
-  }
-
-  if (media.get('type') === 'image') {
-    return (
-      <div className={classNames('focal-point', { dragging })}>
-        <img
-          ref={setRef}
-          draggable={false}
-          src={media.get('url') as string}
-          alt=''
-          role='presentation'
-          onMouseDown={handleMouseDown}
-        />
-        <animated.div className='focal-point__reticle' style={style} />
-      </div>
-    );
-  } else if (media.get('type') === 'gifv') {
-    return (
-      <div className={classNames('focal-point', { dragging })}>
-        <GIFV
-          ref={setRef}
-          src={media.get('url') as string}
-          alt=''
-          onMouseDown={handleMouseDown}
-        />
-        <animated.div className='focal-point__reticle' style={style} />
-      </div>
-    );
-  } else if (media.get('type') === 'video') {
-    return (
-      <Video
-        preview={media.get('preview_url') as string}
-        frameRate={media.getIn(['meta', 'original', 'frame_rate']) as string}
-        aspectRatio={`${media.getIn(['meta', 'original', 'width']) as number} / ${media.getIn(['meta', 'original', 'height']) as number}`}
-        blurhash={media.get('blurhash') as string}
-        src={media.get('url') as string}
-        detailed
-        editable
-      />
-    );
-  } else if (media.get('type') === 'audio') {
-    return (
-      <Audio
-        src={media.get('url') as string}
-        duration={media.getIn(['meta', 'original', 'duration'], 0) as number}
-        poster={
-          (media.get('preview_url') as string | undefined) ??
-          account?.avatar_static
-        }
-        backgroundColor={
-          media.getIn(['meta', 'colors', 'background']) as string
-        }
-        foregroundColor={
-          media.getIn(['meta', 'colors', 'foreground']) as string
-        }
-        accentColor={media.getIn(['meta', 'colors', 'accent']) as string}
-        editable
-      />
-    );
-  } else {
-    return null;
-  }
-};
-
-interface RestoreProps {
-  previousDescription: string;
-  previousPosition: FocalPoint;
-}
-
-interface Props {
-  mediaId: string;
-  onClose: () => void;
-}
-
-interface ConfirmationMessage {
-  message: string;
-  confirm: string;
-  props?: RestoreProps;
-}
-
-export interface ModalRef {
-  getCloseConfirmationMessage: () => null | ConfirmationMessage;
-}
-
-export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
-  ({ mediaId, previousDescription, previousPosition, onClose }, ref) => {
-    const intl = useIntl();
-    const dispatch = useAppDispatch();
-    const media = useAppSelector((state) =>
-      (
-        (state.compose as ImmutableMap<string, unknown>).get(
-          'media_attachments',
-        ) as ImmutableList<MediaAttachment>
-      ).find((x) => x.get('id') === mediaId),
-    );
-    const lang = useAppSelector(
-      (state) =>
-        (state.compose as ImmutableMap<string, unknown>).get('lang') as string,
-    );
-    const focusX =
-      (media?.getIn(['meta', 'focus', 'x'], 0) as number | undefined) ?? 0;
-    const focusY =
-      (media?.getIn(['meta', 'focus', 'y'], 0) as number | undefined) ?? 0;
-    const [description, setDescription] = useState(
-      previousDescription ??
-        (media?.get('description') as string | undefined) ??
-        '',
-    );
-    const [position, setPosition] = useState<FocalPoint>(
-      previousPosition ?? [focusX / 2 + 0.5, focusY / -2 + 0.5],
-    );
-    const [isDetecting, setIsDetecting] = useState(false);
-    const [isSaving, setIsSaving] = useState(false);
-    const dirtyRef = useRef(
-      previousDescription || previousPosition ? true : false,
-    );
-    const type = media?.get('type') as string;
-    const valid = length(description) <= MAX_LENGTH;
-
-    const handleDescriptionChange = useCallback(
-      (e: React.ChangeEvent<HTMLTextAreaElement>) => {
-        setDescription(e.target.value);
-        dirtyRef.current = true;
-      },
-      [setDescription],
-    );
-
-    const handleThumbnailChange = useCallback(
-      (file: File) => {
-        dispatch(uploadThumbnail(mediaId, file));
-      },
-      [dispatch, mediaId],
-    );
-
-    const handlePositionChange = useCallback(
-      (position: FocalPoint) => {
-        setPosition(position);
-        dirtyRef.current = true;
-      },
-      [setPosition],
-    );
-
-    const handleSubmit = useCallback(() => {
-      setIsSaving(true);
-
-      dispatch(
-        changeUploadCompose({
-          id: mediaId,
-          description,
-          focus: `${((position[0] - 0.5) * 2).toFixed(2)},${((position[1] - 0.5) * -2).toFixed(2)}`,
-        }),
-      )
-        .then(() => {
-          setIsSaving(false);
-          dirtyRef.current = false;
-          onClose();
-          return '';
-        })
-        .catch((err: unknown) => {
-          setIsSaving(false);
-          dispatch(showAlertForError(err));
-        });
-    }, [dispatch, setIsSaving, mediaId, onClose, position, description]);
-
-    const handleKeyUp = useCallback(
-      (e: React.KeyboardEvent) => {
-        if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
-          e.preventDefault();
-
-          if (valid) {
-            handleSubmit();
-          }
-        }
-      },
-      [handleSubmit, valid],
-    );
-
-    const handleDetectClick = useCallback(() => {
-      setIsDetecting(true);
-
-      fetchTesseract()
-        .then(async ({ createWorker }) => {
-          const worker = await createWorker('eng', 1, {
-            workerPath: tesseractWorkerPath as string,
-            corePath: tesseractCorePath as string,
-            langPath: `${assetHost}/ocr/lang-data`,
-            cacheMethod: 'write',
-          });
-
-          const image = URL.createObjectURL(media?.get('file') as File);
-          const result = await worker.recognize(image);
-
-          setDescription(result.data.text);
-          setIsDetecting(false);
-
-          await worker.terminate();
-
-          return '';
-        })
-        .catch(() => {
-          setIsDetecting(false);
-        });
-    }, [setDescription, setIsDetecting, media]);
-
-    useImperativeHandle(
-      ref,
-      () => ({
-        getCloseConfirmationMessage: () => {
-          if (dirtyRef.current) {
-            return {
-              message: intl.formatMessage(messages.discardMessage),
-              confirm: intl.formatMessage(messages.discardConfirm),
-              props: {
-                previousDescription: description,
-                previousPosition: position,
-              },
-            };
-          }
-
-          return null;
-        },
-      }),
-      [intl, description, position],
-    );
-
-    return (
-      <div className='modal-root__modal dialog-modal'>
-        <div className='dialog-modal__header'>
-          <Button onClick={handleSubmit} disabled={!valid}>
-            {isSaving ? (
-              <LoadingIndicator />
-            ) : (
-              <FormattedMessage
-                id='alt_text_modal.done'
-                defaultMessage='Done'
-              />
-            )}
-          </Button>
-
-          <span className='dialog-modal__header__title'>
-            <FormattedMessage
-              id='alt_text_modal.add_alt_text'
-              defaultMessage='Add alt text'
-            />
-          </span>
-
-          <Button secondary onClick={onClose}>
-            <FormattedMessage
-              id='alt_text_modal.cancel'
-              defaultMessage='Cancel'
-            />
-          </Button>
-        </div>
-
-        <div className='dialog-modal__content'>
-          <div className='dialog-modal__content__preview'>
-            <Preview
-              mediaId={mediaId}
-              position={position}
-              onPositionChange={handlePositionChange}
-            />
-
-            {(type === 'audio' || type === 'video') && (
-              <UploadButton
-                onSelectFile={handleThumbnailChange}
-                mimeTypes='image/jpeg,image/png,image/gif,image/heic,image/heif,image/webp,image/avif'
-              >
-                <FormattedMessage
-                  id='alt_text_modal.change_thumbnail'
-                  defaultMessage='Change thumbnail'
-                />
-              </UploadButton>
-            )}
-          </div>
-
-          <form
-            className='dialog-modal__content__form simple_form'
-            onSubmit={handleSubmit}
-          >
-            <div className='input'>
-              <div className='label_input'>
-                <Textarea
-                  id='description'
-                  value={isDetecting ? ' ' : description}
-                  onChange={handleDescriptionChange}
-                  onKeyUp={handleKeyUp}
-                  lang={lang}
-                  placeholder={intl.formatMessage(
-                    type === 'audio'
-                      ? messages.placeholderHearing
-                      : messages.placeholderVisual,
-                  )}
-                  minRows={3}
-                  disabled={isDetecting}
-                />
-
-                {isDetecting && (
-                  <div className='label_input__loading-indicator'>
-                    <Skeleton width='100%' />
-                    <Skeleton width='100%' />
-                    <Skeleton width='61%' />
-                  </div>
-                )}
-              </div>
-
-              <div className='input__toolbar'>
-                <CharacterCounter
-                  max={MAX_LENGTH}
-                  text={isDetecting ? '' : description}
-                />
-
-                <div className='spacer' />
-
-                <button
-                  className='link-button'
-                  onClick={handleDetectClick}
-                  disabled={type !== 'image' || isDetecting}
-                >
-                  <FormattedMessage
-                    id='alt_text_modal.add_text_from_image'
-                    defaultMessage='Add text from image'
-                  />
-                </button>
-
-                <InfoButton />
-              </div>
-            </div>
-          </form>
-        </div>
-      </div>
-    );
-  },
-);
-
-AltTextModal.displayName = 'AltTextModal';
diff --git a/app/javascript/mastodon/features/annual_report/archetype.tsx b/app/javascript/mastodon/features/annual_report/archetype.tsx
deleted file mode 100644
index fffbc1803e..0000000000
--- a/app/javascript/mastodon/features/annual_report/archetype.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-import booster from '@/images/archetypes/booster.png';
-import lurker from '@/images/archetypes/lurker.png';
-import oracle from '@/images/archetypes/oracle.png';
-import pollster from '@/images/archetypes/pollster.png';
-import replier from '@/images/archetypes/replier.png';
-import type { Archetype as ArchetypeData } from 'mastodon/models/annual_report';
-
-export const Archetype: React.FC<{
-  data: ArchetypeData;
-}> = ({ data }) => {
-  let illustration, label;
-
-  switch (data) {
-    case 'booster':
-      illustration = booster;
-      label = (
-        <FormattedMessage
-          id='annual_report.summary.archetype.booster'
-          defaultMessage='The cool-hunter'
-        />
-      );
-      break;
-    case 'replier':
-      illustration = replier;
-      label = (
-        <FormattedMessage
-          id='annual_report.summary.archetype.replier'
-          defaultMessage='The social butterfly'
-        />
-      );
-      break;
-    case 'pollster':
-      illustration = pollster;
-      label = (
-        <FormattedMessage
-          id='annual_report.summary.archetype.pollster'
-          defaultMessage='The pollster'
-        />
-      );
-      break;
-    case 'lurker':
-      illustration = lurker;
-      label = (
-        <FormattedMessage
-          id='annual_report.summary.archetype.lurker'
-          defaultMessage='The lurker'
-        />
-      );
-      break;
-    case 'oracle':
-      illustration = oracle;
-      label = (
-        <FormattedMessage
-          id='annual_report.summary.archetype.oracle'
-          defaultMessage='The oracle'
-        />
-      );
-      break;
-  }
-
-  return (
-    <div className='annual-report__bento__box annual-report__summary__archetype'>
-      <div className='annual-report__summary__archetype__label'>{label}</div>
-      <img src={illustration} alt='' />
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/annual_report/followers.tsx b/app/javascript/mastodon/features/annual_report/followers.tsx
deleted file mode 100644
index 196013ae9d..0000000000
--- a/app/javascript/mastodon/features/annual_report/followers.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { FormattedMessage, FormattedNumber } from 'react-intl';
-
-import { Sparklines, SparklinesCurve } from 'react-sparklines';
-
-import { ShortNumber } from 'mastodon/components/short_number';
-import type { TimeSeriesMonth } from 'mastodon/models/annual_report';
-
-export const Followers: React.FC<{
-  data: TimeSeriesMonth[];
-  total?: number;
-}> = ({ data, total }) => {
-  const change = data.reduce((sum, item) => sum + item.followers, 0);
-
-  const cumulativeGraph = data.reduce(
-    (newData, item) => [
-      ...newData,
-      item.followers + (newData[newData.length - 1] ?? 0),
-    ],
-    [0],
-  );
-
-  return (
-    <div className='annual-report__bento__box annual-report__summary__followers'>
-      <Sparklines data={cumulativeGraph} margin={0}>
-        <svg>
-          <defs>
-            <linearGradient id='gradient' x1='0%' y1='0%' x2='0%' y2='100%'>
-              <stop
-                offset='0%'
-                stopColor='var(--sparkline-gradient-top)'
-                stopOpacity='1'
-              />
-              <stop
-                offset='100%'
-                stopColor='var(--sparkline-gradient-bottom)'
-                stopOpacity='0'
-              />
-            </linearGradient>
-          </defs>
-        </svg>
-
-        <SparklinesCurve style={{ fill: 'none' }} />
-      </Sparklines>
-
-      <div className='annual-report__summary__followers__foreground'>
-        <div className='annual-report__summary__followers__number'>
-          {change > -1 ? '+' : '-'}
-          <FormattedNumber value={change} />
-        </div>
-
-        <div className='annual-report__summary__followers__label'>
-          <span>
-            <FormattedMessage
-              id='annual_report.summary.followers.followers'
-              defaultMessage='followers'
-            />
-          </span>
-          <div className='annual-report__summary__followers__footnote'>
-            <FormattedMessage
-              id='annual_report.summary.followers.total'
-              defaultMessage='{count} total'
-              values={{ count: <ShortNumber value={total ?? 0} /> }}
-            />
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/annual_report/highlighted_post.tsx b/app/javascript/mastodon/features/annual_report/highlighted_post.tsx
deleted file mode 100644
index 3a2a70713d..0000000000
--- a/app/javascript/mastodon/features/annual_report/highlighted_post.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-/* eslint-disable @typescript-eslint/no-unsafe-return,
-                  @typescript-eslint/no-explicit-any,
-                  @typescript-eslint/no-unsafe-assignment */
-
-import { useCallback } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import { toggleStatusSpoilers } from 'mastodon/actions/statuses';
-import { DetailedStatus } from 'mastodon/features/status/components/detailed_status';
-import { me } from 'mastodon/initial_state';
-import type { TopStatuses } from 'mastodon/models/annual_report';
-import { makeGetStatus, makeGetPictureInPicture } from 'mastodon/selectors';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-const getStatus = makeGetStatus() as unknown as (arg0: any, arg1: any) => any;
-const getPictureInPicture = makeGetPictureInPicture() as unknown as (
-  arg0: any,
-  arg1: any,
-) => any;
-
-export const HighlightedPost: React.FC<{
-  data: TopStatuses;
-}> = ({ data }) => {
-  let statusId, label;
-
-  if (data.by_reblogs) {
-    statusId = data.by_reblogs;
-    label = (
-      <FormattedMessage
-        id='annual_report.summary.highlighted_post.by_reblogs'
-        defaultMessage='most boosted post'
-      />
-    );
-  } else if (data.by_favourites) {
-    statusId = data.by_favourites;
-    label = (
-      <FormattedMessage
-        id='annual_report.summary.highlighted_post.by_favourites'
-        defaultMessage='most favourited post'
-      />
-    );
-  } else {
-    statusId = data.by_replies;
-    label = (
-      <FormattedMessage
-        id='annual_report.summary.highlighted_post.by_replies'
-        defaultMessage='post with the most replies'
-      />
-    );
-  }
-
-  const dispatch = useAppDispatch();
-  const domain = useAppSelector((state) => state.meta.get('domain'));
-  const status = useAppSelector((state) =>
-    statusId ? getStatus(state, { id: statusId }) : undefined,
-  );
-  const pictureInPicture = useAppSelector((state) =>
-    statusId ? getPictureInPicture(state, { id: statusId }) : undefined,
-  );
-  const account = useAppSelector((state) =>
-    me ? state.accounts.get(me) : undefined,
-  );
-
-  const handleToggleHidden = useCallback(() => {
-    dispatch(toggleStatusSpoilers(statusId));
-  }, [dispatch, statusId]);
-
-  if (!status) {
-    return (
-      <div className='annual-report__bento__box annual-report__summary__most-boosted-post' />
-    );
-  }
-
-  const displayName = (
-    <span className='display-name'>
-      <strong className='display-name__html'>
-        <FormattedMessage
-          id='annual_report.summary.highlighted_post.possessive'
-          defaultMessage="{name}'s"
-          values={{
-            name: account && (
-              <bdi
-                dangerouslySetInnerHTML={{ __html: account.display_name_html }}
-              />
-            ),
-          }}
-        />
-      </strong>
-      <span className='display-name__account'>{label}</span>
-    </span>
-  );
-
-  return (
-    <div className='annual-report__bento__box annual-report__summary__most-boosted-post'>
-      <DetailedStatus
-        status={status}
-        pictureInPicture={pictureInPicture}
-        domain={domain}
-        onToggleHidden={handleToggleHidden}
-        overrideDisplayName={displayName}
-      />
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/annual_report/index.tsx b/app/javascript/mastodon/features/annual_report/index.tsx
deleted file mode 100644
index 91f03330c0..0000000000
--- a/app/javascript/mastodon/features/annual_report/index.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import { useState, useEffect } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import {
-  importFetchedStatuses,
-  importFetchedAccounts,
-} from 'mastodon/actions/importer';
-import { apiRequestGet, apiRequestPost } from 'mastodon/api';
-import { LoadingIndicator } from 'mastodon/components/loading_indicator';
-import { me } from 'mastodon/initial_state';
-import type { Account } from 'mastodon/models/account';
-import type { AnnualReport as AnnualReportData } from 'mastodon/models/annual_report';
-import type { Status } from 'mastodon/models/status';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-import { Archetype } from './archetype';
-import { Followers } from './followers';
-import { HighlightedPost } from './highlighted_post';
-import { MostUsedHashtag } from './most_used_hashtag';
-import { NewPosts } from './new_posts';
-import { Percentile } from './percentile';
-
-interface AnnualReportResponse {
-  annual_reports: AnnualReportData[];
-  accounts: Account[];
-  statuses: Status[];
-}
-
-export const AnnualReport: React.FC<{
-  year: string;
-}> = ({ year }) => {
-  const [response, setResponse] = useState<AnnualReportResponse | null>(null);
-  const [loading, setLoading] = useState(false);
-  const currentAccount = useAppSelector((state) =>
-    me ? state.accounts.get(me) : undefined,
-  );
-  const dispatch = useAppDispatch();
-
-  useEffect(() => {
-    setLoading(true);
-
-    apiRequestGet<AnnualReportResponse>(`v1/annual_reports/${year}`)
-      .then((data) => {
-        dispatch(importFetchedStatuses(data.statuses));
-        dispatch(importFetchedAccounts(data.accounts));
-
-        setResponse(data);
-        setLoading(false);
-
-        return apiRequestPost(`v1/annual_reports/${year}/read`);
-      })
-      .catch(() => {
-        setLoading(false);
-      });
-  }, [dispatch, year, setResponse, setLoading]);
-
-  if (loading) {
-    return <LoadingIndicator />;
-  }
-
-  const report = response?.annual_reports[0];
-
-  if (!report) {
-    return null;
-  }
-
-  return (
-    <div className='annual-report'>
-      <div className='annual-report__header'>
-        <h1>
-          <FormattedMessage
-            id='annual_report.summary.thanks'
-            defaultMessage='Thanks for being part of Mastodon!'
-          />
-        </h1>
-        <p>
-          <FormattedMessage
-            id='annual_report.summary.here_it_is'
-            defaultMessage='Here is your {year} in review:'
-            values={{ year: report.year }}
-          />
-        </p>
-      </div>
-
-      <div className='annual-report__bento annual-report__summary'>
-        <Archetype data={report.data.archetype} />
-        <HighlightedPost data={report.data.top_statuses} />
-        <Followers
-          data={report.data.time_series}
-          total={currentAccount?.followers_count}
-        />
-        <MostUsedHashtag data={report.data.top_hashtags} />
-        <Percentile data={report.data.percentiles} />
-        <NewPosts data={report.data.time_series} />
-      </div>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/annual_report/most_used_app.tsx b/app/javascript/mastodon/features/annual_report/most_used_app.tsx
deleted file mode 100644
index 2d8c8aa582..0000000000
--- a/app/javascript/mastodon/features/annual_report/most_used_app.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-import type { NameAndCount } from 'mastodon/models/annual_report';
-
-export const MostUsedApp: React.FC<{
-  data: NameAndCount[];
-}> = ({ data }) => {
-  const app = data[0];
-
-  if (!app) {
-    return (
-      <div className='annual-report__bento__box annual-report__summary__most-used-app' />
-    );
-  }
-
-  return (
-    <div className='annual-report__bento__box annual-report__summary__most-used-app'>
-      <div className='annual-report__summary__most-used-app__icon'>
-        {app.name}
-      </div>
-      <div className='annual-report__summary__most-used-app__label'>
-        <FormattedMessage
-          id='annual_report.summary.most_used_app.most_used_app'
-          defaultMessage='most used app'
-        />
-      </div>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/annual_report/most_used_hashtag.tsx b/app/javascript/mastodon/features/annual_report/most_used_hashtag.tsx
deleted file mode 100644
index 4b59b89737..0000000000
--- a/app/javascript/mastodon/features/annual_report/most_used_hashtag.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-import type { NameAndCount } from 'mastodon/models/annual_report';
-
-export const MostUsedHashtag: React.FC<{
-  data: NameAndCount[];
-}> = ({ data }) => {
-  const hashtag = data[0];
-
-  return (
-    <div className='annual-report__bento__box annual-report__summary__most-used-hashtag'>
-      <div className='annual-report__summary__most-used-hashtag__hashtag'>
-        {hashtag ? (
-          <>#{hashtag.name}</>
-        ) : (
-          <FormattedMessage
-            id='annual_report.summary.most_used_hashtag.none'
-            defaultMessage='None'
-          />
-        )}
-      </div>
-      <div className='annual-report__summary__most-used-hashtag__label'>
-        <FormattedMessage
-          id='annual_report.summary.most_used_hashtag.most_used_hashtag'
-          defaultMessage='most used hashtag'
-        />
-      </div>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/annual_report/new_posts.tsx b/app/javascript/mastodon/features/annual_report/new_posts.tsx
deleted file mode 100644
index 9ead0176b2..0000000000
--- a/app/javascript/mastodon/features/annual_report/new_posts.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { FormattedNumber, FormattedMessage } from 'react-intl';
-
-import ChatBubbleIcon from '@/material-icons/400-24px/chat_bubble.svg?react';
-import type { TimeSeriesMonth } from 'mastodon/models/annual_report';
-
-export const NewPosts: React.FC<{
-  data: TimeSeriesMonth[];
-}> = ({ data }) => {
-  const posts = data.reduce((sum, item) => sum + item.statuses, 0);
-
-  return (
-    <div className='annual-report__bento__box annual-report__summary__new-posts'>
-      <svg width={500} height={500}>
-        <defs>
-          <pattern
-            id='posts'
-            x='0'
-            y='0'
-            width='32'
-            height='35'
-            patternUnits='userSpaceOnUse'
-          >
-            <circle cx='12' cy='12' r='12' fill='var(--lime)' />
-            <ChatBubbleIcon
-              fill='var(--indigo-1)'
-              x='4'
-              y='4'
-              width='16'
-              height='16'
-            />
-          </pattern>
-        </defs>
-
-        <rect
-          width={500}
-          height={500}
-          fill='url(#posts)'
-          style={{ opacity: 0.2 }}
-        />
-      </svg>
-
-      <div className='annual-report__summary__new-posts__number'>
-        <FormattedNumber value={posts} />
-      </div>
-      <div className='annual-report__summary__new-posts__label'>
-        <FormattedMessage
-          id='annual_report.summary.new_posts.new_posts'
-          defaultMessage='new posts'
-        />
-      </div>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/annual_report/percentile.tsx b/app/javascript/mastodon/features/annual_report/percentile.tsx
deleted file mode 100644
index 0cfa7d18b9..0000000000
--- a/app/javascript/mastodon/features/annual_report/percentile.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-/* eslint-disable react/jsx-no-useless-fragment */
-import { FormattedMessage, FormattedNumber } from 'react-intl';
-
-import { domain } from 'mastodon/initial_state';
-import type { Percentiles } from 'mastodon/models/annual_report';
-
-export const Percentile: React.FC<{
-  data: Percentiles;
-}> = ({ data }) => {
-  const percentile = data.statuses;
-
-  return (
-    <div className='annual-report__bento__box annual-report__summary__percentile'>
-      <FormattedMessage
-        id='annual_report.summary.percentile.text'
-        defaultMessage='<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of {domain} users.</bottomLabel>'
-        values={{
-          topLabel: (str) => (
-            <div className='annual-report__summary__percentile__label'>
-              {str}
-            </div>
-          ),
-          percentage: () => (
-            <div className='annual-report__summary__percentile__number'>
-              <FormattedNumber
-                value={Math.min(percentile, 99) / 100}
-                style='percent'
-                maximumFractionDigits={percentile < 1 ? 1 : 0}
-              />
-            </div>
-          ),
-          bottomLabel: (str) => (
-            <div>
-              <div className='annual-report__summary__percentile__label'>
-                {str}
-              </div>
-
-              {percentile < 6 && (
-                <div className='annual-report__summary__percentile__footnote'>
-                  <FormattedMessage
-                    id='annual_report.summary.percentile.we_wont_tell_bernie'
-                    defaultMessage="We won't tell Bernie."
-                  />
-                </div>
-              )}
-            </div>
-          ),
-
-          domain,
-        }}
-      >
-        {(message) => <>{message}</>}
-      </FormattedMessage>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/antenna_adder/components/antenna.jsx b/app/javascript/mastodon/features/antenna_adder/components/antenna.jsx
new file mode 100644
index 0000000000..eaedc64525
--- /dev/null
+++ b/app/javascript/mastodon/features/antenna_adder/components/antenna.jsx
@@ -0,0 +1,96 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import AddIcon from '@/material-icons/400-24px/add.svg?react';
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
+import { Icon }  from 'mastodon/components/icon';
+
+import { removeFromAntennaAdder, addToAntennaAdder, removeExcludeFromAntennaAdder, addExcludeToAntennaAdder } from '../../../actions/antennas';
+import { IconButton }  from '../../../components/icon_button';
+
+const messages = defineMessages({
+  remove: { id: 'antennas.account.remove', defaultMessage: 'Remove from antenna' },
+  add: { id: 'antennas.account.add', defaultMessage: 'Add to antenna' },
+});
+
+const MapStateToProps = (state, { antennaId, added }) => ({
+  antenna: state.get('antennas').get(antennaId),
+  added: typeof added === 'undefined' ? state.getIn(['antennaAdder', 'antennas', 'items']).includes(antennaId) : added,
+});
+
+const mapDispatchToProps = (dispatch, { antennaId }) => ({
+  onRemove: () => dispatch(removeFromAntennaAdder(antennaId)),
+  onAdd: () => dispatch(addToAntennaAdder(antennaId)),
+  onExcludeRemove: () => dispatch(removeExcludeFromAntennaAdder(antennaId)),
+  onExcludeAdd: () => dispatch(addExcludeToAntennaAdder(antennaId)),
+});
+
+class Antenna extends ImmutablePureComponent {
+
+  static propTypes = {
+    antenna: ImmutablePropTypes.map.isRequired,
+    isExclude: PropTypes.bool.isRequired,
+    intl: PropTypes.object.isRequired,
+    onRemove: PropTypes.func.isRequired,
+    onAdd: PropTypes.func.isRequired,
+    onExcludeRemove: PropTypes.func.isRequired,
+    onExcludeAdd: PropTypes.func.isRequired,
+    added: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    added: false,
+  };
+
+  handleRemove = () => {
+    if (this.props.isExclude) {
+      this.props.onExcludeRemove();
+    } else {
+      this.props.onRemove();
+    }
+  };
+
+  handleAdd = () => {
+    if (this.props.isExclude) {
+      this.props.onExcludeAdd();
+    } else {
+      this.props.onAdd();
+    }
+  };
+
+  render () {
+    const { antenna, intl, added } = this.props;
+
+    let button;
+
+    if (added) {
+      button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={this.handleRemove} />;
+    } else {
+      button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={this.handleAdd} />;
+    }
+
+    return (
+      <div className='list'>
+        <div className='list__wrapper'>
+          <div className='list__display-name'>
+            <Icon id='wifi' icon={AntennaIcon} className='column-link__icon' fixedWidth />
+            {antenna.get('title')}
+          </div>
+
+          <div className='account__relationship'>
+            {button}
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(Antenna));
diff --git a/app/javascript/mastodon/features/antenna_adder/index.jsx b/app/javascript/mastodon/features/antenna_adder/index.jsx
new file mode 100644
index 0000000000..649b4742e7
--- /dev/null
+++ b/app/javascript/mastodon/features/antenna_adder/index.jsx
@@ -0,0 +1,84 @@
+import PropTypes from 'prop-types';
+
+import { injectIntl } from 'react-intl';
+
+import { createSelector } from '@reduxjs/toolkit';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+
+import { setupAntennaAdder, resetAntennaAdder, setupExcludeAntennaAdder } from '../../actions/antennas';
+import NewAntennaForm from '../antennas/components/new_antenna_form';
+import Account from '../list_adder/components/account';
+
+import Antenna from './components/antenna';
+// hack
+
+const getOrderedAntennas = createSelector([state => state.get('antennas')], antennas => {
+  if (!antennas) {
+    return antennas;
+  }
+
+  return antennas.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
+});
+
+const mapStateToProps = state => ({
+  antennaIds: getOrderedAntennas(state).map(antenna=>antenna.get('id')),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onInitialize: accountId => dispatch(setupAntennaAdder(accountId)),
+  onExcludeInitialize: accountId => dispatch(setupExcludeAntennaAdder(accountId)),
+  onReset: () => dispatch(resetAntennaAdder()),
+});
+
+class AntennaAdder extends ImmutablePureComponent {
+
+  static propTypes = {
+    accountId: PropTypes.string.isRequired,
+    isExclude: PropTypes.bool.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    onInitialize: PropTypes.func.isRequired,
+    onExcludeInitialize: PropTypes.func.isRequired,
+    onReset: PropTypes.func.isRequired,
+    antennaIds: ImmutablePropTypes.list.isRequired,
+  };
+
+  componentDidMount () {
+    const { isExclude, onInitialize, onExcludeInitialize, accountId } = this.props;
+    if (isExclude) {
+      onExcludeInitialize(accountId);
+    } else {
+      onInitialize(accountId);
+    }
+  }
+
+  componentWillUnmount () {
+    const { onReset } = this.props;
+    onReset();
+  }
+
+  render () {
+    const { accountId, antennaIds, isExclude } = this.props;
+
+    return (
+      <div className='modal-root__modal list-adder'>
+        <div className='list-adder__account'>
+          <Account accountId={accountId} />
+        </div>
+
+        <NewAntennaForm />
+
+
+        <div className='list-adder__lists'>
+          {antennaIds.map(antennaId => <Antenna key={antennaId} antennaId={antennaId} isExclude={isExclude} />)}
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(AntennaAdder));
diff --git a/app/javascript/mastodon/features/antenna_adder/index.tsx b/app/javascript/mastodon/features/antenna_adder/index.tsx
deleted file mode 100644
index b8e2df8d47..0000000000
--- a/app/javascript/mastodon/features/antenna_adder/index.tsx
+++ /dev/null
@@ -1,232 +0,0 @@
-import { useEffect, useState, useCallback } from 'react';
-
-import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
-
-import { isFulfilled } from '@reduxjs/toolkit';
-
-import CloseIcon from '@/material-icons/400-24px/close.svg?react';
-import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
-import { fetchAntennas } from 'mastodon/actions/antennas';
-import { createAntenna } from 'mastodon/actions/antennas_typed';
-import {
-  apiGetAccountAntennas,
-  apiAddAccountToAntenna,
-  apiAddExcludeAccountToAntenna,
-  apiRemoveAccountFromAntenna,
-  apiRemoveExcludeAccountFromAntenna,
-  apiGetExcludeAccountAntennas,
-} from 'mastodon/api/antennas';
-import type { ApiAntennaJSON } from 'mastodon/api_types/antennas';
-import { Button } from 'mastodon/components/button';
-import { CheckBox } from 'mastodon/components/check_box';
-import { Icon } from 'mastodon/components/icon';
-import { IconButton } from 'mastodon/components/icon_button';
-import { getOrderedAntennas } from 'mastodon/selectors/antennas';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  newAntenna: {
-    id: 'antennas.new_antenna_name',
-    defaultMessage: 'New antenna name',
-  },
-  createAntenna: {
-    id: 'antennas.create',
-    defaultMessage: 'Create',
-  },
-  close: {
-    id: 'lightbox.close',
-    defaultMessage: 'Close',
-  },
-});
-
-const AntennaItem: React.FC<{
-  id: string;
-  title: string;
-  checked: boolean;
-  onChange: (id: string, checked: boolean) => void;
-}> = ({ id, title, checked, onChange }) => {
-  const handleChange = useCallback(
-    (e: React.ChangeEvent<HTMLInputElement>) => {
-      onChange(id, e.target.checked);
-    },
-    [id, onChange],
-  );
-
-  return (
-    // eslint-disable-next-line jsx-a11y/label-has-associated-control
-    <label className='lists__item'>
-      <div className='lists__item__title'>
-        <Icon id='antenna-ul' icon={AntennaIcon} />
-        <span>{title}</span>
-      </div>
-
-      <CheckBox value={id} checked={checked} onChange={handleChange} />
-    </label>
-  );
-};
-
-const NewAntennaItem: React.FC<{
-  onCreate: (antenna: ApiAntennaJSON) => void;
-}> = ({ onCreate }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const [title, setTitle] = useState('');
-
-  const handleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setTitle(value);
-    },
-    [setTitle],
-  );
-
-  const handleSubmit = useCallback(() => {
-    if (title.trim().length === 0) {
-      return;
-    }
-
-    void dispatch(createAntenna({ title })).then((result) => {
-      if (isFulfilled(result)) {
-        onCreate(result.payload);
-        setTitle('');
-      }
-
-      return '';
-    });
-  }, [setTitle, dispatch, onCreate, title]);
-
-  return (
-    <form className='lists__item' onSubmit={handleSubmit}>
-      <label className='lists__item__title'>
-        <Icon id='antenna-ul' icon={AntennaIcon} />
-
-        <input
-          type='text'
-          value={title}
-          onChange={handleChange}
-          maxLength={30}
-          required
-          placeholder={intl.formatMessage(messages.newAntenna)}
-        />
-      </label>
-
-      <Button text={intl.formatMessage(messages.createAntenna)} type='submit' />
-    </form>
-  );
-};
-
-const AntennaAdder: React.FC<{
-  accountId: string;
-  isExclude: boolean;
-  onClose: () => void;
-}> = ({ accountId, isExclude, onClose }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const account = useAppSelector((state) => state.accounts.get(accountId));
-  const antennas = useAppSelector((state) => getOrderedAntennas(state));
-  const [antennaIds, setAntennaIds] = useState<string[]>([]);
-
-  useEffect(() => {
-    dispatch(fetchAntennas());
-
-    const api = isExclude
-      ? apiGetExcludeAccountAntennas
-      : apiGetAccountAntennas;
-
-    api(accountId)
-      .then((data) => {
-        setAntennaIds(data.map((l) => l.id));
-        return '';
-      })
-      .catch(() => {
-        // Nothing
-      });
-  }, [dispatch, setAntennaIds, accountId, isExclude]);
-
-  const handleToggle = useCallback(
-    (antennaId: string, checked: boolean) => {
-      if (checked) {
-        setAntennaIds((currentAntennaIds) => [antennaId, ...currentAntennaIds]);
-
-        const func = isExclude
-          ? apiAddExcludeAccountToAntenna
-          : apiAddAccountToAntenna;
-
-        func(antennaId, accountId).catch(() => {
-          setAntennaIds((currentAntennaIds) =>
-            currentAntennaIds.filter((id) => id !== antennaId),
-          );
-        });
-      } else {
-        setAntennaIds((currentAntennaIds) =>
-          currentAntennaIds.filter((id) => id !== antennaId),
-        );
-
-        const func = isExclude
-          ? apiRemoveExcludeAccountFromAntenna
-          : apiRemoveAccountFromAntenna;
-
-        func(antennaId, accountId).catch(() => {
-          setAntennaIds((currentAntennaIds) => [
-            antennaId,
-            ...currentAntennaIds,
-          ]);
-        });
-      }
-    },
-    [setAntennaIds, accountId, isExclude],
-  );
-
-  const handleCreate = useCallback(
-    (antenna: ApiAntennaJSON) => {
-      setAntennaIds((currentAntennaIds) => [antenna.id, ...currentAntennaIds]);
-
-      apiAddAccountToAntenna(antenna.id, accountId).catch(() => {
-        setAntennaIds((currentAntennaIds) =>
-          currentAntennaIds.filter((id) => id !== antenna.id),
-        );
-      });
-    },
-    [setAntennaIds, accountId],
-  );
-
-  return (
-    <div className='modal-root__modal dialog-modal'>
-      <div className='dialog-modal__header'>
-        <IconButton
-          className='dialog-modal__header__close'
-          title={intl.formatMessage(messages.close)}
-          icon='times'
-          iconComponent={CloseIcon}
-          onClick={onClose}
-        />
-
-        <span className='dialog-modal__header__title'>
-          <FormattedMessage
-            id='antennas.add_to_antennas'
-            defaultMessage='Add {name} to antennas'
-            values={{ name: <strong>@{account?.acct}</strong> }}
-          />
-        </span>
-      </div>
-
-      <div className='dialog-modal__content'>
-        <div className='lists-scrollable'>
-          <NewAntennaItem onCreate={handleCreate} />
-
-          {antennas.map((antenna) => (
-            <AntennaItem
-              key={antenna.id}
-              id={antenna.id}
-              title={antenna.title}
-              checked={antennaIds.includes(antenna.id)}
-              onChange={handleToggle}
-            />
-          ))}
-        </div>
-      </div>
-    </div>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default AntennaAdder;
diff --git a/app/javascript/mastodon/features/antenna_editor/components/account.jsx b/app/javascript/mastodon/features/antenna_editor/components/account.jsx
new file mode 100644
index 0000000000..512e399618
--- /dev/null
+++ b/app/javascript/mastodon/features/antenna_editor/components/account.jsx
@@ -0,0 +1,88 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import AddIcon from '@/material-icons/400-24px/add.svg?react';
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+
+import { removeFromAntennaEditor, addToAntennaEditor, removeExcludeFromAntennaEditor, addExcludeToAntennaEditor } from '../../../actions/antennas';
+import { Avatar } from '../../../components/avatar';
+import { DisplayName } from '../../../components/display_name';
+import { IconButton } from '../../../components/icon_button';
+import { makeGetAccount } from '../../../selectors';
+
+const messages = defineMessages({
+  remove: { id: 'antennas.account.remove', defaultMessage: 'Remove from antenna' },
+  add: { id: 'antennas.account.add', defaultMessage: 'Add to antenna' },
+});
+
+const makeMapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  const mapStateToProps = (state, { accountId, added }) => ({
+    account: getAccount(state, accountId),
+    added: typeof added === 'undefined' ? state.getIn(['antennaEditor', 'accounts', 'items']).includes(accountId) : added,
+  });
+
+  return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch, { accountId }) => ({
+  onRemove: () => dispatch(removeFromAntennaEditor(accountId)),
+  onAdd: () => dispatch(addToAntennaEditor(accountId)),
+  onExcludeRemove: () => dispatch(removeExcludeFromAntennaEditor(accountId)),
+  onExcludeAdd: () => dispatch(addExcludeToAntennaEditor(accountId)),
+});
+
+class Account extends ImmutablePureComponent {
+
+  static propTypes = {
+    account: ImmutablePropTypes.map.isRequired,
+    isExclude: PropTypes.bool.isRequired,
+    intl: PropTypes.object.isRequired,
+    onRemove: PropTypes.func.isRequired,
+    onAdd: PropTypes.func.isRequired,
+    onExcludeRemove: PropTypes.func.isRequired,
+    onExcludeAdd: PropTypes.func.isRequired,
+    added: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    added: false,
+  };
+
+  render () {
+    const { account, intl, isExclude, onRemove, onAdd, onExcludeRemove, onExcludeAdd, added } = this.props;
+
+    let button;
+
+    if (added) {
+      button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={isExclude ? onExcludeRemove : onRemove} />;
+    } else {
+      button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={isExclude ? onExcludeAdd : onAdd} />;
+    }
+
+    return (
+      <div className='account'>
+        <div className='account__wrapper'>
+          <div className='account__display-name'>
+            <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
+            <DisplayName account={account} />
+          </div>
+
+          <div className='account__relationship'>
+            {button}
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(Account));
diff --git a/app/javascript/mastodon/features/antenna_editor/components/edit_antenna_form.jsx b/app/javascript/mastodon/features/antenna_editor/components/edit_antenna_form.jsx
new file mode 100644
index 0000000000..1a6361c60f
--- /dev/null
+++ b/app/javascript/mastodon/features/antenna_editor/components/edit_antenna_form.jsx
@@ -0,0 +1,76 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import CheckIcon from '@/material-icons/400-24px/check.svg?react';
+
+import { changeAntennaEditorTitle, submitAntennaEditor } from '../../../actions/antennas';
+import { IconButton } from '../../../components/icon_button';
+
+const messages = defineMessages({
+  title: { id: 'antennas.edit.submit', defaultMessage: 'Change title' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['antennaEditor', 'title']),
+  disabled: !state.getIn(['antennaEditor', 'isChanged']) || !state.getIn(['antennaEditor', 'title']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onChange: value => dispatch(changeAntennaEditorTitle(value)),
+  onSubmit: () => dispatch(submitAntennaEditor(false)),
+});
+
+class AntennaForm extends PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    disabled: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleSubmit = e => {
+    e.preventDefault();
+    this.props.onSubmit();
+  };
+
+  handleClick = () => {
+    this.props.onSubmit();
+  };
+
+  render () {
+    const { value, disabled, intl } = this.props;
+
+    const title = intl.formatMessage(messages.title);
+
+    return (
+      <form className='column-inline-form' onSubmit={this.handleSubmit}>
+        <input
+          className='setting-text'
+          value={value}
+          onChange={this.handleChange}
+        />
+
+        <IconButton
+          disabled={disabled}
+          icon='check'
+          iconComponent={CheckIcon}
+          title={title}
+          onClick={this.handleClick}
+        />
+      </form>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(AntennaForm));
diff --git a/app/javascript/mastodon/features/antenna_editor/components/search.jsx b/app/javascript/mastodon/features/antenna_editor/components/search.jsx
new file mode 100644
index 0000000000..80b7c1dcda
--- /dev/null
+++ b/app/javascript/mastodon/features/antenna_editor/components/search.jsx
@@ -0,0 +1,83 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import classNames from 'classnames';
+
+import { connect } from 'react-redux';
+
+import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
+import SearchIcon from '@/material-icons/400-24px/search.svg?react';
+import { Icon }  from 'mastodon/components/icon';
+
+import { fetchAntennaSuggestions, clearAntennaSuggestions, changeAntennaSuggestions } from '../../../actions/antennas';
+
+const messages = defineMessages({
+  search: { id: 'antennas.search', defaultMessage: 'Search among people you follow' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['antennaEditor', 'suggestions', 'value']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onSubmit: value => dispatch(fetchAntennaSuggestions(value)),
+  onClear: () => dispatch(clearAntennaSuggestions()),
+  onChange: value => dispatch(changeAntennaSuggestions(value)),
+});
+
+class Search extends PureComponent {
+
+  static propTypes = {
+    intl: PropTypes.object.isRequired,
+    value: PropTypes.string.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+    onClear: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleKeyUp = e => {
+    if (e.keyCode === 13) {
+      this.props.onSubmit(this.props.value);
+    }
+  };
+
+  handleClear = () => {
+    this.props.onClear();
+  };
+
+  render () {
+    const { value, intl } = this.props;
+    const hasValue = value.length > 0;
+
+    return (
+      <div className='list-editor__search search'>
+        <label>
+          <span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
+
+          <input
+            className='search__input'
+            type='text'
+            value={value}
+            onChange={this.handleChange}
+            onKeyUp={this.handleKeyUp}
+            placeholder={intl.formatMessage(messages.search)}
+          />
+        </label>
+
+        <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
+          <Icon id='search' icon={SearchIcon} className={classNames({ active: !hasValue })} />
+          <Icon id='times-circle' icon={CancelIcon} aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Search));
diff --git a/app/javascript/mastodon/features/antenna_editor/index.jsx b/app/javascript/mastodon/features/antenna_editor/index.jsx
new file mode 100644
index 0000000000..95b6b43f88
--- /dev/null
+++ b/app/javascript/mastodon/features/antenna_editor/index.jsx
@@ -0,0 +1,84 @@
+import PropTypes from 'prop-types';
+
+import { injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import spring from 'react-motion/lib/spring';
+
+import { setupAntennaEditor, setupExcludeAntennaEditor, clearAntennaSuggestions, resetAntennaEditor } from '../../actions/antennas';
+import Motion from '../ui/util/optional_motion';
+
+import Account from './components/account';
+import EditAntennaForm from './components/edit_antenna_form';
+import Search from './components/search';
+
+const mapStateToProps = (state) => ({
+  accountIds: state.getIn(['antennaEditor', 'accounts', 'items']),
+  searchAccountIds: state.getIn(['antennaEditor', 'suggestions', 'items']),
+});
+
+const mapDispatchToProps = (dispatch, { isExclude }) => ({
+  onInitialize: antennaId => dispatch(isExclude ? setupExcludeAntennaEditor(antennaId) : setupAntennaEditor(antennaId)),
+  onClear: () => dispatch(clearAntennaSuggestions()),
+  onReset: () => dispatch(resetAntennaEditor()),
+});
+
+class AntennaEditor extends ImmutablePureComponent {
+
+  static propTypes = {
+    antennaId: PropTypes.string.isRequired,
+    isExclude: PropTypes.bool.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    onInitialize: PropTypes.func.isRequired,
+    onClear: PropTypes.func.isRequired,
+    onReset: PropTypes.func.isRequired,
+    accountIds: ImmutablePropTypes.list.isRequired,
+    searchAccountIds: ImmutablePropTypes.list.isRequired,
+  };
+
+  componentDidMount () {
+    const { onInitialize, antennaId } = this.props;
+    onInitialize(antennaId);
+  }
+
+  componentWillUnmount () {
+    const { onReset } = this.props;
+    onReset();
+  }
+
+  render () {
+    const { accountIds, searchAccountIds, onClear, isExclude } = this.props;
+    const showSearch = searchAccountIds.size > 0;
+
+    return (
+      <div className='modal-root__modal list-editor'>
+        <EditAntennaForm />
+
+        <Search />
+
+        <div className='drawer__pager'>
+          <div className='drawer__inner list-editor__accounts'>
+            {accountIds.map(accountId => <Account key={accountId} accountId={accountId} isExclude={isExclude} added />)}
+          </div>
+
+          {showSearch && <div role='button' tabIndex={-1} className='drawer__backdrop' onClick={onClear} />}
+
+          <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
+            {({ x }) => (
+              <div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
+                {searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} isExclude={isExclude} />)}
+              </div>
+            )}
+          </Motion>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(AntennaEditor));
diff --git a/app/javascript/mastodon/features/antenna_setting/components/radio_panel.jsx b/app/javascript/mastodon/features/antenna_setting/components/radio_panel.jsx
new file mode 100644
index 0000000000..9408d13569
--- /dev/null
+++ b/app/javascript/mastodon/features/antenna_setting/components/radio_panel.jsx
@@ -0,0 +1,46 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { injectIntl } from 'react-intl';
+
+import classNames from 'classnames';
+
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
+
+class RadioPanel extends PureComponent {
+
+  static propTypes = {
+    values: ImmutablePropTypes.list.isRequired,
+    value: PropTypes.object.isRequired,
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    const value = e.currentTarget.getAttribute('data-value');
+
+    if (value !== this.props.value.get('value')) {
+      this.props.onChange(value);
+    }
+  };
+
+  render () {
+    const { values, value } = this.props;
+
+    return (
+      <div className='setting-radio-panel'>
+        {values.map((val) => (
+          <button className={classNames('setting-radio-panel__item', {'setting-radio-panel__item__active': value.get('value') === val.get('value')})}
+            key={val.get('value')} onClick={this.handleChange} data-value={val.get('value')}>
+            {val.get('label')}
+          </button>
+        ))}
+      </div>
+    );
+  }
+
+}
+
+export default connect()(injectIntl(RadioPanel));
diff --git a/app/javascript/mastodon/features/antenna_setting/components/text_list.jsx b/app/javascript/mastodon/features/antenna_setting/components/text_list.jsx
new file mode 100644
index 0000000000..040e5b74f1
--- /dev/null
+++ b/app/javascript/mastodon/features/antenna_setting/components/text_list.jsx
@@ -0,0 +1,104 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
+
+import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
+import { Button } from 'mastodon/components/button';
+import { Icon } from 'mastodon/components/icon';
+import { IconButton } from 'mastodon/components/icon_button';
+
+class TextListItem extends PureComponent {
+
+  static propTypes = {
+    icon: PropTypes.string.isRequired,
+    iconComponent: PropTypes.func.isRequired,
+    value: PropTypes.string.isRequired,
+    onRemove: PropTypes.func.isRequired,
+  };
+
+  handleRemove = () => {
+    this.props.onRemove(this.props.value);
+  };
+
+  render () {
+    const { icon, iconComponent, value } = this.props;
+
+    return (
+      <div className='setting-text-list-item'>
+        <Icon id={icon} icon={iconComponent} />
+        <span className='label'>{value}</span>
+        <IconButton icon='trash' iconComponent={DeleteIcon} onClick={this.handleRemove} />
+      </div>
+    );
+  }
+
+}
+
+class TextList extends PureComponent {
+
+  static propTypes = {
+    values: ImmutablePropTypes.list.isRequired,
+    value: PropTypes.string.isRequired,
+    disabled: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    icon: PropTypes.string.isRequired,
+    iconComponent: PropTypes.func.isRequired,
+    label: PropTypes.string.isRequired,
+    title: PropTypes.string.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onAdd: PropTypes.func.isRequired,
+    onRemove: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleAdd = () => {
+    this.props.onAdd();
+  };
+
+  handleSubmit = (e) => {
+    e.preventDefault();
+    this.handleAdd();
+  };
+
+  render () {
+    const { icon, iconComponent, value, values, disabled, label, title } = this.props;
+
+    return (
+      <div className='setting-text-list'>
+        {values.map((val) => (
+          <TextListItem key={val} value={val} icon={icon} iconComponent={iconComponent} onRemove={this.props.onRemove} />
+        ))}
+
+        <form className='add-text-form' onSubmit={this.handleSubmit}>
+          <label>
+            <span style={{ display: 'none' }}>{label}</span>
+
+            <input
+              className='setting-text'
+              value={value}
+              disabled={disabled}
+              onChange={this.handleChange}
+              placeholder={label}
+            />
+          </label>
+
+          <Button
+            disabled={disabled || !value}
+            text={title}
+            onClick={this.handleAdd}
+          />
+        </form>
+      </div>
+    );
+  }
+
+}
+
+export default connect()(injectIntl(TextList));
diff --git a/app/javascript/mastodon/features/antenna_setting/index.jsx b/app/javascript/mastodon/features/antenna_setting/index.jsx
new file mode 100644
index 0000000000..99aa4e2aba
--- /dev/null
+++ b/app/javascript/mastodon/features/antenna_setting/index.jsx
@@ -0,0 +1,587 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
+
+
+import { Helmet } from 'react-helmet';
+import { withRouter } from 'react-router-dom';
+
+import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
+
+import Select, { NonceProvider } from 'react-select';
+import Toggle from 'react-toggle';
+
+import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
+import DomainIcon from '@/material-icons/400-24px/dns.svg?react';
+import EditIcon from '@/material-icons/400-24px/edit.svg?react';
+import HashtagIcon from '@/material-icons/400-24px/tag.svg?react';
+import KeywordIcon from '@/material-icons/400-24px/title.svg?react';
+import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
+import {
+  fetchAntenna,
+  deleteAntenna,
+  updateAntenna,
+  addDomainToAntenna,
+  removeDomainFromAntenna,
+  addExcludeDomainToAntenna,
+  removeExcludeDomainFromAntenna,
+  fetchAntennaDomains,
+  fetchAntennaKeywords,
+  removeKeywordFromAntenna,
+  addKeywordToAntenna,
+  removeExcludeKeywordFromAntenna,
+  addExcludeKeywordToAntenna,
+  fetchAntennaTags,
+  removeTagFromAntenna,
+  addTagToAntenna,
+  removeExcludeTagFromAntenna,
+  addExcludeTagToAntenna,
+} from 'mastodon/actions/antennas';
+import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
+import { fetchLists } from 'mastodon/actions/lists';
+import { openModal } from 'mastodon/actions/modal';
+import { Button } from 'mastodon/components/button';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import { Icon }  from 'mastodon/components/icon';
+import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
+import { enableLocalTimeline } from 'mastodon/initial_state';
+import { WithRouterPropTypes } from 'mastodon/utils/react_router';
+
+import RadioPanel from './components/radio_panel';
+import TextList from './components/text_list';
+
+const messages = defineMessages({
+  deleteMessage: { id: 'confirmations.delete_antenna.message', defaultMessage: 'Are you sure you want to permanently delete this antenna?' },
+  deleteConfirm: { id: 'confirmations.delete_antenna.confirm', defaultMessage: 'Delete' },
+  editAccounts: { id: 'antennas.edit_accounts', defaultMessage: 'Edit accounts' },
+  noOptions: { id: 'antennas.select.no_options_message', defaultMessage: 'Empty lists' },
+  placeholder: { id: 'antennas.select.placeholder', defaultMessage: 'Select list' },
+  addDomainLabel: { id: 'antennas.add_domain_placeholder', defaultMessage: 'New domain' },
+  addKeywordLabel: { id: 'antennas.add_keyword_placeholder', defaultMessage: 'New keyword' },
+  addTagLabel: { id: 'antennas.add_tag_placeholder', defaultMessage: 'New tag' },
+  addDomainTitle: { id: 'antennas.add_domain', defaultMessage: 'Add domain' },
+  addKeywordTitle: { id: 'antennas.add_keyword', defaultMessage: 'Add keyword' },
+  addTagTitle: { id: 'antennas.add_tag', defaultMessage: 'Add tag' },
+  accounts: { id: 'antennas.accounts', defaultMessage: '{count} accounts' },
+  domains: { id: 'antennas.domains', defaultMessage: '{count} domains' },
+  tags: { id: 'antennas.tags', defaultMessage: '{count} tags' },
+  keywords: { id: 'antennas.keywords', defaultMessage: '{count} keywords' },
+  setHome: { id: 'antennas.select.set_home', defaultMessage: 'Set home' },
+});
+
+const mapStateToProps = (state, props) => ({
+  antenna: state.getIn(['antennas', props.params.id]),
+  lists: state.get('lists'),
+  domains: state.getIn(['antennas', props.params.id, 'domains']) || ImmutableMap(),
+  keywords: state.getIn(['antennas', props.params.id, 'keywords']) || ImmutableMap(),
+  tags: state.getIn(['antennas', props.params.id, 'tags']) || ImmutableMap(),
+});
+
+class AntennaSetting extends PureComponent {
+
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    columnId: PropTypes.string,
+    multiColumn: PropTypes.bool,
+    antenna: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
+    lists: ImmutablePropTypes.map,
+    domains: ImmutablePropTypes.map,
+    keywords: ImmutablePropTypes.map,
+    tags: ImmutablePropTypes.map,
+    intl: PropTypes.object.isRequired,
+    ...WithRouterPropTypes,
+  };
+
+  state = {
+    domainName: '',
+    excludeDomainName: '',
+    keywordName: '',
+    excludeKeywordName: '',
+    tagName: '',
+    excludeTagName: '',
+    rangeRadioValue: null,
+    contentRadioValue: null,
+  };
+
+  handlePin = () => {
+    const { columnId, dispatch } = this.props;
+
+    if (columnId) {
+      dispatch(removeColumn(columnId));
+    } else {
+      dispatch(addColumn('ANTENNA', { id: this.props.params.id }));
+      this.props.history.push('/');
+    }
+  };
+
+  handleMove = (dir) => {
+    const { columnId, dispatch } = this.props;
+    dispatch(moveColumn(columnId, dir));
+  };
+
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  };
+
+  componentDidMount () {
+    const { dispatch } = this.props;
+    const { id } = this.props.params;
+
+    dispatch(fetchAntenna(id));
+    dispatch(fetchAntennaDomains(id));
+    dispatch(fetchAntennaKeywords(id));
+    dispatch(fetchAntennaTags(id));
+    dispatch(fetchLists());
+  }
+
+  UNSAFE_componentWillReceiveProps (nextProps) {
+    const { dispatch } = this.props;
+    const { id } = nextProps.params;
+
+    if (id !== this.props.params.id) {
+      dispatch(fetchAntenna(id));
+      dispatch(fetchAntennaKeywords(id));
+      dispatch(fetchAntennaDomains(id));
+      dispatch(fetchAntennaKeywords(id));
+      dispatch(fetchAntennaTags(id));
+      dispatch(fetchLists());
+    }
+  }
+
+  setRef = c => {
+    this.column = c;
+  };
+
+  handleEditClick = () => {
+    this.props.dispatch(openModal({
+      modalType: 'ANTENNA_EDITOR',
+      modalProps: { antennaId: this.props.params.id, isExclude: false },
+    }));
+  };
+
+  handleExcludeEditClick = () => {
+    this.props.dispatch(openModal({
+      modalType: 'ANTENNA_EDITOR',
+      modalProps: { antennaId: this.props.params.id, isExclude: true },
+    }));
+  };
+
+  handleEditAntennaClick = () => {
+    window.open(`/antennas/${this.props.params.id}/edit`, '_blank');
+  };
+
+  handleDeleteClick = () => {
+    const { dispatch, columnId, intl } = this.props;
+    const { id } = this.props.params;
+
+    dispatch(openModal({
+      modalType: 'CONFIRM',
+      modalProps: {
+        message: intl.formatMessage(messages.deleteMessage),
+        confirm: intl.formatMessage(messages.deleteConfirm),
+        onConfirm: () => {
+          dispatch(deleteAntenna(id));
+
+          if (columnId) {
+            dispatch(removeColumn(columnId));
+          } else {
+            this.props.history.push('/antennasw');
+          }
+        },
+      },
+    }));
+  };
+
+  handleTimelineClick = () => {
+    this.props.history.push(`/antennast/${this.props.params.id}`);
+  };
+
+  onStlToggle = ({ target }) => {
+    const { dispatch } = this.props;
+    const { id } = this.props.params;
+    dispatch(updateAntenna(id, undefined, false, undefined, target.checked, undefined, undefined, undefined, undefined));
+  };
+
+  onLtlToggle = ({ target }) => {
+    const { dispatch } = this.props;
+    const { id } = this.props.params;
+    dispatch(updateAntenna(id, undefined, false, undefined, undefined, target.checked, undefined, undefined, undefined));
+  };
+
+  onMediaOnlyToggle = ({ target }) => {
+    const { dispatch } = this.props;
+    const { id } = this.props.params;
+    dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, target.checked, undefined, undefined));
+  };
+
+  onIgnoreReblogToggle = ({ target }) => {
+    const { dispatch } = this.props;
+    const { id } = this.props.params;
+    dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, undefined, target.checked, undefined));
+  };
+
+  onNoInsertFeedsToggle = ({ target }) => {
+    const { dispatch } = this.props;
+    const { id } = this.props.params;
+    dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, undefined, undefined, target.checked));
+  };
+
+  onSelect = value => {
+    const { dispatch } = this.props;
+    const { id } = this.props.params;
+    dispatch(updateAntenna(id, undefined, false, value.value, undefined, undefined, undefined, undefined, undefined));
+  };
+
+  onHomeSelect = () => this.onSelect({ value: '0' });
+
+  noOptionsMessage = () => this.props.intl.formatMessage(messages.noOptions);
+
+  onRangeRadioChanged = (value) => this.setState({ rangeRadioValue: value });
+
+  onContentRadioChanged = (value) => this.setState({ contentRadioValue: value });
+
+  onDomainNameChanged = (value) => this.setState({ domainName: value });
+
+  onDomainAdd = () => {
+    this.props.dispatch(addDomainToAntenna(this.props.params.id, this.state.domainName));
+    this.setState({ domainName: '' });
+  };
+
+  onDomainRemove = (value) => this.props.dispatch(removeDomainFromAntenna(this.props.params.id, value));
+
+  onKeywordNameChanged = (value) => this.setState({ keywordName: value });
+
+  onKeywordAdd = () => {
+    this.props.dispatch(addKeywordToAntenna(this.props.params.id, this.state.keywordName));
+    this.setState({ keywordName: '' });
+  };
+
+  onKeywordRemove = (value) => this.props.dispatch(removeKeywordFromAntenna(this.props.params.id, value));
+
+  onTagNameChanged = (value) => this.setState({ tagName: value });
+
+  onTagAdd = () => {
+    this.props.dispatch(addTagToAntenna(this.props.params.id, this.state.tagName));
+    this.setState({ tagName: '' });
+  };
+
+  onTagRemove = (value) => this.props.dispatch(removeTagFromAntenna(this.props.params.id, value));
+
+  onExcludeDomainNameChanged = (value) => this.setState({ excludeDomainName: value });
+
+  onExcludeDomainAdd = () => {
+    this.props.dispatch(addExcludeDomainToAntenna(this.props.params.id, this.state.excludeDomainName));
+    this.setState({ excludeDomainName: '' });
+  };
+
+  onExcludeDomainRemove = (value) => this.props.dispatch(removeExcludeDomainFromAntenna(this.props.params.id, value));
+
+  onExcludeKeywordNameChanged = (value) => this.setState({ excludeKeywordName: value });
+
+  onExcludeKeywordAdd = () => {
+    this.props.dispatch(addExcludeKeywordToAntenna(this.props.params.id, this.state.excludeKeywordName));
+    this.setState({ excludeKeywordName: '' });
+  };
+
+  onExcludeKeywordRemove = (value) => this.props.dispatch(removeExcludeKeywordFromAntenna(this.props.params.id, value));
+
+  onExcludeTagNameChanged = (value) => this.setState({ excludeTagName: value });
+
+  onExcludeTagAdd = () => {
+    this.props.dispatch(addExcludeTagToAntenna(this.props.params.id, this.state.excludeTagName));
+    this.setState({ excludeTagName: '' });
+  };
+
+  onExcludeTagRemove = (value) => this.props.dispatch(removeExcludeTagFromAntenna(this.props.params.id, value));
+
+  render () {
+    const { columnId, multiColumn, antenna, lists, domains, keywords, tags, intl } = this.props;
+    const { id } = this.props.params;
+    const pinned = !!columnId;
+    const title  = antenna ? antenna.get('title') : id;
+    const isStl = antenna ? antenna.get('stl') : undefined;
+    const isLtl = antenna ? antenna.get('ltl') : undefined;
+    const isMediaOnly = antenna ? antenna.get('with_media_only') : undefined;
+    const isIgnoreReblog = antenna ? antenna.get('ignore_reblog') : undefined;
+    const isInsertFeeds = antenna ? antenna.get('insert_feeds') : undefined;
+
+    if (typeof antenna === 'undefined') {
+      return (
+        <Column>
+          <div className='scrollable'>
+            <LoadingIndicator />
+          </div>
+        </Column>
+      );
+    } else if (antenna === false) {
+      return (
+        <BundleColumnError multiColumn={multiColumn} errorType='routing' />
+      );
+    }
+
+    let columnSettings;
+    if (!isStl && !isLtl) {
+      columnSettings = (
+        <>
+          <section className='similar-row'>
+            <div className='setting-toggle'>
+              <Toggle id={`antenna-${id}-mediaonly`} checked={isMediaOnly} onChange={this.onMediaOnlyToggle} />
+              <label htmlFor={`antenna-${id}-mediaonly`} className='setting-toggle__label'>
+                <FormattedMessage id='antennas.media_only' defaultMessage='Media only' />
+              </label>
+            </div>
+          </section>
+
+          <section className='similar-row'>
+            <div className='setting-toggle'>
+              <Toggle id={`antenna-${id}-ignorereblog`} checked={isIgnoreReblog} onChange={this.onIgnoreReblogToggle} />
+              <label htmlFor={`antenna-${id}-ignorereblog`} className='setting-toggle__label'>
+                <FormattedMessage id='antennas.ignore_reblog' defaultMessage='Exclude boosts' />
+              </label>
+            </div>
+          </section>
+        </>
+      );
+    }
+
+    let stlAlert;
+    if (isStl) {
+      stlAlert = (
+        <div className='antenna-setting'>
+          <p><FormattedMessage id='antennas.in_stl_mode' defaultMessage='This antenna is in STL mode.' /></p>
+        </div>
+      );
+    } else if (isLtl) {
+      stlAlert = (
+        <div className='antenna-setting'>
+          <p><FormattedMessage id='antennas.in_ltl_mode' defaultMessage='This antenna is in LTL mode.' /></p>
+        </div>
+      );
+    }
+
+    const rangeRadioValues = ImmutableList([
+      ImmutableMap({ value: 'accounts', label: intl.formatMessage(messages.accounts, { count: antenna.get('accounts_count') }) }),
+      ImmutableMap({ value: 'domains', label: intl.formatMessage(messages.domains, { count: antenna.get('domains_count') }) }),
+    ]);
+    const rangeRadioValue = ImmutableMap({ value: this.state.rangeRadioValue || (antenna.get('domains_count') > 0 ? 'domains' : 'accounts') });
+    const rangeRadioAlert = antenna.get(rangeRadioValue.get('value') === 'accounts' ? 'domains_count' : 'accounts_count') > 0;
+
+    const contentRadioValues = ImmutableList([
+      ImmutableMap({ value: 'keywords', label: intl.formatMessage(messages.keywords, { count: antenna.get('keywords_count') }) }),
+      ImmutableMap({ value: 'tags', label: intl.formatMessage(messages.tags, { count: antenna.get('tags_count') }) }),
+    ]);
+    const contentRadioValue = ImmutableMap({ value: this.state.contentRadioValue || (antenna.get('tags_count') > 0 ? 'tags' : 'keywords') });
+    const contentRadioAlert = antenna.get(contentRadioValue.get('value') === 'tags' ? 'keywords_count' : 'tags_count') > 0;
+
+    const listOptions = lists.toArray().filter((list) => list.length >= 2 && list[1]).map((list) => {
+      return { value: list[1].get('id'), label: list[1].get('title') };
+    });
+
+    const isShowStlToggle = !isLtl && (enableLocalTimeline || isStl);
+    const isShowLtlToggle = !isStl && (enableLocalTimeline || isLtl);
+
+    return (
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={title}>
+        <ColumnHeader
+          icon='wifi'
+          iconComponent={AntennaIcon}
+          title={title}
+          onPin={this.handlePin}
+          onMove={this.handleMove}
+          onClick={this.handleHeaderClick}
+          pinned={pinned}
+          multiColumn={multiColumn}
+        >
+          <div className='column-settings'>
+            <section className='column-header__links'>
+              <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditAntennaClick}>
+                <Icon id='pencil' icon={EditIcon} /> <FormattedMessage id='antennas.edit_static' defaultMessage='Edit antenna' />
+              </button>
+
+              <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
+                <Icon id='trash' icon={DeleteIcon} /> <FormattedMessage id='antennas.delete' defaultMessage='Delete antenna' />
+              </button>
+
+              <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleTimelineClick}>
+                <Icon id='wifi' icon={AntennaIcon} /> <FormattedMessage id='antennas.go_timeline' defaultMessage='Go to antenna timeline' />
+              </button>
+            </section>
+
+            {isShowStlToggle && (
+              <section>
+                <div className='setting-toggle'>
+                  <Toggle id={`antenna-${id}-stl`} checked={isStl} onChange={this.onStlToggle} />
+                  <label htmlFor={`antenna-${id}-stl`} className='setting-toggle__label'>
+                    <FormattedMessage id='antennas.stl' defaultMessage='STL mode' />
+                  </label>
+                </div>
+              </section>
+            )}
+
+            {isShowLtlToggle && (
+              <section className={isShowStlToggle && 'similar-row'}>
+                <div className='setting-toggle'>
+                  <Toggle id={`antenna-${id}-ltl`} checked={isLtl} onChange={this.onLtlToggle} />
+                  <label htmlFor={`antenna-${id}-ltl`} className='setting-toggle__label'>
+                    <FormattedMessage id='antennas.ltl' defaultMessage='LTL mode' />
+                  </label>
+                </div>
+              </section>
+            )}
+
+            <section className={(isShowStlToggle || isShowLtlToggle) && 'similar-row'}>
+              <div className='setting-toggle'>
+                <Toggle id={`antenna-${id}-noinsertfeeds`} checked={isInsertFeeds} onChange={this.onNoInsertFeedsToggle} />
+                <label htmlFor={`antenna-${id}-noinsertfeeds`} className='setting-toggle__label'>
+                  <FormattedMessage id='antennas.insert_feeds' defaultMessage='Insert to feeds' />
+                </label>
+              </div>
+            </section>
+
+            {columnSettings}
+          </div>
+        </ColumnHeader>
+
+        {stlAlert}
+        <div className='antenna-setting'>
+          {isInsertFeeds && (
+            <>
+              {antenna.get('list') ? (
+                <p><FormattedMessage id='antennas.related_list' defaultMessage='This antenna is related to {listTitle}.' values={{ listTitle: antenna.getIn(['list', 'title']) }} /></p>
+              ) : (
+                <p><FormattedMessage id='antennas.not_related_list' defaultMessage='This antenna is not related list. Posts will appear in home timeline. Open edit page to set list.' /></p>
+              )}
+
+              <NonceProvider nonce={document.querySelector('meta[name=style-nonce]').content} cacheKey='lists'>
+                <Select
+                  value={{ value: antenna.getIn(['list', 'id']), label: antenna.getIn(['list', 'title']) }}
+                  options={listOptions}
+                  noOptionsMessage={this.noOptionsMessage}
+                  onChange={this.onSelect}
+                  className='column-content-select__container'
+                  classNamePrefix='column-content-select'
+                  name='lists'
+                  placeholder={this.props.intl.formatMessage(messages.placeholder)}
+                  defaultOptions
+                />
+              </NonceProvider>
+
+              <Button secondary text={this.props.intl.formatMessage(messages.setHome)} onClick={this.onHomeSelect} />
+            </>
+          )}
+
+          {!isStl && !isLtl && (
+            <>
+              <h2><FormattedMessage id='antennas.filter' defaultMessage='Filter' /></h2>
+              <RadioPanel values={rangeRadioValues} value={rangeRadioValue} onChange={this.onRangeRadioChanged} />
+
+              {rangeRadioValue.get('value') === 'accounts' && <Button text={intl.formatMessage(messages.editAccounts)} onClick={this.handleEditClick} />}
+
+              {rangeRadioValue.get('value') === 'domains' && (
+                <TextList
+                  onChange={this.onDomainNameChanged}
+                  onAdd={this.onDomainAdd}
+                  onRemove={this.onDomainRemove}
+                  value={this.state.domainName}
+                  values={domains.get('domains') || ImmutableList()}
+                  icon='sitemap'
+                  iconComponent={DomainIcon}
+                  label={intl.formatMessage(messages.addDomainLabel)}
+                  title={intl.formatMessage(messages.addDomainTitle)}
+                />
+              )}
+
+              {rangeRadioAlert && <div className='alert'><FormattedMessage id='antennas.warnings.range_radio' defaultMessage='Simultaneous account and domain designation is not recommended.' /></div>}
+
+              <RadioPanel values={contentRadioValues} value={contentRadioValue} onChange={this.onContentRadioChanged} />
+
+              {contentRadioValue.get('value') === 'tags' && (
+                <TextList
+                  onChange={this.onTagNameChanged}
+                  onAdd={this.onTagAdd}
+                  onRemove={this.onTagRemove}
+                  value={this.state.tagName}
+                  values={tags.get('tags') || ImmutableList()}
+                  icon='hashtag'
+                  iconComponent={HashtagIcon}
+                  label={intl.formatMessage(messages.addTagLabel)}
+                  title={intl.formatMessage(messages.addTagTitle)}
+                />
+              )}
+
+              {contentRadioValue.get('value') === 'keywords' && (
+                <TextList
+                  onChange={this.onKeywordNameChanged}
+                  onAdd={this.onKeywordAdd}
+                  onRemove={this.onKeywordRemove}
+                  value={this.state.keywordName}
+                  values={keywords.get('keywords') || ImmutableList()}
+                  icon='paragraph'
+                  iconComponent={KeywordIcon}
+                  label={intl.formatMessage(messages.addKeywordLabel)}
+                  title={intl.formatMessage(messages.addKeywordTitle)}
+                />
+              )}
+
+              {contentRadioAlert && <div className='alert'><FormattedMessage id='antennas.warnings.content_radio' defaultMessage='Simultaneous keyword and tag designation is not recommended.' /></div>}
+
+              <h2><FormattedMessage id='antennas.filter_not' defaultMessage='Filter Not' /></h2>
+              <h3><FormattedMessage id='antennas.exclude_accounts' defaultMessage='Exclude accounts' /></h3>
+              <Button text={intl.formatMessage(messages.editAccounts)} onClick={this.handleExcludeEditClick} />
+              <h3><FormattedMessage id='antennas.exclude_domains' defaultMessage='Exclude domains' /></h3>
+              <TextList
+                onChange={this.onExcludeDomainNameChanged}
+                onAdd={this.onExcludeDomainAdd}
+                onRemove={this.onExcludeDomainRemove}
+                value={this.state.excludeDomainName}
+                values={domains.get('exclude_domains') || ImmutableList()}
+                icon='sitemap'
+                iconComponent={DomainIcon}
+                label={intl.formatMessage(messages.addDomainLabel)}
+                title={intl.formatMessage(messages.addDomainTitle)}
+              />
+              <h3><FormattedMessage id='antennas.exclude_keywords' defaultMessage='Exclude keywords' /></h3>
+              <TextList
+                onChange={this.onExcludeKeywordNameChanged}
+                onAdd={this.onExcludeKeywordAdd}
+                onRemove={this.onExcludeKeywordRemove}
+                value={this.state.excludeKeywordName}
+                values={keywords.get('exclude_keywords') || ImmutableList()}
+                icon='paragraph'
+                iconComponent={KeywordIcon}
+                label={intl.formatMessage(messages.addKeywordLabel)}
+                title={intl.formatMessage(messages.addKeywordTitle)}
+              />
+              <h3><FormattedMessage id='antennas.exclude_tags' defaultMessage='Exclude tags' /></h3>
+              <TextList
+                onChange={this.onExcludeTagNameChanged}
+                onAdd={this.onExcludeTagAdd}
+                onRemove={this.onExcludeTagRemove}
+                value={this.state.excludeTagName}
+                values={tags.get('exclude_tags') || ImmutableList()}
+                icon='hashtag'
+                iconComponent={HashtagIcon}
+                label={intl.formatMessage(messages.addTagLabel)}
+                title={intl.formatMessage(messages.addTagTitle)}
+              />
+            </>
+          )}
+        </div>
+
+        <Helmet>
+          <title>{title}</title>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default withRouter(connect(mapStateToProps)(injectIntl(AntennaSetting)));
diff --git a/app/javascript/mastodon/features/antenna_timeline/index.jsx b/app/javascript/mastodon/features/antenna_timeline/index.jsx
index d7f355d7c9..26b21ff679 100644
--- a/app/javascript/mastodon/features/antenna_timeline/index.jsx
+++ b/app/javascript/mastodon/features/antenna_timeline/index.jsx
@@ -113,7 +113,7 @@ class AntennaTimeline extends PureComponent {
   };
 
   handleEditClick = () => {
-    this.props.history.push(`/antennas/${this.props.params.id}/edit`);
+    this.props.history.push(`/antennasw/${this.props.params.id}`);
   };
 
   handleDeleteClick = () => {
diff --git a/app/javascript/mastodon/features/antennas/components/new_antenna_form.jsx b/app/javascript/mastodon/features/antennas/components/new_antenna_form.jsx
new file mode 100644
index 0000000000..187eb715f3
--- /dev/null
+++ b/app/javascript/mastodon/features/antennas/components/new_antenna_form.jsx
@@ -0,0 +1,80 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import { changeAntennaEditorTitle, submitAntennaEditor } from 'mastodon/actions/antennas';
+import { Button } from 'mastodon/components/button';
+
+const messages = defineMessages({
+  label: { id: 'antennas.new.title_placeholder', defaultMessage: 'New antenna title' },
+  title: { id: 'antennas.new.create', defaultMessage: 'Add antenna' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['antennaEditor', 'title']),
+  disabled: state.getIn(['antennaEditor', 'isSubmitting']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onChange: value => dispatch(changeAntennaEditorTitle(value)),
+  onSubmit: () => dispatch(submitAntennaEditor(true)),
+});
+
+class NewAntennaForm extends PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    disabled: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleSubmit = e => {
+    e.preventDefault();
+    this.props.onSubmit();
+  };
+
+  handleClick = () => {
+    this.props.onSubmit();
+  };
+
+  render () {
+    const { value, disabled, intl } = this.props;
+
+    const label = intl.formatMessage(messages.label);
+    const title = intl.formatMessage(messages.title);
+
+    return (
+      <form className='column-inline-form' onSubmit={this.handleSubmit}>
+        <label>
+          <span style={{ display: 'none' }}>{label}</span>
+
+          <input
+            className='setting-text'
+            value={value}
+            disabled={disabled}
+            onChange={this.handleChange}
+            placeholder={label}
+          />
+        </label>
+
+        <Button
+          disabled={disabled || !value}
+          text={title}
+          onClick={this.handleClick}
+        />
+      </form>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(NewAntennaForm));
diff --git a/app/javascript/mastodon/features/antennas/filtering.tsx b/app/javascript/mastodon/features/antennas/filtering.tsx
deleted file mode 100644
index 4caa3a227e..0000000000
--- a/app/javascript/mastodon/features/antennas/filtering.tsx
+++ /dev/null
@@ -1,730 +0,0 @@
-import { useEffect, useState, useCallback } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import classNames from 'classnames';
-import { useParams, Link } from 'react-router-dom';
-
-import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
-import DomainIcon from '@/material-icons/400-24px/dns.svg?react';
-import HashtagIcon from '@/material-icons/400-24px/tag.svg?react';
-import KeywordIcon from '@/material-icons/400-24px/title.svg?react';
-import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
-import { fetchAntenna } from 'mastodon/actions/antennas';
-import {
-  apiGetAccounts,
-  apiGetDomains,
-  apiAddDomain,
-  apiRemoveDomain,
-  apiGetTags,
-  apiAddTag,
-  apiRemoveTag,
-  apiGetKeywords,
-  apiAddKeyword,
-  apiRemoveKeyword,
-  apiAddExcludeDomain,
-  apiRemoveExcludeDomain,
-  apiAddExcludeTag,
-  apiRemoveExcludeTag,
-  apiAddExcludeKeyword,
-  apiRemoveExcludeKeyword,
-  apiGetExcludeAccounts,
-} from 'mastodon/api/antennas';
-import { Button } from 'mastodon/components/button';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import type { IconProp } from 'mastodon/components/icon';
-import { Icon } from 'mastodon/components/icon';
-import { IconButton } from 'mastodon/components/icon_button';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  deleteMessage: {
-    id: 'confirmations.delete_antenna.message',
-    defaultMessage: 'Are you sure you want to permanently delete this antenna?',
-  },
-  deleteConfirm: {
-    id: 'confirmations.delete_antenna.confirm',
-    defaultMessage: 'Delete',
-  },
-  editAccounts: {
-    id: 'antennas.edit_accounts',
-    defaultMessage: 'Edit accounts',
-  },
-  noOptions: {
-    id: 'antennas.select.no_options_message',
-    defaultMessage: 'Empty lists',
-  },
-  placeholder: {
-    id: 'antennas.select.placeholder',
-    defaultMessage: 'Select list',
-  },
-  addDomainLabel: {
-    id: 'antennas.add_domain_placeholder',
-    defaultMessage: 'New domain',
-  },
-  addKeywordLabel: {
-    id: 'antennas.add_keyword_placeholder',
-    defaultMessage: 'New keyword',
-  },
-  addTagLabel: {
-    id: 'antennas.add_tag_placeholder',
-    defaultMessage: 'New tag',
-  },
-  addDomainTitle: { id: 'antennas.add_domain', defaultMessage: 'Add domain' },
-  addKeywordTitle: {
-    id: 'antennas.add_keyword',
-    defaultMessage: 'Add keyword',
-  },
-  addTagTitle: { id: 'antennas.add_tag', defaultMessage: 'Add tag' },
-  accounts: { id: 'antennas.accounts', defaultMessage: '{count} accounts' },
-  domains: { id: 'antennas.domains', defaultMessage: '{count} domains' },
-  tags: { id: 'antennas.tags', defaultMessage: '{count} tags' },
-  keywords: { id: 'antennas.keywords', defaultMessage: '{count} keywords' },
-  setHome: { id: 'antennas.select.set_home', defaultMessage: 'Set home' },
-});
-
-const TextListItem: React.FC<{
-  icon: string;
-  iconComponent: IconProp;
-  value: string;
-  onRemove: (value: string) => void;
-}> = ({ icon, iconComponent, value, onRemove }) => {
-  const handleRemove = useCallback(() => {
-    onRemove(value);
-  }, [onRemove, value]);
-
-  return (
-    <div className='setting-text-list-item'>
-      <Icon id={icon} icon={iconComponent} />
-      <span className='label'>{value}</span>
-      <IconButton
-        title='Delete'
-        icon='trash'
-        iconComponent={DeleteIcon}
-        onClick={handleRemove}
-      />
-    </div>
-  );
-};
-
-const TextList: React.FC<{
-  values: string[];
-  disabled?: boolean;
-  icon: string;
-  iconComponent: IconProp;
-  label: string;
-  title: string;
-  onAdd: (value: string) => void;
-  onRemove: (value: string) => void;
-}> = ({
-  values,
-  disabled,
-  icon,
-  iconComponent,
-  label,
-  title,
-  onAdd,
-  onRemove,
-}) => {
-  const [value, setValue] = useState('');
-
-  const handleValueChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setValue(value);
-    },
-    [setValue],
-  );
-
-  const handleAdd = useCallback(() => {
-    onAdd(value);
-    setValue('');
-  }, [onAdd, value]);
-
-  const handleRemove = useCallback(
-    (removeValue: string) => {
-      onRemove(removeValue);
-    },
-    [onRemove],
-  );
-
-  const handleSubmit = handleAdd;
-
-  return (
-    <div className='setting-text-list'>
-      <form className='add-text-form' onSubmit={handleSubmit}>
-        <label>
-          <span style={{ display: 'none' }}>{label}</span>
-
-          <input
-            className='setting-text'
-            value={value}
-            disabled={disabled}
-            onChange={handleValueChange}
-            placeholder={label}
-          />
-        </label>
-
-        <Button
-          disabled={disabled || !value}
-          text={title}
-          onClick={handleAdd}
-        />
-      </form>
-
-      {values.map((val) => (
-        <TextListItem
-          key={val}
-          value={val}
-          icon={icon}
-          iconComponent={iconComponent}
-          onRemove={handleRemove}
-        />
-      ))}
-    </div>
-  );
-};
-
-const RadioPanel: React.FC<{
-  antennaId: string;
-  items: { title: string; value: string }[];
-  valueLengths: number[];
-  alertMessage: React.ReactElement;
-  onChange: (value: string) => void;
-}> = ({ antennaId, items, valueLengths, alertMessage, onChange }) => {
-  const [error, setError] = useState(false);
-  const [value, setValue] = useState('');
-  const [lastAntennaId, setLastAntennaId] = useState('');
-
-  useEffect(() => {
-    if (valueLengths.length >= 2) {
-      setError(valueLengths.filter((v) => v > 0).length > 1);
-    } else {
-      setError(false);
-    }
-  }, [valueLengths]);
-
-  useEffect(() => {
-    if (
-      items.length > 0 &&
-      valueLengths.length === items.length &&
-      antennaId !== lastAntennaId
-    ) {
-      for (let i = 0; i < valueLengths.length; i++) {
-        const length = valueLengths[i] ?? 0;
-        const item = items[i] ?? { value: '' };
-        if (length > 0) {
-          setValue(item.value);
-          onChange(item.value);
-          return;
-        }
-      }
-      setValue(items[0]?.value ?? '');
-    }
-  }, [antennaId, lastAntennaId, items, valueLengths, setValue, onChange]);
-
-  const handleChange = useCallback(
-    ({ currentTarget }: React.MouseEvent<HTMLButtonElement>) => {
-      const selected = currentTarget.getAttribute('data-value') ?? '';
-      if (value !== selected) {
-        onChange(selected);
-        setValue(selected);
-
-        // Set the flag for the first manual tab change.
-        setLastAntennaId(antennaId);
-      }
-    },
-    [value, setValue, onChange, setLastAntennaId, antennaId],
-  );
-
-  return (
-    <div>
-      <div className='setting-radio-panel'>
-        {items.map((item) => (
-          <button
-            className={classNames('setting-radio-panel__item', {
-              'setting-radio-panel__item__active': value === item.value,
-            })}
-            key={item.value}
-            onClick={handleChange}
-            data-value={item.value}
-          >
-            {item.title}
-          </button>
-        ))}
-      </div>
-
-      {error && <div className='alert'>{alertMessage}</div>}
-    </div>
-  );
-};
-
-const MembersLink: React.FC<{
-  id: string;
-  isExclude: boolean;
-  onCountFetched?: (count: number) => void;
-}> = ({ id, isExclude, onCountFetched }) => {
-  const [count, setCount] = useState(0);
-  const [avatars, setAvatars] = useState<string[]>([]);
-
-  useEffect(() => {
-    const api = isExclude ? apiGetExcludeAccounts : apiGetAccounts;
-    void api(id)
-      .then((data) => {
-        setCount(data.length);
-        if (onCountFetched) {
-          onCountFetched(data.length);
-        }
-        setAvatars(data.slice(0, 3).map((a) => a.avatar));
-        return '';
-      })
-      .catch(() => {
-        // Nothing
-      });
-  }, [id, setCount, setAvatars, isExclude, onCountFetched]);
-
-  return (
-    <Link
-      to={`/antennas/${id}/${isExclude ? 'exclude_members' : 'members'}`}
-      className='app-form__link'
-    >
-      <div className='app-form__link__text'>
-        <strong>
-          <FormattedMessage
-            id='antennas.antenna_accounts'
-            defaultMessage='Antenna accounts'
-          />
-        </strong>
-        <FormattedMessage
-          id='antennas.antenna_accounts_count'
-          defaultMessage='{count, plural, one {# member} other {# accounts}}'
-          values={{ count }}
-        />
-      </div>
-
-      <div className='avatar-pile'>
-        {avatars.map((url) => (
-          <img key={url} src={url} alt='' />
-        ))}
-      </div>
-    </Link>
-  );
-};
-
-const AntennaSetting: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const { id } = useParams<{ id?: string }>();
-  const intl = useIntl();
-  //const history = useHistory();
-
-  const antenna = useAppSelector((state) =>
-    id ? state.antennas.get(id) : undefined,
-  );
-  const [domainList, setDomainList] = useState([] as string[]);
-  const [excludeDomainList, setExcludeDomainList] = useState([] as string[]);
-  const [tagList, setTagList] = useState([] as string[]);
-  const [excludeTagList, setExcludeTagList] = useState([] as string[]);
-  const [keywordList, setKeywordList] = useState([] as string[]);
-  const [excludeKeywordList, setExcludeKeywordList] = useState([] as string[]);
-  const [accountsCount, setAccountsCount] = useState(0);
-  const [rangeMode, setRangeMode] = useState('accounts');
-  const [contentMode, setContentMode] = useState('keywords');
-
-  useEffect(() => {
-    if (id) {
-      dispatch(fetchAntenna(id));
-
-      void apiGetDomains(id).then((data) => {
-        setDomainList(data.domains);
-        setExcludeDomainList(data.exclude_domains);
-        return true;
-      });
-
-      void apiGetTags(id).then((data) => {
-        setTagList(data.tags);
-        setExcludeTagList(data.exclude_tags);
-        return true;
-      });
-
-      void apiGetKeywords(id).then((data) => {
-        setKeywordList(data.keywords);
-        setExcludeKeywordList(data.exclude_keywords);
-        return true;
-      });
-    }
-  }, [
-    dispatch,
-    id,
-    setDomainList,
-    setExcludeDomainList,
-    setTagList,
-    setExcludeTagList,
-    setKeywordList,
-    setExcludeKeywordList,
-  ]);
-
-  const handleAccountsFetched = useCallback(
-    (count: number) => {
-      setAccountsCount(count);
-    },
-    [setAccountsCount],
-  );
-
-  const handleAddDomain = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiAddDomain(id, value).then(() => {
-        setDomainList([...domainList, value]);
-        return value;
-      });
-    },
-    [id, domainList, setDomainList],
-  );
-
-  const handleRemoveDomain = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiRemoveDomain(id, value).then(() => {
-        setDomainList(domainList.filter((v) => v !== value));
-        return value;
-      });
-    },
-    [id, domainList, setDomainList],
-  );
-
-  const handleAddExcludeDomain = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiAddExcludeDomain(id, value).then(() => {
-        setExcludeDomainList([...excludeDomainList, value]);
-        return value;
-      });
-    },
-    [id, excludeDomainList, setExcludeDomainList],
-  );
-
-  const handleRemoveExcludeDomain = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiRemoveExcludeDomain(id, value).then(() => {
-        setExcludeDomainList(excludeDomainList.filter((v) => v !== value));
-        return value;
-      });
-    },
-    [id, excludeDomainList, setExcludeDomainList],
-  );
-
-  const handleAddTag = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiAddTag(id, value).then(() => {
-        setTagList([...tagList, value]);
-        return value;
-      });
-    },
-    [id, tagList, setTagList],
-  );
-
-  const handleRemoveTag = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiRemoveTag(id, value).then(() => {
-        setTagList(tagList.filter((v) => v !== value));
-        return value;
-      });
-    },
-    [id, tagList, setTagList],
-  );
-
-  const handleAddExcludeTag = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiAddExcludeTag(id, value).then(() => {
-        setExcludeTagList([...excludeTagList, value]);
-        return value;
-      });
-    },
-    [id, excludeTagList, setExcludeTagList],
-  );
-
-  const handleRemoveExcludeTag = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiRemoveExcludeTag(id, value).then(() => {
-        setExcludeTagList(excludeTagList.filter((v) => v !== value));
-        return value;
-      });
-    },
-    [id, excludeTagList, setExcludeTagList],
-  );
-
-  const handleAddKeyword = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiAddKeyword(id, value).then(() => {
-        setKeywordList([...keywordList, value]);
-        return value;
-      });
-    },
-    [id, keywordList, setKeywordList],
-  );
-
-  const handleRemoveKeyword = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiRemoveKeyword(id, value).then(() => {
-        setKeywordList(keywordList.filter((v) => v !== value));
-        return value;
-      });
-    },
-    [id, keywordList, setKeywordList],
-  );
-
-  const handleAddExcludeKeyword = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiAddExcludeKeyword(id, value).then(() => {
-        setExcludeKeywordList([...excludeKeywordList, value]);
-        return value;
-      });
-    },
-    [id, excludeKeywordList, setExcludeKeywordList],
-  );
-
-  const handleRemoveExcludeKeyword = useCallback(
-    (value: string) => {
-      if (!id) return;
-
-      void apiRemoveExcludeKeyword(id, value).then(() => {
-        setExcludeKeywordList(excludeKeywordList.filter((v) => v !== value));
-        return value;
-      });
-    },
-    [id, excludeKeywordList, setExcludeKeywordList],
-  );
-
-  const handleRangeRadioChange = useCallback(
-    (value: string) => {
-      setRangeMode(value);
-    },
-    [setRangeMode],
-  );
-
-  const handleContentRadioChange = useCallback(
-    (value: string) => {
-      setContentMode(value);
-    },
-    [setContentMode],
-  );
-
-  if (!antenna || !id) return <div />;
-
-  if (antenna.stl)
-    return (
-      <div className='antenna-setting'>
-        <p>
-          <FormattedMessage
-            id='antennas.in_stl_mode'
-            defaultMessage='This antenna is in STL mode.'
-          />
-        </p>
-      </div>
-    );
-
-  if (antenna.ltl)
-    return (
-      <div className='antenna-setting'>
-        <p>
-          <FormattedMessage
-            id='antennas.in_ltl_mode'
-            defaultMessage='This antenna is in LTL mode.'
-          />
-        </p>
-      </div>
-    );
-
-  const rangeRadioItems = [
-    {
-      value: 'accounts',
-      title: intl.formatMessage(messages.accounts, { count: accountsCount }),
-    },
-    {
-      value: 'domains',
-      title: intl.formatMessage(messages.domains, { count: domainList.length }),
-    },
-  ];
-  const rangeRadioLengths = [accountsCount, domainList.length];
-
-  const contentRadioItems = [
-    {
-      value: 'keywords',
-      title: intl.formatMessage(messages.keywords, {
-        count: keywordList.length,
-      }),
-    },
-    {
-      value: 'tags',
-      title: intl.formatMessage(messages.tags, { count: tagList.length }),
-    },
-  ];
-  const contentRadioLengths = [keywordList.length, tagList.length];
-
-  return (
-    <Column bindToDocument={!multiColumn} label={antenna.title}>
-      <ColumnHeader
-        title={antenna.title}
-        icon='antenna-ul'
-        iconComponent={AntennaIcon}
-        multiColumn={multiColumn}
-        showBackButton
-      />
-
-      <div className='scrollable antenna-setting'>
-        <RadioPanel
-          antennaId={id}
-          items={rangeRadioItems}
-          valueLengths={rangeRadioLengths}
-          alertMessage={
-            <div className='alert'>
-              <FormattedMessage
-                id='antennas.warnings.range_radio'
-                defaultMessage='Simultaneous account and domain designation is not recommended.'
-              />
-            </div>
-          }
-          onChange={handleRangeRadioChange}
-        />
-        {rangeMode === 'accounts' && (
-          <MembersLink
-            id={id}
-            onCountFetched={handleAccountsFetched}
-            isExclude={false}
-          />
-        )}
-        {rangeMode === 'domains' && (
-          <TextList
-            values={domainList}
-            icon='sitemap'
-            iconComponent={DomainIcon}
-            label={intl.formatMessage(messages.addDomainLabel)}
-            title={intl.formatMessage(messages.addDomainTitle)}
-            onAdd={handleAddDomain}
-            onRemove={handleRemoveDomain}
-          />
-        )}
-
-        <RadioPanel
-          antennaId={id}
-          items={contentRadioItems}
-          valueLengths={contentRadioLengths}
-          alertMessage={
-            <div className='alert'>
-              <FormattedMessage
-                id='antennas.warnings.content_radio'
-                defaultMessage='Simultaneous keyword and tag designation is not recommended.'
-              />
-            </div>
-          }
-          onChange={handleContentRadioChange}
-        />
-        {contentMode === 'keywords' && (
-          <TextList
-            values={keywordList}
-            icon='paragraph'
-            iconComponent={KeywordIcon}
-            label={intl.formatMessage(messages.addKeywordLabel)}
-            title={intl.formatMessage(messages.addKeywordTitle)}
-            onAdd={handleAddKeyword}
-            onRemove={handleRemoveKeyword}
-          />
-        )}
-        {contentMode === 'tags' && (
-          <TextList
-            values={tagList}
-            icon='hashtag'
-            iconComponent={HashtagIcon}
-            label={intl.formatMessage(messages.addTagLabel)}
-            title={intl.formatMessage(messages.addTagTitle)}
-            onAdd={handleAddTag}
-            onRemove={handleRemoveTag}
-          />
-        )}
-
-        <h2>
-          <FormattedMessage
-            id='antennas.filter_not'
-            defaultMessage='Filter Not'
-          />
-        </h2>
-        <h3>
-          <FormattedMessage
-            id='antennas.exclude_accounts'
-            defaultMessage='Exclude accounts'
-          />
-        </h3>
-        <MembersLink id={id} isExclude />
-        <h3>
-          <FormattedMessage
-            id='antennas.exclude_domains'
-            defaultMessage='Exclude domains'
-          />
-        </h3>
-        <TextList
-          values={excludeDomainList}
-          icon='sitemap'
-          iconComponent={DomainIcon}
-          label={intl.formatMessage(messages.addDomainLabel)}
-          title={intl.formatMessage(messages.addDomainTitle)}
-          onAdd={handleAddExcludeDomain}
-          onRemove={handleRemoveExcludeDomain}
-        />
-        <h3>
-          <FormattedMessage
-            id='antennas.exclude_keywords'
-            defaultMessage='Exclude keywords'
-          />
-        </h3>
-        <TextList
-          values={excludeKeywordList}
-          icon='paragraph'
-          iconComponent={KeywordIcon}
-          label={intl.formatMessage(messages.addKeywordLabel)}
-          title={intl.formatMessage(messages.addKeywordTitle)}
-          onAdd={handleAddExcludeKeyword}
-          onRemove={handleRemoveExcludeKeyword}
-        />
-        <h3>
-          <FormattedMessage
-            id='antennas.exclude_tags'
-            defaultMessage='Exclude tags'
-          />
-        </h3>
-        <TextList
-          values={excludeTagList}
-          icon='paragraph'
-          iconComponent={HashtagIcon}
-          label={intl.formatMessage(messages.addTagLabel)}
-          title={intl.formatMessage(messages.addTagTitle)}
-          onAdd={handleAddExcludeTag}
-          onRemove={handleRemoveExcludeTag}
-        />
-      </div>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default AntennaSetting;
diff --git a/app/javascript/mastodon/features/antennas/index.jsx b/app/javascript/mastodon/features/antennas/index.jsx
new file mode 100644
index 0000000000..8c1f17bcc2
--- /dev/null
+++ b/app/javascript/mastodon/features/antennas/index.jsx
@@ -0,0 +1,97 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import { createSelector } from '@reduxjs/toolkit';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
+import { fetchAntennas } from 'mastodon/actions/antennas';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import ScrollableList from 'mastodon/components/scrollable_list';
+import ColumnLink from 'mastodon/features/ui/components/column_link';
+import ColumnSubheading from 'mastodon/features/ui/components/column_subheading';
+
+import NewAntennaForm from './components/new_antenna_form';
+
+const messages = defineMessages({
+  heading: { id: 'column.antennas', defaultMessage: 'Antennas' },
+  subheading: { id: 'antennas.subheading', defaultMessage: 'Your antennas' },
+  insert_list: { id: 'antennas.insert_list', defaultMessage: 'List' },
+  insert_home: { id: 'antennas.insert_home', defaultMessage: 'Home' },
+});
+
+const getOrderedAntennas = createSelector([state => state.get('antennas')], antennas => {
+  if (!antennas) {
+    return antennas;
+  }
+
+  return antennas.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
+});
+
+const mapStateToProps = state => ({
+  antennas: getOrderedAntennas(state),
+});
+
+class Antennas extends ImmutablePureComponent {
+
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    antennas: ImmutablePropTypes.list,
+    intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
+  };
+
+  UNSAFE_componentWillMount () {
+    this.props.dispatch(fetchAntennas());
+  }
+
+  render () {
+    const { intl, antennas, multiColumn } = this.props;
+
+    if (!antennas) {
+      return (
+        <Column>
+          <LoadingIndicator />
+        </Column>
+      );
+    }
+
+    const emptyMessage = <FormattedMessage id='empty_column.antennas' defaultMessage="You don't have any antennas yet. When you create one, it will show up here." />;
+
+    return (
+      <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.heading)}>
+        <ColumnHeader title={intl.formatMessage(messages.heading)} icon='wifi' iconComponent={AntennaIcon} multiColumn={multiColumn} />
+
+        <NewAntennaForm />
+
+        <ScrollableList
+          scrollKey='antennas'
+          emptyMessage={emptyMessage}
+          prepend={<ColumnSubheading text={intl.formatMessage(messages.subheading)} />}
+          bindToDocument={!multiColumn}
+        >
+          {antennas.map(antenna => (
+            <ColumnLink key={antenna.get('id')} to={`/antennast/${antenna.get('id')}`} icon='wifi' iconComponent={AntennaIcon} text={antenna.get('title')}
+              badge={antenna.get('insert_feeds') ? intl.formatMessage(antenna.get('list') ? messages.insert_list : messages.insert_home) : undefined} />
+          ))}
+        </ScrollableList>
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.heading)}</title>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(Antennas));
diff --git a/app/javascript/mastodon/features/antennas/index.tsx b/app/javascript/mastodon/features/antennas/index.tsx
deleted file mode 100644
index c411cee735..0000000000
--- a/app/javascript/mastodon/features/antennas/index.tsx
+++ /dev/null
@@ -1,189 +0,0 @@
-import { useEffect, useMemo, useCallback } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { Link } from 'react-router-dom';
-
-import AddIcon from '@/material-icons/400-24px/add.svg?react';
-import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
-import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
-import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
-import { fetchAntennas } from 'mastodon/actions/antennas';
-import { openModal } from 'mastodon/actions/modal';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
-import { Icon } from 'mastodon/components/icon';
-import ScrollableList from 'mastodon/components/scrollable_list';
-import { getOrderedAntennas } from 'mastodon/selectors/antennas';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-const messages = defineMessages({
-  heading: { id: 'column.antennas', defaultMessage: 'Antennas' },
-  create: { id: 'antennas.create_antenna', defaultMessage: 'Create antenna' },
-  edit: { id: 'antennas.edit', defaultMessage: 'Edit antenna' },
-  delete: { id: 'antennas.delete', defaultMessage: 'Delete antenna' },
-  more: { id: 'status.more', defaultMessage: 'More' },
-});
-
-const AntennaItem: React.FC<{
-  id: string;
-  title: string;
-  insert_feeds: boolean;
-  isList: boolean;
-  listTitle?: string;
-  stl: boolean;
-  ltl: boolean;
-}> = ({ id, title, insert_feeds, isList, listTitle, stl, ltl }) => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-
-  const handleDeleteClick = useCallback(() => {
-    dispatch(
-      openModal({
-        modalType: 'CONFIRM_DELETE_ANTENNA',
-        modalProps: {
-          antennaId: id,
-        },
-      }),
-    );
-  }, [dispatch, id]);
-
-  const menu = useMemo(
-    () => [
-      { text: intl.formatMessage(messages.edit), to: `/antennas/${id}/edit` },
-      { text: intl.formatMessage(messages.delete), action: handleDeleteClick },
-    ],
-    [intl, id, handleDeleteClick],
-  );
-
-  return (
-    <div className='lists__item'>
-      <Link to={`/antennas/${id}`} className='lists__item__title'>
-        <Icon id='antenna-ul' icon={AntennaIcon} />
-        <span>
-          {title}
-
-          {stl && (
-            <span className='column-link__badge'>
-              <FormattedMessage id='antennas.badge_stl' defaultMessage='STL' />
-            </span>
-          )}
-          {ltl && (
-            <span className='column-link__badge'>
-              <FormattedMessage id='antennas.badge_ltl' defaultMessage='LTL' />
-            </span>
-          )}
-
-          {insert_feeds && (
-            <span className='lists__item__memo'>
-              {isList && listTitle && (
-                <FormattedMessage
-                  id='antennas.memo_insert_list'
-                  defaultMessage='List: "{title}"'
-                  values={{ title: listTitle }}
-                />
-              )}
-              {!isList && (
-                <FormattedMessage
-                  id='antennas.memo_insert_home'
-                  defaultMessage='Inserts home timeline.'
-                />
-              )}
-            </span>
-          )}
-        </span>
-      </Link>
-
-      <Dropdown
-        scrollKey='antennas'
-        items={menu}
-        icon='ellipsis-h'
-        iconComponent={MoreHorizIcon}
-        title={intl.formatMessage(messages.more)}
-      />
-    </div>
-  );
-};
-
-const Antennas: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-  const antennas = useAppSelector((state) => getOrderedAntennas(state));
-
-  useEffect(() => {
-    dispatch(fetchAntennas());
-  }, [dispatch]);
-
-  const emptyMessage = (
-    <>
-      <span>
-        <FormattedMessage
-          id='antennas.no_antennas_yet'
-          defaultMessage='No antennas yet.'
-        />
-        <br />
-        <FormattedMessage
-          id='antennas.create_a_antenna_to_organize'
-          defaultMessage='Create a new antenna to organize your Home feed'
-        />
-      </span>
-
-      <SquigglyArrow className='empty-column-indicator__arrow' />
-    </>
-  );
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(messages.heading)}
-    >
-      <ColumnHeader
-        title={intl.formatMessage(messages.heading)}
-        icon='antenna-ul'
-        iconComponent={AntennaIcon}
-        multiColumn={multiColumn}
-        extraButton={
-          <Link
-            to='/antennas/new'
-            className='column-header__button'
-            title={intl.formatMessage(messages.create)}
-            aria-label={intl.formatMessage(messages.create)}
-          >
-            <Icon id='plus' icon={AddIcon} />
-          </Link>
-        }
-      />
-
-      <ScrollableList
-        scrollKey='antennas'
-        emptyMessage={emptyMessage}
-        bindToDocument={!multiColumn}
-      >
-        {antennas.map((antenna) => (
-          <AntennaItem
-            key={antenna.id}
-            id={antenna.id}
-            title={antenna.title}
-            insert_feeds={antenna.insert_feeds}
-            isList={!!antenna.list}
-            listTitle={antenna.list?.title}
-            stl={antenna.stl}
-            ltl={antenna.ltl}
-          />
-        ))}
-      </ScrollableList>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default Antennas;
diff --git a/app/javascript/mastodon/features/antennas/members.tsx b/app/javascript/mastodon/features/antennas/members.tsx
deleted file mode 100644
index b0836d2c1f..0000000000
--- a/app/javascript/mastodon/features/antennas/members.tsx
+++ /dev/null
@@ -1,392 +0,0 @@
-import { useCallback, useState, useEffect, useRef } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { useParams, Link } from 'react-router-dom';
-
-import { useDebouncedCallback } from 'use-debounce';
-
-import AddIcon from '@/material-icons/400-24px/add.svg?react';
-import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react';
-import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
-import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
-import { fetchFollowing } from 'mastodon/actions/accounts';
-import { fetchAntenna } from 'mastodon/actions/antennas';
-import { importFetchedAccounts } from 'mastodon/actions/importer';
-import { apiRequest } from 'mastodon/api';
-import {
-  apiGetAccounts,
-  apiAddAccountToAntenna,
-  apiRemoveAccountFromAntenna,
-  apiRemoveExcludeAccountFromAntenna,
-  apiAddExcludeAccountToAntenna,
-  apiGetExcludeAccounts,
-} from 'mastodon/api/antennas';
-import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
-import { Avatar } from 'mastodon/components/avatar';
-import { Button } from 'mastodon/components/button';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { FollowersCounter } from 'mastodon/components/counters';
-import { DisplayName } from 'mastodon/components/display_name';
-import { Icon } from 'mastodon/components/icon';
-import ScrollableList from 'mastodon/components/scrollable_list';
-import { ShortNumber } from 'mastodon/components/short_number';
-import { VerifiedBadge } from 'mastodon/components/verified_badge';
-import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
-import { me } from 'mastodon/initial_state';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  heading: {
-    id: 'column.antenna_members',
-    defaultMessage: 'Manage antenna members',
-  },
-  placeholder: {
-    id: 'antennas.search_placeholder',
-    defaultMessage: 'Search people you follow',
-  },
-  enterSearch: {
-    id: 'antennas.add_to_antenna',
-    defaultMessage: 'Add to antenna',
-  },
-  add: { id: 'antennas.add_member', defaultMessage: 'Add' },
-  remove: { id: 'antennas.remove_member', defaultMessage: 'Remove' },
-  back: { id: 'column_back_button.label', defaultMessage: 'Back' },
-});
-
-type Mode = 'remove' | 'add';
-
-const ColumnSearchHeader: React.FC<{
-  onBack: () => void;
-  onSubmit: (value: string) => void;
-}> = ({ onBack, onSubmit }) => {
-  const intl = useIntl();
-  const [value, setValue] = useState('');
-
-  const handleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setValue(value);
-      onSubmit(value);
-    },
-    [setValue, onSubmit],
-  );
-
-  const handleSubmit = useCallback(() => {
-    onSubmit(value);
-  }, [onSubmit, value]);
-
-  return (
-    <ButtonInTabsBar>
-      <form className='column-search-header' onSubmit={handleSubmit}>
-        <button
-          type='button'
-          className='column-header__back-button compact'
-          onClick={onBack}
-          aria-label={intl.formatMessage(messages.back)}
-        >
-          <Icon
-            id='chevron-left'
-            icon={ArrowBackIcon}
-            className='column-back-button__icon'
-          />
-        </button>
-
-        <input
-          type='search'
-          value={value}
-          onChange={handleChange}
-          placeholder={intl.formatMessage(messages.placeholder)}
-          /* eslint-disable-next-line jsx-a11y/no-autofocus */
-          autoFocus
-        />
-      </form>
-    </ButtonInTabsBar>
-  );
-};
-
-const AccountItem: React.FC<{
-  accountId: string;
-  antennaId: string;
-  partOfAntenna: boolean;
-  isExclude?: boolean;
-  onToggle: (accountId: string) => void;
-}> = ({ accountId, antennaId, partOfAntenna, isExclude, onToggle }) => {
-  const intl = useIntl();
-  const account = useAppSelector((state) => state.accounts.get(accountId));
-
-  const handleClick = useCallback(() => {
-    if (partOfAntenna) {
-      const api = isExclude
-        ? apiRemoveExcludeAccountFromAntenna
-        : apiRemoveAccountFromAntenna;
-      void api(antennaId, accountId);
-    } else {
-      const api = isExclude
-        ? apiAddExcludeAccountToAntenna
-        : apiAddAccountToAntenna;
-      void api(antennaId, accountId);
-    }
-
-    onToggle(accountId);
-  }, [accountId, antennaId, partOfAntenna, onToggle, isExclude]);
-
-  if (!account) {
-    return null;
-  }
-
-  const firstVerifiedField = account.fields.find((item) => !!item.verified_at);
-
-  return (
-    <div className='account'>
-      <div className='account__wrapper'>
-        <Link
-          key={account.id}
-          className='account__display-name'
-          title={account.acct}
-          to={`/@${account.acct}`}
-          data-hover-card-account={account.id}
-        >
-          <div className='account__avatar-wrapper'>
-            <Avatar account={account} size={36} />
-          </div>
-
-          <div className='account__contents'>
-            <DisplayName account={account} />
-
-            <div className='account__details'>
-              <ShortNumber
-                value={account.followers_count}
-                renderer={FollowersCounter}
-                isHide={account.other_settings.hide_followers_count}
-              />{' '}
-              {firstVerifiedField && (
-                <VerifiedBadge link={firstVerifiedField.value} />
-              )}
-            </div>
-          </div>
-        </Link>
-
-        <div className='account__relationship'>
-          <Button
-            text={intl.formatMessage(
-              partOfAntenna ? messages.remove : messages.add,
-            )}
-            onClick={handleClick}
-          />
-        </div>
-      </div>
-    </div>
-  );
-};
-
-const AntennaMembers: React.FC<{
-  isExclude?: boolean;
-  multiColumn?: boolean;
-}> = ({ isExclude, multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const { id } = useParams<{ id: string }>();
-  const intl = useIntl();
-
-  const [searching, setSearching] = useState(false);
-  const [accountIds, setAccountIds] = useState<string[]>([]);
-  const [searchAccountIds, setSearchAccountIds] = useState<string[]>([]);
-  const [loading, setLoading] = useState(true);
-  const [mode, setMode] = useState<Mode>('remove');
-
-  useEffect(() => {
-    if (id) {
-      setLoading(true);
-      dispatch(fetchAntenna(id));
-
-      const api = isExclude ? apiGetExcludeAccounts : apiGetAccounts;
-      void api(id)
-        .then((data) => {
-          dispatch(importFetchedAccounts(data));
-          setAccountIds(data.map((a) => a.id));
-          setLoading(false);
-          return '';
-        })
-        .catch(() => {
-          setLoading(false);
-        });
-
-      dispatch(fetchFollowing(me));
-    }
-  }, [dispatch, id, isExclude]);
-
-  const handleSearchClick = useCallback(() => {
-    setMode('add');
-  }, [setMode]);
-
-  const handleDismissSearchClick = useCallback(() => {
-    setMode('remove');
-    setSearching(false);
-  }, [setMode]);
-
-  const handleAccountToggle = useCallback(
-    (accountId: string) => {
-      const partOfAntenna = accountIds.includes(accountId);
-
-      if (partOfAntenna) {
-        setAccountIds(accountIds.filter((id) => id !== accountId));
-      } else {
-        setAccountIds([accountId, ...accountIds]);
-      }
-    },
-    [accountIds, setAccountIds],
-  );
-
-  const searchRequestRef = useRef<AbortController | null>(null);
-
-  const handleSearch = useDebouncedCallback(
-    (value: string) => {
-      if (searchRequestRef.current) {
-        searchRequestRef.current.abort();
-      }
-
-      if (value.trim().length === 0) {
-        setSearching(false);
-        return;
-      }
-
-      setLoading(true);
-
-      searchRequestRef.current = new AbortController();
-
-      void apiRequest<ApiAccountJSON[]>('GET', 'v1/accounts/search', {
-        signal: searchRequestRef.current.signal,
-        params: {
-          q: value,
-          resolve: false,
-        },
-      })
-        .then((data) => {
-          dispatch(importFetchedAccounts(data));
-          setSearchAccountIds(data.map((a) => a.id));
-          setLoading(false);
-          setSearching(true);
-          return '';
-        })
-        .catch(() => {
-          setSearching(true);
-          setLoading(false);
-        });
-    },
-    500,
-    { leading: true, trailing: true },
-  );
-
-  let displayedAccountIds: string[];
-
-  if (mode === 'add') {
-    displayedAccountIds = searching ? searchAccountIds : accountIds;
-  } else {
-    displayedAccountIds = accountIds;
-  }
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(messages.heading)}
-    >
-      {mode === 'remove' ? (
-        <ColumnHeader
-          title={intl.formatMessage(messages.heading)}
-          icon='antenna-ul'
-          iconComponent={AntennaIcon}
-          multiColumn={multiColumn}
-          showBackButton
-          extraButton={
-            <button
-              onClick={handleSearchClick}
-              type='button'
-              className='column-header__button'
-              title={intl.formatMessage(messages.enterSearch)}
-              aria-label={intl.formatMessage(messages.enterSearch)}
-            >
-              <Icon id='plus' icon={AddIcon} />
-            </button>
-          }
-        />
-      ) : (
-        <ColumnSearchHeader
-          onBack={handleDismissSearchClick}
-          onSubmit={handleSearch}
-        />
-      )}
-
-      <ScrollableList
-        scrollKey='antenna_members'
-        trackScroll={!multiColumn}
-        bindToDocument={!multiColumn}
-        isLoading={loading}
-        showLoading={loading && displayedAccountIds.length === 0}
-        hasMore={false}
-        footer={
-          mode === 'remove' && (
-            <>
-              {displayedAccountIds.length > 0 && <div className='spacer' />}
-
-              <div className='column-footer'>
-                <Link
-                  to={`/antennas/${id}/filtering`}
-                  className='button button--block'
-                >
-                  <FormattedMessage id='antennas.done' defaultMessage='Done' />
-                </Link>
-              </div>
-            </>
-          )
-        }
-        emptyMessage={
-          mode === 'remove' ? (
-            <>
-              <span>
-                <FormattedMessage
-                  id='antennas.no_members_yet'
-                  defaultMessage='No members yet.'
-                />
-                <br />
-                <FormattedMessage
-                  id='antennas.find_users_to_add'
-                  defaultMessage='Find users to add'
-                />
-              </span>
-
-              <SquigglyArrow className='empty-column-indicator__arrow' />
-            </>
-          ) : (
-            <FormattedMessage
-              id='antennas.no_results_found'
-              defaultMessage='No results found.'
-            />
-          )
-        }
-      >
-        {displayedAccountIds.map((accountId) => (
-          <AccountItem
-            key={accountId}
-            accountId={accountId}
-            antennaId={id}
-            partOfAntenna={
-              displayedAccountIds === accountIds ||
-              accountIds.includes(accountId)
-            }
-            isExclude={isExclude}
-            onToggle={handleAccountToggle}
-          />
-        ))}
-      </ScrollableList>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default AntennaMembers;
diff --git a/app/javascript/mastodon/features/antennas/new.tsx b/app/javascript/mastodon/features/antennas/new.tsx
deleted file mode 100644
index 56373bb60c..0000000000
--- a/app/javascript/mastodon/features/antennas/new.tsx
+++ /dev/null
@@ -1,537 +0,0 @@
-import { useCallback, useState, useEffect } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { useParams, useHistory, Link } from 'react-router-dom';
-
-import { isFulfilled } from '@reduxjs/toolkit';
-
-import Toggle from 'react-toggle';
-
-import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
-import { fetchAntenna } from 'mastodon/actions/antennas';
-import { createAntenna, updateAntenna } from 'mastodon/actions/antennas_typed';
-import { fetchLists } from 'mastodon/actions/lists';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { LoadingIndicator } from 'mastodon/components/loading_indicator';
-import { getOrderedLists } from 'mastodon/selectors/lists';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  edit: { id: 'column.edit_antenna', defaultMessage: 'Edit antenna' },
-  create: { id: 'column.create_antenna', defaultMessage: 'Create antenna' },
-});
-
-const FiltersLink: React.FC<{
-  id: string;
-}> = ({ id }) => {
-  return (
-    <Link to={`/antennas/${id}/filtering`} className='app-form__link'>
-      <div className='app-form__link__text'>
-        <strong>
-          <FormattedMessage
-            id='antennas.filter_items'
-            defaultMessage='Move to antenna filter setting'
-          />
-        </strong>
-      </div>
-    </Link>
-  );
-};
-
-const NewAntenna: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const { id } = useParams<{ id?: string }>();
-  const intl = useIntl();
-  const history = useHistory();
-
-  const antenna = useAppSelector((state) =>
-    id ? state.antennas.get(id) : undefined,
-  );
-  const lists = useAppSelector((state) => getOrderedLists(state));
-  const [title, setTitle] = useState('');
-  const [stl, setStl] = useState(false);
-  const [ltl, setLtl] = useState(false);
-  const [insertFeeds, setInsertFeeds] = useState(false);
-  const [listId, setListId] = useState('0');
-  const [withMediaOnly, setWithMediaOnly] = useState(false);
-  const [ignoreReblog, setIgnoreReblog] = useState(false);
-  const [mode, setMode] = useState('filtering');
-  const [destination, setDestination] = useState('timeline');
-  const [favourite, setFavourite] = useState(true);
-  const [submitting, setSubmitting] = useState(false);
-
-  useEffect(() => {
-    if (id) {
-      dispatch(fetchAntenna(id));
-      dispatch(fetchLists());
-    }
-  }, [dispatch, id]);
-
-  useEffect(() => {
-    if (id && antenna) {
-      setTitle(antenna.title);
-      setStl(antenna.stl);
-      setLtl(antenna.ltl);
-      setInsertFeeds(antenna.insert_feeds);
-      setListId(antenna.list?.id ?? '0');
-      setWithMediaOnly(antenna.with_media_only);
-      setIgnoreReblog(antenna.ignore_reblog);
-      setFavourite(antenna.favourite);
-
-      if (antenna.stl) {
-        setMode('stl');
-      } else if (antenna.ltl) {
-        setMode('ltl');
-      } else {
-        setMode('filtering');
-      }
-
-      if (antenna.insert_feeds) {
-        if (antenna.list) {
-          setDestination('list');
-        } else {
-          setDestination('home');
-        }
-      } else {
-        setDestination('timeline');
-      }
-    }
-  }, [
-    setTitle,
-    setStl,
-    setLtl,
-    setInsertFeeds,
-    setListId,
-    setWithMediaOnly,
-    setIgnoreReblog,
-    setMode,
-    setDestination,
-    setFavourite,
-    id,
-    antenna,
-    lists,
-  ]);
-
-  const handleTitleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setTitle(value);
-    },
-    [setTitle],
-  );
-
-  const handleListIdChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLSelectElement>) => {
-      setListId(value);
-    },
-    [setListId],
-  );
-
-  const handleModeChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLSelectElement>) => {
-      if (value === 'stl') {
-        setStl(true);
-        setLtl(false);
-      } else if (value === 'ltl') {
-        setStl(false);
-        setLtl(true);
-      } else if (value === 'filtering') {
-        setStl(false);
-        setLtl(false);
-      }
-
-      setMode(value);
-    },
-    [setLtl, setStl, setMode],
-  );
-
-  const handleDestinationChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLSelectElement>) => {
-      if (value === 'list') {
-        setInsertFeeds(true);
-        if (listId === '0' && lists.length > 0) {
-          setListId(lists[0]?.id ?? '0');
-        }
-      } else if (value === 'home') {
-        setInsertFeeds(true);
-        // listId = 0
-      } else if (value === 'timeline') {
-        setInsertFeeds(false);
-      }
-
-      setDestination(value);
-    },
-    [setDestination, setListId, listId, lists],
-  );
-
-  const handleWithMediaOnlyChange = useCallback(
-    ({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
-      setWithMediaOnly(checked);
-    },
-    [setWithMediaOnly],
-  );
-
-  const handleIgnoreReblogChange = useCallback(
-    ({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
-      setIgnoreReblog(checked);
-    },
-    [setIgnoreReblog],
-  );
-
-  const handleFavouriteChange = useCallback(
-    ({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
-      setFavourite(checked);
-    },
-    [setFavourite],
-  );
-
-  const handleSubmit = useCallback(() => {
-    setSubmitting(true);
-
-    if (id) {
-      void dispatch(
-        updateAntenna({
-          id,
-          title,
-          stl,
-          ltl,
-          insert_feeds: insertFeeds,
-          list_id: destination === 'list' ? listId : '0',
-          with_media_only: withMediaOnly,
-          ignore_reblog: ignoreReblog,
-          favourite,
-        }),
-      ).then(() => {
-        setSubmitting(false);
-        return '';
-      });
-    } else {
-      void dispatch(
-        createAntenna({
-          title,
-          stl,
-          ltl,
-          insert_feeds: insertFeeds,
-          list_id: destination === 'list' ? listId : '0',
-          with_media_only: withMediaOnly,
-          ignore_reblog: ignoreReblog,
-          favourite,
-        }),
-      ).then((result) => {
-        setSubmitting(false);
-
-        if (isFulfilled(result)) {
-          history.replace(`/antennas/${result.payload.id}/edit`);
-          if (stl || ltl) {
-            history.push(`/antennas`);
-          } else {
-            history.push(`/antennas/${result.payload.id}/filtering`);
-          }
-        }
-
-        return '';
-      });
-    }
-  }, [
-    history,
-    dispatch,
-    setSubmitting,
-    id,
-    title,
-    stl,
-    ltl,
-    insertFeeds,
-    listId,
-    withMediaOnly,
-    ignoreReblog,
-    destination,
-    favourite,
-  ]);
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(id ? messages.edit : messages.create)}
-    >
-      <ColumnHeader
-        title={intl.formatMessage(id ? messages.edit : messages.create)}
-        icon='antenna-ul'
-        iconComponent={AntennaIcon}
-        multiColumn={multiColumn}
-        showBackButton
-      />
-
-      <div className='scrollable'>
-        <form className='simple_form app-form' onSubmit={handleSubmit}>
-          <div className='fields-group'>
-            <div className='input with_label'>
-              <div className='label_input'>
-                <label htmlFor='antenna_title'>
-                  <FormattedMessage
-                    id='antennas.antenna_name'
-                    defaultMessage='Antenna name'
-                  />
-                </label>
-
-                <div className='label_input__wrapper'>
-                  <input
-                    id='antenna_title'
-                    type='text'
-                    value={title}
-                    onChange={handleTitleChange}
-                    maxLength={30}
-                    required
-                    placeholder=' '
-                  />
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <div className='fields-group'>
-            <div className='input with_label'>
-              <div className='label_input'>
-                <label htmlFor='antenna_list'>
-                  <FormattedMessage id='antennas.mode' defaultMessage='Mode' />
-                </label>
-
-                <div className='label_input__wrapper'>
-                  <select
-                    id='antenna_insert_list'
-                    value={mode}
-                    onChange={handleModeChange}
-                  >
-                    <FormattedMessage
-                      id='antennas.mode.stl'
-                      defaultMessage='Social timeline mode'
-                    >
-                      {(msg) => <option value='stl'>{msg}</option>}
-                    </FormattedMessage>
-                    <FormattedMessage
-                      id='antennas.mode.ltl'
-                      defaultMessage='Local timeline mode'
-                    >
-                      {(msg) => <option value='ltl'>{msg}</option>}
-                    </FormattedMessage>
-                    <FormattedMessage
-                      id='antennas.mode.filtering'
-                      defaultMessage='Filtering'
-                    >
-                      {(msg) => <option value='filtering'>{msg}</option>}
-                    </FormattedMessage>
-                  </select>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <div className='fields-group'>
-            <div className='input with_label'>
-              <div className='label_input'>
-                <label htmlFor='antenna_list'>
-                  <FormattedMessage
-                    id='antennas.destination'
-                    defaultMessage='Destination'
-                  />
-                </label>
-
-                <div className='label_input__wrapper'>
-                  <select
-                    id='antenna_insert_destination'
-                    value={destination}
-                    onChange={handleDestinationChange}
-                  >
-                    <FormattedMessage
-                      id='antennas.destination.home'
-                      defaultMessage='Insert to home'
-                    >
-                      {(msg) => <option value='home'>{msg}</option>}
-                    </FormattedMessage>
-                    <FormattedMessage
-                      id='antennas.destination.list'
-                      defaultMessage='Insert to list'
-                    >
-                      {(msg) => <option value='list'>{msg}</option>}
-                    </FormattedMessage>
-                    <FormattedMessage
-                      id='antennas.destination.timeline'
-                      defaultMessage='Antenna timeline only'
-                    >
-                      {(msg) => <option value='timeline'>{msg}</option>}
-                    </FormattedMessage>
-                  </select>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          {destination === 'list' && (
-            <div className='fields-group'>
-              <div className='input with_label'>
-                <div className='label_input'>
-                  <label htmlFor='antenna_list'>
-                    <FormattedMessage
-                      id='antennas.list_selection'
-                      defaultMessage='List to insert'
-                    />
-                  </label>
-
-                  <div className='label_input__wrapper'>
-                    <select
-                      id='antenna_insert_list'
-                      value={listId}
-                      onChange={handleListIdChange}
-                    >
-                      {lists.map((list) => (
-                        <option key={list.id} value={list.id}>
-                          {list.title}
-                        </option>
-                      ))}
-                    </select>
-                  </div>
-                </div>
-              </div>
-            </div>
-          )}
-
-          {id && mode === 'filtering' && (
-            <div className='fields-group'>
-              <FiltersLink id={id} />
-            </div>
-          )}
-
-          {!id && mode === 'filtering' && (
-            <div className='fields-group'>
-              <div className='app-form__memo'>
-                <FormattedMessage
-                  id='antennas.save_to_edit_filtering'
-                  defaultMessage='You can edit the filtering after saving.'
-                />
-              </div>
-            </div>
-          )}
-
-          {mode === 'filtering' && (
-            <>
-              <div className='fields-group'>
-                {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
-                <label className='app-form__toggle'>
-                  <div className='app-form__toggle__label'>
-                    <strong>
-                      <FormattedMessage
-                        id='antennas.media_only'
-                        defaultMessage='Media only'
-                      />
-                    </strong>
-                    <span className='hint'>
-                      <FormattedMessage
-                        id='antennas.media_only_hint'
-                        defaultMessage='Only posts with media will be added antenna.'
-                      />
-                    </span>
-                  </div>
-
-                  <div className='app-form__toggle__toggle'>
-                    <div>
-                      <Toggle
-                        checked={withMediaOnly}
-                        onChange={handleWithMediaOnlyChange}
-                      />
-                    </div>
-                  </div>
-                </label>
-              </div>
-
-              <div className='fields-group'>
-                {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
-                <label className='app-form__toggle'>
-                  <div className='app-form__toggle__label'>
-                    <strong>
-                      <FormattedMessage
-                        id='antennas.ignore_reblog'
-                        defaultMessage='Exclude boosts'
-                      />
-                    </strong>
-                    <span className='hint'>
-                      <FormattedMessage
-                        id='antennas.ignore_reblog_hint'
-                        defaultMessage='Boosts will be excluded from antenna detection.'
-                      />
-                    </span>
-                  </div>
-
-                  <div className='app-form__toggle__toggle'>
-                    <div>
-                      <Toggle
-                        checked={ignoreReblog}
-                        onChange={handleIgnoreReblogChange}
-                      />
-                    </div>
-                  </div>
-                </label>
-              </div>
-            </>
-          )}
-
-          <div className='fields-group'>
-            {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
-            <label className='app-form__toggle'>
-              <div className='app-form__toggle__label'>
-                <strong>
-                  <FormattedMessage
-                    id='antennas.favourite'
-                    defaultMessage='Favorite'
-                  />
-                </strong>
-                <span className='hint'>
-                  <FormattedMessage
-                    id='antennas.favourite_hint'
-                    defaultMessage='When opening the Web Client on a PC, this antenna appears in the navigation.'
-                  />
-                </span>
-              </div>
-
-              <div className='app-form__toggle__toggle'>
-                <div>
-                  <Toggle
-                    checked={favourite}
-                    onChange={handleFavouriteChange}
-                  />
-                </div>
-              </div>
-            </label>
-          </div>
-
-          <div className='actions'>
-            <button className='button' type='submit'>
-              {submitting ? (
-                <LoadingIndicator />
-              ) : id ? (
-                <FormattedMessage id='antennas.save' defaultMessage='Save' />
-              ) : (
-                <FormattedMessage
-                  id='antennas.create'
-                  defaultMessage='Create'
-                />
-              )}
-            </button>
-          </div>
-        </form>
-      </div>
-
-      <Helmet>
-        <title>
-          {intl.formatMessage(id ? messages.edit : messages.create)}
-        </title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default NewAntenna;
diff --git a/app/javascript/mastodon/features/audio/index.jsx b/app/javascript/mastodon/features/audio/index.jsx
index 53ce3f0bdb..fdc1b0be0f 100644
--- a/app/javascript/mastodon/features/audio/index.jsx
+++ b/app/javascript/mastodon/features/audio/index.jsx
@@ -1,7 +1,7 @@
 import PropTypes from 'prop-types';
 import { PureComponent } from 'react';
 
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
 
 import classNames from 'classnames';
 
@@ -16,7 +16,6 @@ import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?reac
 import VolumeOffIcon from '@/material-icons/400-24px/volume_off-fill.svg?react';
 import VolumeUpIcon from '@/material-icons/400-24px/volume_up-fill.svg?react';
 import { Icon }  from 'mastodon/components/icon';
-import { SpoilerButton } from 'mastodon/components/spoiler_button';
 import { formatTime, getPointerPosition, fileNameFromURL } from 'mastodon/features/video';
 
 import { Blurhash } from '../../components/blurhash';
@@ -27,8 +26,8 @@ import Visualizer from './visualizer';
 const messages = defineMessages({
   play: { id: 'video.play', defaultMessage: 'Play' },
   pause: { id: 'video.pause', defaultMessage: 'Pause' },
-  mute: { id: 'video.mute', defaultMessage: 'Mute' },
-  unmute: { id: 'video.unmute', defaultMessage: 'Unmute' },
+  mute: { id: 'video.mute', defaultMessage: 'Mute sound' },
+  unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
   download: { id: 'video.download', defaultMessage: 'Download file' },
   hide: { id: 'audio.hide', defaultMessage: 'Hide audio' },
 });
@@ -62,7 +61,6 @@ class Audio extends PureComponent {
     volume: PropTypes.number,
     muted: PropTypes.bool,
     deployPictureInPicture: PropTypes.func,
-    matchedFilters: PropTypes.arrayOf(PropTypes.string),
   };
 
   state = {
@@ -473,11 +471,19 @@ class Audio extends PureComponent {
   };
 
   render () {
-    const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash, matchedFilters } = this.props;
+    const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash } = this.props;
     const { paused, volume, currentTime, duration, buffer, dragging, revealed } = this.state;
     const progress = Math.min((currentTime / duration) * 100, 100);
     const muted = this.state.muted || volume === 0;
 
+    let warning;
+
+    if (sensitive) {
+      warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
+    } else {
+      warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
+    }
+
     return (
       <div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), aspectRatio: '16 / 9' }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex={0} onKeyDown={this.handleKeyDown}>
 
@@ -515,7 +521,14 @@ class Audio extends PureComponent {
           lang={lang}
         />
 
-        <SpoilerButton hidden={revealed || editable} sensitive={sensitive} onClick={this.toggleReveal} matchedFilters={matchedFilters} />
+        <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
+          <button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
+            <span className='spoiler-button__overlay__label'>
+              {warning}
+              <span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
+            </span>
+          </button>
+        </div>
 
         {(revealed || editable) && <img
           src={this.props.poster}
@@ -568,14 +581,10 @@ class Audio extends PureComponent {
             </div>
 
             <div className='video-player__buttons right'>
-              {!editable && (
-                <>
-                  <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' icon={VisibilityOffIcon} /></button>
-                  <a title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} className='video-player__download__icon player-button' href={this.props.src} download>
-                    <Icon id='download' icon={DownloadIcon} />
-                  </a>
-                </>
-              )}
+              {!editable && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' icon={VisibilityOffIcon} /></button>}
+              <a title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} className='video-player__download__icon player-button' href={this.props.src} download>
+                <Icon id={'download'} icon={DownloadIcon} />
+              </a>
             </div>
           </div>
         </div>
diff --git a/app/javascript/mastodon/features/blocks/index.jsx b/app/javascript/mastodon/features/blocks/index.jsx
index 57f86042e3..1a631d3d07 100644
--- a/app/javascript/mastodon/features/blocks/index.jsx
+++ b/app/javascript/mastodon/features/blocks/index.jsx
@@ -9,11 +9,11 @@ import { connect } from 'react-redux';
 import { debounce } from 'lodash';
 
 import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react';
-import { Account } from 'mastodon/components/account';
 
 import { fetchBlocks, expandBlocks } from '../../actions/blocks';
 import { LoadingIndicator } from '../../components/loading_indicator';
 import ScrollableList from '../../components/scrollable_list';
+import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
 
 const messages = defineMessages({
@@ -70,7 +70,7 @@ class Blocks extends ImmutablePureComponent {
           bindToDocument={!multiColumn}
         >
           {accountIds.map(id =>
-            <Account key={id} id={id} defaultAction='block' />,
+            <AccountContainer key={id} id={id} defaultAction='block' />,
           )}
         </ScrollableList>
       </Column>
diff --git a/app/javascript/mastodon/features/bookmark_categories/components/new_bookmark_category_form.jsx b/app/javascript/mastodon/features/bookmark_categories/components/new_bookmark_category_form.jsx
new file mode 100644
index 0000000000..35309c121d
--- /dev/null
+++ b/app/javascript/mastodon/features/bookmark_categories/components/new_bookmark_category_form.jsx
@@ -0,0 +1,80 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import { changeBookmarkCategoryEditorTitle, submitBookmarkCategoryEditor } from 'mastodon/actions/bookmark_categories';
+import { Button } from 'mastodon/components/button';
+
+const messages = defineMessages({
+  label: { id: 'bookmark_categories.new.title_placeholder', defaultMessage: 'New category title' },
+  title: { id: 'bookmark_categories.new.create', defaultMessage: 'Add category' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['bookmarkCategoryEditor', 'title']),
+  disabled: state.getIn(['bookmarkCategoryEditor', 'isSubmitting']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onChange: value => dispatch(changeBookmarkCategoryEditorTitle(value)),
+  onSubmit: () => dispatch(submitBookmarkCategoryEditor(true)),
+});
+
+class NewBookmarkCategoryForm extends PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    disabled: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleSubmit = e => {
+    e.preventDefault();
+    this.props.onSubmit();
+  };
+
+  handleClick = () => {
+    this.props.onSubmit();
+  };
+
+  render () {
+    const { value, disabled, intl } = this.props;
+
+    const label = intl.formatMessage(messages.label);
+    const title = intl.formatMessage(messages.title);
+
+    return (
+      <form className='column-inline-form' onSubmit={this.handleSubmit}>
+        <label>
+          <span style={{ display: 'none' }}>{label}</span>
+
+          <input
+            className='setting-text'
+            value={value}
+            disabled={disabled}
+            onChange={this.handleChange}
+            placeholder={label}
+          />
+        </label>
+
+        <Button
+          disabled={disabled || !value}
+          text={title}
+          onClick={this.handleClick}
+        />
+      </form>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(NewBookmarkCategoryForm));
diff --git a/app/javascript/mastodon/features/bookmark_categories/index.jsx b/app/javascript/mastodon/features/bookmark_categories/index.jsx
new file mode 100644
index 0000000000..aeb37db27c
--- /dev/null
+++ b/app/javascript/mastodon/features/bookmark_categories/index.jsx
@@ -0,0 +1,98 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import { createSelector } from '@reduxjs/toolkit';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+
+import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg';
+import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
+import { fetchBookmarkCategories } from 'mastodon/actions/bookmark_categories';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import ScrollableList from 'mastodon/components/scrollable_list';
+import ColumnLink from 'mastodon/features/ui/components/column_link';
+import ColumnSubheading from 'mastodon/features/ui/components/column_subheading';
+
+import NewListForm from './components/new_bookmark_category_form';
+
+const messages = defineMessages({
+  heading: { id: 'column.bookmark_categories', defaultMessage: 'Bookmark categories' },
+  subheading: { id: 'bookmark_categories.subheading', defaultMessage: 'Your categories' },
+  allBookmarks: { id: 'bookmark_categories.all_bookmarks', defaultMessage: 'All bookmarks' },
+});
+
+const getOrderedCategories = createSelector([state => state.get('bookmark_categories')], categories => {
+  if (!categories) {
+    return categories;
+  }
+
+  return categories.toList().filter(item => !!item && typeof item.get('title') !== 'undefined' && item.get('title') !== null).sort((a, b) => a.get('title').localeCompare(b.get('title')));
+});
+
+const mapStateToProps = state => ({
+  categories: getOrderedCategories(state),
+});
+
+class BookmarkCategories extends ImmutablePureComponent {
+
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    categories: ImmutablePropTypes.list,
+    intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
+  };
+
+  UNSAFE_componentWillMount () {
+    this.props.dispatch(fetchBookmarkCategories());
+  }
+
+  render () {
+    const { intl, categories, multiColumn } = this.props;
+
+    if (!categories) {
+      return (
+        <Column>
+          <LoadingIndicator />
+        </Column>
+      );
+    }
+
+    const emptyMessage = <FormattedMessage id='empty_column.bookmark_categories' defaultMessage="You don't have any categories yet. When you create one, it will show up here." />;
+
+    return (
+      <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.heading)}>
+        <ColumnHeader title={intl.formatMessage(messages.heading)} icon='bookmark' iconComponent={BookmarksIcon} multiColumn={multiColumn} />
+
+        <NewListForm />
+
+        <ColumnLink to='/bookmarks' icon='bookmark' iconComponent={BookmarkIcon} text={intl.formatMessage(messages.allBookmarks)} />
+        <ScrollableList
+          scrollKey='bookmark_categories'
+          emptyMessage={emptyMessage}
+          prepend={<ColumnSubheading text={intl.formatMessage(messages.subheading)} />}
+          bindToDocument={!multiColumn}
+        >
+          {categories.map(category =>
+            <ColumnLink key={category.get('id')} to={`/bookmark_categories/${category.get('id')}`} icon='bookmark' iconComponent={BookmarkIcon} text={category.get('title')} />,
+          )}
+        </ScrollableList>
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.heading)}</title>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(BookmarkCategories));
diff --git a/app/javascript/mastodon/features/bookmark_categories/index.tsx b/app/javascript/mastodon/features/bookmark_categories/index.tsx
deleted file mode 100644
index 92a2647989..0000000000
--- a/app/javascript/mastodon/features/bookmark_categories/index.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-import { useEffect, useMemo, useCallback } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { Link } from 'react-router-dom';
-
-import AddIcon from '@/material-icons/400-24px/add.svg?react';
-import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react';
-import BookmarksIcon from '@/material-icons/400-24px/bookmarks.svg?react';
-import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
-import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
-import { fetchBookmarkCategories } from 'mastodon/actions/bookmark_categories';
-import { openModal } from 'mastodon/actions/modal';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
-import { Icon } from 'mastodon/components/icon';
-import ScrollableList from 'mastodon/components/scrollable_list';
-import { getOrderedBookmarkCategories } from 'mastodon/selectors/bookmark_categories';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-const messages = defineMessages({
-  heading: {
-    id: 'column.bookmark_categories',
-    defaultMessage: 'BookmarkCategories',
-  },
-  create: {
-    id: 'bookmark_categories.create_bookmark_category',
-    defaultMessage: 'Create category',
-  },
-  edit: {
-    id: 'bookmark_categories.edit',
-    defaultMessage: 'Edit category',
-  },
-  delete: {
-    id: 'bookmark_categories.delete',
-    defaultMessage: 'Delete category',
-  },
-  more: { id: 'status.more', defaultMessage: 'More' },
-});
-
-const BookmarkCategoryItem: React.FC<{
-  id: string;
-  title: string;
-}> = ({ id, title }) => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-
-  const handleDeleteClick = useCallback(() => {
-    dispatch(
-      openModal({
-        modalType: 'CONFIRM_DELETE_BOOKMARK_CATEGORY',
-        modalProps: {
-          bookmark_categoryId: id,
-        },
-      }),
-    );
-  }, [dispatch, id]);
-
-  const menu = useMemo(
-    () => [
-      {
-        text: intl.formatMessage(messages.edit),
-        to: `/bookmark_categories/${id}/edit`,
-      },
-      { text: intl.formatMessage(messages.delete), action: handleDeleteClick },
-    ],
-    [intl, id, handleDeleteClick],
-  );
-
-  return (
-    <div className='lists__item'>
-      <Link to={`/bookmark_categories/${id}`} className='lists__item__title'>
-        <Icon id='bookmark_category-ul' icon={BookmarkIcon} />
-        <span>{title}</span>
-      </Link>
-
-      <Dropdown
-        scrollKey='bookmark_categories'
-        items={menu}
-        icon='ellipsis-h'
-        iconComponent={MoreHorizIcon}
-        title={intl.formatMessage(messages.more)}
-      />
-    </div>
-  );
-};
-
-const BookmarkCategories: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-  const bookmark_categories = useAppSelector((state) =>
-    getOrderedBookmarkCategories(state),
-  );
-
-  useEffect(() => {
-    dispatch(fetchBookmarkCategories());
-  }, [dispatch]);
-
-  const emptyMessage = (
-    <>
-      <span>
-        <FormattedMessage
-          id='bookmark_categories.no_bookmark_categories_yet'
-          defaultMessage='No bookmark_categories yet.'
-        />
-        <br />
-        <FormattedMessage
-          id='bookmark_categories.create_a_bookmark_category_to_organize'
-          defaultMessage='Create a new bookmark_category to organize your Home feed'
-        />
-      </span>
-
-      <SquigglyArrow className='empty-column-indicator__arrow' />
-    </>
-  );
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(messages.heading)}
-    >
-      <ColumnHeader
-        title={intl.formatMessage(messages.heading)}
-        icon='bookmark_category-ul'
-        iconComponent={BookmarkIcon}
-        multiColumn={multiColumn}
-        extraButton={
-          <Link
-            to='/bookmark_categories/new'
-            className='column-header__button'
-            title={intl.formatMessage(messages.create)}
-            aria-label={intl.formatMessage(messages.create)}
-          >
-            <Icon id='plus' icon={AddIcon} />
-          </Link>
-        }
-      />
-
-      <ScrollableList
-        scrollKey='bookmark_categories'
-        emptyMessage={emptyMessage}
-        bindToDocument={!multiColumn}
-        alwaysPrepend
-        prepend={
-          <div className='lists__item'>
-            <Link to={'/bookmarks'} className='lists__item__title'>
-              <Icon id='bookmarks' icon={BookmarksIcon} />
-              <span>
-                <FormattedMessage
-                  id='bookmark_categories.all_bookmarks'
-                  defaultMessage='All bookmarks'
-                />
-              </span>
-            </Link>
-          </div>
-        }
-      >
-        {bookmark_categories.map((bookmark_category) => (
-          <BookmarkCategoryItem
-            key={bookmark_category.id}
-            id={bookmark_category.id}
-            title={bookmark_category.title}
-          />
-        ))}
-      </ScrollableList>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default BookmarkCategories;
diff --git a/app/javascript/mastodon/features/bookmark_categories/new.tsx b/app/javascript/mastodon/features/bookmark_categories/new.tsx
deleted file mode 100644
index 3154b643ef..0000000000
--- a/app/javascript/mastodon/features/bookmark_categories/new.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-import { useCallback, useState, useEffect } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { useParams, useHistory } from 'react-router-dom';
-
-import { isFulfilled } from '@reduxjs/toolkit';
-
-import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react';
-import { fetchBookmarkCategory } from 'mastodon/actions/bookmark_categories';
-import {
-  createBookmarkCategory,
-  updateBookmarkCategory,
-} from 'mastodon/actions/bookmark_categories_typed';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { LoadingIndicator } from 'mastodon/components/loading_indicator';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  edit: {
-    id: 'column.edit_bookmark_category',
-    defaultMessage: 'Edit bookmark_category',
-  },
-  create: {
-    id: 'column.create_bookmark_category',
-    defaultMessage: 'Create bookmark_category',
-  },
-});
-
-const NewBookmarkCategory: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const { id } = useParams<{ id?: string }>();
-  const intl = useIntl();
-  const history = useHistory();
-
-  const bookmark_category = useAppSelector((state) =>
-    id ? state.bookmark_categories.get(id) : undefined,
-  );
-  const [title, setTitle] = useState('');
-  const [submitting, setSubmitting] = useState(false);
-
-  useEffect(() => {
-    if (id) {
-      dispatch(fetchBookmarkCategory(id));
-    }
-  }, [dispatch, id]);
-
-  useEffect(() => {
-    if (id && bookmark_category) {
-      setTitle(bookmark_category.title);
-    }
-  }, [setTitle, id, bookmark_category]);
-
-  const handleTitleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setTitle(value);
-    },
-    [setTitle],
-  );
-
-  const handleSubmit = useCallback(() => {
-    setSubmitting(true);
-
-    if (id) {
-      void dispatch(
-        updateBookmarkCategory({
-          id,
-          title,
-        }),
-      ).then(() => {
-        setSubmitting(false);
-        return '';
-      });
-    } else {
-      void dispatch(
-        createBookmarkCategory({
-          title,
-        }),
-      ).then((result) => {
-        setSubmitting(false);
-
-        if (isFulfilled(result)) {
-          history.replace(`/bookmark_categories/${result.payload.id}/edit`);
-          history.push(`/bookmark_categories`);
-        }
-
-        return '';
-      });
-    }
-  }, [history, dispatch, setSubmitting, id, title]);
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(id ? messages.edit : messages.create)}
-    >
-      <ColumnHeader
-        title={intl.formatMessage(id ? messages.edit : messages.create)}
-        icon='bookmark_category-ul'
-        iconComponent={BookmarkIcon}
-        multiColumn={multiColumn}
-        showBackButton
-      />
-
-      <div className='scrollable'>
-        <form className='simple_form app-form' onSubmit={handleSubmit}>
-          <div className='fields-group'>
-            <div className='input with_label'>
-              <div className='label_input'>
-                <label htmlFor='bookmark_category_title'>
-                  <FormattedMessage
-                    id='bookmark_categories.bookmark_category_name'
-                    defaultMessage='BookmarkCategory name'
-                  />
-                </label>
-
-                <div className='label_input__wrapper'>
-                  <input
-                    id='bookmark_category_title'
-                    type='text'
-                    value={title}
-                    onChange={handleTitleChange}
-                    maxLength={30}
-                    required
-                    placeholder=' '
-                  />
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <div className='actions'>
-            <button className='button' type='submit'>
-              {submitting ? (
-                <LoadingIndicator />
-              ) : id ? (
-                <FormattedMessage
-                  id='bookmark_categories.save'
-                  defaultMessage='Save'
-                />
-              ) : (
-                <FormattedMessage
-                  id='bookmark_categories.create'
-                  defaultMessage='Create'
-                />
-              )}
-            </button>
-          </div>
-        </form>
-      </div>
-
-      <Helmet>
-        <title>
-          {intl.formatMessage(id ? messages.edit : messages.create)}
-        </title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default NewBookmarkCategory;
diff --git a/app/javascript/mastodon/features/bookmark_category_adder/components/account.jsx b/app/javascript/mastodon/features/bookmark_category_adder/components/account.jsx
new file mode 100644
index 0000000000..31a2e96379
--- /dev/null
+++ b/app/javascript/mastodon/features/bookmark_category_adder/components/account.jsx
@@ -0,0 +1,43 @@
+import { injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { Avatar } from '../../../components/avatar';
+import { DisplayName } from '../../../components/display_name';
+import { makeGetAccount } from '../../../selectors';
+
+const makeMapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  const mapStateToProps = (state, { accountId }) => ({
+    account: getAccount(state, accountId),
+  });
+
+  return mapStateToProps;
+};
+
+class Account extends ImmutablePureComponent {
+
+  static propTypes = {
+    account: ImmutablePropTypes.map.isRequired,
+  };
+
+  render () {
+    const { account } = this.props;
+    return (
+      <div className='account'>
+        <div className='account__wrapper'>
+          <div className='account__display-name'>
+            <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
+            <DisplayName account={account} />
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(makeMapStateToProps)(injectIntl(Account));
diff --git a/app/javascript/mastodon/features/bookmark_category_adder/components/bookmark_category.jsx b/app/javascript/mastodon/features/bookmark_category_adder/components/bookmark_category.jsx
new file mode 100644
index 0000000000..5f33a9d0f5
--- /dev/null
+++ b/app/javascript/mastodon/features/bookmark_category_adder/components/bookmark_category.jsx
@@ -0,0 +1,76 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import AddIcon from '@/material-icons/400-24px/add.svg?react';
+import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react';
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+import { Icon }  from 'mastodon/components/icon';
+
+import { removeFromBookmarkCategoryAdder, addToBookmarkCategoryAdder } from '../../../actions/bookmark_categories';
+import { IconButton }  from '../../../components/icon_button';
+
+const messages = defineMessages({
+  remove: { id: 'bookmark_categories.status.remove', defaultMessage: 'Remove from bookmark category' },
+  add: { id: 'bookmark_categories.status.add', defaultMessage: 'Add to bookmark category' },
+});
+
+const MapStateToProps = (state, { bookmarkCategoryId, added }) => ({
+  bookmarkCategory: state.get('bookmark_categories').get(bookmarkCategoryId),
+  added: typeof added === 'undefined' ? state.getIn(['bookmarkCategoryAdder', 'bookmarkCategories', 'items']).includes(bookmarkCategoryId) : added,
+});
+
+const mapDispatchToProps = (dispatch, { bookmarkCategoryId }) => ({
+  onRemove: () => dispatch(removeFromBookmarkCategoryAdder(bookmarkCategoryId)),
+  onAdd: () => dispatch(addToBookmarkCategoryAdder(bookmarkCategoryId)),
+});
+
+class BookmarkCategory extends ImmutablePureComponent {
+
+  static propTypes = {
+    bookmarkCategory: ImmutablePropTypes.map.isRequired,
+    intl: PropTypes.object.isRequired,
+    onRemove: PropTypes.func.isRequired,
+    onAdd: PropTypes.func.isRequired,
+    added: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    added: false,
+  };
+
+  render () {
+    const { bookmarkCategory, intl, onRemove, onAdd, added } = this.props;
+
+    let button;
+
+    if (added) {
+      button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
+    } else {
+      button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
+    }
+
+    return (
+      <div className='list'>
+        <div className='list__wrapper'>
+          <div className='list__display-name'>
+            <Icon id='bookmark' icon={BookmarkIcon} className='column-link__icon' fixedWidth />
+            {bookmarkCategory.get('title')}
+          </div>
+
+          <div className='account__relationship'>
+            {button}
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(BookmarkCategory));
diff --git a/app/javascript/mastodon/features/bookmark_category_adder/index.jsx b/app/javascript/mastodon/features/bookmark_category_adder/index.jsx
new file mode 100644
index 0000000000..7381e1688d
--- /dev/null
+++ b/app/javascript/mastodon/features/bookmark_category_adder/index.jsx
@@ -0,0 +1,78 @@
+import PropTypes from 'prop-types';
+
+import { injectIntl } from 'react-intl';
+
+import { createSelector } from '@reduxjs/toolkit';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+
+import { setupBookmarkCategoryAdder, resetBookmarkCategoryAdder } from '../../actions/bookmark_categories';
+import NewBookmarkCategoryForm from '../bookmark_categories/components/new_bookmark_category_form';
+
+// import Account from './components/account';
+import BookmarkCategory from './components/bookmark_category';
+
+const getOrderedBookmarkCategories = createSelector([state => state.get('bookmark_categories')], bookmarkCategories => {
+  if (!bookmarkCategories) {
+    return bookmarkCategories;
+  }
+
+  return bookmarkCategories.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
+});
+
+const mapStateToProps = state => ({
+  bookmarkCategoryIds: getOrderedBookmarkCategories(state).map(bookmarkCategory=>bookmarkCategory.get('id')),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onInitialize: statusId => dispatch(setupBookmarkCategoryAdder(statusId)),
+  onReset: () => dispatch(resetBookmarkCategoryAdder()),
+});
+
+class BookmarkCategoryAdder extends ImmutablePureComponent {
+
+  static propTypes = {
+    statusId: PropTypes.string.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    onInitialize: PropTypes.func.isRequired,
+    onReset: PropTypes.func.isRequired,
+    bookmarkCategoryIds: ImmutablePropTypes.list.isRequired,
+  };
+
+  componentDidMount () {
+    const { onInitialize, statusId } = this.props;
+    onInitialize(statusId);
+  }
+
+  componentWillUnmount () {
+    const { onReset } = this.props;
+    onReset();
+  }
+
+  render () {
+    const { bookmarkCategoryIds } = this.props;
+
+    return (
+      <div className='modal-root__modal list-adder'>
+        {/*
+        <div className='list-adder__account'>
+          <Account accountId={accountId} />
+        </div>
+        */}
+
+        <NewBookmarkCategoryForm />
+
+
+        <div className='list-adder__lists'>
+          {bookmarkCategoryIds.map(BookmarkCategoryId => <BookmarkCategory key={BookmarkCategoryId} bookmarkCategoryId={BookmarkCategoryId} />)}
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(BookmarkCategoryAdder));
diff --git a/app/javascript/mastodon/features/bookmark_category_adder/index.tsx b/app/javascript/mastodon/features/bookmark_category_adder/index.tsx
deleted file mode 100644
index e2fe0d52a3..0000000000
--- a/app/javascript/mastodon/features/bookmark_category_adder/index.tsx
+++ /dev/null
@@ -1,268 +0,0 @@
-import { useEffect, useState, useCallback } from 'react';
-
-import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
-
-import { isFulfilled } from '@reduxjs/toolkit';
-
-import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react';
-import CloseIcon from '@/material-icons/400-24px/close.svg?react';
-import {
-  bookmarkCategoryEditorAddSuccess,
-  bookmarkCategoryEditorRemoveSuccess,
-  fetchBookmarkCategories,
-} from 'mastodon/actions/bookmark_categories';
-import { createBookmarkCategory } from 'mastodon/actions/bookmark_categories_typed';
-import { unbookmark } from 'mastodon/actions/interactions';
-import {
-  apiGetStatusBookmarkCategories,
-  apiAddStatusToBookmarkCategory,
-  apiRemoveStatusFromBookmarkCategory,
-} from 'mastodon/api/bookmark_categories';
-import type { ApiBookmarkCategoryJSON } from 'mastodon/api_types/bookmark_categories';
-import { Button } from 'mastodon/components/button';
-import { CheckBox } from 'mastodon/components/check_box';
-import { Icon } from 'mastodon/components/icon';
-import { IconButton } from 'mastodon/components/icon_button';
-import { bookmarkCategoryNeeded } from 'mastodon/initial_state';
-import { getOrderedBookmarkCategories } from 'mastodon/selectors/bookmark_categories';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  newBookmarkCategory: {
-    id: 'bookmark_categories.new_bookmark_category_name',
-    defaultMessage: 'New bookmark_category name',
-  },
-  createBookmarkCategory: {
-    id: 'bookmark_categories.create',
-    defaultMessage: 'Create',
-  },
-  close: {
-    id: 'lightbox.close',
-    defaultMessage: 'Close',
-  },
-});
-
-const BookmarkCategoryItem: React.FC<{
-  id: string;
-  title: string;
-  checked: boolean;
-  onChange: (id: string, checked: boolean) => void;
-}> = ({ id, title, checked, onChange }) => {
-  const handleChange = useCallback(
-    (e: React.ChangeEvent<HTMLInputElement>) => {
-      onChange(id, e.target.checked);
-    },
-    [id, onChange],
-  );
-
-  return (
-    // eslint-disable-next-line jsx-a11y/label-has-associated-control
-    <label className='lists__item'>
-      <div className='lists__item__title'>
-        <Icon id='bookmark_category-ul' icon={BookmarkIcon} />
-        <span>{title}</span>
-      </div>
-
-      <CheckBox value={id} checked={checked} onChange={handleChange} />
-    </label>
-  );
-};
-
-const NewBookmarkCategoryItem: React.FC<{
-  onCreate: (bookmark_category: ApiBookmarkCategoryJSON) => void;
-}> = ({ onCreate }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const [title, setTitle] = useState('');
-
-  const handleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setTitle(value);
-    },
-    [setTitle],
-  );
-
-  const handleSubmit = useCallback(() => {
-    if (title.trim().length === 0) {
-      return;
-    }
-
-    void dispatch(createBookmarkCategory({ title })).then((result) => {
-      if (isFulfilled(result)) {
-        onCreate(result.payload);
-        setTitle('');
-      }
-
-      return '';
-    });
-  }, [setTitle, dispatch, onCreate, title]);
-
-  return (
-    <form className='lists__item' onSubmit={handleSubmit}>
-      <label className='lists__item__title'>
-        <Icon id='bookmark_category-ul' icon={BookmarkIcon} />
-
-        <input
-          type='text'
-          value={title}
-          onChange={handleChange}
-          maxLength={30}
-          required
-          placeholder={intl.formatMessage(messages.newBookmarkCategory)}
-        />
-      </label>
-
-      <Button
-        text={intl.formatMessage(messages.createBookmarkCategory)}
-        type='submit'
-      />
-    </form>
-  );
-};
-
-const BookmarkCategoryAdder: React.FC<{
-  statusId: string;
-  onClose: () => void;
-}> = ({ statusId, onClose }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const bookmark_categories = useAppSelector((state) =>
-    getOrderedBookmarkCategories(state),
-  );
-  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return
-  const status = useAppSelector((state) => state.statuses.get(statusId));
-  const [bookmark_categoryIds, setBookmarkCategoryIds] = useState<string[]>(
-    [] as string[],
-  );
-
-  useEffect(() => {
-    dispatch(fetchBookmarkCategories());
-
-    apiGetStatusBookmarkCategories(statusId)
-      .then((data) => {
-        setBookmarkCategoryIds(data.map((l) => l.id));
-        return '';
-      })
-      .catch(() => {
-        // Nothing
-      });
-  }, [dispatch, setBookmarkCategoryIds, statusId]);
-
-  const handleToggle = useCallback(
-    (bookmark_categoryId: string, checked: boolean) => {
-      if (checked) {
-        setBookmarkCategoryIds((currentBookmarkCategoryIds) => [
-          bookmark_categoryId,
-          ...currentBookmarkCategoryIds,
-        ]);
-
-        apiAddStatusToBookmarkCategory(bookmark_categoryId, statusId)
-          .then(() => {
-            dispatch(
-              bookmarkCategoryEditorAddSuccess(bookmark_categoryId, statusId),
-            );
-            return true;
-          })
-          .catch(() => {
-            setBookmarkCategoryIds((currentBookmarkCategoryIds) =>
-              currentBookmarkCategoryIds.filter(
-                (id) => id !== bookmark_categoryId,
-              ),
-            );
-          });
-      } else {
-        setBookmarkCategoryIds((currentBookmarkCategoryIds) =>
-          currentBookmarkCategoryIds.filter((id) => id !== bookmark_categoryId),
-        );
-
-        apiRemoveStatusFromBookmarkCategory(bookmark_categoryId, statusId)
-          .then(() => {
-            dispatch(
-              bookmarkCategoryEditorRemoveSuccess(
-                bookmark_categoryId,
-                statusId,
-              ),
-            );
-
-            if (
-              bookmarkCategoryNeeded &&
-              bookmark_categoryIds.filter((id) => id !== bookmark_categoryId)
-                .length === 0
-            ) {
-              dispatch(unbookmark(status));
-            }
-
-            return true;
-          })
-          .catch(() => {
-            setBookmarkCategoryIds((currentBookmarkCategoryIds) => [
-              bookmark_categoryId,
-              ...currentBookmarkCategoryIds,
-            ]);
-          });
-      }
-    },
-    [setBookmarkCategoryIds, statusId, dispatch, bookmark_categoryIds, status],
-  );
-
-  const handleCreate = useCallback(
-    (bookmark_category: ApiBookmarkCategoryJSON) => {
-      setBookmarkCategoryIds((currentBookmarkCategoryIds) => [
-        bookmark_category.id,
-        ...currentBookmarkCategoryIds,
-      ]);
-
-      apiAddStatusToBookmarkCategory(bookmark_category.id, statusId).catch(
-        () => {
-          setBookmarkCategoryIds((currentBookmarkCategoryIds) =>
-            currentBookmarkCategoryIds.filter(
-              (id) => id !== bookmark_category.id,
-            ),
-          );
-        },
-      );
-    },
-    [setBookmarkCategoryIds, statusId],
-  );
-
-  return (
-    <div className='modal-root__modal dialog-modal'>
-      <div className='dialog-modal__header'>
-        <IconButton
-          className='dialog-modal__header__close'
-          title={intl.formatMessage(messages.close)}
-          icon='times'
-          iconComponent={CloseIcon}
-          onClick={onClose}
-        />
-
-        <span className='dialog-modal__header__title'>
-          <FormattedMessage
-            id='bookmark_categories.add_to_bookmark_categories'
-            defaultMessage='Add {name} to bookmark_categories'
-            values={{ name: <strong>@</strong> }}
-          />
-        </span>
-      </div>
-
-      <div className='dialog-modal__content'>
-        <div className='lists-scrollable'>
-          <NewBookmarkCategoryItem onCreate={handleCreate} />
-
-          {bookmark_categories.map((bookmark_category) => (
-            <BookmarkCategoryItem
-              key={bookmark_category.id}
-              id={bookmark_category.id}
-              title={bookmark_category.title}
-              checked={bookmark_categoryIds.includes(bookmark_category.id)}
-              onChange={handleToggle}
-            />
-          ))}
-        </div>
-      </div>
-    </div>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default BookmarkCategoryAdder;
diff --git a/app/javascript/mastodon/features/bookmark_category_statuses/components/edit_bookmark_category_form.jsx b/app/javascript/mastodon/features/bookmark_category_statuses/components/edit_bookmark_category_form.jsx
new file mode 100644
index 0000000000..de3f3f7384
--- /dev/null
+++ b/app/javascript/mastodon/features/bookmark_category_statuses/components/edit_bookmark_category_form.jsx
@@ -0,0 +1,76 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import CheckIcon from '@/material-icons/400-24px/check.svg?react';
+
+import { changeBookmarkCategoryEditorTitle, submitBookmarkCategoryEditor } from '../../../actions/bookmark_categories';
+import { IconButton } from '../../../components/icon_button';
+
+const messages = defineMessages({
+  title: { id: 'bookmark_categories.edit.submit', defaultMessage: 'Change title' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['bookmarkCategoryEditor', 'title']),
+  disabled: !state.getIn(['bookmarkCategoryEditor', 'isChanged']) || !state.getIn(['bookmarkCategoryEditor', 'title']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onChange: value => dispatch(changeBookmarkCategoryEditorTitle(value)),
+  onSubmit: () => dispatch(submitBookmarkCategoryEditor(false)),
+});
+
+class EditBookmarkCategoryForm extends PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    disabled: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleSubmit = e => {
+    e.preventDefault();
+    this.props.onSubmit();
+  };
+
+  handleClick = () => {
+    this.props.onSubmit();
+  };
+
+  render () {
+    const { value, disabled, intl } = this.props;
+
+    const title = intl.formatMessage(messages.title);
+
+    return (
+      <form className='column-inline-form' onSubmit={this.handleSubmit}>
+        <input
+          className='setting-text'
+          value={value}
+          onChange={this.handleChange}
+        />
+
+        <IconButton
+          disabled={disabled}
+          icon='check'
+          iconComponent={CheckIcon}
+          title={title}
+          onClick={this.handleClick}
+        />
+      </form>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(EditBookmarkCategoryForm));
diff --git a/app/javascript/mastodon/features/bookmark_category_statuses/index.jsx b/app/javascript/mastodon/features/bookmark_category_statuses/index.jsx
new file mode 100644
index 0000000000..733a6646c0
--- /dev/null
+++ b/app/javascript/mastodon/features/bookmark_category_statuses/index.jsx
@@ -0,0 +1,195 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+
+import { Helmet } from 'react-helmet';
+import { withRouter } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+
+import { debounce } from 'lodash';
+
+import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg';
+import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
+import EditIcon from '@/material-icons/400-24px/edit.svg?react';
+import { deleteBookmarkCategory, expandBookmarkCategoryStatuses, fetchBookmarkCategory, fetchBookmarkCategoryStatuses , setupBookmarkCategoryEditor } from 'mastodon/actions/bookmark_categories';
+import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
+import { openModal } from 'mastodon/actions/modal';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import { Icon }  from 'mastodon/components/icon';
+import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import StatusList from 'mastodon/components/status_list';
+import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
+import { getBookmarkCategoryStatusList } from 'mastodon/selectors';
+import { WithRouterPropTypes } from 'mastodon/utils/react_router';
+
+import EditBookmarkCategoryForm from './components/edit_bookmark_category_form';
+
+
+const messages = defineMessages({
+  deleteMessage: { id: 'confirmations.delete_bookmark_category.message', defaultMessage: 'Are you sure you want to permanently delete this category?' },
+  deleteConfirm: { id: 'confirmations.delete_bookmark_category.confirm', defaultMessage: 'Delete' },
+  heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
+});
+
+const mapStateToProps = (state, { params }) => ({
+  bookmarkCategory: state.getIn(['bookmark_categories', params.id]),
+  statusIds: getBookmarkCategoryStatusList(state, params.id),
+  isLoading: state.getIn(['bookmark_categories', params.id, 'isLoading'], true),
+  isEditing: state.getIn(['bookmarkCategoryEditor', 'bookmarkCategoryId']) === params.id,
+  hasMore: !!state.getIn(['bookmark_categories', params.id, 'next']),
+});
+
+class BookmarkCategoryStatuses extends ImmutablePureComponent {
+
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    statusIds: ImmutablePropTypes.list.isRequired,
+    bookmarkCategory: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
+    intl: PropTypes.object.isRequired,
+    columnId: PropTypes.string,
+    multiColumn: PropTypes.bool,
+    hasMore: PropTypes.bool,
+    isLoading: PropTypes.bool,
+    isEditing: PropTypes.bool,
+    ...WithRouterPropTypes,
+  };
+
+  UNSAFE_componentWillMount () {
+    this.props.dispatch(fetchBookmarkCategory(this.props.params.id));
+    this.props.dispatch(fetchBookmarkCategoryStatuses(this.props.params.id));
+  }
+
+  handlePin = () => {
+    const { columnId, dispatch } = this.props;
+
+    if (columnId) {
+      dispatch(removeColumn(columnId));
+    } else {
+      dispatch(addColumn('BOOKMARKS_EX', { id: this.props.params.id }));
+      this.props.history.push('/');
+    }
+  };
+
+  handleMove = (dir) => {
+    const { columnId, dispatch } = this.props;
+    dispatch(moveColumn(columnId, dir));
+  };
+
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  };
+
+  handleEditClick = () => {
+    this.props.dispatch(setupBookmarkCategoryEditor(this.props.params.id));
+  };
+
+  handleDeleteClick = () => {
+    const { dispatch, columnId, intl } = this.props;
+    const { id } = this.props.params;
+
+    dispatch(openModal({
+      modalType: 'CONFIRM',
+      modalProps: {
+        message: intl.formatMessage(messages.deleteMessage),
+        confirm: intl.formatMessage(messages.deleteConfirm),
+        onConfirm: () => {
+          dispatch(deleteBookmarkCategory(id));
+
+          if (columnId) {
+            dispatch(removeColumn(columnId));
+          } else {
+            this.props.history.push('/bookmark_categories');
+          }
+        },
+      },
+    }));
+  };
+
+  setRef = c => {
+    this.column = c;
+  };
+
+  handleLoadMore = debounce(() => {
+    this.props.dispatch(expandBookmarkCategoryStatuses(this.props.params.id));
+  }, 300, { leading: true });
+
+  render () {
+    const { intl, bookmarkCategory, statusIds, columnId, multiColumn, hasMore, isLoading, isEditing } = this.props;
+    const pinned = !!columnId;
+
+    if (typeof bookmarkCategory === 'undefined') {
+      return (
+        <Column>
+          <div className='scrollable'>
+            <LoadingIndicator />
+          </div>
+        </Column>
+      );
+    } else if (bookmarkCategory === false) {
+      return (
+        <BundleColumnError multiColumn={multiColumn} errorType='routing' />
+      );
+    }
+
+    const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses' defaultMessage="You don't have any bookmarked posts yet. When you bookmark one, it will show up here." />;
+
+    const editor = isEditing && (
+      <EditBookmarkCategoryForm />
+    );
+
+    return (
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
+        <ColumnHeader
+          icon='bookmark'
+          iconComponent={BookmarkIcon}
+          title={bookmarkCategory.get('title')}
+          onPin={this.handlePin}
+          onMove={this.handleMove}
+          onClick={this.handleHeaderClick}
+          pinned={pinned}
+          multiColumn={multiColumn}
+        >
+          <div className='column-settings'>
+            <section className='column-header__links'>
+              <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}>
+                <Icon id='pencil' icon={EditIcon} /> <FormattedMessage id='bookmark_categories.edit' defaultMessage='Edit category' />
+              </button>
+
+              <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
+                <Icon id='trash' icon={DeleteIcon} /> <FormattedMessage id='bookmark_categories.delete' defaultMessage='Delete category' />
+              </button>
+
+              {editor}
+            </section>
+          </div>
+        </ColumnHeader>
+
+        <StatusList
+          trackScroll={!pinned}
+          statusIds={statusIds}
+          scrollKey={`bookmark_ex_statuses-${columnId}`}
+          hasMore={hasMore}
+          isLoading={isLoading}
+          onLoadMore={this.handleLoadMore}
+          emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
+        />
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.heading)}</title>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default withRouter(connect(mapStateToProps)(injectIntl(BookmarkCategoryStatuses)));
diff --git a/app/javascript/mastodon/features/bookmark_category_statuses/index.tsx b/app/javascript/mastodon/features/bookmark_category_statuses/index.tsx
deleted file mode 100644
index 7facdcc449..0000000000
--- a/app/javascript/mastodon/features/bookmark_category_statuses/index.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import { useEffect, useRef, useCallback } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { useParams } from 'react-router';
-
-import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react';
-import {
-  expandBookmarkCategoryStatuses,
-  fetchBookmarkCategory,
-  fetchBookmarkCategoryStatuses,
-} from 'mastodon/actions/bookmark_categories';
-import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
-import { Column } from 'mastodon/components/column';
-import type { ColumnRef } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import StatusList from 'mastodon/components/status_list';
-import { getSubStatusList } from 'mastodon/selectors';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const BookmarkCategoryStatuses: React.FC<{
-  columnId: string;
-  multiColumn: boolean;
-}> = ({ columnId, multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const { id } = useParams<{ id: string }>();
-  const columnRef = useRef<ColumnRef>(null);
-  const statusIds = useAppSelector((state) =>
-    getSubStatusList(state, 'bookmark_category', id),
-  );
-  const isLoading = useAppSelector(
-    (state) =>
-      state.status_lists.getIn(
-        ['bookmark_category_statuses', id, 'isLoading'],
-        true,
-      ) as boolean,
-  );
-  const hasMore = useAppSelector(
-    (state) =>
-      !!state.status_lists.getIn(['bookmark_category_statuses', id, 'next']),
-  );
-  const bookmarkCategory = useAppSelector((state) =>
-    state.bookmark_categories.get(id),
-  );
-
-  useEffect(() => {
-    dispatch(fetchBookmarkCategory(id));
-    dispatch(fetchBookmarkCategoryStatuses(id));
-  }, [dispatch, id]);
-
-  const handlePin = useCallback(() => {
-    if (columnId) {
-      dispatch(removeColumn(columnId));
-    } else {
-      dispatch(addColumn('BOOKMARKS_EX', { id }));
-    }
-  }, [dispatch, columnId, id]);
-
-  const handleMove = useCallback(
-    (dir: number) => {
-      dispatch(moveColumn(columnId, dir));
-    },
-    [dispatch, columnId],
-  );
-
-  const handleHeaderClick = useCallback(() => {
-    columnRef.current?.scrollTop();
-  }, []);
-
-  const handleLoadMore = useCallback(() => {
-    dispatch(expandBookmarkCategoryStatuses(id));
-  }, [dispatch, id]);
-
-  const pinned = !!columnId;
-
-  const emptyMessage = (
-    <FormattedMessage
-      id='empty_column.bookmarked_statuses'
-      defaultMessage="You don't have any bookmarked posts yet. When you bookmark one, it will show up here."
-    />
-  );
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      ref={columnRef}
-      label={bookmarkCategory?.get('title')}
-    >
-      <ColumnHeader
-        icon='bookmark'
-        iconComponent={BookmarkIcon}
-        title={bookmarkCategory?.get('title')}
-        onPin={handlePin}
-        onMove={handleMove}
-        onClick={handleHeaderClick}
-        pinned={pinned}
-        multiColumn={multiColumn}
-      />
-
-      <StatusList
-        trackScroll={!pinned}
-        statusIds={statusIds}
-        scrollKey={`bookmark_ex_statuses-${columnId}`}
-        hasMore={hasMore}
-        isLoading={isLoading}
-        onLoadMore={handleLoadMore}
-        emptyMessage={emptyMessage}
-        bindToDocument={!multiColumn}
-      />
-
-      <Helmet>
-        <title>{bookmarkCategory?.get('title')}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default BookmarkCategoryStatuses;
diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.jsx b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx
new file mode 100644
index 0000000000..93be1c6b2e
--- /dev/null
+++ b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx
@@ -0,0 +1,116 @@
+// Kmyblue tracking marker: copied bookmark_category_statuses, circle_statuses
+
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { debounce } from 'lodash';
+
+import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
+import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'mastodon/actions/bookmarks';
+import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import StatusList from 'mastodon/components/status_list';
+import { getStatusList } from 'mastodon/selectors';
+
+const messages = defineMessages({
+  heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
+});
+
+const mapStateToProps = state => ({
+  statusIds: getStatusList(state, 'bookmarks'),
+  isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true),
+  hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
+});
+
+class Bookmarks extends ImmutablePureComponent {
+
+  static propTypes = {
+    dispatch: PropTypes.func.isRequired,
+    statusIds: ImmutablePropTypes.list.isRequired,
+    intl: PropTypes.object.isRequired,
+    columnId: PropTypes.string,
+    multiColumn: PropTypes.bool,
+    hasMore: PropTypes.bool,
+    isLoading: PropTypes.bool,
+  };
+
+  UNSAFE_componentWillMount () {
+    this.props.dispatch(fetchBookmarkedStatuses());
+  }
+
+  handlePin = () => {
+    const { columnId, dispatch } = this.props;
+
+    if (columnId) {
+      dispatch(removeColumn(columnId));
+    } else {
+      dispatch(addColumn('BOOKMARKS', {}));
+    }
+  };
+
+  handleMove = (dir) => {
+    const { columnId, dispatch } = this.props;
+    dispatch(moveColumn(columnId, dir));
+  };
+
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  };
+
+  setRef = c => {
+    this.column = c;
+  };
+
+  handleLoadMore = debounce(() => {
+    this.props.dispatch(expandBookmarkedStatuses());
+  }, 300, { leading: true });
+
+  render () {
+    const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
+    const pinned = !!columnId;
+
+    const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses' defaultMessage="You don't have any bookmarked posts yet. When you bookmark one, it will show up here." />;
+
+    return (
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
+        <ColumnHeader
+          icon='bookmarks'
+          iconComponent={BookmarksIcon}
+          title={intl.formatMessage(messages.heading)}
+          onPin={this.handlePin}
+          onMove={this.handleMove}
+          onClick={this.handleHeaderClick}
+          pinned={pinned}
+          multiColumn={multiColumn}
+        />
+
+        <StatusList
+          trackScroll={!pinned}
+          statusIds={statusIds}
+          scrollKey={`bookmarked_statuses-${columnId}`}
+          hasMore={hasMore}
+          isLoading={isLoading}
+          onLoadMore={this.handleLoadMore}
+          emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
+        />
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.heading)}</title>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(Bookmarks));
diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.tsx b/app/javascript/mastodon/features/bookmarked_statuses/index.tsx
deleted file mode 100644
index 5d4574b05b..0000000000
--- a/app/javascript/mastodon/features/bookmarked_statuses/index.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import { useEffect, useRef, useCallback } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-
-import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
-import {
-  fetchBookmarkedStatuses,
-  expandBookmarkedStatuses,
-} from 'mastodon/actions/bookmarks';
-import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
-import { Column } from 'mastodon/components/column';
-import type { ColumnRef } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import StatusList from 'mastodon/components/status_list';
-import { getStatusList } from 'mastodon/selectors';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
-});
-
-const Bookmarks: React.FC<{
-  columnId: string;
-  multiColumn: boolean;
-}> = ({ columnId, multiColumn }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const columnRef = useRef<ColumnRef>(null);
-  const statusIds = useAppSelector((state) =>
-    getStatusList(state, 'bookmarks'),
-  );
-  const isLoading = useAppSelector(
-    (state) =>
-      state.status_lists.getIn(['bookmarks', 'isLoading'], true) as boolean,
-  );
-  const hasMore = useAppSelector(
-    (state) => !!state.status_lists.getIn(['bookmarks', 'next']),
-  );
-
-  useEffect(() => {
-    dispatch(fetchBookmarkedStatuses());
-  }, [dispatch]);
-
-  const handlePin = useCallback(() => {
-    if (columnId) {
-      dispatch(removeColumn(columnId));
-    } else {
-      dispatch(addColumn('BOOKMARKS', {}));
-    }
-  }, [dispatch, columnId]);
-
-  const handleMove = useCallback(
-    (dir: number) => {
-      dispatch(moveColumn(columnId, dir));
-    },
-    [dispatch, columnId],
-  );
-
-  const handleHeaderClick = useCallback(() => {
-    columnRef.current?.scrollTop();
-  }, []);
-
-  const handleLoadMore = useCallback(() => {
-    dispatch(expandBookmarkedStatuses());
-  }, [dispatch]);
-
-  const pinned = !!columnId;
-
-  const emptyMessage = (
-    <FormattedMessage
-      id='empty_column.bookmarked_statuses'
-      defaultMessage="You don't have any bookmarked posts yet. When you bookmark one, it will show up here."
-    />
-  );
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      ref={columnRef}
-      label={intl.formatMessage(messages.heading)}
-    >
-      <ColumnHeader
-        icon='bookmarks'
-        iconComponent={BookmarksIcon}
-        title={intl.formatMessage(messages.heading)}
-        onPin={handlePin}
-        onMove={handleMove}
-        onClick={handleHeaderClick}
-        pinned={pinned}
-        multiColumn={multiColumn}
-      />
-
-      <StatusList
-        trackScroll={!pinned}
-        statusIds={statusIds}
-        scrollKey={`bookmarked_statuses-${columnId}`}
-        hasMore={hasMore}
-        isLoading={isLoading}
-        onLoadMore={handleLoadMore}
-        emptyMessage={emptyMessage}
-        bindToDocument={!multiColumn}
-        timelineId='bookmarks'
-      />
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default Bookmarks;
diff --git a/app/javascript/mastodon/features/circle_adder/components/account.jsx b/app/javascript/mastodon/features/circle_adder/components/account.jsx
new file mode 100644
index 0000000000..31a2e96379
--- /dev/null
+++ b/app/javascript/mastodon/features/circle_adder/components/account.jsx
@@ -0,0 +1,43 @@
+import { injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { Avatar } from '../../../components/avatar';
+import { DisplayName } from '../../../components/display_name';
+import { makeGetAccount } from '../../../selectors';
+
+const makeMapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  const mapStateToProps = (state, { accountId }) => ({
+    account: getAccount(state, accountId),
+  });
+
+  return mapStateToProps;
+};
+
+class Account extends ImmutablePureComponent {
+
+  static propTypes = {
+    account: ImmutablePropTypes.map.isRequired,
+  };
+
+  render () {
+    const { account } = this.props;
+    return (
+      <div className='account'>
+        <div className='account__wrapper'>
+          <div className='account__display-name'>
+            <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
+            <DisplayName account={account} />
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(makeMapStateToProps)(injectIntl(Account));
diff --git a/app/javascript/mastodon/features/circle_adder/components/circle.jsx b/app/javascript/mastodon/features/circle_adder/components/circle.jsx
new file mode 100644
index 0000000000..0534f1896b
--- /dev/null
+++ b/app/javascript/mastodon/features/circle_adder/components/circle.jsx
@@ -0,0 +1,75 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
+import AddIcon from '@/material-icons/400-24px/add.svg?react';
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+import { Icon }  from 'mastodon/components/icon';
+
+import { removeFromCircleAdder, addToCircleAdder } from '../../../actions/circles';
+import { IconButton }  from '../../../components/icon_button';
+
+const messages = defineMessages({
+  remove: { id: 'circles.account.remove', defaultMessage: 'Remove from circle' },
+  add: { id: 'circles.account.add', defaultMessage: 'Add to circle' },
+});
+
+const MapStateToProps = (state, { circleId, added }) => ({
+  circle: state.get('circles').get(circleId),
+  added: typeof added === 'undefined' ? state.getIn(['circleAdder', 'circles', 'items']).includes(circleId) : added,
+});
+
+const mapDispatchToProps = (dispatch, { circleId }) => ({
+  onRemove: () => dispatch(removeFromCircleAdder(circleId)),
+  onAdd: () => dispatch(addToCircleAdder(circleId)),
+});
+
+class Circle extends ImmutablePureComponent {
+
+  static propTypes = {
+    circle: ImmutablePropTypes.map.isRequired,
+    intl: PropTypes.object.isRequired,
+    onRemove: PropTypes.func.isRequired,
+    onAdd: PropTypes.func.isRequired,
+    added: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    added: false,
+  };
+
+  render () {
+    const { circle, intl, onRemove, onAdd, added } = this.props;
+
+    let button;
+
+    if (added) {
+      button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
+    } else {
+      button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
+    }
+
+    return (
+      <div className='list'>
+        <div className='list__wrapper'>
+          <div className='list__display-name'>
+            <Icon id='user-circle' icon={CircleIcon} className='column-link__icon' fixedWidth />
+            {circle.get('title')}
+          </div>
+
+          <div className='account__relationship'>
+            {button}
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(Circle));
diff --git a/app/javascript/mastodon/features/circle_adder/index.jsx b/app/javascript/mastodon/features/circle_adder/index.jsx
new file mode 100644
index 0000000000..ae9c647bdd
--- /dev/null
+++ b/app/javascript/mastodon/features/circle_adder/index.jsx
@@ -0,0 +1,77 @@
+import PropTypes from 'prop-types';
+
+import { injectIntl } from 'react-intl';
+
+import { createSelector } from '@reduxjs/toolkit';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+
+import { setupCircleAdder, resetCircleAdder } from '../../actions/circles';
+import NewCircleForm from '../circles/components/new_circle_form';
+
+import Account from './components/account';
+import Circle from './components/circle';
+// hack
+
+const getOrderedCircles = createSelector([state => state.get('circles')], circles => {
+  if (!circles) {
+    return circles;
+  }
+
+  return circles.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
+});
+
+const mapStateToProps = state => ({
+  circleIds: getOrderedCircles(state).map(circle=>circle.get('id')),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onInitialize: accountId => dispatch(setupCircleAdder(accountId)),
+  onReset: () => dispatch(resetCircleAdder()),
+});
+
+class CircleAdder extends ImmutablePureComponent {
+
+  static propTypes = {
+    accountId: PropTypes.string.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    onInitialize: PropTypes.func.isRequired,
+    onReset: PropTypes.func.isRequired,
+    circleIds: ImmutablePropTypes.list.isRequired,
+  };
+
+  componentDidMount () {
+    const { onInitialize, accountId } = this.props;
+    onInitialize(accountId);
+  }
+
+  componentWillUnmount () {
+    const { onReset } = this.props;
+    onReset();
+  }
+
+  render () {
+    const { accountId, circleIds } = this.props;
+
+    return (
+      <div className='modal-root__modal list-adder'>
+        <div className='list-adder__account'>
+          <Account accountId={accountId} />
+        </div>
+
+        <NewCircleForm />
+
+
+        <div className='list-adder__lists'>
+          {circleIds.map(CircleId => <Circle key={CircleId} circleId={CircleId} />)}
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(CircleAdder));
diff --git a/app/javascript/mastodon/features/circle_adder/index.tsx b/app/javascript/mastodon/features/circle_adder/index.tsx
deleted file mode 100644
index 7939779a3b..0000000000
--- a/app/javascript/mastodon/features/circle_adder/index.tsx
+++ /dev/null
@@ -1,213 +0,0 @@
-import { useEffect, useState, useCallback } from 'react';
-
-import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
-
-import { isFulfilled } from '@reduxjs/toolkit';
-
-import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
-import CloseIcon from '@/material-icons/400-24px/close.svg?react';
-import { fetchCircles } from 'mastodon/actions/circles';
-import { createCircle } from 'mastodon/actions/circles_typed';
-import {
-  apiGetAccountCircles,
-  apiAddAccountToCircle,
-  apiRemoveAccountFromCircle,
-} from 'mastodon/api/circles';
-import type { ApiCircleJSON } from 'mastodon/api_types/circles';
-import { Button } from 'mastodon/components/button';
-import { CheckBox } from 'mastodon/components/check_box';
-import { Icon } from 'mastodon/components/icon';
-import { IconButton } from 'mastodon/components/icon_button';
-import { getOrderedCircles } from 'mastodon/selectors/circles';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  newCircle: {
-    id: 'circles.new_circle_name',
-    defaultMessage: 'New circle name',
-  },
-  createCircle: {
-    id: 'circles.create',
-    defaultMessage: 'Create',
-  },
-  close: {
-    id: 'lightbox.close',
-    defaultMessage: 'Close',
-  },
-});
-
-const CircleItem: React.FC<{
-  id: string;
-  title: string;
-  checked: boolean;
-  onChange: (id: string, checked: boolean) => void;
-}> = ({ id, title, checked, onChange }) => {
-  const handleChange = useCallback(
-    (e: React.ChangeEvent<HTMLInputElement>) => {
-      onChange(id, e.target.checked);
-    },
-    [id, onChange],
-  );
-
-  return (
-    // eslint-disable-next-line jsx-a11y/label-has-associated-control
-    <label className='lists__item'>
-      <div className='lists__item__title'>
-        <Icon id='circle-ul' icon={CircleIcon} />
-        <span>{title}</span>
-      </div>
-
-      <CheckBox value={id} checked={checked} onChange={handleChange} />
-    </label>
-  );
-};
-
-const NewCircleItem: React.FC<{
-  onCreate: (circle: ApiCircleJSON) => void;
-}> = ({ onCreate }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const [title, setTitle] = useState('');
-
-  const handleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setTitle(value);
-    },
-    [setTitle],
-  );
-
-  const handleSubmit = useCallback(() => {
-    if (title.trim().length === 0) {
-      return;
-    }
-
-    void dispatch(createCircle({ title })).then((result) => {
-      if (isFulfilled(result)) {
-        onCreate(result.payload);
-        setTitle('');
-      }
-
-      return '';
-    });
-  }, [setTitle, dispatch, onCreate, title]);
-
-  return (
-    <form className='lists__item' onSubmit={handleSubmit}>
-      <label className='lists__item__title'>
-        <Icon id='circle-ul' icon={CircleIcon} />
-
-        <input
-          type='text'
-          value={title}
-          onChange={handleChange}
-          maxLength={30}
-          required
-          placeholder={intl.formatMessage(messages.newCircle)}
-        />
-      </label>
-
-      <Button text={intl.formatMessage(messages.createCircle)} type='submit' />
-    </form>
-  );
-};
-
-const CircleAdder: React.FC<{
-  accountId: string;
-  onClose: () => void;
-}> = ({ accountId, onClose }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const account = useAppSelector((state) => state.accounts.get(accountId));
-  const circles = useAppSelector((state) => getOrderedCircles(state));
-  const [circleIds, setCircleIds] = useState<string[]>([]);
-
-  useEffect(() => {
-    dispatch(fetchCircles());
-
-    apiGetAccountCircles(accountId)
-      .then((data) => {
-        setCircleIds(data.map((l) => l.id));
-        return '';
-      })
-      .catch(() => {
-        // Nothing
-      });
-  }, [dispatch, setCircleIds, accountId]);
-
-  const handleToggle = useCallback(
-    (circleId: string, checked: boolean) => {
-      if (checked) {
-        setCircleIds((currentCircleIds) => [circleId, ...currentCircleIds]);
-
-        apiAddAccountToCircle(circleId, accountId).catch(() => {
-          setCircleIds((currentCircleIds) =>
-            currentCircleIds.filter((id) => id !== circleId),
-          );
-        });
-      } else {
-        setCircleIds((currentCircleIds) =>
-          currentCircleIds.filter((id) => id !== circleId),
-        );
-
-        apiRemoveAccountFromCircle(circleId, accountId).catch(() => {
-          setCircleIds((currentCircleIds) => [circleId, ...currentCircleIds]);
-        });
-      }
-    },
-    [setCircleIds, accountId],
-  );
-
-  const handleCreate = useCallback(
-    (circle: ApiCircleJSON) => {
-      setCircleIds((currentCircleIds) => [circle.id, ...currentCircleIds]);
-
-      apiAddAccountToCircle(circle.id, accountId).catch(() => {
-        setCircleIds((currentCircleIds) =>
-          currentCircleIds.filter((id) => id !== circle.id),
-        );
-      });
-    },
-    [setCircleIds, accountId],
-  );
-
-  return (
-    <div className='modal-root__modal dialog-modal'>
-      <div className='dialog-modal__header'>
-        <IconButton
-          className='dialog-modal__header__close'
-          title={intl.formatMessage(messages.close)}
-          icon='times'
-          iconComponent={CloseIcon}
-          onClick={onClose}
-        />
-
-        <span className='dialog-modal__header__title'>
-          <FormattedMessage
-            id='circles.add_to_circles'
-            defaultMessage='Add {name} to circles'
-            values={{ name: <strong>@{account?.acct}</strong> }}
-          />
-        </span>
-      </div>
-
-      <div className='dialog-modal__content'>
-        <div className='lists-scrollable'>
-          <NewCircleItem onCreate={handleCreate} />
-
-          {circles.map((circle) => (
-            <CircleItem
-              key={circle.id}
-              id={circle.id}
-              title={circle.title}
-              checked={circleIds.includes(circle.id)}
-              onChange={handleToggle}
-            />
-          ))}
-        </div>
-      </div>
-    </div>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default CircleAdder;
diff --git a/app/javascript/mastodon/features/circle_editor/components/account.jsx b/app/javascript/mastodon/features/circle_editor/components/account.jsx
new file mode 100644
index 0000000000..1b8a462d1b
--- /dev/null
+++ b/app/javascript/mastodon/features/circle_editor/components/account.jsx
@@ -0,0 +1,83 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import AddIcon from '@/material-icons/400-24px/add.svg?react';
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+
+import { removeFromCircleEditor, addToCircleEditor } from '../../../actions/circles';
+import { Avatar } from '../../../components/avatar';
+import { DisplayName } from '../../../components/display_name';
+import { IconButton } from '../../../components/icon_button';
+import { makeGetAccount } from '../../../selectors';
+
+const messages = defineMessages({
+  remove: { id: 'circles.account.remove', defaultMessage: 'Remove from circle' },
+  add: { id: 'circles.account.add', defaultMessage: 'Add to circle' },
+});
+
+const makeMapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  const mapStateToProps = (state, { accountId, added }) => ({
+    account: getAccount(state, accountId),
+    added: typeof added === 'undefined' ? state.getIn(['circleEditor', 'accounts', 'items']).includes(accountId) : added,
+  });
+
+  return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch, { accountId }) => ({
+  onRemove: () => dispatch(removeFromCircleEditor(accountId)),
+  onAdd: () => dispatch(addToCircleEditor(accountId)),
+});
+
+class Account extends ImmutablePureComponent {
+
+  static propTypes = {
+    account: ImmutablePropTypes.map.isRequired,
+    intl: PropTypes.object.isRequired,
+    onRemove: PropTypes.func.isRequired,
+    onAdd: PropTypes.func.isRequired,
+    added: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    added: false,
+  };
+
+  render () {
+    const { account, intl, onRemove, onAdd, added } = this.props;
+
+    let button;
+
+    if (added) {
+      button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
+    } else {
+      button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
+    }
+
+    return (
+      <div className='account'>
+        <div className='account__wrapper'>
+          <div className='account__display-name'>
+            <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
+            <DisplayName account={account} />
+          </div>
+
+          <div className='account__relationship'>
+            {button}
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(Account));
diff --git a/app/javascript/mastodon/features/circle_editor/components/edit_circle_form.jsx b/app/javascript/mastodon/features/circle_editor/components/edit_circle_form.jsx
new file mode 100644
index 0000000000..54d71891b8
--- /dev/null
+++ b/app/javascript/mastodon/features/circle_editor/components/edit_circle_form.jsx
@@ -0,0 +1,76 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import CheckIcon from '@/material-icons/400-24px/check.svg?react';
+
+import { changeCircleEditorTitle, submitCircleEditor } from '../../../actions/circles';
+import { IconButton } from '../../../components/icon_button';
+
+const messages = defineMessages({
+  title: { id: 'circles.edit.submit', defaultMessage: 'Change title' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['circleEditor', 'title']),
+  disabled: !state.getIn(['circleEditor', 'isChanged']) || !state.getIn(['circleEditor', 'title']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onChange: value => dispatch(changeCircleEditorTitle(value)),
+  onSubmit: () => dispatch(submitCircleEditor(false)),
+});
+
+class CircleForm extends PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    disabled: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleSubmit = e => {
+    e.preventDefault();
+    this.props.onSubmit();
+  };
+
+  handleClick = () => {
+    this.props.onSubmit();
+  };
+
+  render () {
+    const { value, disabled, intl } = this.props;
+
+    const title = intl.formatMessage(messages.title);
+
+    return (
+      <form className='column-inline-form' onSubmit={this.handleSubmit}>
+        <input
+          className='setting-text'
+          value={value}
+          onChange={this.handleChange}
+        />
+
+        <IconButton
+          disabled={disabled}
+          icon='check'
+          iconComponent={CheckIcon}
+          title={title}
+          onClick={this.handleClick}
+        />
+      </form>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(CircleForm));
diff --git a/app/javascript/mastodon/features/circle_editor/components/search.jsx b/app/javascript/mastodon/features/circle_editor/components/search.jsx
new file mode 100644
index 0000000000..e84ac2cc20
--- /dev/null
+++ b/app/javascript/mastodon/features/circle_editor/components/search.jsx
@@ -0,0 +1,83 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import classNames from 'classnames';
+
+import { connect } from 'react-redux';
+
+import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
+import SearchIcon from '@/material-icons/400-24px/search.svg?react';
+import { Icon }  from 'mastodon/components/icon';
+
+import { fetchCircleSuggestions, clearCircleSuggestions, changeCircleSuggestions } from '../../../actions/circles';
+
+const messages = defineMessages({
+  search: { id: 'circles.search', defaultMessage: 'Search among people follow you' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['circleEditor', 'suggestions', 'value']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onSubmit: value => dispatch(fetchCircleSuggestions(value)),
+  onClear: () => dispatch(clearCircleSuggestions()),
+  onChange: value => dispatch(changeCircleSuggestions(value)),
+});
+
+class Search extends PureComponent {
+
+  static propTypes = {
+    intl: PropTypes.object.isRequired,
+    value: PropTypes.string.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+    onClear: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleKeyUp = e => {
+    if (e.keyCode === 13) {
+      this.props.onSubmit(this.props.value);
+    }
+  };
+
+  handleClear = () => {
+    this.props.onClear();
+  };
+
+  render () {
+    const { value, intl } = this.props;
+    const hasValue = value.length > 0;
+
+    return (
+      <div className='list-editor__search search'>
+        <label>
+          <span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
+
+          <input
+            className='search__input'
+            type='text'
+            value={value}
+            onChange={this.handleChange}
+            onKeyUp={this.handleKeyUp}
+            placeholder={intl.formatMessage(messages.search)}
+          />
+        </label>
+
+        <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
+          <Icon id='search' icon={SearchIcon} className={classNames({ active: !hasValue })} />
+          <Icon id='times-circle' icon={CancelIcon} aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Search));
diff --git a/app/javascript/mastodon/features/circle_editor/index.jsx b/app/javascript/mastodon/features/circle_editor/index.jsx
new file mode 100644
index 0000000000..6d47b86c3d
--- /dev/null
+++ b/app/javascript/mastodon/features/circle_editor/index.jsx
@@ -0,0 +1,83 @@
+import PropTypes from 'prop-types';
+
+import { injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import spring from 'react-motion/lib/spring';
+
+import { setupCircleEditor, clearCircleSuggestions, resetCircleEditor } from '../../actions/circles';
+import Motion from '../ui/util/optional_motion';
+
+import Account from './components/account';
+import EditCircleForm from './components/edit_circle_form';
+import Search from './components/search';
+
+const mapStateToProps = state => ({
+  accountIds: state.getIn(['circleEditor', 'accounts', 'items']),
+  searchAccountIds: state.getIn(['circleEditor', 'suggestions', 'items']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onInitialize: circleId => dispatch(setupCircleEditor(circleId)),
+  onClear: () => dispatch(clearCircleSuggestions()),
+  onReset: () => dispatch(resetCircleEditor()),
+});
+
+class CircleEditor extends ImmutablePureComponent {
+
+  static propTypes = {
+    circleId: PropTypes.string.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    onInitialize: PropTypes.func.isRequired,
+    onClear: PropTypes.func.isRequired,
+    onReset: PropTypes.func.isRequired,
+    accountIds: ImmutablePropTypes.list.isRequired,
+    searchAccountIds: ImmutablePropTypes.list.isRequired,
+  };
+
+  componentDidMount () {
+    const { onInitialize, circleId } = this.props;
+    onInitialize(circleId);
+  }
+
+  componentWillUnmount () {
+    const { onReset } = this.props;
+    onReset();
+  }
+
+  render () {
+    const { accountIds, searchAccountIds, onClear } = this.props;
+    const showSearch = searchAccountIds.size > 0;
+
+    return (
+      <div className='modal-root__modal list-editor'>
+        <EditCircleForm />
+
+        <Search />
+
+        <div className='drawer__pager'>
+          <div className='drawer__inner list-editor__accounts'>
+            {accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
+          </div>
+
+          {showSearch && <div role='button' tabIndex={-1} className='drawer__backdrop' onClick={onClear} />}
+
+          <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
+            {({ x }) => (
+              <div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
+                {searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
+              </div>
+            )}
+          </Motion>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(CircleEditor));
diff --git a/app/javascript/mastodon/features/circle_statuses/index.jsx b/app/javascript/mastodon/features/circle_statuses/index.jsx
new file mode 100644
index 0000000000..51cb964343
--- /dev/null
+++ b/app/javascript/mastodon/features/circle_statuses/index.jsx
@@ -0,0 +1,189 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+
+import { Helmet } from 'react-helmet';
+import { withRouter } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+
+import { debounce } from 'lodash';
+
+import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
+import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
+import EditIcon from '@/material-icons/400-24px/edit.svg?react';
+import { deleteCircle, expandCircleStatuses, fetchCircle, fetchCircleStatuses } from 'mastodon/actions/circles';
+import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
+import { openModal } from 'mastodon/actions/modal';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import { Icon }  from 'mastodon/components/icon';
+import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import StatusList from 'mastodon/components/status_list';
+import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
+import { getCircleStatusList } from 'mastodon/selectors';
+import { WithRouterPropTypes } from 'mastodon/utils/react_router';
+
+
+const messages = defineMessages({
+  deleteMessage: { id: 'confirmations.delete_circle.message', defaultMessage: 'Are you sure you want to permanently delete this circle?' },
+  deleteConfirm: { id: 'confirmations.delete_circle.confirm', defaultMessage: 'Delete' },
+  heading: { id: 'column.circles', defaultMessage: 'Circles' },
+});
+
+const mapStateToProps = (state, { params }) => ({
+  circle: state.getIn(['circles', params.id]),
+  statusIds: getCircleStatusList(state, params.id),
+  isLoading: state.getIn(['circles', params.id, 'isLoading'], true),
+  isEditing: state.getIn(['circleEditor', 'circleId']) === params.id,
+  hasMore: !!state.getIn(['circles', params.id, 'next']),
+});
+
+class CircleStatuses extends ImmutablePureComponent {
+
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    statusIds: ImmutablePropTypes.list.isRequired,
+    circle: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
+    intl: PropTypes.object.isRequired,
+    columnId: PropTypes.string,
+    multiColumn: PropTypes.bool,
+    hasMore: PropTypes.bool,
+    isLoading: PropTypes.bool,
+    ...WithRouterPropTypes,
+  };
+
+  UNSAFE_componentWillMount () {
+    this.props.dispatch(fetchCircle(this.props.params.id));
+    this.props.dispatch(fetchCircleStatuses(this.props.params.id));
+  }
+
+  handlePin = () => {
+    const { columnId, dispatch } = this.props;
+
+    if (columnId) {
+      dispatch(removeColumn(columnId));
+    } else {
+      dispatch(addColumn('CIRCLE_STATUSES', { id: this.props.params.id }));
+      this.props.history.push('/');
+    }
+  };
+
+  handleMove = (dir) => {
+    const { columnId, dispatch } = this.props;
+    dispatch(moveColumn(columnId, dir));
+  };
+
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  };
+
+  handleEditClick = () => {
+    this.props.dispatch(openModal({
+      modalType: 'CIRCLE_EDITOR',
+      modalProps: { circleId: this.props.params.id },
+    }));
+  };
+
+  handleDeleteClick = () => {
+    const { dispatch, columnId, intl } = this.props;
+    const { id } = this.props.params;
+
+    dispatch(openModal({
+      modalType: 'CONFIRM',
+      modalProps: {
+        message: intl.formatMessage(messages.deleteMessage),
+        confirm: intl.formatMessage(messages.deleteConfirm),
+        onConfirm: () => {
+          dispatch(deleteCircle(id));
+
+          if (columnId) {
+            dispatch(removeColumn(columnId));
+          } else {
+            this.props.history.push('/circles');
+          }
+        },
+      },
+    }));
+  };
+
+  setRef = c => {
+    this.column = c;
+  };
+
+  handleLoadMore = debounce(() => {
+    this.props.dispatch(expandCircleStatuses(this.props.params.id));
+  }, 300, { leading: true });
+
+  render () {
+    const { intl, circle, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
+    const pinned = !!columnId;
+
+    if (typeof circle === 'undefined') {
+      return (
+        <Column>
+          <div className='scrollable'>
+            <LoadingIndicator />
+          </div>
+        </Column>
+      );
+    } else if (circle === false) {
+      return (
+        <BundleColumnError multiColumn={multiColumn} errorType='routing' />
+      );
+    }
+
+    const emptyMessage = <FormattedMessage id='empty_column.circle_statuses' defaultMessage="You don't have any circle posts yet. When you post one as circle, it will show up here." />;
+
+    return (
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
+        <ColumnHeader
+          icon='user-circle'
+          iconComponent={CircleIcon}
+          title={circle.get('title')}
+          onPin={this.handlePin}
+          onMove={this.handleMove}
+          onClick={this.handleHeaderClick}
+          pinned={pinned}
+          multiColumn={multiColumn}
+        >
+          <div className='column-settings'>
+            <section className='column-header__links'>
+              <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}>
+                <Icon id='pencil' icon={EditIcon} /> <FormattedMessage id='circles.edit' defaultMessage='Edit circle' />
+              </button>
+
+              <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
+                <Icon id='trash' icon={DeleteIcon} /> <FormattedMessage id='circles.delete' defaultMessage='Delete circle' />
+              </button>
+            </section>
+          </div>
+        </ColumnHeader>
+
+        <StatusList
+          trackScroll={!pinned}
+          statusIds={statusIds}
+          scrollKey={`circle_statuses-${columnId}`}
+          hasMore={hasMore}
+          isLoading={isLoading}
+          onLoadMore={this.handleLoadMore}
+          emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
+        />
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.heading)}</title>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default withRouter(connect(mapStateToProps)(injectIntl(CircleStatuses)));
diff --git a/app/javascript/mastodon/features/circle_statuses/index.tsx b/app/javascript/mastodon/features/circle_statuses/index.tsx
deleted file mode 100644
index 2f962a5f13..0000000000
--- a/app/javascript/mastodon/features/circle_statuses/index.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { useEffect, useRef, useCallback } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { useParams } from 'react-router';
-
-import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
-import {
-  expandCircleStatuses,
-  fetchCircle,
-  fetchCircleStatuses,
-} from 'mastodon/actions/circles';
-import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
-import { Column } from 'mastodon/components/column';
-import type { ColumnRef } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import StatusList from 'mastodon/components/status_list';
-import { getSubStatusList } from 'mastodon/selectors';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const CircleStatuses: React.FC<{
-  columnId: string;
-  multiColumn: boolean;
-}> = ({ columnId, multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const { id } = useParams<{ id: string }>();
-  const columnRef = useRef<ColumnRef>(null);
-  const statusIds = useAppSelector((state) =>
-    getSubStatusList(state, 'circle', id),
-  );
-  const isLoading = useAppSelector(
-    (state) =>
-      state.status_lists.getIn(
-        ['circle_statuses', id, 'isLoading'],
-        true,
-      ) as boolean,
-  );
-  const hasMore = useAppSelector(
-    (state) => !!state.status_lists.getIn(['circle_statuses', id, 'next']),
-  );
-  const circle = useAppSelector((state) => state.circles.get(id));
-
-  useEffect(() => {
-    dispatch(fetchCircle(id));
-    dispatch(fetchCircleStatuses(id));
-  }, [dispatch, id]);
-
-  const handlePin = useCallback(() => {
-    if (columnId) {
-      dispatch(removeColumn(columnId));
-    } else {
-      dispatch(addColumn('CIRCLE_STATUSES', { id }));
-    }
-  }, [dispatch, columnId, id]);
-
-  const handleMove = useCallback(
-    (dir: number) => {
-      dispatch(moveColumn(columnId, dir));
-    },
-    [dispatch, columnId],
-  );
-
-  const handleHeaderClick = useCallback(() => {
-    columnRef.current?.scrollTop();
-  }, []);
-
-  const handleLoadMore = useCallback(() => {
-    dispatch(expandCircleStatuses(id));
-  }, [dispatch, id]);
-
-  const pinned = !!columnId;
-
-  const emptyMessage = (
-    <FormattedMessage
-      id='empty_column.circle_statuses'
-      defaultMessage="You don't have any circle posts yet. When you post one as circle, it will show up here."
-    />
-  );
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      ref={columnRef}
-      label={circle?.get('title')}
-    >
-      <ColumnHeader
-        icon='bookmark'
-        iconComponent={CircleIcon}
-        title={circle?.get('title')}
-        onPin={handlePin}
-        onMove={handleMove}
-        onClick={handleHeaderClick}
-        pinned={pinned}
-        multiColumn={multiColumn}
-      />
-
-      <StatusList
-        trackScroll={!pinned}
-        statusIds={statusIds}
-        scrollKey={`bookmark_ex_statuses-${columnId}`}
-        hasMore={hasMore}
-        isLoading={isLoading}
-        onLoadMore={handleLoadMore}
-        emptyMessage={emptyMessage}
-        bindToDocument={!multiColumn}
-      />
-
-      <Helmet>
-        <title>{circle?.get('title')}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default CircleStatuses;
diff --git a/app/javascript/mastodon/features/circles/components/new_circle_form.jsx b/app/javascript/mastodon/features/circles/components/new_circle_form.jsx
new file mode 100644
index 0000000000..a28d0a1266
--- /dev/null
+++ b/app/javascript/mastodon/features/circles/components/new_circle_form.jsx
@@ -0,0 +1,80 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import { changeCircleEditorTitle, submitCircleEditor } from 'mastodon/actions/circles';
+import { Button } from 'mastodon/components/button';
+
+const messages = defineMessages({
+  label: { id: 'circles.new.title_placeholder', defaultMessage: 'New circle title' },
+  title: { id: 'circles.new.create', defaultMessage: 'Add circle' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['circleEditor', 'title']),
+  disabled: state.getIn(['circleEditor', 'isSubmitting']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onChange: value => dispatch(changeCircleEditorTitle(value)),
+  onSubmit: () => dispatch(submitCircleEditor(true)),
+});
+
+class NewCircleForm extends PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    disabled: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleSubmit = e => {
+    e.preventDefault();
+    this.props.onSubmit();
+  };
+
+  handleClick = () => {
+    this.props.onSubmit();
+  };
+
+  render () {
+    const { value, disabled, intl } = this.props;
+
+    const label = intl.formatMessage(messages.label);
+    const title = intl.formatMessage(messages.title);
+
+    return (
+      <form className='column-inline-form' onSubmit={this.handleSubmit}>
+        <label>
+          <span style={{ display: 'none' }}>{label}</span>
+
+          <input
+            className='setting-text'
+            value={value}
+            disabled={disabled}
+            onChange={this.handleChange}
+            placeholder={label}
+          />
+        </label>
+
+        <Button
+          disabled={disabled || !value}
+          text={title}
+          onClick={this.handleClick}
+        />
+      </form>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(NewCircleForm));
diff --git a/app/javascript/mastodon/features/circles/index.jsx b/app/javascript/mastodon/features/circles/index.jsx
new file mode 100644
index 0000000000..8b5a62db5e
--- /dev/null
+++ b/app/javascript/mastodon/features/circles/index.jsx
@@ -0,0 +1,125 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import { createSelector } from '@reduxjs/toolkit';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+
+import CirclesIcon from '@/material-icons/400-24px/account_circle-fill.svg?react';
+import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
+import { fetchCircles, deleteCircle } from 'mastodon/actions/circles';
+import { openModal } from 'mastodon/actions/modal';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import ScrollableList from 'mastodon/components/scrollable_list';
+import ColumnLink from 'mastodon/features/ui/components/column_link';
+import ColumnSubheading from 'mastodon/features/ui/components/column_subheading';
+
+import NewCircleForm from './components/new_circle_form';
+
+const messages = defineMessages({
+  heading: { id: 'column.circles', defaultMessage: 'Circles' },
+  subheading: { id: 'circles.subheading', defaultMessage: 'Your circles' },
+  deleteMessage: { id: 'confirmations.delete_circle.message', defaultMessage: 'Are you sure you want to permanently delete this circle?' },
+  deleteConfirm: { id: 'confirmations.delete_circle.confirm', defaultMessage: 'Delete' },
+});
+
+const getOrderedCircles = createSelector([state => state.get('circles')], circles => {
+  if (!circles) {
+    return circles;
+  }
+
+  return circles.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
+});
+
+const mapStateToProps = state => ({
+  circles: getOrderedCircles(state),
+});
+
+class Circles extends ImmutablePureComponent {
+
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    circles: ImmutablePropTypes.list,
+    intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
+  };
+
+  UNSAFE_componentWillMount () {
+    this.props.dispatch(fetchCircles());
+  }
+
+  handleEditClick = (e) => {
+    e.preventDefault();
+    this.props.dispatch(openModal({
+      modalType: 'CIRCLE_EDITOR',
+      modalProps: { circleId: e.currentTarget.getAttribute('data-id') },
+    }));
+  };
+
+  handleRemoveClick = (e) => {
+    const { dispatch, intl } = this.props;
+
+    e.preventDefault();
+    const id = e.currentTarget.getAttribute('data-id');
+
+    dispatch(openModal({
+      modalType: 'CONFIRM',
+      modalProps: {
+        message: intl.formatMessage(messages.deleteMessage),
+        confirm: intl.formatMessage(messages.deleteConfirm),
+        onConfirm: () => {
+          dispatch(deleteCircle(id));
+        },
+      },
+    }));
+  };
+
+  render () {
+    const { intl, circles, multiColumn } = this.props;
+
+    if (!circles) {
+      return (
+        <Column>
+          <LoadingIndicator />
+        </Column>
+      );
+    }
+
+    const emptyMessage = <FormattedMessage id='empty_column.circles' defaultMessage="You don't have any circles yet. When you create one, it will show up here." />;
+
+    return (
+      <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.heading)}>
+        <ColumnHeader title={intl.formatMessage(messages.heading)} icon='user-circle' iconComponent={CirclesIcon} multiColumn={multiColumn} />
+
+        <NewCircleForm />
+
+        <ScrollableList
+          scrollKey='circles'
+          emptyMessage={emptyMessage}
+          prepend={<ColumnSubheading text={intl.formatMessage(messages.subheading)} />}
+          bindToDocument={!multiColumn}
+        >
+          {circles.map(circle =>
+            <ColumnLink key={circle.get('id')} to={`/circles/${circle.get('id')}`} icon='user-circle' iconComponent={CircleIcon} text={circle.get('title')} />,
+          )}
+        </ScrollableList>
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.heading)}</title>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(Circles));
diff --git a/app/javascript/mastodon/features/circles/index.tsx b/app/javascript/mastodon/features/circles/index.tsx
deleted file mode 100644
index 38291febc7..0000000000
--- a/app/javascript/mastodon/features/circles/index.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import { useEffect, useMemo, useCallback } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { Link } from 'react-router-dom';
-
-import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
-import AddIcon from '@/material-icons/400-24px/add.svg?react';
-import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
-import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
-import { fetchCircles } from 'mastodon/actions/circles';
-import { openModal } from 'mastodon/actions/modal';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
-import { Icon } from 'mastodon/components/icon';
-import ScrollableList from 'mastodon/components/scrollable_list';
-import { getOrderedCircles } from 'mastodon/selectors/circles';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-const messages = defineMessages({
-  heading: { id: 'column.circles', defaultMessage: 'Circles' },
-  create: { id: 'circles.create_circle', defaultMessage: 'Create circle' },
-  edit: { id: 'circles.edit', defaultMessage: 'Edit circle' },
-  delete: { id: 'circles.delete', defaultMessage: 'Delete circle' },
-  more: { id: 'status.more', defaultMessage: 'More' },
-});
-
-const CircleItem: React.FC<{
-  id: string;
-  title: string;
-}> = ({ id, title }) => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-
-  const handleDeleteClick = useCallback(() => {
-    dispatch(
-      openModal({
-        modalType: 'CONFIRM_DELETE_CIRCLE',
-        modalProps: {
-          circleId: id,
-        },
-      }),
-    );
-  }, [dispatch, id]);
-
-  const menu = useMemo(
-    () => [
-      { text: intl.formatMessage(messages.edit), to: `/circles/${id}/edit` },
-      { text: intl.formatMessage(messages.delete), action: handleDeleteClick },
-    ],
-    [intl, id, handleDeleteClick],
-  );
-
-  return (
-    <div className='lists__item'>
-      <Link to={`/circles/${id}`} className='lists__item__title'>
-        <Icon id='circle-ul' icon={CircleIcon} />
-        <span>{title}</span>
-      </Link>
-
-      <Dropdown
-        scrollKey='circles'
-        items={menu}
-        icon='ellipsis-h'
-        iconComponent={MoreHorizIcon}
-        title={intl.formatMessage(messages.more)}
-      />
-    </div>
-  );
-};
-
-const Circles: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-  const circles = useAppSelector((state) => getOrderedCircles(state));
-
-  useEffect(() => {
-    dispatch(fetchCircles());
-  }, [dispatch]);
-
-  const emptyMessage = (
-    <>
-      <span>
-        <FormattedMessage
-          id='circles.no_circles_yet'
-          defaultMessage='No circles yet.'
-        />
-        <br />
-        <FormattedMessage
-          id='circles.create_a_circle_to_organize'
-          defaultMessage='Create a new circle to organize your Home feed'
-        />
-      </span>
-
-      <SquigglyArrow className='empty-column-indicator__arrow' />
-    </>
-  );
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(messages.heading)}
-    >
-      <ColumnHeader
-        title={intl.formatMessage(messages.heading)}
-        icon='circle-ul'
-        iconComponent={CircleIcon}
-        multiColumn={multiColumn}
-        extraButton={
-          <Link
-            to='/circles/new'
-            className='column-header__button'
-            title={intl.formatMessage(messages.create)}
-            aria-label={intl.formatMessage(messages.create)}
-          >
-            <Icon id='plus' icon={AddIcon} />
-          </Link>
-        }
-      />
-
-      <ScrollableList
-        scrollKey='circles'
-        emptyMessage={emptyMessage}
-        bindToDocument={!multiColumn}
-      >
-        {circles.map((circle) => (
-          <CircleItem key={circle.id} id={circle.id} title={circle.title} />
-        ))}
-      </ScrollableList>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default Circles;
diff --git a/app/javascript/mastodon/features/circles/members.tsx b/app/javascript/mastodon/features/circles/members.tsx
deleted file mode 100644
index a2f09bace1..0000000000
--- a/app/javascript/mastodon/features/circles/members.tsx
+++ /dev/null
@@ -1,377 +0,0 @@
-import { useCallback, useState, useEffect, useRef } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { useParams, Link } from 'react-router-dom';
-
-import { useDebouncedCallback } from 'use-debounce';
-
-import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
-import AddIcon from '@/material-icons/400-24px/add.svg?react';
-import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react';
-import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
-import { fetchFollowers } from 'mastodon/actions/accounts';
-import { fetchCircle } from 'mastodon/actions/circles';
-import { importFetchedAccounts } from 'mastodon/actions/importer';
-import { apiRequest } from 'mastodon/api';
-import {
-  apiGetAccounts,
-  apiAddAccountToCircle,
-  apiRemoveAccountFromCircle,
-} from 'mastodon/api/circles';
-import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
-import { Avatar } from 'mastodon/components/avatar';
-import { Button } from 'mastodon/components/button';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { FollowersCounter } from 'mastodon/components/counters';
-import { DisplayName } from 'mastodon/components/display_name';
-import { Icon } from 'mastodon/components/icon';
-import ScrollableList from 'mastodon/components/scrollable_list';
-import { ShortNumber } from 'mastodon/components/short_number';
-import { VerifiedBadge } from 'mastodon/components/verified_badge';
-import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
-import { me } from 'mastodon/initial_state';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  heading: {
-    id: 'column.circle_members',
-    defaultMessage: 'Manage circle members',
-  },
-  placeholder: {
-    id: 'circles.search_placeholder',
-    defaultMessage: 'Search people you follow',
-  },
-  enterSearch: { id: 'circles.add_to_circle', defaultMessage: 'Add to circle' },
-  add: { id: 'circles.add_member', defaultMessage: 'Add' },
-  remove: { id: 'circles.remove_member', defaultMessage: 'Remove' },
-  back: { id: 'column_back_button.label', defaultMessage: 'Back' },
-});
-
-type Mode = 'remove' | 'add';
-
-const ColumnSearchHeader: React.FC<{
-  onBack: () => void;
-  onSubmit: (value: string) => void;
-}> = ({ onBack, onSubmit }) => {
-  const intl = useIntl();
-  const [value, setValue] = useState('');
-
-  const handleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setValue(value);
-      onSubmit(value);
-    },
-    [setValue, onSubmit],
-  );
-
-  const handleSubmit = useCallback(() => {
-    onSubmit(value);
-  }, [onSubmit, value]);
-
-  return (
-    <ButtonInTabsBar>
-      <form className='column-search-header' onSubmit={handleSubmit}>
-        <button
-          type='button'
-          className='column-header__back-button compact'
-          onClick={onBack}
-          aria-label={intl.formatMessage(messages.back)}
-        >
-          <Icon
-            id='chevron-left'
-            icon={ArrowBackIcon}
-            className='column-back-button__icon'
-          />
-        </button>
-
-        <input
-          type='search'
-          value={value}
-          onChange={handleChange}
-          placeholder={intl.formatMessage(messages.placeholder)}
-          /* eslint-disable-next-line jsx-a11y/no-autofocus */
-          autoFocus
-        />
-      </form>
-    </ButtonInTabsBar>
-  );
-};
-
-const AccountItem: React.FC<{
-  accountId: string;
-  circleId: string;
-  partOfCircle: boolean;
-  onToggle: (accountId: string) => void;
-}> = ({ accountId, circleId, partOfCircle, onToggle }) => {
-  const intl = useIntl();
-  const account = useAppSelector((state) => state.accounts.get(accountId));
-
-  const handleClick = useCallback(() => {
-    if (partOfCircle) {
-      void apiRemoveAccountFromCircle(circleId, accountId);
-    } else {
-      void apiAddAccountToCircle(circleId, accountId);
-    }
-
-    onToggle(accountId);
-  }, [accountId, circleId, partOfCircle, onToggle]);
-
-  if (!account) {
-    return null;
-  }
-
-  const firstVerifiedField = account.fields.find((item) => !!item.verified_at);
-
-  return (
-    <div className='account'>
-      <div className='account__wrapper'>
-        <Link
-          key={account.id}
-          className='account__display-name'
-          title={account.acct}
-          to={`/@${account.acct}`}
-          data-hover-card-account={account.id}
-        >
-          <div className='account__avatar-wrapper'>
-            <Avatar account={account} size={36} />
-          </div>
-
-          <div className='account__contents'>
-            <DisplayName account={account} />
-
-            <div className='account__details'>
-              <ShortNumber
-                value={account.followers_count}
-                renderer={FollowersCounter}
-                isHide={account.other_settings.hide_followers_count}
-              />{' '}
-              {firstVerifiedField && (
-                <VerifiedBadge link={firstVerifiedField.value} />
-              )}
-            </div>
-          </div>
-        </Link>
-
-        <div className='account__relationship'>
-          <Button
-            text={intl.formatMessage(
-              partOfCircle ? messages.remove : messages.add,
-            )}
-            onClick={handleClick}
-          />
-        </div>
-      </div>
-    </div>
-  );
-};
-
-const CircleMembers: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const { id } = useParams<{ id: string }>();
-  const intl = useIntl();
-
-  const followingAccountIds = useAppSelector(
-    (state) => state.user_lists.getIn(['followers', me, 'items']) as string[],
-  );
-  const [searching, setSearching] = useState(false);
-  const [accountIds, setAccountIds] = useState<string[]>([]);
-  const [searchAccountIds, setSearchAccountIds] = useState<string[]>([]);
-  const [loading, setLoading] = useState(true);
-  const [mode, setMode] = useState<Mode>('remove');
-
-  useEffect(() => {
-    if (id) {
-      setLoading(true);
-      dispatch(fetchCircle(id));
-
-      void apiGetAccounts(id)
-        .then((data) => {
-          dispatch(importFetchedAccounts(data));
-          setAccountIds(data.map((a) => a.id));
-          setLoading(false);
-          return '';
-        })
-        .catch(() => {
-          setLoading(false);
-        });
-
-      dispatch(fetchFollowers(me));
-    }
-  }, [dispatch, id]);
-
-  const handleSearchClick = useCallback(() => {
-    setMode('add');
-  }, [setMode]);
-
-  const handleDismissSearchClick = useCallback(() => {
-    setMode('remove');
-    setSearching(false);
-  }, [setMode]);
-
-  const handleAccountToggle = useCallback(
-    (accountId: string) => {
-      const partOfCircle = accountIds.includes(accountId);
-
-      if (partOfCircle) {
-        setAccountIds(accountIds.filter((id) => id !== accountId));
-      } else {
-        setAccountIds([accountId, ...accountIds]);
-      }
-    },
-    [accountIds, setAccountIds],
-  );
-
-  const searchRequestRef = useRef<AbortController | null>(null);
-
-  const handleSearch = useDebouncedCallback(
-    (value: string) => {
-      if (searchRequestRef.current) {
-        searchRequestRef.current.abort();
-      }
-
-      if (value.trim().length === 0) {
-        setSearching(false);
-        return;
-      }
-
-      setLoading(true);
-
-      searchRequestRef.current = new AbortController();
-
-      void apiRequest<ApiAccountJSON[]>('GET', 'v1/accounts/search', {
-        signal: searchRequestRef.current.signal,
-        params: {
-          q: value,
-          resolve: false,
-          following: true,
-        },
-      })
-        .then((data) => {
-          dispatch(importFetchedAccounts(data));
-          setSearchAccountIds(data.map((a) => a.id));
-          setLoading(false);
-          setSearching(true);
-          return '';
-        })
-        .catch(() => {
-          setSearching(true);
-          setLoading(false);
-        });
-    },
-    500,
-    { leading: true, trailing: true },
-  );
-
-  let displayedAccountIds: string[];
-
-  if (mode === 'add') {
-    displayedAccountIds = searching ? searchAccountIds : followingAccountIds;
-  } else {
-    displayedAccountIds = accountIds;
-  }
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(messages.heading)}
-    >
-      {mode === 'remove' ? (
-        <ColumnHeader
-          title={intl.formatMessage(messages.heading)}
-          icon='circle-ul'
-          iconComponent={CircleIcon}
-          multiColumn={multiColumn}
-          showBackButton
-          extraButton={
-            <button
-              onClick={handleSearchClick}
-              type='button'
-              className='column-header__button'
-              title={intl.formatMessage(messages.enterSearch)}
-              aria-label={intl.formatMessage(messages.enterSearch)}
-            >
-              <Icon id='plus' icon={AddIcon} />
-            </button>
-          }
-        />
-      ) : (
-        <ColumnSearchHeader
-          onBack={handleDismissSearchClick}
-          onSubmit={handleSearch}
-        />
-      )}
-
-      <ScrollableList
-        scrollKey='circle_members'
-        trackScroll={!multiColumn}
-        bindToDocument={!multiColumn}
-        isLoading={loading}
-        showLoading={loading && displayedAccountIds.length === 0}
-        hasMore={false}
-        footer={
-          mode === 'remove' && (
-            <>
-              {displayedAccountIds.length > 0 && <div className='spacer' />}
-
-              <div className='column-footer'>
-                <Link to={`/circles/${id}`} className='button button--block'>
-                  <FormattedMessage id='circles.done' defaultMessage='Done' />
-                </Link>
-              </div>
-            </>
-          )
-        }
-        emptyMessage={
-          mode === 'remove' ? (
-            <>
-              <span>
-                <FormattedMessage
-                  id='circles.no_members_yet'
-                  defaultMessage='No members yet.'
-                />
-                <br />
-                <FormattedMessage
-                  id='circles.find_users_to_add'
-                  defaultMessage='Find users to add'
-                />
-              </span>
-
-              <SquigglyArrow className='empty-column-indicator__arrow' />
-            </>
-          ) : (
-            <FormattedMessage
-              id='circles.no_results_found'
-              defaultMessage='No results found.'
-            />
-          )
-        }
-      >
-        {displayedAccountIds.map((accountId) => (
-          <AccountItem
-            key={accountId}
-            accountId={accountId}
-            circleId={id}
-            partOfCircle={
-              displayedAccountIds === accountIds ||
-              accountIds.includes(accountId)
-            }
-            onToggle={handleAccountToggle}
-          />
-        ))}
-      </ScrollableList>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default CircleMembers;
diff --git a/app/javascript/mastodon/features/circles/new.tsx b/app/javascript/mastodon/features/circles/new.tsx
deleted file mode 100644
index f48bcaadc0..0000000000
--- a/app/javascript/mastodon/features/circles/new.tsx
+++ /dev/null
@@ -1,212 +0,0 @@
-import { useCallback, useState, useEffect } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { useParams, useHistory, Link } from 'react-router-dom';
-
-import { isFulfilled } from '@reduxjs/toolkit';
-
-import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
-import { fetchCircle } from 'mastodon/actions/circles';
-import { createCircle, updateCircle } from 'mastodon/actions/circles_typed';
-import { apiGetAccounts } from 'mastodon/api/circles';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { LoadingIndicator } from 'mastodon/components/loading_indicator';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  edit: { id: 'column.edit_circle', defaultMessage: 'Edit circle' },
-  create: { id: 'column.create_circle', defaultMessage: 'Create circle' },
-});
-
-const MembersLink: React.FC<{
-  id: string;
-}> = ({ id }) => {
-  const [count, setCount] = useState(0);
-  const [avatars, setAvatars] = useState<string[]>([]);
-
-  useEffect(() => {
-    void apiGetAccounts(id)
-      .then((data) => {
-        setCount(data.length);
-        setAvatars(data.slice(0, 3).map((a) => a.avatar));
-        return '';
-      })
-      .catch(() => {
-        // Nothing
-      });
-  }, [id, setCount, setAvatars]);
-
-  return (
-    <Link to={`/circles/${id}/members`} className='app-form__link'>
-      <div className='app-form__link__text'>
-        <strong>
-          <FormattedMessage
-            id='circles.circle_members'
-            defaultMessage='Circle members'
-          />
-        </strong>
-        <FormattedMessage
-          id='circles.circle_members_count'
-          defaultMessage='{count, plural, one {# member} other {# members}}'
-          values={{ count }}
-        />
-      </div>
-
-      <div className='avatar-pile'>
-        {avatars.map((url) => (
-          <img key={url} src={url} alt='' />
-        ))}
-      </div>
-    </Link>
-  );
-};
-
-const NewCircle: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const { id } = useParams<{ id?: string }>();
-  const intl = useIntl();
-  const history = useHistory();
-
-  const circle = useAppSelector((state) =>
-    id ? state.circles.get(id) : undefined,
-  );
-  const [title, setTitle] = useState('');
-  const [submitting, setSubmitting] = useState(false);
-
-  useEffect(() => {
-    if (id) {
-      dispatch(fetchCircle(id));
-    }
-  }, [dispatch, id]);
-
-  useEffect(() => {
-    if (id && circle) {
-      setTitle(circle.title);
-    }
-  }, [setTitle, id, circle]);
-
-  const handleTitleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setTitle(value);
-    },
-    [setTitle],
-  );
-
-  const handleSubmit = useCallback(() => {
-    setSubmitting(true);
-
-    if (id) {
-      void dispatch(
-        updateCircle({
-          id,
-          title,
-        }),
-      ).then(() => {
-        setSubmitting(false);
-        return '';
-      });
-    } else {
-      void dispatch(
-        createCircle({
-          title,
-        }),
-      ).then((result) => {
-        setSubmitting(false);
-
-        if (isFulfilled(result)) {
-          history.replace(`/circles/${result.payload.id}/edit`);
-          history.push(`/circles/${result.payload.id}/members`);
-        }
-
-        return '';
-      });
-    }
-  }, [history, dispatch, setSubmitting, id, title]);
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(id ? messages.edit : messages.create)}
-    >
-      <ColumnHeader
-        title={intl.formatMessage(id ? messages.edit : messages.create)}
-        icon='circle-ul'
-        iconComponent={CircleIcon}
-        multiColumn={multiColumn}
-        showBackButton
-      />
-
-      <div className='scrollable'>
-        <form className='simple_form app-form' onSubmit={handleSubmit}>
-          <div className='fields-group'>
-            <div className='input with_label'>
-              <div className='label_input'>
-                <label htmlFor='circle_title'>
-                  <FormattedMessage
-                    id='circles.circle_name'
-                    defaultMessage='Circle name'
-                  />
-                </label>
-
-                <div className='label_input__wrapper'>
-                  <input
-                    id='circle_title'
-                    type='text'
-                    value={title}
-                    onChange={handleTitleChange}
-                    maxLength={30}
-                    required
-                    placeholder=' '
-                  />
-                </div>
-              </div>
-            </div>
-          </div>
-
-          {id && (
-            <div className='fields-group'>
-              <MembersLink id={id} />
-            </div>
-          )}
-          {!id && (
-            <div className='fields-group'>
-              <div className='app-form__memo'>
-                <FormattedMessage
-                  id='circles.save_to_edit_member'
-                  defaultMessage='You can edit circle members after saving.'
-                />
-              </div>
-            </div>
-          )}
-
-          <div className='actions'>
-            <button className='button' type='submit'>
-              {submitting ? (
-                <LoadingIndicator />
-              ) : id ? (
-                <FormattedMessage id='circles.save' defaultMessage='Save' />
-              ) : (
-                <FormattedMessage id='circles.create' defaultMessage='Create' />
-              )}
-            </button>
-          </div>
-        </form>
-      </div>
-
-      <Helmet>
-        <title>
-          {intl.formatMessage(id ? messages.edit : messages.create)}
-        </title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default NewCircle;
diff --git a/app/javascript/mastodon/features/compose/components/action_bar.jsx b/app/javascript/mastodon/features/compose/components/action_bar.jsx
new file mode 100644
index 0000000000..fae7736855
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/components/action_bar.jsx
@@ -0,0 +1,67 @@
+import { useCallback } from 'react';
+
+import { defineMessages, useIntl } from 'react-intl';
+
+import { useDispatch } from 'react-redux';
+
+import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
+import { openModal } from 'mastodon/actions/modal';
+import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
+
+const messages = defineMessages({
+  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
+  pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
+  preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+  reaction_deck: { id: 'navigation_bar.reaction_deck', defaultMessage: 'Reaction deck' },
+  follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
+  favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
+  emoji_reactions: { id: 'navigation_bar.emoji_reactions', defaultMessage: 'Stamps' },
+  lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
+  followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
+  blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
+  domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
+  mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
+  filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
+  logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
+  bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
+});
+
+export const ActionBar = () => {
+  const dispatch = useDispatch();
+  const intl = useIntl();
+
+  const handleLogoutClick = useCallback(() => {
+    dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
+  }, [dispatch]);
+
+  let menu = [];
+
+  menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
+  menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
+  menu.push({ text: intl.formatMessage(messages.reaction_deck), to: '/reaction_deck' });
+  menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
+  menu.push(null);
+  menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
+  menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
+  menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
+  menu.push({ text: intl.formatMessage(messages.emoji_reactions), to: '/emoji_reactions' });
+  menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
+  menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
+  menu.push(null);
+  menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
+  menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
+  menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
+  menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' });
+  menu.push(null);
+  menu.push({ text: intl.formatMessage(messages.logout), action: handleLogoutClick });
+
+  return (
+    <DropdownMenuContainer
+      items={menu}
+      icon='bars'
+      iconComponent={MoreHorizIcon}
+      size={24}
+      direction='right'
+    />
+  );
+};
diff --git a/app/javascript/mastodon/features/compose/components/action_bar.tsx b/app/javascript/mastodon/features/compose/components/action_bar.tsx
deleted file mode 100644
index 4a92485209..0000000000
--- a/app/javascript/mastodon/features/compose/components/action_bar.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { useMemo } from 'react';
-
-import { defineMessages, useIntl } from 'react-intl';
-
-import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
-import { openModal } from 'mastodon/actions/modal';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
-import { useAppDispatch } from 'mastodon/store';
-
-const messages = defineMessages({
-  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
-  pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
-  preferences: {
-    id: 'navigation_bar.preferences',
-    defaultMessage: 'Preferences',
-  },
-  reaction_deck: {
-    id: 'navigation_bar.reaction_deck',
-    defaultMessage: 'Reaction deck',
-  },
-  follow_requests: {
-    id: 'navigation_bar.follow_requests',
-    defaultMessage: 'Follow requests',
-  },
-  favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
-  emoji_reactions: {
-    id: 'navigation_bar.emoji_reactions',
-    defaultMessage: 'Stamps',
-  },
-  lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
-  followed_tags: {
-    id: 'navigation_bar.followed_tags',
-    defaultMessage: 'Followed hashtags',
-  },
-  blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
-  domain_blocks: {
-    id: 'navigation_bar.domain_blocks',
-    defaultMessage: 'Blocked domains',
-  },
-  mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
-  filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
-  logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
-  bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
-});
-
-export const ActionBar: React.FC = () => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-
-  const menu = useMemo(() => {
-    const handleLogoutClick = () => {
-      dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} }));
-    };
-
-    return [
-      {
-        text: intl.formatMessage(messages.edit_profile),
-        href: '/settings/profile',
-      },
-      {
-        text: intl.formatMessage(messages.preferences),
-        href: '/settings/preferences',
-      },
-      { text: intl.formatMessage(messages.pins), to: '/pinned' },
-      null,
-      {
-        text: intl.formatMessage(messages.follow_requests),
-        to: '/follow_requests',
-      },
-      { text: intl.formatMessage(messages.favourites), to: '/favourites' },
-      { text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' },
-      {
-        text: intl.formatMessage(messages.emoji_reactions),
-        to: '/emoji_reactions',
-      },
-      { text: intl.formatMessage(messages.lists), to: '/lists' },
-      {
-        text: intl.formatMessage(messages.followed_tags),
-        to: '/followed_tags',
-      },
-      null,
-      { text: intl.formatMessage(messages.mutes), to: '/mutes' },
-      { text: intl.formatMessage(messages.blocks), to: '/blocks' },
-      {
-        text: intl.formatMessage(messages.domain_blocks),
-        to: '/domain_blocks',
-      },
-      { text: intl.formatMessage(messages.filters), href: '/filters' },
-      null,
-      { text: intl.formatMessage(messages.logout), action: handleLogoutClick },
-    ];
-  }, [intl, dispatch]);
-
-  return <Dropdown items={menu} icon='bars' iconComponent={MoreHorizIcon} />;
-};
diff --git a/app/javascript/mastodon/features/compose/components/character_counter.jsx b/app/javascript/mastodon/features/compose/components/character_counter.jsx
new file mode 100644
index 0000000000..f400d1fe2f
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/components/character_counter.jsx
@@ -0,0 +1,18 @@
+import PropTypes from 'prop-types';
+
+import { length } from 'stringz';
+
+export const CharacterCounter = ({ text, max }) => {
+  const diff = max - length(text);
+
+  if (diff < 0) {
+    return <span className='character-counter character-counter--over'>{diff}</span>;
+  }
+
+  return <span className='character-counter'>{diff}</span>;
+};
+
+CharacterCounter.propTypes = {
+  text: PropTypes.string.isRequired,
+  max: PropTypes.number.isRequired,
+};
diff --git a/app/javascript/mastodon/features/compose/components/character_counter.tsx b/app/javascript/mastodon/features/compose/components/character_counter.tsx
deleted file mode 100644
index df70045493..0000000000
--- a/app/javascript/mastodon/features/compose/components/character_counter.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { length } from 'stringz';
-
-export const CharacterCounter: React.FC<{
-  text: string;
-  max: number;
-}> = ({ text, max }) => {
-  const diff = max - length(text);
-
-  if (diff < 0) {
-    return (
-      <span className='character-counter character-counter--over'>{diff}</span>
-    );
-  }
-
-  return <span className='character-counter'>{diff}</span>;
-};
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx
index 889e7951d8..4776e3e20c 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.jsx
+++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx
@@ -10,8 +10,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 
 import { length } from 'stringz';
 
-import { missingAltTextModal } from 'mastodon/initial_state';
-
 import AutosuggestInput from '../../../components/autosuggest_input';
 import AutosuggestTextarea from '../../../components/autosuggest_textarea';
 import { Button } from '../../../components/button';
@@ -19,22 +17,22 @@ import CircleDropdownContainer from '../containers/circle_dropdown_container';
 import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
 import ExpirationDropdownContainer from '../containers/expiration_dropdown_container';
 import FeaturedTagsDropdownContainer from '../containers/featured_tags_dropdown_container';
+import LanguageDropdown from '../containers/language_dropdown_container';
 import MarkdownButtonContainer from '../containers/markdown_button_container';
 import PollButtonContainer from '../containers/poll_button_container';
 import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
 import SearchabilityDropdownContainer from '../containers/searchability_dropdown_container';
 import SpoilerButtonContainer from '../containers/spoiler_button_container';
 import UploadButtonContainer from '../containers/upload_button_container';
+import WarningContainer from '../containers/warning_container';
 import { countableText } from '../util/counter';
 
 import { CharacterCounter } from './character_counter';
 import { EditIndicator } from './edit_indicator';
-import { LanguageDropdown } from './language_dropdown';
 import { NavigationBar } from './navigation_bar';
 import { PollForm } from "./poll_form";
 import { ReplyIndicator } from './reply_indicator';
 import { UploadForm } from './upload_form';
-import { Warning } from './warning';
 
 const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
 
@@ -75,7 +73,6 @@ class ComposeForm extends ImmutablePureComponent {
     autoFocus: PropTypes.bool,
     withoutNavigation: PropTypes.bool,
     anyMedia: PropTypes.bool,
-    missingAltText: PropTypes.bool,
     isInReply: PropTypes.bool,
     singleColumn: PropTypes.bool,
     lang: PropTypes.string,
@@ -129,7 +126,7 @@ class ComposeForm extends ImmutablePureComponent {
       return;
     }
 
-    this.props.onSubmit(missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct');
+    this.props.onSubmit();
 
     if (e) {
       e.preventDefault();
@@ -254,7 +251,7 @@ class ComposeForm extends ImmutablePureComponent {
       <form className='compose-form' onSubmit={this.handleSubmit}>
         <ReplyIndicator />
         {!withoutNavigation && <NavigationBar />}
-        <Warning />
+        <WarningContainer />
 
         <div className={classNames('compose-form__highlightable', { active: highlighted })} ref={this.setRef}>
           <div className='compose-form__scrollable'>
@@ -335,7 +332,6 @@ class ComposeForm extends ImmutablePureComponent {
               <div className='compose-form__submit'>
                 <Button
                   type='submit'
-                  compact
                   text={intl.formatMessage(this.props.isEditing ? messages.saveChanges : (this.props.isInReply ? messages.reply : messages.publish))}
                   disabled={!this.canSubmit()}
                 />
diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx
index 175bb6ada9..dac873c8e4 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx
@@ -12,14 +12,11 @@ import Overlay from 'react-overlays/Overlay';
 
 import MoodIcon from '@/material-icons/400-20px/mood.svg?react';
 import { IconButton } from 'mastodon/components/icon_button';
-import emojiCompressed from 'mastodon/features/emoji/emoji_compressed';
 import { assetHost } from 'mastodon/utils/config';
 
 import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji';
 import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
 
-const nimblePickerData = emojiCompressed[5];
-
 const messages = defineMessages({
   emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
   emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
@@ -40,18 +37,15 @@ let EmojiPicker, Emoji; // load asynchronously
 
 const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
 
-const backgroundImageFn = () => `${assetHost}/emoji/sheet_15.png`;
+const backgroundImageFn = () => `${assetHost}/emoji/sheet_13.png`;
 
 const notFoundFn = () => (
   <div className='emoji-mart-no-results'>
     <Emoji
-      data={nimblePickerData}
       emoji='sleuth_or_spy'
       set='twitter'
       size={32}
       sheetSize={32}
-      sheetColumns={62}
-      sheetRows={62}
       backgroundImageFn={backgroundImageFn}
     />
 
@@ -73,7 +67,7 @@ class ModifierPickerMenu extends PureComponent {
     this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1);
   };
 
-  UNSAFE_componentWillReceiveProps(nextProps) {
+  UNSAFE_componentWillReceiveProps (nextProps) {
     if (nextProps.active) {
       this.attachListeners();
     } else {
@@ -81,7 +75,7 @@ class ModifierPickerMenu extends PureComponent {
     }
   }
 
-  componentWillUnmount() {
+  componentWillUnmount () {
     this.removeListeners();
   }
 
@@ -91,12 +85,12 @@ class ModifierPickerMenu extends PureComponent {
     }
   };
 
-  attachListeners() {
+  attachListeners () {
     document.addEventListener('click', this.handleDocumentClick, { capture: true });
     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
   }
 
-  removeListeners() {
+  removeListeners () {
     document.removeEventListener('click', this.handleDocumentClick, { capture: true });
     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
   }
@@ -105,17 +99,17 @@ class ModifierPickerMenu extends PureComponent {
     this.node = c;
   };
 
-  render() {
+  render () {
     const { active } = this.props;
 
     return (
       <div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
-        <button type='button' onClick={this.handleClick} data-index={1}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
-        <button type='button' onClick={this.handleClick} data-index={2}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
-        <button type='button' onClick={this.handleClick} data-index={3}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
-        <button type='button' onClick={this.handleClick} data-index={4}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
-        <button type='button' onClick={this.handleClick} data-index={5}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
-        <button type='button' onClick={this.handleClick} data-index={6}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
+        <button type='button' onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
+        <button type='button' onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
+        <button type='button' onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
+        <button type='button' onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
+        <button type='button' onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
+        <button type='button' onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
       </div>
     );
   }
@@ -145,12 +139,12 @@ class ModifierPicker extends PureComponent {
     this.props.onClose();
   };
 
-  render() {
+  render () {
     const { active, modifier } = this.props;
 
     return (
       <div className='emoji-picker-dropdown__modifiers'>
-        <Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
+        <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
         <ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />
       </div>
     );
@@ -190,7 +184,7 @@ class EmojiPickerMenuImpl extends PureComponent {
     }
   };
 
-  componentDidMount() {
+  componentDidMount () {
     document.addEventListener('click', this.handleDocumentClick, { capture: true });
     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
 
@@ -205,7 +199,7 @@ class EmojiPickerMenuImpl extends PureComponent {
     });
   }
 
-  componentWillUnmount() {
+  componentWillUnmount () {
     document.removeEventListener('click', this.handleDocumentClick, { capture: true });
     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
   }
@@ -258,7 +252,7 @@ class EmojiPickerMenuImpl extends PureComponent {
     this.props.onSkinTone(modifier);
   };
 
-  render() {
+  render () {
     const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props;
 
     if (loading) {
@@ -286,9 +280,6 @@ class EmojiPickerMenuImpl extends PureComponent {
     return (
       <div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
         <EmojiPicker
-          data={nimblePickerData}
-          sheetColumns={62}
-          sheetRows={62}
           perLine={8}
           emojiSize={22}
           sheetSize={32}
@@ -355,7 +346,7 @@ class EmojiPickerDropdown extends PureComponent {
 
       EmojiPickerAsync().then(EmojiMart => {
         EmojiPicker = EmojiMart.Picker;
-        Emoji = EmojiMart.Emoji;
+        Emoji       = EmojiMart.Emoji;
 
         this.setState({ loading: false });
       }).catch(() => {
@@ -396,7 +387,7 @@ class EmojiPickerDropdown extends PureComponent {
     this.setState({ placement: state.placement });
   };
 
-  render() {
+  render () {
     const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, inverted } = this.props;
     const title = intl.formatMessage(messages.emoji);
     const { active, loading, placement } = this.state;
@@ -413,7 +404,7 @@ class EmojiPickerDropdown extends PureComponent {
         />
 
         <Overlay show={active} placement={placement} flip target={this.findTarget} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
-          {({ props, placement }) => (
+          {({ props, placement })=> (
             <div {...props} style={{ ...props.style }}>
               <div className={`dropdown-animation ${placement}`}>
                 <EmojiPickerMenu
diff --git a/app/javascript/mastodon/features/compose/components/language_dropdown.jsx b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx
new file mode 100644
index 0000000000..b164a07cbd
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx
@@ -0,0 +1,324 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { injectIntl, defineMessages } from 'react-intl';
+
+import classNames from 'classnames';
+
+import { supportsPassiveEvents } from 'detect-passive-events';
+import fuzzysort from 'fuzzysort';
+import Overlay from 'react-overlays/Overlay';
+
+import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
+import SearchIcon from '@/material-icons/400-24px/search.svg?react';
+import TranslateIcon from '@/material-icons/400-24px/translate.svg?react';
+import { Icon } from 'mastodon/components/icon';
+import { languages as preloadedLanguages } from 'mastodon/initial_state';
+
+const messages = defineMessages({
+  changeLanguage: { id: 'compose.language.change', defaultMessage: 'Change language' },
+  search: { id: 'compose.language.search', defaultMessage: 'Search languages...' },
+  clear: { id: 'emoji_button.clear', defaultMessage: 'Clear' },
+});
+
+const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
+
+class LanguageDropdownMenu extends PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string).isRequired,
+    onClose: PropTypes.func.isRequired,
+    onChange: PropTypes.func.isRequired,
+    languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
+    intl: PropTypes.object,
+  };
+
+  static defaultProps = {
+    languages: preloadedLanguages,
+  };
+
+  state = {
+    searchValue: '',
+  };
+
+  handleDocumentClick = e => {
+    if (this.node && !this.node.contains(e.target)) {
+      this.props.onClose();
+      e.stopPropagation();
+    }
+  };
+
+  componentDidMount () {
+    document.addEventListener('click', this.handleDocumentClick, { capture: true });
+    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
+
+    // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
+    // to wait for a frame before focusing
+    requestAnimationFrame(() => {
+      if (this.node) {
+        const element = this.node.querySelector('input[type="search"]');
+        if (element) element.focus();
+      }
+    });
+  }
+
+  componentWillUnmount () {
+    document.removeEventListener('click', this.handleDocumentClick, { capture: true });
+    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
+  }
+
+  setRef = c => {
+    this.node = c;
+  };
+
+  setListRef = c => {
+    this.listNode = c;
+  };
+
+  handleSearchChange = ({ target }) => {
+    this.setState({ searchValue: target.value });
+  };
+
+  search () {
+    const { languages, value, frequentlyUsedLanguages } = this.props;
+    const { searchValue } = this.state;
+
+    if (searchValue === '') {
+      return [...languages].sort((a, b) => {
+        // Push current selection to the top of the list
+
+        if (a[0] === value) {
+          return -1;
+        } else if (b[0] === value) {
+          return 1;
+        } else {
+          // Sort according to frequently used languages
+
+          const indexOfA = frequentlyUsedLanguages.indexOf(a[0]);
+          const indexOfB = frequentlyUsedLanguages.indexOf(b[0]);
+
+          return ((indexOfA > -1 ? indexOfA : Infinity) - (indexOfB > -1 ? indexOfB : Infinity));
+        }
+      });
+    }
+
+    return fuzzysort.go(searchValue, languages, {
+      keys: ['0', '1', '2'],
+      limit: 5,
+      threshold: -10000,
+    }).map(result => result.obj);
+  }
+
+  handleClick = e => {
+    const value = e.currentTarget.getAttribute('data-index');
+
+    e.preventDefault();
+
+    this.props.onClose();
+    this.props.onChange(value);
+  };
+
+  handleKeyDown = e => {
+    const { onClose } = this.props;
+    const index = Array.from(this.listNode.childNodes).findIndex(node => node === e.currentTarget);
+
+    let element = null;
+
+    switch(e.key) {
+    case 'Escape':
+      onClose();
+      break;
+    case ' ':
+    case 'Enter':
+      this.handleClick(e);
+      break;
+    case 'ArrowDown':
+      element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
+      break;
+    case 'ArrowUp':
+      element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
+      break;
+    case 'Tab':
+      if (e.shiftKey) {
+        element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
+      } else {
+        element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
+      }
+      break;
+    case 'Home':
+      element = this.listNode.firstChild;
+      break;
+    case 'End':
+      element = this.listNode.lastChild;
+      break;
+    }
+
+    if (element) {
+      element.focus();
+      e.preventDefault();
+      e.stopPropagation();
+    }
+  };
+
+  handleSearchKeyDown = e => {
+    const { onChange, onClose } = this.props;
+    const { searchValue } = this.state;
+
+    let element = null;
+
+    switch(e.key) {
+    case 'Tab':
+    case 'ArrowDown':
+      element = this.listNode.firstChild;
+
+      if (element) {
+        element.focus();
+        e.preventDefault();
+        e.stopPropagation();
+      }
+
+      break;
+    case 'Enter':
+      element = this.listNode.firstChild;
+
+      if (element) {
+        onChange(element.getAttribute('data-index'));
+        onClose();
+      }
+      break;
+    case 'Escape':
+      if (searchValue !== '') {
+        e.preventDefault();
+        this.handleClear();
+      }
+
+      break;
+    }
+  };
+
+  handleClear = () => {
+    this.setState({ searchValue: '' });
+  };
+
+  renderItem = lang => {
+    const { value } = this.props;
+
+    return (
+      <div key={lang[0]} role='option' tabIndex={0} data-index={lang[0]} className={classNames('language-dropdown__dropdown__results__item', { active: lang[0] === value })} aria-selected={lang[0] === value} onClick={this.handleClick} onKeyDown={this.handleKeyDown}>
+        <span className='language-dropdown__dropdown__results__item__native-name' lang={lang[0]}>{lang[2]}</span> <span className='language-dropdown__dropdown__results__item__common-name'>({lang[1]})</span>
+      </div>
+    );
+  };
+
+  render () {
+    const { intl } = this.props;
+    const { searchValue } = this.state;
+    const isSearching = searchValue !== '';
+    const results = this.search();
+
+    return (
+      <div ref={this.setRef}>
+        <div className='emoji-mart-search'>
+          <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
+          <button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}><Icon icon={!isSearching ? SearchIcon : CancelIcon} /></button>
+        </div>
+
+        <div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>
+          {results.map(this.renderItem)}
+        </div>
+      </div>
+    );
+  }
+
+}
+
+class LanguageDropdown extends PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string,
+    frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string),
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func,
+  };
+
+  state = {
+    open: false,
+    placement: 'bottom',
+  };
+
+  handleToggle = () => {
+    if (this.state.open && this.activeElement) {
+      this.activeElement.focus({ preventScroll: true });
+    }
+
+    this.setState({ open: !this.state.open });
+  };
+
+  handleClose = () => {
+    if (this.state.open && this.activeElement) {
+      this.activeElement.focus({ preventScroll: true });
+    }
+
+    this.setState({ open: false });
+  };
+
+  handleChange = value => {
+    const { onChange } = this.props;
+    onChange(value);
+  };
+
+  setTargetRef = c => {
+    this.target = c;
+  };
+
+  findTarget = () => {
+    return this.target;
+  };
+
+  handleOverlayEnter = (state) => {
+    this.setState({ placement: state.placement });
+  };
+
+  render () {
+    const { value, intl, frequentlyUsedLanguages } = this.props;
+    const { open, placement } = this.state;
+    const current = preloadedLanguages.find(lang => lang[0] === value) ?? [];
+
+    return (
+      <div ref={this.setTargetRef} onKeyDown={this.handleKeyDown}>
+        <button
+          type='button'
+          title={intl.formatMessage(messages.changeLanguage)}
+          aria-expanded={open}
+          onClick={this.handleToggle}
+          onMouseDown={this.handleMouseDown}
+          onKeyDown={this.handleButtonKeyDown}
+          className={classNames('dropdown-button', { active: open })}
+        >
+          <Icon icon={TranslateIcon} />
+          <span className='dropdown-button__label'>{current[2] ?? value}</span>
+        </button>
+
+        <Overlay show={open} offset={[5, 5]} placement={placement} flip target={this.findTarget} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
+          {({ props, placement }) => (
+            <div {...props}>
+              <div className={`dropdown-animation language-dropdown__dropdown ${placement}`} >
+                <LanguageDropdownMenu
+                  value={value}
+                  frequentlyUsedLanguages={frequentlyUsedLanguages}
+                  onClose={this.handleClose}
+                  onChange={this.handleChange}
+                  intl={intl}
+                />
+              </div>
+            </div>
+          )}
+        </Overlay>
+      </div>
+    );
+  }
+
+}
+
+export default injectIntl(LanguageDropdown);
diff --git a/app/javascript/mastodon/features/compose/components/language_dropdown.tsx b/app/javascript/mastodon/features/compose/components/language_dropdown.tsx
deleted file mode 100644
index d11891308f..0000000000
--- a/app/javascript/mastodon/features/compose/components/language_dropdown.tsx
+++ /dev/null
@@ -1,428 +0,0 @@
-import { useCallback, useRef, useState, useEffect, useMemo } from 'react';
-
-import { useIntl, defineMessages } from 'react-intl';
-
-import classNames from 'classnames';
-
-import { createSelector } from '@reduxjs/toolkit';
-import { Map as ImmutableMap } from 'immutable';
-
-import fuzzysort from 'fuzzysort';
-import Overlay from 'react-overlays/Overlay';
-import type { State, Placement } from 'react-overlays/usePopper';
-
-import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
-import SearchIcon from '@/material-icons/400-24px/search.svg?react';
-import TranslateIcon from '@/material-icons/400-24px/translate.svg?react';
-import { changeComposeLanguage } from 'mastodon/actions/compose';
-import { Icon } from 'mastodon/components/icon';
-import { languages as preloadedLanguages } from 'mastodon/initial_state';
-import type { RootState } from 'mastodon/store';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-import { debouncedGuess } from '../util/language_detection';
-
-const messages = defineMessages({
-  changeLanguage: {
-    id: 'compose.language.change',
-    defaultMessage: 'Change language',
-  },
-  search: {
-    id: 'compose.language.search',
-    defaultMessage: 'Search languages...',
-  },
-  clear: { id: 'emoji_button.clear', defaultMessage: 'Clear' },
-});
-
-type Language = [string, string, string];
-
-const getFrequentlyUsedLanguages = createSelector(
-  [
-    (state: RootState) =>
-      (state.settings as ImmutableMap<string, unknown>).get(
-        'frequentlyUsedLanguages',
-        ImmutableMap(),
-      ) as ImmutableMap<string, number>,
-  ],
-  (languageCounters) =>
-    languageCounters
-      .keySeq()
-      .sort(
-        (a, b) =>
-          (languageCounters.get(a) ?? 0) - (languageCounters.get(b) ?? 0),
-      )
-      .reverse()
-      .toArray(),
-);
-
-const LanguageDropdownMenu: React.FC<{
-  value: string;
-  guess?: string;
-  onClose: () => void;
-  onChange: (arg0: string) => void;
-}> = ({ value, guess, onClose, onChange }) => {
-  const languages = preloadedLanguages as Language[];
-  const intl = useIntl();
-  const [searchValue, setSearchValue] = useState('');
-  const nodeRef = useRef<HTMLDivElement>(null);
-  const listNodeRef = useRef<HTMLDivElement>(null);
-
-  const frequentlyUsedLanguages = useAppSelector(getFrequentlyUsedLanguages);
-
-  const handleSearchChange = useCallback(
-    ({ target }: React.ChangeEvent<HTMLInputElement>) => {
-      setSearchValue(target.value);
-    },
-    [setSearchValue],
-  );
-
-  const handleClick = useCallback(
-    (e: React.MouseEvent | React.KeyboardEvent) => {
-      const value = e.currentTarget.getAttribute('data-index');
-
-      if (!value) {
-        return;
-      }
-
-      e.preventDefault();
-
-      onClose();
-      onChange(value);
-    },
-    [onClose, onChange],
-  );
-
-  const handleKeyDown = useCallback(
-    (e: React.KeyboardEvent) => {
-      if (!listNodeRef.current) {
-        return;
-      }
-
-      const index = Array.from(listNodeRef.current.childNodes).findIndex(
-        (node) => node === e.currentTarget,
-      );
-
-      let element = null;
-
-      switch (e.key) {
-        case 'Escape':
-          onClose();
-          break;
-        case ' ':
-        case 'Enter':
-          handleClick(e);
-          break;
-        case 'ArrowDown':
-          element =
-            listNodeRef.current.childNodes[index + 1] ??
-            listNodeRef.current.firstChild;
-          break;
-        case 'ArrowUp':
-          element =
-            listNodeRef.current.childNodes[index - 1] ??
-            listNodeRef.current.lastChild;
-          break;
-        case 'Tab':
-          if (e.shiftKey) {
-            element =
-              listNodeRef.current.childNodes[index - 1] ??
-              listNodeRef.current.lastChild;
-          } else {
-            element =
-              listNodeRef.current.childNodes[index + 1] ??
-              listNodeRef.current.firstChild;
-          }
-          break;
-        case 'Home':
-          element = listNodeRef.current.firstChild;
-          break;
-        case 'End':
-          element = listNodeRef.current.lastChild;
-          break;
-      }
-
-      if (element && element instanceof HTMLElement) {
-        element.focus();
-        e.preventDefault();
-        e.stopPropagation();
-      }
-    },
-    [onClose, handleClick],
-  );
-
-  const handleSearchKeyDown = useCallback(
-    (e: React.KeyboardEvent) => {
-      let element = null;
-
-      if (!listNodeRef.current) {
-        return;
-      }
-
-      switch (e.key) {
-        case 'Tab':
-        case 'ArrowDown':
-          element = listNodeRef.current.firstChild;
-
-          if (element && element instanceof HTMLElement) {
-            element.focus();
-            e.preventDefault();
-            e.stopPropagation();
-          }
-
-          break;
-        case 'Enter':
-          element = listNodeRef.current.firstChild;
-
-          if (element && element instanceof HTMLElement) {
-            const value = element.getAttribute('data-index');
-
-            if (value) {
-              onChange(value);
-              onClose();
-            }
-          }
-          break;
-        case 'Escape':
-          if (searchValue !== '') {
-            e.preventDefault();
-            setSearchValue('');
-          }
-
-          break;
-      }
-    },
-    [setSearchValue, onChange, onClose, searchValue],
-  );
-
-  const handleClear = useCallback(() => {
-    setSearchValue('');
-  }, [setSearchValue]);
-
-  const isSearching = searchValue !== '';
-
-  useEffect(() => {
-    const handleDocumentClick = (e: MouseEvent) => {
-      if (
-        nodeRef.current &&
-        e.target instanceof HTMLElement &&
-        !nodeRef.current.contains(e.target)
-      ) {
-        onClose();
-        e.stopPropagation();
-      }
-    };
-
-    document.addEventListener('click', handleDocumentClick, { capture: true });
-
-    // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
-    // to wait for a frame before focusing
-    requestAnimationFrame(() => {
-      if (nodeRef.current) {
-        const element = nodeRef.current.querySelector<HTMLInputElement>(
-          'input[type="search"]',
-        );
-        if (element) element.focus();
-      }
-    });
-
-    return () => {
-      document.removeEventListener('click', handleDocumentClick);
-    };
-  }, [onClose]);
-
-  const results = useMemo(() => {
-    if (searchValue === '') {
-      return [...languages].sort((a, b) => {
-        if (guess && a[0] === guess) {
-          // Push guessed language higher than current selection
-          return -1;
-        } else if (guess && b[0] === guess) {
-          return 1;
-        } else if (a[0] === value) {
-          // Push current selection to the top of the list
-          return -1;
-        } else if (b[0] === value) {
-          return 1;
-        } else {
-          // Sort according to frequently used languages
-
-          const indexOfA = frequentlyUsedLanguages.indexOf(a[0]);
-          const indexOfB = frequentlyUsedLanguages.indexOf(b[0]);
-
-          return (
-            (indexOfA > -1 ? indexOfA : Infinity) -
-            (indexOfB > -1 ? indexOfB : Infinity)
-          );
-        }
-      });
-    }
-
-    return fuzzysort
-      .go(searchValue, languages, {
-        keys: ['0', '1', '2'],
-        limit: 5,
-        threshold: -10000,
-      })
-      .map((result) => result.obj);
-  }, [searchValue, languages, guess, frequentlyUsedLanguages, value]);
-
-  return (
-    <div ref={nodeRef}>
-      <div className='emoji-mart-search'>
-        <input
-          type='search'
-          value={searchValue}
-          onChange={handleSearchChange}
-          onKeyDown={handleSearchKeyDown}
-          placeholder={intl.formatMessage(messages.search)}
-        />
-        <button
-          type='button'
-          className='emoji-mart-search-icon'
-          disabled={!isSearching}
-          aria-label={intl.formatMessage(messages.clear)}
-          onClick={handleClear}
-        >
-          <Icon id='' icon={!isSearching ? SearchIcon : CancelIcon} />
-        </button>
-      </div>
-
-      <div
-        className='language-dropdown__dropdown__results emoji-mart-scroll'
-        role='listbox'
-        ref={listNodeRef}
-      >
-        {results.map((lang) => (
-          <div
-            key={lang[0]}
-            role='option'
-            tabIndex={0}
-            data-index={lang[0]}
-            className={classNames(
-              'language-dropdown__dropdown__results__item',
-              { active: lang[0] === value },
-            )}
-            aria-selected={lang[0] === value}
-            onClick={handleClick}
-            onKeyDown={handleKeyDown}
-          >
-            <span
-              className='language-dropdown__dropdown__results__item__native-name'
-              lang={lang[0]}
-            >
-              {lang[2]}
-            </span>{' '}
-            <span className='language-dropdown__dropdown__results__item__common-name'>
-              ({lang[1]})
-            </span>
-          </div>
-        ))}
-      </div>
-    </div>
-  );
-};
-
-export const LanguageDropdown: React.FC = () => {
-  const [open, setOpen] = useState(false);
-  const [placement, setPlacement] = useState<Placement | undefined>('bottom');
-  const [guess, setGuess] = useState('');
-  const activeElementRef = useRef<HTMLElement | null>(null);
-  const targetRef = useRef(null);
-
-  const intl = useIntl();
-
-  const dispatch = useAppDispatch();
-  const value = useAppSelector(
-    (state) => state.compose.get('language') as string,
-  );
-  const text = useAppSelector((state) => state.compose.get('text') as string);
-
-  const current =
-    (preloadedLanguages as Language[]).find((lang) => lang[0] === value) ?? [];
-
-  const handleMouseDown = useCallback(() => {
-    if (!open && document.activeElement instanceof HTMLElement) {
-      activeElementRef.current = document.activeElement;
-    }
-  }, [open]);
-
-  const handleToggle = useCallback(() => {
-    if (open && activeElementRef.current)
-      activeElementRef.current.focus({ preventScroll: true });
-
-    setOpen(!open);
-  }, [open, setOpen]);
-
-  const handleClose = useCallback(() => {
-    if (open && activeElementRef.current)
-      activeElementRef.current.focus({ preventScroll: true });
-
-    setOpen(false);
-  }, [open, setOpen]);
-
-  const handleChange = useCallback(
-    (value: string) => {
-      dispatch(changeComposeLanguage(value));
-    },
-    [dispatch],
-  );
-
-  const handleOverlayEnter = useCallback(
-    (state: Partial<State>) => {
-      setPlacement(state.placement);
-    },
-    [setPlacement],
-  );
-
-  useEffect(() => {
-    if (text.length > 20) {
-      debouncedGuess(text, setGuess);
-    } else {
-      debouncedGuess.cancel();
-      setGuess('');
-    }
-  }, [text, setGuess]);
-
-  return (
-    <div ref={targetRef}>
-      <button
-        type='button'
-        title={intl.formatMessage(messages.changeLanguage)}
-        aria-expanded={open}
-        onClick={handleToggle}
-        onMouseDown={handleMouseDown}
-        className={classNames('dropdown-button', {
-          active: open,
-          warning: guess !== '' && guess !== value,
-        })}
-      >
-        <Icon id='' icon={TranslateIcon} />
-        <span className='dropdown-button__label'>{current[2] ?? value}</span>
-      </button>
-
-      <Overlay
-        show={open}
-        offset={[5, 5]}
-        placement={placement}
-        flip
-        target={targetRef}
-        popperConfig={{ strategy: 'fixed', onFirstUpdate: handleOverlayEnter }}
-      >
-        {({ props, placement }) => (
-          <div {...props}>
-            <div
-              className={`dropdown-animation language-dropdown__dropdown ${placement}`}
-            >
-              <LanguageDropdownMenu
-                value={value}
-                guess={guess}
-                onClose={handleClose}
-                onChange={handleChange}
-              />
-            </div>
-          </div>
-        )}
-      </Overlay>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.jsx b/app/javascript/mastodon/features/compose/components/navigation_bar.jsx
index 38382b0ca0..ec5578eef3 100644
--- a/app/javascript/mastodon/features/compose/components/navigation_bar.jsx
+++ b/app/javascript/mastodon/features/compose/components/navigation_bar.jsx
@@ -6,7 +6,7 @@ import { useSelector, useDispatch } from 'react-redux';
 
 import CloseIcon from '@/material-icons/400-24px/close.svg?react';
 import { cancelReplyCompose } from 'mastodon/actions/compose';
-import { Account } from 'mastodon/components/account';
+import Account from 'mastodon/components/account';
 import { IconButton } from 'mastodon/components/icon_button';
 import { me } from 'mastodon/initial_state';
 
@@ -20,6 +20,7 @@ const messages = defineMessages({
 export const NavigationBar = () => {
   const dispatch = useDispatch();
   const intl = useIntl();
+  const account = useSelector(state => state.getIn(['accounts', me]));
   const isReplying = useSelector(state => !!state.getIn(['compose', 'in_reply_to']));
 
   const handleCancelClick = useCallback(() => {
@@ -28,7 +29,7 @@ export const NavigationBar = () => {
 
   return (
     <div className='navigation-bar'>
-      <Account id={me} minimal />
+      <Account account={account} minimal />
       {isReplying ? <IconButton title={intl.formatMessage(messages.cancel)} iconComponent={CloseIcon} onClick={handleCancelClick} /> : <ActionBar />}
     </div>
   );
diff --git a/app/javascript/mastodon/features/compose/components/poll_form.jsx b/app/javascript/mastodon/features/compose/components/poll_form.jsx
index e8d5331196..d2adc58cc0 100644
--- a/app/javascript/mastodon/features/compose/components/poll_form.jsx
+++ b/app/javascript/mastodon/features/compose/components/poll_form.jsx
@@ -25,7 +25,7 @@ const messages = defineMessages({
   minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
   hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
   days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
-  singleChoice: { id: 'compose_form.poll.single', defaultMessage: 'Single choice' },
+  singleChoice: { id: 'compose_form.poll.single', defaultMessage: 'Pick one' },
   multipleChoice: { id: 'compose_form.poll.multiple', defaultMessage: 'Multiple choice' },
 });
 
diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx
new file mode 100644
index 0000000000..466487a5f6
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/components/search.jsx
@@ -0,0 +1,405 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl, FormattedMessage, FormattedList } from 'react-intl';
+
+import classNames from 'classnames';
+import { withRouter } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+
+import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+import SearchIcon from '@/material-icons/400-24px/search.svg?react';
+import { Icon }  from 'mastodon/components/icon';
+import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
+import { domain, searchEnabled } from 'mastodon/initial_state';
+import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
+import { WithRouterPropTypes } from 'mastodon/utils/react_router';
+
+const messages = defineMessages({
+  placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
+  placeholderSignedIn: { id: 'search.search_or_paste', defaultMessage: 'Search or paste URL' },
+});
+
+const labelForRecentSearch = search => {
+  switch(search.get('type')) {
+  case 'account':
+    return `@${search.get('q')}`;
+  case 'hashtag':
+    return `#${search.get('q')}`;
+  default:
+    return search.get('q');
+  }
+};
+
+class Search extends PureComponent {
+  static propTypes = {
+    identity: identityContextPropShape,
+    value: PropTypes.string.isRequired,
+    recent: ImmutablePropTypes.orderedSet,
+    submitted: PropTypes.bool,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+    onOpenURL: PropTypes.func.isRequired,
+    onClickSearchResult: PropTypes.func.isRequired,
+    onForgetSearchResult: PropTypes.func.isRequired,
+    onClear: PropTypes.func.isRequired,
+    onShow: PropTypes.func.isRequired,
+    openInRoute: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    singleColumn: PropTypes.bool,
+    ...WithRouterPropTypes,
+  };
+
+  state = {
+    expanded: false,
+    selectedOption: -1,
+    options: [],
+  };
+
+  defaultOptions = [
+    { key: 'prompt-has', label: <><mark>has:</mark> <FormattedList type='disjunction' value={['media', 'poll', 'embed']} /></>, action: e => { e.preventDefault(); this._insertText('has:'); } },
+    { key: 'prompt-is', label: <><mark>is:</mark> <FormattedList type='disjunction' value={['reply', 'sensitive']} /></>, action: e => { e.preventDefault(); this._insertText('is:'); } },
+    { key: 'prompt-my', label: <><mark>my:</mark> <FormattedList type='disjunction' value={['favourited', 'bookmarked', 'boosted']} /></>, action: e => { e.preventDefault(); this._insertText('my:'); } },
+    { key: 'prompt-language', label: <><mark>language:</mark> <FormattedMessage id='search_popout.language_code' defaultMessage='ISO language code' /></>, action: e => { e.preventDefault(); this._insertText('language:'); } },
+    { key: 'prompt-from', label: <><mark>from:</mark> <FormattedMessage id='search_popout.user' defaultMessage='user' /></>, action: e => { e.preventDefault(); this._insertText('from:'); } },
+    { key: 'prompt-domain', label: <><mark>domain:</mark> <FormattedMessage id='search_popout.domain' defaultMessage='domain' /></>, action: e => { e.preventDefault(); this._insertText('domain:'); } },
+    { key: 'prompt-before', label: <><mark>before:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('before:'); } },
+    { key: 'prompt-during', label: <><mark>during:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('during:'); } },
+    { key: 'prompt-after', label: <><mark>after:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('after:'); } },
+    { key: 'prompt-in', label: <><mark>in:</mark> <FormattedList type='disjunction' value={['all', 'library', 'public']} /></>, action: e => { e.preventDefault(); this._insertText('in:'); } },
+    { key: 'prompt-order', label: <><mark>order:</mark> <FormattedList type='disjunction' value={['desc', 'asc']} /></>, action: e => { e.preventDefault(); this._insertText('order:'); } },
+  ];
+
+  setRef = c => {
+    this.searchForm = c;
+  };
+
+  handleChange = ({ target }) => {
+    const { onChange } = this.props;
+
+    onChange(target.value);
+
+    this._calculateOptions(target.value);
+  };
+
+  handleClear = e => {
+    const { value, submitted, onClear } = this.props;
+
+    e.preventDefault();
+
+    if (value.length > 0 || submitted) {
+      onClear();
+      this.setState({ options: [], selectedOption: -1 });
+    }
+  };
+
+  handleKeyDown = (e) => {
+    const { selectedOption } = this.state;
+    const options = searchEnabled ? this._getOptions().concat(this.defaultOptions) : this._getOptions();
+
+    switch(e.key) {
+    case 'Escape':
+      e.preventDefault();
+      this._unfocus();
+
+      break;
+    case 'ArrowDown':
+      e.preventDefault();
+
+      if (options.length > 0) {
+        this.setState({ selectedOption: Math.min(selectedOption + 1, options.length - 1) });
+      }
+
+      break;
+    case 'ArrowUp':
+      e.preventDefault();
+
+      if (options.length > 0) {
+        this.setState({ selectedOption: Math.max(selectedOption - 1, -1) });
+      }
+
+      break;
+    case 'Enter':
+      e.preventDefault();
+
+      if (selectedOption === -1) {
+        this._submit();
+      } else if (options.length > 0) {
+        options[selectedOption].action(e);
+      }
+
+      break;
+    case 'Delete':
+      if (selectedOption > -1 && options.length > 0) {
+        const search = options[selectedOption];
+
+        if (typeof search.forget === 'function') {
+          e.preventDefault();
+          search.forget(e);
+        }
+      }
+
+      break;
+    }
+  };
+
+  handleFocus = () => {
+    const { onShow, singleColumn } = this.props;
+
+    this.setState({ expanded: true, selectedOption: -1 });
+    onShow();
+
+    if (this.searchForm && !singleColumn) {
+      const { left, right } = this.searchForm.getBoundingClientRect();
+
+      if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) {
+        this.searchForm.scrollIntoView();
+      }
+    }
+  };
+
+  handleBlur = () => {
+    this.setState({ expanded: false, selectedOption: -1 });
+  };
+
+  handleHashtagClick = () => {
+    const { value, onClickSearchResult, history } = this.props;
+
+    const query = value.trim().replace(/^#/, '');
+
+    history.push(`/tags/${query}`);
+    onClickSearchResult(query, 'hashtag');
+    this._unfocus();
+  };
+
+  handleAccountClick = () => {
+    const { value, onClickSearchResult, history } = this.props;
+
+    const query = value.trim().replace(/^@/, '');
+
+    history.push(`/@${query}`);
+    onClickSearchResult(query, 'account');
+    this._unfocus();
+  };
+
+  handleURLClick = () => {
+    const { value, onOpenURL, history } = this.props;
+
+    onOpenURL(value, history);
+    this._unfocus();
+  };
+
+  handleStatusSearch = () => {
+    this._submit('statuses');
+  };
+
+  handleAccountSearch = () => {
+    this._submit('accounts');
+  };
+
+  handleRecentSearchClick = search => {
+    const { onChange, history } = this.props;
+
+    if (search.get('type') === 'account') {
+      history.push(`/@${search.get('q')}`);
+    } else if (search.get('type') === 'hashtag') {
+      history.push(`/tags/${search.get('q')}`);
+    } else {
+      onChange(search.get('q'));
+      this._submit(search.get('type'));
+    }
+
+    this._unfocus();
+  };
+
+  handleForgetRecentSearchClick = search => {
+    const { onForgetSearchResult } = this.props;
+
+    onForgetSearchResult(search.get('q'));
+  };
+
+  _unfocus () {
+    document.querySelector('.ui').parentElement.focus();
+  }
+
+  _insertText (text) {
+    const { value, onChange } = this.props;
+
+    if (value === '') {
+      onChange(text);
+    } else if (value[value.length - 1] === ' ') {
+      onChange(`${value}${text}`);
+    } else {
+      onChange(`${value} ${text}`);
+    }
+  }
+
+  _submit (type) {
+    const { onSubmit, openInRoute, value, onClickSearchResult, history } = this.props;
+
+    onSubmit(type);
+
+    if (value) {
+      onClickSearchResult(value, type);
+    }
+
+    if (openInRoute) {
+      history.push('/search');
+    }
+
+    this._unfocus();
+  }
+
+  _getOptions () {
+    const { options } = this.state;
+
+    if (options.length > 0) {
+      return options;
+    }
+
+    const { recent } = this.props;
+
+    return recent.toArray().map(search => ({
+      key: `${search.get('type')}/${search.get('q')}`,
+
+      label: labelForRecentSearch(search),
+
+      action: () => this.handleRecentSearchClick(search),
+
+      forget: e => {
+        e.stopPropagation();
+        this.handleForgetRecentSearchClick(search);
+      },
+    }));
+  }
+
+  _calculateOptions (value) {
+    const { signedIn } = this.props.identity;
+    const trimmedValue = value.trim();
+    const options = [];
+
+    if (trimmedValue.length > 0) {
+      const couldBeURL = trimmedValue.startsWith('https://') && !trimmedValue.includes(' ');
+
+      if (couldBeURL) {
+        options.push({ key: 'open-url', label: <FormattedMessage id='search.quick_action.open_url' defaultMessage='Open URL in Mastodon' />, action: this.handleURLClick });
+      }
+
+      const couldBeHashtag = (trimmedValue.startsWith('#') && trimmedValue.length > 1) || trimmedValue.match(HASHTAG_REGEX);
+
+      if (couldBeHashtag) {
+        options.push({ key: 'go-to-hashtag', label: <FormattedMessage id='search.quick_action.go_to_hashtag' defaultMessage='Go to hashtag {x}' values={{ x: <mark>#{trimmedValue.replace(/^#/, '')}</mark> }} />, action: this.handleHashtagClick });
+      }
+
+      const couldBeUsername = trimmedValue.match(/^@?[a-z0-9_-]+(@[^\s]+)?$/i);
+
+      if (couldBeUsername) {
+        options.push({ key: 'go-to-account', label: <FormattedMessage id='search.quick_action.go_to_account' defaultMessage='Go to profile {x}' values={{ x: <mark>@{trimmedValue.replace(/^@/, '')}</mark> }} />, action: this.handleAccountClick });
+      }
+
+      const couldBeStatusSearch = searchEnabled;
+
+      if (couldBeStatusSearch && signedIn) {
+        options.push({ key: 'status-search', label: <FormattedMessage id='search.quick_action.status_search' defaultMessage='Posts matching {x}' values={{ x: <mark>{trimmedValue}</mark> }} />, action: this.handleStatusSearch });
+      }
+
+      const couldBeUserSearch = true;
+
+      if (couldBeUserSearch) {
+        options.push({ key: 'account-search', label: <FormattedMessage id='search.quick_action.account_search' defaultMessage='Profiles matching {x}' values={{ x: <mark>{trimmedValue}</mark> }} />, action: this.handleAccountSearch });
+      }
+    }
+
+    this.setState({ options });
+  }
+
+  render () {
+    const { intl, value, submitted, recent } = this.props;
+    const { expanded, options, selectedOption } = this.state;
+    const { signedIn } = this.props.identity;
+
+    const hasValue = value.length > 0 || submitted;
+
+    return (
+      <div className={classNames('search', { active: expanded })}>
+        <input
+          ref={this.setRef}
+          className='search__input'
+          type='text'
+          placeholder={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)}
+          aria-label={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)}
+          value={value}
+          onChange={this.handleChange}
+          onKeyDown={this.handleKeyDown}
+          onFocus={this.handleFocus}
+          onBlur={this.handleBlur}
+        />
+
+        <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
+          <Icon id='search' icon={SearchIcon} className={hasValue ? '' : 'active'} />
+          <Icon id='times-circle' icon={CancelIcon} className={hasValue ? 'active' : ''} aria-label={intl.formatMessage(messages.placeholder)} />
+        </div>
+
+        <div className='search__popout'>
+          {options.length === 0 && (
+            <>
+              <h4><FormattedMessage id='search_popout.recent' defaultMessage='Recent searches' /></h4>
+
+              <div className='search__popout__menu'>
+                {recent.size > 0 ? this._getOptions().map(({ label, key, action, forget }, i) => (
+                  <button key={key} onMouseDown={action} className={classNames('search__popout__menu__item search__popout__menu__item--flex', { selected: selectedOption === i })}>
+                    <span>{label}</span>
+                    <button className='icon-button' onMouseDown={forget}><Icon id='times' icon={CloseIcon} /></button>
+                  </button>
+                )) : (
+                  <div className='search__popout__menu__message'>
+                    <FormattedMessage id='search.no_recent_searches' defaultMessage='No recent searches' />
+                  </div>
+                )}
+              </div>
+            </>
+          )}
+
+          {options.length > 0 && (
+            <>
+              <h4><FormattedMessage id='search_popout.quick_actions' defaultMessage='Quick actions' /></h4>
+
+              <div className='search__popout__menu'>
+                {options.map(({ key, label, action }, i) => (
+                  <button key={key} onMouseDown={action} className={classNames('search__popout__menu__item', { selected: selectedOption === i })}>
+                    {label}
+                  </button>
+                ))}
+              </div>
+            </>
+          )}
+
+          <h4><FormattedMessage id='search_popout.options' defaultMessage='Search options' /></h4>
+
+          {searchEnabled && signedIn ? (
+            <div className='search__popout__menu'>
+              {this.defaultOptions.map(({ key, label, action }, i) => (
+                <button key={key} onMouseDown={action} className={classNames('search__popout__menu__item', { selected: selectedOption === ((options.length || recent.size) + i) })}>
+                  {label}
+                </button>
+              ))}
+            </div>
+          ) : (
+            <div className='search__popout__menu__message'>
+              {searchEnabled ? (
+                <FormattedMessage id='search_popout.full_text_search_logged_out_message' defaultMessage='Only available when logged in.' />
+              ) : (
+                <FormattedMessage id='search_popout.full_text_search_disabled_message' defaultMessage='Not available on {domain}.' values={{ domain }} />
+              )}
+            </div>
+          )}
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default withRouter(withIdentity(injectIntl(Search)));
diff --git a/app/javascript/mastodon/features/compose/components/search.tsx b/app/javascript/mastodon/features/compose/components/search.tsx
deleted file mode 100644
index 8c277605f2..0000000000
--- a/app/javascript/mastodon/features/compose/components/search.tsx
+++ /dev/null
@@ -1,635 +0,0 @@
-import { useCallback, useState, useRef } from 'react';
-
-import {
-  defineMessages,
-  useIntl,
-  FormattedMessage,
-  FormattedList,
-} from 'react-intl';
-
-import classNames from 'classnames';
-import { useHistory } from 'react-router-dom';
-
-import { isFulfilled } from '@reduxjs/toolkit';
-
-import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
-import CloseIcon from '@/material-icons/400-24px/close.svg?react';
-import SearchIcon from '@/material-icons/400-24px/search.svg?react';
-import {
-  clickSearchResult,
-  forgetSearchResult,
-  openURL,
-} from 'mastodon/actions/search';
-import { Icon } from 'mastodon/components/icon';
-import { useIdentity } from 'mastodon/identity_context';
-import { domain, searchEnabled } from 'mastodon/initial_state';
-import type { RecentSearch, SearchType } from 'mastodon/models/search';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
-
-const messages = defineMessages({
-  placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
-  placeholderSignedIn: {
-    id: 'search.search_or_paste',
-    defaultMessage: 'Search or paste URL',
-  },
-});
-
-const labelForRecentSearch = (search: RecentSearch) => {
-  switch (search.type) {
-    case 'account':
-      return `@${search.q}`;
-    case 'hashtag':
-      return `#${search.q}`;
-    default:
-      return search.q;
-  }
-};
-
-const unfocus = () => {
-  document.querySelector('.ui')?.parentElement?.focus();
-};
-
-interface SearchOption {
-  key: string;
-  label: React.ReactNode;
-  action: (e: React.MouseEvent | React.KeyboardEvent) => void;
-  forget?: (e: React.MouseEvent | React.KeyboardEvent) => void;
-}
-
-export const Search: React.FC<{
-  singleColumn: boolean;
-  initialValue?: string;
-}> = ({ singleColumn, initialValue }) => {
-  const intl = useIntl();
-  const recent = useAppSelector((state) => state.search.recent);
-  const { signedIn } = useIdentity();
-  const dispatch = useAppDispatch();
-  const history = useHistory();
-  const searchInputRef = useRef<HTMLInputElement>(null);
-  const [value, setValue] = useState(initialValue ?? '');
-  const hasValue = value.length > 0;
-  const [expanded, setExpanded] = useState(false);
-  const [selectedOption, setSelectedOption] = useState(-1);
-  const [quickActions, setQuickActions] = useState<SearchOption[]>([]);
-  const searchOptions: SearchOption[] = [];
-
-  if (searchEnabled) {
-    searchOptions.push(
-      {
-        key: 'prompt-has',
-        label: (
-          <>
-            <mark>has:</mark>{' '}
-            <FormattedList
-              type='disjunction'
-              value={['media', 'poll', 'embed']}
-            />
-          </>
-        ),
-        action: (e) => {
-          e.preventDefault();
-          insertText('has:');
-        },
-      },
-      {
-        key: 'prompt-is',
-        label: (
-          <>
-            <mark>is:</mark>{' '}
-            <FormattedList type='disjunction' value={['reply', 'sensitive']} />
-          </>
-        ),
-        action: (e) => {
-          e.preventDefault();
-          insertText('is:');
-        },
-      },
-      {
-        key: 'prompt-my',
-        label: (
-          <>
-            <mark>my:</mark>{' '}
-            <FormattedList type='disjunction' value={['fav', 'bm']} />
-          </>
-        ),
-        action: (e) => {
-          e.preventDefault();
-          insertText('my:');
-        },
-      },
-      {
-        key: 'prompt-language',
-        label: (
-          <>
-            <mark>language:</mark>{' '}
-            <FormattedMessage
-              id='search_popout.language_code'
-              defaultMessage='ISO language code'
-            />
-          </>
-        ),
-        action: (e) => {
-          e.preventDefault();
-          insertText('language:');
-        },
-      },
-      {
-        key: 'prompt-from',
-        label: (
-          <>
-            <mark>from:</mark>{' '}
-            <FormattedMessage id='search_popout.user' defaultMessage='user' />
-          </>
-        ),
-        action: (e) => {
-          e.preventDefault();
-          insertText('from:');
-        },
-      },
-      {
-        key: 'prompt-domain',
-        label: (
-          <>
-            <mark>domain:</mark>{' '}
-            <FormattedMessage
-              id='search_popout.domain'
-              defaultMessage='user domain'
-            />
-          </>
-        ),
-        action: (e) => {
-          e.preventDefault();
-          insertText('domain:');
-        },
-      },
-      {
-        key: 'prompt-before',
-        label: (
-          <>
-            <mark>before:</mark>{' '}
-            <FormattedMessage
-              id='search_popout.specific_date'
-              defaultMessage='specific date'
-            />
-          </>
-        ),
-        action: (e) => {
-          e.preventDefault();
-          insertText('before:');
-        },
-      },
-      {
-        key: 'prompt-during',
-        label: (
-          <>
-            <mark>during:</mark>{' '}
-            <FormattedMessage
-              id='search_popout.specific_date'
-              defaultMessage='specific date'
-            />
-          </>
-        ),
-        action: (e) => {
-          e.preventDefault();
-          insertText('during:');
-        },
-      },
-      {
-        key: 'prompt-after',
-        label: (
-          <>
-            <mark>after:</mark>{' '}
-            <FormattedMessage
-              id='search_popout.specific_date'
-              defaultMessage='specific date'
-            />
-          </>
-        ),
-        action: (e) => {
-          e.preventDefault();
-          insertText('after:');
-        },
-      },
-      {
-        key: 'prompt-in',
-        label: (
-          <>
-            <mark>in:</mark>{' '}
-            <FormattedList
-              type='disjunction'
-              value={['all', 'library', 'public']}
-            />
-          </>
-        ),
-        action: (e) => {
-          e.preventDefault();
-          insertText('in:');
-        },
-      },
-      {
-        key: 'prompt-order',
-        label: (
-          <>
-            <mark>order:</mark>{' '}
-            <FormattedList type='disjunction' value={['desc', 'asc']} />
-          </>
-        ),
-        action: (e) => {
-          e.preventDefault();
-          insertText('order:');
-        },
-      },
-    );
-  }
-
-  const recentOptions: SearchOption[] = recent.map((search) => ({
-    key: `${search.type}/${search.q}`,
-    label: labelForRecentSearch(search),
-    action: () => {
-      setValue(search.q);
-
-      if (search.type === 'account') {
-        history.push(`/@${search.q}`);
-      } else if (search.type === 'hashtag') {
-        history.push(`/tags/${search.q}`);
-      } else {
-        const queryParams = new URLSearchParams({ q: search.q });
-        if (search.type) queryParams.set('type', search.type);
-        history.push({ pathname: '/search', search: queryParams.toString() });
-      }
-
-      unfocus();
-    },
-    forget: (e) => {
-      e.stopPropagation();
-      void dispatch(forgetSearchResult(search.q));
-    },
-  }));
-
-  const navigableOptions = hasValue
-    ? quickActions.concat(searchOptions)
-    : recentOptions.concat(quickActions, searchOptions);
-
-  const insertText = (text: string) => {
-    setValue((currentValue) => {
-      if (currentValue === '') {
-        return text;
-      } else if (currentValue.endsWith(' ')) {
-        return `${currentValue}${text}`;
-      } else {
-        return `${currentValue} ${text}`;
-      }
-    });
-  };
-
-  const submit = useCallback(
-    (q: string, type?: SearchType) => {
-      void dispatch(clickSearchResult({ q, type }));
-      const queryParams = new URLSearchParams({ q });
-      if (type) queryParams.set('type', type);
-      history.push({ pathname: '/search', search: queryParams.toString() });
-      unfocus();
-    },
-    [dispatch, history],
-  );
-
-  const handleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setValue(value);
-
-      const trimmedValue = value.trim();
-      const newQuickActions = [];
-
-      if (trimmedValue.length > 0) {
-        const couldBeURL =
-          trimmedValue.startsWith('https://') && !trimmedValue.includes(' ');
-
-        if (couldBeURL) {
-          newQuickActions.push({
-            key: 'open-url',
-            label: (
-              <FormattedMessage
-                id='search.quick_action.open_url'
-                defaultMessage='Open URL in Mastodon'
-              />
-            ),
-            action: async () => {
-              const result = await dispatch(openURL({ url: trimmedValue }));
-
-              if (isFulfilled(result)) {
-                if (result.payload.accounts[0]) {
-                  history.push(`/@${result.payload.accounts[0].acct}`);
-                } else if (result.payload.statuses[0]) {
-                  history.push(
-                    `/@${result.payload.statuses[0].account.acct}/${result.payload.statuses[0].id}`,
-                  );
-                }
-              }
-
-              unfocus();
-            },
-          });
-        }
-
-        const couldBeHashtag =
-          (trimmedValue.startsWith('#') && trimmedValue.length > 1) ||
-          trimmedValue.match(HASHTAG_REGEX);
-
-        if (couldBeHashtag) {
-          newQuickActions.push({
-            key: 'go-to-hashtag',
-            label: (
-              <FormattedMessage
-                id='search.quick_action.go_to_hashtag'
-                defaultMessage='Go to hashtag {x}'
-                values={{ x: <mark>#{trimmedValue.replace(/^#/, '')}</mark> }}
-              />
-            ),
-            action: () => {
-              const query = trimmedValue.replace(/^#/, '');
-              history.push(`/tags/${query}`);
-              void dispatch(clickSearchResult({ q: query, type: 'hashtag' }));
-              unfocus();
-            },
-          });
-        }
-
-        const couldBeUsername = /^@?[a-z0-9_-]+(@[^\s]+)?$/i.exec(trimmedValue);
-
-        if (couldBeUsername) {
-          newQuickActions.push({
-            key: 'go-to-account',
-            label: (
-              <FormattedMessage
-                id='search.quick_action.go_to_account'
-                defaultMessage='Go to profile {x}'
-                values={{ x: <mark>@{trimmedValue.replace(/^@/, '')}</mark> }}
-              />
-            ),
-            action: () => {
-              const query = trimmedValue.replace(/^@/, '');
-              history.push(`/@${query}`);
-              void dispatch(clickSearchResult({ q: query, type: 'account' }));
-              unfocus();
-            },
-          });
-        }
-
-        const couldBeStatusSearch = searchEnabled;
-
-        if (couldBeStatusSearch && signedIn) {
-          newQuickActions.push({
-            key: 'status-search',
-            label: (
-              <FormattedMessage
-                id='search.quick_action.status_search'
-                defaultMessage='Posts matching {x}'
-                values={{ x: <mark>{trimmedValue}</mark> }}
-              />
-            ),
-            action: () => {
-              submit(trimmedValue, 'statuses');
-            },
-          });
-        }
-
-        newQuickActions.push({
-          key: 'account-search',
-          label: (
-            <FormattedMessage
-              id='search.quick_action.account_search'
-              defaultMessage='Profiles matching {x}'
-              values={{ x: <mark>{trimmedValue}</mark> }}
-            />
-          ),
-          action: () => {
-            submit(trimmedValue, 'accounts');
-          },
-        });
-      }
-
-      setQuickActions(newQuickActions);
-    },
-    [dispatch, history, signedIn, setValue, setQuickActions, submit],
-  );
-
-  const handleClear = useCallback(() => {
-    setValue('');
-    setQuickActions([]);
-    setSelectedOption(-1);
-  }, [setValue, setQuickActions, setSelectedOption]);
-
-  const handleKeyDown = useCallback(
-    (e: React.KeyboardEvent) => {
-      switch (e.key) {
-        case 'Escape':
-          e.preventDefault();
-          unfocus();
-
-          break;
-        case 'ArrowDown':
-          e.preventDefault();
-
-          if (navigableOptions.length > 0) {
-            setSelectedOption(
-              Math.min(selectedOption + 1, navigableOptions.length - 1),
-            );
-          }
-
-          break;
-        case 'ArrowUp':
-          e.preventDefault();
-
-          if (navigableOptions.length > 0) {
-            setSelectedOption(Math.max(selectedOption - 1, -1));
-          }
-
-          break;
-        case 'Enter':
-          e.preventDefault();
-
-          if (selectedOption === -1) {
-            submit(value);
-          } else if (navigableOptions.length > 0) {
-            navigableOptions[selectedOption]?.action(e);
-          }
-
-          break;
-        case 'Delete':
-          if (selectedOption > -1 && navigableOptions.length > 0) {
-            const search = navigableOptions[selectedOption];
-
-            if (typeof search?.forget === 'function') {
-              e.preventDefault();
-              search.forget(e);
-            }
-          }
-
-          break;
-      }
-    },
-    [navigableOptions, value, selectedOption, setSelectedOption, submit],
-  );
-
-  const handleFocus = useCallback(() => {
-    setExpanded(true);
-    setSelectedOption(-1);
-
-    if (searchInputRef.current && !singleColumn) {
-      const { left, right } = searchInputRef.current.getBoundingClientRect();
-
-      if (
-        left < 0 ||
-        right > (window.innerWidth || document.documentElement.clientWidth)
-      ) {
-        searchInputRef.current.scrollIntoView();
-      }
-    }
-  }, [setExpanded, setSelectedOption, singleColumn]);
-
-  const handleBlur = useCallback(() => {
-    setExpanded(false);
-    setSelectedOption(-1);
-  }, [setExpanded, setSelectedOption]);
-
-  return (
-    <form className={classNames('search', { active: expanded })}>
-      <input
-        ref={searchInputRef}
-        className='search__input'
-        type='text'
-        placeholder={intl.formatMessage(
-          signedIn ? messages.placeholderSignedIn : messages.placeholder,
-        )}
-        aria-label={intl.formatMessage(
-          signedIn ? messages.placeholderSignedIn : messages.placeholder,
-        )}
-        value={value}
-        onChange={handleChange}
-        onKeyDown={handleKeyDown}
-        onFocus={handleFocus}
-        onBlur={handleBlur}
-      />
-
-      <button type='button' className='search__icon' onClick={handleClear}>
-        <Icon
-          id='search'
-          icon={SearchIcon}
-          className={hasValue ? '' : 'active'}
-        />
-        <Icon
-          id='times-circle'
-          icon={CancelIcon}
-          className={hasValue ? 'active' : ''}
-          aria-label={intl.formatMessage(messages.placeholder)}
-        />
-      </button>
-
-      <div className='search__popout'>
-        {!hasValue && (
-          <>
-            <h4>
-              <FormattedMessage
-                id='search_popout.recent'
-                defaultMessage='Recent searches'
-              />
-            </h4>
-
-            <div className='search__popout__menu'>
-              {recentOptions.length > 0 ? (
-                recentOptions.map(({ label, key, action, forget }, i) => (
-                  <button
-                    key={key}
-                    onMouseDown={action}
-                    className={classNames(
-                      'search__popout__menu__item search__popout__menu__item--flex',
-                      { selected: selectedOption === i },
-                    )}
-                  >
-                    <span>{label}</span>
-                    <button className='icon-button' onMouseDown={forget}>
-                      <Icon id='times' icon={CloseIcon} />
-                    </button>
-                  </button>
-                ))
-              ) : (
-                <div className='search__popout__menu__message'>
-                  <FormattedMessage
-                    id='search.no_recent_searches'
-                    defaultMessage='No recent searches'
-                  />
-                </div>
-              )}
-            </div>
-          </>
-        )}
-
-        {quickActions.length > 0 && (
-          <>
-            <h4>
-              <FormattedMessage
-                id='search_popout.quick_actions'
-                defaultMessage='Quick actions'
-              />
-            </h4>
-
-            <div className='search__popout__menu'>
-              {quickActions.map(({ key, label, action }, i) => (
-                <button
-                  key={key}
-                  onMouseDown={action}
-                  className={classNames('search__popout__menu__item', {
-                    selected: selectedOption === i,
-                  })}
-                >
-                  {label}
-                </button>
-              ))}
-            </div>
-          </>
-        )}
-
-        <h4>
-          <FormattedMessage
-            id='search_popout.options'
-            defaultMessage='Search options'
-          />
-        </h4>
-
-        {searchEnabled && signedIn ? (
-          <div className='search__popout__menu'>
-            {searchOptions.map(({ key, label, action }, i) => (
-              <button
-                key={key}
-                onMouseDown={action}
-                className={classNames('search__popout__menu__item', {
-                  selected:
-                    selectedOption ===
-                    (quickActions.length || recent.length) + i,
-                })}
-              >
-                {label}
-              </button>
-            ))}
-          </div>
-        ) : (
-          <div className='search__popout__menu__message'>
-            {searchEnabled ? (
-              <FormattedMessage
-                id='search_popout.full_text_search_logged_out_message'
-                defaultMessage='Only available when logged in.'
-              />
-            ) : (
-              <FormattedMessage
-                id='search_popout.full_text_search_disabled_message'
-                defaultMessage='Not available on {domain}.'
-                values={{ domain }}
-              />
-            )}
-          </div>
-        )}
-      </div>
-    </form>
-  );
-};
diff --git a/app/javascript/mastodon/features/compose/components/search_results.jsx b/app/javascript/mastodon/features/compose/components/search_results.jsx
new file mode 100644
index 0000000000..6a482c8ec2
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/components/search_results.jsx
@@ -0,0 +1,93 @@
+import { useCallback } from 'react';
+
+import { FormattedMessage } from 'react-intl';
+
+import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react';
+import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
+import TagIcon from '@/material-icons/400-24px/tag.svg?react';
+import { expandSearch } from 'mastodon/actions/search';
+import { Icon }  from 'mastodon/components/icon';
+import { LoadMore } from 'mastodon/components/load_more';
+import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import { SearchSection } from 'mastodon/features/explore/components/search_section';
+import { useAppDispatch, useAppSelector } from 'mastodon/store';
+
+import { ImmutableHashtag as Hashtag } from '../../../components/hashtag';
+import AccountContainer from '../../../containers/account_container';
+import StatusContainer from '../../../containers/status_container';
+
+const INITIAL_PAGE_LIMIT = 10;
+
+const withoutLastResult = list => {
+  if (list.size > INITIAL_PAGE_LIMIT && list.size % INITIAL_PAGE_LIMIT === 1) {
+    return list.skipLast(1);
+  } else {
+    return list;
+  }
+};
+
+export const SearchResults = () => {
+  const results = useAppSelector((state) => state.getIn(['search', 'results']));
+  const isLoading = useAppSelector((state) => state.getIn(['search', 'isLoading']));
+
+  const dispatch = useAppDispatch();
+
+  const handleLoadMoreAccounts = useCallback(() => {
+    dispatch(expandSearch('accounts'));
+  }, [dispatch]);
+
+  const handleLoadMoreStatuses = useCallback(() => {
+    dispatch(expandSearch('statuses'));
+  }, [dispatch]);
+
+  const handleLoadMoreHashtags = useCallback(() => {
+    dispatch(expandSearch('hashtags'));
+  }, [dispatch]);
+
+  let accounts, statuses, hashtags;
+
+  if (results.get('accounts') && results.get('accounts').size > 0) {
+    accounts = (
+      <SearchSection title={<><Icon id='users' icon={PeopleIcon} /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>}>
+        {withoutLastResult(results.get('accounts')).map(accountId => <AccountContainer key={accountId} id={accountId} />)}
+        {(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={handleLoadMoreAccounts} />}
+      </SearchSection>
+    );
+  }
+
+  if (results.get('hashtags') && results.get('hashtags').size > 0) {
+    hashtags = (
+      <SearchSection title={<><Icon id='hashtag' icon={TagIcon} /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></>}>
+        {withoutLastResult(results.get('hashtags')).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
+        {(results.get('hashtags').size > INITIAL_PAGE_LIMIT && results.get('hashtags').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={handleLoadMoreHashtags} />}
+      </SearchSection>
+    );
+  }
+
+  if (results.get('statuses') && results.get('statuses').size > 0) {
+    statuses = (
+      <SearchSection title={<><Icon id='quote-right' icon={FindInPageIcon} /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></>}>
+        {withoutLastResult(results.get('statuses')).map(statusId => <StatusContainer key={statusId} id={statusId} />)}
+        {(results.get('statuses').size > INITIAL_PAGE_LIMIT && results.get('statuses').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={handleLoadMoreStatuses} />}
+      </SearchSection>
+    );
+  }
+
+  return (
+    <div className='search-results'>
+      {!accounts && !hashtags && !statuses && (
+        isLoading ? (
+          <LoadingIndicator />
+        ) : (
+          <div className='empty-column-indicator'>
+            <FormattedMessage id='search_results.nothing_found' defaultMessage='Could not find anything for these search terms' />
+          </div>
+        )
+      )}
+      {accounts}
+      {hashtags}
+      {statuses}
+    </div>
+  );
+
+};
diff --git a/app/javascript/mastodon/features/compose/components/upload.tsx b/app/javascript/mastodon/features/compose/components/upload.tsx
index 67c8ee5e9a..3a24b28291 100644
--- a/app/javascript/mastodon/features/compose/components/upload.tsx
+++ b/app/javascript/mastodon/features/compose/components/upload.tsx
@@ -4,16 +4,16 @@ import { FormattedMessage } from 'react-intl';
 
 import classNames from 'classnames';
 
-import type { Map as ImmutableMap, List as ImmutableList } from 'immutable';
-
 import { useSortable } from '@dnd-kit/sortable';
 import { CSS } from '@dnd-kit/utilities';
 
 import CloseIcon from '@/material-icons/400-20px/close.svg?react';
 import EditIcon from '@/material-icons/400-24px/edit.svg?react';
 import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
-import { undoUploadCompose } from 'mastodon/actions/compose';
-import { openModal } from 'mastodon/actions/modal';
+import {
+  undoUploadCompose,
+  initMediaEditModal,
+} from 'mastodon/actions/compose';
 import { Blurhash } from 'mastodon/components/blurhash';
 import { Icon } from 'mastodon/components/icon';
 import type { MediaAttachment } from 'mastodon/models/media_attachment';
@@ -27,15 +27,16 @@ export const Upload: React.FC<{
   wide?: boolean;
 }> = ({ id, dragging, overlay, tall, wide }) => {
   const dispatch = useAppDispatch();
-  const media = useAppSelector((state) =>
-    (
-      (state.compose as ImmutableMap<string, unknown>).get(
-        'media_attachments',
-      ) as ImmutableList<MediaAttachment>
-    ).find((item) => item.get('id') === id),
+  const media = useAppSelector(
+    (state) =>
+      state.compose // eslint-disable-line @typescript-eslint/no-unsafe-call
+        .get('media_attachments') // eslint-disable-line @typescript-eslint/no-unsafe-member-access
+        .find((item: MediaAttachment) => item.get('id') === id) as  // eslint-disable-line @typescript-eslint/no-unsafe-member-access
+        | MediaAttachment
+        | undefined,
   );
   const sensitive = useAppSelector(
-    (state) => state.compose.get('spoiler') as boolean,
+    (state) => state.compose.get('spoiler') as boolean, // eslint-disable-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
   );
 
   const handleUndoClick = useCallback(() => {
@@ -43,9 +44,7 @@ export const Upload: React.FC<{
   }, [dispatch, id]);
 
   const handleFocalPointClick = useCallback(() => {
-    dispatch(
-      openModal({ modalType: 'FOCAL_POINT', modalProps: { mediaId: id } }),
-    );
+    dispatch(initMediaEditModal(id));
   }, [dispatch, id]);
 
   const { attributes, listeners, setNodeRef, transform, transition } =
diff --git a/app/javascript/mastodon/features/compose/components/upload_form.tsx b/app/javascript/mastodon/features/compose/components/upload_form.tsx
index 81e361e35e..1c01c2bd9c 100644
--- a/app/javascript/mastodon/features/compose/components/upload_form.tsx
+++ b/app/javascript/mastodon/features/compose/components/upload_form.tsx
@@ -2,11 +2,7 @@ import { useState, useCallback, useMemo } from 'react';
 
 import { useIntl, defineMessages } from 'react-intl';
 
-import type {
-  List,
-  Map as ImmutableMap,
-  List as ImmutableList,
-} from 'immutable';
+import type { List } from 'immutable';
 
 import type {
   DragStartEvent,
@@ -67,20 +63,18 @@ export const UploadForm: React.FC = () => {
   const intl = useIntl();
   const mediaIds = useAppSelector(
     (state) =>
-      (
-        (state.compose as ImmutableMap<string, unknown>).get(
-          'media_attachments',
-        ) as ImmutableList<MediaAttachment>
-      ).map((item: MediaAttachment) => item.get('id')) as List<string>,
+      state.compose // eslint-disable-line @typescript-eslint/no-unsafe-call
+        .get('media_attachments') // eslint-disable-line @typescript-eslint/no-unsafe-member-access
+        .map((item: MediaAttachment) => item.get('id')) as List<string>, // eslint-disable-line @typescript-eslint/no-unsafe-member-access
   );
   const active = useAppSelector(
-    (state) => state.compose.get('is_uploading') as boolean,
+    (state) => state.compose.get('is_uploading') as boolean, // eslint-disable-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
   );
   const progress = useAppSelector(
-    (state) => state.compose.get('progress') as number,
+    (state) => state.compose.get('progress') as number, // eslint-disable-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
   );
   const isProcessing = useAppSelector(
-    (state) => state.compose.get('is_processing') as boolean,
+    (state) => state.compose.get('is_processing') as boolean, // eslint-disable-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
   );
   const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
   const sensors = useSensors(
diff --git a/app/javascript/mastodon/features/compose/components/upload_progress.jsx b/app/javascript/mastodon/features/compose/components/upload_progress.jsx
new file mode 100644
index 0000000000..fd0c8f4530
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/components/upload_progress.jsx
@@ -0,0 +1,48 @@
+import PropTypes from 'prop-types';
+
+import { FormattedMessage } from 'react-intl';
+
+import spring from 'react-motion/lib/spring';
+
+import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react';
+import { Icon }  from 'mastodon/components/icon';
+
+import Motion from '../../ui/util/optional_motion';
+
+export const UploadProgress = ({ active, progress, isProcessing }) => {
+  if (!active) {
+    return null;
+  }
+
+  let message;
+
+  if (isProcessing) {
+    message = <FormattedMessage id='upload_progress.processing' defaultMessage='Processing…' />;
+  } else {
+    message = <FormattedMessage id='upload_progress.label' defaultMessage='Uploading…' />;
+  }
+
+  return (
+    <div className='upload-progress'>
+      <Icon id='upload' icon={UploadFileIcon} />
+
+      <div className='upload-progress__message'>
+        {message}
+
+        <div className='upload-progress__backdrop'>
+          <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
+            {({ width }) =>
+              <div className='upload-progress__tracker' style={{ width: `${width}%` }} />
+            }
+          </Motion>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+UploadProgress.propTypes = {
+  active: PropTypes.bool,
+  progress: PropTypes.number,
+  isProcessing: PropTypes.bool,
+};
diff --git a/app/javascript/mastodon/features/compose/components/upload_progress.tsx b/app/javascript/mastodon/features/compose/components/upload_progress.tsx
deleted file mode 100644
index be15917784..0000000000
--- a/app/javascript/mastodon/features/compose/components/upload_progress.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-import { animated, useSpring } from '@react-spring/web';
-
-import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react';
-import { Icon } from 'mastodon/components/icon';
-import { reduceMotion } from 'mastodon/initial_state';
-
-interface UploadProgressProps {
-  active: boolean;
-  progress: number;
-  isProcessing?: boolean;
-}
-
-export const UploadProgress: React.FC<UploadProgressProps> = ({
-  active,
-  progress,
-  isProcessing = false,
-}) => {
-  const styles = useSpring({
-    from: { width: '0%' },
-    to: { width: `${progress}%` },
-    immediate: reduceMotion || !active, // If this is not active, update the UI immediately.
-  });
-  if (!active) {
-    return null;
-  }
-
-  return (
-    <div className='upload-progress'>
-      <Icon id='upload' icon={UploadFileIcon} />
-
-      <div className='upload-progress__message'>
-        {isProcessing ? (
-          <FormattedMessage
-            id='upload_progress.processing'
-            defaultMessage='Processing…'
-          />
-        ) : (
-          <FormattedMessage
-            id='upload_progress.label'
-            defaultMessage='Uploading…'
-          />
-        )}
-
-        <div className='upload-progress__backdrop'>
-          <animated.div className='upload-progress__tracker' style={styles} />
-        </div>
-      </div>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/compose/components/warning.jsx b/app/javascript/mastodon/features/compose/components/warning.jsx
new file mode 100644
index 0000000000..c5babc30a5
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/components/warning.jsx
@@ -0,0 +1,28 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import spring from 'react-motion/lib/spring';
+
+import Motion from '../../ui/util/optional_motion';
+
+export default class Warning extends PureComponent {
+
+  static propTypes = {
+    message: PropTypes.node.isRequired,
+  };
+
+  render () {
+    const { message } = this.props;
+
+    return (
+      <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
+        {({ opacity, scaleX, scaleY }) => (
+          <div className='compose-form__warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
+            {message}
+          </div>
+        )}
+      </Motion>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/compose/components/warning.tsx b/app/javascript/mastodon/features/compose/components/warning.tsx
deleted file mode 100644
index 36b971b044..0000000000
--- a/app/javascript/mastodon/features/compose/components/warning.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-import { createSelector } from '@reduxjs/toolkit';
-
-import { animated, useSpring } from '@react-spring/web';
-
-import { me } from 'mastodon/initial_state';
-import { useAppSelector } from 'mastodon/store';
-import type { RootState } from 'mastodon/store';
-import { HASHTAG_PATTERN_REGEX } from 'mastodon/utils/hashtags';
-import { MENTION_PATTERN_REGEX } from 'mastodon/utils/mentions';
-
-const selector = createSelector(
-  (state: RootState) => state.compose.get('privacy') as string,
-  (state: RootState) => !!state.accounts.getIn([me, 'locked']),
-  (state: RootState) => state.compose.get('text') as string,
-  (state: RootState) => state.compose.get('searchability') as string,
-  (state: RootState) => state.compose.get('limited_scope') as string,
-  (privacy, locked, text, searchability, limited_scope) => ({
-    needsLockWarning: privacy === 'private' && !locked,
-    hashtagWarning:
-      !['public', 'public_unlisted', 'login'].includes(privacy) &&
-      (privacy !== 'unlisted' || searchability !== 'public') &&
-      HASHTAG_PATTERN_REGEX.test(text),
-    directMessageWarning: privacy === 'direct',
-    searchabilityWarning: searchability === 'limited',
-    mentionWarning:
-      ['mutual', 'circle', 'limited'].includes(privacy) &&
-      MENTION_PATTERN_REGEX.test(text),
-    limitedPostWarning:
-      ['mutual', 'circle'].includes(privacy) && !limited_scope,
-  }),
-);
-
-export const Warning = () => {
-  const {
-    needsLockWarning,
-    hashtagWarning,
-    directMessageWarning,
-    searchabilityWarning,
-    mentionWarning,
-    limitedPostWarning,
-  } = useAppSelector(selector);
-  if (needsLockWarning) {
-    return (
-      <WarningMessage>
-        <FormattedMessage
-          id='compose_form.lock_disclaimer'
-          defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.'
-          values={{
-            locked: (
-              <a href='/settings/profile'>
-                <FormattedMessage
-                  id='compose_form.lock_disclaimer.lock'
-                  defaultMessage='locked'
-                />
-              </a>
-            ),
-          }}
-        />
-      </WarningMessage>
-    );
-  }
-
-  if (hashtagWarning) {
-    return (
-      <WarningMessage>
-        <FormattedMessage
-          id='compose_form.hashtag_warning'
-          defaultMessage="This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag."
-        />
-      </WarningMessage>
-    );
-  }
-
-  if (directMessageWarning) {
-    return (
-      <WarningMessage>
-        <FormattedMessage
-          id='compose_form.encryption_warning'
-          defaultMessage='Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.'
-        />{' '}
-        <a href='/terms' target='_blank'>
-          <FormattedMessage
-            id='compose_form.direct_message_warning_learn_more'
-            defaultMessage='Learn more'
-          />
-        </a>
-      </WarningMessage>
-    );
-  }
-
-  if (searchabilityWarning) {
-    return (
-      <WarningMessage>
-        <FormattedMessage
-          id='compose_form.searchability_warning'
-          defaultMessage='Self only searchability is not available other mastodon servers. Others can search your post.'
-        />
-      </WarningMessage>
-    );
-  }
-
-  if (mentionWarning) {
-    return (
-      <WarningMessage>
-        <FormattedMessage
-          id='compose_form.mention_warning'
-          defaultMessage='When you add a mention to a limited post, the person you are mentioning can also see this post.'
-        />
-      </WarningMessage>
-    );
-  }
-
-  if (limitedPostWarning) {
-    return (
-      <WarningMessage>
-        <FormattedMessage
-          id='compose_form.limited_post_warning'
-          defaultMessage='Limited posts are NOT reached Misskey, normal Mastodon or so on.'
-        />
-      </WarningMessage>
-    );
-  }
-
-  return null;
-};
-
-export const WarningMessage: React.FC<React.PropsWithChildren> = ({
-  children,
-}) => {
-  const styles = useSpring({
-    from: {
-      opacity: 0,
-      transform: 'scale(0.85, 0.75)',
-    },
-    to: {
-      opacity: 1,
-      transform: 'scale(1, 1)',
-    },
-  });
-  return (
-    <animated.div className='compose-form__warning' style={styles}>
-      {children}
-    </animated.div>
-  );
-};
diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
index bb990f4a4a..4672ae5f68 100644
--- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js
+++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
@@ -11,9 +11,7 @@ import {
   insertExpirationCompose,
   insertFeaturedTagCompose,
   uploadCompose,
-} from 'mastodon/actions/compose';
-import { openModal } from 'mastodon/actions/modal';
-
+} from '../../../actions/compose';
 import ComposeForm from '../components/compose_form';
 
 const mapStateToProps = state => ({
@@ -31,7 +29,6 @@ const mapStateToProps = state => ({
   isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
   isUploading: state.getIn(['compose', 'is_uploading']),
   anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
-  missingAltText: state.getIn(['compose', 'media_attachments']).some(media => ['image', 'gifv'].includes(media.get('type')) && (media.get('description') ?? '').length === 0),
   isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
   lang: state.getIn(['compose', 'language']),
   circleId: state.getIn(['compose', 'circle_id']),
@@ -44,15 +41,8 @@ const mapDispatchToProps = (dispatch) => ({
     dispatch(changeCompose(text));
   },
 
-  onSubmit (missingAltText) {
-    if (missingAltText) {
-      dispatch(openModal({
-        modalType: 'CONFIRM_MISSING_ALT_TEXT',
-        modalProps: {},
-      }));
-    } else {
-      dispatch(submitCompose());
-    }
+  onSubmit () {
+    dispatch(submitCompose());
   },
 
   onClearSuggestions () {
diff --git a/app/javascript/mastodon/features/compose/containers/language_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/language_dropdown_container.js
new file mode 100644
index 0000000000..64c90afa92
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/containers/language_dropdown_container.js
@@ -0,0 +1,32 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { Map as ImmutableMap } from 'immutable';
+import { connect } from 'react-redux';
+
+
+import { changeComposeLanguage } from 'mastodon/actions/compose';
+
+import LanguageDropdown from '../components/language_dropdown';
+
+const getFrequentlyUsedLanguages = createSelector([
+  state => state.getIn(['settings', 'frequentlyUsedLanguages'], ImmutableMap()),
+], languageCounters => (
+  languageCounters.keySeq()
+    .sort((a, b) => languageCounters.get(a) - languageCounters.get(b))
+    .reverse()
+    .toArray()
+));
+
+const mapStateToProps = state => ({
+  frequentlyUsedLanguages: getFrequentlyUsedLanguages(state),
+  value: state.getIn(['compose', 'language']),
+});
+
+const mapDispatchToProps = dispatch => ({
+
+  onChange (value) {
+    dispatch(changeComposeLanguage(value));
+  },
+
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(LanguageDropdown);
diff --git a/app/javascript/mastodon/features/compose/containers/search_container.js b/app/javascript/mastodon/features/compose/containers/search_container.js
new file mode 100644
index 0000000000..616b91369c
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/containers/search_container.js
@@ -0,0 +1,59 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { connect } from 'react-redux';
+
+import {
+  changeSearch,
+  clearSearch,
+  submitSearch,
+  showSearch,
+  openURL,
+  clickSearchResult,
+  forgetSearchResult,
+} from 'mastodon/actions/search';
+
+import Search from '../components/search';
+
+const getRecentSearches = createSelector(
+  state => state.getIn(['search', 'recent']),
+  recent => recent.reverse(),
+);
+
+const mapStateToProps = state => ({
+  value: state.getIn(['search', 'value']),
+  submitted: state.getIn(['search', 'submitted']),
+  recent: getRecentSearches(state),
+});
+
+const mapDispatchToProps = dispatch => ({
+
+  onChange (value) {
+    dispatch(changeSearch(value));
+  },
+
+  onClear () {
+    dispatch(clearSearch());
+  },
+
+  onSubmit (type) {
+    dispatch(submitSearch(type));
+  },
+
+  onShow () {
+    dispatch(showSearch());
+  },
+
+  onOpenURL (q, routerHistory) {
+    dispatch(openURL(q, routerHistory));
+  },
+
+  onClickSearchResult (q, type) {
+    dispatch(clickSearchResult(q, type));
+  },
+
+  onForgetSearchResult (q) {
+    dispatch(forgetSearchResult(q));
+  },
+
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Search);
diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.jsx b/app/javascript/mastodon/features/compose/containers/warning_container.jsx
new file mode 100644
index 0000000000..bc74d1209b
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/containers/warning_container.jsx
@@ -0,0 +1,65 @@
+import PropTypes from 'prop-types';
+
+import { FormattedMessage } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import { me } from 'mastodon/initial_state';
+import { HASHTAG_PATTERN_REGEX } from 'mastodon/utils/hashtags';
+import { MENTION_PATTERN_REGEX } from 'mastodon/utils/mentions';
+
+import Warning from '../components/warning';
+
+const mapStateToProps = state => ({
+  needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
+  hashtagWarning: !['public', 'public_unlisted', 'login'].includes(state.getIn(['compose', 'privacy'])) && state.getIn(['compose', 'searchability']) !== 'public' && HASHTAG_PATTERN_REGEX.test(state.getIn(['compose', 'text'])),
+  directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct',
+  searchabilityWarning: state.getIn(['compose', 'searchability']) === 'limited',
+  mentionWarning: ['mutual', 'circle', 'limited'].includes(state.getIn(['compose', 'privacy'])) && MENTION_PATTERN_REGEX.test(state.getIn(['compose', 'text'])),
+  limitedPostWarning: ['mutual', 'circle'].includes(state.getIn(['compose', 'privacy'])) && !state.getIn(['compose', 'limited_scope']),
+});
+
+const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning, searchabilityWarning, mentionWarning, limitedPostWarning }) => {
+  if (needsLockWarning) {
+    return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <a href='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></a> }} />} />;
+  }
+
+  if (hashtagWarning) {
+    return <Warning message={<FormattedMessage id='compose_form.hashtag_warning' defaultMessage="This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag." />} />;
+  }
+
+  if (directMessageWarning) {
+    const message = (
+      <span>
+        <FormattedMessage id='compose_form.encryption_warning' defaultMessage='Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.' /> <a href='/terms' target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a>
+      </span>
+    );
+
+    return <Warning message={message} />;
+  }
+
+  if (searchabilityWarning) {
+    return <Warning message={<FormattedMessage id='compose_form.searchability_warning' defaultMessage='Self only searchability is not available other mastodon servers. Others can search your post.' />} />;
+  }
+
+  if (mentionWarning) {
+    return <Warning message={<FormattedMessage id='compose_form.mention_warning' defaultMessage='When you add a mention to a limited post, the person you are mentioning can also see this post.' />} />;
+  }
+
+  if (limitedPostWarning) {
+    return <Warning message={<FormattedMessage id='compose_form.limited_post_warning' defaultMessage='Limited posts are NOT reached Misskey, normal Mastodon or so on.' />} />;
+  }
+
+  return null;
+};
+
+WarningWrapper.propTypes = {
+  needsLockWarning: PropTypes.bool,
+  hashtagWarning: PropTypes.bool,
+  directMessageWarning: PropTypes.bool,
+  searchabilityWarning: PropTypes.bool,
+  mentionWarning: PropTypes.bool,
+  limitedPostWarning: PropTypes.bool,
+};
+
+export default connect(mapStateToProps)(WarningWrapper);
diff --git a/app/javascript/mastodon/features/compose/index.jsx b/app/javascript/mastodon/features/compose/index.jsx
index 660f08615b..3a96ab49c3 100644
--- a/app/javascript/mastodon/features/compose/index.jsx
+++ b/app/javascript/mastodon/features/compose/index.jsx
@@ -9,6 +9,8 @@ import { Link } from 'react-router-dom';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { connect } from 'react-redux';
 
+import spring from 'react-motion/lib/spring';
+
 import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
 import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
 import LogoutIcon from '@/material-icons/400-24px/logout.svg?react';
@@ -24,9 +26,11 @@ import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
 import { changeComposing, mountCompose, unmountCompose } from '../../actions/compose';
 import { mascot } from '../../initial_state';
 import { isMobile } from '../../is_mobile';
+import Motion from '../ui/util/optional_motion';
 
-import { Search } from './components/search';
+import { SearchResults } from './components/search_results';
 import ComposeFormContainer from './containers/compose_form_container';
+import SearchContainer from './containers/search_container';
 
 const messages = defineMessages({
   start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@@ -39,8 +43,9 @@ const messages = defineMessages({
   compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' },
 });
 
-const mapStateToProps = (state) => ({
+const mapStateToProps = (state, ownProps) => ({
   columns: state.getIn(['settings', 'columns']),
+  showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : false,
 });
 
 class Compose extends PureComponent {
@@ -49,6 +54,7 @@ class Compose extends PureComponent {
     dispatch: PropTypes.func.isRequired,
     columns: ImmutablePropTypes.list.isRequired,
     multiColumn: PropTypes.bool,
+    showSearch: PropTypes.bool,
     intl: PropTypes.object.isRequired,
   };
 
@@ -82,7 +88,7 @@ class Compose extends PureComponent {
   };
 
   render () {
-    const { multiColumn, intl } = this.props;
+    const { multiColumn, showSearch, intl } = this.props;
 
     if (multiColumn) {
       const { columns } = this.props;
@@ -107,7 +113,7 @@ class Compose extends PureComponent {
             <a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' icon={LogoutIcon} /></a>
           </nav>
 
-          {multiColumn && <Search /> }
+          {multiColumn && <SearchContainer /> }
 
           <div className='drawer__pager'>
             <div className='drawer__inner' onFocus={this.onFocus}>
@@ -117,6 +123,14 @@ class Compose extends PureComponent {
                 <img alt='' draggable='false' src={mascot || elephantUIPlane} />
               </div>
             </div>
+
+            <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
+              {({ x }) => (
+                <div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
+                  <SearchResults />
+                </div>
+              )}
+            </Motion>
           </div>
         </div>
       );
diff --git a/app/javascript/mastodon/features/compose/util/language_detection.js b/app/javascript/mastodon/features/compose/util/language_detection.js
deleted file mode 100644
index ed22a2bd9c..0000000000
--- a/app/javascript/mastodon/features/compose/util/language_detection.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import lande from 'lande';
-import { debounce } from 'lodash';
-
-import { urlRegex } from './url_regex';
-
-const ISO_639_MAP = {
-  afr: 'af', // Afrikaans
-  ara: 'ar', // Arabic
-  aze: 'az', // Azerbaijani
-  bel: 'be', // Belarusian
-  ben: 'bn', // Bengali
-  bul: 'bg', // Bulgarian
-  cat: 'ca', // Catalan
-  ces: 'cs', // Czech
-  ckb: 'ku', // Kurdish
-  cmn: 'zh', // Mandarin
-  dan: 'da', // Danish
-  deu: 'de', // German
-  ell: 'el', // Greek
-  eng: 'en', // English
-  est: 'et', // Estonian
-  eus: 'eu', // Basque
-  fin: 'fi', // Finnish
-  fra: 'fr', // French
-  hau: 'ha', // Hausa
-  heb: 'he', // Hebrew
-  hin: 'hi', // Hindi
-  hrv: 'hr', // Croatian
-  hun: 'hu', // Hungarian
-  hye: 'hy', // Armenian
-  ind: 'id', // Indonesian
-  isl: 'is', // Icelandic
-  ita: 'it', // Italian
-  jpn: 'ja', // Japanese
-  kat: 'ka', // Georgian
-  kaz: 'kk', // Kazakh
-  kor: 'ko', // Korean
-  lit: 'lt', // Lithuanian
-  mar: 'mr', // Marathi
-  mkd: 'mk', // Macedonian
-  nld: 'nl', // Dutch
-  nob: 'no', // Norwegian
-  pes: 'fa', // Persian
-  pol: 'pl', // Polish
-  por: 'pt', // Portuguese
-  ron: 'ro', // Romanian
-  run: 'rn', // Rundi
-  rus: 'ru', // Russian
-  slk: 'sk', // Slovak
-  spa: 'es', // Spanish
-  srp: 'sr', // Serbian
-  swe: 'sv', // Swedish
-  tgl: 'tl', // Tagalog
-  tur: 'tr', // Turkish
-  ukr: 'uk', // Ukrainian
-  vie: 'vi', // Vietnamese
-};
-
-const guessLanguage = (text) => {
-  text = text
-    .replace(urlRegex, '')
-    .replace(/(^|[^/\w])@(([a-z0-9_]+)@[a-z0-9.-]+[a-z0-9]+)/ig, '');
-
-  if (text.length > 20) {
-    const [lang, confidence] = lande(text)[0];
-  
-    if (confidence > 0.8)
-      return ISO_639_MAP[lang];
-  }
-
-  return '';
-};
-
-export const debouncedGuess = debounce((text, setGuess) => {
-  setGuess(guessLanguage(text));
-}, 500, { maxWait: 1500, leading: true, trailing: true });
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx
index c27cd3727f..0d154db1e1 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx
@@ -24,7 +24,7 @@ import AvatarComposite from 'mastodon/components/avatar_composite';
 import { IconButton } from 'mastodon/components/icon_button';
 import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
 import StatusContent from 'mastodon/components/status_content';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
+import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
 import { autoPlayGif } from 'mastodon/initial_state';
 import { makeGetStatus } from 'mastodon/selectors';
 
@@ -205,7 +205,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
             <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.reply)} icon='reply' iconComponent={ReplyIcon} onClick={handleReply} />
 
             <div className='status__action-bar-dropdown'>
-              <Dropdown
+              <DropdownMenuContainer
                 scrollKey={scrollKey}
                 status={lastStatus}
                 items={menu}
diff --git a/app/javascript/mastodon/features/directory/index.tsx b/app/javascript/mastodon/features/directory/index.tsx
index 1374f99235..d0e57600bb 100644
--- a/app/javascript/mastodon/features/directory/index.tsx
+++ b/app/javascript/mastodon/features/directory/index.tsx
@@ -15,16 +15,16 @@ import {
   changeColumnParams,
 } from 'mastodon/actions/columns';
 import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory';
-import { Column } from 'mastodon/components/column';
-import type { ColumnRef } from 'mastodon/components/column';
+import Column from 'mastodon/components/column';
 import { ColumnHeader } from 'mastodon/components/column_header';
 import { LoadMore } from 'mastodon/components/load_more';
 import { LoadingIndicator } from 'mastodon/components/loading_indicator';
 import { RadioButton } from 'mastodon/components/radio_button';
 import ScrollContainer from 'mastodon/containers/scroll_container';
-import { useSearchParam } from 'mastodon/hooks/useSearchParam';
 import { useAppDispatch, useAppSelector } from 'mastodon/store';
 
+import { useSearchParam } from '../../../hooks/useSearchParam';
+
 import { AccountCard } from './components/account_card';
 
 const messages = defineMessages({
@@ -49,7 +49,7 @@ export const Directory: React.FC<{
   const intl = useIntl();
   const dispatch = useAppDispatch();
 
-  const column = useRef<ColumnRef>(null);
+  const column = useRef<Column>(null);
 
   const [orderParam, setOrderParam] = useSearchParam('order');
   const [localParam, setLocalParam] = useSearchParam('local');
diff --git a/app/javascript/mastodon/features/domain_blocks/index.jsx b/app/javascript/mastodon/features/domain_blocks/index.jsx
new file mode 100644
index 0000000000..3656596806
--- /dev/null
+++ b/app/javascript/mastodon/features/domain_blocks/index.jsx
@@ -0,0 +1,85 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { debounce } from 'lodash';
+
+import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react';
+import { Domain } from 'mastodon/components/domain';
+
+import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
+import { LoadingIndicator } from '../../components/loading_indicator';
+import ScrollableList from '../../components/scrollable_list';
+import Column from '../ui/components/column';
+
+const messages = defineMessages({
+  heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
+});
+
+const mapStateToProps = state => ({
+  domains: state.getIn(['domain_lists', 'blocks', 'items']),
+  hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']),
+});
+
+class Blocks extends ImmutablePureComponent {
+
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    hasMore: PropTypes.bool,
+    domains: ImmutablePropTypes.orderedSet,
+    intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
+  };
+
+  UNSAFE_componentWillMount () {
+    this.props.dispatch(fetchDomainBlocks());
+  }
+
+  handleLoadMore = debounce(() => {
+    this.props.dispatch(expandDomainBlocks());
+  }, 300, { leading: true });
+
+  render () {
+    const { intl, domains, hasMore, multiColumn } = this.props;
+
+    if (!domains) {
+      return (
+        <Column>
+          <LoadingIndicator />
+        </Column>
+      );
+    }
+
+    const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />;
+
+    return (
+      <Column bindToDocument={!multiColumn} icon='ban' iconComponent={BlockIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
+        <ScrollableList
+          scrollKey='domain_blocks'
+          onLoadMore={this.handleLoadMore}
+          hasMore={hasMore}
+          emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
+        >
+          {domains.map(domain =>
+            <Domain key={domain} domain={domain} />,
+          )}
+        </ScrollableList>
+
+        <Helmet>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(Blocks));
diff --git a/app/javascript/mastodon/features/domain_blocks/index.tsx b/app/javascript/mastodon/features/domain_blocks/index.tsx
deleted file mode 100644
index 900aba4745..0000000000
--- a/app/javascript/mastodon/features/domain_blocks/index.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import { useEffect, useRef, useCallback, useState } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-
-import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react';
-import { apiGetDomainBlocks } from 'mastodon/api/domain_blocks';
-import { Column } from 'mastodon/components/column';
-import type { ColumnRef } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { Domain } from 'mastodon/components/domain';
-import ScrollableList from 'mastodon/components/scrollable_list';
-
-const messages = defineMessages({
-  heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
-});
-
-const Blocks: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
-  const intl = useIntl();
-  const [domains, setDomains] = useState<string[]>([]);
-  const [loading, setLoading] = useState(false);
-  const [next, setNext] = useState<string | undefined>();
-  const hasMore = !!next;
-  const columnRef = useRef<ColumnRef>(null);
-
-  useEffect(() => {
-    setLoading(true);
-
-    void apiGetDomainBlocks()
-      .then(({ domains, links }) => {
-        const next = links.refs.find((link) => link.rel === 'next');
-
-        setLoading(false);
-        setDomains(domains);
-        setNext(next?.uri);
-
-        return '';
-      })
-      .catch(() => {
-        setLoading(false);
-      });
-  }, [setLoading, setDomains, setNext]);
-
-  const handleLoadMore = useCallback(() => {
-    setLoading(true);
-
-    void apiGetDomainBlocks(next)
-      .then(({ domains, links }) => {
-        const next = links.refs.find((link) => link.rel === 'next');
-
-        setLoading(false);
-        setDomains((previousDomains) => [...previousDomains, ...domains]);
-        setNext(next?.uri);
-
-        return '';
-      })
-      .catch(() => {
-        setLoading(false);
-      });
-  }, [setLoading, setDomains, setNext, next]);
-
-  const handleHeaderClick = useCallback(() => {
-    columnRef.current?.scrollTop();
-  }, []);
-
-  const emptyMessage = (
-    <FormattedMessage
-      id='empty_column.domain_blocks'
-      defaultMessage='There are no blocked domains yet.'
-    />
-  );
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      ref={columnRef}
-      label={intl.formatMessage(messages.heading)}
-    >
-      <ColumnHeader
-        icon='ban'
-        iconComponent={BlockIcon}
-        title={intl.formatMessage(messages.heading)}
-        onClick={handleHeaderClick}
-        multiColumn={multiColumn}
-        showBackButton
-      />
-
-      <ScrollableList
-        scrollKey='domain_blocks'
-        onLoadMore={handleLoadMore}
-        hasMore={hasMore}
-        isLoading={loading}
-        showLoading={loading && domains.length === 0}
-        emptyMessage={emptyMessage}
-        trackScroll={!multiColumn}
-        bindToDocument={!multiColumn}
-      >
-        {domains.map((domain) => (
-          <Domain key={domain} domain={domain} />
-        ))}
-      </ScrollableList>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default Blocks;
diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.d.ts b/app/javascript/mastodon/features/emoji/emoji_compressed.d.ts
index 4e8c28a4ee..bd41e5b838 100644
--- a/app/javascript/mastodon/features/emoji/emoji_compressed.d.ts
+++ b/app/javascript/mastodon/features/emoji/emoji_compressed.d.ts
@@ -45,7 +45,6 @@ type EmojiCompressed = [
   Category[],
   Data['aliases'],
   EmojisWithoutShortCodes,
-  Data,
 ];
 
 /*
diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.js b/app/javascript/mastodon/features/emoji/emoji_compressed.js
index 10eb440a55..6c2a5963d6 100644
--- a/app/javascript/mastodon/features/emoji/emoji_compressed.js
+++ b/app/javascript/mastodon/features/emoji/emoji_compressed.js
@@ -1,3 +1,5 @@
+/* eslint-disable import/no-commonjs --
+   We need to use CommonJS here due to preval */
 // @preval
 // http://www.unicode.org/Public/emoji/5.0/emoji-test.txt
 // This file contains the compressed version of the emoji data from
@@ -7,91 +9,18 @@
 
 // This version comment should be bumped each time the emoji data is changed
 // to ensure that the prevaled file is regenerated by Babel
-// version: 3
+// version: 2
 
-// This json file contains the names of the categories.
-const emojiMart5LocalesData = require('@emoji-mart/data/i18n/en.json');
-const emojiMart5Data = require('@emoji-mart/data/sets/15/all.json');
+const { emojiIndex } = require('emoji-mart');
+let data = require('emoji-mart/data/all.json');
 const { uncompress: emojiMartUncompress } = require('emoji-mart/dist/utils/data');
-const _ = require('lodash');
-
 
 const emojiMap = require('./emoji_map.json');
-// This json file is downloaded from https://github.com/iamcal/emoji-data/
-// and is used to correct the sheet coordinates since we're using that repo's sheet
-const emojiSheetData = require('./emoji_sheet.json');
 const { unicodeToFilename } = require('./unicode_to_filename_s');
 const { unicodeToUnifiedName } = require('./unicode_to_unified_name_s');
 
-// Grabbed from `emoji_utils` to avoid circular dependency
-function unifiedToNative(unified) {
-  let unicodes = unified.split('-'),
-    codePoints = unicodes.map((u) => `0x${u}`);
-
-  return String.fromCodePoint(...codePoints);
-}
-
-let data = {
-  compressed: true,
-  categories: emojiMart5Data.categories.map(cat => {
-    return {
-      ...cat,
-      name: emojiMart5LocalesData.categories[cat.id]
-    };
-  }),
-  aliases: emojiMart5Data.aliases,
-  emojis: _(emojiMart5Data.emojis).values().map(emoji => {
-    let skin_variations = {};
-    const unified = emoji.skins[0].unified.toUpperCase();
-    const emojiFromRawData = emojiSheetData.find(e => e.unified === unified);
-
-    if (!emojiFromRawData) {
-      return undefined;
-    }
-
-    if (emoji.skins.length > 1) {
-      const [, ...nonDefaultSkins] = emoji.skins;
-      nonDefaultSkins.forEach(skin => {
-        const [matchingRawCodePoints,matchingRawEmoji] = Object.entries(emojiFromRawData.skin_variations).find((pair) => {
-          const [, value] = pair;
-          return value.unified.toLowerCase() === skin.unified;
-        });
-
-        if (matchingRawEmoji && matchingRawCodePoints) {
-          // At the time of writing, the json from `@emoji-mart/data` doesn't have data
-          // for emoji like `woman-heart-woman` with two different skin tones.
-          const skinToneCode = matchingRawCodePoints.split('-')[0];
-          skin_variations[skinToneCode] = {
-            unified: matchingRawEmoji.unified.toUpperCase(),
-            non_qualified: null,
-            sheet_x: matchingRawEmoji.sheet_x,
-            sheet_y: matchingRawEmoji.sheet_y,
-            has_img_twitter: true,
-            native: unifiedToNative(matchingRawEmoji.unified.toUpperCase())
-          };
-        }
-      });
-    }
-
-    return {
-      a: emoji.name,
-      b: unified,
-      c: undefined,
-      f: true,
-      j: [emoji.id, ...emoji.keywords],
-      k: [emojiFromRawData.sheet_x, emojiFromRawData.sheet_y],
-      m: emoji.emoticons?.[0],
-      l: emoji.emoticons,
-      o: emoji.version,
-      id: emoji.id,
-      skin_variations,
-      native: unifiedToNative(unified.toUpperCase())
-    };
-  }).compact().keyBy(e => e.id).mapValues(e => _.omit(e, 'id')).value()
-};
-
-if (data.compressed) {
-  emojiMartUncompress(data);
+if(data.compressed) {
+  data = emojiMartUncompress(data);
 }
 
 const emojiMartData = data;
@@ -103,10 +32,15 @@ const shortcodeMap   = {};
 const shortCodesToEmojiData = {};
 const emojisWithoutShortCodes = [];
 
-Object.keys(emojiMart5Data.emojis).forEach(key => {
-  let emoji = emojiMart5Data.emojis[key];
+Object.keys(emojiIndex.emojis).forEach(key => {
+  let emoji = emojiIndex.emojis[key];
 
-  shortcodeMap[emoji.skins[0].native] = emoji.id;
+  // Emojis with skin tone modifiers are stored like this
+  if (Object.hasOwn(emoji, '1')) {
+    emoji = emoji['1'];
+  }
+
+  shortcodeMap[emoji.native] = emoji.id;
 });
 
 const stripModifiers = unicode => {
@@ -150,9 +84,13 @@ Object.keys(emojiMap).forEach(key => {
   }
 });
 
-Object.keys(emojiMartData.emojis).forEach(key => {
-  let emoji = emojiMartData.emojis[key];
+Object.keys(emojiIndex.emojis).forEach(key => {
+  let emoji = emojiIndex.emojis[key];
 
+  // Emojis with skin tone modifiers are stored like this
+  if (Object.hasOwn(emoji, '1')) {
+    emoji = emoji['1'];
+  }
 
   const { native } = emoji;
   let { short_names, search, unified } = emojiMartData.emojis[key];
@@ -197,5 +135,4 @@ module.exports = JSON.parse(JSON.stringify([
   emojiMartData.categories,
   emojiMartData.aliases,
   emojisWithoutShortCodes,
-  emojiMartData
 ]));
diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts
index 8eeb457055..806a3f8927 100644
--- a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts
+++ b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts
@@ -8,15 +8,14 @@ import type { Search, ShortCodesToEmojiData } from './emoji_compressed';
 import emojiCompressed from './emoji_compressed';
 import { unicodeToUnifiedName } from './unicode_to_unified_name';
 
-type Emojis = Record<
-  NonNullable<keyof ShortCodesToEmojiData>,
-  {
+type Emojis = {
+  [key in NonNullable<keyof ShortCodesToEmojiData>]: {
     native: BaseEmoji['native'];
     search: Search;
     short_names: Emoji['short_names'];
     unified: Emoji['unified'];
-  }
->;
+  };
+};
 
 const [
   shortCodesToEmojiData,
diff --git a/app/javascript/mastodon/features/emoji/emoji_picker.js b/app/javascript/mastodon/features/emoji/emoji_picker.js
index 76cbe447a5..8725d39ecd 100644
--- a/app/javascript/mastodon/features/emoji/emoji_picker.js
+++ b/app/javascript/mastodon/features/emoji/emoji_picker.js
@@ -1,5 +1,5 @@
-import Emoji from 'emoji-mart/dist-es/components/emoji/nimble-emoji';
-import Picker from 'emoji-mart/dist-es/components/picker/nimble-picker';
+import Emoji from 'emoji-mart/dist-es/components/emoji/emoji';
+import Picker from 'emoji-mart/dist-es/components/picker/picker';
 
 export {
   Picker,
diff --git a/app/javascript/mastodon/features/emoji/emoji_sheet.json b/app/javascript/mastodon/features/emoji/emoji_sheet.json
deleted file mode 100644
index 455b906cec..0000000000
--- a/app/javascript/mastodon/features/emoji/emoji_sheet.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"unified":"0023-FE0F-20E3","sheet_x":0,"sheet_y":0,"skin_variations":{}},{"unified":"002A-FE0F-20E3","sheet_x":0,"sheet_y":1,"skin_variations":{}},{"unified":"0030-FE0F-20E3","sheet_x":0,"sheet_y":2,"skin_variations":{}},{"unified":"0031-FE0F-20E3","sheet_x":0,"sheet_y":3,"skin_variations":{}},{"unified":"0032-FE0F-20E3","sheet_x":0,"sheet_y":4,"skin_variations":{}},{"unified":"0033-FE0F-20E3","sheet_x":0,"sheet_y":5,"skin_variations":{}},{"unified":"0034-FE0F-20E3","sheet_x":0,"sheet_y":6,"skin_variations":{}},{"unified":"0035-FE0F-20E3","sheet_x":0,"sheet_y":7,"skin_variations":{}},{"unified":"0036-FE0F-20E3","sheet_x":0,"sheet_y":8,"skin_variations":{}},{"unified":"0037-FE0F-20E3","sheet_x":0,"sheet_y":9,"skin_variations":{}},{"unified":"0038-FE0F-20E3","sheet_x":0,"sheet_y":10,"skin_variations":{}},{"unified":"0039-FE0F-20E3","sheet_x":0,"sheet_y":11,"skin_variations":{}},{"unified":"00A9-FE0F","sheet_x":0,"sheet_y":12,"skin_variations":{}},{"unified":"00AE-FE0F","sheet_x":0,"sheet_y":13,"skin_variations":{}},{"unified":"1F004","sheet_x":0,"sheet_y":14,"skin_variations":{}},{"unified":"1F0CF","sheet_x":0,"sheet_y":15,"skin_variations":{}},{"unified":"1F170-FE0F","sheet_x":0,"sheet_y":16,"skin_variations":{}},{"unified":"1F171-FE0F","sheet_x":0,"sheet_y":17,"skin_variations":{}},{"unified":"1F17E-FE0F","sheet_x":0,"sheet_y":18,"skin_variations":{}},{"unified":"1F17F-FE0F","sheet_x":0,"sheet_y":19,"skin_variations":{}},{"unified":"1F18E","sheet_x":0,"sheet_y":20,"skin_variations":{}},{"unified":"1F191","sheet_x":0,"sheet_y":21,"skin_variations":{}},{"unified":"1F192","sheet_x":0,"sheet_y":22,"skin_variations":{}},{"unified":"1F193","sheet_x":0,"sheet_y":23,"skin_variations":{}},{"unified":"1F194","sheet_x":0,"sheet_y":24,"skin_variations":{}},{"unified":"1F195","sheet_x":0,"sheet_y":25,"skin_variations":{}},{"unified":"1F196","sheet_x":0,"sheet_y":26,"skin_variations":{}},{"unified":"1F197","sheet_x":0,"sheet_y":27,"skin_variations":{}},{"unified":"1F198","sheet_x":0,"sheet_y":28,"skin_variations":{}},{"unified":"1F199","sheet_x":0,"sheet_y":29,"skin_variations":{}},{"unified":"1F19A","sheet_x":0,"sheet_y":30,"skin_variations":{}},{"unified":"1F1E6-1F1E8","sheet_x":0,"sheet_y":31,"skin_variations":{}},{"unified":"1F1E6-1F1E9","sheet_x":0,"sheet_y":32,"skin_variations":{}},{"unified":"1F1E6-1F1EA","sheet_x":0,"sheet_y":33,"skin_variations":{}},{"unified":"1F1E6-1F1EB","sheet_x":0,"sheet_y":34,"skin_variations":{}},{"unified":"1F1E6-1F1EC","sheet_x":0,"sheet_y":35,"skin_variations":{}},{"unified":"1F1E6-1F1EE","sheet_x":0,"sheet_y":36,"skin_variations":{}},{"unified":"1F1E6-1F1F1","sheet_x":0,"sheet_y":37,"skin_variations":{}},{"unified":"1F1E6-1F1F2","sheet_x":0,"sheet_y":38,"skin_variations":{}},{"unified":"1F1E6-1F1F4","sheet_x":0,"sheet_y":39,"skin_variations":{}},{"unified":"1F1E6-1F1F6","sheet_x":0,"sheet_y":40,"skin_variations":{}},{"unified":"1F1E6-1F1F7","sheet_x":0,"sheet_y":41,"skin_variations":{}},{"unified":"1F1E6-1F1F8","sheet_x":0,"sheet_y":42,"skin_variations":{}},{"unified":"1F1E6-1F1F9","sheet_x":0,"sheet_y":43,"skin_variations":{}},{"unified":"1F1E6-1F1FA","sheet_x":0,"sheet_y":44,"skin_variations":{}},{"unified":"1F1E6-1F1FC","sheet_x":0,"sheet_y":45,"skin_variations":{}},{"unified":"1F1E6-1F1FD","sheet_x":0,"sheet_y":46,"skin_variations":{}},{"unified":"1F1E6-1F1FF","sheet_x":0,"sheet_y":47,"skin_variations":{}},{"unified":"1F1E7-1F1E6","sheet_x":0,"sheet_y":48,"skin_variations":{}},{"unified":"1F1E7-1F1E7","sheet_x":0,"sheet_y":49,"skin_variations":{}},{"unified":"1F1E7-1F1E9","sheet_x":0,"sheet_y":50,"skin_variations":{}},{"unified":"1F1E7-1F1EA","sheet_x":0,"sheet_y":51,"skin_variations":{}},{"unified":"1F1E7-1F1EB","sheet_x":0,"sheet_y":52,"skin_variations":{}},{"unified":"1F1E7-1F1EC","sheet_x":0,"sheet_y":53,"skin_variations":{}},{"unified":"1F1E7-1F1ED","sheet_x":0,"sheet_y":54,"skin_variations":{}},{"unified":"1F1E7-1F1EE","sheet_x":0,"sheet_y":55,"skin_variations":{}},{"unified":"1F1E7-1F1EF","sheet_x":0,"sheet_y":56,"skin_variations":{}},{"unified":"1F1E7-1F1F1","sheet_x":0,"sheet_y":57,"skin_variations":{}},{"unified":"1F1E7-1F1F2","sheet_x":0,"sheet_y":58,"skin_variations":{}},{"unified":"1F1E7-1F1F3","sheet_x":0,"sheet_y":59,"skin_variations":{}},{"unified":"1F1E7-1F1F4","sheet_x":0,"sheet_y":60,"skin_variations":{}},{"unified":"1F1E7-1F1F6","sheet_x":0,"sheet_y":61,"skin_variations":{}},{"unified":"1F1E7-1F1F7","sheet_x":1,"sheet_y":0,"skin_variations":{}},{"unified":"1F1E7-1F1F8","sheet_x":1,"sheet_y":1,"skin_variations":{}},{"unified":"1F1E7-1F1F9","sheet_x":1,"sheet_y":2,"skin_variations":{}},{"unified":"1F1E7-1F1FB","sheet_x":1,"sheet_y":3,"skin_variations":{}},{"unified":"1F1E7-1F1FC","sheet_x":1,"sheet_y":4,"skin_variations":{}},{"unified":"1F1E7-1F1FE","sheet_x":1,"sheet_y":5,"skin_variations":{}},{"unified":"1F1E7-1F1FF","sheet_x":1,"sheet_y":6,"skin_variations":{}},{"unified":"1F1E8-1F1E6","sheet_x":1,"sheet_y":7,"skin_variations":{}},{"unified":"1F1E8-1F1E8","sheet_x":1,"sheet_y":8,"skin_variations":{}},{"unified":"1F1E8-1F1E9","sheet_x":1,"sheet_y":9,"skin_variations":{}},{"unified":"1F1E8-1F1EB","sheet_x":1,"sheet_y":10,"skin_variations":{}},{"unified":"1F1E8-1F1EC","sheet_x":1,"sheet_y":11,"skin_variations":{}},{"unified":"1F1E8-1F1ED","sheet_x":1,"sheet_y":12,"skin_variations":{}},{"unified":"1F1E8-1F1EE","sheet_x":1,"sheet_y":13,"skin_variations":{}},{"unified":"1F1E8-1F1F0","sheet_x":1,"sheet_y":14,"skin_variations":{}},{"unified":"1F1E8-1F1F1","sheet_x":1,"sheet_y":15,"skin_variations":{}},{"unified":"1F1E8-1F1F2","sheet_x":1,"sheet_y":16,"skin_variations":{}},{"unified":"1F1E8-1F1F3","sheet_x":1,"sheet_y":17,"skin_variations":{}},{"unified":"1F1E8-1F1F4","sheet_x":1,"sheet_y":18,"skin_variations":{}},{"unified":"1F1E8-1F1F5","sheet_x":1,"sheet_y":19,"skin_variations":{}},{"unified":"1F1E8-1F1F7","sheet_x":1,"sheet_y":20,"skin_variations":{}},{"unified":"1F1E8-1F1FA","sheet_x":1,"sheet_y":21,"skin_variations":{}},{"unified":"1F1E8-1F1FB","sheet_x":1,"sheet_y":22,"skin_variations":{}},{"unified":"1F1E8-1F1FC","sheet_x":1,"sheet_y":23,"skin_variations":{}},{"unified":"1F1E8-1F1FD","sheet_x":1,"sheet_y":24,"skin_variations":{}},{"unified":"1F1E8-1F1FE","sheet_x":1,"sheet_y":25,"skin_variations":{}},{"unified":"1F1E8-1F1FF","sheet_x":1,"sheet_y":26,"skin_variations":{}},{"unified":"1F1E9-1F1EA","sheet_x":1,"sheet_y":27,"skin_variations":{}},{"unified":"1F1E9-1F1EC","sheet_x":1,"sheet_y":28,"skin_variations":{}},{"unified":"1F1E9-1F1EF","sheet_x":1,"sheet_y":29,"skin_variations":{}},{"unified":"1F1E9-1F1F0","sheet_x":1,"sheet_y":30,"skin_variations":{}},{"unified":"1F1E9-1F1F2","sheet_x":1,"sheet_y":31,"skin_variations":{}},{"unified":"1F1E9-1F1F4","sheet_x":1,"sheet_y":32,"skin_variations":{}},{"unified":"1F1E9-1F1FF","sheet_x":1,"sheet_y":33,"skin_variations":{}},{"unified":"1F1EA-1F1E6","sheet_x":1,"sheet_y":34,"skin_variations":{}},{"unified":"1F1EA-1F1E8","sheet_x":1,"sheet_y":35,"skin_variations":{}},{"unified":"1F1EA-1F1EA","sheet_x":1,"sheet_y":36,"skin_variations":{}},{"unified":"1F1EA-1F1EC","sheet_x":1,"sheet_y":37,"skin_variations":{}},{"unified":"1F1EA-1F1ED","sheet_x":1,"sheet_y":38,"skin_variations":{}},{"unified":"1F1EA-1F1F7","sheet_x":1,"sheet_y":39,"skin_variations":{}},{"unified":"1F1EA-1F1F8","sheet_x":1,"sheet_y":40,"skin_variations":{}},{"unified":"1F1EA-1F1F9","sheet_x":1,"sheet_y":41,"skin_variations":{}},{"unified":"1F1EA-1F1FA","sheet_x":1,"sheet_y":42,"skin_variations":{}},{"unified":"1F1EB-1F1EE","sheet_x":1,"sheet_y":43,"skin_variations":{}},{"unified":"1F1EB-1F1EF","sheet_x":1,"sheet_y":44,"skin_variations":{}},{"unified":"1F1EB-1F1F0","sheet_x":1,"sheet_y":45,"skin_variations":{}},{"unified":"1F1EB-1F1F2","sheet_x":1,"sheet_y":46,"skin_variations":{}},{"unified":"1F1EB-1F1F4","sheet_x":1,"sheet_y":47,"skin_variations":{}},{"unified":"1F1EB-1F1F7","sheet_x":1,"sheet_y":48,"skin_variations":{}},{"unified":"1F1EC-1F1E6","sheet_x":1,"sheet_y":49,"skin_variations":{}},{"unified":"1F1EC-1F1E7","sheet_x":1,"sheet_y":50,"skin_variations":{}},{"unified":"1F1EC-1F1E9","sheet_x":1,"sheet_y":51,"skin_variations":{}},{"unified":"1F1EC-1F1EA","sheet_x":1,"sheet_y":52,"skin_variations":{}},{"unified":"1F1EC-1F1EB","sheet_x":1,"sheet_y":53,"skin_variations":{}},{"unified":"1F1EC-1F1EC","sheet_x":1,"sheet_y":54,"skin_variations":{}},{"unified":"1F1EC-1F1ED","sheet_x":1,"sheet_y":55,"skin_variations":{}},{"unified":"1F1EC-1F1EE","sheet_x":1,"sheet_y":56,"skin_variations":{}},{"unified":"1F1EC-1F1F1","sheet_x":1,"sheet_y":57,"skin_variations":{}},{"unified":"1F1EC-1F1F2","sheet_x":1,"sheet_y":58,"skin_variations":{}},{"unified":"1F1EC-1F1F3","sheet_x":1,"sheet_y":59,"skin_variations":{}},{"unified":"1F1EC-1F1F5","sheet_x":1,"sheet_y":60,"skin_variations":{}},{"unified":"1F1EC-1F1F6","sheet_x":1,"sheet_y":61,"skin_variations":{}},{"unified":"1F1EC-1F1F7","sheet_x":2,"sheet_y":0,"skin_variations":{}},{"unified":"1F1EC-1F1F8","sheet_x":2,"sheet_y":1,"skin_variations":{}},{"unified":"1F1EC-1F1F9","sheet_x":2,"sheet_y":2,"skin_variations":{}},{"unified":"1F1EC-1F1FA","sheet_x":2,"sheet_y":3,"skin_variations":{}},{"unified":"1F1EC-1F1FC","sheet_x":2,"sheet_y":4,"skin_variations":{}},{"unified":"1F1EC-1F1FE","sheet_x":2,"sheet_y":5,"skin_variations":{}},{"unified":"1F1ED-1F1F0","sheet_x":2,"sheet_y":6,"skin_variations":{}},{"unified":"1F1ED-1F1F2","sheet_x":2,"sheet_y":7,"skin_variations":{}},{"unified":"1F1ED-1F1F3","sheet_x":2,"sheet_y":8,"skin_variations":{}},{"unified":"1F1ED-1F1F7","sheet_x":2,"sheet_y":9,"skin_variations":{}},{"unified":"1F1ED-1F1F9","sheet_x":2,"sheet_y":10,"skin_variations":{}},{"unified":"1F1ED-1F1FA","sheet_x":2,"sheet_y":11,"skin_variations":{}},{"unified":"1F1EE-1F1E8","sheet_x":2,"sheet_y":12,"skin_variations":{}},{"unified":"1F1EE-1F1E9","sheet_x":2,"sheet_y":13,"skin_variations":{}},{"unified":"1F1EE-1F1EA","sheet_x":2,"sheet_y":14,"skin_variations":{}},{"unified":"1F1EE-1F1F1","sheet_x":2,"sheet_y":15,"skin_variations":{}},{"unified":"1F1EE-1F1F2","sheet_x":2,"sheet_y":16,"skin_variations":{}},{"unified":"1F1EE-1F1F3","sheet_x":2,"sheet_y":17,"skin_variations":{}},{"unified":"1F1EE-1F1F4","sheet_x":2,"sheet_y":18,"skin_variations":{}},{"unified":"1F1EE-1F1F6","sheet_x":2,"sheet_y":19,"skin_variations":{}},{"unified":"1F1EE-1F1F7","sheet_x":2,"sheet_y":20,"skin_variations":{}},{"unified":"1F1EE-1F1F8","sheet_x":2,"sheet_y":21,"skin_variations":{}},{"unified":"1F1EE-1F1F9","sheet_x":2,"sheet_y":22,"skin_variations":{}},{"unified":"1F1EF-1F1EA","sheet_x":2,"sheet_y":23,"skin_variations":{}},{"unified":"1F1EF-1F1F2","sheet_x":2,"sheet_y":24,"skin_variations":{}},{"unified":"1F1EF-1F1F4","sheet_x":2,"sheet_y":25,"skin_variations":{}},{"unified":"1F1EF-1F1F5","sheet_x":2,"sheet_y":26,"skin_variations":{}},{"unified":"1F1F0-1F1EA","sheet_x":2,"sheet_y":27,"skin_variations":{}},{"unified":"1F1F0-1F1EC","sheet_x":2,"sheet_y":28,"skin_variations":{}},{"unified":"1F1F0-1F1ED","sheet_x":2,"sheet_y":29,"skin_variations":{}},{"unified":"1F1F0-1F1EE","sheet_x":2,"sheet_y":30,"skin_variations":{}},{"unified":"1F1F0-1F1F2","sheet_x":2,"sheet_y":31,"skin_variations":{}},{"unified":"1F1F0-1F1F3","sheet_x":2,"sheet_y":32,"skin_variations":{}},{"unified":"1F1F0-1F1F5","sheet_x":2,"sheet_y":33,"skin_variations":{}},{"unified":"1F1F0-1F1F7","sheet_x":2,"sheet_y":34,"skin_variations":{}},{"unified":"1F1F0-1F1FC","sheet_x":2,"sheet_y":35,"skin_variations":{}},{"unified":"1F1F0-1F1FE","sheet_x":2,"sheet_y":36,"skin_variations":{}},{"unified":"1F1F0-1F1FF","sheet_x":2,"sheet_y":37,"skin_variations":{}},{"unified":"1F1F1-1F1E6","sheet_x":2,"sheet_y":38,"skin_variations":{}},{"unified":"1F1F1-1F1E7","sheet_x":2,"sheet_y":39,"skin_variations":{}},{"unified":"1F1F1-1F1E8","sheet_x":2,"sheet_y":40,"skin_variations":{}},{"unified":"1F1F1-1F1EE","sheet_x":2,"sheet_y":41,"skin_variations":{}},{"unified":"1F1F1-1F1F0","sheet_x":2,"sheet_y":42,"skin_variations":{}},{"unified":"1F1F1-1F1F7","sheet_x":2,"sheet_y":43,"skin_variations":{}},{"unified":"1F1F1-1F1F8","sheet_x":2,"sheet_y":44,"skin_variations":{}},{"unified":"1F1F1-1F1F9","sheet_x":2,"sheet_y":45,"skin_variations":{}},{"unified":"1F1F1-1F1FA","sheet_x":2,"sheet_y":46,"skin_variations":{}},{"unified":"1F1F1-1F1FB","sheet_x":2,"sheet_y":47,"skin_variations":{}},{"unified":"1F1F1-1F1FE","sheet_x":2,"sheet_y":48,"skin_variations":{}},{"unified":"1F1F2-1F1E6","sheet_x":2,"sheet_y":49,"skin_variations":{}},{"unified":"1F1F2-1F1E8","sheet_x":2,"sheet_y":50,"skin_variations":{}},{"unified":"1F1F2-1F1E9","sheet_x":2,"sheet_y":51,"skin_variations":{}},{"unified":"1F1F2-1F1EA","sheet_x":2,"sheet_y":52,"skin_variations":{}},{"unified":"1F1F2-1F1EB","sheet_x":2,"sheet_y":53,"skin_variations":{}},{"unified":"1F1F2-1F1EC","sheet_x":2,"sheet_y":54,"skin_variations":{}},{"unified":"1F1F2-1F1ED","sheet_x":2,"sheet_y":55,"skin_variations":{}},{"unified":"1F1F2-1F1F0","sheet_x":2,"sheet_y":56,"skin_variations":{}},{"unified":"1F1F2-1F1F1","sheet_x":2,"sheet_y":57,"skin_variations":{}},{"unified":"1F1F2-1F1F2","sheet_x":2,"sheet_y":58,"skin_variations":{}},{"unified":"1F1F2-1F1F3","sheet_x":2,"sheet_y":59,"skin_variations":{}},{"unified":"1F1F2-1F1F4","sheet_x":2,"sheet_y":60,"skin_variations":{}},{"unified":"1F1F2-1F1F5","sheet_x":2,"sheet_y":61,"skin_variations":{}},{"unified":"1F1F2-1F1F6","sheet_x":3,"sheet_y":0,"skin_variations":{}},{"unified":"1F1F2-1F1F7","sheet_x":3,"sheet_y":1,"skin_variations":{}},{"unified":"1F1F2-1F1F8","sheet_x":3,"sheet_y":2,"skin_variations":{}},{"unified":"1F1F2-1F1F9","sheet_x":3,"sheet_y":3,"skin_variations":{}},{"unified":"1F1F2-1F1FA","sheet_x":3,"sheet_y":4,"skin_variations":{}},{"unified":"1F1F2-1F1FB","sheet_x":3,"sheet_y":5,"skin_variations":{}},{"unified":"1F1F2-1F1FC","sheet_x":3,"sheet_y":6,"skin_variations":{}},{"unified":"1F1F2-1F1FD","sheet_x":3,"sheet_y":7,"skin_variations":{}},{"unified":"1F1F2-1F1FE","sheet_x":3,"sheet_y":8,"skin_variations":{}},{"unified":"1F1F2-1F1FF","sheet_x":3,"sheet_y":9,"skin_variations":{}},{"unified":"1F1F3-1F1E6","sheet_x":3,"sheet_y":10,"skin_variations":{}},{"unified":"1F1F3-1F1E8","sheet_x":3,"sheet_y":11,"skin_variations":{}},{"unified":"1F1F3-1F1EA","sheet_x":3,"sheet_y":12,"skin_variations":{}},{"unified":"1F1F3-1F1EB","sheet_x":3,"sheet_y":13,"skin_variations":{}},{"unified":"1F1F3-1F1EC","sheet_x":3,"sheet_y":14,"skin_variations":{}},{"unified":"1F1F3-1F1EE","sheet_x":3,"sheet_y":15,"skin_variations":{}},{"unified":"1F1F3-1F1F1","sheet_x":3,"sheet_y":16,"skin_variations":{}},{"unified":"1F1F3-1F1F4","sheet_x":3,"sheet_y":17,"skin_variations":{}},{"unified":"1F1F3-1F1F5","sheet_x":3,"sheet_y":18,"skin_variations":{}},{"unified":"1F1F3-1F1F7","sheet_x":3,"sheet_y":19,"skin_variations":{}},{"unified":"1F1F3-1F1FA","sheet_x":3,"sheet_y":20,"skin_variations":{}},{"unified":"1F1F3-1F1FF","sheet_x":3,"sheet_y":21,"skin_variations":{}},{"unified":"1F1F4-1F1F2","sheet_x":3,"sheet_y":22,"skin_variations":{}},{"unified":"1F1F5-1F1E6","sheet_x":3,"sheet_y":23,"skin_variations":{}},{"unified":"1F1F5-1F1EA","sheet_x":3,"sheet_y":24,"skin_variations":{}},{"unified":"1F1F5-1F1EB","sheet_x":3,"sheet_y":25,"skin_variations":{}},{"unified":"1F1F5-1F1EC","sheet_x":3,"sheet_y":26,"skin_variations":{}},{"unified":"1F1F5-1F1ED","sheet_x":3,"sheet_y":27,"skin_variations":{}},{"unified":"1F1F5-1F1F0","sheet_x":3,"sheet_y":28,"skin_variations":{}},{"unified":"1F1F5-1F1F1","sheet_x":3,"sheet_y":29,"skin_variations":{}},{"unified":"1F1F5-1F1F2","sheet_x":3,"sheet_y":30,"skin_variations":{}},{"unified":"1F1F5-1F1F3","sheet_x":3,"sheet_y":31,"skin_variations":{}},{"unified":"1F1F5-1F1F7","sheet_x":3,"sheet_y":32,"skin_variations":{}},{"unified":"1F1F5-1F1F8","sheet_x":3,"sheet_y":33,"skin_variations":{}},{"unified":"1F1F5-1F1F9","sheet_x":3,"sheet_y":34,"skin_variations":{}},{"unified":"1F1F5-1F1FC","sheet_x":3,"sheet_y":35,"skin_variations":{}},{"unified":"1F1F5-1F1FE","sheet_x":3,"sheet_y":36,"skin_variations":{}},{"unified":"1F1F6-1F1E6","sheet_x":3,"sheet_y":37,"skin_variations":{}},{"unified":"1F1F7-1F1EA","sheet_x":3,"sheet_y":38,"skin_variations":{}},{"unified":"1F1F7-1F1F4","sheet_x":3,"sheet_y":39,"skin_variations":{}},{"unified":"1F1F7-1F1F8","sheet_x":3,"sheet_y":40,"skin_variations":{}},{"unified":"1F1F7-1F1FA","sheet_x":3,"sheet_y":41,"skin_variations":{}},{"unified":"1F1F7-1F1FC","sheet_x":3,"sheet_y":42,"skin_variations":{}},{"unified":"1F1F8-1F1E6","sheet_x":3,"sheet_y":43,"skin_variations":{}},{"unified":"1F1F8-1F1E7","sheet_x":3,"sheet_y":44,"skin_variations":{}},{"unified":"1F1F8-1F1E8","sheet_x":3,"sheet_y":45,"skin_variations":{}},{"unified":"1F1F8-1F1E9","sheet_x":3,"sheet_y":46,"skin_variations":{}},{"unified":"1F1F8-1F1EA","sheet_x":3,"sheet_y":47,"skin_variations":{}},{"unified":"1F1F8-1F1EC","sheet_x":3,"sheet_y":48,"skin_variations":{}},{"unified":"1F1F8-1F1ED","sheet_x":3,"sheet_y":49,"skin_variations":{}},{"unified":"1F1F8-1F1EE","sheet_x":3,"sheet_y":50,"skin_variations":{}},{"unified":"1F1F8-1F1EF","sheet_x":3,"sheet_y":51,"skin_variations":{}},{"unified":"1F1F8-1F1F0","sheet_x":3,"sheet_y":52,"skin_variations":{}},{"unified":"1F1F8-1F1F1","sheet_x":3,"sheet_y":53,"skin_variations":{}},{"unified":"1F1F8-1F1F2","sheet_x":3,"sheet_y":54,"skin_variations":{}},{"unified":"1F1F8-1F1F3","sheet_x":3,"sheet_y":55,"skin_variations":{}},{"unified":"1F1F8-1F1F4","sheet_x":3,"sheet_y":56,"skin_variations":{}},{"unified":"1F1F8-1F1F7","sheet_x":3,"sheet_y":57,"skin_variations":{}},{"unified":"1F1F8-1F1F8","sheet_x":3,"sheet_y":58,"skin_variations":{}},{"unified":"1F1F8-1F1F9","sheet_x":3,"sheet_y":59,"skin_variations":{}},{"unified":"1F1F8-1F1FB","sheet_x":3,"sheet_y":60,"skin_variations":{}},{"unified":"1F1F8-1F1FD","sheet_x":3,"sheet_y":61,"skin_variations":{}},{"unified":"1F1F8-1F1FE","sheet_x":4,"sheet_y":0,"skin_variations":{}},{"unified":"1F1F8-1F1FF","sheet_x":4,"sheet_y":1,"skin_variations":{}},{"unified":"1F1F9-1F1E6","sheet_x":4,"sheet_y":2,"skin_variations":{}},{"unified":"1F1F9-1F1E8","sheet_x":4,"sheet_y":3,"skin_variations":{}},{"unified":"1F1F9-1F1E9","sheet_x":4,"sheet_y":4,"skin_variations":{}},{"unified":"1F1F9-1F1EB","sheet_x":4,"sheet_y":5,"skin_variations":{}},{"unified":"1F1F9-1F1EC","sheet_x":4,"sheet_y":6,"skin_variations":{}},{"unified":"1F1F9-1F1ED","sheet_x":4,"sheet_y":7,"skin_variations":{}},{"unified":"1F1F9-1F1EF","sheet_x":4,"sheet_y":8,"skin_variations":{}},{"unified":"1F1F9-1F1F0","sheet_x":4,"sheet_y":9,"skin_variations":{}},{"unified":"1F1F9-1F1F1","sheet_x":4,"sheet_y":10,"skin_variations":{}},{"unified":"1F1F9-1F1F2","sheet_x":4,"sheet_y":11,"skin_variations":{}},{"unified":"1F1F9-1F1F3","sheet_x":4,"sheet_y":12,"skin_variations":{}},{"unified":"1F1F9-1F1F4","sheet_x":4,"sheet_y":13,"skin_variations":{}},{"unified":"1F1F9-1F1F7","sheet_x":4,"sheet_y":14,"skin_variations":{}},{"unified":"1F1F9-1F1F9","sheet_x":4,"sheet_y":15,"skin_variations":{}},{"unified":"1F1F9-1F1FB","sheet_x":4,"sheet_y":16,"skin_variations":{}},{"unified":"1F1F9-1F1FC","sheet_x":4,"sheet_y":17,"skin_variations":{}},{"unified":"1F1F9-1F1FF","sheet_x":4,"sheet_y":18,"skin_variations":{}},{"unified":"1F1FA-1F1E6","sheet_x":4,"sheet_y":19,"skin_variations":{}},{"unified":"1F1FA-1F1EC","sheet_x":4,"sheet_y":20,"skin_variations":{}},{"unified":"1F1FA-1F1F2","sheet_x":4,"sheet_y":21,"skin_variations":{}},{"unified":"1F1FA-1F1F3","sheet_x":4,"sheet_y":22,"skin_variations":{}},{"unified":"1F1FA-1F1F8","sheet_x":4,"sheet_y":23,"skin_variations":{}},{"unified":"1F1FA-1F1FE","sheet_x":4,"sheet_y":24,"skin_variations":{}},{"unified":"1F1FA-1F1FF","sheet_x":4,"sheet_y":25,"skin_variations":{}},{"unified":"1F1FB-1F1E6","sheet_x":4,"sheet_y":26,"skin_variations":{}},{"unified":"1F1FB-1F1E8","sheet_x":4,"sheet_y":27,"skin_variations":{}},{"unified":"1F1FB-1F1EA","sheet_x":4,"sheet_y":28,"skin_variations":{}},{"unified":"1F1FB-1F1EC","sheet_x":4,"sheet_y":29,"skin_variations":{}},{"unified":"1F1FB-1F1EE","sheet_x":4,"sheet_y":30,"skin_variations":{}},{"unified":"1F1FB-1F1F3","sheet_x":4,"sheet_y":31,"skin_variations":{}},{"unified":"1F1FB-1F1FA","sheet_x":4,"sheet_y":32,"skin_variations":{}},{"unified":"1F1FC-1F1EB","sheet_x":4,"sheet_y":33,"skin_variations":{}},{"unified":"1F1FC-1F1F8","sheet_x":4,"sheet_y":34,"skin_variations":{}},{"unified":"1F1FD-1F1F0","sheet_x":4,"sheet_y":35,"skin_variations":{}},{"unified":"1F1FE-1F1EA","sheet_x":4,"sheet_y":36,"skin_variations":{}},{"unified":"1F1FE-1F1F9","sheet_x":4,"sheet_y":37,"skin_variations":{}},{"unified":"1F1FF-1F1E6","sheet_x":4,"sheet_y":38,"skin_variations":{}},{"unified":"1F1FF-1F1F2","sheet_x":4,"sheet_y":39,"skin_variations":{}},{"unified":"1F1FF-1F1FC","sheet_x":4,"sheet_y":40,"skin_variations":{}},{"unified":"1F201","sheet_x":4,"sheet_y":41,"skin_variations":{}},{"unified":"1F202-FE0F","sheet_x":4,"sheet_y":42,"skin_variations":{}},{"unified":"1F21A","sheet_x":4,"sheet_y":43,"skin_variations":{}},{"unified":"1F22F","sheet_x":4,"sheet_y":44,"skin_variations":{}},{"unified":"1F232","sheet_x":4,"sheet_y":45,"skin_variations":{}},{"unified":"1F233","sheet_x":4,"sheet_y":46,"skin_variations":{}},{"unified":"1F234","sheet_x":4,"sheet_y":47,"skin_variations":{}},{"unified":"1F235","sheet_x":4,"sheet_y":48,"skin_variations":{}},{"unified":"1F236","sheet_x":4,"sheet_y":49,"skin_variations":{}},{"unified":"1F237-FE0F","sheet_x":4,"sheet_y":50,"skin_variations":{}},{"unified":"1F238","sheet_x":4,"sheet_y":51,"skin_variations":{}},{"unified":"1F239","sheet_x":4,"sheet_y":52,"skin_variations":{}},{"unified":"1F23A","sheet_x":4,"sheet_y":53,"skin_variations":{}},{"unified":"1F250","sheet_x":4,"sheet_y":54,"skin_variations":{}},{"unified":"1F251","sheet_x":4,"sheet_y":55,"skin_variations":{}},{"unified":"1F300","sheet_x":4,"sheet_y":56,"skin_variations":{}},{"unified":"1F301","sheet_x":4,"sheet_y":57,"skin_variations":{}},{"unified":"1F302","sheet_x":4,"sheet_y":58,"skin_variations":{}},{"unified":"1F303","sheet_x":4,"sheet_y":59,"skin_variations":{}},{"unified":"1F304","sheet_x":4,"sheet_y":60,"skin_variations":{}},{"unified":"1F305","sheet_x":4,"sheet_y":61,"skin_variations":{}},{"unified":"1F306","sheet_x":5,"sheet_y":0,"skin_variations":{}},{"unified":"1F307","sheet_x":5,"sheet_y":1,"skin_variations":{}},{"unified":"1F308","sheet_x":5,"sheet_y":2,"skin_variations":{}},{"unified":"1F309","sheet_x":5,"sheet_y":3,"skin_variations":{}},{"unified":"1F30A","sheet_x":5,"sheet_y":4,"skin_variations":{}},{"unified":"1F30B","sheet_x":5,"sheet_y":5,"skin_variations":{}},{"unified":"1F30C","sheet_x":5,"sheet_y":6,"skin_variations":{}},{"unified":"1F30D","sheet_x":5,"sheet_y":7,"skin_variations":{}},{"unified":"1F30E","sheet_x":5,"sheet_y":8,"skin_variations":{}},{"unified":"1F30F","sheet_x":5,"sheet_y":9,"skin_variations":{}},{"unified":"1F310","sheet_x":5,"sheet_y":10,"skin_variations":{}},{"unified":"1F311","sheet_x":5,"sheet_y":11,"skin_variations":{}},{"unified":"1F312","sheet_x":5,"sheet_y":12,"skin_variations":{}},{"unified":"1F313","sheet_x":5,"sheet_y":13,"skin_variations":{}},{"unified":"1F314","sheet_x":5,"sheet_y":14,"skin_variations":{}},{"unified":"1F315","sheet_x":5,"sheet_y":15,"skin_variations":{}},{"unified":"1F316","sheet_x":5,"sheet_y":16,"skin_variations":{}},{"unified":"1F317","sheet_x":5,"sheet_y":17,"skin_variations":{}},{"unified":"1F318","sheet_x":5,"sheet_y":18,"skin_variations":{}},{"unified":"1F319","sheet_x":5,"sheet_y":19,"skin_variations":{}},{"unified":"1F31A","sheet_x":5,"sheet_y":20,"skin_variations":{}},{"unified":"1F31B","sheet_x":5,"sheet_y":21,"skin_variations":{}},{"unified":"1F31C","sheet_x":5,"sheet_y":22,"skin_variations":{}},{"unified":"1F31D","sheet_x":5,"sheet_y":23,"skin_variations":{}},{"unified":"1F31E","sheet_x":5,"sheet_y":24,"skin_variations":{}},{"unified":"1F31F","sheet_x":5,"sheet_y":25,"skin_variations":{}},{"unified":"1F320","sheet_x":5,"sheet_y":26,"skin_variations":{}},{"unified":"1F321-FE0F","sheet_x":5,"sheet_y":27,"skin_variations":{}},{"unified":"1F324-FE0F","sheet_x":5,"sheet_y":28,"skin_variations":{}},{"unified":"1F325-FE0F","sheet_x":5,"sheet_y":29,"skin_variations":{}},{"unified":"1F326-FE0F","sheet_x":5,"sheet_y":30,"skin_variations":{}},{"unified":"1F327-FE0F","sheet_x":5,"sheet_y":31,"skin_variations":{}},{"unified":"1F328-FE0F","sheet_x":5,"sheet_y":32,"skin_variations":{}},{"unified":"1F329-FE0F","sheet_x":5,"sheet_y":33,"skin_variations":{}},{"unified":"1F32A-FE0F","sheet_x":5,"sheet_y":34,"skin_variations":{}},{"unified":"1F32B-FE0F","sheet_x":5,"sheet_y":35,"skin_variations":{}},{"unified":"1F32C-FE0F","sheet_x":5,"sheet_y":36,"skin_variations":{}},{"unified":"1F32D","sheet_x":5,"sheet_y":37,"skin_variations":{}},{"unified":"1F32E","sheet_x":5,"sheet_y":38,"skin_variations":{}},{"unified":"1F32F","sheet_x":5,"sheet_y":39,"skin_variations":{}},{"unified":"1F330","sheet_x":5,"sheet_y":40,"skin_variations":{}},{"unified":"1F331","sheet_x":5,"sheet_y":41,"skin_variations":{}},{"unified":"1F332","sheet_x":5,"sheet_y":42,"skin_variations":{}},{"unified":"1F333","sheet_x":5,"sheet_y":43,"skin_variations":{}},{"unified":"1F334","sheet_x":5,"sheet_y":44,"skin_variations":{}},{"unified":"1F335","sheet_x":5,"sheet_y":45,"skin_variations":{}},{"unified":"1F336-FE0F","sheet_x":5,"sheet_y":46,"skin_variations":{}},{"unified":"1F337","sheet_x":5,"sheet_y":47,"skin_variations":{}},{"unified":"1F338","sheet_x":5,"sheet_y":48,"skin_variations":{}},{"unified":"1F339","sheet_x":5,"sheet_y":49,"skin_variations":{}},{"unified":"1F33A","sheet_x":5,"sheet_y":50,"skin_variations":{}},{"unified":"1F33B","sheet_x":5,"sheet_y":51,"skin_variations":{}},{"unified":"1F33C","sheet_x":5,"sheet_y":52,"skin_variations":{}},{"unified":"1F33D","sheet_x":5,"sheet_y":53,"skin_variations":{}},{"unified":"1F33E","sheet_x":5,"sheet_y":54,"skin_variations":{}},{"unified":"1F33F","sheet_x":5,"sheet_y":55,"skin_variations":{}},{"unified":"1F340","sheet_x":5,"sheet_y":56,"skin_variations":{}},{"unified":"1F341","sheet_x":5,"sheet_y":57,"skin_variations":{}},{"unified":"1F342","sheet_x":5,"sheet_y":58,"skin_variations":{}},{"unified":"1F343","sheet_x":5,"sheet_y":59,"skin_variations":{}},{"unified":"1F344-200D-1F7EB","sheet_x":5,"sheet_y":60,"skin_variations":{}},{"unified":"1F344","sheet_x":5,"sheet_y":61,"skin_variations":{}},{"unified":"1F345","sheet_x":6,"sheet_y":0,"skin_variations":{}},{"unified":"1F346","sheet_x":6,"sheet_y":1,"skin_variations":{}},{"unified":"1F347","sheet_x":6,"sheet_y":2,"skin_variations":{}},{"unified":"1F348","sheet_x":6,"sheet_y":3,"skin_variations":{}},{"unified":"1F349","sheet_x":6,"sheet_y":4,"skin_variations":{}},{"unified":"1F34A","sheet_x":6,"sheet_y":5,"skin_variations":{}},{"unified":"1F34B-200D-1F7E9","sheet_x":6,"sheet_y":6,"skin_variations":{}},{"unified":"1F34B","sheet_x":6,"sheet_y":7,"skin_variations":{}},{"unified":"1F34C","sheet_x":6,"sheet_y":8,"skin_variations":{}},{"unified":"1F34D","sheet_x":6,"sheet_y":9,"skin_variations":{}},{"unified":"1F34E","sheet_x":6,"sheet_y":10,"skin_variations":{}},{"unified":"1F34F","sheet_x":6,"sheet_y":11,"skin_variations":{}},{"unified":"1F350","sheet_x":6,"sheet_y":12,"skin_variations":{}},{"unified":"1F351","sheet_x":6,"sheet_y":13,"skin_variations":{}},{"unified":"1F352","sheet_x":6,"sheet_y":14,"skin_variations":{}},{"unified":"1F353","sheet_x":6,"sheet_y":15,"skin_variations":{}},{"unified":"1F354","sheet_x":6,"sheet_y":16,"skin_variations":{}},{"unified":"1F355","sheet_x":6,"sheet_y":17,"skin_variations":{}},{"unified":"1F356","sheet_x":6,"sheet_y":18,"skin_variations":{}},{"unified":"1F357","sheet_x":6,"sheet_y":19,"skin_variations":{}},{"unified":"1F358","sheet_x":6,"sheet_y":20,"skin_variations":{}},{"unified":"1F359","sheet_x":6,"sheet_y":21,"skin_variations":{}},{"unified":"1F35A","sheet_x":6,"sheet_y":22,"skin_variations":{}},{"unified":"1F35B","sheet_x":6,"sheet_y":23,"skin_variations":{}},{"unified":"1F35C","sheet_x":6,"sheet_y":24,"skin_variations":{}},{"unified":"1F35D","sheet_x":6,"sheet_y":25,"skin_variations":{}},{"unified":"1F35E","sheet_x":6,"sheet_y":26,"skin_variations":{}},{"unified":"1F35F","sheet_x":6,"sheet_y":27,"skin_variations":{}},{"unified":"1F360","sheet_x":6,"sheet_y":28,"skin_variations":{}},{"unified":"1F361","sheet_x":6,"sheet_y":29,"skin_variations":{}},{"unified":"1F362","sheet_x":6,"sheet_y":30,"skin_variations":{}},{"unified":"1F363","sheet_x":6,"sheet_y":31,"skin_variations":{}},{"unified":"1F364","sheet_x":6,"sheet_y":32,"skin_variations":{}},{"unified":"1F365","sheet_x":6,"sheet_y":33,"skin_variations":{}},{"unified":"1F366","sheet_x":6,"sheet_y":34,"skin_variations":{}},{"unified":"1F367","sheet_x":6,"sheet_y":35,"skin_variations":{}},{"unified":"1F368","sheet_x":6,"sheet_y":36,"skin_variations":{}},{"unified":"1F369","sheet_x":6,"sheet_y":37,"skin_variations":{}},{"unified":"1F36A","sheet_x":6,"sheet_y":38,"skin_variations":{}},{"unified":"1F36B","sheet_x":6,"sheet_y":39,"skin_variations":{}},{"unified":"1F36C","sheet_x":6,"sheet_y":40,"skin_variations":{}},{"unified":"1F36D","sheet_x":6,"sheet_y":41,"skin_variations":{}},{"unified":"1F36E","sheet_x":6,"sheet_y":42,"skin_variations":{}},{"unified":"1F36F","sheet_x":6,"sheet_y":43,"skin_variations":{}},{"unified":"1F370","sheet_x":6,"sheet_y":44,"skin_variations":{}},{"unified":"1F371","sheet_x":6,"sheet_y":45,"skin_variations":{}},{"unified":"1F372","sheet_x":6,"sheet_y":46,"skin_variations":{}},{"unified":"1F373","sheet_x":6,"sheet_y":47,"skin_variations":{}},{"unified":"1F374","sheet_x":6,"sheet_y":48,"skin_variations":{}},{"unified":"1F375","sheet_x":6,"sheet_y":49,"skin_variations":{}},{"unified":"1F376","sheet_x":6,"sheet_y":50,"skin_variations":{}},{"unified":"1F377","sheet_x":6,"sheet_y":51,"skin_variations":{}},{"unified":"1F378","sheet_x":6,"sheet_y":52,"skin_variations":{}},{"unified":"1F379","sheet_x":6,"sheet_y":53,"skin_variations":{}},{"unified":"1F37A","sheet_x":6,"sheet_y":54,"skin_variations":{}},{"unified":"1F37B","sheet_x":6,"sheet_y":55,"skin_variations":{}},{"unified":"1F37C","sheet_x":6,"sheet_y":56,"skin_variations":{}},{"unified":"1F37D-FE0F","sheet_x":6,"sheet_y":57,"skin_variations":{}},{"unified":"1F37E","sheet_x":6,"sheet_y":58,"skin_variations":{}},{"unified":"1F37F","sheet_x":6,"sheet_y":59,"skin_variations":{}},{"unified":"1F380","sheet_x":6,"sheet_y":60,"skin_variations":{}},{"unified":"1F381","sheet_x":6,"sheet_y":61,"skin_variations":{}},{"unified":"1F382","sheet_x":7,"sheet_y":0,"skin_variations":{}},{"unified":"1F383","sheet_x":7,"sheet_y":1,"skin_variations":{}},{"unified":"1F384","sheet_x":7,"sheet_y":2,"skin_variations":{}},{"unified":"1F385","sheet_x":7,"sheet_y":3,"skin_variations":{"1F3FB":{"unified":"1F385-1F3FB","sheet_x":7,"sheet_y":4},"1F3FC":{"unified":"1F385-1F3FC","sheet_x":7,"sheet_y":5},"1F3FD":{"unified":"1F385-1F3FD","sheet_x":7,"sheet_y":6},"1F3FE":{"unified":"1F385-1F3FE","sheet_x":7,"sheet_y":7},"1F3FF":{"unified":"1F385-1F3FF","sheet_x":7,"sheet_y":8}}},{"unified":"1F386","sheet_x":7,"sheet_y":9,"skin_variations":{}},{"unified":"1F387","sheet_x":7,"sheet_y":10,"skin_variations":{}},{"unified":"1F388","sheet_x":7,"sheet_y":11,"skin_variations":{}},{"unified":"1F389","sheet_x":7,"sheet_y":12,"skin_variations":{}},{"unified":"1F38A","sheet_x":7,"sheet_y":13,"skin_variations":{}},{"unified":"1F38B","sheet_x":7,"sheet_y":14,"skin_variations":{}},{"unified":"1F38C","sheet_x":7,"sheet_y":15,"skin_variations":{}},{"unified":"1F38D","sheet_x":7,"sheet_y":16,"skin_variations":{}},{"unified":"1F38E","sheet_x":7,"sheet_y":17,"skin_variations":{}},{"unified":"1F38F","sheet_x":7,"sheet_y":18,"skin_variations":{}},{"unified":"1F390","sheet_x":7,"sheet_y":19,"skin_variations":{}},{"unified":"1F391","sheet_x":7,"sheet_y":20,"skin_variations":{}},{"unified":"1F392","sheet_x":7,"sheet_y":21,"skin_variations":{}},{"unified":"1F393","sheet_x":7,"sheet_y":22,"skin_variations":{}},{"unified":"1F396-FE0F","sheet_x":7,"sheet_y":23,"skin_variations":{}},{"unified":"1F397-FE0F","sheet_x":7,"sheet_y":24,"skin_variations":{}},{"unified":"1F399-FE0F","sheet_x":7,"sheet_y":25,"skin_variations":{}},{"unified":"1F39A-FE0F","sheet_x":7,"sheet_y":26,"skin_variations":{}},{"unified":"1F39B-FE0F","sheet_x":7,"sheet_y":27,"skin_variations":{}},{"unified":"1F39E-FE0F","sheet_x":7,"sheet_y":28,"skin_variations":{}},{"unified":"1F39F-FE0F","sheet_x":7,"sheet_y":29,"skin_variations":{}},{"unified":"1F3A0","sheet_x":7,"sheet_y":30,"skin_variations":{}},{"unified":"1F3A1","sheet_x":7,"sheet_y":31,"skin_variations":{}},{"unified":"1F3A2","sheet_x":7,"sheet_y":32,"skin_variations":{}},{"unified":"1F3A3","sheet_x":7,"sheet_y":33,"skin_variations":{}},{"unified":"1F3A4","sheet_x":7,"sheet_y":34,"skin_variations":{}},{"unified":"1F3A5","sheet_x":7,"sheet_y":35,"skin_variations":{}},{"unified":"1F3A6","sheet_x":7,"sheet_y":36,"skin_variations":{}},{"unified":"1F3A7","sheet_x":7,"sheet_y":37,"skin_variations":{}},{"unified":"1F3A8","sheet_x":7,"sheet_y":38,"skin_variations":{}},{"unified":"1F3A9","sheet_x":7,"sheet_y":39,"skin_variations":{}},{"unified":"1F3AA","sheet_x":7,"sheet_y":40,"skin_variations":{}},{"unified":"1F3AB","sheet_x":7,"sheet_y":41,"skin_variations":{}},{"unified":"1F3AC","sheet_x":7,"sheet_y":42,"skin_variations":{}},{"unified":"1F3AD","sheet_x":7,"sheet_y":43,"skin_variations":{}},{"unified":"1F3AE","sheet_x":7,"sheet_y":44,"skin_variations":{}},{"unified":"1F3AF","sheet_x":7,"sheet_y":45,"skin_variations":{}},{"unified":"1F3B0","sheet_x":7,"sheet_y":46,"skin_variations":{}},{"unified":"1F3B1","sheet_x":7,"sheet_y":47,"skin_variations":{}},{"unified":"1F3B2","sheet_x":7,"sheet_y":48,"skin_variations":{}},{"unified":"1F3B3","sheet_x":7,"sheet_y":49,"skin_variations":{}},{"unified":"1F3B4","sheet_x":7,"sheet_y":50,"skin_variations":{}},{"unified":"1F3B5","sheet_x":7,"sheet_y":51,"skin_variations":{}},{"unified":"1F3B6","sheet_x":7,"sheet_y":52,"skin_variations":{}},{"unified":"1F3B7","sheet_x":7,"sheet_y":53,"skin_variations":{}},{"unified":"1F3B8","sheet_x":7,"sheet_y":54,"skin_variations":{}},{"unified":"1F3B9","sheet_x":7,"sheet_y":55,"skin_variations":{}},{"unified":"1F3BA","sheet_x":7,"sheet_y":56,"skin_variations":{}},{"unified":"1F3BB","sheet_x":7,"sheet_y":57,"skin_variations":{}},{"unified":"1F3BC","sheet_x":7,"sheet_y":58,"skin_variations":{}},{"unified":"1F3BD","sheet_x":7,"sheet_y":59,"skin_variations":{}},{"unified":"1F3BE","sheet_x":7,"sheet_y":60,"skin_variations":{}},{"unified":"1F3BF","sheet_x":7,"sheet_y":61,"skin_variations":{}},{"unified":"1F3C0","sheet_x":8,"sheet_y":0,"skin_variations":{}},{"unified":"1F3C1","sheet_x":8,"sheet_y":1,"skin_variations":{}},{"unified":"1F3C2","sheet_x":8,"sheet_y":2,"skin_variations":{"1F3FB":{"unified":"1F3C2-1F3FB","sheet_x":8,"sheet_y":3},"1F3FC":{"unified":"1F3C2-1F3FC","sheet_x":8,"sheet_y":4},"1F3FD":{"unified":"1F3C2-1F3FD","sheet_x":8,"sheet_y":5},"1F3FE":{"unified":"1F3C2-1F3FE","sheet_x":8,"sheet_y":6},"1F3FF":{"unified":"1F3C2-1F3FF","sheet_x":8,"sheet_y":7}}},{"unified":"1F3C3-200D-2640-FE0F","sheet_x":8,"sheet_y":8,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2640-FE0F","sheet_x":8,"sheet_y":9},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2640-FE0F","sheet_x":8,"sheet_y":10},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2640-FE0F","sheet_x":8,"sheet_y":11},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2640-FE0F","sheet_x":8,"sheet_y":12},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2640-FE0F","sheet_x":8,"sheet_y":13}}},{"unified":"1F3C3-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":14,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":15},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":16},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":17},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":18},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":19}}},{"unified":"1F3C3-200D-2642-FE0F","sheet_x":8,"sheet_y":20,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2642-FE0F","sheet_x":8,"sheet_y":21},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2642-FE0F","sheet_x":8,"sheet_y":22},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2642-FE0F","sheet_x":8,"sheet_y":23},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2642-FE0F","sheet_x":8,"sheet_y":24},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2642-FE0F","sheet_x":8,"sheet_y":25}}},{"unified":"1F3C3-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":26,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":27},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":28},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":29},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":30},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":8,"sheet_y":31}}},{"unified":"1F3C3-200D-27A1-FE0F","sheet_x":8,"sheet_y":32,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-27A1-FE0F","sheet_x":8,"sheet_y":33},"1F3FC":{"unified":"1F3C3-1F3FC-200D-27A1-FE0F","sheet_x":8,"sheet_y":34},"1F3FD":{"unified":"1F3C3-1F3FD-200D-27A1-FE0F","sheet_x":8,"sheet_y":35},"1F3FE":{"unified":"1F3C3-1F3FE-200D-27A1-FE0F","sheet_x":8,"sheet_y":36},"1F3FF":{"unified":"1F3C3-1F3FF-200D-27A1-FE0F","sheet_x":8,"sheet_y":37}}},{"unified":"1F3C3","sheet_x":8,"sheet_y":38,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB","sheet_x":8,"sheet_y":39},"1F3FC":{"unified":"1F3C3-1F3FC","sheet_x":8,"sheet_y":40},"1F3FD":{"unified":"1F3C3-1F3FD","sheet_x":8,"sheet_y":41},"1F3FE":{"unified":"1F3C3-1F3FE","sheet_x":8,"sheet_y":42},"1F3FF":{"unified":"1F3C3-1F3FF","sheet_x":8,"sheet_y":43}}},{"unified":"1F3C4-200D-2640-FE0F","sheet_x":8,"sheet_y":44,"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB-200D-2640-FE0F","sheet_x":8,"sheet_y":45},"1F3FC":{"unified":"1F3C4-1F3FC-200D-2640-FE0F","sheet_x":8,"sheet_y":46},"1F3FD":{"unified":"1F3C4-1F3FD-200D-2640-FE0F","sheet_x":8,"sheet_y":47},"1F3FE":{"unified":"1F3C4-1F3FE-200D-2640-FE0F","sheet_x":8,"sheet_y":48},"1F3FF":{"unified":"1F3C4-1F3FF-200D-2640-FE0F","sheet_x":8,"sheet_y":49}}},{"unified":"1F3C4-200D-2642-FE0F","sheet_x":8,"sheet_y":50,"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB-200D-2642-FE0F","sheet_x":8,"sheet_y":51},"1F3FC":{"unified":"1F3C4-1F3FC-200D-2642-FE0F","sheet_x":8,"sheet_y":52},"1F3FD":{"unified":"1F3C4-1F3FD-200D-2642-FE0F","sheet_x":8,"sheet_y":53},"1F3FE":{"unified":"1F3C4-1F3FE-200D-2642-FE0F","sheet_x":8,"sheet_y":54},"1F3FF":{"unified":"1F3C4-1F3FF-200D-2642-FE0F","sheet_x":8,"sheet_y":55}}},{"unified":"1F3C4","sheet_x":8,"sheet_y":56,"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB","sheet_x":8,"sheet_y":57},"1F3FC":{"unified":"1F3C4-1F3FC","sheet_x":8,"sheet_y":58},"1F3FD":{"unified":"1F3C4-1F3FD","sheet_x":8,"sheet_y":59},"1F3FE":{"unified":"1F3C4-1F3FE","sheet_x":8,"sheet_y":60},"1F3FF":{"unified":"1F3C4-1F3FF","sheet_x":8,"sheet_y":61}}},{"unified":"1F3C5","sheet_x":9,"sheet_y":0,"skin_variations":{}},{"unified":"1F3C6","sheet_x":9,"sheet_y":1,"skin_variations":{}},{"unified":"1F3C7","sheet_x":9,"sheet_y":2,"skin_variations":{"1F3FB":{"unified":"1F3C7-1F3FB","sheet_x":9,"sheet_y":3},"1F3FC":{"unified":"1F3C7-1F3FC","sheet_x":9,"sheet_y":4},"1F3FD":{"unified":"1F3C7-1F3FD","sheet_x":9,"sheet_y":5},"1F3FE":{"unified":"1F3C7-1F3FE","sheet_x":9,"sheet_y":6},"1F3FF":{"unified":"1F3C7-1F3FF","sheet_x":9,"sheet_y":7}}},{"unified":"1F3C8","sheet_x":9,"sheet_y":8,"skin_variations":{}},{"unified":"1F3C9","sheet_x":9,"sheet_y":9,"skin_variations":{}},{"unified":"1F3CA-200D-2640-FE0F","sheet_x":9,"sheet_y":10,"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB-200D-2640-FE0F","sheet_x":9,"sheet_y":11},"1F3FC":{"unified":"1F3CA-1F3FC-200D-2640-FE0F","sheet_x":9,"sheet_y":12},"1F3FD":{"unified":"1F3CA-1F3FD-200D-2640-FE0F","sheet_x":9,"sheet_y":13},"1F3FE":{"unified":"1F3CA-1F3FE-200D-2640-FE0F","sheet_x":9,"sheet_y":14},"1F3FF":{"unified":"1F3CA-1F3FF-200D-2640-FE0F","sheet_x":9,"sheet_y":15}}},{"unified":"1F3CA-200D-2642-FE0F","sheet_x":9,"sheet_y":16,"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB-200D-2642-FE0F","sheet_x":9,"sheet_y":17},"1F3FC":{"unified":"1F3CA-1F3FC-200D-2642-FE0F","sheet_x":9,"sheet_y":18},"1F3FD":{"unified":"1F3CA-1F3FD-200D-2642-FE0F","sheet_x":9,"sheet_y":19},"1F3FE":{"unified":"1F3CA-1F3FE-200D-2642-FE0F","sheet_x":9,"sheet_y":20},"1F3FF":{"unified":"1F3CA-1F3FF-200D-2642-FE0F","sheet_x":9,"sheet_y":21}}},{"unified":"1F3CA","sheet_x":9,"sheet_y":22,"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB","sheet_x":9,"sheet_y":23},"1F3FC":{"unified":"1F3CA-1F3FC","sheet_x":9,"sheet_y":24},"1F3FD":{"unified":"1F3CA-1F3FD","sheet_x":9,"sheet_y":25},"1F3FE":{"unified":"1F3CA-1F3FE","sheet_x":9,"sheet_y":26},"1F3FF":{"unified":"1F3CA-1F3FF","sheet_x":9,"sheet_y":27}}},{"unified":"1F3CB-FE0F-200D-2640-FE0F","sheet_x":9,"sheet_y":28,"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB-200D-2640-FE0F","sheet_x":9,"sheet_y":29},"1F3FC":{"unified":"1F3CB-1F3FC-200D-2640-FE0F","sheet_x":9,"sheet_y":30},"1F3FD":{"unified":"1F3CB-1F3FD-200D-2640-FE0F","sheet_x":9,"sheet_y":31},"1F3FE":{"unified":"1F3CB-1F3FE-200D-2640-FE0F","sheet_x":9,"sheet_y":32},"1F3FF":{"unified":"1F3CB-1F3FF-200D-2640-FE0F","sheet_x":9,"sheet_y":33}}},{"unified":"1F3CB-FE0F-200D-2642-FE0F","sheet_x":9,"sheet_y":34,"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB-200D-2642-FE0F","sheet_x":9,"sheet_y":35},"1F3FC":{"unified":"1F3CB-1F3FC-200D-2642-FE0F","sheet_x":9,"sheet_y":36},"1F3FD":{"unified":"1F3CB-1F3FD-200D-2642-FE0F","sheet_x":9,"sheet_y":37},"1F3FE":{"unified":"1F3CB-1F3FE-200D-2642-FE0F","sheet_x":9,"sheet_y":38},"1F3FF":{"unified":"1F3CB-1F3FF-200D-2642-FE0F","sheet_x":9,"sheet_y":39}}},{"unified":"1F3CB-FE0F","sheet_x":9,"sheet_y":40,"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB","sheet_x":9,"sheet_y":41},"1F3FC":{"unified":"1F3CB-1F3FC","sheet_x":9,"sheet_y":42},"1F3FD":{"unified":"1F3CB-1F3FD","sheet_x":9,"sheet_y":43},"1F3FE":{"unified":"1F3CB-1F3FE","sheet_x":9,"sheet_y":44},"1F3FF":{"unified":"1F3CB-1F3FF","sheet_x":9,"sheet_y":45}}},{"unified":"1F3CC-FE0F-200D-2640-FE0F","sheet_x":9,"sheet_y":46,"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB-200D-2640-FE0F","sheet_x":9,"sheet_y":47},"1F3FC":{"unified":"1F3CC-1F3FC-200D-2640-FE0F","sheet_x":9,"sheet_y":48},"1F3FD":{"unified":"1F3CC-1F3FD-200D-2640-FE0F","sheet_x":9,"sheet_y":49},"1F3FE":{"unified":"1F3CC-1F3FE-200D-2640-FE0F","sheet_x":9,"sheet_y":50},"1F3FF":{"unified":"1F3CC-1F3FF-200D-2640-FE0F","sheet_x":9,"sheet_y":51}}},{"unified":"1F3CC-FE0F-200D-2642-FE0F","sheet_x":9,"sheet_y":52,"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB-200D-2642-FE0F","sheet_x":9,"sheet_y":53},"1F3FC":{"unified":"1F3CC-1F3FC-200D-2642-FE0F","sheet_x":9,"sheet_y":54},"1F3FD":{"unified":"1F3CC-1F3FD-200D-2642-FE0F","sheet_x":9,"sheet_y":55},"1F3FE":{"unified":"1F3CC-1F3FE-200D-2642-FE0F","sheet_x":9,"sheet_y":56},"1F3FF":{"unified":"1F3CC-1F3FF-200D-2642-FE0F","sheet_x":9,"sheet_y":57}}},{"unified":"1F3CC-FE0F","sheet_x":9,"sheet_y":58,"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB","sheet_x":9,"sheet_y":59},"1F3FC":{"unified":"1F3CC-1F3FC","sheet_x":9,"sheet_y":60},"1F3FD":{"unified":"1F3CC-1F3FD","sheet_x":9,"sheet_y":61},"1F3FE":{"unified":"1F3CC-1F3FE","sheet_x":10,"sheet_y":0},"1F3FF":{"unified":"1F3CC-1F3FF","sheet_x":10,"sheet_y":1}}},{"unified":"1F3CD-FE0F","sheet_x":10,"sheet_y":2,"skin_variations":{}},{"unified":"1F3CE-FE0F","sheet_x":10,"sheet_y":3,"skin_variations":{}},{"unified":"1F3CF","sheet_x":10,"sheet_y":4,"skin_variations":{}},{"unified":"1F3D0","sheet_x":10,"sheet_y":5,"skin_variations":{}},{"unified":"1F3D1","sheet_x":10,"sheet_y":6,"skin_variations":{}},{"unified":"1F3D2","sheet_x":10,"sheet_y":7,"skin_variations":{}},{"unified":"1F3D3","sheet_x":10,"sheet_y":8,"skin_variations":{}},{"unified":"1F3D4-FE0F","sheet_x":10,"sheet_y":9,"skin_variations":{}},{"unified":"1F3D5-FE0F","sheet_x":10,"sheet_y":10,"skin_variations":{}},{"unified":"1F3D6-FE0F","sheet_x":10,"sheet_y":11,"skin_variations":{}},{"unified":"1F3D7-FE0F","sheet_x":10,"sheet_y":12,"skin_variations":{}},{"unified":"1F3D8-FE0F","sheet_x":10,"sheet_y":13,"skin_variations":{}},{"unified":"1F3D9-FE0F","sheet_x":10,"sheet_y":14,"skin_variations":{}},{"unified":"1F3DA-FE0F","sheet_x":10,"sheet_y":15,"skin_variations":{}},{"unified":"1F3DB-FE0F","sheet_x":10,"sheet_y":16,"skin_variations":{}},{"unified":"1F3DC-FE0F","sheet_x":10,"sheet_y":17,"skin_variations":{}},{"unified":"1F3DD-FE0F","sheet_x":10,"sheet_y":18,"skin_variations":{}},{"unified":"1F3DE-FE0F","sheet_x":10,"sheet_y":19,"skin_variations":{}},{"unified":"1F3DF-FE0F","sheet_x":10,"sheet_y":20,"skin_variations":{}},{"unified":"1F3E0","sheet_x":10,"sheet_y":21,"skin_variations":{}},{"unified":"1F3E1","sheet_x":10,"sheet_y":22,"skin_variations":{}},{"unified":"1F3E2","sheet_x":10,"sheet_y":23,"skin_variations":{}},{"unified":"1F3E3","sheet_x":10,"sheet_y":24,"skin_variations":{}},{"unified":"1F3E4","sheet_x":10,"sheet_y":25,"skin_variations":{}},{"unified":"1F3E5","sheet_x":10,"sheet_y":26,"skin_variations":{}},{"unified":"1F3E6","sheet_x":10,"sheet_y":27,"skin_variations":{}},{"unified":"1F3E7","sheet_x":10,"sheet_y":28,"skin_variations":{}},{"unified":"1F3E8","sheet_x":10,"sheet_y":29,"skin_variations":{}},{"unified":"1F3E9","sheet_x":10,"sheet_y":30,"skin_variations":{}},{"unified":"1F3EA","sheet_x":10,"sheet_y":31,"skin_variations":{}},{"unified":"1F3EB","sheet_x":10,"sheet_y":32,"skin_variations":{}},{"unified":"1F3EC","sheet_x":10,"sheet_y":33,"skin_variations":{}},{"unified":"1F3ED","sheet_x":10,"sheet_y":34,"skin_variations":{}},{"unified":"1F3EE","sheet_x":10,"sheet_y":35,"skin_variations":{}},{"unified":"1F3EF","sheet_x":10,"sheet_y":36,"skin_variations":{}},{"unified":"1F3F0","sheet_x":10,"sheet_y":37,"skin_variations":{}},{"unified":"1F3F3-FE0F-200D-1F308","sheet_x":10,"sheet_y":38,"skin_variations":{}},{"unified":"1F3F3-FE0F-200D-26A7-FE0F","sheet_x":10,"sheet_y":39,"skin_variations":{}},{"unified":"1F3F3-FE0F","sheet_x":10,"sheet_y":40,"skin_variations":{}},{"unified":"1F3F4-200D-2620-FE0F","sheet_x":10,"sheet_y":41,"skin_variations":{}},{"unified":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","sheet_x":10,"sheet_y":42,"skin_variations":{}},{"unified":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","sheet_x":10,"sheet_y":43,"skin_variations":{}},{"unified":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","sheet_x":10,"sheet_y":44,"skin_variations":{}},{"unified":"1F3F4","sheet_x":10,"sheet_y":45,"skin_variations":{}},{"unified":"1F3F5-FE0F","sheet_x":10,"sheet_y":46,"skin_variations":{}},{"unified":"1F3F7-FE0F","sheet_x":10,"sheet_y":47,"skin_variations":{}},{"unified":"1F3F8","sheet_x":10,"sheet_y":48,"skin_variations":{}},{"unified":"1F3F9","sheet_x":10,"sheet_y":49,"skin_variations":{}},{"unified":"1F3FA","sheet_x":10,"sheet_y":50,"skin_variations":{}},{"unified":"1F3FB","sheet_x":10,"sheet_y":51,"skin_variations":{}},{"unified":"1F3FC","sheet_x":10,"sheet_y":52,"skin_variations":{}},{"unified":"1F3FD","sheet_x":10,"sheet_y":53,"skin_variations":{}},{"unified":"1F3FE","sheet_x":10,"sheet_y":54,"skin_variations":{}},{"unified":"1F3FF","sheet_x":10,"sheet_y":55,"skin_variations":{}},{"unified":"1F400","sheet_x":10,"sheet_y":56,"skin_variations":{}},{"unified":"1F401","sheet_x":10,"sheet_y":57,"skin_variations":{}},{"unified":"1F402","sheet_x":10,"sheet_y":58,"skin_variations":{}},{"unified":"1F403","sheet_x":10,"sheet_y":59,"skin_variations":{}},{"unified":"1F404","sheet_x":10,"sheet_y":60,"skin_variations":{}},{"unified":"1F405","sheet_x":10,"sheet_y":61,"skin_variations":{}},{"unified":"1F406","sheet_x":11,"sheet_y":0,"skin_variations":{}},{"unified":"1F407","sheet_x":11,"sheet_y":1,"skin_variations":{}},{"unified":"1F408-200D-2B1B","sheet_x":11,"sheet_y":2,"skin_variations":{}},{"unified":"1F408","sheet_x":11,"sheet_y":3,"skin_variations":{}},{"unified":"1F409","sheet_x":11,"sheet_y":4,"skin_variations":{}},{"unified":"1F40A","sheet_x":11,"sheet_y":5,"skin_variations":{}},{"unified":"1F40B","sheet_x":11,"sheet_y":6,"skin_variations":{}},{"unified":"1F40C","sheet_x":11,"sheet_y":7,"skin_variations":{}},{"unified":"1F40D","sheet_x":11,"sheet_y":8,"skin_variations":{}},{"unified":"1F40E","sheet_x":11,"sheet_y":9,"skin_variations":{}},{"unified":"1F40F","sheet_x":11,"sheet_y":10,"skin_variations":{}},{"unified":"1F410","sheet_x":11,"sheet_y":11,"skin_variations":{}},{"unified":"1F411","sheet_x":11,"sheet_y":12,"skin_variations":{}},{"unified":"1F412","sheet_x":11,"sheet_y":13,"skin_variations":{}},{"unified":"1F413","sheet_x":11,"sheet_y":14,"skin_variations":{}},{"unified":"1F414","sheet_x":11,"sheet_y":15,"skin_variations":{}},{"unified":"1F415-200D-1F9BA","sheet_x":11,"sheet_y":16,"skin_variations":{}},{"unified":"1F415","sheet_x":11,"sheet_y":17,"skin_variations":{}},{"unified":"1F416","sheet_x":11,"sheet_y":18,"skin_variations":{}},{"unified":"1F417","sheet_x":11,"sheet_y":19,"skin_variations":{}},{"unified":"1F418","sheet_x":11,"sheet_y":20,"skin_variations":{}},{"unified":"1F419","sheet_x":11,"sheet_y":21,"skin_variations":{}},{"unified":"1F41A","sheet_x":11,"sheet_y":22,"skin_variations":{}},{"unified":"1F41B","sheet_x":11,"sheet_y":23,"skin_variations":{}},{"unified":"1F41C","sheet_x":11,"sheet_y":24,"skin_variations":{}},{"unified":"1F41D","sheet_x":11,"sheet_y":25,"skin_variations":{}},{"unified":"1F41E","sheet_x":11,"sheet_y":26,"skin_variations":{}},{"unified":"1F41F","sheet_x":11,"sheet_y":27,"skin_variations":{}},{"unified":"1F420","sheet_x":11,"sheet_y":28,"skin_variations":{}},{"unified":"1F421","sheet_x":11,"sheet_y":29,"skin_variations":{}},{"unified":"1F422","sheet_x":11,"sheet_y":30,"skin_variations":{}},{"unified":"1F423","sheet_x":11,"sheet_y":31,"skin_variations":{}},{"unified":"1F424","sheet_x":11,"sheet_y":32,"skin_variations":{}},{"unified":"1F425","sheet_x":11,"sheet_y":33,"skin_variations":{}},{"unified":"1F426-200D-1F525","sheet_x":11,"sheet_y":34,"skin_variations":{}},{"unified":"1F426-200D-2B1B","sheet_x":11,"sheet_y":35,"skin_variations":{}},{"unified":"1F426","sheet_x":11,"sheet_y":36,"skin_variations":{}},{"unified":"1F427","sheet_x":11,"sheet_y":37,"skin_variations":{}},{"unified":"1F428","sheet_x":11,"sheet_y":38,"skin_variations":{}},{"unified":"1F429","sheet_x":11,"sheet_y":39,"skin_variations":{}},{"unified":"1F42A","sheet_x":11,"sheet_y":40,"skin_variations":{}},{"unified":"1F42B","sheet_x":11,"sheet_y":41,"skin_variations":{}},{"unified":"1F42C","sheet_x":11,"sheet_y":42,"skin_variations":{}},{"unified":"1F42D","sheet_x":11,"sheet_y":43,"skin_variations":{}},{"unified":"1F42E","sheet_x":11,"sheet_y":44,"skin_variations":{}},{"unified":"1F42F","sheet_x":11,"sheet_y":45,"skin_variations":{}},{"unified":"1F430","sheet_x":11,"sheet_y":46,"skin_variations":{}},{"unified":"1F431","sheet_x":11,"sheet_y":47,"skin_variations":{}},{"unified":"1F432","sheet_x":11,"sheet_y":48,"skin_variations":{}},{"unified":"1F433","sheet_x":11,"sheet_y":49,"skin_variations":{}},{"unified":"1F434","sheet_x":11,"sheet_y":50,"skin_variations":{}},{"unified":"1F435","sheet_x":11,"sheet_y":51,"skin_variations":{}},{"unified":"1F436","sheet_x":11,"sheet_y":52,"skin_variations":{}},{"unified":"1F437","sheet_x":11,"sheet_y":53,"skin_variations":{}},{"unified":"1F438","sheet_x":11,"sheet_y":54,"skin_variations":{}},{"unified":"1F439","sheet_x":11,"sheet_y":55,"skin_variations":{}},{"unified":"1F43A","sheet_x":11,"sheet_y":56,"skin_variations":{}},{"unified":"1F43B-200D-2744-FE0F","sheet_x":11,"sheet_y":57,"skin_variations":{}},{"unified":"1F43B","sheet_x":11,"sheet_y":58,"skin_variations":{}},{"unified":"1F43C","sheet_x":11,"sheet_y":59,"skin_variations":{}},{"unified":"1F43D","sheet_x":11,"sheet_y":60,"skin_variations":{}},{"unified":"1F43E","sheet_x":11,"sheet_y":61,"skin_variations":{}},{"unified":"1F43F-FE0F","sheet_x":12,"sheet_y":0,"skin_variations":{}},{"unified":"1F440","sheet_x":12,"sheet_y":1,"skin_variations":{}},{"unified":"1F441-FE0F-200D-1F5E8-FE0F","sheet_x":12,"sheet_y":2,"skin_variations":{}},{"unified":"1F441-FE0F","sheet_x":12,"sheet_y":3,"skin_variations":{}},{"unified":"1F442","sheet_x":12,"sheet_y":4,"skin_variations":{"1F3FB":{"unified":"1F442-1F3FB","sheet_x":12,"sheet_y":5},"1F3FC":{"unified":"1F442-1F3FC","sheet_x":12,"sheet_y":6},"1F3FD":{"unified":"1F442-1F3FD","sheet_x":12,"sheet_y":7},"1F3FE":{"unified":"1F442-1F3FE","sheet_x":12,"sheet_y":8},"1F3FF":{"unified":"1F442-1F3FF","sheet_x":12,"sheet_y":9}}},{"unified":"1F443","sheet_x":12,"sheet_y":10,"skin_variations":{"1F3FB":{"unified":"1F443-1F3FB","sheet_x":12,"sheet_y":11},"1F3FC":{"unified":"1F443-1F3FC","sheet_x":12,"sheet_y":12},"1F3FD":{"unified":"1F443-1F3FD","sheet_x":12,"sheet_y":13},"1F3FE":{"unified":"1F443-1F3FE","sheet_x":12,"sheet_y":14},"1F3FF":{"unified":"1F443-1F3FF","sheet_x":12,"sheet_y":15}}},{"unified":"1F444","sheet_x":12,"sheet_y":16,"skin_variations":{}},{"unified":"1F445","sheet_x":12,"sheet_y":17,"skin_variations":{}},{"unified":"1F446","sheet_x":12,"sheet_y":18,"skin_variations":{"1F3FB":{"unified":"1F446-1F3FB","sheet_x":12,"sheet_y":19},"1F3FC":{"unified":"1F446-1F3FC","sheet_x":12,"sheet_y":20},"1F3FD":{"unified":"1F446-1F3FD","sheet_x":12,"sheet_y":21},"1F3FE":{"unified":"1F446-1F3FE","sheet_x":12,"sheet_y":22},"1F3FF":{"unified":"1F446-1F3FF","sheet_x":12,"sheet_y":23}}},{"unified":"1F447","sheet_x":12,"sheet_y":24,"skin_variations":{"1F3FB":{"unified":"1F447-1F3FB","sheet_x":12,"sheet_y":25},"1F3FC":{"unified":"1F447-1F3FC","sheet_x":12,"sheet_y":26},"1F3FD":{"unified":"1F447-1F3FD","sheet_x":12,"sheet_y":27},"1F3FE":{"unified":"1F447-1F3FE","sheet_x":12,"sheet_y":28},"1F3FF":{"unified":"1F447-1F3FF","sheet_x":12,"sheet_y":29}}},{"unified":"1F448","sheet_x":12,"sheet_y":30,"skin_variations":{"1F3FB":{"unified":"1F448-1F3FB","sheet_x":12,"sheet_y":31},"1F3FC":{"unified":"1F448-1F3FC","sheet_x":12,"sheet_y":32},"1F3FD":{"unified":"1F448-1F3FD","sheet_x":12,"sheet_y":33},"1F3FE":{"unified":"1F448-1F3FE","sheet_x":12,"sheet_y":34},"1F3FF":{"unified":"1F448-1F3FF","sheet_x":12,"sheet_y":35}}},{"unified":"1F449","sheet_x":12,"sheet_y":36,"skin_variations":{"1F3FB":{"unified":"1F449-1F3FB","sheet_x":12,"sheet_y":37},"1F3FC":{"unified":"1F449-1F3FC","sheet_x":12,"sheet_y":38},"1F3FD":{"unified":"1F449-1F3FD","sheet_x":12,"sheet_y":39},"1F3FE":{"unified":"1F449-1F3FE","sheet_x":12,"sheet_y":40},"1F3FF":{"unified":"1F449-1F3FF","sheet_x":12,"sheet_y":41}}},{"unified":"1F44A","sheet_x":12,"sheet_y":42,"skin_variations":{"1F3FB":{"unified":"1F44A-1F3FB","sheet_x":12,"sheet_y":43},"1F3FC":{"unified":"1F44A-1F3FC","sheet_x":12,"sheet_y":44},"1F3FD":{"unified":"1F44A-1F3FD","sheet_x":12,"sheet_y":45},"1F3FE":{"unified":"1F44A-1F3FE","sheet_x":12,"sheet_y":46},"1F3FF":{"unified":"1F44A-1F3FF","sheet_x":12,"sheet_y":47}}},{"unified":"1F44B","sheet_x":12,"sheet_y":48,"skin_variations":{"1F3FB":{"unified":"1F44B-1F3FB","sheet_x":12,"sheet_y":49},"1F3FC":{"unified":"1F44B-1F3FC","sheet_x":12,"sheet_y":50},"1F3FD":{"unified":"1F44B-1F3FD","sheet_x":12,"sheet_y":51},"1F3FE":{"unified":"1F44B-1F3FE","sheet_x":12,"sheet_y":52},"1F3FF":{"unified":"1F44B-1F3FF","sheet_x":12,"sheet_y":53}}},{"unified":"1F44C","sheet_x":12,"sheet_y":54,"skin_variations":{"1F3FB":{"unified":"1F44C-1F3FB","sheet_x":12,"sheet_y":55},"1F3FC":{"unified":"1F44C-1F3FC","sheet_x":12,"sheet_y":56},"1F3FD":{"unified":"1F44C-1F3FD","sheet_x":12,"sheet_y":57},"1F3FE":{"unified":"1F44C-1F3FE","sheet_x":12,"sheet_y":58},"1F3FF":{"unified":"1F44C-1F3FF","sheet_x":12,"sheet_y":59}}},{"unified":"1F44D","sheet_x":12,"sheet_y":60,"skin_variations":{"1F3FB":{"unified":"1F44D-1F3FB","sheet_x":12,"sheet_y":61},"1F3FC":{"unified":"1F44D-1F3FC","sheet_x":13,"sheet_y":0},"1F3FD":{"unified":"1F44D-1F3FD","sheet_x":13,"sheet_y":1},"1F3FE":{"unified":"1F44D-1F3FE","sheet_x":13,"sheet_y":2},"1F3FF":{"unified":"1F44D-1F3FF","sheet_x":13,"sheet_y":3}}},{"unified":"1F44E","sheet_x":13,"sheet_y":4,"skin_variations":{"1F3FB":{"unified":"1F44E-1F3FB","sheet_x":13,"sheet_y":5},"1F3FC":{"unified":"1F44E-1F3FC","sheet_x":13,"sheet_y":6},"1F3FD":{"unified":"1F44E-1F3FD","sheet_x":13,"sheet_y":7},"1F3FE":{"unified":"1F44E-1F3FE","sheet_x":13,"sheet_y":8},"1F3FF":{"unified":"1F44E-1F3FF","sheet_x":13,"sheet_y":9}}},{"unified":"1F44F","sheet_x":13,"sheet_y":10,"skin_variations":{"1F3FB":{"unified":"1F44F-1F3FB","sheet_x":13,"sheet_y":11},"1F3FC":{"unified":"1F44F-1F3FC","sheet_x":13,"sheet_y":12},"1F3FD":{"unified":"1F44F-1F3FD","sheet_x":13,"sheet_y":13},"1F3FE":{"unified":"1F44F-1F3FE","sheet_x":13,"sheet_y":14},"1F3FF":{"unified":"1F44F-1F3FF","sheet_x":13,"sheet_y":15}}},{"unified":"1F450","sheet_x":13,"sheet_y":16,"skin_variations":{"1F3FB":{"unified":"1F450-1F3FB","sheet_x":13,"sheet_y":17},"1F3FC":{"unified":"1F450-1F3FC","sheet_x":13,"sheet_y":18},"1F3FD":{"unified":"1F450-1F3FD","sheet_x":13,"sheet_y":19},"1F3FE":{"unified":"1F450-1F3FE","sheet_x":13,"sheet_y":20},"1F3FF":{"unified":"1F450-1F3FF","sheet_x":13,"sheet_y":21}}},{"unified":"1F451","sheet_x":13,"sheet_y":22,"skin_variations":{}},{"unified":"1F452","sheet_x":13,"sheet_y":23,"skin_variations":{}},{"unified":"1F453","sheet_x":13,"sheet_y":24,"skin_variations":{}},{"unified":"1F454","sheet_x":13,"sheet_y":25,"skin_variations":{}},{"unified":"1F455","sheet_x":13,"sheet_y":26,"skin_variations":{}},{"unified":"1F456","sheet_x":13,"sheet_y":27,"skin_variations":{}},{"unified":"1F457","sheet_x":13,"sheet_y":28,"skin_variations":{}},{"unified":"1F458","sheet_x":13,"sheet_y":29,"skin_variations":{}},{"unified":"1F459","sheet_x":13,"sheet_y":30,"skin_variations":{}},{"unified":"1F45A","sheet_x":13,"sheet_y":31,"skin_variations":{}},{"unified":"1F45B","sheet_x":13,"sheet_y":32,"skin_variations":{}},{"unified":"1F45C","sheet_x":13,"sheet_y":33,"skin_variations":{}},{"unified":"1F45D","sheet_x":13,"sheet_y":34,"skin_variations":{}},{"unified":"1F45E","sheet_x":13,"sheet_y":35,"skin_variations":{}},{"unified":"1F45F","sheet_x":13,"sheet_y":36,"skin_variations":{}},{"unified":"1F460","sheet_x":13,"sheet_y":37,"skin_variations":{}},{"unified":"1F461","sheet_x":13,"sheet_y":38,"skin_variations":{}},{"unified":"1F462","sheet_x":13,"sheet_y":39,"skin_variations":{}},{"unified":"1F463","sheet_x":13,"sheet_y":40,"skin_variations":{}},{"unified":"1F464","sheet_x":13,"sheet_y":41,"skin_variations":{}},{"unified":"1F465","sheet_x":13,"sheet_y":42,"skin_variations":{}},{"unified":"1F466","sheet_x":13,"sheet_y":43,"skin_variations":{"1F3FB":{"unified":"1F466-1F3FB","sheet_x":13,"sheet_y":44},"1F3FC":{"unified":"1F466-1F3FC","sheet_x":13,"sheet_y":45},"1F3FD":{"unified":"1F466-1F3FD","sheet_x":13,"sheet_y":46},"1F3FE":{"unified":"1F466-1F3FE","sheet_x":13,"sheet_y":47},"1F3FF":{"unified":"1F466-1F3FF","sheet_x":13,"sheet_y":48}}},{"unified":"1F467","sheet_x":13,"sheet_y":49,"skin_variations":{"1F3FB":{"unified":"1F467-1F3FB","sheet_x":13,"sheet_y":50},"1F3FC":{"unified":"1F467-1F3FC","sheet_x":13,"sheet_y":51},"1F3FD":{"unified":"1F467-1F3FD","sheet_x":13,"sheet_y":52},"1F3FE":{"unified":"1F467-1F3FE","sheet_x":13,"sheet_y":53},"1F3FF":{"unified":"1F467-1F3FF","sheet_x":13,"sheet_y":54}}},{"unified":"1F468-200D-1F33E","sheet_x":13,"sheet_y":55,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F33E","sheet_x":13,"sheet_y":56},"1F3FC":{"unified":"1F468-1F3FC-200D-1F33E","sheet_x":13,"sheet_y":57},"1F3FD":{"unified":"1F468-1F3FD-200D-1F33E","sheet_x":13,"sheet_y":58},"1F3FE":{"unified":"1F468-1F3FE-200D-1F33E","sheet_x":13,"sheet_y":59},"1F3FF":{"unified":"1F468-1F3FF-200D-1F33E","sheet_x":13,"sheet_y":60}}},{"unified":"1F468-200D-1F373","sheet_x":13,"sheet_y":61,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F373","sheet_x":14,"sheet_y":0},"1F3FC":{"unified":"1F468-1F3FC-200D-1F373","sheet_x":14,"sheet_y":1},"1F3FD":{"unified":"1F468-1F3FD-200D-1F373","sheet_x":14,"sheet_y":2},"1F3FE":{"unified":"1F468-1F3FE-200D-1F373","sheet_x":14,"sheet_y":3},"1F3FF":{"unified":"1F468-1F3FF-200D-1F373","sheet_x":14,"sheet_y":4}}},{"unified":"1F468-200D-1F37C","sheet_x":14,"sheet_y":5,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F37C","sheet_x":14,"sheet_y":6},"1F3FC":{"unified":"1F468-1F3FC-200D-1F37C","sheet_x":14,"sheet_y":7},"1F3FD":{"unified":"1F468-1F3FD-200D-1F37C","sheet_x":14,"sheet_y":8},"1F3FE":{"unified":"1F468-1F3FE-200D-1F37C","sheet_x":14,"sheet_y":9},"1F3FF":{"unified":"1F468-1F3FF-200D-1F37C","sheet_x":14,"sheet_y":10}}},{"unified":"1F468-200D-1F393","sheet_x":14,"sheet_y":11,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F393","sheet_x":14,"sheet_y":12},"1F3FC":{"unified":"1F468-1F3FC-200D-1F393","sheet_x":14,"sheet_y":13},"1F3FD":{"unified":"1F468-1F3FD-200D-1F393","sheet_x":14,"sheet_y":14},"1F3FE":{"unified":"1F468-1F3FE-200D-1F393","sheet_x":14,"sheet_y":15},"1F3FF":{"unified":"1F468-1F3FF-200D-1F393","sheet_x":14,"sheet_y":16}}},{"unified":"1F468-200D-1F3A4","sheet_x":14,"sheet_y":17,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3A4","sheet_x":14,"sheet_y":18},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3A4","sheet_x":14,"sheet_y":19},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3A4","sheet_x":14,"sheet_y":20},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3A4","sheet_x":14,"sheet_y":21},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3A4","sheet_x":14,"sheet_y":22}}},{"unified":"1F468-200D-1F3A8","sheet_x":14,"sheet_y":23,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3A8","sheet_x":14,"sheet_y":24},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3A8","sheet_x":14,"sheet_y":25},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3A8","sheet_x":14,"sheet_y":26},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3A8","sheet_x":14,"sheet_y":27},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3A8","sheet_x":14,"sheet_y":28}}},{"unified":"1F468-200D-1F3EB","sheet_x":14,"sheet_y":29,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3EB","sheet_x":14,"sheet_y":30},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3EB","sheet_x":14,"sheet_y":31},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3EB","sheet_x":14,"sheet_y":32},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3EB","sheet_x":14,"sheet_y":33},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3EB","sheet_x":14,"sheet_y":34}}},{"unified":"1F468-200D-1F3ED","sheet_x":14,"sheet_y":35,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3ED","sheet_x":14,"sheet_y":36},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3ED","sheet_x":14,"sheet_y":37},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3ED","sheet_x":14,"sheet_y":38},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3ED","sheet_x":14,"sheet_y":39},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3ED","sheet_x":14,"sheet_y":40}}},{"unified":"1F468-200D-1F466-200D-1F466","sheet_x":14,"sheet_y":41,"skin_variations":{}},{"unified":"1F468-200D-1F466","sheet_x":14,"sheet_y":42,"skin_variations":{}},{"unified":"1F468-200D-1F467-200D-1F466","sheet_x":14,"sheet_y":43,"skin_variations":{}},{"unified":"1F468-200D-1F467-200D-1F467","sheet_x":14,"sheet_y":44,"skin_variations":{}},{"unified":"1F468-200D-1F467","sheet_x":14,"sheet_y":45,"skin_variations":{}},{"unified":"1F468-200D-1F468-200D-1F466","sheet_x":14,"sheet_y":46,"skin_variations":{}},{"unified":"1F468-200D-1F468-200D-1F466-200D-1F466","sheet_x":14,"sheet_y":47,"skin_variations":{}},{"unified":"1F468-200D-1F468-200D-1F467","sheet_x":14,"sheet_y":48,"skin_variations":{}},{"unified":"1F468-200D-1F468-200D-1F467-200D-1F466","sheet_x":14,"sheet_y":49,"skin_variations":{}},{"unified":"1F468-200D-1F468-200D-1F467-200D-1F467","sheet_x":14,"sheet_y":50,"skin_variations":{}},{"unified":"1F468-200D-1F469-200D-1F466","sheet_x":14,"sheet_y":51,"skin_variations":{}},{"unified":"1F468-200D-1F469-200D-1F466-200D-1F466","sheet_x":14,"sheet_y":52,"skin_variations":{}},{"unified":"1F468-200D-1F469-200D-1F467","sheet_x":14,"sheet_y":53,"skin_variations":{}},{"unified":"1F468-200D-1F469-200D-1F467-200D-1F466","sheet_x":14,"sheet_y":54,"skin_variations":{}},{"unified":"1F468-200D-1F469-200D-1F467-200D-1F467","sheet_x":14,"sheet_y":55,"skin_variations":{}},{"unified":"1F468-200D-1F4BB","sheet_x":14,"sheet_y":56,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F4BB","sheet_x":14,"sheet_y":57},"1F3FC":{"unified":"1F468-1F3FC-200D-1F4BB","sheet_x":14,"sheet_y":58},"1F3FD":{"unified":"1F468-1F3FD-200D-1F4BB","sheet_x":14,"sheet_y":59},"1F3FE":{"unified":"1F468-1F3FE-200D-1F4BB","sheet_x":14,"sheet_y":60},"1F3FF":{"unified":"1F468-1F3FF-200D-1F4BB","sheet_x":14,"sheet_y":61}}},{"unified":"1F468-200D-1F4BC","sheet_x":15,"sheet_y":0,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F4BC","sheet_x":15,"sheet_y":1},"1F3FC":{"unified":"1F468-1F3FC-200D-1F4BC","sheet_x":15,"sheet_y":2},"1F3FD":{"unified":"1F468-1F3FD-200D-1F4BC","sheet_x":15,"sheet_y":3},"1F3FE":{"unified":"1F468-1F3FE-200D-1F4BC","sheet_x":15,"sheet_y":4},"1F3FF":{"unified":"1F468-1F3FF-200D-1F4BC","sheet_x":15,"sheet_y":5}}},{"unified":"1F468-200D-1F527","sheet_x":15,"sheet_y":6,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F527","sheet_x":15,"sheet_y":7},"1F3FC":{"unified":"1F468-1F3FC-200D-1F527","sheet_x":15,"sheet_y":8},"1F3FD":{"unified":"1F468-1F3FD-200D-1F527","sheet_x":15,"sheet_y":9},"1F3FE":{"unified":"1F468-1F3FE-200D-1F527","sheet_x":15,"sheet_y":10},"1F3FF":{"unified":"1F468-1F3FF-200D-1F527","sheet_x":15,"sheet_y":11}}},{"unified":"1F468-200D-1F52C","sheet_x":15,"sheet_y":12,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F52C","sheet_x":15,"sheet_y":13},"1F3FC":{"unified":"1F468-1F3FC-200D-1F52C","sheet_x":15,"sheet_y":14},"1F3FD":{"unified":"1F468-1F3FD-200D-1F52C","sheet_x":15,"sheet_y":15},"1F3FE":{"unified":"1F468-1F3FE-200D-1F52C","sheet_x":15,"sheet_y":16},"1F3FF":{"unified":"1F468-1F3FF-200D-1F52C","sheet_x":15,"sheet_y":17}}},{"unified":"1F468-200D-1F680","sheet_x":15,"sheet_y":18,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F680","sheet_x":15,"sheet_y":19},"1F3FC":{"unified":"1F468-1F3FC-200D-1F680","sheet_x":15,"sheet_y":20},"1F3FD":{"unified":"1F468-1F3FD-200D-1F680","sheet_x":15,"sheet_y":21},"1F3FE":{"unified":"1F468-1F3FE-200D-1F680","sheet_x":15,"sheet_y":22},"1F3FF":{"unified":"1F468-1F3FF-200D-1F680","sheet_x":15,"sheet_y":23}}},{"unified":"1F468-200D-1F692","sheet_x":15,"sheet_y":24,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F692","sheet_x":15,"sheet_y":25},"1F3FC":{"unified":"1F468-1F3FC-200D-1F692","sheet_x":15,"sheet_y":26},"1F3FD":{"unified":"1F468-1F3FD-200D-1F692","sheet_x":15,"sheet_y":27},"1F3FE":{"unified":"1F468-1F3FE-200D-1F692","sheet_x":15,"sheet_y":28},"1F3FF":{"unified":"1F468-1F3FF-200D-1F692","sheet_x":15,"sheet_y":29}}},{"unified":"1F468-200D-1F9AF-200D-27A1-FE0F","sheet_x":15,"sheet_y":30,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9AF-200D-27A1-FE0F","sheet_x":15,"sheet_y":31},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9AF-200D-27A1-FE0F","sheet_x":15,"sheet_y":32},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9AF-200D-27A1-FE0F","sheet_x":15,"sheet_y":33},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9AF-200D-27A1-FE0F","sheet_x":15,"sheet_y":34},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9AF-200D-27A1-FE0F","sheet_x":15,"sheet_y":35}}},{"unified":"1F468-200D-1F9AF","sheet_x":15,"sheet_y":36,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9AF","sheet_x":15,"sheet_y":37},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9AF","sheet_x":15,"sheet_y":38},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9AF","sheet_x":15,"sheet_y":39},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9AF","sheet_x":15,"sheet_y":40},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9AF","sheet_x":15,"sheet_y":41}}},{"unified":"1F468-200D-1F9B0","sheet_x":15,"sheet_y":42,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9B0","sheet_x":15,"sheet_y":43},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9B0","sheet_x":15,"sheet_y":44},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9B0","sheet_x":15,"sheet_y":45},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9B0","sheet_x":15,"sheet_y":46},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9B0","sheet_x":15,"sheet_y":47}}},{"unified":"1F468-200D-1F9B1","sheet_x":15,"sheet_y":48,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9B1","sheet_x":15,"sheet_y":49},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9B1","sheet_x":15,"sheet_y":50},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9B1","sheet_x":15,"sheet_y":51},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9B1","sheet_x":15,"sheet_y":52},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9B1","sheet_x":15,"sheet_y":53}}},{"unified":"1F468-200D-1F9B2","sheet_x":15,"sheet_y":54,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9B2","sheet_x":15,"sheet_y":55},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9B2","sheet_x":15,"sheet_y":56},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9B2","sheet_x":15,"sheet_y":57},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9B2","sheet_x":15,"sheet_y":58},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9B2","sheet_x":15,"sheet_y":59}}},{"unified":"1F468-200D-1F9B3","sheet_x":15,"sheet_y":60,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9B3","sheet_x":15,"sheet_y":61},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9B3","sheet_x":16,"sheet_y":0},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9B3","sheet_x":16,"sheet_y":1},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9B3","sheet_x":16,"sheet_y":2},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9B3","sheet_x":16,"sheet_y":3}}},{"unified":"1F468-200D-1F9BC-200D-27A1-FE0F","sheet_x":16,"sheet_y":4,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9BC-200D-27A1-FE0F","sheet_x":16,"sheet_y":5},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9BC-200D-27A1-FE0F","sheet_x":16,"sheet_y":6},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9BC-200D-27A1-FE0F","sheet_x":16,"sheet_y":7},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9BC-200D-27A1-FE0F","sheet_x":16,"sheet_y":8},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9BC-200D-27A1-FE0F","sheet_x":16,"sheet_y":9}}},{"unified":"1F468-200D-1F9BC","sheet_x":16,"sheet_y":10,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9BC","sheet_x":16,"sheet_y":11},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9BC","sheet_x":16,"sheet_y":12},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9BC","sheet_x":16,"sheet_y":13},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9BC","sheet_x":16,"sheet_y":14},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9BC","sheet_x":16,"sheet_y":15}}},{"unified":"1F468-200D-1F9BD-200D-27A1-FE0F","sheet_x":16,"sheet_y":16,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9BD-200D-27A1-FE0F","sheet_x":16,"sheet_y":17},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9BD-200D-27A1-FE0F","sheet_x":16,"sheet_y":18},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9BD-200D-27A1-FE0F","sheet_x":16,"sheet_y":19},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9BD-200D-27A1-FE0F","sheet_x":16,"sheet_y":20},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9BD-200D-27A1-FE0F","sheet_x":16,"sheet_y":21}}},{"unified":"1F468-200D-1F9BD","sheet_x":16,"sheet_y":22,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9BD","sheet_x":16,"sheet_y":23},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9BD","sheet_x":16,"sheet_y":24},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9BD","sheet_x":16,"sheet_y":25},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9BD","sheet_x":16,"sheet_y":26},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9BD","sheet_x":16,"sheet_y":27}}},{"unified":"1F468-200D-2695-FE0F","sheet_x":16,"sheet_y":28,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2695-FE0F","sheet_x":16,"sheet_y":29},"1F3FC":{"unified":"1F468-1F3FC-200D-2695-FE0F","sheet_x":16,"sheet_y":30},"1F3FD":{"unified":"1F468-1F3FD-200D-2695-FE0F","sheet_x":16,"sheet_y":31},"1F3FE":{"unified":"1F468-1F3FE-200D-2695-FE0F","sheet_x":16,"sheet_y":32},"1F3FF":{"unified":"1F468-1F3FF-200D-2695-FE0F","sheet_x":16,"sheet_y":33}}},{"unified":"1F468-200D-2696-FE0F","sheet_x":16,"sheet_y":34,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2696-FE0F","sheet_x":16,"sheet_y":35},"1F3FC":{"unified":"1F468-1F3FC-200D-2696-FE0F","sheet_x":16,"sheet_y":36},"1F3FD":{"unified":"1F468-1F3FD-200D-2696-FE0F","sheet_x":16,"sheet_y":37},"1F3FE":{"unified":"1F468-1F3FE-200D-2696-FE0F","sheet_x":16,"sheet_y":38},"1F3FF":{"unified":"1F468-1F3FF-200D-2696-FE0F","sheet_x":16,"sheet_y":39}}},{"unified":"1F468-200D-2708-FE0F","sheet_x":16,"sheet_y":40,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2708-FE0F","sheet_x":16,"sheet_y":41},"1F3FC":{"unified":"1F468-1F3FC-200D-2708-FE0F","sheet_x":16,"sheet_y":42},"1F3FD":{"unified":"1F468-1F3FD-200D-2708-FE0F","sheet_x":16,"sheet_y":43},"1F3FE":{"unified":"1F468-1F3FE-200D-2708-FE0F","sheet_x":16,"sheet_y":44},"1F3FF":{"unified":"1F468-1F3FF-200D-2708-FE0F","sheet_x":16,"sheet_y":45}}},{"unified":"1F468-200D-2764-FE0F-200D-1F468","sheet_x":16,"sheet_y":46,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FB","sheet_x":16,"sheet_y":47},"1F3FB-1F3FC":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FC","sheet_x":16,"sheet_y":48},"1F3FB-1F3FD":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FD","sheet_x":16,"sheet_y":49},"1F3FB-1F3FE":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FE","sheet_x":16,"sheet_y":50},"1F3FB-1F3FF":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FF","sheet_x":16,"sheet_y":51},"1F3FC-1F3FB":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FB","sheet_x":16,"sheet_y":52},"1F3FC-1F3FC":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FC","sheet_x":16,"sheet_y":53},"1F3FC-1F3FD":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FD","sheet_x":16,"sheet_y":54},"1F3FC-1F3FE":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FE","sheet_x":16,"sheet_y":55},"1F3FC-1F3FF":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FF","sheet_x":16,"sheet_y":56},"1F3FD-1F3FB":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FB","sheet_x":16,"sheet_y":57},"1F3FD-1F3FC":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FC","sheet_x":16,"sheet_y":58},"1F3FD-1F3FD":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FD","sheet_x":16,"sheet_y":59},"1F3FD-1F3FE":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FE","sheet_x":16,"sheet_y":60},"1F3FD-1F3FF":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FF","sheet_x":16,"sheet_y":61},"1F3FE-1F3FB":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FB","sheet_x":17,"sheet_y":0},"1F3FE-1F3FC":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FC","sheet_x":17,"sheet_y":1},"1F3FE-1F3FD":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FD","sheet_x":17,"sheet_y":2},"1F3FE-1F3FE":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FE","sheet_x":17,"sheet_y":3},"1F3FE-1F3FF":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FF","sheet_x":17,"sheet_y":4},"1F3FF-1F3FB":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FB","sheet_x":17,"sheet_y":5},"1F3FF-1F3FC":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FC","sheet_x":17,"sheet_y":6},"1F3FF-1F3FD":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FD","sheet_x":17,"sheet_y":7},"1F3FF-1F3FE":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FE","sheet_x":17,"sheet_y":8},"1F3FF-1F3FF":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FF","sheet_x":17,"sheet_y":9}}},{"unified":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","sheet_x":17,"sheet_y":10,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","sheet_x":17,"sheet_y":11},"1F3FB-1F3FC":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","sheet_x":17,"sheet_y":12},"1F3FB-1F3FD":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","sheet_x":17,"sheet_y":13},"1F3FB-1F3FE":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","sheet_x":17,"sheet_y":14},"1F3FB-1F3FF":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","sheet_x":17,"sheet_y":15},"1F3FC-1F3FB":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","sheet_x":17,"sheet_y":16},"1F3FC-1F3FC":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","sheet_x":17,"sheet_y":17},"1F3FC-1F3FD":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","sheet_x":17,"sheet_y":18},"1F3FC-1F3FE":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","sheet_x":17,"sheet_y":19},"1F3FC-1F3FF":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","sheet_x":17,"sheet_y":20},"1F3FD-1F3FB":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","sheet_x":17,"sheet_y":21},"1F3FD-1F3FC":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","sheet_x":17,"sheet_y":22},"1F3FD-1F3FD":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","sheet_x":17,"sheet_y":23},"1F3FD-1F3FE":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","sheet_x":17,"sheet_y":24},"1F3FD-1F3FF":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","sheet_x":17,"sheet_y":25},"1F3FE-1F3FB":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","sheet_x":17,"sheet_y":26},"1F3FE-1F3FC":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","sheet_x":17,"sheet_y":27},"1F3FE-1F3FD":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","sheet_x":17,"sheet_y":28},"1F3FE-1F3FE":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","sheet_x":17,"sheet_y":29},"1F3FE-1F3FF":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","sheet_x":17,"sheet_y":30},"1F3FF-1F3FB":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","sheet_x":17,"sheet_y":31},"1F3FF-1F3FC":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","sheet_x":17,"sheet_y":32},"1F3FF-1F3FD":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","sheet_x":17,"sheet_y":33},"1F3FF-1F3FE":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","sheet_x":17,"sheet_y":34},"1F3FF-1F3FF":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","sheet_x":17,"sheet_y":35}}},{"unified":"1F468","sheet_x":17,"sheet_y":36,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB","sheet_x":17,"sheet_y":37},"1F3FC":{"unified":"1F468-1F3FC","sheet_x":17,"sheet_y":38},"1F3FD":{"unified":"1F468-1F3FD","sheet_x":17,"sheet_y":39},"1F3FE":{"unified":"1F468-1F3FE","sheet_x":17,"sheet_y":40},"1F3FF":{"unified":"1F468-1F3FF","sheet_x":17,"sheet_y":41}}},{"unified":"1F469-200D-1F33E","sheet_x":17,"sheet_y":42,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F33E","sheet_x":17,"sheet_y":43},"1F3FC":{"unified":"1F469-1F3FC-200D-1F33E","sheet_x":17,"sheet_y":44},"1F3FD":{"unified":"1F469-1F3FD-200D-1F33E","sheet_x":17,"sheet_y":45},"1F3FE":{"unified":"1F469-1F3FE-200D-1F33E","sheet_x":17,"sheet_y":46},"1F3FF":{"unified":"1F469-1F3FF-200D-1F33E","sheet_x":17,"sheet_y":47}}},{"unified":"1F469-200D-1F373","sheet_x":17,"sheet_y":48,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F373","sheet_x":17,"sheet_y":49},"1F3FC":{"unified":"1F469-1F3FC-200D-1F373","sheet_x":17,"sheet_y":50},"1F3FD":{"unified":"1F469-1F3FD-200D-1F373","sheet_x":17,"sheet_y":51},"1F3FE":{"unified":"1F469-1F3FE-200D-1F373","sheet_x":17,"sheet_y":52},"1F3FF":{"unified":"1F469-1F3FF-200D-1F373","sheet_x":17,"sheet_y":53}}},{"unified":"1F469-200D-1F37C","sheet_x":17,"sheet_y":54,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F37C","sheet_x":17,"sheet_y":55},"1F3FC":{"unified":"1F469-1F3FC-200D-1F37C","sheet_x":17,"sheet_y":56},"1F3FD":{"unified":"1F469-1F3FD-200D-1F37C","sheet_x":17,"sheet_y":57},"1F3FE":{"unified":"1F469-1F3FE-200D-1F37C","sheet_x":17,"sheet_y":58},"1F3FF":{"unified":"1F469-1F3FF-200D-1F37C","sheet_x":17,"sheet_y":59}}},{"unified":"1F469-200D-1F393","sheet_x":17,"sheet_y":60,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F393","sheet_x":17,"sheet_y":61},"1F3FC":{"unified":"1F469-1F3FC-200D-1F393","sheet_x":18,"sheet_y":0},"1F3FD":{"unified":"1F469-1F3FD-200D-1F393","sheet_x":18,"sheet_y":1},"1F3FE":{"unified":"1F469-1F3FE-200D-1F393","sheet_x":18,"sheet_y":2},"1F3FF":{"unified":"1F469-1F3FF-200D-1F393","sheet_x":18,"sheet_y":3}}},{"unified":"1F469-200D-1F3A4","sheet_x":18,"sheet_y":4,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3A4","sheet_x":18,"sheet_y":5},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3A4","sheet_x":18,"sheet_y":6},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3A4","sheet_x":18,"sheet_y":7},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3A4","sheet_x":18,"sheet_y":8},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3A4","sheet_x":18,"sheet_y":9}}},{"unified":"1F469-200D-1F3A8","sheet_x":18,"sheet_y":10,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3A8","sheet_x":18,"sheet_y":11},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3A8","sheet_x":18,"sheet_y":12},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3A8","sheet_x":18,"sheet_y":13},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3A8","sheet_x":18,"sheet_y":14},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3A8","sheet_x":18,"sheet_y":15}}},{"unified":"1F469-200D-1F3EB","sheet_x":18,"sheet_y":16,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3EB","sheet_x":18,"sheet_y":17},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3EB","sheet_x":18,"sheet_y":18},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3EB","sheet_x":18,"sheet_y":19},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3EB","sheet_x":18,"sheet_y":20},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3EB","sheet_x":18,"sheet_y":21}}},{"unified":"1F469-200D-1F3ED","sheet_x":18,"sheet_y":22,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3ED","sheet_x":18,"sheet_y":23},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3ED","sheet_x":18,"sheet_y":24},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3ED","sheet_x":18,"sheet_y":25},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3ED","sheet_x":18,"sheet_y":26},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3ED","sheet_x":18,"sheet_y":27}}},{"unified":"1F469-200D-1F466-200D-1F466","sheet_x":18,"sheet_y":28,"skin_variations":{}},{"unified":"1F469-200D-1F466","sheet_x":18,"sheet_y":29,"skin_variations":{}},{"unified":"1F469-200D-1F467-200D-1F466","sheet_x":18,"sheet_y":30,"skin_variations":{}},{"unified":"1F469-200D-1F467-200D-1F467","sheet_x":18,"sheet_y":31,"skin_variations":{}},{"unified":"1F469-200D-1F467","sheet_x":18,"sheet_y":32,"skin_variations":{}},{"unified":"1F469-200D-1F469-200D-1F466","sheet_x":18,"sheet_y":33,"skin_variations":{}},{"unified":"1F469-200D-1F469-200D-1F466-200D-1F466","sheet_x":18,"sheet_y":34,"skin_variations":{}},{"unified":"1F469-200D-1F469-200D-1F467","sheet_x":18,"sheet_y":35,"skin_variations":{}},{"unified":"1F469-200D-1F469-200D-1F467-200D-1F466","sheet_x":18,"sheet_y":36,"skin_variations":{}},{"unified":"1F469-200D-1F469-200D-1F467-200D-1F467","sheet_x":18,"sheet_y":37,"skin_variations":{}},{"unified":"1F469-200D-1F4BB","sheet_x":18,"sheet_y":38,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F4BB","sheet_x":18,"sheet_y":39},"1F3FC":{"unified":"1F469-1F3FC-200D-1F4BB","sheet_x":18,"sheet_y":40},"1F3FD":{"unified":"1F469-1F3FD-200D-1F4BB","sheet_x":18,"sheet_y":41},"1F3FE":{"unified":"1F469-1F3FE-200D-1F4BB","sheet_x":18,"sheet_y":42},"1F3FF":{"unified":"1F469-1F3FF-200D-1F4BB","sheet_x":18,"sheet_y":43}}},{"unified":"1F469-200D-1F4BC","sheet_x":18,"sheet_y":44,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F4BC","sheet_x":18,"sheet_y":45},"1F3FC":{"unified":"1F469-1F3FC-200D-1F4BC","sheet_x":18,"sheet_y":46},"1F3FD":{"unified":"1F469-1F3FD-200D-1F4BC","sheet_x":18,"sheet_y":47},"1F3FE":{"unified":"1F469-1F3FE-200D-1F4BC","sheet_x":18,"sheet_y":48},"1F3FF":{"unified":"1F469-1F3FF-200D-1F4BC","sheet_x":18,"sheet_y":49}}},{"unified":"1F469-200D-1F527","sheet_x":18,"sheet_y":50,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F527","sheet_x":18,"sheet_y":51},"1F3FC":{"unified":"1F469-1F3FC-200D-1F527","sheet_x":18,"sheet_y":52},"1F3FD":{"unified":"1F469-1F3FD-200D-1F527","sheet_x":18,"sheet_y":53},"1F3FE":{"unified":"1F469-1F3FE-200D-1F527","sheet_x":18,"sheet_y":54},"1F3FF":{"unified":"1F469-1F3FF-200D-1F527","sheet_x":18,"sheet_y":55}}},{"unified":"1F469-200D-1F52C","sheet_x":18,"sheet_y":56,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F52C","sheet_x":18,"sheet_y":57},"1F3FC":{"unified":"1F469-1F3FC-200D-1F52C","sheet_x":18,"sheet_y":58},"1F3FD":{"unified":"1F469-1F3FD-200D-1F52C","sheet_x":18,"sheet_y":59},"1F3FE":{"unified":"1F469-1F3FE-200D-1F52C","sheet_x":18,"sheet_y":60},"1F3FF":{"unified":"1F469-1F3FF-200D-1F52C","sheet_x":18,"sheet_y":61}}},{"unified":"1F469-200D-1F680","sheet_x":19,"sheet_y":0,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F680","sheet_x":19,"sheet_y":1},"1F3FC":{"unified":"1F469-1F3FC-200D-1F680","sheet_x":19,"sheet_y":2},"1F3FD":{"unified":"1F469-1F3FD-200D-1F680","sheet_x":19,"sheet_y":3},"1F3FE":{"unified":"1F469-1F3FE-200D-1F680","sheet_x":19,"sheet_y":4},"1F3FF":{"unified":"1F469-1F3FF-200D-1F680","sheet_x":19,"sheet_y":5}}},{"unified":"1F469-200D-1F692","sheet_x":19,"sheet_y":6,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F692","sheet_x":19,"sheet_y":7},"1F3FC":{"unified":"1F469-1F3FC-200D-1F692","sheet_x":19,"sheet_y":8},"1F3FD":{"unified":"1F469-1F3FD-200D-1F692","sheet_x":19,"sheet_y":9},"1F3FE":{"unified":"1F469-1F3FE-200D-1F692","sheet_x":19,"sheet_y":10},"1F3FF":{"unified":"1F469-1F3FF-200D-1F692","sheet_x":19,"sheet_y":11}}},{"unified":"1F469-200D-1F9AF-200D-27A1-FE0F","sheet_x":19,"sheet_y":12,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9AF-200D-27A1-FE0F","sheet_x":19,"sheet_y":13},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9AF-200D-27A1-FE0F","sheet_x":19,"sheet_y":14},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9AF-200D-27A1-FE0F","sheet_x":19,"sheet_y":15},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9AF-200D-27A1-FE0F","sheet_x":19,"sheet_y":16},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9AF-200D-27A1-FE0F","sheet_x":19,"sheet_y":17}}},{"unified":"1F469-200D-1F9AF","sheet_x":19,"sheet_y":18,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9AF","sheet_x":19,"sheet_y":19},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9AF","sheet_x":19,"sheet_y":20},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9AF","sheet_x":19,"sheet_y":21},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9AF","sheet_x":19,"sheet_y":22},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9AF","sheet_x":19,"sheet_y":23}}},{"unified":"1F469-200D-1F9B0","sheet_x":19,"sheet_y":24,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9B0","sheet_x":19,"sheet_y":25},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9B0","sheet_x":19,"sheet_y":26},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9B0","sheet_x":19,"sheet_y":27},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9B0","sheet_x":19,"sheet_y":28},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9B0","sheet_x":19,"sheet_y":29}}},{"unified":"1F469-200D-1F9B1","sheet_x":19,"sheet_y":30,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9B1","sheet_x":19,"sheet_y":31},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9B1","sheet_x":19,"sheet_y":32},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9B1","sheet_x":19,"sheet_y":33},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9B1","sheet_x":19,"sheet_y":34},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9B1","sheet_x":19,"sheet_y":35}}},{"unified":"1F469-200D-1F9B2","sheet_x":19,"sheet_y":36,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9B2","sheet_x":19,"sheet_y":37},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9B2","sheet_x":19,"sheet_y":38},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9B2","sheet_x":19,"sheet_y":39},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9B2","sheet_x":19,"sheet_y":40},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9B2","sheet_x":19,"sheet_y":41}}},{"unified":"1F469-200D-1F9B3","sheet_x":19,"sheet_y":42,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9B3","sheet_x":19,"sheet_y":43},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9B3","sheet_x":19,"sheet_y":44},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9B3","sheet_x":19,"sheet_y":45},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9B3","sheet_x":19,"sheet_y":46},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9B3","sheet_x":19,"sheet_y":47}}},{"unified":"1F469-200D-1F9BC-200D-27A1-FE0F","sheet_x":19,"sheet_y":48,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9BC-200D-27A1-FE0F","sheet_x":19,"sheet_y":49},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9BC-200D-27A1-FE0F","sheet_x":19,"sheet_y":50},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9BC-200D-27A1-FE0F","sheet_x":19,"sheet_y":51},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9BC-200D-27A1-FE0F","sheet_x":19,"sheet_y":52},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9BC-200D-27A1-FE0F","sheet_x":19,"sheet_y":53}}},{"unified":"1F469-200D-1F9BC","sheet_x":19,"sheet_y":54,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9BC","sheet_x":19,"sheet_y":55},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9BC","sheet_x":19,"sheet_y":56},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9BC","sheet_x":19,"sheet_y":57},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9BC","sheet_x":19,"sheet_y":58},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9BC","sheet_x":19,"sheet_y":59}}},{"unified":"1F469-200D-1F9BD-200D-27A1-FE0F","sheet_x":19,"sheet_y":60,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9BD-200D-27A1-FE0F","sheet_x":19,"sheet_y":61},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9BD-200D-27A1-FE0F","sheet_x":20,"sheet_y":0},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9BD-200D-27A1-FE0F","sheet_x":20,"sheet_y":1},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9BD-200D-27A1-FE0F","sheet_x":20,"sheet_y":2},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9BD-200D-27A1-FE0F","sheet_x":20,"sheet_y":3}}},{"unified":"1F469-200D-1F9BD","sheet_x":20,"sheet_y":4,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9BD","sheet_x":20,"sheet_y":5},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9BD","sheet_x":20,"sheet_y":6},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9BD","sheet_x":20,"sheet_y":7},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9BD","sheet_x":20,"sheet_y":8},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9BD","sheet_x":20,"sheet_y":9}}},{"unified":"1F469-200D-2695-FE0F","sheet_x":20,"sheet_y":10,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2695-FE0F","sheet_x":20,"sheet_y":11},"1F3FC":{"unified":"1F469-1F3FC-200D-2695-FE0F","sheet_x":20,"sheet_y":12},"1F3FD":{"unified":"1F469-1F3FD-200D-2695-FE0F","sheet_x":20,"sheet_y":13},"1F3FE":{"unified":"1F469-1F3FE-200D-2695-FE0F","sheet_x":20,"sheet_y":14},"1F3FF":{"unified":"1F469-1F3FF-200D-2695-FE0F","sheet_x":20,"sheet_y":15}}},{"unified":"1F469-200D-2696-FE0F","sheet_x":20,"sheet_y":16,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2696-FE0F","sheet_x":20,"sheet_y":17},"1F3FC":{"unified":"1F469-1F3FC-200D-2696-FE0F","sheet_x":20,"sheet_y":18},"1F3FD":{"unified":"1F469-1F3FD-200D-2696-FE0F","sheet_x":20,"sheet_y":19},"1F3FE":{"unified":"1F469-1F3FE-200D-2696-FE0F","sheet_x":20,"sheet_y":20},"1F3FF":{"unified":"1F469-1F3FF-200D-2696-FE0F","sheet_x":20,"sheet_y":21}}},{"unified":"1F469-200D-2708-FE0F","sheet_x":20,"sheet_y":22,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2708-FE0F","sheet_x":20,"sheet_y":23},"1F3FC":{"unified":"1F469-1F3FC-200D-2708-FE0F","sheet_x":20,"sheet_y":24},"1F3FD":{"unified":"1F469-1F3FD-200D-2708-FE0F","sheet_x":20,"sheet_y":25},"1F3FE":{"unified":"1F469-1F3FE-200D-2708-FE0F","sheet_x":20,"sheet_y":26},"1F3FF":{"unified":"1F469-1F3FF-200D-2708-FE0F","sheet_x":20,"sheet_y":27}}},{"unified":"1F469-200D-2764-FE0F-200D-1F468","sheet_x":20,"sheet_y":28,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FB","sheet_x":20,"sheet_y":29},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FC","sheet_x":20,"sheet_y":30},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FD","sheet_x":20,"sheet_y":31},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FE","sheet_x":20,"sheet_y":32},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FF","sheet_x":20,"sheet_y":33},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FB","sheet_x":20,"sheet_y":34},"1F3FC-1F3FC":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FC","sheet_x":20,"sheet_y":35},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FD","sheet_x":20,"sheet_y":36},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FE","sheet_x":20,"sheet_y":37},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FF","sheet_x":20,"sheet_y":38},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FB","sheet_x":20,"sheet_y":39},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FC","sheet_x":20,"sheet_y":40},"1F3FD-1F3FD":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FD","sheet_x":20,"sheet_y":41},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FE","sheet_x":20,"sheet_y":42},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FF","sheet_x":20,"sheet_y":43},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FB","sheet_x":20,"sheet_y":44},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FC","sheet_x":20,"sheet_y":45},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FD","sheet_x":20,"sheet_y":46},"1F3FE-1F3FE":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FE","sheet_x":20,"sheet_y":47},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FF","sheet_x":20,"sheet_y":48},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FB","sheet_x":20,"sheet_y":49},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FC","sheet_x":20,"sheet_y":50},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FD","sheet_x":20,"sheet_y":51},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FE","sheet_x":20,"sheet_y":52},"1F3FF-1F3FF":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FF","sheet_x":20,"sheet_y":53}}},{"unified":"1F469-200D-2764-FE0F-200D-1F469","sheet_x":20,"sheet_y":54,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FB","sheet_x":20,"sheet_y":55},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FC","sheet_x":20,"sheet_y":56},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FD","sheet_x":20,"sheet_y":57},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FE","sheet_x":20,"sheet_y":58},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FF","sheet_x":20,"sheet_y":59},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FB","sheet_x":20,"sheet_y":60},"1F3FC-1F3FC":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FC","sheet_x":20,"sheet_y":61},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FD","sheet_x":21,"sheet_y":0},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FE","sheet_x":21,"sheet_y":1},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FF","sheet_x":21,"sheet_y":2},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FB","sheet_x":21,"sheet_y":3},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FC","sheet_x":21,"sheet_y":4},"1F3FD-1F3FD":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FD","sheet_x":21,"sheet_y":5},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FE","sheet_x":21,"sheet_y":6},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FF","sheet_x":21,"sheet_y":7},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FB","sheet_x":21,"sheet_y":8},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FC","sheet_x":21,"sheet_y":9},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FD","sheet_x":21,"sheet_y":10},"1F3FE-1F3FE":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FE","sheet_x":21,"sheet_y":11},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FF","sheet_x":21,"sheet_y":12},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FB","sheet_x":21,"sheet_y":13},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FC","sheet_x":21,"sheet_y":14},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FD","sheet_x":21,"sheet_y":15},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FE","sheet_x":21,"sheet_y":16},"1F3FF-1F3FF":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FF","sheet_x":21,"sheet_y":17}}},{"unified":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","sheet_x":21,"sheet_y":18,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","sheet_x":21,"sheet_y":19},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","sheet_x":21,"sheet_y":20},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","sheet_x":21,"sheet_y":21},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","sheet_x":21,"sheet_y":22},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","sheet_x":21,"sheet_y":23},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","sheet_x":21,"sheet_y":24},"1F3FC-1F3FC":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","sheet_x":21,"sheet_y":25},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","sheet_x":21,"sheet_y":26},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","sheet_x":21,"sheet_y":27},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","sheet_x":21,"sheet_y":28},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","sheet_x":21,"sheet_y":29},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","sheet_x":21,"sheet_y":30},"1F3FD-1F3FD":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","sheet_x":21,"sheet_y":31},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","sheet_x":21,"sheet_y":32},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","sheet_x":21,"sheet_y":33},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","sheet_x":21,"sheet_y":34},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","sheet_x":21,"sheet_y":35},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","sheet_x":21,"sheet_y":36},"1F3FE-1F3FE":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","sheet_x":21,"sheet_y":37},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","sheet_x":21,"sheet_y":38},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","sheet_x":21,"sheet_y":39},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","sheet_x":21,"sheet_y":40},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","sheet_x":21,"sheet_y":41},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","sheet_x":21,"sheet_y":42},"1F3FF-1F3FF":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","sheet_x":21,"sheet_y":43}}},{"unified":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","sheet_x":21,"sheet_y":44,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","sheet_x":21,"sheet_y":45},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","sheet_x":21,"sheet_y":46},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","sheet_x":21,"sheet_y":47},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","sheet_x":21,"sheet_y":48},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","sheet_x":21,"sheet_y":49},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","sheet_x":21,"sheet_y":50},"1F3FC-1F3FC":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","sheet_x":21,"sheet_y":51},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","sheet_x":21,"sheet_y":52},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","sheet_x":21,"sheet_y":53},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","sheet_x":21,"sheet_y":54},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","sheet_x":21,"sheet_y":55},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","sheet_x":21,"sheet_y":56},"1F3FD-1F3FD":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","sheet_x":21,"sheet_y":57},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","sheet_x":21,"sheet_y":58},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","sheet_x":21,"sheet_y":59},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","sheet_x":21,"sheet_y":60},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","sheet_x":21,"sheet_y":61},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","sheet_x":22,"sheet_y":0},"1F3FE-1F3FE":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","sheet_x":22,"sheet_y":1},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","sheet_x":22,"sheet_y":2},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","sheet_x":22,"sheet_y":3},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","sheet_x":22,"sheet_y":4},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","sheet_x":22,"sheet_y":5},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","sheet_x":22,"sheet_y":6},"1F3FF-1F3FF":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","sheet_x":22,"sheet_y":7}}},{"unified":"1F469","sheet_x":22,"sheet_y":8,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB","sheet_x":22,"sheet_y":9},"1F3FC":{"unified":"1F469-1F3FC","sheet_x":22,"sheet_y":10},"1F3FD":{"unified":"1F469-1F3FD","sheet_x":22,"sheet_y":11},"1F3FE":{"unified":"1F469-1F3FE","sheet_x":22,"sheet_y":12},"1F3FF":{"unified":"1F469-1F3FF","sheet_x":22,"sheet_y":13}}},{"unified":"1F46A","sheet_x":22,"sheet_y":14,"skin_variations":{}},{"unified":"1F46B","sheet_x":22,"sheet_y":15,"skin_variations":{"1F3FB":{"unified":"1F46B-1F3FB","sheet_x":22,"sheet_y":16},"1F3FC":{"unified":"1F46B-1F3FC","sheet_x":22,"sheet_y":17},"1F3FD":{"unified":"1F46B-1F3FD","sheet_x":22,"sheet_y":18},"1F3FE":{"unified":"1F46B-1F3FE","sheet_x":22,"sheet_y":19},"1F3FF":{"unified":"1F46B-1F3FF","sheet_x":22,"sheet_y":20},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FC","sheet_x":22,"sheet_y":21},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FD","sheet_x":22,"sheet_y":22},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FE","sheet_x":22,"sheet_y":23},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FF","sheet_x":22,"sheet_y":24},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FB","sheet_x":22,"sheet_y":25},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FD","sheet_x":22,"sheet_y":26},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FE","sheet_x":22,"sheet_y":27},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FF","sheet_x":22,"sheet_y":28},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FB","sheet_x":22,"sheet_y":29},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FC","sheet_x":22,"sheet_y":30},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FE","sheet_x":22,"sheet_y":31},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FF","sheet_x":22,"sheet_y":32},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FB","sheet_x":22,"sheet_y":33},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FC","sheet_x":22,"sheet_y":34},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FD","sheet_x":22,"sheet_y":35},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FF","sheet_x":22,"sheet_y":36},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FB","sheet_x":22,"sheet_y":37},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FC","sheet_x":22,"sheet_y":38},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FD","sheet_x":22,"sheet_y":39},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FE","sheet_x":22,"sheet_y":40}}},{"unified":"1F46C","sheet_x":22,"sheet_y":41,"skin_variations":{"1F3FB":{"unified":"1F46C-1F3FB","sheet_x":22,"sheet_y":42},"1F3FC":{"unified":"1F46C-1F3FC","sheet_x":22,"sheet_y":43},"1F3FD":{"unified":"1F46C-1F3FD","sheet_x":22,"sheet_y":44},"1F3FE":{"unified":"1F46C-1F3FE","sheet_x":22,"sheet_y":45},"1F3FF":{"unified":"1F46C-1F3FF","sheet_x":22,"sheet_y":46},"1F3FB-1F3FC":{"unified":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FC","sheet_x":22,"sheet_y":47},"1F3FB-1F3FD":{"unified":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FD","sheet_x":22,"sheet_y":48},"1F3FB-1F3FE":{"unified":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FE","sheet_x":22,"sheet_y":49},"1F3FB-1F3FF":{"unified":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FF","sheet_x":22,"sheet_y":50},"1F3FC-1F3FB":{"unified":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FB","sheet_x":22,"sheet_y":51},"1F3FC-1F3FD":{"unified":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FD","sheet_x":22,"sheet_y":52},"1F3FC-1F3FE":{"unified":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FE","sheet_x":22,"sheet_y":53},"1F3FC-1F3FF":{"unified":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FF","sheet_x":22,"sheet_y":54},"1F3FD-1F3FB":{"unified":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FB","sheet_x":22,"sheet_y":55},"1F3FD-1F3FC":{"unified":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FC","sheet_x":22,"sheet_y":56},"1F3FD-1F3FE":{"unified":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FE","sheet_x":22,"sheet_y":57},"1F3FD-1F3FF":{"unified":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FF","sheet_x":22,"sheet_y":58},"1F3FE-1F3FB":{"unified":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FB","sheet_x":22,"sheet_y":59},"1F3FE-1F3FC":{"unified":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FC","sheet_x":22,"sheet_y":60},"1F3FE-1F3FD":{"unified":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FD","sheet_x":22,"sheet_y":61},"1F3FE-1F3FF":{"unified":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FF","sheet_x":23,"sheet_y":0},"1F3FF-1F3FB":{"unified":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FB","sheet_x":23,"sheet_y":1},"1F3FF-1F3FC":{"unified":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FC","sheet_x":23,"sheet_y":2},"1F3FF-1F3FD":{"unified":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FD","sheet_x":23,"sheet_y":3},"1F3FF-1F3FE":{"unified":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FE","sheet_x":23,"sheet_y":4}}},{"unified":"1F46D","sheet_x":23,"sheet_y":5,"skin_variations":{"1F3FB":{"unified":"1F46D-1F3FB","sheet_x":23,"sheet_y":6},"1F3FC":{"unified":"1F46D-1F3FC","sheet_x":23,"sheet_y":7},"1F3FD":{"unified":"1F46D-1F3FD","sheet_x":23,"sheet_y":8},"1F3FE":{"unified":"1F46D-1F3FE","sheet_x":23,"sheet_y":9},"1F3FF":{"unified":"1F46D-1F3FF","sheet_x":23,"sheet_y":10},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FC","sheet_x":23,"sheet_y":11},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FD","sheet_x":23,"sheet_y":12},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FE","sheet_x":23,"sheet_y":13},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FF","sheet_x":23,"sheet_y":14},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FB","sheet_x":23,"sheet_y":15},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FD","sheet_x":23,"sheet_y":16},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FE","sheet_x":23,"sheet_y":17},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FF","sheet_x":23,"sheet_y":18},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FB","sheet_x":23,"sheet_y":19},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FC","sheet_x":23,"sheet_y":20},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FE","sheet_x":23,"sheet_y":21},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FF","sheet_x":23,"sheet_y":22},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FB","sheet_x":23,"sheet_y":23},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FC","sheet_x":23,"sheet_y":24},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FD","sheet_x":23,"sheet_y":25},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FF","sheet_x":23,"sheet_y":26},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FB","sheet_x":23,"sheet_y":27},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FC","sheet_x":23,"sheet_y":28},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FD","sheet_x":23,"sheet_y":29},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FE","sheet_x":23,"sheet_y":30}}},{"unified":"1F46E-200D-2640-FE0F","sheet_x":23,"sheet_y":31,"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB-200D-2640-FE0F","sheet_x":23,"sheet_y":32},"1F3FC":{"unified":"1F46E-1F3FC-200D-2640-FE0F","sheet_x":23,"sheet_y":33},"1F3FD":{"unified":"1F46E-1F3FD-200D-2640-FE0F","sheet_x":23,"sheet_y":34},"1F3FE":{"unified":"1F46E-1F3FE-200D-2640-FE0F","sheet_x":23,"sheet_y":35},"1F3FF":{"unified":"1F46E-1F3FF-200D-2640-FE0F","sheet_x":23,"sheet_y":36}}},{"unified":"1F46E-200D-2642-FE0F","sheet_x":23,"sheet_y":37,"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB-200D-2642-FE0F","sheet_x":23,"sheet_y":38},"1F3FC":{"unified":"1F46E-1F3FC-200D-2642-FE0F","sheet_x":23,"sheet_y":39},"1F3FD":{"unified":"1F46E-1F3FD-200D-2642-FE0F","sheet_x":23,"sheet_y":40},"1F3FE":{"unified":"1F46E-1F3FE-200D-2642-FE0F","sheet_x":23,"sheet_y":41},"1F3FF":{"unified":"1F46E-1F3FF-200D-2642-FE0F","sheet_x":23,"sheet_y":42}}},{"unified":"1F46E","sheet_x":23,"sheet_y":43,"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB","sheet_x":23,"sheet_y":44},"1F3FC":{"unified":"1F46E-1F3FC","sheet_x":23,"sheet_y":45},"1F3FD":{"unified":"1F46E-1F3FD","sheet_x":23,"sheet_y":46},"1F3FE":{"unified":"1F46E-1F3FE","sheet_x":23,"sheet_y":47},"1F3FF":{"unified":"1F46E-1F3FF","sheet_x":23,"sheet_y":48}}},{"unified":"1F46F-200D-2640-FE0F","sheet_x":23,"sheet_y":49,"skin_variations":{}},{"unified":"1F46F-200D-2642-FE0F","sheet_x":23,"sheet_y":50,"skin_variations":{}},{"unified":"1F46F","sheet_x":23,"sheet_y":51,"skin_variations":{}},{"unified":"1F470-200D-2640-FE0F","sheet_x":23,"sheet_y":52,"skin_variations":{"1F3FB":{"unified":"1F470-1F3FB-200D-2640-FE0F","sheet_x":23,"sheet_y":53},"1F3FC":{"unified":"1F470-1F3FC-200D-2640-FE0F","sheet_x":23,"sheet_y":54},"1F3FD":{"unified":"1F470-1F3FD-200D-2640-FE0F","sheet_x":23,"sheet_y":55},"1F3FE":{"unified":"1F470-1F3FE-200D-2640-FE0F","sheet_x":23,"sheet_y":56},"1F3FF":{"unified":"1F470-1F3FF-200D-2640-FE0F","sheet_x":23,"sheet_y":57}}},{"unified":"1F470-200D-2642-FE0F","sheet_x":23,"sheet_y":58,"skin_variations":{"1F3FB":{"unified":"1F470-1F3FB-200D-2642-FE0F","sheet_x":23,"sheet_y":59},"1F3FC":{"unified":"1F470-1F3FC-200D-2642-FE0F","sheet_x":23,"sheet_y":60},"1F3FD":{"unified":"1F470-1F3FD-200D-2642-FE0F","sheet_x":23,"sheet_y":61},"1F3FE":{"unified":"1F470-1F3FE-200D-2642-FE0F","sheet_x":24,"sheet_y":0},"1F3FF":{"unified":"1F470-1F3FF-200D-2642-FE0F","sheet_x":24,"sheet_y":1}}},{"unified":"1F470","sheet_x":24,"sheet_y":2,"skin_variations":{"1F3FB":{"unified":"1F470-1F3FB","sheet_x":24,"sheet_y":3},"1F3FC":{"unified":"1F470-1F3FC","sheet_x":24,"sheet_y":4},"1F3FD":{"unified":"1F470-1F3FD","sheet_x":24,"sheet_y":5},"1F3FE":{"unified":"1F470-1F3FE","sheet_x":24,"sheet_y":6},"1F3FF":{"unified":"1F470-1F3FF","sheet_x":24,"sheet_y":7}}},{"unified":"1F471-200D-2640-FE0F","sheet_x":24,"sheet_y":8,"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB-200D-2640-FE0F","sheet_x":24,"sheet_y":9},"1F3FC":{"unified":"1F471-1F3FC-200D-2640-FE0F","sheet_x":24,"sheet_y":10},"1F3FD":{"unified":"1F471-1F3FD-200D-2640-FE0F","sheet_x":24,"sheet_y":11},"1F3FE":{"unified":"1F471-1F3FE-200D-2640-FE0F","sheet_x":24,"sheet_y":12},"1F3FF":{"unified":"1F471-1F3FF-200D-2640-FE0F","sheet_x":24,"sheet_y":13}}},{"unified":"1F471-200D-2642-FE0F","sheet_x":24,"sheet_y":14,"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB-200D-2642-FE0F","sheet_x":24,"sheet_y":15},"1F3FC":{"unified":"1F471-1F3FC-200D-2642-FE0F","sheet_x":24,"sheet_y":16},"1F3FD":{"unified":"1F471-1F3FD-200D-2642-FE0F","sheet_x":24,"sheet_y":17},"1F3FE":{"unified":"1F471-1F3FE-200D-2642-FE0F","sheet_x":24,"sheet_y":18},"1F3FF":{"unified":"1F471-1F3FF-200D-2642-FE0F","sheet_x":24,"sheet_y":19}}},{"unified":"1F471","sheet_x":24,"sheet_y":20,"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB","sheet_x":24,"sheet_y":21},"1F3FC":{"unified":"1F471-1F3FC","sheet_x":24,"sheet_y":22},"1F3FD":{"unified":"1F471-1F3FD","sheet_x":24,"sheet_y":23},"1F3FE":{"unified":"1F471-1F3FE","sheet_x":24,"sheet_y":24},"1F3FF":{"unified":"1F471-1F3FF","sheet_x":24,"sheet_y":25}}},{"unified":"1F472","sheet_x":24,"sheet_y":26,"skin_variations":{"1F3FB":{"unified":"1F472-1F3FB","sheet_x":24,"sheet_y":27},"1F3FC":{"unified":"1F472-1F3FC","sheet_x":24,"sheet_y":28},"1F3FD":{"unified":"1F472-1F3FD","sheet_x":24,"sheet_y":29},"1F3FE":{"unified":"1F472-1F3FE","sheet_x":24,"sheet_y":30},"1F3FF":{"unified":"1F472-1F3FF","sheet_x":24,"sheet_y":31}}},{"unified":"1F473-200D-2640-FE0F","sheet_x":24,"sheet_y":32,"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB-200D-2640-FE0F","sheet_x":24,"sheet_y":33},"1F3FC":{"unified":"1F473-1F3FC-200D-2640-FE0F","sheet_x":24,"sheet_y":34},"1F3FD":{"unified":"1F473-1F3FD-200D-2640-FE0F","sheet_x":24,"sheet_y":35},"1F3FE":{"unified":"1F473-1F3FE-200D-2640-FE0F","sheet_x":24,"sheet_y":36},"1F3FF":{"unified":"1F473-1F3FF-200D-2640-FE0F","sheet_x":24,"sheet_y":37}}},{"unified":"1F473-200D-2642-FE0F","sheet_x":24,"sheet_y":38,"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB-200D-2642-FE0F","sheet_x":24,"sheet_y":39},"1F3FC":{"unified":"1F473-1F3FC-200D-2642-FE0F","sheet_x":24,"sheet_y":40},"1F3FD":{"unified":"1F473-1F3FD-200D-2642-FE0F","sheet_x":24,"sheet_y":41},"1F3FE":{"unified":"1F473-1F3FE-200D-2642-FE0F","sheet_x":24,"sheet_y":42},"1F3FF":{"unified":"1F473-1F3FF-200D-2642-FE0F","sheet_x":24,"sheet_y":43}}},{"unified":"1F473","sheet_x":24,"sheet_y":44,"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB","sheet_x":24,"sheet_y":45},"1F3FC":{"unified":"1F473-1F3FC","sheet_x":24,"sheet_y":46},"1F3FD":{"unified":"1F473-1F3FD","sheet_x":24,"sheet_y":47},"1F3FE":{"unified":"1F473-1F3FE","sheet_x":24,"sheet_y":48},"1F3FF":{"unified":"1F473-1F3FF","sheet_x":24,"sheet_y":49}}},{"unified":"1F474","sheet_x":24,"sheet_y":50,"skin_variations":{"1F3FB":{"unified":"1F474-1F3FB","sheet_x":24,"sheet_y":51},"1F3FC":{"unified":"1F474-1F3FC","sheet_x":24,"sheet_y":52},"1F3FD":{"unified":"1F474-1F3FD","sheet_x":24,"sheet_y":53},"1F3FE":{"unified":"1F474-1F3FE","sheet_x":24,"sheet_y":54},"1F3FF":{"unified":"1F474-1F3FF","sheet_x":24,"sheet_y":55}}},{"unified":"1F475","sheet_x":24,"sheet_y":56,"skin_variations":{"1F3FB":{"unified":"1F475-1F3FB","sheet_x":24,"sheet_y":57},"1F3FC":{"unified":"1F475-1F3FC","sheet_x":24,"sheet_y":58},"1F3FD":{"unified":"1F475-1F3FD","sheet_x":24,"sheet_y":59},"1F3FE":{"unified":"1F475-1F3FE","sheet_x":24,"sheet_y":60},"1F3FF":{"unified":"1F475-1F3FF","sheet_x":24,"sheet_y":61}}},{"unified":"1F476","sheet_x":25,"sheet_y":0,"skin_variations":{"1F3FB":{"unified":"1F476-1F3FB","sheet_x":25,"sheet_y":1},"1F3FC":{"unified":"1F476-1F3FC","sheet_x":25,"sheet_y":2},"1F3FD":{"unified":"1F476-1F3FD","sheet_x":25,"sheet_y":3},"1F3FE":{"unified":"1F476-1F3FE","sheet_x":25,"sheet_y":4},"1F3FF":{"unified":"1F476-1F3FF","sheet_x":25,"sheet_y":5}}},{"unified":"1F477-200D-2640-FE0F","sheet_x":25,"sheet_y":6,"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB-200D-2640-FE0F","sheet_x":25,"sheet_y":7},"1F3FC":{"unified":"1F477-1F3FC-200D-2640-FE0F","sheet_x":25,"sheet_y":8},"1F3FD":{"unified":"1F477-1F3FD-200D-2640-FE0F","sheet_x":25,"sheet_y":9},"1F3FE":{"unified":"1F477-1F3FE-200D-2640-FE0F","sheet_x":25,"sheet_y":10},"1F3FF":{"unified":"1F477-1F3FF-200D-2640-FE0F","sheet_x":25,"sheet_y":11}}},{"unified":"1F477-200D-2642-FE0F","sheet_x":25,"sheet_y":12,"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB-200D-2642-FE0F","sheet_x":25,"sheet_y":13},"1F3FC":{"unified":"1F477-1F3FC-200D-2642-FE0F","sheet_x":25,"sheet_y":14},"1F3FD":{"unified":"1F477-1F3FD-200D-2642-FE0F","sheet_x":25,"sheet_y":15},"1F3FE":{"unified":"1F477-1F3FE-200D-2642-FE0F","sheet_x":25,"sheet_y":16},"1F3FF":{"unified":"1F477-1F3FF-200D-2642-FE0F","sheet_x":25,"sheet_y":17}}},{"unified":"1F477","sheet_x":25,"sheet_y":18,"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB","sheet_x":25,"sheet_y":19},"1F3FC":{"unified":"1F477-1F3FC","sheet_x":25,"sheet_y":20},"1F3FD":{"unified":"1F477-1F3FD","sheet_x":25,"sheet_y":21},"1F3FE":{"unified":"1F477-1F3FE","sheet_x":25,"sheet_y":22},"1F3FF":{"unified":"1F477-1F3FF","sheet_x":25,"sheet_y":23}}},{"unified":"1F478","sheet_x":25,"sheet_y":24,"skin_variations":{"1F3FB":{"unified":"1F478-1F3FB","sheet_x":25,"sheet_y":25},"1F3FC":{"unified":"1F478-1F3FC","sheet_x":25,"sheet_y":26},"1F3FD":{"unified":"1F478-1F3FD","sheet_x":25,"sheet_y":27},"1F3FE":{"unified":"1F478-1F3FE","sheet_x":25,"sheet_y":28},"1F3FF":{"unified":"1F478-1F3FF","sheet_x":25,"sheet_y":29}}},{"unified":"1F479","sheet_x":25,"sheet_y":30,"skin_variations":{}},{"unified":"1F47A","sheet_x":25,"sheet_y":31,"skin_variations":{}},{"unified":"1F47B","sheet_x":25,"sheet_y":32,"skin_variations":{}},{"unified":"1F47C","sheet_x":25,"sheet_y":33,"skin_variations":{"1F3FB":{"unified":"1F47C-1F3FB","sheet_x":25,"sheet_y":34},"1F3FC":{"unified":"1F47C-1F3FC","sheet_x":25,"sheet_y":35},"1F3FD":{"unified":"1F47C-1F3FD","sheet_x":25,"sheet_y":36},"1F3FE":{"unified":"1F47C-1F3FE","sheet_x":25,"sheet_y":37},"1F3FF":{"unified":"1F47C-1F3FF","sheet_x":25,"sheet_y":38}}},{"unified":"1F47D","sheet_x":25,"sheet_y":39,"skin_variations":{}},{"unified":"1F47E","sheet_x":25,"sheet_y":40,"skin_variations":{}},{"unified":"1F47F","sheet_x":25,"sheet_y":41,"skin_variations":{}},{"unified":"1F480","sheet_x":25,"sheet_y":42,"skin_variations":{}},{"unified":"1F481-200D-2640-FE0F","sheet_x":25,"sheet_y":43,"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB-200D-2640-FE0F","sheet_x":25,"sheet_y":44},"1F3FC":{"unified":"1F481-1F3FC-200D-2640-FE0F","sheet_x":25,"sheet_y":45},"1F3FD":{"unified":"1F481-1F3FD-200D-2640-FE0F","sheet_x":25,"sheet_y":46},"1F3FE":{"unified":"1F481-1F3FE-200D-2640-FE0F","sheet_x":25,"sheet_y":47},"1F3FF":{"unified":"1F481-1F3FF-200D-2640-FE0F","sheet_x":25,"sheet_y":48}}},{"unified":"1F481-200D-2642-FE0F","sheet_x":25,"sheet_y":49,"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB-200D-2642-FE0F","sheet_x":25,"sheet_y":50},"1F3FC":{"unified":"1F481-1F3FC-200D-2642-FE0F","sheet_x":25,"sheet_y":51},"1F3FD":{"unified":"1F481-1F3FD-200D-2642-FE0F","sheet_x":25,"sheet_y":52},"1F3FE":{"unified":"1F481-1F3FE-200D-2642-FE0F","sheet_x":25,"sheet_y":53},"1F3FF":{"unified":"1F481-1F3FF-200D-2642-FE0F","sheet_x":25,"sheet_y":54}}},{"unified":"1F481","sheet_x":25,"sheet_y":55,"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB","sheet_x":25,"sheet_y":56},"1F3FC":{"unified":"1F481-1F3FC","sheet_x":25,"sheet_y":57},"1F3FD":{"unified":"1F481-1F3FD","sheet_x":25,"sheet_y":58},"1F3FE":{"unified":"1F481-1F3FE","sheet_x":25,"sheet_y":59},"1F3FF":{"unified":"1F481-1F3FF","sheet_x":25,"sheet_y":60}}},{"unified":"1F482-200D-2640-FE0F","sheet_x":25,"sheet_y":61,"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB-200D-2640-FE0F","sheet_x":26,"sheet_y":0},"1F3FC":{"unified":"1F482-1F3FC-200D-2640-FE0F","sheet_x":26,"sheet_y":1},"1F3FD":{"unified":"1F482-1F3FD-200D-2640-FE0F","sheet_x":26,"sheet_y":2},"1F3FE":{"unified":"1F482-1F3FE-200D-2640-FE0F","sheet_x":26,"sheet_y":3},"1F3FF":{"unified":"1F482-1F3FF-200D-2640-FE0F","sheet_x":26,"sheet_y":4}}},{"unified":"1F482-200D-2642-FE0F","sheet_x":26,"sheet_y":5,"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB-200D-2642-FE0F","sheet_x":26,"sheet_y":6},"1F3FC":{"unified":"1F482-1F3FC-200D-2642-FE0F","sheet_x":26,"sheet_y":7},"1F3FD":{"unified":"1F482-1F3FD-200D-2642-FE0F","sheet_x":26,"sheet_y":8},"1F3FE":{"unified":"1F482-1F3FE-200D-2642-FE0F","sheet_x":26,"sheet_y":9},"1F3FF":{"unified":"1F482-1F3FF-200D-2642-FE0F","sheet_x":26,"sheet_y":10}}},{"unified":"1F482","sheet_x":26,"sheet_y":11,"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB","sheet_x":26,"sheet_y":12},"1F3FC":{"unified":"1F482-1F3FC","sheet_x":26,"sheet_y":13},"1F3FD":{"unified":"1F482-1F3FD","sheet_x":26,"sheet_y":14},"1F3FE":{"unified":"1F482-1F3FE","sheet_x":26,"sheet_y":15},"1F3FF":{"unified":"1F482-1F3FF","sheet_x":26,"sheet_y":16}}},{"unified":"1F483","sheet_x":26,"sheet_y":17,"skin_variations":{"1F3FB":{"unified":"1F483-1F3FB","sheet_x":26,"sheet_y":18},"1F3FC":{"unified":"1F483-1F3FC","sheet_x":26,"sheet_y":19},"1F3FD":{"unified":"1F483-1F3FD","sheet_x":26,"sheet_y":20},"1F3FE":{"unified":"1F483-1F3FE","sheet_x":26,"sheet_y":21},"1F3FF":{"unified":"1F483-1F3FF","sheet_x":26,"sheet_y":22}}},{"unified":"1F484","sheet_x":26,"sheet_y":23,"skin_variations":{}},{"unified":"1F485","sheet_x":26,"sheet_y":24,"skin_variations":{"1F3FB":{"unified":"1F485-1F3FB","sheet_x":26,"sheet_y":25},"1F3FC":{"unified":"1F485-1F3FC","sheet_x":26,"sheet_y":26},"1F3FD":{"unified":"1F485-1F3FD","sheet_x":26,"sheet_y":27},"1F3FE":{"unified":"1F485-1F3FE","sheet_x":26,"sheet_y":28},"1F3FF":{"unified":"1F485-1F3FF","sheet_x":26,"sheet_y":29}}},{"unified":"1F486-200D-2640-FE0F","sheet_x":26,"sheet_y":30,"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB-200D-2640-FE0F","sheet_x":26,"sheet_y":31},"1F3FC":{"unified":"1F486-1F3FC-200D-2640-FE0F","sheet_x":26,"sheet_y":32},"1F3FD":{"unified":"1F486-1F3FD-200D-2640-FE0F","sheet_x":26,"sheet_y":33},"1F3FE":{"unified":"1F486-1F3FE-200D-2640-FE0F","sheet_x":26,"sheet_y":34},"1F3FF":{"unified":"1F486-1F3FF-200D-2640-FE0F","sheet_x":26,"sheet_y":35}}},{"unified":"1F486-200D-2642-FE0F","sheet_x":26,"sheet_y":36,"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB-200D-2642-FE0F","sheet_x":26,"sheet_y":37},"1F3FC":{"unified":"1F486-1F3FC-200D-2642-FE0F","sheet_x":26,"sheet_y":38},"1F3FD":{"unified":"1F486-1F3FD-200D-2642-FE0F","sheet_x":26,"sheet_y":39},"1F3FE":{"unified":"1F486-1F3FE-200D-2642-FE0F","sheet_x":26,"sheet_y":40},"1F3FF":{"unified":"1F486-1F3FF-200D-2642-FE0F","sheet_x":26,"sheet_y":41}}},{"unified":"1F486","sheet_x":26,"sheet_y":42,"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB","sheet_x":26,"sheet_y":43},"1F3FC":{"unified":"1F486-1F3FC","sheet_x":26,"sheet_y":44},"1F3FD":{"unified":"1F486-1F3FD","sheet_x":26,"sheet_y":45},"1F3FE":{"unified":"1F486-1F3FE","sheet_x":26,"sheet_y":46},"1F3FF":{"unified":"1F486-1F3FF","sheet_x":26,"sheet_y":47}}},{"unified":"1F487-200D-2640-FE0F","sheet_x":26,"sheet_y":48,"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB-200D-2640-FE0F","sheet_x":26,"sheet_y":49},"1F3FC":{"unified":"1F487-1F3FC-200D-2640-FE0F","sheet_x":26,"sheet_y":50},"1F3FD":{"unified":"1F487-1F3FD-200D-2640-FE0F","sheet_x":26,"sheet_y":51},"1F3FE":{"unified":"1F487-1F3FE-200D-2640-FE0F","sheet_x":26,"sheet_y":52},"1F3FF":{"unified":"1F487-1F3FF-200D-2640-FE0F","sheet_x":26,"sheet_y":53}}},{"unified":"1F487-200D-2642-FE0F","sheet_x":26,"sheet_y":54,"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB-200D-2642-FE0F","sheet_x":26,"sheet_y":55},"1F3FC":{"unified":"1F487-1F3FC-200D-2642-FE0F","sheet_x":26,"sheet_y":56},"1F3FD":{"unified":"1F487-1F3FD-200D-2642-FE0F","sheet_x":26,"sheet_y":57},"1F3FE":{"unified":"1F487-1F3FE-200D-2642-FE0F","sheet_x":26,"sheet_y":58},"1F3FF":{"unified":"1F487-1F3FF-200D-2642-FE0F","sheet_x":26,"sheet_y":59}}},{"unified":"1F487","sheet_x":26,"sheet_y":60,"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB","sheet_x":26,"sheet_y":61},"1F3FC":{"unified":"1F487-1F3FC","sheet_x":27,"sheet_y":0},"1F3FD":{"unified":"1F487-1F3FD","sheet_x":27,"sheet_y":1},"1F3FE":{"unified":"1F487-1F3FE","sheet_x":27,"sheet_y":2},"1F3FF":{"unified":"1F487-1F3FF","sheet_x":27,"sheet_y":3}}},{"unified":"1F488","sheet_x":27,"sheet_y":4,"skin_variations":{}},{"unified":"1F489","sheet_x":27,"sheet_y":5,"skin_variations":{}},{"unified":"1F48A","sheet_x":27,"sheet_y":6,"skin_variations":{}},{"unified":"1F48B","sheet_x":27,"sheet_y":7,"skin_variations":{}},{"unified":"1F48C","sheet_x":27,"sheet_y":8,"skin_variations":{}},{"unified":"1F48D","sheet_x":27,"sheet_y":9,"skin_variations":{}},{"unified":"1F48E","sheet_x":27,"sheet_y":10,"skin_variations":{}},{"unified":"1F48F","sheet_x":27,"sheet_y":11,"skin_variations":{"1F3FB":{"unified":"1F48F-1F3FB","sheet_x":27,"sheet_y":12},"1F3FC":{"unified":"1F48F-1F3FC","sheet_x":27,"sheet_y":13},"1F3FD":{"unified":"1F48F-1F3FD","sheet_x":27,"sheet_y":14},"1F3FE":{"unified":"1F48F-1F3FE","sheet_x":27,"sheet_y":15},"1F3FF":{"unified":"1F48F-1F3FF","sheet_x":27,"sheet_y":16},"1F3FB-1F3FC":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","sheet_x":27,"sheet_y":17},"1F3FB-1F3FD":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","sheet_x":27,"sheet_y":18},"1F3FB-1F3FE":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","sheet_x":27,"sheet_y":19},"1F3FB-1F3FF":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","sheet_x":27,"sheet_y":20},"1F3FC-1F3FB":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","sheet_x":27,"sheet_y":21},"1F3FC-1F3FD":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","sheet_x":27,"sheet_y":22},"1F3FC-1F3FE":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","sheet_x":27,"sheet_y":23},"1F3FC-1F3FF":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","sheet_x":27,"sheet_y":24},"1F3FD-1F3FB":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","sheet_x":27,"sheet_y":25},"1F3FD-1F3FC":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","sheet_x":27,"sheet_y":26},"1F3FD-1F3FE":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","sheet_x":27,"sheet_y":27},"1F3FD-1F3FF":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","sheet_x":27,"sheet_y":28},"1F3FE-1F3FB":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","sheet_x":27,"sheet_y":29},"1F3FE-1F3FC":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","sheet_x":27,"sheet_y":30},"1F3FE-1F3FD":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","sheet_x":27,"sheet_y":31},"1F3FE-1F3FF":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","sheet_x":27,"sheet_y":32},"1F3FF-1F3FB":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","sheet_x":27,"sheet_y":33},"1F3FF-1F3FC":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","sheet_x":27,"sheet_y":34},"1F3FF-1F3FD":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","sheet_x":27,"sheet_y":35},"1F3FF-1F3FE":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","sheet_x":27,"sheet_y":36}}},{"unified":"1F490","sheet_x":27,"sheet_y":37,"skin_variations":{}},{"unified":"1F491","sheet_x":27,"sheet_y":38,"skin_variations":{"1F3FB":{"unified":"1F491-1F3FB","sheet_x":27,"sheet_y":39},"1F3FC":{"unified":"1F491-1F3FC","sheet_x":27,"sheet_y":40},"1F3FD":{"unified":"1F491-1F3FD","sheet_x":27,"sheet_y":41},"1F3FE":{"unified":"1F491-1F3FE","sheet_x":27,"sheet_y":42},"1F3FF":{"unified":"1F491-1F3FF","sheet_x":27,"sheet_y":43},"1F3FB-1F3FC":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FC","sheet_x":27,"sheet_y":44},"1F3FB-1F3FD":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FD","sheet_x":27,"sheet_y":45},"1F3FB-1F3FE":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FE","sheet_x":27,"sheet_y":46},"1F3FB-1F3FF":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FF","sheet_x":27,"sheet_y":47},"1F3FC-1F3FB":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FB","sheet_x":27,"sheet_y":48},"1F3FC-1F3FD":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FD","sheet_x":27,"sheet_y":49},"1F3FC-1F3FE":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FE","sheet_x":27,"sheet_y":50},"1F3FC-1F3FF":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FF","sheet_x":27,"sheet_y":51},"1F3FD-1F3FB":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FB","sheet_x":27,"sheet_y":52},"1F3FD-1F3FC":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FC","sheet_x":27,"sheet_y":53},"1F3FD-1F3FE":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FE","sheet_x":27,"sheet_y":54},"1F3FD-1F3FF":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FF","sheet_x":27,"sheet_y":55},"1F3FE-1F3FB":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FB","sheet_x":27,"sheet_y":56},"1F3FE-1F3FC":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FC","sheet_x":27,"sheet_y":57},"1F3FE-1F3FD":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FD","sheet_x":27,"sheet_y":58},"1F3FE-1F3FF":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FF","sheet_x":27,"sheet_y":59},"1F3FF-1F3FB":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FB","sheet_x":27,"sheet_y":60},"1F3FF-1F3FC":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FC","sheet_x":27,"sheet_y":61},"1F3FF-1F3FD":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FD","sheet_x":28,"sheet_y":0},"1F3FF-1F3FE":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FE","sheet_x":28,"sheet_y":1}}},{"unified":"1F492","sheet_x":28,"sheet_y":2,"skin_variations":{}},{"unified":"1F493","sheet_x":28,"sheet_y":3,"skin_variations":{}},{"unified":"1F494","sheet_x":28,"sheet_y":4,"skin_variations":{}},{"unified":"1F495","sheet_x":28,"sheet_y":5,"skin_variations":{}},{"unified":"1F496","sheet_x":28,"sheet_y":6,"skin_variations":{}},{"unified":"1F497","sheet_x":28,"sheet_y":7,"skin_variations":{}},{"unified":"1F498","sheet_x":28,"sheet_y":8,"skin_variations":{}},{"unified":"1F499","sheet_x":28,"sheet_y":9,"skin_variations":{}},{"unified":"1F49A","sheet_x":28,"sheet_y":10,"skin_variations":{}},{"unified":"1F49B","sheet_x":28,"sheet_y":11,"skin_variations":{}},{"unified":"1F49C","sheet_x":28,"sheet_y":12,"skin_variations":{}},{"unified":"1F49D","sheet_x":28,"sheet_y":13,"skin_variations":{}},{"unified":"1F49E","sheet_x":28,"sheet_y":14,"skin_variations":{}},{"unified":"1F49F","sheet_x":28,"sheet_y":15,"skin_variations":{}},{"unified":"1F4A0","sheet_x":28,"sheet_y":16,"skin_variations":{}},{"unified":"1F4A1","sheet_x":28,"sheet_y":17,"skin_variations":{}},{"unified":"1F4A2","sheet_x":28,"sheet_y":18,"skin_variations":{}},{"unified":"1F4A3","sheet_x":28,"sheet_y":19,"skin_variations":{}},{"unified":"1F4A4","sheet_x":28,"sheet_y":20,"skin_variations":{}},{"unified":"1F4A5","sheet_x":28,"sheet_y":21,"skin_variations":{}},{"unified":"1F4A6","sheet_x":28,"sheet_y":22,"skin_variations":{}},{"unified":"1F4A7","sheet_x":28,"sheet_y":23,"skin_variations":{}},{"unified":"1F4A8","sheet_x":28,"sheet_y":24,"skin_variations":{}},{"unified":"1F4A9","sheet_x":28,"sheet_y":25,"skin_variations":{}},{"unified":"1F4AA","sheet_x":28,"sheet_y":26,"skin_variations":{"1F3FB":{"unified":"1F4AA-1F3FB","sheet_x":28,"sheet_y":27},"1F3FC":{"unified":"1F4AA-1F3FC","sheet_x":28,"sheet_y":28},"1F3FD":{"unified":"1F4AA-1F3FD","sheet_x":28,"sheet_y":29},"1F3FE":{"unified":"1F4AA-1F3FE","sheet_x":28,"sheet_y":30},"1F3FF":{"unified":"1F4AA-1F3FF","sheet_x":28,"sheet_y":31}}},{"unified":"1F4AB","sheet_x":28,"sheet_y":32,"skin_variations":{}},{"unified":"1F4AC","sheet_x":28,"sheet_y":33,"skin_variations":{}},{"unified":"1F4AD","sheet_x":28,"sheet_y":34,"skin_variations":{}},{"unified":"1F4AE","sheet_x":28,"sheet_y":35,"skin_variations":{}},{"unified":"1F4AF","sheet_x":28,"sheet_y":36,"skin_variations":{}},{"unified":"1F4B0","sheet_x":28,"sheet_y":37,"skin_variations":{}},{"unified":"1F4B1","sheet_x":28,"sheet_y":38,"skin_variations":{}},{"unified":"1F4B2","sheet_x":28,"sheet_y":39,"skin_variations":{}},{"unified":"1F4B3","sheet_x":28,"sheet_y":40,"skin_variations":{}},{"unified":"1F4B4","sheet_x":28,"sheet_y":41,"skin_variations":{}},{"unified":"1F4B5","sheet_x":28,"sheet_y":42,"skin_variations":{}},{"unified":"1F4B6","sheet_x":28,"sheet_y":43,"skin_variations":{}},{"unified":"1F4B7","sheet_x":28,"sheet_y":44,"skin_variations":{}},{"unified":"1F4B8","sheet_x":28,"sheet_y":45,"skin_variations":{}},{"unified":"1F4B9","sheet_x":28,"sheet_y":46,"skin_variations":{}},{"unified":"1F4BA","sheet_x":28,"sheet_y":47,"skin_variations":{}},{"unified":"1F4BB","sheet_x":28,"sheet_y":48,"skin_variations":{}},{"unified":"1F4BC","sheet_x":28,"sheet_y":49,"skin_variations":{}},{"unified":"1F4BD","sheet_x":28,"sheet_y":50,"skin_variations":{}},{"unified":"1F4BE","sheet_x":28,"sheet_y":51,"skin_variations":{}},{"unified":"1F4BF","sheet_x":28,"sheet_y":52,"skin_variations":{}},{"unified":"1F4C0","sheet_x":28,"sheet_y":53,"skin_variations":{}},{"unified":"1F4C1","sheet_x":28,"sheet_y":54,"skin_variations":{}},{"unified":"1F4C2","sheet_x":28,"sheet_y":55,"skin_variations":{}},{"unified":"1F4C3","sheet_x":28,"sheet_y":56,"skin_variations":{}},{"unified":"1F4C4","sheet_x":28,"sheet_y":57,"skin_variations":{}},{"unified":"1F4C5","sheet_x":28,"sheet_y":58,"skin_variations":{}},{"unified":"1F4C6","sheet_x":28,"sheet_y":59,"skin_variations":{}},{"unified":"1F4C7","sheet_x":28,"sheet_y":60,"skin_variations":{}},{"unified":"1F4C8","sheet_x":28,"sheet_y":61,"skin_variations":{}},{"unified":"1F4C9","sheet_x":29,"sheet_y":0,"skin_variations":{}},{"unified":"1F4CA","sheet_x":29,"sheet_y":1,"skin_variations":{}},{"unified":"1F4CB","sheet_x":29,"sheet_y":2,"skin_variations":{}},{"unified":"1F4CC","sheet_x":29,"sheet_y":3,"skin_variations":{}},{"unified":"1F4CD","sheet_x":29,"sheet_y":4,"skin_variations":{}},{"unified":"1F4CE","sheet_x":29,"sheet_y":5,"skin_variations":{}},{"unified":"1F4CF","sheet_x":29,"sheet_y":6,"skin_variations":{}},{"unified":"1F4D0","sheet_x":29,"sheet_y":7,"skin_variations":{}},{"unified":"1F4D1","sheet_x":29,"sheet_y":8,"skin_variations":{}},{"unified":"1F4D2","sheet_x":29,"sheet_y":9,"skin_variations":{}},{"unified":"1F4D3","sheet_x":29,"sheet_y":10,"skin_variations":{}},{"unified":"1F4D4","sheet_x":29,"sheet_y":11,"skin_variations":{}},{"unified":"1F4D5","sheet_x":29,"sheet_y":12,"skin_variations":{}},{"unified":"1F4D6","sheet_x":29,"sheet_y":13,"skin_variations":{}},{"unified":"1F4D7","sheet_x":29,"sheet_y":14,"skin_variations":{}},{"unified":"1F4D8","sheet_x":29,"sheet_y":15,"skin_variations":{}},{"unified":"1F4D9","sheet_x":29,"sheet_y":16,"skin_variations":{}},{"unified":"1F4DA","sheet_x":29,"sheet_y":17,"skin_variations":{}},{"unified":"1F4DB","sheet_x":29,"sheet_y":18,"skin_variations":{}},{"unified":"1F4DC","sheet_x":29,"sheet_y":19,"skin_variations":{}},{"unified":"1F4DD","sheet_x":29,"sheet_y":20,"skin_variations":{}},{"unified":"1F4DE","sheet_x":29,"sheet_y":21,"skin_variations":{}},{"unified":"1F4DF","sheet_x":29,"sheet_y":22,"skin_variations":{}},{"unified":"1F4E0","sheet_x":29,"sheet_y":23,"skin_variations":{}},{"unified":"1F4E1","sheet_x":29,"sheet_y":24,"skin_variations":{}},{"unified":"1F4E2","sheet_x":29,"sheet_y":25,"skin_variations":{}},{"unified":"1F4E3","sheet_x":29,"sheet_y":26,"skin_variations":{}},{"unified":"1F4E4","sheet_x":29,"sheet_y":27,"skin_variations":{}},{"unified":"1F4E5","sheet_x":29,"sheet_y":28,"skin_variations":{}},{"unified":"1F4E6","sheet_x":29,"sheet_y":29,"skin_variations":{}},{"unified":"1F4E7","sheet_x":29,"sheet_y":30,"skin_variations":{}},{"unified":"1F4E8","sheet_x":29,"sheet_y":31,"skin_variations":{}},{"unified":"1F4E9","sheet_x":29,"sheet_y":32,"skin_variations":{}},{"unified":"1F4EA","sheet_x":29,"sheet_y":33,"skin_variations":{}},{"unified":"1F4EB","sheet_x":29,"sheet_y":34,"skin_variations":{}},{"unified":"1F4EC","sheet_x":29,"sheet_y":35,"skin_variations":{}},{"unified":"1F4ED","sheet_x":29,"sheet_y":36,"skin_variations":{}},{"unified":"1F4EE","sheet_x":29,"sheet_y":37,"skin_variations":{}},{"unified":"1F4EF","sheet_x":29,"sheet_y":38,"skin_variations":{}},{"unified":"1F4F0","sheet_x":29,"sheet_y":39,"skin_variations":{}},{"unified":"1F4F1","sheet_x":29,"sheet_y":40,"skin_variations":{}},{"unified":"1F4F2","sheet_x":29,"sheet_y":41,"skin_variations":{}},{"unified":"1F4F3","sheet_x":29,"sheet_y":42,"skin_variations":{}},{"unified":"1F4F4","sheet_x":29,"sheet_y":43,"skin_variations":{}},{"unified":"1F4F5","sheet_x":29,"sheet_y":44,"skin_variations":{}},{"unified":"1F4F6","sheet_x":29,"sheet_y":45,"skin_variations":{}},{"unified":"1F4F7","sheet_x":29,"sheet_y":46,"skin_variations":{}},{"unified":"1F4F8","sheet_x":29,"sheet_y":47,"skin_variations":{}},{"unified":"1F4F9","sheet_x":29,"sheet_y":48,"skin_variations":{}},{"unified":"1F4FA","sheet_x":29,"sheet_y":49,"skin_variations":{}},{"unified":"1F4FB","sheet_x":29,"sheet_y":50,"skin_variations":{}},{"unified":"1F4FC","sheet_x":29,"sheet_y":51,"skin_variations":{}},{"unified":"1F4FD-FE0F","sheet_x":29,"sheet_y":52,"skin_variations":{}},{"unified":"1F4FF","sheet_x":29,"sheet_y":53,"skin_variations":{}},{"unified":"1F500","sheet_x":29,"sheet_y":54,"skin_variations":{}},{"unified":"1F501","sheet_x":29,"sheet_y":55,"skin_variations":{}},{"unified":"1F502","sheet_x":29,"sheet_y":56,"skin_variations":{}},{"unified":"1F503","sheet_x":29,"sheet_y":57,"skin_variations":{}},{"unified":"1F504","sheet_x":29,"sheet_y":58,"skin_variations":{}},{"unified":"1F505","sheet_x":29,"sheet_y":59,"skin_variations":{}},{"unified":"1F506","sheet_x":29,"sheet_y":60,"skin_variations":{}},{"unified":"1F507","sheet_x":29,"sheet_y":61,"skin_variations":{}},{"unified":"1F508","sheet_x":30,"sheet_y":0,"skin_variations":{}},{"unified":"1F509","sheet_x":30,"sheet_y":1,"skin_variations":{}},{"unified":"1F50A","sheet_x":30,"sheet_y":2,"skin_variations":{}},{"unified":"1F50B","sheet_x":30,"sheet_y":3,"skin_variations":{}},{"unified":"1F50C","sheet_x":30,"sheet_y":4,"skin_variations":{}},{"unified":"1F50D","sheet_x":30,"sheet_y":5,"skin_variations":{}},{"unified":"1F50E","sheet_x":30,"sheet_y":6,"skin_variations":{}},{"unified":"1F50F","sheet_x":30,"sheet_y":7,"skin_variations":{}},{"unified":"1F510","sheet_x":30,"sheet_y":8,"skin_variations":{}},{"unified":"1F511","sheet_x":30,"sheet_y":9,"skin_variations":{}},{"unified":"1F512","sheet_x":30,"sheet_y":10,"skin_variations":{}},{"unified":"1F513","sheet_x":30,"sheet_y":11,"skin_variations":{}},{"unified":"1F514","sheet_x":30,"sheet_y":12,"skin_variations":{}},{"unified":"1F515","sheet_x":30,"sheet_y":13,"skin_variations":{}},{"unified":"1F516","sheet_x":30,"sheet_y":14,"skin_variations":{}},{"unified":"1F517","sheet_x":30,"sheet_y":15,"skin_variations":{}},{"unified":"1F518","sheet_x":30,"sheet_y":16,"skin_variations":{}},{"unified":"1F519","sheet_x":30,"sheet_y":17,"skin_variations":{}},{"unified":"1F51A","sheet_x":30,"sheet_y":18,"skin_variations":{}},{"unified":"1F51B","sheet_x":30,"sheet_y":19,"skin_variations":{}},{"unified":"1F51C","sheet_x":30,"sheet_y":20,"skin_variations":{}},{"unified":"1F51D","sheet_x":30,"sheet_y":21,"skin_variations":{}},{"unified":"1F51E","sheet_x":30,"sheet_y":22,"skin_variations":{}},{"unified":"1F51F","sheet_x":30,"sheet_y":23,"skin_variations":{}},{"unified":"1F520","sheet_x":30,"sheet_y":24,"skin_variations":{}},{"unified":"1F521","sheet_x":30,"sheet_y":25,"skin_variations":{}},{"unified":"1F522","sheet_x":30,"sheet_y":26,"skin_variations":{}},{"unified":"1F523","sheet_x":30,"sheet_y":27,"skin_variations":{}},{"unified":"1F524","sheet_x":30,"sheet_y":28,"skin_variations":{}},{"unified":"1F525","sheet_x":30,"sheet_y":29,"skin_variations":{}},{"unified":"1F526","sheet_x":30,"sheet_y":30,"skin_variations":{}},{"unified":"1F527","sheet_x":30,"sheet_y":31,"skin_variations":{}},{"unified":"1F528","sheet_x":30,"sheet_y":32,"skin_variations":{}},{"unified":"1F529","sheet_x":30,"sheet_y":33,"skin_variations":{}},{"unified":"1F52A","sheet_x":30,"sheet_y":34,"skin_variations":{}},{"unified":"1F52B","sheet_x":30,"sheet_y":35,"skin_variations":{}},{"unified":"1F52C","sheet_x":30,"sheet_y":36,"skin_variations":{}},{"unified":"1F52D","sheet_x":30,"sheet_y":37,"skin_variations":{}},{"unified":"1F52E","sheet_x":30,"sheet_y":38,"skin_variations":{}},{"unified":"1F52F","sheet_x":30,"sheet_y":39,"skin_variations":{}},{"unified":"1F530","sheet_x":30,"sheet_y":40,"skin_variations":{}},{"unified":"1F531","sheet_x":30,"sheet_y":41,"skin_variations":{}},{"unified":"1F532","sheet_x":30,"sheet_y":42,"skin_variations":{}},{"unified":"1F533","sheet_x":30,"sheet_y":43,"skin_variations":{}},{"unified":"1F534","sheet_x":30,"sheet_y":44,"skin_variations":{}},{"unified":"1F535","sheet_x":30,"sheet_y":45,"skin_variations":{}},{"unified":"1F536","sheet_x":30,"sheet_y":46,"skin_variations":{}},{"unified":"1F537","sheet_x":30,"sheet_y":47,"skin_variations":{}},{"unified":"1F538","sheet_x":30,"sheet_y":48,"skin_variations":{}},{"unified":"1F539","sheet_x":30,"sheet_y":49,"skin_variations":{}},{"unified":"1F53A","sheet_x":30,"sheet_y":50,"skin_variations":{}},{"unified":"1F53B","sheet_x":30,"sheet_y":51,"skin_variations":{}},{"unified":"1F53C","sheet_x":30,"sheet_y":52,"skin_variations":{}},{"unified":"1F53D","sheet_x":30,"sheet_y":53,"skin_variations":{}},{"unified":"1F549-FE0F","sheet_x":30,"sheet_y":54,"skin_variations":{}},{"unified":"1F54A-FE0F","sheet_x":30,"sheet_y":55,"skin_variations":{}},{"unified":"1F54B","sheet_x":30,"sheet_y":56,"skin_variations":{}},{"unified":"1F54C","sheet_x":30,"sheet_y":57,"skin_variations":{}},{"unified":"1F54D","sheet_x":30,"sheet_y":58,"skin_variations":{}},{"unified":"1F54E","sheet_x":30,"sheet_y":59,"skin_variations":{}},{"unified":"1F550","sheet_x":30,"sheet_y":60,"skin_variations":{}},{"unified":"1F551","sheet_x":30,"sheet_y":61,"skin_variations":{}},{"unified":"1F552","sheet_x":31,"sheet_y":0,"skin_variations":{}},{"unified":"1F553","sheet_x":31,"sheet_y":1,"skin_variations":{}},{"unified":"1F554","sheet_x":31,"sheet_y":2,"skin_variations":{}},{"unified":"1F555","sheet_x":31,"sheet_y":3,"skin_variations":{}},{"unified":"1F556","sheet_x":31,"sheet_y":4,"skin_variations":{}},{"unified":"1F557","sheet_x":31,"sheet_y":5,"skin_variations":{}},{"unified":"1F558","sheet_x":31,"sheet_y":6,"skin_variations":{}},{"unified":"1F559","sheet_x":31,"sheet_y":7,"skin_variations":{}},{"unified":"1F55A","sheet_x":31,"sheet_y":8,"skin_variations":{}},{"unified":"1F55B","sheet_x":31,"sheet_y":9,"skin_variations":{}},{"unified":"1F55C","sheet_x":31,"sheet_y":10,"skin_variations":{}},{"unified":"1F55D","sheet_x":31,"sheet_y":11,"skin_variations":{}},{"unified":"1F55E","sheet_x":31,"sheet_y":12,"skin_variations":{}},{"unified":"1F55F","sheet_x":31,"sheet_y":13,"skin_variations":{}},{"unified":"1F560","sheet_x":31,"sheet_y":14,"skin_variations":{}},{"unified":"1F561","sheet_x":31,"sheet_y":15,"skin_variations":{}},{"unified":"1F562","sheet_x":31,"sheet_y":16,"skin_variations":{}},{"unified":"1F563","sheet_x":31,"sheet_y":17,"skin_variations":{}},{"unified":"1F564","sheet_x":31,"sheet_y":18,"skin_variations":{}},{"unified":"1F565","sheet_x":31,"sheet_y":19,"skin_variations":{}},{"unified":"1F566","sheet_x":31,"sheet_y":20,"skin_variations":{}},{"unified":"1F567","sheet_x":31,"sheet_y":21,"skin_variations":{}},{"unified":"1F56F-FE0F","sheet_x":31,"sheet_y":22,"skin_variations":{}},{"unified":"1F570-FE0F","sheet_x":31,"sheet_y":23,"skin_variations":{}},{"unified":"1F573-FE0F","sheet_x":31,"sheet_y":24,"skin_variations":{}},{"unified":"1F574-FE0F","sheet_x":31,"sheet_y":25,"skin_variations":{"1F3FB":{"unified":"1F574-1F3FB","sheet_x":31,"sheet_y":26},"1F3FC":{"unified":"1F574-1F3FC","sheet_x":31,"sheet_y":27},"1F3FD":{"unified":"1F574-1F3FD","sheet_x":31,"sheet_y":28},"1F3FE":{"unified":"1F574-1F3FE","sheet_x":31,"sheet_y":29},"1F3FF":{"unified":"1F574-1F3FF","sheet_x":31,"sheet_y":30}}},{"unified":"1F575-FE0F-200D-2640-FE0F","sheet_x":31,"sheet_y":31,"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB-200D-2640-FE0F","sheet_x":31,"sheet_y":32},"1F3FC":{"unified":"1F575-1F3FC-200D-2640-FE0F","sheet_x":31,"sheet_y":33},"1F3FD":{"unified":"1F575-1F3FD-200D-2640-FE0F","sheet_x":31,"sheet_y":34},"1F3FE":{"unified":"1F575-1F3FE-200D-2640-FE0F","sheet_x":31,"sheet_y":35},"1F3FF":{"unified":"1F575-1F3FF-200D-2640-FE0F","sheet_x":31,"sheet_y":36}}},{"unified":"1F575-FE0F-200D-2642-FE0F","sheet_x":31,"sheet_y":37,"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB-200D-2642-FE0F","sheet_x":31,"sheet_y":38},"1F3FC":{"unified":"1F575-1F3FC-200D-2642-FE0F","sheet_x":31,"sheet_y":39},"1F3FD":{"unified":"1F575-1F3FD-200D-2642-FE0F","sheet_x":31,"sheet_y":40},"1F3FE":{"unified":"1F575-1F3FE-200D-2642-FE0F","sheet_x":31,"sheet_y":41},"1F3FF":{"unified":"1F575-1F3FF-200D-2642-FE0F","sheet_x":31,"sheet_y":42}}},{"unified":"1F575-FE0F","sheet_x":31,"sheet_y":43,"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB","sheet_x":31,"sheet_y":44},"1F3FC":{"unified":"1F575-1F3FC","sheet_x":31,"sheet_y":45},"1F3FD":{"unified":"1F575-1F3FD","sheet_x":31,"sheet_y":46},"1F3FE":{"unified":"1F575-1F3FE","sheet_x":31,"sheet_y":47},"1F3FF":{"unified":"1F575-1F3FF","sheet_x":31,"sheet_y":48}}},{"unified":"1F576-FE0F","sheet_x":31,"sheet_y":49,"skin_variations":{}},{"unified":"1F577-FE0F","sheet_x":31,"sheet_y":50,"skin_variations":{}},{"unified":"1F578-FE0F","sheet_x":31,"sheet_y":51,"skin_variations":{}},{"unified":"1F579-FE0F","sheet_x":31,"sheet_y":52,"skin_variations":{}},{"unified":"1F57A","sheet_x":31,"sheet_y":53,"skin_variations":{"1F3FB":{"unified":"1F57A-1F3FB","sheet_x":31,"sheet_y":54},"1F3FC":{"unified":"1F57A-1F3FC","sheet_x":31,"sheet_y":55},"1F3FD":{"unified":"1F57A-1F3FD","sheet_x":31,"sheet_y":56},"1F3FE":{"unified":"1F57A-1F3FE","sheet_x":31,"sheet_y":57},"1F3FF":{"unified":"1F57A-1F3FF","sheet_x":31,"sheet_y":58}}},{"unified":"1F587-FE0F","sheet_x":31,"sheet_y":59,"skin_variations":{}},{"unified":"1F58A-FE0F","sheet_x":31,"sheet_y":60,"skin_variations":{}},{"unified":"1F58B-FE0F","sheet_x":31,"sheet_y":61,"skin_variations":{}},{"unified":"1F58C-FE0F","sheet_x":32,"sheet_y":0,"skin_variations":{}},{"unified":"1F58D-FE0F","sheet_x":32,"sheet_y":1,"skin_variations":{}},{"unified":"1F590-FE0F","sheet_x":32,"sheet_y":2,"skin_variations":{"1F3FB":{"unified":"1F590-1F3FB","sheet_x":32,"sheet_y":3},"1F3FC":{"unified":"1F590-1F3FC","sheet_x":32,"sheet_y":4},"1F3FD":{"unified":"1F590-1F3FD","sheet_x":32,"sheet_y":5},"1F3FE":{"unified":"1F590-1F3FE","sheet_x":32,"sheet_y":6},"1F3FF":{"unified":"1F590-1F3FF","sheet_x":32,"sheet_y":7}}},{"unified":"1F595","sheet_x":32,"sheet_y":8,"skin_variations":{"1F3FB":{"unified":"1F595-1F3FB","sheet_x":32,"sheet_y":9},"1F3FC":{"unified":"1F595-1F3FC","sheet_x":32,"sheet_y":10},"1F3FD":{"unified":"1F595-1F3FD","sheet_x":32,"sheet_y":11},"1F3FE":{"unified":"1F595-1F3FE","sheet_x":32,"sheet_y":12},"1F3FF":{"unified":"1F595-1F3FF","sheet_x":32,"sheet_y":13}}},{"unified":"1F596","sheet_x":32,"sheet_y":14,"skin_variations":{"1F3FB":{"unified":"1F596-1F3FB","sheet_x":32,"sheet_y":15},"1F3FC":{"unified":"1F596-1F3FC","sheet_x":32,"sheet_y":16},"1F3FD":{"unified":"1F596-1F3FD","sheet_x":32,"sheet_y":17},"1F3FE":{"unified":"1F596-1F3FE","sheet_x":32,"sheet_y":18},"1F3FF":{"unified":"1F596-1F3FF","sheet_x":32,"sheet_y":19}}},{"unified":"1F5A4","sheet_x":32,"sheet_y":20,"skin_variations":{}},{"unified":"1F5A5-FE0F","sheet_x":32,"sheet_y":21,"skin_variations":{}},{"unified":"1F5A8-FE0F","sheet_x":32,"sheet_y":22,"skin_variations":{}},{"unified":"1F5B1-FE0F","sheet_x":32,"sheet_y":23,"skin_variations":{}},{"unified":"1F5B2-FE0F","sheet_x":32,"sheet_y":24,"skin_variations":{}},{"unified":"1F5BC-FE0F","sheet_x":32,"sheet_y":25,"skin_variations":{}},{"unified":"1F5C2-FE0F","sheet_x":32,"sheet_y":26,"skin_variations":{}},{"unified":"1F5C3-FE0F","sheet_x":32,"sheet_y":27,"skin_variations":{}},{"unified":"1F5C4-FE0F","sheet_x":32,"sheet_y":28,"skin_variations":{}},{"unified":"1F5D1-FE0F","sheet_x":32,"sheet_y":29,"skin_variations":{}},{"unified":"1F5D2-FE0F","sheet_x":32,"sheet_y":30,"skin_variations":{}},{"unified":"1F5D3-FE0F","sheet_x":32,"sheet_y":31,"skin_variations":{}},{"unified":"1F5DC-FE0F","sheet_x":32,"sheet_y":32,"skin_variations":{}},{"unified":"1F5DD-FE0F","sheet_x":32,"sheet_y":33,"skin_variations":{}},{"unified":"1F5DE-FE0F","sheet_x":32,"sheet_y":34,"skin_variations":{}},{"unified":"1F5E1-FE0F","sheet_x":32,"sheet_y":35,"skin_variations":{}},{"unified":"1F5E3-FE0F","sheet_x":32,"sheet_y":36,"skin_variations":{}},{"unified":"1F5E8-FE0F","sheet_x":32,"sheet_y":37,"skin_variations":{}},{"unified":"1F5EF-FE0F","sheet_x":32,"sheet_y":38,"skin_variations":{}},{"unified":"1F5F3-FE0F","sheet_x":32,"sheet_y":39,"skin_variations":{}},{"unified":"1F5FA-FE0F","sheet_x":32,"sheet_y":40,"skin_variations":{}},{"unified":"1F5FB","sheet_x":32,"sheet_y":41,"skin_variations":{}},{"unified":"1F5FC","sheet_x":32,"sheet_y":42,"skin_variations":{}},{"unified":"1F5FD","sheet_x":32,"sheet_y":43,"skin_variations":{}},{"unified":"1F5FE","sheet_x":32,"sheet_y":44,"skin_variations":{}},{"unified":"1F5FF","sheet_x":32,"sheet_y":45,"skin_variations":{}},{"unified":"1F600","sheet_x":32,"sheet_y":46,"skin_variations":{}},{"unified":"1F601","sheet_x":32,"sheet_y":47,"skin_variations":{}},{"unified":"1F602","sheet_x":32,"sheet_y":48,"skin_variations":{}},{"unified":"1F603","sheet_x":32,"sheet_y":49,"skin_variations":{}},{"unified":"1F604","sheet_x":32,"sheet_y":50,"skin_variations":{}},{"unified":"1F605","sheet_x":32,"sheet_y":51,"skin_variations":{}},{"unified":"1F606","sheet_x":32,"sheet_y":52,"skin_variations":{}},{"unified":"1F607","sheet_x":32,"sheet_y":53,"skin_variations":{}},{"unified":"1F608","sheet_x":32,"sheet_y":54,"skin_variations":{}},{"unified":"1F609","sheet_x":32,"sheet_y":55,"skin_variations":{}},{"unified":"1F60A","sheet_x":32,"sheet_y":56,"skin_variations":{}},{"unified":"1F60B","sheet_x":32,"sheet_y":57,"skin_variations":{}},{"unified":"1F60C","sheet_x":32,"sheet_y":58,"skin_variations":{}},{"unified":"1F60D","sheet_x":32,"sheet_y":59,"skin_variations":{}},{"unified":"1F60E","sheet_x":32,"sheet_y":60,"skin_variations":{}},{"unified":"1F60F","sheet_x":32,"sheet_y":61,"skin_variations":{}},{"unified":"1F610","sheet_x":33,"sheet_y":0,"skin_variations":{}},{"unified":"1F611","sheet_x":33,"sheet_y":1,"skin_variations":{}},{"unified":"1F612","sheet_x":33,"sheet_y":2,"skin_variations":{}},{"unified":"1F613","sheet_x":33,"sheet_y":3,"skin_variations":{}},{"unified":"1F614","sheet_x":33,"sheet_y":4,"skin_variations":{}},{"unified":"1F615","sheet_x":33,"sheet_y":5,"skin_variations":{}},{"unified":"1F616","sheet_x":33,"sheet_y":6,"skin_variations":{}},{"unified":"1F617","sheet_x":33,"sheet_y":7,"skin_variations":{}},{"unified":"1F618","sheet_x":33,"sheet_y":8,"skin_variations":{}},{"unified":"1F619","sheet_x":33,"sheet_y":9,"skin_variations":{}},{"unified":"1F61A","sheet_x":33,"sheet_y":10,"skin_variations":{}},{"unified":"1F61B","sheet_x":33,"sheet_y":11,"skin_variations":{}},{"unified":"1F61C","sheet_x":33,"sheet_y":12,"skin_variations":{}},{"unified":"1F61D","sheet_x":33,"sheet_y":13,"skin_variations":{}},{"unified":"1F61E","sheet_x":33,"sheet_y":14,"skin_variations":{}},{"unified":"1F61F","sheet_x":33,"sheet_y":15,"skin_variations":{}},{"unified":"1F620","sheet_x":33,"sheet_y":16,"skin_variations":{}},{"unified":"1F621","sheet_x":33,"sheet_y":17,"skin_variations":{}},{"unified":"1F622","sheet_x":33,"sheet_y":18,"skin_variations":{}},{"unified":"1F623","sheet_x":33,"sheet_y":19,"skin_variations":{}},{"unified":"1F624","sheet_x":33,"sheet_y":20,"skin_variations":{}},{"unified":"1F625","sheet_x":33,"sheet_y":21,"skin_variations":{}},{"unified":"1F626","sheet_x":33,"sheet_y":22,"skin_variations":{}},{"unified":"1F627","sheet_x":33,"sheet_y":23,"skin_variations":{}},{"unified":"1F628","sheet_x":33,"sheet_y":24,"skin_variations":{}},{"unified":"1F629","sheet_x":33,"sheet_y":25,"skin_variations":{}},{"unified":"1F62A","sheet_x":33,"sheet_y":26,"skin_variations":{}},{"unified":"1F62B","sheet_x":33,"sheet_y":27,"skin_variations":{}},{"unified":"1F62C","sheet_x":33,"sheet_y":28,"skin_variations":{}},{"unified":"1F62D","sheet_x":33,"sheet_y":29,"skin_variations":{}},{"unified":"1F62E-200D-1F4A8","sheet_x":33,"sheet_y":30,"skin_variations":{}},{"unified":"1F62E","sheet_x":33,"sheet_y":31,"skin_variations":{}},{"unified":"1F62F","sheet_x":33,"sheet_y":32,"skin_variations":{}},{"unified":"1F630","sheet_x":33,"sheet_y":33,"skin_variations":{}},{"unified":"1F631","sheet_x":33,"sheet_y":34,"skin_variations":{}},{"unified":"1F632","sheet_x":33,"sheet_y":35,"skin_variations":{}},{"unified":"1F633","sheet_x":33,"sheet_y":36,"skin_variations":{}},{"unified":"1F634","sheet_x":33,"sheet_y":37,"skin_variations":{}},{"unified":"1F635-200D-1F4AB","sheet_x":33,"sheet_y":38,"skin_variations":{}},{"unified":"1F635","sheet_x":33,"sheet_y":39,"skin_variations":{}},{"unified":"1F636-200D-1F32B-FE0F","sheet_x":33,"sheet_y":40,"skin_variations":{}},{"unified":"1F636","sheet_x":33,"sheet_y":41,"skin_variations":{}},{"unified":"1F637","sheet_x":33,"sheet_y":42,"skin_variations":{}},{"unified":"1F638","sheet_x":33,"sheet_y":43,"skin_variations":{}},{"unified":"1F639","sheet_x":33,"sheet_y":44,"skin_variations":{}},{"unified":"1F63A","sheet_x":33,"sheet_y":45,"skin_variations":{}},{"unified":"1F63B","sheet_x":33,"sheet_y":46,"skin_variations":{}},{"unified":"1F63C","sheet_x":33,"sheet_y":47,"skin_variations":{}},{"unified":"1F63D","sheet_x":33,"sheet_y":48,"skin_variations":{}},{"unified":"1F63E","sheet_x":33,"sheet_y":49,"skin_variations":{}},{"unified":"1F63F","sheet_x":33,"sheet_y":50,"skin_variations":{}},{"unified":"1F640","sheet_x":33,"sheet_y":51,"skin_variations":{}},{"unified":"1F641","sheet_x":33,"sheet_y":52,"skin_variations":{}},{"unified":"1F642-200D-2194-FE0F","sheet_x":33,"sheet_y":53,"skin_variations":{}},{"unified":"1F642-200D-2195-FE0F","sheet_x":33,"sheet_y":54,"skin_variations":{}},{"unified":"1F642","sheet_x":33,"sheet_y":55,"skin_variations":{}},{"unified":"1F643","sheet_x":33,"sheet_y":56,"skin_variations":{}},{"unified":"1F644","sheet_x":33,"sheet_y":57,"skin_variations":{}},{"unified":"1F645-200D-2640-FE0F","sheet_x":33,"sheet_y":58,"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB-200D-2640-FE0F","sheet_x":33,"sheet_y":59},"1F3FC":{"unified":"1F645-1F3FC-200D-2640-FE0F","sheet_x":33,"sheet_y":60},"1F3FD":{"unified":"1F645-1F3FD-200D-2640-FE0F","sheet_x":33,"sheet_y":61},"1F3FE":{"unified":"1F645-1F3FE-200D-2640-FE0F","sheet_x":34,"sheet_y":0},"1F3FF":{"unified":"1F645-1F3FF-200D-2640-FE0F","sheet_x":34,"sheet_y":1}}},{"unified":"1F645-200D-2642-FE0F","sheet_x":34,"sheet_y":2,"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB-200D-2642-FE0F","sheet_x":34,"sheet_y":3},"1F3FC":{"unified":"1F645-1F3FC-200D-2642-FE0F","sheet_x":34,"sheet_y":4},"1F3FD":{"unified":"1F645-1F3FD-200D-2642-FE0F","sheet_x":34,"sheet_y":5},"1F3FE":{"unified":"1F645-1F3FE-200D-2642-FE0F","sheet_x":34,"sheet_y":6},"1F3FF":{"unified":"1F645-1F3FF-200D-2642-FE0F","sheet_x":34,"sheet_y":7}}},{"unified":"1F645","sheet_x":34,"sheet_y":8,"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB","sheet_x":34,"sheet_y":9},"1F3FC":{"unified":"1F645-1F3FC","sheet_x":34,"sheet_y":10},"1F3FD":{"unified":"1F645-1F3FD","sheet_x":34,"sheet_y":11},"1F3FE":{"unified":"1F645-1F3FE","sheet_x":34,"sheet_y":12},"1F3FF":{"unified":"1F645-1F3FF","sheet_x":34,"sheet_y":13}}},{"unified":"1F646-200D-2640-FE0F","sheet_x":34,"sheet_y":14,"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB-200D-2640-FE0F","sheet_x":34,"sheet_y":15},"1F3FC":{"unified":"1F646-1F3FC-200D-2640-FE0F","sheet_x":34,"sheet_y":16},"1F3FD":{"unified":"1F646-1F3FD-200D-2640-FE0F","sheet_x":34,"sheet_y":17},"1F3FE":{"unified":"1F646-1F3FE-200D-2640-FE0F","sheet_x":34,"sheet_y":18},"1F3FF":{"unified":"1F646-1F3FF-200D-2640-FE0F","sheet_x":34,"sheet_y":19}}},{"unified":"1F646-200D-2642-FE0F","sheet_x":34,"sheet_y":20,"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB-200D-2642-FE0F","sheet_x":34,"sheet_y":21},"1F3FC":{"unified":"1F646-1F3FC-200D-2642-FE0F","sheet_x":34,"sheet_y":22},"1F3FD":{"unified":"1F646-1F3FD-200D-2642-FE0F","sheet_x":34,"sheet_y":23},"1F3FE":{"unified":"1F646-1F3FE-200D-2642-FE0F","sheet_x":34,"sheet_y":24},"1F3FF":{"unified":"1F646-1F3FF-200D-2642-FE0F","sheet_x":34,"sheet_y":25}}},{"unified":"1F646","sheet_x":34,"sheet_y":26,"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB","sheet_x":34,"sheet_y":27},"1F3FC":{"unified":"1F646-1F3FC","sheet_x":34,"sheet_y":28},"1F3FD":{"unified":"1F646-1F3FD","sheet_x":34,"sheet_y":29},"1F3FE":{"unified":"1F646-1F3FE","sheet_x":34,"sheet_y":30},"1F3FF":{"unified":"1F646-1F3FF","sheet_x":34,"sheet_y":31}}},{"unified":"1F647-200D-2640-FE0F","sheet_x":34,"sheet_y":32,"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB-200D-2640-FE0F","sheet_x":34,"sheet_y":33},"1F3FC":{"unified":"1F647-1F3FC-200D-2640-FE0F","sheet_x":34,"sheet_y":34},"1F3FD":{"unified":"1F647-1F3FD-200D-2640-FE0F","sheet_x":34,"sheet_y":35},"1F3FE":{"unified":"1F647-1F3FE-200D-2640-FE0F","sheet_x":34,"sheet_y":36},"1F3FF":{"unified":"1F647-1F3FF-200D-2640-FE0F","sheet_x":34,"sheet_y":37}}},{"unified":"1F647-200D-2642-FE0F","sheet_x":34,"sheet_y":38,"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB-200D-2642-FE0F","sheet_x":34,"sheet_y":39},"1F3FC":{"unified":"1F647-1F3FC-200D-2642-FE0F","sheet_x":34,"sheet_y":40},"1F3FD":{"unified":"1F647-1F3FD-200D-2642-FE0F","sheet_x":34,"sheet_y":41},"1F3FE":{"unified":"1F647-1F3FE-200D-2642-FE0F","sheet_x":34,"sheet_y":42},"1F3FF":{"unified":"1F647-1F3FF-200D-2642-FE0F","sheet_x":34,"sheet_y":43}}},{"unified":"1F647","sheet_x":34,"sheet_y":44,"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB","sheet_x":34,"sheet_y":45},"1F3FC":{"unified":"1F647-1F3FC","sheet_x":34,"sheet_y":46},"1F3FD":{"unified":"1F647-1F3FD","sheet_x":34,"sheet_y":47},"1F3FE":{"unified":"1F647-1F3FE","sheet_x":34,"sheet_y":48},"1F3FF":{"unified":"1F647-1F3FF","sheet_x":34,"sheet_y":49}}},{"unified":"1F648","sheet_x":34,"sheet_y":50,"skin_variations":{}},{"unified":"1F649","sheet_x":34,"sheet_y":51,"skin_variations":{}},{"unified":"1F64A","sheet_x":34,"sheet_y":52,"skin_variations":{}},{"unified":"1F64B-200D-2640-FE0F","sheet_x":34,"sheet_y":53,"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB-200D-2640-FE0F","sheet_x":34,"sheet_y":54},"1F3FC":{"unified":"1F64B-1F3FC-200D-2640-FE0F","sheet_x":34,"sheet_y":55},"1F3FD":{"unified":"1F64B-1F3FD-200D-2640-FE0F","sheet_x":34,"sheet_y":56},"1F3FE":{"unified":"1F64B-1F3FE-200D-2640-FE0F","sheet_x":34,"sheet_y":57},"1F3FF":{"unified":"1F64B-1F3FF-200D-2640-FE0F","sheet_x":34,"sheet_y":58}}},{"unified":"1F64B-200D-2642-FE0F","sheet_x":34,"sheet_y":59,"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB-200D-2642-FE0F","sheet_x":34,"sheet_y":60},"1F3FC":{"unified":"1F64B-1F3FC-200D-2642-FE0F","sheet_x":34,"sheet_y":61},"1F3FD":{"unified":"1F64B-1F3FD-200D-2642-FE0F","sheet_x":35,"sheet_y":0},"1F3FE":{"unified":"1F64B-1F3FE-200D-2642-FE0F","sheet_x":35,"sheet_y":1},"1F3FF":{"unified":"1F64B-1F3FF-200D-2642-FE0F","sheet_x":35,"sheet_y":2}}},{"unified":"1F64B","sheet_x":35,"sheet_y":3,"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB","sheet_x":35,"sheet_y":4},"1F3FC":{"unified":"1F64B-1F3FC","sheet_x":35,"sheet_y":5},"1F3FD":{"unified":"1F64B-1F3FD","sheet_x":35,"sheet_y":6},"1F3FE":{"unified":"1F64B-1F3FE","sheet_x":35,"sheet_y":7},"1F3FF":{"unified":"1F64B-1F3FF","sheet_x":35,"sheet_y":8}}},{"unified":"1F64C","sheet_x":35,"sheet_y":9,"skin_variations":{"1F3FB":{"unified":"1F64C-1F3FB","sheet_x":35,"sheet_y":10},"1F3FC":{"unified":"1F64C-1F3FC","sheet_x":35,"sheet_y":11},"1F3FD":{"unified":"1F64C-1F3FD","sheet_x":35,"sheet_y":12},"1F3FE":{"unified":"1F64C-1F3FE","sheet_x":35,"sheet_y":13},"1F3FF":{"unified":"1F64C-1F3FF","sheet_x":35,"sheet_y":14}}},{"unified":"1F64D-200D-2640-FE0F","sheet_x":35,"sheet_y":15,"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB-200D-2640-FE0F","sheet_x":35,"sheet_y":16},"1F3FC":{"unified":"1F64D-1F3FC-200D-2640-FE0F","sheet_x":35,"sheet_y":17},"1F3FD":{"unified":"1F64D-1F3FD-200D-2640-FE0F","sheet_x":35,"sheet_y":18},"1F3FE":{"unified":"1F64D-1F3FE-200D-2640-FE0F","sheet_x":35,"sheet_y":19},"1F3FF":{"unified":"1F64D-1F3FF-200D-2640-FE0F","sheet_x":35,"sheet_y":20}}},{"unified":"1F64D-200D-2642-FE0F","sheet_x":35,"sheet_y":21,"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB-200D-2642-FE0F","sheet_x":35,"sheet_y":22},"1F3FC":{"unified":"1F64D-1F3FC-200D-2642-FE0F","sheet_x":35,"sheet_y":23},"1F3FD":{"unified":"1F64D-1F3FD-200D-2642-FE0F","sheet_x":35,"sheet_y":24},"1F3FE":{"unified":"1F64D-1F3FE-200D-2642-FE0F","sheet_x":35,"sheet_y":25},"1F3FF":{"unified":"1F64D-1F3FF-200D-2642-FE0F","sheet_x":35,"sheet_y":26}}},{"unified":"1F64D","sheet_x":35,"sheet_y":27,"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB","sheet_x":35,"sheet_y":28},"1F3FC":{"unified":"1F64D-1F3FC","sheet_x":35,"sheet_y":29},"1F3FD":{"unified":"1F64D-1F3FD","sheet_x":35,"sheet_y":30},"1F3FE":{"unified":"1F64D-1F3FE","sheet_x":35,"sheet_y":31},"1F3FF":{"unified":"1F64D-1F3FF","sheet_x":35,"sheet_y":32}}},{"unified":"1F64E-200D-2640-FE0F","sheet_x":35,"sheet_y":33,"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB-200D-2640-FE0F","sheet_x":35,"sheet_y":34},"1F3FC":{"unified":"1F64E-1F3FC-200D-2640-FE0F","sheet_x":35,"sheet_y":35},"1F3FD":{"unified":"1F64E-1F3FD-200D-2640-FE0F","sheet_x":35,"sheet_y":36},"1F3FE":{"unified":"1F64E-1F3FE-200D-2640-FE0F","sheet_x":35,"sheet_y":37},"1F3FF":{"unified":"1F64E-1F3FF-200D-2640-FE0F","sheet_x":35,"sheet_y":38}}},{"unified":"1F64E-200D-2642-FE0F","sheet_x":35,"sheet_y":39,"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB-200D-2642-FE0F","sheet_x":35,"sheet_y":40},"1F3FC":{"unified":"1F64E-1F3FC-200D-2642-FE0F","sheet_x":35,"sheet_y":41},"1F3FD":{"unified":"1F64E-1F3FD-200D-2642-FE0F","sheet_x":35,"sheet_y":42},"1F3FE":{"unified":"1F64E-1F3FE-200D-2642-FE0F","sheet_x":35,"sheet_y":43},"1F3FF":{"unified":"1F64E-1F3FF-200D-2642-FE0F","sheet_x":35,"sheet_y":44}}},{"unified":"1F64E","sheet_x":35,"sheet_y":45,"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB","sheet_x":35,"sheet_y":46},"1F3FC":{"unified":"1F64E-1F3FC","sheet_x":35,"sheet_y":47},"1F3FD":{"unified":"1F64E-1F3FD","sheet_x":35,"sheet_y":48},"1F3FE":{"unified":"1F64E-1F3FE","sheet_x":35,"sheet_y":49},"1F3FF":{"unified":"1F64E-1F3FF","sheet_x":35,"sheet_y":50}}},{"unified":"1F64F","sheet_x":35,"sheet_y":51,"skin_variations":{"1F3FB":{"unified":"1F64F-1F3FB","sheet_x":35,"sheet_y":52},"1F3FC":{"unified":"1F64F-1F3FC","sheet_x":35,"sheet_y":53},"1F3FD":{"unified":"1F64F-1F3FD","sheet_x":35,"sheet_y":54},"1F3FE":{"unified":"1F64F-1F3FE","sheet_x":35,"sheet_y":55},"1F3FF":{"unified":"1F64F-1F3FF","sheet_x":35,"sheet_y":56}}},{"unified":"1F680","sheet_x":35,"sheet_y":57,"skin_variations":{}},{"unified":"1F681","sheet_x":35,"sheet_y":58,"skin_variations":{}},{"unified":"1F682","sheet_x":35,"sheet_y":59,"skin_variations":{}},{"unified":"1F683","sheet_x":35,"sheet_y":60,"skin_variations":{}},{"unified":"1F684","sheet_x":35,"sheet_y":61,"skin_variations":{}},{"unified":"1F685","sheet_x":36,"sheet_y":0,"skin_variations":{}},{"unified":"1F686","sheet_x":36,"sheet_y":1,"skin_variations":{}},{"unified":"1F687","sheet_x":36,"sheet_y":2,"skin_variations":{}},{"unified":"1F688","sheet_x":36,"sheet_y":3,"skin_variations":{}},{"unified":"1F689","sheet_x":36,"sheet_y":4,"skin_variations":{}},{"unified":"1F68A","sheet_x":36,"sheet_y":5,"skin_variations":{}},{"unified":"1F68B","sheet_x":36,"sheet_y":6,"skin_variations":{}},{"unified":"1F68C","sheet_x":36,"sheet_y":7,"skin_variations":{}},{"unified":"1F68D","sheet_x":36,"sheet_y":8,"skin_variations":{}},{"unified":"1F68E","sheet_x":36,"sheet_y":9,"skin_variations":{}},{"unified":"1F68F","sheet_x":36,"sheet_y":10,"skin_variations":{}},{"unified":"1F690","sheet_x":36,"sheet_y":11,"skin_variations":{}},{"unified":"1F691","sheet_x":36,"sheet_y":12,"skin_variations":{}},{"unified":"1F692","sheet_x":36,"sheet_y":13,"skin_variations":{}},{"unified":"1F693","sheet_x":36,"sheet_y":14,"skin_variations":{}},{"unified":"1F694","sheet_x":36,"sheet_y":15,"skin_variations":{}},{"unified":"1F695","sheet_x":36,"sheet_y":16,"skin_variations":{}},{"unified":"1F696","sheet_x":36,"sheet_y":17,"skin_variations":{}},{"unified":"1F697","sheet_x":36,"sheet_y":18,"skin_variations":{}},{"unified":"1F698","sheet_x":36,"sheet_y":19,"skin_variations":{}},{"unified":"1F699","sheet_x":36,"sheet_y":20,"skin_variations":{}},{"unified":"1F69A","sheet_x":36,"sheet_y":21,"skin_variations":{}},{"unified":"1F69B","sheet_x":36,"sheet_y":22,"skin_variations":{}},{"unified":"1F69C","sheet_x":36,"sheet_y":23,"skin_variations":{}},{"unified":"1F69D","sheet_x":36,"sheet_y":24,"skin_variations":{}},{"unified":"1F69E","sheet_x":36,"sheet_y":25,"skin_variations":{}},{"unified":"1F69F","sheet_x":36,"sheet_y":26,"skin_variations":{}},{"unified":"1F6A0","sheet_x":36,"sheet_y":27,"skin_variations":{}},{"unified":"1F6A1","sheet_x":36,"sheet_y":28,"skin_variations":{}},{"unified":"1F6A2","sheet_x":36,"sheet_y":29,"skin_variations":{}},{"unified":"1F6A3-200D-2640-FE0F","sheet_x":36,"sheet_y":30,"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB-200D-2640-FE0F","sheet_x":36,"sheet_y":31},"1F3FC":{"unified":"1F6A3-1F3FC-200D-2640-FE0F","sheet_x":36,"sheet_y":32},"1F3FD":{"unified":"1F6A3-1F3FD-200D-2640-FE0F","sheet_x":36,"sheet_y":33},"1F3FE":{"unified":"1F6A3-1F3FE-200D-2640-FE0F","sheet_x":36,"sheet_y":34},"1F3FF":{"unified":"1F6A3-1F3FF-200D-2640-FE0F","sheet_x":36,"sheet_y":35}}},{"unified":"1F6A3-200D-2642-FE0F","sheet_x":36,"sheet_y":36,"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB-200D-2642-FE0F","sheet_x":36,"sheet_y":37},"1F3FC":{"unified":"1F6A3-1F3FC-200D-2642-FE0F","sheet_x":36,"sheet_y":38},"1F3FD":{"unified":"1F6A3-1F3FD-200D-2642-FE0F","sheet_x":36,"sheet_y":39},"1F3FE":{"unified":"1F6A3-1F3FE-200D-2642-FE0F","sheet_x":36,"sheet_y":40},"1F3FF":{"unified":"1F6A3-1F3FF-200D-2642-FE0F","sheet_x":36,"sheet_y":41}}},{"unified":"1F6A3","sheet_x":36,"sheet_y":42,"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB","sheet_x":36,"sheet_y":43},"1F3FC":{"unified":"1F6A3-1F3FC","sheet_x":36,"sheet_y":44},"1F3FD":{"unified":"1F6A3-1F3FD","sheet_x":36,"sheet_y":45},"1F3FE":{"unified":"1F6A3-1F3FE","sheet_x":36,"sheet_y":46},"1F3FF":{"unified":"1F6A3-1F3FF","sheet_x":36,"sheet_y":47}}},{"unified":"1F6A4","sheet_x":36,"sheet_y":48,"skin_variations":{}},{"unified":"1F6A5","sheet_x":36,"sheet_y":49,"skin_variations":{}},{"unified":"1F6A6","sheet_x":36,"sheet_y":50,"skin_variations":{}},{"unified":"1F6A7","sheet_x":36,"sheet_y":51,"skin_variations":{}},{"unified":"1F6A8","sheet_x":36,"sheet_y":52,"skin_variations":{}},{"unified":"1F6A9","sheet_x":36,"sheet_y":53,"skin_variations":{}},{"unified":"1F6AA","sheet_x":36,"sheet_y":54,"skin_variations":{}},{"unified":"1F6AB","sheet_x":36,"sheet_y":55,"skin_variations":{}},{"unified":"1F6AC","sheet_x":36,"sheet_y":56,"skin_variations":{}},{"unified":"1F6AD","sheet_x":36,"sheet_y":57,"skin_variations":{}},{"unified":"1F6AE","sheet_x":36,"sheet_y":58,"skin_variations":{}},{"unified":"1F6AF","sheet_x":36,"sheet_y":59,"skin_variations":{}},{"unified":"1F6B0","sheet_x":36,"sheet_y":60,"skin_variations":{}},{"unified":"1F6B1","sheet_x":36,"sheet_y":61,"skin_variations":{}},{"unified":"1F6B2","sheet_x":37,"sheet_y":0,"skin_variations":{}},{"unified":"1F6B3","sheet_x":37,"sheet_y":1,"skin_variations":{}},{"unified":"1F6B4-200D-2640-FE0F","sheet_x":37,"sheet_y":2,"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB-200D-2640-FE0F","sheet_x":37,"sheet_y":3},"1F3FC":{"unified":"1F6B4-1F3FC-200D-2640-FE0F","sheet_x":37,"sheet_y":4},"1F3FD":{"unified":"1F6B4-1F3FD-200D-2640-FE0F","sheet_x":37,"sheet_y":5},"1F3FE":{"unified":"1F6B4-1F3FE-200D-2640-FE0F","sheet_x":37,"sheet_y":6},"1F3FF":{"unified":"1F6B4-1F3FF-200D-2640-FE0F","sheet_x":37,"sheet_y":7}}},{"unified":"1F6B4-200D-2642-FE0F","sheet_x":37,"sheet_y":8,"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB-200D-2642-FE0F","sheet_x":37,"sheet_y":9},"1F3FC":{"unified":"1F6B4-1F3FC-200D-2642-FE0F","sheet_x":37,"sheet_y":10},"1F3FD":{"unified":"1F6B4-1F3FD-200D-2642-FE0F","sheet_x":37,"sheet_y":11},"1F3FE":{"unified":"1F6B4-1F3FE-200D-2642-FE0F","sheet_x":37,"sheet_y":12},"1F3FF":{"unified":"1F6B4-1F3FF-200D-2642-FE0F","sheet_x":37,"sheet_y":13}}},{"unified":"1F6B4","sheet_x":37,"sheet_y":14,"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB","sheet_x":37,"sheet_y":15},"1F3FC":{"unified":"1F6B4-1F3FC","sheet_x":37,"sheet_y":16},"1F3FD":{"unified":"1F6B4-1F3FD","sheet_x":37,"sheet_y":17},"1F3FE":{"unified":"1F6B4-1F3FE","sheet_x":37,"sheet_y":18},"1F3FF":{"unified":"1F6B4-1F3FF","sheet_x":37,"sheet_y":19}}},{"unified":"1F6B5-200D-2640-FE0F","sheet_x":37,"sheet_y":20,"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB-200D-2640-FE0F","sheet_x":37,"sheet_y":21},"1F3FC":{"unified":"1F6B5-1F3FC-200D-2640-FE0F","sheet_x":37,"sheet_y":22},"1F3FD":{"unified":"1F6B5-1F3FD-200D-2640-FE0F","sheet_x":37,"sheet_y":23},"1F3FE":{"unified":"1F6B5-1F3FE-200D-2640-FE0F","sheet_x":37,"sheet_y":24},"1F3FF":{"unified":"1F6B5-1F3FF-200D-2640-FE0F","sheet_x":37,"sheet_y":25}}},{"unified":"1F6B5-200D-2642-FE0F","sheet_x":37,"sheet_y":26,"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB-200D-2642-FE0F","sheet_x":37,"sheet_y":27},"1F3FC":{"unified":"1F6B5-1F3FC-200D-2642-FE0F","sheet_x":37,"sheet_y":28},"1F3FD":{"unified":"1F6B5-1F3FD-200D-2642-FE0F","sheet_x":37,"sheet_y":29},"1F3FE":{"unified":"1F6B5-1F3FE-200D-2642-FE0F","sheet_x":37,"sheet_y":30},"1F3FF":{"unified":"1F6B5-1F3FF-200D-2642-FE0F","sheet_x":37,"sheet_y":31}}},{"unified":"1F6B5","sheet_x":37,"sheet_y":32,"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB","sheet_x":37,"sheet_y":33},"1F3FC":{"unified":"1F6B5-1F3FC","sheet_x":37,"sheet_y":34},"1F3FD":{"unified":"1F6B5-1F3FD","sheet_x":37,"sheet_y":35},"1F3FE":{"unified":"1F6B5-1F3FE","sheet_x":37,"sheet_y":36},"1F3FF":{"unified":"1F6B5-1F3FF","sheet_x":37,"sheet_y":37}}},{"unified":"1F6B6-200D-2640-FE0F","sheet_x":37,"sheet_y":38,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2640-FE0F","sheet_x":37,"sheet_y":39},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2640-FE0F","sheet_x":37,"sheet_y":40},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2640-FE0F","sheet_x":37,"sheet_y":41},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2640-FE0F","sheet_x":37,"sheet_y":42},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2640-FE0F","sheet_x":37,"sheet_y":43}}},{"unified":"1F6B6-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":44,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":45},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":46},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":47},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":48},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":49}}},{"unified":"1F6B6-200D-2642-FE0F","sheet_x":37,"sheet_y":50,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2642-FE0F","sheet_x":37,"sheet_y":51},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2642-FE0F","sheet_x":37,"sheet_y":52},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2642-FE0F","sheet_x":37,"sheet_y":53},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2642-FE0F","sheet_x":37,"sheet_y":54},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2642-FE0F","sheet_x":37,"sheet_y":55}}},{"unified":"1F6B6-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":56,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":57},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":58},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":59},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":60},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":37,"sheet_y":61}}},{"unified":"1F6B6-200D-27A1-FE0F","sheet_x":38,"sheet_y":0,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-27A1-FE0F","sheet_x":38,"sheet_y":1},"1F3FC":{"unified":"1F6B6-1F3FC-200D-27A1-FE0F","sheet_x":38,"sheet_y":2},"1F3FD":{"unified":"1F6B6-1F3FD-200D-27A1-FE0F","sheet_x":38,"sheet_y":3},"1F3FE":{"unified":"1F6B6-1F3FE-200D-27A1-FE0F","sheet_x":38,"sheet_y":4},"1F3FF":{"unified":"1F6B6-1F3FF-200D-27A1-FE0F","sheet_x":38,"sheet_y":5}}},{"unified":"1F6B6","sheet_x":38,"sheet_y":6,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB","sheet_x":38,"sheet_y":7},"1F3FC":{"unified":"1F6B6-1F3FC","sheet_x":38,"sheet_y":8},"1F3FD":{"unified":"1F6B6-1F3FD","sheet_x":38,"sheet_y":9},"1F3FE":{"unified":"1F6B6-1F3FE","sheet_x":38,"sheet_y":10},"1F3FF":{"unified":"1F6B6-1F3FF","sheet_x":38,"sheet_y":11}}},{"unified":"1F6B7","sheet_x":38,"sheet_y":12,"skin_variations":{}},{"unified":"1F6B8","sheet_x":38,"sheet_y":13,"skin_variations":{}},{"unified":"1F6B9","sheet_x":38,"sheet_y":14,"skin_variations":{}},{"unified":"1F6BA","sheet_x":38,"sheet_y":15,"skin_variations":{}},{"unified":"1F6BB","sheet_x":38,"sheet_y":16,"skin_variations":{}},{"unified":"1F6BC","sheet_x":38,"sheet_y":17,"skin_variations":{}},{"unified":"1F6BD","sheet_x":38,"sheet_y":18,"skin_variations":{}},{"unified":"1F6BE","sheet_x":38,"sheet_y":19,"skin_variations":{}},{"unified":"1F6BF","sheet_x":38,"sheet_y":20,"skin_variations":{}},{"unified":"1F6C0","sheet_x":38,"sheet_y":21,"skin_variations":{"1F3FB":{"unified":"1F6C0-1F3FB","sheet_x":38,"sheet_y":22},"1F3FC":{"unified":"1F6C0-1F3FC","sheet_x":38,"sheet_y":23},"1F3FD":{"unified":"1F6C0-1F3FD","sheet_x":38,"sheet_y":24},"1F3FE":{"unified":"1F6C0-1F3FE","sheet_x":38,"sheet_y":25},"1F3FF":{"unified":"1F6C0-1F3FF","sheet_x":38,"sheet_y":26}}},{"unified":"1F6C1","sheet_x":38,"sheet_y":27,"skin_variations":{}},{"unified":"1F6C2","sheet_x":38,"sheet_y":28,"skin_variations":{}},{"unified":"1F6C3","sheet_x":38,"sheet_y":29,"skin_variations":{}},{"unified":"1F6C4","sheet_x":38,"sheet_y":30,"skin_variations":{}},{"unified":"1F6C5","sheet_x":38,"sheet_y":31,"skin_variations":{}},{"unified":"1F6CB-FE0F","sheet_x":38,"sheet_y":32,"skin_variations":{}},{"unified":"1F6CC","sheet_x":38,"sheet_y":33,"skin_variations":{"1F3FB":{"unified":"1F6CC-1F3FB","sheet_x":38,"sheet_y":34},"1F3FC":{"unified":"1F6CC-1F3FC","sheet_x":38,"sheet_y":35},"1F3FD":{"unified":"1F6CC-1F3FD","sheet_x":38,"sheet_y":36},"1F3FE":{"unified":"1F6CC-1F3FE","sheet_x":38,"sheet_y":37},"1F3FF":{"unified":"1F6CC-1F3FF","sheet_x":38,"sheet_y":38}}},{"unified":"1F6CD-FE0F","sheet_x":38,"sheet_y":39,"skin_variations":{}},{"unified":"1F6CE-FE0F","sheet_x":38,"sheet_y":40,"skin_variations":{}},{"unified":"1F6CF-FE0F","sheet_x":38,"sheet_y":41,"skin_variations":{}},{"unified":"1F6D0","sheet_x":38,"sheet_y":42,"skin_variations":{}},{"unified":"1F6D1","sheet_x":38,"sheet_y":43,"skin_variations":{}},{"unified":"1F6D2","sheet_x":38,"sheet_y":44,"skin_variations":{}},{"unified":"1F6D5","sheet_x":38,"sheet_y":45,"skin_variations":{}},{"unified":"1F6D6","sheet_x":38,"sheet_y":46,"skin_variations":{}},{"unified":"1F6D7","sheet_x":38,"sheet_y":47,"skin_variations":{}},{"unified":"1F6DC","sheet_x":38,"sheet_y":48,"skin_variations":{}},{"unified":"1F6DD","sheet_x":38,"sheet_y":49,"skin_variations":{}},{"unified":"1F6DE","sheet_x":38,"sheet_y":50,"skin_variations":{}},{"unified":"1F6DF","sheet_x":38,"sheet_y":51,"skin_variations":{}},{"unified":"1F6E0-FE0F","sheet_x":38,"sheet_y":52,"skin_variations":{}},{"unified":"1F6E1-FE0F","sheet_x":38,"sheet_y":53,"skin_variations":{}},{"unified":"1F6E2-FE0F","sheet_x":38,"sheet_y":54,"skin_variations":{}},{"unified":"1F6E3-FE0F","sheet_x":38,"sheet_y":55,"skin_variations":{}},{"unified":"1F6E4-FE0F","sheet_x":38,"sheet_y":56,"skin_variations":{}},{"unified":"1F6E5-FE0F","sheet_x":38,"sheet_y":57,"skin_variations":{}},{"unified":"1F6E9-FE0F","sheet_x":38,"sheet_y":58,"skin_variations":{}},{"unified":"1F6EB","sheet_x":38,"sheet_y":59,"skin_variations":{}},{"unified":"1F6EC","sheet_x":38,"sheet_y":60,"skin_variations":{}},{"unified":"1F6F0-FE0F","sheet_x":38,"sheet_y":61,"skin_variations":{}},{"unified":"1F6F3-FE0F","sheet_x":39,"sheet_y":0,"skin_variations":{}},{"unified":"1F6F4","sheet_x":39,"sheet_y":1,"skin_variations":{}},{"unified":"1F6F5","sheet_x":39,"sheet_y":2,"skin_variations":{}},{"unified":"1F6F6","sheet_x":39,"sheet_y":3,"skin_variations":{}},{"unified":"1F6F7","sheet_x":39,"sheet_y":4,"skin_variations":{}},{"unified":"1F6F8","sheet_x":39,"sheet_y":5,"skin_variations":{}},{"unified":"1F6F9","sheet_x":39,"sheet_y":6,"skin_variations":{}},{"unified":"1F6FA","sheet_x":39,"sheet_y":7,"skin_variations":{}},{"unified":"1F6FB","sheet_x":39,"sheet_y":8,"skin_variations":{}},{"unified":"1F6FC","sheet_x":39,"sheet_y":9,"skin_variations":{}},{"unified":"1F7E0","sheet_x":39,"sheet_y":10,"skin_variations":{}},{"unified":"1F7E1","sheet_x":39,"sheet_y":11,"skin_variations":{}},{"unified":"1F7E2","sheet_x":39,"sheet_y":12,"skin_variations":{}},{"unified":"1F7E3","sheet_x":39,"sheet_y":13,"skin_variations":{}},{"unified":"1F7E4","sheet_x":39,"sheet_y":14,"skin_variations":{}},{"unified":"1F7E5","sheet_x":39,"sheet_y":15,"skin_variations":{}},{"unified":"1F7E6","sheet_x":39,"sheet_y":16,"skin_variations":{}},{"unified":"1F7E7","sheet_x":39,"sheet_y":17,"skin_variations":{}},{"unified":"1F7E8","sheet_x":39,"sheet_y":18,"skin_variations":{}},{"unified":"1F7E9","sheet_x":39,"sheet_y":19,"skin_variations":{}},{"unified":"1F7EA","sheet_x":39,"sheet_y":20,"skin_variations":{}},{"unified":"1F7EB","sheet_x":39,"sheet_y":21,"skin_variations":{}},{"unified":"1F7F0","sheet_x":39,"sheet_y":22,"skin_variations":{}},{"unified":"1F90C","sheet_x":39,"sheet_y":23,"skin_variations":{"1F3FB":{"unified":"1F90C-1F3FB","sheet_x":39,"sheet_y":24},"1F3FC":{"unified":"1F90C-1F3FC","sheet_x":39,"sheet_y":25},"1F3FD":{"unified":"1F90C-1F3FD","sheet_x":39,"sheet_y":26},"1F3FE":{"unified":"1F90C-1F3FE","sheet_x":39,"sheet_y":27},"1F3FF":{"unified":"1F90C-1F3FF","sheet_x":39,"sheet_y":28}}},{"unified":"1F90D","sheet_x":39,"sheet_y":29,"skin_variations":{}},{"unified":"1F90E","sheet_x":39,"sheet_y":30,"skin_variations":{}},{"unified":"1F90F","sheet_x":39,"sheet_y":31,"skin_variations":{"1F3FB":{"unified":"1F90F-1F3FB","sheet_x":39,"sheet_y":32},"1F3FC":{"unified":"1F90F-1F3FC","sheet_x":39,"sheet_y":33},"1F3FD":{"unified":"1F90F-1F3FD","sheet_x":39,"sheet_y":34},"1F3FE":{"unified":"1F90F-1F3FE","sheet_x":39,"sheet_y":35},"1F3FF":{"unified":"1F90F-1F3FF","sheet_x":39,"sheet_y":36}}},{"unified":"1F910","sheet_x":39,"sheet_y":37,"skin_variations":{}},{"unified":"1F911","sheet_x":39,"sheet_y":38,"skin_variations":{}},{"unified":"1F912","sheet_x":39,"sheet_y":39,"skin_variations":{}},{"unified":"1F913","sheet_x":39,"sheet_y":40,"skin_variations":{}},{"unified":"1F914","sheet_x":39,"sheet_y":41,"skin_variations":{}},{"unified":"1F915","sheet_x":39,"sheet_y":42,"skin_variations":{}},{"unified":"1F916","sheet_x":39,"sheet_y":43,"skin_variations":{}},{"unified":"1F917","sheet_x":39,"sheet_y":44,"skin_variations":{}},{"unified":"1F918","sheet_x":39,"sheet_y":45,"skin_variations":{"1F3FB":{"unified":"1F918-1F3FB","sheet_x":39,"sheet_y":46},"1F3FC":{"unified":"1F918-1F3FC","sheet_x":39,"sheet_y":47},"1F3FD":{"unified":"1F918-1F3FD","sheet_x":39,"sheet_y":48},"1F3FE":{"unified":"1F918-1F3FE","sheet_x":39,"sheet_y":49},"1F3FF":{"unified":"1F918-1F3FF","sheet_x":39,"sheet_y":50}}},{"unified":"1F919","sheet_x":39,"sheet_y":51,"skin_variations":{"1F3FB":{"unified":"1F919-1F3FB","sheet_x":39,"sheet_y":52},"1F3FC":{"unified":"1F919-1F3FC","sheet_x":39,"sheet_y":53},"1F3FD":{"unified":"1F919-1F3FD","sheet_x":39,"sheet_y":54},"1F3FE":{"unified":"1F919-1F3FE","sheet_x":39,"sheet_y":55},"1F3FF":{"unified":"1F919-1F3FF","sheet_x":39,"sheet_y":56}}},{"unified":"1F91A","sheet_x":39,"sheet_y":57,"skin_variations":{"1F3FB":{"unified":"1F91A-1F3FB","sheet_x":39,"sheet_y":58},"1F3FC":{"unified":"1F91A-1F3FC","sheet_x":39,"sheet_y":59},"1F3FD":{"unified":"1F91A-1F3FD","sheet_x":39,"sheet_y":60},"1F3FE":{"unified":"1F91A-1F3FE","sheet_x":39,"sheet_y":61},"1F3FF":{"unified":"1F91A-1F3FF","sheet_x":40,"sheet_y":0}}},{"unified":"1F91B","sheet_x":40,"sheet_y":1,"skin_variations":{"1F3FB":{"unified":"1F91B-1F3FB","sheet_x":40,"sheet_y":2},"1F3FC":{"unified":"1F91B-1F3FC","sheet_x":40,"sheet_y":3},"1F3FD":{"unified":"1F91B-1F3FD","sheet_x":40,"sheet_y":4},"1F3FE":{"unified":"1F91B-1F3FE","sheet_x":40,"sheet_y":5},"1F3FF":{"unified":"1F91B-1F3FF","sheet_x":40,"sheet_y":6}}},{"unified":"1F91C","sheet_x":40,"sheet_y":7,"skin_variations":{"1F3FB":{"unified":"1F91C-1F3FB","sheet_x":40,"sheet_y":8},"1F3FC":{"unified":"1F91C-1F3FC","sheet_x":40,"sheet_y":9},"1F3FD":{"unified":"1F91C-1F3FD","sheet_x":40,"sheet_y":10},"1F3FE":{"unified":"1F91C-1F3FE","sheet_x":40,"sheet_y":11},"1F3FF":{"unified":"1F91C-1F3FF","sheet_x":40,"sheet_y":12}}},{"unified":"1F91D","sheet_x":40,"sheet_y":13,"skin_variations":{"1F3FB":{"unified":"1F91D-1F3FB","sheet_x":40,"sheet_y":14},"1F3FC":{"unified":"1F91D-1F3FC","sheet_x":40,"sheet_y":15},"1F3FD":{"unified":"1F91D-1F3FD","sheet_x":40,"sheet_y":16},"1F3FE":{"unified":"1F91D-1F3FE","sheet_x":40,"sheet_y":17},"1F3FF":{"unified":"1F91D-1F3FF","sheet_x":40,"sheet_y":18},"1F3FB-1F3FC":{"unified":"1FAF1-1F3FB-200D-1FAF2-1F3FC","sheet_x":40,"sheet_y":19},"1F3FB-1F3FD":{"unified":"1FAF1-1F3FB-200D-1FAF2-1F3FD","sheet_x":40,"sheet_y":20},"1F3FB-1F3FE":{"unified":"1FAF1-1F3FB-200D-1FAF2-1F3FE","sheet_x":40,"sheet_y":21},"1F3FB-1F3FF":{"unified":"1FAF1-1F3FB-200D-1FAF2-1F3FF","sheet_x":40,"sheet_y":22},"1F3FC-1F3FB":{"unified":"1FAF1-1F3FC-200D-1FAF2-1F3FB","sheet_x":40,"sheet_y":23},"1F3FC-1F3FD":{"unified":"1FAF1-1F3FC-200D-1FAF2-1F3FD","sheet_x":40,"sheet_y":24},"1F3FC-1F3FE":{"unified":"1FAF1-1F3FC-200D-1FAF2-1F3FE","sheet_x":40,"sheet_y":25},"1F3FC-1F3FF":{"unified":"1FAF1-1F3FC-200D-1FAF2-1F3FF","sheet_x":40,"sheet_y":26},"1F3FD-1F3FB":{"unified":"1FAF1-1F3FD-200D-1FAF2-1F3FB","sheet_x":40,"sheet_y":27},"1F3FD-1F3FC":{"unified":"1FAF1-1F3FD-200D-1FAF2-1F3FC","sheet_x":40,"sheet_y":28},"1F3FD-1F3FE":{"unified":"1FAF1-1F3FD-200D-1FAF2-1F3FE","sheet_x":40,"sheet_y":29},"1F3FD-1F3FF":{"unified":"1FAF1-1F3FD-200D-1FAF2-1F3FF","sheet_x":40,"sheet_y":30},"1F3FE-1F3FB":{"unified":"1FAF1-1F3FE-200D-1FAF2-1F3FB","sheet_x":40,"sheet_y":31},"1F3FE-1F3FC":{"unified":"1FAF1-1F3FE-200D-1FAF2-1F3FC","sheet_x":40,"sheet_y":32},"1F3FE-1F3FD":{"unified":"1FAF1-1F3FE-200D-1FAF2-1F3FD","sheet_x":40,"sheet_y":33},"1F3FE-1F3FF":{"unified":"1FAF1-1F3FE-200D-1FAF2-1F3FF","sheet_x":40,"sheet_y":34},"1F3FF-1F3FB":{"unified":"1FAF1-1F3FF-200D-1FAF2-1F3FB","sheet_x":40,"sheet_y":35},"1F3FF-1F3FC":{"unified":"1FAF1-1F3FF-200D-1FAF2-1F3FC","sheet_x":40,"sheet_y":36},"1F3FF-1F3FD":{"unified":"1FAF1-1F3FF-200D-1FAF2-1F3FD","sheet_x":40,"sheet_y":37},"1F3FF-1F3FE":{"unified":"1FAF1-1F3FF-200D-1FAF2-1F3FE","sheet_x":40,"sheet_y":38}}},{"unified":"1F91E","sheet_x":40,"sheet_y":39,"skin_variations":{"1F3FB":{"unified":"1F91E-1F3FB","sheet_x":40,"sheet_y":40},"1F3FC":{"unified":"1F91E-1F3FC","sheet_x":40,"sheet_y":41},"1F3FD":{"unified":"1F91E-1F3FD","sheet_x":40,"sheet_y":42},"1F3FE":{"unified":"1F91E-1F3FE","sheet_x":40,"sheet_y":43},"1F3FF":{"unified":"1F91E-1F3FF","sheet_x":40,"sheet_y":44}}},{"unified":"1F91F","sheet_x":40,"sheet_y":45,"skin_variations":{"1F3FB":{"unified":"1F91F-1F3FB","sheet_x":40,"sheet_y":46},"1F3FC":{"unified":"1F91F-1F3FC","sheet_x":40,"sheet_y":47},"1F3FD":{"unified":"1F91F-1F3FD","sheet_x":40,"sheet_y":48},"1F3FE":{"unified":"1F91F-1F3FE","sheet_x":40,"sheet_y":49},"1F3FF":{"unified":"1F91F-1F3FF","sheet_x":40,"sheet_y":50}}},{"unified":"1F920","sheet_x":40,"sheet_y":51,"skin_variations":{}},{"unified":"1F921","sheet_x":40,"sheet_y":52,"skin_variations":{}},{"unified":"1F922","sheet_x":40,"sheet_y":53,"skin_variations":{}},{"unified":"1F923","sheet_x":40,"sheet_y":54,"skin_variations":{}},{"unified":"1F924","sheet_x":40,"sheet_y":55,"skin_variations":{}},{"unified":"1F925","sheet_x":40,"sheet_y":56,"skin_variations":{}},{"unified":"1F926-200D-2640-FE0F","sheet_x":40,"sheet_y":57,"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB-200D-2640-FE0F","sheet_x":40,"sheet_y":58},"1F3FC":{"unified":"1F926-1F3FC-200D-2640-FE0F","sheet_x":40,"sheet_y":59},"1F3FD":{"unified":"1F926-1F3FD-200D-2640-FE0F","sheet_x":40,"sheet_y":60},"1F3FE":{"unified":"1F926-1F3FE-200D-2640-FE0F","sheet_x":40,"sheet_y":61},"1F3FF":{"unified":"1F926-1F3FF-200D-2640-FE0F","sheet_x":41,"sheet_y":0}}},{"unified":"1F926-200D-2642-FE0F","sheet_x":41,"sheet_y":1,"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB-200D-2642-FE0F","sheet_x":41,"sheet_y":2},"1F3FC":{"unified":"1F926-1F3FC-200D-2642-FE0F","sheet_x":41,"sheet_y":3},"1F3FD":{"unified":"1F926-1F3FD-200D-2642-FE0F","sheet_x":41,"sheet_y":4},"1F3FE":{"unified":"1F926-1F3FE-200D-2642-FE0F","sheet_x":41,"sheet_y":5},"1F3FF":{"unified":"1F926-1F3FF-200D-2642-FE0F","sheet_x":41,"sheet_y":6}}},{"unified":"1F926","sheet_x":41,"sheet_y":7,"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB","sheet_x":41,"sheet_y":8},"1F3FC":{"unified":"1F926-1F3FC","sheet_x":41,"sheet_y":9},"1F3FD":{"unified":"1F926-1F3FD","sheet_x":41,"sheet_y":10},"1F3FE":{"unified":"1F926-1F3FE","sheet_x":41,"sheet_y":11},"1F3FF":{"unified":"1F926-1F3FF","sheet_x":41,"sheet_y":12}}},{"unified":"1F927","sheet_x":41,"sheet_y":13,"skin_variations":{}},{"unified":"1F928","sheet_x":41,"sheet_y":14,"skin_variations":{}},{"unified":"1F929","sheet_x":41,"sheet_y":15,"skin_variations":{}},{"unified":"1F92A","sheet_x":41,"sheet_y":16,"skin_variations":{}},{"unified":"1F92B","sheet_x":41,"sheet_y":17,"skin_variations":{}},{"unified":"1F92C","sheet_x":41,"sheet_y":18,"skin_variations":{}},{"unified":"1F92D","sheet_x":41,"sheet_y":19,"skin_variations":{}},{"unified":"1F92E","sheet_x":41,"sheet_y":20,"skin_variations":{}},{"unified":"1F92F","sheet_x":41,"sheet_y":21,"skin_variations":{}},{"unified":"1F930","sheet_x":41,"sheet_y":22,"skin_variations":{"1F3FB":{"unified":"1F930-1F3FB","sheet_x":41,"sheet_y":23},"1F3FC":{"unified":"1F930-1F3FC","sheet_x":41,"sheet_y":24},"1F3FD":{"unified":"1F930-1F3FD","sheet_x":41,"sheet_y":25},"1F3FE":{"unified":"1F930-1F3FE","sheet_x":41,"sheet_y":26},"1F3FF":{"unified":"1F930-1F3FF","sheet_x":41,"sheet_y":27}}},{"unified":"1F931","sheet_x":41,"sheet_y":28,"skin_variations":{"1F3FB":{"unified":"1F931-1F3FB","sheet_x":41,"sheet_y":29},"1F3FC":{"unified":"1F931-1F3FC","sheet_x":41,"sheet_y":30},"1F3FD":{"unified":"1F931-1F3FD","sheet_x":41,"sheet_y":31},"1F3FE":{"unified":"1F931-1F3FE","sheet_x":41,"sheet_y":32},"1F3FF":{"unified":"1F931-1F3FF","sheet_x":41,"sheet_y":33}}},{"unified":"1F932","sheet_x":41,"sheet_y":34,"skin_variations":{"1F3FB":{"unified":"1F932-1F3FB","sheet_x":41,"sheet_y":35},"1F3FC":{"unified":"1F932-1F3FC","sheet_x":41,"sheet_y":36},"1F3FD":{"unified":"1F932-1F3FD","sheet_x":41,"sheet_y":37},"1F3FE":{"unified":"1F932-1F3FE","sheet_x":41,"sheet_y":38},"1F3FF":{"unified":"1F932-1F3FF","sheet_x":41,"sheet_y":39}}},{"unified":"1F933","sheet_x":41,"sheet_y":40,"skin_variations":{"1F3FB":{"unified":"1F933-1F3FB","sheet_x":41,"sheet_y":41},"1F3FC":{"unified":"1F933-1F3FC","sheet_x":41,"sheet_y":42},"1F3FD":{"unified":"1F933-1F3FD","sheet_x":41,"sheet_y":43},"1F3FE":{"unified":"1F933-1F3FE","sheet_x":41,"sheet_y":44},"1F3FF":{"unified":"1F933-1F3FF","sheet_x":41,"sheet_y":45}}},{"unified":"1F934","sheet_x":41,"sheet_y":46,"skin_variations":{"1F3FB":{"unified":"1F934-1F3FB","sheet_x":41,"sheet_y":47},"1F3FC":{"unified":"1F934-1F3FC","sheet_x":41,"sheet_y":48},"1F3FD":{"unified":"1F934-1F3FD","sheet_x":41,"sheet_y":49},"1F3FE":{"unified":"1F934-1F3FE","sheet_x":41,"sheet_y":50},"1F3FF":{"unified":"1F934-1F3FF","sheet_x":41,"sheet_y":51}}},{"unified":"1F935-200D-2640-FE0F","sheet_x":41,"sheet_y":52,"skin_variations":{"1F3FB":{"unified":"1F935-1F3FB-200D-2640-FE0F","sheet_x":41,"sheet_y":53},"1F3FC":{"unified":"1F935-1F3FC-200D-2640-FE0F","sheet_x":41,"sheet_y":54},"1F3FD":{"unified":"1F935-1F3FD-200D-2640-FE0F","sheet_x":41,"sheet_y":55},"1F3FE":{"unified":"1F935-1F3FE-200D-2640-FE0F","sheet_x":41,"sheet_y":56},"1F3FF":{"unified":"1F935-1F3FF-200D-2640-FE0F","sheet_x":41,"sheet_y":57}}},{"unified":"1F935-200D-2642-FE0F","sheet_x":41,"sheet_y":58,"skin_variations":{"1F3FB":{"unified":"1F935-1F3FB-200D-2642-FE0F","sheet_x":41,"sheet_y":59},"1F3FC":{"unified":"1F935-1F3FC-200D-2642-FE0F","sheet_x":41,"sheet_y":60},"1F3FD":{"unified":"1F935-1F3FD-200D-2642-FE0F","sheet_x":41,"sheet_y":61},"1F3FE":{"unified":"1F935-1F3FE-200D-2642-FE0F","sheet_x":42,"sheet_y":0},"1F3FF":{"unified":"1F935-1F3FF-200D-2642-FE0F","sheet_x":42,"sheet_y":1}}},{"unified":"1F935","sheet_x":42,"sheet_y":2,"skin_variations":{"1F3FB":{"unified":"1F935-1F3FB","sheet_x":42,"sheet_y":3},"1F3FC":{"unified":"1F935-1F3FC","sheet_x":42,"sheet_y":4},"1F3FD":{"unified":"1F935-1F3FD","sheet_x":42,"sheet_y":5},"1F3FE":{"unified":"1F935-1F3FE","sheet_x":42,"sheet_y":6},"1F3FF":{"unified":"1F935-1F3FF","sheet_x":42,"sheet_y":7}}},{"unified":"1F936","sheet_x":42,"sheet_y":8,"skin_variations":{"1F3FB":{"unified":"1F936-1F3FB","sheet_x":42,"sheet_y":9},"1F3FC":{"unified":"1F936-1F3FC","sheet_x":42,"sheet_y":10},"1F3FD":{"unified":"1F936-1F3FD","sheet_x":42,"sheet_y":11},"1F3FE":{"unified":"1F936-1F3FE","sheet_x":42,"sheet_y":12},"1F3FF":{"unified":"1F936-1F3FF","sheet_x":42,"sheet_y":13}}},{"unified":"1F937-200D-2640-FE0F","sheet_x":42,"sheet_y":14,"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB-200D-2640-FE0F","sheet_x":42,"sheet_y":15},"1F3FC":{"unified":"1F937-1F3FC-200D-2640-FE0F","sheet_x":42,"sheet_y":16},"1F3FD":{"unified":"1F937-1F3FD-200D-2640-FE0F","sheet_x":42,"sheet_y":17},"1F3FE":{"unified":"1F937-1F3FE-200D-2640-FE0F","sheet_x":42,"sheet_y":18},"1F3FF":{"unified":"1F937-1F3FF-200D-2640-FE0F","sheet_x":42,"sheet_y":19}}},{"unified":"1F937-200D-2642-FE0F","sheet_x":42,"sheet_y":20,"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB-200D-2642-FE0F","sheet_x":42,"sheet_y":21},"1F3FC":{"unified":"1F937-1F3FC-200D-2642-FE0F","sheet_x":42,"sheet_y":22},"1F3FD":{"unified":"1F937-1F3FD-200D-2642-FE0F","sheet_x":42,"sheet_y":23},"1F3FE":{"unified":"1F937-1F3FE-200D-2642-FE0F","sheet_x":42,"sheet_y":24},"1F3FF":{"unified":"1F937-1F3FF-200D-2642-FE0F","sheet_x":42,"sheet_y":25}}},{"unified":"1F937","sheet_x":42,"sheet_y":26,"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB","sheet_x":42,"sheet_y":27},"1F3FC":{"unified":"1F937-1F3FC","sheet_x":42,"sheet_y":28},"1F3FD":{"unified":"1F937-1F3FD","sheet_x":42,"sheet_y":29},"1F3FE":{"unified":"1F937-1F3FE","sheet_x":42,"sheet_y":30},"1F3FF":{"unified":"1F937-1F3FF","sheet_x":42,"sheet_y":31}}},{"unified":"1F938-200D-2640-FE0F","sheet_x":42,"sheet_y":32,"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB-200D-2640-FE0F","sheet_x":42,"sheet_y":33},"1F3FC":{"unified":"1F938-1F3FC-200D-2640-FE0F","sheet_x":42,"sheet_y":34},"1F3FD":{"unified":"1F938-1F3FD-200D-2640-FE0F","sheet_x":42,"sheet_y":35},"1F3FE":{"unified":"1F938-1F3FE-200D-2640-FE0F","sheet_x":42,"sheet_y":36},"1F3FF":{"unified":"1F938-1F3FF-200D-2640-FE0F","sheet_x":42,"sheet_y":37}}},{"unified":"1F938-200D-2642-FE0F","sheet_x":42,"sheet_y":38,"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB-200D-2642-FE0F","sheet_x":42,"sheet_y":39},"1F3FC":{"unified":"1F938-1F3FC-200D-2642-FE0F","sheet_x":42,"sheet_y":40},"1F3FD":{"unified":"1F938-1F3FD-200D-2642-FE0F","sheet_x":42,"sheet_y":41},"1F3FE":{"unified":"1F938-1F3FE-200D-2642-FE0F","sheet_x":42,"sheet_y":42},"1F3FF":{"unified":"1F938-1F3FF-200D-2642-FE0F","sheet_x":42,"sheet_y":43}}},{"unified":"1F938","sheet_x":42,"sheet_y":44,"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB","sheet_x":42,"sheet_y":45},"1F3FC":{"unified":"1F938-1F3FC","sheet_x":42,"sheet_y":46},"1F3FD":{"unified":"1F938-1F3FD","sheet_x":42,"sheet_y":47},"1F3FE":{"unified":"1F938-1F3FE","sheet_x":42,"sheet_y":48},"1F3FF":{"unified":"1F938-1F3FF","sheet_x":42,"sheet_y":49}}},{"unified":"1F939-200D-2640-FE0F","sheet_x":42,"sheet_y":50,"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB-200D-2640-FE0F","sheet_x":42,"sheet_y":51},"1F3FC":{"unified":"1F939-1F3FC-200D-2640-FE0F","sheet_x":42,"sheet_y":52},"1F3FD":{"unified":"1F939-1F3FD-200D-2640-FE0F","sheet_x":42,"sheet_y":53},"1F3FE":{"unified":"1F939-1F3FE-200D-2640-FE0F","sheet_x":42,"sheet_y":54},"1F3FF":{"unified":"1F939-1F3FF-200D-2640-FE0F","sheet_x":42,"sheet_y":55}}},{"unified":"1F939-200D-2642-FE0F","sheet_x":42,"sheet_y":56,"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB-200D-2642-FE0F","sheet_x":42,"sheet_y":57},"1F3FC":{"unified":"1F939-1F3FC-200D-2642-FE0F","sheet_x":42,"sheet_y":58},"1F3FD":{"unified":"1F939-1F3FD-200D-2642-FE0F","sheet_x":42,"sheet_y":59},"1F3FE":{"unified":"1F939-1F3FE-200D-2642-FE0F","sheet_x":42,"sheet_y":60},"1F3FF":{"unified":"1F939-1F3FF-200D-2642-FE0F","sheet_x":42,"sheet_y":61}}},{"unified":"1F939","sheet_x":43,"sheet_y":0,"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB","sheet_x":43,"sheet_y":1},"1F3FC":{"unified":"1F939-1F3FC","sheet_x":43,"sheet_y":2},"1F3FD":{"unified":"1F939-1F3FD","sheet_x":43,"sheet_y":3},"1F3FE":{"unified":"1F939-1F3FE","sheet_x":43,"sheet_y":4},"1F3FF":{"unified":"1F939-1F3FF","sheet_x":43,"sheet_y":5}}},{"unified":"1F93A","sheet_x":43,"sheet_y":6,"skin_variations":{}},{"unified":"1F93C-200D-2640-FE0F","sheet_x":43,"sheet_y":7,"skin_variations":{}},{"unified":"1F93C-200D-2642-FE0F","sheet_x":43,"sheet_y":8,"skin_variations":{}},{"unified":"1F93C","sheet_x":43,"sheet_y":9,"skin_variations":{}},{"unified":"1F93D-200D-2640-FE0F","sheet_x":43,"sheet_y":10,"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB-200D-2640-FE0F","sheet_x":43,"sheet_y":11},"1F3FC":{"unified":"1F93D-1F3FC-200D-2640-FE0F","sheet_x":43,"sheet_y":12},"1F3FD":{"unified":"1F93D-1F3FD-200D-2640-FE0F","sheet_x":43,"sheet_y":13},"1F3FE":{"unified":"1F93D-1F3FE-200D-2640-FE0F","sheet_x":43,"sheet_y":14},"1F3FF":{"unified":"1F93D-1F3FF-200D-2640-FE0F","sheet_x":43,"sheet_y":15}}},{"unified":"1F93D-200D-2642-FE0F","sheet_x":43,"sheet_y":16,"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB-200D-2642-FE0F","sheet_x":43,"sheet_y":17},"1F3FC":{"unified":"1F93D-1F3FC-200D-2642-FE0F","sheet_x":43,"sheet_y":18},"1F3FD":{"unified":"1F93D-1F3FD-200D-2642-FE0F","sheet_x":43,"sheet_y":19},"1F3FE":{"unified":"1F93D-1F3FE-200D-2642-FE0F","sheet_x":43,"sheet_y":20},"1F3FF":{"unified":"1F93D-1F3FF-200D-2642-FE0F","sheet_x":43,"sheet_y":21}}},{"unified":"1F93D","sheet_x":43,"sheet_y":22,"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB","sheet_x":43,"sheet_y":23},"1F3FC":{"unified":"1F93D-1F3FC","sheet_x":43,"sheet_y":24},"1F3FD":{"unified":"1F93D-1F3FD","sheet_x":43,"sheet_y":25},"1F3FE":{"unified":"1F93D-1F3FE","sheet_x":43,"sheet_y":26},"1F3FF":{"unified":"1F93D-1F3FF","sheet_x":43,"sheet_y":27}}},{"unified":"1F93E-200D-2640-FE0F","sheet_x":43,"sheet_y":28,"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB-200D-2640-FE0F","sheet_x":43,"sheet_y":29},"1F3FC":{"unified":"1F93E-1F3FC-200D-2640-FE0F","sheet_x":43,"sheet_y":30},"1F3FD":{"unified":"1F93E-1F3FD-200D-2640-FE0F","sheet_x":43,"sheet_y":31},"1F3FE":{"unified":"1F93E-1F3FE-200D-2640-FE0F","sheet_x":43,"sheet_y":32},"1F3FF":{"unified":"1F93E-1F3FF-200D-2640-FE0F","sheet_x":43,"sheet_y":33}}},{"unified":"1F93E-200D-2642-FE0F","sheet_x":43,"sheet_y":34,"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB-200D-2642-FE0F","sheet_x":43,"sheet_y":35},"1F3FC":{"unified":"1F93E-1F3FC-200D-2642-FE0F","sheet_x":43,"sheet_y":36},"1F3FD":{"unified":"1F93E-1F3FD-200D-2642-FE0F","sheet_x":43,"sheet_y":37},"1F3FE":{"unified":"1F93E-1F3FE-200D-2642-FE0F","sheet_x":43,"sheet_y":38},"1F3FF":{"unified":"1F93E-1F3FF-200D-2642-FE0F","sheet_x":43,"sheet_y":39}}},{"unified":"1F93E","sheet_x":43,"sheet_y":40,"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB","sheet_x":43,"sheet_y":41},"1F3FC":{"unified":"1F93E-1F3FC","sheet_x":43,"sheet_y":42},"1F3FD":{"unified":"1F93E-1F3FD","sheet_x":43,"sheet_y":43},"1F3FE":{"unified":"1F93E-1F3FE","sheet_x":43,"sheet_y":44},"1F3FF":{"unified":"1F93E-1F3FF","sheet_x":43,"sheet_y":45}}},{"unified":"1F93F","sheet_x":43,"sheet_y":46,"skin_variations":{}},{"unified":"1F940","sheet_x":43,"sheet_y":47,"skin_variations":{}},{"unified":"1F941","sheet_x":43,"sheet_y":48,"skin_variations":{}},{"unified":"1F942","sheet_x":43,"sheet_y":49,"skin_variations":{}},{"unified":"1F943","sheet_x":43,"sheet_y":50,"skin_variations":{}},{"unified":"1F944","sheet_x":43,"sheet_y":51,"skin_variations":{}},{"unified":"1F945","sheet_x":43,"sheet_y":52,"skin_variations":{}},{"unified":"1F947","sheet_x":43,"sheet_y":53,"skin_variations":{}},{"unified":"1F948","sheet_x":43,"sheet_y":54,"skin_variations":{}},{"unified":"1F949","sheet_x":43,"sheet_y":55,"skin_variations":{}},{"unified":"1F94A","sheet_x":43,"sheet_y":56,"skin_variations":{}},{"unified":"1F94B","sheet_x":43,"sheet_y":57,"skin_variations":{}},{"unified":"1F94C","sheet_x":43,"sheet_y":58,"skin_variations":{}},{"unified":"1F94D","sheet_x":43,"sheet_y":59,"skin_variations":{}},{"unified":"1F94E","sheet_x":43,"sheet_y":60,"skin_variations":{}},{"unified":"1F94F","sheet_x":43,"sheet_y":61,"skin_variations":{}},{"unified":"1F950","sheet_x":44,"sheet_y":0,"skin_variations":{}},{"unified":"1F951","sheet_x":44,"sheet_y":1,"skin_variations":{}},{"unified":"1F952","sheet_x":44,"sheet_y":2,"skin_variations":{}},{"unified":"1F953","sheet_x":44,"sheet_y":3,"skin_variations":{}},{"unified":"1F954","sheet_x":44,"sheet_y":4,"skin_variations":{}},{"unified":"1F955","sheet_x":44,"sheet_y":5,"skin_variations":{}},{"unified":"1F956","sheet_x":44,"sheet_y":6,"skin_variations":{}},{"unified":"1F957","sheet_x":44,"sheet_y":7,"skin_variations":{}},{"unified":"1F958","sheet_x":44,"sheet_y":8,"skin_variations":{}},{"unified":"1F959","sheet_x":44,"sheet_y":9,"skin_variations":{}},{"unified":"1F95A","sheet_x":44,"sheet_y":10,"skin_variations":{}},{"unified":"1F95B","sheet_x":44,"sheet_y":11,"skin_variations":{}},{"unified":"1F95C","sheet_x":44,"sheet_y":12,"skin_variations":{}},{"unified":"1F95D","sheet_x":44,"sheet_y":13,"skin_variations":{}},{"unified":"1F95E","sheet_x":44,"sheet_y":14,"skin_variations":{}},{"unified":"1F95F","sheet_x":44,"sheet_y":15,"skin_variations":{}},{"unified":"1F960","sheet_x":44,"sheet_y":16,"skin_variations":{}},{"unified":"1F961","sheet_x":44,"sheet_y":17,"skin_variations":{}},{"unified":"1F962","sheet_x":44,"sheet_y":18,"skin_variations":{}},{"unified":"1F963","sheet_x":44,"sheet_y":19,"skin_variations":{}},{"unified":"1F964","sheet_x":44,"sheet_y":20,"skin_variations":{}},{"unified":"1F965","sheet_x":44,"sheet_y":21,"skin_variations":{}},{"unified":"1F966","sheet_x":44,"sheet_y":22,"skin_variations":{}},{"unified":"1F967","sheet_x":44,"sheet_y":23,"skin_variations":{}},{"unified":"1F968","sheet_x":44,"sheet_y":24,"skin_variations":{}},{"unified":"1F969","sheet_x":44,"sheet_y":25,"skin_variations":{}},{"unified":"1F96A","sheet_x":44,"sheet_y":26,"skin_variations":{}},{"unified":"1F96B","sheet_x":44,"sheet_y":27,"skin_variations":{}},{"unified":"1F96C","sheet_x":44,"sheet_y":28,"skin_variations":{}},{"unified":"1F96D","sheet_x":44,"sheet_y":29,"skin_variations":{}},{"unified":"1F96E","sheet_x":44,"sheet_y":30,"skin_variations":{}},{"unified":"1F96F","sheet_x":44,"sheet_y":31,"skin_variations":{}},{"unified":"1F970","sheet_x":44,"sheet_y":32,"skin_variations":{}},{"unified":"1F971","sheet_x":44,"sheet_y":33,"skin_variations":{}},{"unified":"1F972","sheet_x":44,"sheet_y":34,"skin_variations":{}},{"unified":"1F973","sheet_x":44,"sheet_y":35,"skin_variations":{}},{"unified":"1F974","sheet_x":44,"sheet_y":36,"skin_variations":{}},{"unified":"1F975","sheet_x":44,"sheet_y":37,"skin_variations":{}},{"unified":"1F976","sheet_x":44,"sheet_y":38,"skin_variations":{}},{"unified":"1F977","sheet_x":44,"sheet_y":39,"skin_variations":{"1F3FB":{"unified":"1F977-1F3FB","sheet_x":44,"sheet_y":40},"1F3FC":{"unified":"1F977-1F3FC","sheet_x":44,"sheet_y":41},"1F3FD":{"unified":"1F977-1F3FD","sheet_x":44,"sheet_y":42},"1F3FE":{"unified":"1F977-1F3FE","sheet_x":44,"sheet_y":43},"1F3FF":{"unified":"1F977-1F3FF","sheet_x":44,"sheet_y":44}}},{"unified":"1F978","sheet_x":44,"sheet_y":45,"skin_variations":{}},{"unified":"1F979","sheet_x":44,"sheet_y":46,"skin_variations":{}},{"unified":"1F97A","sheet_x":44,"sheet_y":47,"skin_variations":{}},{"unified":"1F97B","sheet_x":44,"sheet_y":48,"skin_variations":{}},{"unified":"1F97C","sheet_x":44,"sheet_y":49,"skin_variations":{}},{"unified":"1F97D","sheet_x":44,"sheet_y":50,"skin_variations":{}},{"unified":"1F97E","sheet_x":44,"sheet_y":51,"skin_variations":{}},{"unified":"1F97F","sheet_x":44,"sheet_y":52,"skin_variations":{}},{"unified":"1F980","sheet_x":44,"sheet_y":53,"skin_variations":{}},{"unified":"1F981","sheet_x":44,"sheet_y":54,"skin_variations":{}},{"unified":"1F982","sheet_x":44,"sheet_y":55,"skin_variations":{}},{"unified":"1F983","sheet_x":44,"sheet_y":56,"skin_variations":{}},{"unified":"1F984","sheet_x":44,"sheet_y":57,"skin_variations":{}},{"unified":"1F985","sheet_x":44,"sheet_y":58,"skin_variations":{}},{"unified":"1F986","sheet_x":44,"sheet_y":59,"skin_variations":{}},{"unified":"1F987","sheet_x":44,"sheet_y":60,"skin_variations":{}},{"unified":"1F988","sheet_x":44,"sheet_y":61,"skin_variations":{}},{"unified":"1F989","sheet_x":45,"sheet_y":0,"skin_variations":{}},{"unified":"1F98A","sheet_x":45,"sheet_y":1,"skin_variations":{}},{"unified":"1F98B","sheet_x":45,"sheet_y":2,"skin_variations":{}},{"unified":"1F98C","sheet_x":45,"sheet_y":3,"skin_variations":{}},{"unified":"1F98D","sheet_x":45,"sheet_y":4,"skin_variations":{}},{"unified":"1F98E","sheet_x":45,"sheet_y":5,"skin_variations":{}},{"unified":"1F98F","sheet_x":45,"sheet_y":6,"skin_variations":{}},{"unified":"1F990","sheet_x":45,"sheet_y":7,"skin_variations":{}},{"unified":"1F991","sheet_x":45,"sheet_y":8,"skin_variations":{}},{"unified":"1F992","sheet_x":45,"sheet_y":9,"skin_variations":{}},{"unified":"1F993","sheet_x":45,"sheet_y":10,"skin_variations":{}},{"unified":"1F994","sheet_x":45,"sheet_y":11,"skin_variations":{}},{"unified":"1F995","sheet_x":45,"sheet_y":12,"skin_variations":{}},{"unified":"1F996","sheet_x":45,"sheet_y":13,"skin_variations":{}},{"unified":"1F997","sheet_x":45,"sheet_y":14,"skin_variations":{}},{"unified":"1F998","sheet_x":45,"sheet_y":15,"skin_variations":{}},{"unified":"1F999","sheet_x":45,"sheet_y":16,"skin_variations":{}},{"unified":"1F99A","sheet_x":45,"sheet_y":17,"skin_variations":{}},{"unified":"1F99B","sheet_x":45,"sheet_y":18,"skin_variations":{}},{"unified":"1F99C","sheet_x":45,"sheet_y":19,"skin_variations":{}},{"unified":"1F99D","sheet_x":45,"sheet_y":20,"skin_variations":{}},{"unified":"1F99E","sheet_x":45,"sheet_y":21,"skin_variations":{}},{"unified":"1F99F","sheet_x":45,"sheet_y":22,"skin_variations":{}},{"unified":"1F9A0","sheet_x":45,"sheet_y":23,"skin_variations":{}},{"unified":"1F9A1","sheet_x":45,"sheet_y":24,"skin_variations":{}},{"unified":"1F9A2","sheet_x":45,"sheet_y":25,"skin_variations":{}},{"unified":"1F9A3","sheet_x":45,"sheet_y":26,"skin_variations":{}},{"unified":"1F9A4","sheet_x":45,"sheet_y":27,"skin_variations":{}},{"unified":"1F9A5","sheet_x":45,"sheet_y":28,"skin_variations":{}},{"unified":"1F9A6","sheet_x":45,"sheet_y":29,"skin_variations":{}},{"unified":"1F9A7","sheet_x":45,"sheet_y":30,"skin_variations":{}},{"unified":"1F9A8","sheet_x":45,"sheet_y":31,"skin_variations":{}},{"unified":"1F9A9","sheet_x":45,"sheet_y":32,"skin_variations":{}},{"unified":"1F9AA","sheet_x":45,"sheet_y":33,"skin_variations":{}},{"unified":"1F9AB","sheet_x":45,"sheet_y":34,"skin_variations":{}},{"unified":"1F9AC","sheet_x":45,"sheet_y":35,"skin_variations":{}},{"unified":"1F9AD","sheet_x":45,"sheet_y":36,"skin_variations":{}},{"unified":"1F9AE","sheet_x":45,"sheet_y":37,"skin_variations":{}},{"unified":"1F9AF","sheet_x":45,"sheet_y":38,"skin_variations":{}},{"unified":"1F9B4","sheet_x":45,"sheet_y":39,"skin_variations":{}},{"unified":"1F9B5","sheet_x":45,"sheet_y":40,"skin_variations":{"1F3FB":{"unified":"1F9B5-1F3FB","sheet_x":45,"sheet_y":41},"1F3FC":{"unified":"1F9B5-1F3FC","sheet_x":45,"sheet_y":42},"1F3FD":{"unified":"1F9B5-1F3FD","sheet_x":45,"sheet_y":43},"1F3FE":{"unified":"1F9B5-1F3FE","sheet_x":45,"sheet_y":44},"1F3FF":{"unified":"1F9B5-1F3FF","sheet_x":45,"sheet_y":45}}},{"unified":"1F9B6","sheet_x":45,"sheet_y":46,"skin_variations":{"1F3FB":{"unified":"1F9B6-1F3FB","sheet_x":45,"sheet_y":47},"1F3FC":{"unified":"1F9B6-1F3FC","sheet_x":45,"sheet_y":48},"1F3FD":{"unified":"1F9B6-1F3FD","sheet_x":45,"sheet_y":49},"1F3FE":{"unified":"1F9B6-1F3FE","sheet_x":45,"sheet_y":50},"1F3FF":{"unified":"1F9B6-1F3FF","sheet_x":45,"sheet_y":51}}},{"unified":"1F9B7","sheet_x":45,"sheet_y":52,"skin_variations":{}},{"unified":"1F9B8-200D-2640-FE0F","sheet_x":45,"sheet_y":53,"skin_variations":{"1F3FB":{"unified":"1F9B8-1F3FB-200D-2640-FE0F","sheet_x":45,"sheet_y":54},"1F3FC":{"unified":"1F9B8-1F3FC-200D-2640-FE0F","sheet_x":45,"sheet_y":55},"1F3FD":{"unified":"1F9B8-1F3FD-200D-2640-FE0F","sheet_x":45,"sheet_y":56},"1F3FE":{"unified":"1F9B8-1F3FE-200D-2640-FE0F","sheet_x":45,"sheet_y":57},"1F3FF":{"unified":"1F9B8-1F3FF-200D-2640-FE0F","sheet_x":45,"sheet_y":58}}},{"unified":"1F9B8-200D-2642-FE0F","sheet_x":45,"sheet_y":59,"skin_variations":{"1F3FB":{"unified":"1F9B8-1F3FB-200D-2642-FE0F","sheet_x":45,"sheet_y":60},"1F3FC":{"unified":"1F9B8-1F3FC-200D-2642-FE0F","sheet_x":45,"sheet_y":61},"1F3FD":{"unified":"1F9B8-1F3FD-200D-2642-FE0F","sheet_x":46,"sheet_y":0},"1F3FE":{"unified":"1F9B8-1F3FE-200D-2642-FE0F","sheet_x":46,"sheet_y":1},"1F3FF":{"unified":"1F9B8-1F3FF-200D-2642-FE0F","sheet_x":46,"sheet_y":2}}},{"unified":"1F9B8","sheet_x":46,"sheet_y":3,"skin_variations":{"1F3FB":{"unified":"1F9B8-1F3FB","sheet_x":46,"sheet_y":4},"1F3FC":{"unified":"1F9B8-1F3FC","sheet_x":46,"sheet_y":5},"1F3FD":{"unified":"1F9B8-1F3FD","sheet_x":46,"sheet_y":6},"1F3FE":{"unified":"1F9B8-1F3FE","sheet_x":46,"sheet_y":7},"1F3FF":{"unified":"1F9B8-1F3FF","sheet_x":46,"sheet_y":8}}},{"unified":"1F9B9-200D-2640-FE0F","sheet_x":46,"sheet_y":9,"skin_variations":{"1F3FB":{"unified":"1F9B9-1F3FB-200D-2640-FE0F","sheet_x":46,"sheet_y":10},"1F3FC":{"unified":"1F9B9-1F3FC-200D-2640-FE0F","sheet_x":46,"sheet_y":11},"1F3FD":{"unified":"1F9B9-1F3FD-200D-2640-FE0F","sheet_x":46,"sheet_y":12},"1F3FE":{"unified":"1F9B9-1F3FE-200D-2640-FE0F","sheet_x":46,"sheet_y":13},"1F3FF":{"unified":"1F9B9-1F3FF-200D-2640-FE0F","sheet_x":46,"sheet_y":14}}},{"unified":"1F9B9-200D-2642-FE0F","sheet_x":46,"sheet_y":15,"skin_variations":{"1F3FB":{"unified":"1F9B9-1F3FB-200D-2642-FE0F","sheet_x":46,"sheet_y":16},"1F3FC":{"unified":"1F9B9-1F3FC-200D-2642-FE0F","sheet_x":46,"sheet_y":17},"1F3FD":{"unified":"1F9B9-1F3FD-200D-2642-FE0F","sheet_x":46,"sheet_y":18},"1F3FE":{"unified":"1F9B9-1F3FE-200D-2642-FE0F","sheet_x":46,"sheet_y":19},"1F3FF":{"unified":"1F9B9-1F3FF-200D-2642-FE0F","sheet_x":46,"sheet_y":20}}},{"unified":"1F9B9","sheet_x":46,"sheet_y":21,"skin_variations":{"1F3FB":{"unified":"1F9B9-1F3FB","sheet_x":46,"sheet_y":22},"1F3FC":{"unified":"1F9B9-1F3FC","sheet_x":46,"sheet_y":23},"1F3FD":{"unified":"1F9B9-1F3FD","sheet_x":46,"sheet_y":24},"1F3FE":{"unified":"1F9B9-1F3FE","sheet_x":46,"sheet_y":25},"1F3FF":{"unified":"1F9B9-1F3FF","sheet_x":46,"sheet_y":26}}},{"unified":"1F9BA","sheet_x":46,"sheet_y":27,"skin_variations":{}},{"unified":"1F9BB","sheet_x":46,"sheet_y":28,"skin_variations":{"1F3FB":{"unified":"1F9BB-1F3FB","sheet_x":46,"sheet_y":29},"1F3FC":{"unified":"1F9BB-1F3FC","sheet_x":46,"sheet_y":30},"1F3FD":{"unified":"1F9BB-1F3FD","sheet_x":46,"sheet_y":31},"1F3FE":{"unified":"1F9BB-1F3FE","sheet_x":46,"sheet_y":32},"1F3FF":{"unified":"1F9BB-1F3FF","sheet_x":46,"sheet_y":33}}},{"unified":"1F9BC","sheet_x":46,"sheet_y":34,"skin_variations":{}},{"unified":"1F9BD","sheet_x":46,"sheet_y":35,"skin_variations":{}},{"unified":"1F9BE","sheet_x":46,"sheet_y":36,"skin_variations":{}},{"unified":"1F9BF","sheet_x":46,"sheet_y":37,"skin_variations":{}},{"unified":"1F9C0","sheet_x":46,"sheet_y":38,"skin_variations":{}},{"unified":"1F9C1","sheet_x":46,"sheet_y":39,"skin_variations":{}},{"unified":"1F9C2","sheet_x":46,"sheet_y":40,"skin_variations":{}},{"unified":"1F9C3","sheet_x":46,"sheet_y":41,"skin_variations":{}},{"unified":"1F9C4","sheet_x":46,"sheet_y":42,"skin_variations":{}},{"unified":"1F9C5","sheet_x":46,"sheet_y":43,"skin_variations":{}},{"unified":"1F9C6","sheet_x":46,"sheet_y":44,"skin_variations":{}},{"unified":"1F9C7","sheet_x":46,"sheet_y":45,"skin_variations":{}},{"unified":"1F9C8","sheet_x":46,"sheet_y":46,"skin_variations":{}},{"unified":"1F9C9","sheet_x":46,"sheet_y":47,"skin_variations":{}},{"unified":"1F9CA","sheet_x":46,"sheet_y":48,"skin_variations":{}},{"unified":"1F9CB","sheet_x":46,"sheet_y":49,"skin_variations":{}},{"unified":"1F9CC","sheet_x":46,"sheet_y":50,"skin_variations":{}},{"unified":"1F9CD-200D-2640-FE0F","sheet_x":46,"sheet_y":51,"skin_variations":{"1F3FB":{"unified":"1F9CD-1F3FB-200D-2640-FE0F","sheet_x":46,"sheet_y":52},"1F3FC":{"unified":"1F9CD-1F3FC-200D-2640-FE0F","sheet_x":46,"sheet_y":53},"1F3FD":{"unified":"1F9CD-1F3FD-200D-2640-FE0F","sheet_x":46,"sheet_y":54},"1F3FE":{"unified":"1F9CD-1F3FE-200D-2640-FE0F","sheet_x":46,"sheet_y":55},"1F3FF":{"unified":"1F9CD-1F3FF-200D-2640-FE0F","sheet_x":46,"sheet_y":56}}},{"unified":"1F9CD-200D-2642-FE0F","sheet_x":46,"sheet_y":57,"skin_variations":{"1F3FB":{"unified":"1F9CD-1F3FB-200D-2642-FE0F","sheet_x":46,"sheet_y":58},"1F3FC":{"unified":"1F9CD-1F3FC-200D-2642-FE0F","sheet_x":46,"sheet_y":59},"1F3FD":{"unified":"1F9CD-1F3FD-200D-2642-FE0F","sheet_x":46,"sheet_y":60},"1F3FE":{"unified":"1F9CD-1F3FE-200D-2642-FE0F","sheet_x":46,"sheet_y":61},"1F3FF":{"unified":"1F9CD-1F3FF-200D-2642-FE0F","sheet_x":47,"sheet_y":0}}},{"unified":"1F9CD","sheet_x":47,"sheet_y":1,"skin_variations":{"1F3FB":{"unified":"1F9CD-1F3FB","sheet_x":47,"sheet_y":2},"1F3FC":{"unified":"1F9CD-1F3FC","sheet_x":47,"sheet_y":3},"1F3FD":{"unified":"1F9CD-1F3FD","sheet_x":47,"sheet_y":4},"1F3FE":{"unified":"1F9CD-1F3FE","sheet_x":47,"sheet_y":5},"1F3FF":{"unified":"1F9CD-1F3FF","sheet_x":47,"sheet_y":6}}},{"unified":"1F9CE-200D-2640-FE0F","sheet_x":47,"sheet_y":7,"skin_variations":{"1F3FB":{"unified":"1F9CE-1F3FB-200D-2640-FE0F","sheet_x":47,"sheet_y":8},"1F3FC":{"unified":"1F9CE-1F3FC-200D-2640-FE0F","sheet_x":47,"sheet_y":9},"1F3FD":{"unified":"1F9CE-1F3FD-200D-2640-FE0F","sheet_x":47,"sheet_y":10},"1F3FE":{"unified":"1F9CE-1F3FE-200D-2640-FE0F","sheet_x":47,"sheet_y":11},"1F3FF":{"unified":"1F9CE-1F3FF-200D-2640-FE0F","sheet_x":47,"sheet_y":12}}},{"unified":"1F9CE-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":13,"skin_variations":{"1F3FB":{"unified":"1F9CE-1F3FB-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":14},"1F3FC":{"unified":"1F9CE-1F3FC-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":15},"1F3FD":{"unified":"1F9CE-1F3FD-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":16},"1F3FE":{"unified":"1F9CE-1F3FE-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":17},"1F3FF":{"unified":"1F9CE-1F3FF-200D-2640-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":18}}},{"unified":"1F9CE-200D-2642-FE0F","sheet_x":47,"sheet_y":19,"skin_variations":{"1F3FB":{"unified":"1F9CE-1F3FB-200D-2642-FE0F","sheet_x":47,"sheet_y":20},"1F3FC":{"unified":"1F9CE-1F3FC-200D-2642-FE0F","sheet_x":47,"sheet_y":21},"1F3FD":{"unified":"1F9CE-1F3FD-200D-2642-FE0F","sheet_x":47,"sheet_y":22},"1F3FE":{"unified":"1F9CE-1F3FE-200D-2642-FE0F","sheet_x":47,"sheet_y":23},"1F3FF":{"unified":"1F9CE-1F3FF-200D-2642-FE0F","sheet_x":47,"sheet_y":24}}},{"unified":"1F9CE-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":25,"skin_variations":{"1F3FB":{"unified":"1F9CE-1F3FB-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":26},"1F3FC":{"unified":"1F9CE-1F3FC-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":27},"1F3FD":{"unified":"1F9CE-1F3FD-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":28},"1F3FE":{"unified":"1F9CE-1F3FE-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":29},"1F3FF":{"unified":"1F9CE-1F3FF-200D-2642-FE0F-200D-27A1-FE0F","sheet_x":47,"sheet_y":30}}},{"unified":"1F9CE-200D-27A1-FE0F","sheet_x":47,"sheet_y":31,"skin_variations":{"1F3FB":{"unified":"1F9CE-1F3FB-200D-27A1-FE0F","sheet_x":47,"sheet_y":32},"1F3FC":{"unified":"1F9CE-1F3FC-200D-27A1-FE0F","sheet_x":47,"sheet_y":33},"1F3FD":{"unified":"1F9CE-1F3FD-200D-27A1-FE0F","sheet_x":47,"sheet_y":34},"1F3FE":{"unified":"1F9CE-1F3FE-200D-27A1-FE0F","sheet_x":47,"sheet_y":35},"1F3FF":{"unified":"1F9CE-1F3FF-200D-27A1-FE0F","sheet_x":47,"sheet_y":36}}},{"unified":"1F9CE","sheet_x":47,"sheet_y":37,"skin_variations":{"1F3FB":{"unified":"1F9CE-1F3FB","sheet_x":47,"sheet_y":38},"1F3FC":{"unified":"1F9CE-1F3FC","sheet_x":47,"sheet_y":39},"1F3FD":{"unified":"1F9CE-1F3FD","sheet_x":47,"sheet_y":40},"1F3FE":{"unified":"1F9CE-1F3FE","sheet_x":47,"sheet_y":41},"1F3FF":{"unified":"1F9CE-1F3FF","sheet_x":47,"sheet_y":42}}},{"unified":"1F9CF-200D-2640-FE0F","sheet_x":47,"sheet_y":43,"skin_variations":{"1F3FB":{"unified":"1F9CF-1F3FB-200D-2640-FE0F","sheet_x":47,"sheet_y":44},"1F3FC":{"unified":"1F9CF-1F3FC-200D-2640-FE0F","sheet_x":47,"sheet_y":45},"1F3FD":{"unified":"1F9CF-1F3FD-200D-2640-FE0F","sheet_x":47,"sheet_y":46},"1F3FE":{"unified":"1F9CF-1F3FE-200D-2640-FE0F","sheet_x":47,"sheet_y":47},"1F3FF":{"unified":"1F9CF-1F3FF-200D-2640-FE0F","sheet_x":47,"sheet_y":48}}},{"unified":"1F9CF-200D-2642-FE0F","sheet_x":47,"sheet_y":49,"skin_variations":{"1F3FB":{"unified":"1F9CF-1F3FB-200D-2642-FE0F","sheet_x":47,"sheet_y":50},"1F3FC":{"unified":"1F9CF-1F3FC-200D-2642-FE0F","sheet_x":47,"sheet_y":51},"1F3FD":{"unified":"1F9CF-1F3FD-200D-2642-FE0F","sheet_x":47,"sheet_y":52},"1F3FE":{"unified":"1F9CF-1F3FE-200D-2642-FE0F","sheet_x":47,"sheet_y":53},"1F3FF":{"unified":"1F9CF-1F3FF-200D-2642-FE0F","sheet_x":47,"sheet_y":54}}},{"unified":"1F9CF","sheet_x":47,"sheet_y":55,"skin_variations":{"1F3FB":{"unified":"1F9CF-1F3FB","sheet_x":47,"sheet_y":56},"1F3FC":{"unified":"1F9CF-1F3FC","sheet_x":47,"sheet_y":57},"1F3FD":{"unified":"1F9CF-1F3FD","sheet_x":47,"sheet_y":58},"1F3FE":{"unified":"1F9CF-1F3FE","sheet_x":47,"sheet_y":59},"1F3FF":{"unified":"1F9CF-1F3FF","sheet_x":47,"sheet_y":60}}},{"unified":"1F9D0","sheet_x":47,"sheet_y":61,"skin_variations":{}},{"unified":"1F9D1-200D-1F33E","sheet_x":48,"sheet_y":0,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F33E","sheet_x":48,"sheet_y":1},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F33E","sheet_x":48,"sheet_y":2},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F33E","sheet_x":48,"sheet_y":3},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F33E","sheet_x":48,"sheet_y":4},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F33E","sheet_x":48,"sheet_y":5}}},{"unified":"1F9D1-200D-1F373","sheet_x":48,"sheet_y":6,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F373","sheet_x":48,"sheet_y":7},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F373","sheet_x":48,"sheet_y":8},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F373","sheet_x":48,"sheet_y":9},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F373","sheet_x":48,"sheet_y":10},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F373","sheet_x":48,"sheet_y":11}}},{"unified":"1F9D1-200D-1F37C","sheet_x":48,"sheet_y":12,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F37C","sheet_x":48,"sheet_y":13},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F37C","sheet_x":48,"sheet_y":14},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F37C","sheet_x":48,"sheet_y":15},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F37C","sheet_x":48,"sheet_y":16},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F37C","sheet_x":48,"sheet_y":17}}},{"unified":"1F9D1-200D-1F384","sheet_x":48,"sheet_y":18,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F384","sheet_x":48,"sheet_y":19},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F384","sheet_x":48,"sheet_y":20},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F384","sheet_x":48,"sheet_y":21},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F384","sheet_x":48,"sheet_y":22},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F384","sheet_x":48,"sheet_y":23}}},{"unified":"1F9D1-200D-1F393","sheet_x":48,"sheet_y":24,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F393","sheet_x":48,"sheet_y":25},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F393","sheet_x":48,"sheet_y":26},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F393","sheet_x":48,"sheet_y":27},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F393","sheet_x":48,"sheet_y":28},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F393","sheet_x":48,"sheet_y":29}}},{"unified":"1F9D1-200D-1F3A4","sheet_x":48,"sheet_y":30,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F3A4","sheet_x":48,"sheet_y":31},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F3A4","sheet_x":48,"sheet_y":32},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F3A4","sheet_x":48,"sheet_y":33},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F3A4","sheet_x":48,"sheet_y":34},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F3A4","sheet_x":48,"sheet_y":35}}},{"unified":"1F9D1-200D-1F3A8","sheet_x":48,"sheet_y":36,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F3A8","sheet_x":48,"sheet_y":37},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F3A8","sheet_x":48,"sheet_y":38},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F3A8","sheet_x":48,"sheet_y":39},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F3A8","sheet_x":48,"sheet_y":40},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F3A8","sheet_x":48,"sheet_y":41}}},{"unified":"1F9D1-200D-1F3EB","sheet_x":48,"sheet_y":42,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F3EB","sheet_x":48,"sheet_y":43},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F3EB","sheet_x":48,"sheet_y":44},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F3EB","sheet_x":48,"sheet_y":45},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F3EB","sheet_x":48,"sheet_y":46},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F3EB","sheet_x":48,"sheet_y":47}}},{"unified":"1F9D1-200D-1F3ED","sheet_x":48,"sheet_y":48,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F3ED","sheet_x":48,"sheet_y":49},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F3ED","sheet_x":48,"sheet_y":50},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F3ED","sheet_x":48,"sheet_y":51},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F3ED","sheet_x":48,"sheet_y":52},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F3ED","sheet_x":48,"sheet_y":53}}},{"unified":"1F9D1-200D-1F4BB","sheet_x":48,"sheet_y":54,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F4BB","sheet_x":48,"sheet_y":55},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F4BB","sheet_x":48,"sheet_y":56},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F4BB","sheet_x":48,"sheet_y":57},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F4BB","sheet_x":48,"sheet_y":58},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F4BB","sheet_x":48,"sheet_y":59}}},{"unified":"1F9D1-200D-1F4BC","sheet_x":48,"sheet_y":60,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F4BC","sheet_x":48,"sheet_y":61},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F4BC","sheet_x":49,"sheet_y":0},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F4BC","sheet_x":49,"sheet_y":1},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F4BC","sheet_x":49,"sheet_y":2},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F4BC","sheet_x":49,"sheet_y":3}}},{"unified":"1F9D1-200D-1F527","sheet_x":49,"sheet_y":4,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F527","sheet_x":49,"sheet_y":5},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F527","sheet_x":49,"sheet_y":6},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F527","sheet_x":49,"sheet_y":7},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F527","sheet_x":49,"sheet_y":8},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F527","sheet_x":49,"sheet_y":9}}},{"unified":"1F9D1-200D-1F52C","sheet_x":49,"sheet_y":10,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F52C","sheet_x":49,"sheet_y":11},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F52C","sheet_x":49,"sheet_y":12},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F52C","sheet_x":49,"sheet_y":13},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F52C","sheet_x":49,"sheet_y":14},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F52C","sheet_x":49,"sheet_y":15}}},{"unified":"1F9D1-200D-1F680","sheet_x":49,"sheet_y":16,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F680","sheet_x":49,"sheet_y":17},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F680","sheet_x":49,"sheet_y":18},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F680","sheet_x":49,"sheet_y":19},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F680","sheet_x":49,"sheet_y":20},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F680","sheet_x":49,"sheet_y":21}}},{"unified":"1F9D1-200D-1F692","sheet_x":49,"sheet_y":22,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F692","sheet_x":49,"sheet_y":23},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F692","sheet_x":49,"sheet_y":24},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F692","sheet_x":49,"sheet_y":25},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F692","sheet_x":49,"sheet_y":26},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F692","sheet_x":49,"sheet_y":27}}},{"unified":"1F9D1-200D-1F91D-200D-1F9D1","sheet_x":49,"sheet_y":28,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FB","sheet_x":49,"sheet_y":29},"1F3FB-1F3FC":{"unified":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FC","sheet_x":49,"sheet_y":30},"1F3FB-1F3FD":{"unified":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FD","sheet_x":49,"sheet_y":31},"1F3FB-1F3FE":{"unified":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FE","sheet_x":49,"sheet_y":32},"1F3FB-1F3FF":{"unified":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FF","sheet_x":49,"sheet_y":33},"1F3FC-1F3FB":{"unified":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FB","sheet_x":49,"sheet_y":34},"1F3FC-1F3FC":{"unified":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FC","sheet_x":49,"sheet_y":35},"1F3FC-1F3FD":{"unified":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FD","sheet_x":49,"sheet_y":36},"1F3FC-1F3FE":{"unified":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FE","sheet_x":49,"sheet_y":37},"1F3FC-1F3FF":{"unified":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FF","sheet_x":49,"sheet_y":38},"1F3FD-1F3FB":{"unified":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FB","sheet_x":49,"sheet_y":39},"1F3FD-1F3FC":{"unified":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FC","sheet_x":49,"sheet_y":40},"1F3FD-1F3FD":{"unified":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FD","sheet_x":49,"sheet_y":41},"1F3FD-1F3FE":{"unified":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FE","sheet_x":49,"sheet_y":42},"1F3FD-1F3FF":{"unified":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FF","sheet_x":49,"sheet_y":43},"1F3FE-1F3FB":{"unified":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FB","sheet_x":49,"sheet_y":44},"1F3FE-1F3FC":{"unified":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FC","sheet_x":49,"sheet_y":45},"1F3FE-1F3FD":{"unified":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FD","sheet_x":49,"sheet_y":46},"1F3FE-1F3FE":{"unified":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FE","sheet_x":49,"sheet_y":47},"1F3FE-1F3FF":{"unified":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FF","sheet_x":49,"sheet_y":48},"1F3FF-1F3FB":{"unified":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FB","sheet_x":49,"sheet_y":49},"1F3FF-1F3FC":{"unified":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FC","sheet_x":49,"sheet_y":50},"1F3FF-1F3FD":{"unified":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FD","sheet_x":49,"sheet_y":51},"1F3FF-1F3FE":{"unified":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FE","sheet_x":49,"sheet_y":52},"1F3FF-1F3FF":{"unified":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FF","sheet_x":49,"sheet_y":53}}},{"unified":"1F9D1-200D-1F9AF-200D-27A1-FE0F","sheet_x":49,"sheet_y":54,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9AF-200D-27A1-FE0F","sheet_x":49,"sheet_y":55},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9AF-200D-27A1-FE0F","sheet_x":49,"sheet_y":56},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9AF-200D-27A1-FE0F","sheet_x":49,"sheet_y":57},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9AF-200D-27A1-FE0F","sheet_x":49,"sheet_y":58},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9AF-200D-27A1-FE0F","sheet_x":49,"sheet_y":59}}},{"unified":"1F9D1-200D-1F9AF","sheet_x":49,"sheet_y":60,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9AF","sheet_x":49,"sheet_y":61},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9AF","sheet_x":50,"sheet_y":0},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9AF","sheet_x":50,"sheet_y":1},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9AF","sheet_x":50,"sheet_y":2},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9AF","sheet_x":50,"sheet_y":3}}},{"unified":"1F9D1-200D-1F9B0","sheet_x":50,"sheet_y":4,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9B0","sheet_x":50,"sheet_y":5},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9B0","sheet_x":50,"sheet_y":6},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9B0","sheet_x":50,"sheet_y":7},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9B0","sheet_x":50,"sheet_y":8},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9B0","sheet_x":50,"sheet_y":9}}},{"unified":"1F9D1-200D-1F9B1","sheet_x":50,"sheet_y":10,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9B1","sheet_x":50,"sheet_y":11},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9B1","sheet_x":50,"sheet_y":12},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9B1","sheet_x":50,"sheet_y":13},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9B1","sheet_x":50,"sheet_y":14},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9B1","sheet_x":50,"sheet_y":15}}},{"unified":"1F9D1-200D-1F9B2","sheet_x":50,"sheet_y":16,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9B2","sheet_x":50,"sheet_y":17},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9B2","sheet_x":50,"sheet_y":18},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9B2","sheet_x":50,"sheet_y":19},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9B2","sheet_x":50,"sheet_y":20},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9B2","sheet_x":50,"sheet_y":21}}},{"unified":"1F9D1-200D-1F9B3","sheet_x":50,"sheet_y":22,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9B3","sheet_x":50,"sheet_y":23},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9B3","sheet_x":50,"sheet_y":24},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9B3","sheet_x":50,"sheet_y":25},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9B3","sheet_x":50,"sheet_y":26},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9B3","sheet_x":50,"sheet_y":27}}},{"unified":"1F9D1-200D-1F9BC-200D-27A1-FE0F","sheet_x":50,"sheet_y":28,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9BC-200D-27A1-FE0F","sheet_x":50,"sheet_y":29},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9BC-200D-27A1-FE0F","sheet_x":50,"sheet_y":30},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9BC-200D-27A1-FE0F","sheet_x":50,"sheet_y":31},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9BC-200D-27A1-FE0F","sheet_x":50,"sheet_y":32},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9BC-200D-27A1-FE0F","sheet_x":50,"sheet_y":33}}},{"unified":"1F9D1-200D-1F9BC","sheet_x":50,"sheet_y":34,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9BC","sheet_x":50,"sheet_y":35},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9BC","sheet_x":50,"sheet_y":36},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9BC","sheet_x":50,"sheet_y":37},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9BC","sheet_x":50,"sheet_y":38},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9BC","sheet_x":50,"sheet_y":39}}},{"unified":"1F9D1-200D-1F9BD-200D-27A1-FE0F","sheet_x":50,"sheet_y":40,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9BD-200D-27A1-FE0F","sheet_x":50,"sheet_y":41},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9BD-200D-27A1-FE0F","sheet_x":50,"sheet_y":42},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9BD-200D-27A1-FE0F","sheet_x":50,"sheet_y":43},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9BD-200D-27A1-FE0F","sheet_x":50,"sheet_y":44},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9BD-200D-27A1-FE0F","sheet_x":50,"sheet_y":45}}},{"unified":"1F9D1-200D-1F9BD","sheet_x":50,"sheet_y":46,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9BD","sheet_x":50,"sheet_y":47},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9BD","sheet_x":50,"sheet_y":48},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9BD","sheet_x":50,"sheet_y":49},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9BD","sheet_x":50,"sheet_y":50},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9BD","sheet_x":50,"sheet_y":51}}},{"unified":"1F9D1-200D-1F9D1-200D-1F9D2","sheet_x":50,"sheet_y":52,"skin_variations":{}},{"unified":"1F9D1-200D-1F9D1-200D-1F9D2-200D-1F9D2","sheet_x":50,"sheet_y":53,"skin_variations":{}},{"unified":"1F9D1-200D-1F9D2-200D-1F9D2","sheet_x":50,"sheet_y":54,"skin_variations":{}},{"unified":"1F9D1-200D-1F9D2","sheet_x":50,"sheet_y":55,"skin_variations":{}},{"unified":"1F9D1-200D-2695-FE0F","sheet_x":50,"sheet_y":56,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-2695-FE0F","sheet_x":50,"sheet_y":57},"1F3FC":{"unified":"1F9D1-1F3FC-200D-2695-FE0F","sheet_x":50,"sheet_y":58},"1F3FD":{"unified":"1F9D1-1F3FD-200D-2695-FE0F","sheet_x":50,"sheet_y":59},"1F3FE":{"unified":"1F9D1-1F3FE-200D-2695-FE0F","sheet_x":50,"sheet_y":60},"1F3FF":{"unified":"1F9D1-1F3FF-200D-2695-FE0F","sheet_x":50,"sheet_y":61}}},{"unified":"1F9D1-200D-2696-FE0F","sheet_x":51,"sheet_y":0,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-2696-FE0F","sheet_x":51,"sheet_y":1},"1F3FC":{"unified":"1F9D1-1F3FC-200D-2696-FE0F","sheet_x":51,"sheet_y":2},"1F3FD":{"unified":"1F9D1-1F3FD-200D-2696-FE0F","sheet_x":51,"sheet_y":3},"1F3FE":{"unified":"1F9D1-1F3FE-200D-2696-FE0F","sheet_x":51,"sheet_y":4},"1F3FF":{"unified":"1F9D1-1F3FF-200D-2696-FE0F","sheet_x":51,"sheet_y":5}}},{"unified":"1F9D1-200D-2708-FE0F","sheet_x":51,"sheet_y":6,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-2708-FE0F","sheet_x":51,"sheet_y":7},"1F3FC":{"unified":"1F9D1-1F3FC-200D-2708-FE0F","sheet_x":51,"sheet_y":8},"1F3FD":{"unified":"1F9D1-1F3FD-200D-2708-FE0F","sheet_x":51,"sheet_y":9},"1F3FE":{"unified":"1F9D1-1F3FE-200D-2708-FE0F","sheet_x":51,"sheet_y":10},"1F3FF":{"unified":"1F9D1-1F3FF-200D-2708-FE0F","sheet_x":51,"sheet_y":11}}},{"unified":"1F9D1","sheet_x":51,"sheet_y":12,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB","sheet_x":51,"sheet_y":13},"1F3FC":{"unified":"1F9D1-1F3FC","sheet_x":51,"sheet_y":14},"1F3FD":{"unified":"1F9D1-1F3FD","sheet_x":51,"sheet_y":15},"1F3FE":{"unified":"1F9D1-1F3FE","sheet_x":51,"sheet_y":16},"1F3FF":{"unified":"1F9D1-1F3FF","sheet_x":51,"sheet_y":17}}},{"unified":"1F9D2","sheet_x":51,"sheet_y":18,"skin_variations":{"1F3FB":{"unified":"1F9D2-1F3FB","sheet_x":51,"sheet_y":19},"1F3FC":{"unified":"1F9D2-1F3FC","sheet_x":51,"sheet_y":20},"1F3FD":{"unified":"1F9D2-1F3FD","sheet_x":51,"sheet_y":21},"1F3FE":{"unified":"1F9D2-1F3FE","sheet_x":51,"sheet_y":22},"1F3FF":{"unified":"1F9D2-1F3FF","sheet_x":51,"sheet_y":23}}},{"unified":"1F9D3","sheet_x":51,"sheet_y":24,"skin_variations":{"1F3FB":{"unified":"1F9D3-1F3FB","sheet_x":51,"sheet_y":25},"1F3FC":{"unified":"1F9D3-1F3FC","sheet_x":51,"sheet_y":26},"1F3FD":{"unified":"1F9D3-1F3FD","sheet_x":51,"sheet_y":27},"1F3FE":{"unified":"1F9D3-1F3FE","sheet_x":51,"sheet_y":28},"1F3FF":{"unified":"1F9D3-1F3FF","sheet_x":51,"sheet_y":29}}},{"unified":"1F9D4-200D-2640-FE0F","sheet_x":51,"sheet_y":30,"skin_variations":{"1F3FB":{"unified":"1F9D4-1F3FB-200D-2640-FE0F","sheet_x":51,"sheet_y":31},"1F3FC":{"unified":"1F9D4-1F3FC-200D-2640-FE0F","sheet_x":51,"sheet_y":32},"1F3FD":{"unified":"1F9D4-1F3FD-200D-2640-FE0F","sheet_x":51,"sheet_y":33},"1F3FE":{"unified":"1F9D4-1F3FE-200D-2640-FE0F","sheet_x":51,"sheet_y":34},"1F3FF":{"unified":"1F9D4-1F3FF-200D-2640-FE0F","sheet_x":51,"sheet_y":35}}},{"unified":"1F9D4-200D-2642-FE0F","sheet_x":51,"sheet_y":36,"skin_variations":{"1F3FB":{"unified":"1F9D4-1F3FB-200D-2642-FE0F","sheet_x":51,"sheet_y":37},"1F3FC":{"unified":"1F9D4-1F3FC-200D-2642-FE0F","sheet_x":51,"sheet_y":38},"1F3FD":{"unified":"1F9D4-1F3FD-200D-2642-FE0F","sheet_x":51,"sheet_y":39},"1F3FE":{"unified":"1F9D4-1F3FE-200D-2642-FE0F","sheet_x":51,"sheet_y":40},"1F3FF":{"unified":"1F9D4-1F3FF-200D-2642-FE0F","sheet_x":51,"sheet_y":41}}},{"unified":"1F9D4","sheet_x":51,"sheet_y":42,"skin_variations":{"1F3FB":{"unified":"1F9D4-1F3FB","sheet_x":51,"sheet_y":43},"1F3FC":{"unified":"1F9D4-1F3FC","sheet_x":51,"sheet_y":44},"1F3FD":{"unified":"1F9D4-1F3FD","sheet_x":51,"sheet_y":45},"1F3FE":{"unified":"1F9D4-1F3FE","sheet_x":51,"sheet_y":46},"1F3FF":{"unified":"1F9D4-1F3FF","sheet_x":51,"sheet_y":47}}},{"unified":"1F9D5","sheet_x":51,"sheet_y":48,"skin_variations":{"1F3FB":{"unified":"1F9D5-1F3FB","sheet_x":51,"sheet_y":49},"1F3FC":{"unified":"1F9D5-1F3FC","sheet_x":51,"sheet_y":50},"1F3FD":{"unified":"1F9D5-1F3FD","sheet_x":51,"sheet_y":51},"1F3FE":{"unified":"1F9D5-1F3FE","sheet_x":51,"sheet_y":52},"1F3FF":{"unified":"1F9D5-1F3FF","sheet_x":51,"sheet_y":53}}},{"unified":"1F9D6-200D-2640-FE0F","sheet_x":51,"sheet_y":54,"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB-200D-2640-FE0F","sheet_x":51,"sheet_y":55},"1F3FC":{"unified":"1F9D6-1F3FC-200D-2640-FE0F","sheet_x":51,"sheet_y":56},"1F3FD":{"unified":"1F9D6-1F3FD-200D-2640-FE0F","sheet_x":51,"sheet_y":57},"1F3FE":{"unified":"1F9D6-1F3FE-200D-2640-FE0F","sheet_x":51,"sheet_y":58},"1F3FF":{"unified":"1F9D6-1F3FF-200D-2640-FE0F","sheet_x":51,"sheet_y":59}}},{"unified":"1F9D6-200D-2642-FE0F","sheet_x":51,"sheet_y":60,"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB-200D-2642-FE0F","sheet_x":51,"sheet_y":61},"1F3FC":{"unified":"1F9D6-1F3FC-200D-2642-FE0F","sheet_x":52,"sheet_y":0},"1F3FD":{"unified":"1F9D6-1F3FD-200D-2642-FE0F","sheet_x":52,"sheet_y":1},"1F3FE":{"unified":"1F9D6-1F3FE-200D-2642-FE0F","sheet_x":52,"sheet_y":2},"1F3FF":{"unified":"1F9D6-1F3FF-200D-2642-FE0F","sheet_x":52,"sheet_y":3}}},{"unified":"1F9D6","sheet_x":52,"sheet_y":4,"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB","sheet_x":52,"sheet_y":5},"1F3FC":{"unified":"1F9D6-1F3FC","sheet_x":52,"sheet_y":6},"1F3FD":{"unified":"1F9D6-1F3FD","sheet_x":52,"sheet_y":7},"1F3FE":{"unified":"1F9D6-1F3FE","sheet_x":52,"sheet_y":8},"1F3FF":{"unified":"1F9D6-1F3FF","sheet_x":52,"sheet_y":9}}},{"unified":"1F9D7-200D-2640-FE0F","sheet_x":52,"sheet_y":10,"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB-200D-2640-FE0F","sheet_x":52,"sheet_y":11},"1F3FC":{"unified":"1F9D7-1F3FC-200D-2640-FE0F","sheet_x":52,"sheet_y":12},"1F3FD":{"unified":"1F9D7-1F3FD-200D-2640-FE0F","sheet_x":52,"sheet_y":13},"1F3FE":{"unified":"1F9D7-1F3FE-200D-2640-FE0F","sheet_x":52,"sheet_y":14},"1F3FF":{"unified":"1F9D7-1F3FF-200D-2640-FE0F","sheet_x":52,"sheet_y":15}}},{"unified":"1F9D7-200D-2642-FE0F","sheet_x":52,"sheet_y":16,"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB-200D-2642-FE0F","sheet_x":52,"sheet_y":17},"1F3FC":{"unified":"1F9D7-1F3FC-200D-2642-FE0F","sheet_x":52,"sheet_y":18},"1F3FD":{"unified":"1F9D7-1F3FD-200D-2642-FE0F","sheet_x":52,"sheet_y":19},"1F3FE":{"unified":"1F9D7-1F3FE-200D-2642-FE0F","sheet_x":52,"sheet_y":20},"1F3FF":{"unified":"1F9D7-1F3FF-200D-2642-FE0F","sheet_x":52,"sheet_y":21}}},{"unified":"1F9D7","sheet_x":52,"sheet_y":22,"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB","sheet_x":52,"sheet_y":23},"1F3FC":{"unified":"1F9D7-1F3FC","sheet_x":52,"sheet_y":24},"1F3FD":{"unified":"1F9D7-1F3FD","sheet_x":52,"sheet_y":25},"1F3FE":{"unified":"1F9D7-1F3FE","sheet_x":52,"sheet_y":26},"1F3FF":{"unified":"1F9D7-1F3FF","sheet_x":52,"sheet_y":27}}},{"unified":"1F9D8-200D-2640-FE0F","sheet_x":52,"sheet_y":28,"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB-200D-2640-FE0F","sheet_x":52,"sheet_y":29},"1F3FC":{"unified":"1F9D8-1F3FC-200D-2640-FE0F","sheet_x":52,"sheet_y":30},"1F3FD":{"unified":"1F9D8-1F3FD-200D-2640-FE0F","sheet_x":52,"sheet_y":31},"1F3FE":{"unified":"1F9D8-1F3FE-200D-2640-FE0F","sheet_x":52,"sheet_y":32},"1F3FF":{"unified":"1F9D8-1F3FF-200D-2640-FE0F","sheet_x":52,"sheet_y":33}}},{"unified":"1F9D8-200D-2642-FE0F","sheet_x":52,"sheet_y":34,"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB-200D-2642-FE0F","sheet_x":52,"sheet_y":35},"1F3FC":{"unified":"1F9D8-1F3FC-200D-2642-FE0F","sheet_x":52,"sheet_y":36},"1F3FD":{"unified":"1F9D8-1F3FD-200D-2642-FE0F","sheet_x":52,"sheet_y":37},"1F3FE":{"unified":"1F9D8-1F3FE-200D-2642-FE0F","sheet_x":52,"sheet_y":38},"1F3FF":{"unified":"1F9D8-1F3FF-200D-2642-FE0F","sheet_x":52,"sheet_y":39}}},{"unified":"1F9D8","sheet_x":52,"sheet_y":40,"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB","sheet_x":52,"sheet_y":41},"1F3FC":{"unified":"1F9D8-1F3FC","sheet_x":52,"sheet_y":42},"1F3FD":{"unified":"1F9D8-1F3FD","sheet_x":52,"sheet_y":43},"1F3FE":{"unified":"1F9D8-1F3FE","sheet_x":52,"sheet_y":44},"1F3FF":{"unified":"1F9D8-1F3FF","sheet_x":52,"sheet_y":45}}},{"unified":"1F9D9-200D-2640-FE0F","sheet_x":52,"sheet_y":46,"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB-200D-2640-FE0F","sheet_x":52,"sheet_y":47},"1F3FC":{"unified":"1F9D9-1F3FC-200D-2640-FE0F","sheet_x":52,"sheet_y":48},"1F3FD":{"unified":"1F9D9-1F3FD-200D-2640-FE0F","sheet_x":52,"sheet_y":49},"1F3FE":{"unified":"1F9D9-1F3FE-200D-2640-FE0F","sheet_x":52,"sheet_y":50},"1F3FF":{"unified":"1F9D9-1F3FF-200D-2640-FE0F","sheet_x":52,"sheet_y":51}}},{"unified":"1F9D9-200D-2642-FE0F","sheet_x":52,"sheet_y":52,"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB-200D-2642-FE0F","sheet_x":52,"sheet_y":53},"1F3FC":{"unified":"1F9D9-1F3FC-200D-2642-FE0F","sheet_x":52,"sheet_y":54},"1F3FD":{"unified":"1F9D9-1F3FD-200D-2642-FE0F","sheet_x":52,"sheet_y":55},"1F3FE":{"unified":"1F9D9-1F3FE-200D-2642-FE0F","sheet_x":52,"sheet_y":56},"1F3FF":{"unified":"1F9D9-1F3FF-200D-2642-FE0F","sheet_x":52,"sheet_y":57}}},{"unified":"1F9D9","sheet_x":52,"sheet_y":58,"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB","sheet_x":52,"sheet_y":59},"1F3FC":{"unified":"1F9D9-1F3FC","sheet_x":52,"sheet_y":60},"1F3FD":{"unified":"1F9D9-1F3FD","sheet_x":52,"sheet_y":61},"1F3FE":{"unified":"1F9D9-1F3FE","sheet_x":53,"sheet_y":0},"1F3FF":{"unified":"1F9D9-1F3FF","sheet_x":53,"sheet_y":1}}},{"unified":"1F9DA-200D-2640-FE0F","sheet_x":53,"sheet_y":2,"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB-200D-2640-FE0F","sheet_x":53,"sheet_y":3},"1F3FC":{"unified":"1F9DA-1F3FC-200D-2640-FE0F","sheet_x":53,"sheet_y":4},"1F3FD":{"unified":"1F9DA-1F3FD-200D-2640-FE0F","sheet_x":53,"sheet_y":5},"1F3FE":{"unified":"1F9DA-1F3FE-200D-2640-FE0F","sheet_x":53,"sheet_y":6},"1F3FF":{"unified":"1F9DA-1F3FF-200D-2640-FE0F","sheet_x":53,"sheet_y":7}}},{"unified":"1F9DA-200D-2642-FE0F","sheet_x":53,"sheet_y":8,"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB-200D-2642-FE0F","sheet_x":53,"sheet_y":9},"1F3FC":{"unified":"1F9DA-1F3FC-200D-2642-FE0F","sheet_x":53,"sheet_y":10},"1F3FD":{"unified":"1F9DA-1F3FD-200D-2642-FE0F","sheet_x":53,"sheet_y":11},"1F3FE":{"unified":"1F9DA-1F3FE-200D-2642-FE0F","sheet_x":53,"sheet_y":12},"1F3FF":{"unified":"1F9DA-1F3FF-200D-2642-FE0F","sheet_x":53,"sheet_y":13}}},{"unified":"1F9DA","sheet_x":53,"sheet_y":14,"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB","sheet_x":53,"sheet_y":15},"1F3FC":{"unified":"1F9DA-1F3FC","sheet_x":53,"sheet_y":16},"1F3FD":{"unified":"1F9DA-1F3FD","sheet_x":53,"sheet_y":17},"1F3FE":{"unified":"1F9DA-1F3FE","sheet_x":53,"sheet_y":18},"1F3FF":{"unified":"1F9DA-1F3FF","sheet_x":53,"sheet_y":19}}},{"unified":"1F9DB-200D-2640-FE0F","sheet_x":53,"sheet_y":20,"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB-200D-2640-FE0F","sheet_x":53,"sheet_y":21},"1F3FC":{"unified":"1F9DB-1F3FC-200D-2640-FE0F","sheet_x":53,"sheet_y":22},"1F3FD":{"unified":"1F9DB-1F3FD-200D-2640-FE0F","sheet_x":53,"sheet_y":23},"1F3FE":{"unified":"1F9DB-1F3FE-200D-2640-FE0F","sheet_x":53,"sheet_y":24},"1F3FF":{"unified":"1F9DB-1F3FF-200D-2640-FE0F","sheet_x":53,"sheet_y":25}}},{"unified":"1F9DB-200D-2642-FE0F","sheet_x":53,"sheet_y":26,"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB-200D-2642-FE0F","sheet_x":53,"sheet_y":27},"1F3FC":{"unified":"1F9DB-1F3FC-200D-2642-FE0F","sheet_x":53,"sheet_y":28},"1F3FD":{"unified":"1F9DB-1F3FD-200D-2642-FE0F","sheet_x":53,"sheet_y":29},"1F3FE":{"unified":"1F9DB-1F3FE-200D-2642-FE0F","sheet_x":53,"sheet_y":30},"1F3FF":{"unified":"1F9DB-1F3FF-200D-2642-FE0F","sheet_x":53,"sheet_y":31}}},{"unified":"1F9DB","sheet_x":53,"sheet_y":32,"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB","sheet_x":53,"sheet_y":33},"1F3FC":{"unified":"1F9DB-1F3FC","sheet_x":53,"sheet_y":34},"1F3FD":{"unified":"1F9DB-1F3FD","sheet_x":53,"sheet_y":35},"1F3FE":{"unified":"1F9DB-1F3FE","sheet_x":53,"sheet_y":36},"1F3FF":{"unified":"1F9DB-1F3FF","sheet_x":53,"sheet_y":37}}},{"unified":"1F9DC-200D-2640-FE0F","sheet_x":53,"sheet_y":38,"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB-200D-2640-FE0F","sheet_x":53,"sheet_y":39},"1F3FC":{"unified":"1F9DC-1F3FC-200D-2640-FE0F","sheet_x":53,"sheet_y":40},"1F3FD":{"unified":"1F9DC-1F3FD-200D-2640-FE0F","sheet_x":53,"sheet_y":41},"1F3FE":{"unified":"1F9DC-1F3FE-200D-2640-FE0F","sheet_x":53,"sheet_y":42},"1F3FF":{"unified":"1F9DC-1F3FF-200D-2640-FE0F","sheet_x":53,"sheet_y":43}}},{"unified":"1F9DC-200D-2642-FE0F","sheet_x":53,"sheet_y":44,"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB-200D-2642-FE0F","sheet_x":53,"sheet_y":45},"1F3FC":{"unified":"1F9DC-1F3FC-200D-2642-FE0F","sheet_x":53,"sheet_y":46},"1F3FD":{"unified":"1F9DC-1F3FD-200D-2642-FE0F","sheet_x":53,"sheet_y":47},"1F3FE":{"unified":"1F9DC-1F3FE-200D-2642-FE0F","sheet_x":53,"sheet_y":48},"1F3FF":{"unified":"1F9DC-1F3FF-200D-2642-FE0F","sheet_x":53,"sheet_y":49}}},{"unified":"1F9DC","sheet_x":53,"sheet_y":50,"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB","sheet_x":53,"sheet_y":51},"1F3FC":{"unified":"1F9DC-1F3FC","sheet_x":53,"sheet_y":52},"1F3FD":{"unified":"1F9DC-1F3FD","sheet_x":53,"sheet_y":53},"1F3FE":{"unified":"1F9DC-1F3FE","sheet_x":53,"sheet_y":54},"1F3FF":{"unified":"1F9DC-1F3FF","sheet_x":53,"sheet_y":55}}},{"unified":"1F9DD-200D-2640-FE0F","sheet_x":53,"sheet_y":56,"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB-200D-2640-FE0F","sheet_x":53,"sheet_y":57},"1F3FC":{"unified":"1F9DD-1F3FC-200D-2640-FE0F","sheet_x":53,"sheet_y":58},"1F3FD":{"unified":"1F9DD-1F3FD-200D-2640-FE0F","sheet_x":53,"sheet_y":59},"1F3FE":{"unified":"1F9DD-1F3FE-200D-2640-FE0F","sheet_x":53,"sheet_y":60},"1F3FF":{"unified":"1F9DD-1F3FF-200D-2640-FE0F","sheet_x":53,"sheet_y":61}}},{"unified":"1F9DD-200D-2642-FE0F","sheet_x":54,"sheet_y":0,"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB-200D-2642-FE0F","sheet_x":54,"sheet_y":1},"1F3FC":{"unified":"1F9DD-1F3FC-200D-2642-FE0F","sheet_x":54,"sheet_y":2},"1F3FD":{"unified":"1F9DD-1F3FD-200D-2642-FE0F","sheet_x":54,"sheet_y":3},"1F3FE":{"unified":"1F9DD-1F3FE-200D-2642-FE0F","sheet_x":54,"sheet_y":4},"1F3FF":{"unified":"1F9DD-1F3FF-200D-2642-FE0F","sheet_x":54,"sheet_y":5}}},{"unified":"1F9DD","sheet_x":54,"sheet_y":6,"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB","sheet_x":54,"sheet_y":7},"1F3FC":{"unified":"1F9DD-1F3FC","sheet_x":54,"sheet_y":8},"1F3FD":{"unified":"1F9DD-1F3FD","sheet_x":54,"sheet_y":9},"1F3FE":{"unified":"1F9DD-1F3FE","sheet_x":54,"sheet_y":10},"1F3FF":{"unified":"1F9DD-1F3FF","sheet_x":54,"sheet_y":11}}},{"unified":"1F9DE-200D-2640-FE0F","sheet_x":54,"sheet_y":12,"skin_variations":{}},{"unified":"1F9DE-200D-2642-FE0F","sheet_x":54,"sheet_y":13,"skin_variations":{}},{"unified":"1F9DE","sheet_x":54,"sheet_y":14,"skin_variations":{}},{"unified":"1F9DF-200D-2640-FE0F","sheet_x":54,"sheet_y":15,"skin_variations":{}},{"unified":"1F9DF-200D-2642-FE0F","sheet_x":54,"sheet_y":16,"skin_variations":{}},{"unified":"1F9DF","sheet_x":54,"sheet_y":17,"skin_variations":{}},{"unified":"1F9E0","sheet_x":54,"sheet_y":18,"skin_variations":{}},{"unified":"1F9E1","sheet_x":54,"sheet_y":19,"skin_variations":{}},{"unified":"1F9E2","sheet_x":54,"sheet_y":20,"skin_variations":{}},{"unified":"1F9E3","sheet_x":54,"sheet_y":21,"skin_variations":{}},{"unified":"1F9E4","sheet_x":54,"sheet_y":22,"skin_variations":{}},{"unified":"1F9E5","sheet_x":54,"sheet_y":23,"skin_variations":{}},{"unified":"1F9E6","sheet_x":54,"sheet_y":24,"skin_variations":{}},{"unified":"1F9E7","sheet_x":54,"sheet_y":25,"skin_variations":{}},{"unified":"1F9E8","sheet_x":54,"sheet_y":26,"skin_variations":{}},{"unified":"1F9E9","sheet_x":54,"sheet_y":27,"skin_variations":{}},{"unified":"1F9EA","sheet_x":54,"sheet_y":28,"skin_variations":{}},{"unified":"1F9EB","sheet_x":54,"sheet_y":29,"skin_variations":{}},{"unified":"1F9EC","sheet_x":54,"sheet_y":30,"skin_variations":{}},{"unified":"1F9ED","sheet_x":54,"sheet_y":31,"skin_variations":{}},{"unified":"1F9EE","sheet_x":54,"sheet_y":32,"skin_variations":{}},{"unified":"1F9EF","sheet_x":54,"sheet_y":33,"skin_variations":{}},{"unified":"1F9F0","sheet_x":54,"sheet_y":34,"skin_variations":{}},{"unified":"1F9F1","sheet_x":54,"sheet_y":35,"skin_variations":{}},{"unified":"1F9F2","sheet_x":54,"sheet_y":36,"skin_variations":{}},{"unified":"1F9F3","sheet_x":54,"sheet_y":37,"skin_variations":{}},{"unified":"1F9F4","sheet_x":54,"sheet_y":38,"skin_variations":{}},{"unified":"1F9F5","sheet_x":54,"sheet_y":39,"skin_variations":{}},{"unified":"1F9F6","sheet_x":54,"sheet_y":40,"skin_variations":{}},{"unified":"1F9F7","sheet_x":54,"sheet_y":41,"skin_variations":{}},{"unified":"1F9F8","sheet_x":54,"sheet_y":42,"skin_variations":{}},{"unified":"1F9F9","sheet_x":54,"sheet_y":43,"skin_variations":{}},{"unified":"1F9FA","sheet_x":54,"sheet_y":44,"skin_variations":{}},{"unified":"1F9FB","sheet_x":54,"sheet_y":45,"skin_variations":{}},{"unified":"1F9FC","sheet_x":54,"sheet_y":46,"skin_variations":{}},{"unified":"1F9FD","sheet_x":54,"sheet_y":47,"skin_variations":{}},{"unified":"1F9FE","sheet_x":54,"sheet_y":48,"skin_variations":{}},{"unified":"1F9FF","sheet_x":54,"sheet_y":49,"skin_variations":{}},{"unified":"1FA70","sheet_x":54,"sheet_y":50,"skin_variations":{}},{"unified":"1FA71","sheet_x":54,"sheet_y":51,"skin_variations":{}},{"unified":"1FA72","sheet_x":54,"sheet_y":52,"skin_variations":{}},{"unified":"1FA73","sheet_x":54,"sheet_y":53,"skin_variations":{}},{"unified":"1FA74","sheet_x":54,"sheet_y":54,"skin_variations":{}},{"unified":"1FA75","sheet_x":54,"sheet_y":55,"skin_variations":{}},{"unified":"1FA76","sheet_x":54,"sheet_y":56,"skin_variations":{}},{"unified":"1FA77","sheet_x":54,"sheet_y":57,"skin_variations":{}},{"unified":"1FA78","sheet_x":54,"sheet_y":58,"skin_variations":{}},{"unified":"1FA79","sheet_x":54,"sheet_y":59,"skin_variations":{}},{"unified":"1FA7A","sheet_x":54,"sheet_y":60,"skin_variations":{}},{"unified":"1FA7B","sheet_x":54,"sheet_y":61,"skin_variations":{}},{"unified":"1FA7C","sheet_x":55,"sheet_y":0,"skin_variations":{}},{"unified":"1FA80","sheet_x":55,"sheet_y":1,"skin_variations":{}},{"unified":"1FA81","sheet_x":55,"sheet_y":2,"skin_variations":{}},{"unified":"1FA82","sheet_x":55,"sheet_y":3,"skin_variations":{}},{"unified":"1FA83","sheet_x":55,"sheet_y":4,"skin_variations":{}},{"unified":"1FA84","sheet_x":55,"sheet_y":5,"skin_variations":{}},{"unified":"1FA85","sheet_x":55,"sheet_y":6,"skin_variations":{}},{"unified":"1FA86","sheet_x":55,"sheet_y":7,"skin_variations":{}},{"unified":"1FA87","sheet_x":55,"sheet_y":8,"skin_variations":{}},{"unified":"1FA88","sheet_x":55,"sheet_y":9,"skin_variations":{}},{"unified":"1FA90","sheet_x":55,"sheet_y":10,"skin_variations":{}},{"unified":"1FA91","sheet_x":55,"sheet_y":11,"skin_variations":{}},{"unified":"1FA92","sheet_x":55,"sheet_y":12,"skin_variations":{}},{"unified":"1FA93","sheet_x":55,"sheet_y":13,"skin_variations":{}},{"unified":"1FA94","sheet_x":55,"sheet_y":14,"skin_variations":{}},{"unified":"1FA95","sheet_x":55,"sheet_y":15,"skin_variations":{}},{"unified":"1FA96","sheet_x":55,"sheet_y":16,"skin_variations":{}},{"unified":"1FA97","sheet_x":55,"sheet_y":17,"skin_variations":{}},{"unified":"1FA98","sheet_x":55,"sheet_y":18,"skin_variations":{}},{"unified":"1FA99","sheet_x":55,"sheet_y":19,"skin_variations":{}},{"unified":"1FA9A","sheet_x":55,"sheet_y":20,"skin_variations":{}},{"unified":"1FA9B","sheet_x":55,"sheet_y":21,"skin_variations":{}},{"unified":"1FA9C","sheet_x":55,"sheet_y":22,"skin_variations":{}},{"unified":"1FA9D","sheet_x":55,"sheet_y":23,"skin_variations":{}},{"unified":"1FA9E","sheet_x":55,"sheet_y":24,"skin_variations":{}},{"unified":"1FA9F","sheet_x":55,"sheet_y":25,"skin_variations":{}},{"unified":"1FAA0","sheet_x":55,"sheet_y":26,"skin_variations":{}},{"unified":"1FAA1","sheet_x":55,"sheet_y":27,"skin_variations":{}},{"unified":"1FAA2","sheet_x":55,"sheet_y":28,"skin_variations":{}},{"unified":"1FAA3","sheet_x":55,"sheet_y":29,"skin_variations":{}},{"unified":"1FAA4","sheet_x":55,"sheet_y":30,"skin_variations":{}},{"unified":"1FAA5","sheet_x":55,"sheet_y":31,"skin_variations":{}},{"unified":"1FAA6","sheet_x":55,"sheet_y":32,"skin_variations":{}},{"unified":"1FAA7","sheet_x":55,"sheet_y":33,"skin_variations":{}},{"unified":"1FAA8","sheet_x":55,"sheet_y":34,"skin_variations":{}},{"unified":"1FAA9","sheet_x":55,"sheet_y":35,"skin_variations":{}},{"unified":"1FAAA","sheet_x":55,"sheet_y":36,"skin_variations":{}},{"unified":"1FAAB","sheet_x":55,"sheet_y":37,"skin_variations":{}},{"unified":"1FAAC","sheet_x":55,"sheet_y":38,"skin_variations":{}},{"unified":"1FAAD","sheet_x":55,"sheet_y":39,"skin_variations":{}},{"unified":"1FAAE","sheet_x":55,"sheet_y":40,"skin_variations":{}},{"unified":"1FAAF","sheet_x":55,"sheet_y":41,"skin_variations":{}},{"unified":"1FAB0","sheet_x":55,"sheet_y":42,"skin_variations":{}},{"unified":"1FAB1","sheet_x":55,"sheet_y":43,"skin_variations":{}},{"unified":"1FAB2","sheet_x":55,"sheet_y":44,"skin_variations":{}},{"unified":"1FAB3","sheet_x":55,"sheet_y":45,"skin_variations":{}},{"unified":"1FAB4","sheet_x":55,"sheet_y":46,"skin_variations":{}},{"unified":"1FAB5","sheet_x":55,"sheet_y":47,"skin_variations":{}},{"unified":"1FAB6","sheet_x":55,"sheet_y":48,"skin_variations":{}},{"unified":"1FAB7","sheet_x":55,"sheet_y":49,"skin_variations":{}},{"unified":"1FAB8","sheet_x":55,"sheet_y":50,"skin_variations":{}},{"unified":"1FAB9","sheet_x":55,"sheet_y":51,"skin_variations":{}},{"unified":"1FABA","sheet_x":55,"sheet_y":52,"skin_variations":{}},{"unified":"1FABB","sheet_x":55,"sheet_y":53,"skin_variations":{}},{"unified":"1FABC","sheet_x":55,"sheet_y":54,"skin_variations":{}},{"unified":"1FABD","sheet_x":55,"sheet_y":55,"skin_variations":{}},{"unified":"1FABF","sheet_x":55,"sheet_y":56,"skin_variations":{}},{"unified":"1FAC0","sheet_x":55,"sheet_y":57,"skin_variations":{}},{"unified":"1FAC1","sheet_x":55,"sheet_y":58,"skin_variations":{}},{"unified":"1FAC2","sheet_x":55,"sheet_y":59,"skin_variations":{}},{"unified":"1FAC3","sheet_x":55,"sheet_y":60,"skin_variations":{"1F3FB":{"unified":"1FAC3-1F3FB","sheet_x":55,"sheet_y":61},"1F3FC":{"unified":"1FAC3-1F3FC","sheet_x":56,"sheet_y":0},"1F3FD":{"unified":"1FAC3-1F3FD","sheet_x":56,"sheet_y":1},"1F3FE":{"unified":"1FAC3-1F3FE","sheet_x":56,"sheet_y":2},"1F3FF":{"unified":"1FAC3-1F3FF","sheet_x":56,"sheet_y":3}}},{"unified":"1FAC4","sheet_x":56,"sheet_y":4,"skin_variations":{"1F3FB":{"unified":"1FAC4-1F3FB","sheet_x":56,"sheet_y":5},"1F3FC":{"unified":"1FAC4-1F3FC","sheet_x":56,"sheet_y":6},"1F3FD":{"unified":"1FAC4-1F3FD","sheet_x":56,"sheet_y":7},"1F3FE":{"unified":"1FAC4-1F3FE","sheet_x":56,"sheet_y":8},"1F3FF":{"unified":"1FAC4-1F3FF","sheet_x":56,"sheet_y":9}}},{"unified":"1FAC5","sheet_x":56,"sheet_y":10,"skin_variations":{"1F3FB":{"unified":"1FAC5-1F3FB","sheet_x":56,"sheet_y":11},"1F3FC":{"unified":"1FAC5-1F3FC","sheet_x":56,"sheet_y":12},"1F3FD":{"unified":"1FAC5-1F3FD","sheet_x":56,"sheet_y":13},"1F3FE":{"unified":"1FAC5-1F3FE","sheet_x":56,"sheet_y":14},"1F3FF":{"unified":"1FAC5-1F3FF","sheet_x":56,"sheet_y":15}}},{"unified":"1FACE","sheet_x":56,"sheet_y":16,"skin_variations":{}},{"unified":"1FACF","sheet_x":56,"sheet_y":17,"skin_variations":{}},{"unified":"1FAD0","sheet_x":56,"sheet_y":18,"skin_variations":{}},{"unified":"1FAD1","sheet_x":56,"sheet_y":19,"skin_variations":{}},{"unified":"1FAD2","sheet_x":56,"sheet_y":20,"skin_variations":{}},{"unified":"1FAD3","sheet_x":56,"sheet_y":21,"skin_variations":{}},{"unified":"1FAD4","sheet_x":56,"sheet_y":22,"skin_variations":{}},{"unified":"1FAD5","sheet_x":56,"sheet_y":23,"skin_variations":{}},{"unified":"1FAD6","sheet_x":56,"sheet_y":24,"skin_variations":{}},{"unified":"1FAD7","sheet_x":56,"sheet_y":25,"skin_variations":{}},{"unified":"1FAD8","sheet_x":56,"sheet_y":26,"skin_variations":{}},{"unified":"1FAD9","sheet_x":56,"sheet_y":27,"skin_variations":{}},{"unified":"1FADA","sheet_x":56,"sheet_y":28,"skin_variations":{}},{"unified":"1FADB","sheet_x":56,"sheet_y":29,"skin_variations":{}},{"unified":"1FAE0","sheet_x":56,"sheet_y":30,"skin_variations":{}},{"unified":"1FAE1","sheet_x":56,"sheet_y":31,"skin_variations":{}},{"unified":"1FAE2","sheet_x":56,"sheet_y":32,"skin_variations":{}},{"unified":"1FAE3","sheet_x":56,"sheet_y":33,"skin_variations":{}},{"unified":"1FAE4","sheet_x":56,"sheet_y":34,"skin_variations":{}},{"unified":"1FAE5","sheet_x":56,"sheet_y":35,"skin_variations":{}},{"unified":"1FAE6","sheet_x":56,"sheet_y":36,"skin_variations":{}},{"unified":"1FAE7","sheet_x":56,"sheet_y":37,"skin_variations":{}},{"unified":"1FAE8","sheet_x":56,"sheet_y":38,"skin_variations":{}},{"unified":"1FAF0","sheet_x":56,"sheet_y":39,"skin_variations":{"1F3FB":{"unified":"1FAF0-1F3FB","sheet_x":56,"sheet_y":40},"1F3FC":{"unified":"1FAF0-1F3FC","sheet_x":56,"sheet_y":41},"1F3FD":{"unified":"1FAF0-1F3FD","sheet_x":56,"sheet_y":42},"1F3FE":{"unified":"1FAF0-1F3FE","sheet_x":56,"sheet_y":43},"1F3FF":{"unified":"1FAF0-1F3FF","sheet_x":56,"sheet_y":44}}},{"unified":"1FAF1","sheet_x":56,"sheet_y":45,"skin_variations":{"1F3FB":{"unified":"1FAF1-1F3FB","sheet_x":56,"sheet_y":46},"1F3FC":{"unified":"1FAF1-1F3FC","sheet_x":56,"sheet_y":47},"1F3FD":{"unified":"1FAF1-1F3FD","sheet_x":56,"sheet_y":48},"1F3FE":{"unified":"1FAF1-1F3FE","sheet_x":56,"sheet_y":49},"1F3FF":{"unified":"1FAF1-1F3FF","sheet_x":56,"sheet_y":50}}},{"unified":"1FAF2","sheet_x":56,"sheet_y":51,"skin_variations":{"1F3FB":{"unified":"1FAF2-1F3FB","sheet_x":56,"sheet_y":52},"1F3FC":{"unified":"1FAF2-1F3FC","sheet_x":56,"sheet_y":53},"1F3FD":{"unified":"1FAF2-1F3FD","sheet_x":56,"sheet_y":54},"1F3FE":{"unified":"1FAF2-1F3FE","sheet_x":56,"sheet_y":55},"1F3FF":{"unified":"1FAF2-1F3FF","sheet_x":56,"sheet_y":56}}},{"unified":"1FAF3","sheet_x":56,"sheet_y":57,"skin_variations":{"1F3FB":{"unified":"1FAF3-1F3FB","sheet_x":56,"sheet_y":58},"1F3FC":{"unified":"1FAF3-1F3FC","sheet_x":56,"sheet_y":59},"1F3FD":{"unified":"1FAF3-1F3FD","sheet_x":56,"sheet_y":60},"1F3FE":{"unified":"1FAF3-1F3FE","sheet_x":56,"sheet_y":61},"1F3FF":{"unified":"1FAF3-1F3FF","sheet_x":57,"sheet_y":0}}},{"unified":"1FAF4","sheet_x":57,"sheet_y":1,"skin_variations":{"1F3FB":{"unified":"1FAF4-1F3FB","sheet_x":57,"sheet_y":2},"1F3FC":{"unified":"1FAF4-1F3FC","sheet_x":57,"sheet_y":3},"1F3FD":{"unified":"1FAF4-1F3FD","sheet_x":57,"sheet_y":4},"1F3FE":{"unified":"1FAF4-1F3FE","sheet_x":57,"sheet_y":5},"1F3FF":{"unified":"1FAF4-1F3FF","sheet_x":57,"sheet_y":6}}},{"unified":"1FAF5","sheet_x":57,"sheet_y":7,"skin_variations":{"1F3FB":{"unified":"1FAF5-1F3FB","sheet_x":57,"sheet_y":8},"1F3FC":{"unified":"1FAF5-1F3FC","sheet_x":57,"sheet_y":9},"1F3FD":{"unified":"1FAF5-1F3FD","sheet_x":57,"sheet_y":10},"1F3FE":{"unified":"1FAF5-1F3FE","sheet_x":57,"sheet_y":11},"1F3FF":{"unified":"1FAF5-1F3FF","sheet_x":57,"sheet_y":12}}},{"unified":"1FAF6","sheet_x":57,"sheet_y":13,"skin_variations":{"1F3FB":{"unified":"1FAF6-1F3FB","sheet_x":57,"sheet_y":14},"1F3FC":{"unified":"1FAF6-1F3FC","sheet_x":57,"sheet_y":15},"1F3FD":{"unified":"1FAF6-1F3FD","sheet_x":57,"sheet_y":16},"1F3FE":{"unified":"1FAF6-1F3FE","sheet_x":57,"sheet_y":17},"1F3FF":{"unified":"1FAF6-1F3FF","sheet_x":57,"sheet_y":18}}},{"unified":"1FAF7","sheet_x":57,"sheet_y":19,"skin_variations":{"1F3FB":{"unified":"1FAF7-1F3FB","sheet_x":57,"sheet_y":20},"1F3FC":{"unified":"1FAF7-1F3FC","sheet_x":57,"sheet_y":21},"1F3FD":{"unified":"1FAF7-1F3FD","sheet_x":57,"sheet_y":22},"1F3FE":{"unified":"1FAF7-1F3FE","sheet_x":57,"sheet_y":23},"1F3FF":{"unified":"1FAF7-1F3FF","sheet_x":57,"sheet_y":24}}},{"unified":"1FAF8","sheet_x":57,"sheet_y":25,"skin_variations":{"1F3FB":{"unified":"1FAF8-1F3FB","sheet_x":57,"sheet_y":26},"1F3FC":{"unified":"1FAF8-1F3FC","sheet_x":57,"sheet_y":27},"1F3FD":{"unified":"1FAF8-1F3FD","sheet_x":57,"sheet_y":28},"1F3FE":{"unified":"1FAF8-1F3FE","sheet_x":57,"sheet_y":29},"1F3FF":{"unified":"1FAF8-1F3FF","sheet_x":57,"sheet_y":30}}},{"unified":"203C-FE0F","sheet_x":57,"sheet_y":31,"skin_variations":{}},{"unified":"2049-FE0F","sheet_x":57,"sheet_y":32,"skin_variations":{}},{"unified":"2122-FE0F","sheet_x":57,"sheet_y":33,"skin_variations":{}},{"unified":"2139-FE0F","sheet_x":57,"sheet_y":34,"skin_variations":{}},{"unified":"2194-FE0F","sheet_x":57,"sheet_y":35,"skin_variations":{}},{"unified":"2195-FE0F","sheet_x":57,"sheet_y":36,"skin_variations":{}},{"unified":"2196-FE0F","sheet_x":57,"sheet_y":37,"skin_variations":{}},{"unified":"2197-FE0F","sheet_x":57,"sheet_y":38,"skin_variations":{}},{"unified":"2198-FE0F","sheet_x":57,"sheet_y":39,"skin_variations":{}},{"unified":"2199-FE0F","sheet_x":57,"sheet_y":40,"skin_variations":{}},{"unified":"21A9-FE0F","sheet_x":57,"sheet_y":41,"skin_variations":{}},{"unified":"21AA-FE0F","sheet_x":57,"sheet_y":42,"skin_variations":{}},{"unified":"231A","sheet_x":57,"sheet_y":43,"skin_variations":{}},{"unified":"231B","sheet_x":57,"sheet_y":44,"skin_variations":{}},{"unified":"2328-FE0F","sheet_x":57,"sheet_y":45,"skin_variations":{}},{"unified":"23CF-FE0F","sheet_x":57,"sheet_y":46,"skin_variations":{}},{"unified":"23E9","sheet_x":57,"sheet_y":47,"skin_variations":{}},{"unified":"23EA","sheet_x":57,"sheet_y":48,"skin_variations":{}},{"unified":"23EB","sheet_x":57,"sheet_y":49,"skin_variations":{}},{"unified":"23EC","sheet_x":57,"sheet_y":50,"skin_variations":{}},{"unified":"23ED-FE0F","sheet_x":57,"sheet_y":51,"skin_variations":{}},{"unified":"23EE-FE0F","sheet_x":57,"sheet_y":52,"skin_variations":{}},{"unified":"23EF-FE0F","sheet_x":57,"sheet_y":53,"skin_variations":{}},{"unified":"23F0","sheet_x":57,"sheet_y":54,"skin_variations":{}},{"unified":"23F1-FE0F","sheet_x":57,"sheet_y":55,"skin_variations":{}},{"unified":"23F2-FE0F","sheet_x":57,"sheet_y":56,"skin_variations":{}},{"unified":"23F3","sheet_x":57,"sheet_y":57,"skin_variations":{}},{"unified":"23F8-FE0F","sheet_x":57,"sheet_y":58,"skin_variations":{}},{"unified":"23F9-FE0F","sheet_x":57,"sheet_y":59,"skin_variations":{}},{"unified":"23FA-FE0F","sheet_x":57,"sheet_y":60,"skin_variations":{}},{"unified":"24C2-FE0F","sheet_x":57,"sheet_y":61,"skin_variations":{}},{"unified":"25AA-FE0F","sheet_x":58,"sheet_y":0,"skin_variations":{}},{"unified":"25AB-FE0F","sheet_x":58,"sheet_y":1,"skin_variations":{}},{"unified":"25B6-FE0F","sheet_x":58,"sheet_y":2,"skin_variations":{}},{"unified":"25C0-FE0F","sheet_x":58,"sheet_y":3,"skin_variations":{}},{"unified":"25FB-FE0F","sheet_x":58,"sheet_y":4,"skin_variations":{}},{"unified":"25FC-FE0F","sheet_x":58,"sheet_y":5,"skin_variations":{}},{"unified":"25FD","sheet_x":58,"sheet_y":6,"skin_variations":{}},{"unified":"25FE","sheet_x":58,"sheet_y":7,"skin_variations":{}},{"unified":"2600-FE0F","sheet_x":58,"sheet_y":8,"skin_variations":{}},{"unified":"2601-FE0F","sheet_x":58,"sheet_y":9,"skin_variations":{}},{"unified":"2602-FE0F","sheet_x":58,"sheet_y":10,"skin_variations":{}},{"unified":"2603-FE0F","sheet_x":58,"sheet_y":11,"skin_variations":{}},{"unified":"2604-FE0F","sheet_x":58,"sheet_y":12,"skin_variations":{}},{"unified":"260E-FE0F","sheet_x":58,"sheet_y":13,"skin_variations":{}},{"unified":"2611-FE0F","sheet_x":58,"sheet_y":14,"skin_variations":{}},{"unified":"2614","sheet_x":58,"sheet_y":15,"skin_variations":{}},{"unified":"2615","sheet_x":58,"sheet_y":16,"skin_variations":{}},{"unified":"2618-FE0F","sheet_x":58,"sheet_y":17,"skin_variations":{}},{"unified":"261D-FE0F","sheet_x":58,"sheet_y":18,"skin_variations":{"1F3FB":{"unified":"261D-1F3FB","sheet_x":58,"sheet_y":19},"1F3FC":{"unified":"261D-1F3FC","sheet_x":58,"sheet_y":20},"1F3FD":{"unified":"261D-1F3FD","sheet_x":58,"sheet_y":21},"1F3FE":{"unified":"261D-1F3FE","sheet_x":58,"sheet_y":22},"1F3FF":{"unified":"261D-1F3FF","sheet_x":58,"sheet_y":23}}},{"unified":"2620-FE0F","sheet_x":58,"sheet_y":24,"skin_variations":{}},{"unified":"2622-FE0F","sheet_x":58,"sheet_y":25,"skin_variations":{}},{"unified":"2623-FE0F","sheet_x":58,"sheet_y":26,"skin_variations":{}},{"unified":"2626-FE0F","sheet_x":58,"sheet_y":27,"skin_variations":{}},{"unified":"262A-FE0F","sheet_x":58,"sheet_y":28,"skin_variations":{}},{"unified":"262E-FE0F","sheet_x":58,"sheet_y":29,"skin_variations":{}},{"unified":"262F-FE0F","sheet_x":58,"sheet_y":30,"skin_variations":{}},{"unified":"2638-FE0F","sheet_x":58,"sheet_y":31,"skin_variations":{}},{"unified":"2639-FE0F","sheet_x":58,"sheet_y":32,"skin_variations":{}},{"unified":"263A-FE0F","sheet_x":58,"sheet_y":33,"skin_variations":{}},{"unified":"2640-FE0F","sheet_x":58,"sheet_y":34,"skin_variations":{}},{"unified":"2642-FE0F","sheet_x":58,"sheet_y":35,"skin_variations":{}},{"unified":"2648","sheet_x":58,"sheet_y":36,"skin_variations":{}},{"unified":"2649","sheet_x":58,"sheet_y":37,"skin_variations":{}},{"unified":"264A","sheet_x":58,"sheet_y":38,"skin_variations":{}},{"unified":"264B","sheet_x":58,"sheet_y":39,"skin_variations":{}},{"unified":"264C","sheet_x":58,"sheet_y":40,"skin_variations":{}},{"unified":"264D","sheet_x":58,"sheet_y":41,"skin_variations":{}},{"unified":"264E","sheet_x":58,"sheet_y":42,"skin_variations":{}},{"unified":"264F","sheet_x":58,"sheet_y":43,"skin_variations":{}},{"unified":"2650","sheet_x":58,"sheet_y":44,"skin_variations":{}},{"unified":"2651","sheet_x":58,"sheet_y":45,"skin_variations":{}},{"unified":"2652","sheet_x":58,"sheet_y":46,"skin_variations":{}},{"unified":"2653","sheet_x":58,"sheet_y":47,"skin_variations":{}},{"unified":"265F-FE0F","sheet_x":58,"sheet_y":48,"skin_variations":{}},{"unified":"2660-FE0F","sheet_x":58,"sheet_y":49,"skin_variations":{}},{"unified":"2663-FE0F","sheet_x":58,"sheet_y":50,"skin_variations":{}},{"unified":"2665-FE0F","sheet_x":58,"sheet_y":51,"skin_variations":{}},{"unified":"2666-FE0F","sheet_x":58,"sheet_y":52,"skin_variations":{}},{"unified":"2668-FE0F","sheet_x":58,"sheet_y":53,"skin_variations":{}},{"unified":"267B-FE0F","sheet_x":58,"sheet_y":54,"skin_variations":{}},{"unified":"267E-FE0F","sheet_x":58,"sheet_y":55,"skin_variations":{}},{"unified":"267F","sheet_x":58,"sheet_y":56,"skin_variations":{}},{"unified":"2692-FE0F","sheet_x":58,"sheet_y":57,"skin_variations":{}},{"unified":"2693","sheet_x":58,"sheet_y":58,"skin_variations":{}},{"unified":"2694-FE0F","sheet_x":58,"sheet_y":59,"skin_variations":{}},{"unified":"2695-FE0F","sheet_x":58,"sheet_y":60,"skin_variations":{}},{"unified":"2696-FE0F","sheet_x":58,"sheet_y":61,"skin_variations":{}},{"unified":"2697-FE0F","sheet_x":59,"sheet_y":0,"skin_variations":{}},{"unified":"2699-FE0F","sheet_x":59,"sheet_y":1,"skin_variations":{}},{"unified":"269B-FE0F","sheet_x":59,"sheet_y":2,"skin_variations":{}},{"unified":"269C-FE0F","sheet_x":59,"sheet_y":3,"skin_variations":{}},{"unified":"26A0-FE0F","sheet_x":59,"sheet_y":4,"skin_variations":{}},{"unified":"26A1","sheet_x":59,"sheet_y":5,"skin_variations":{}},{"unified":"26A7-FE0F","sheet_x":59,"sheet_y":6,"skin_variations":{}},{"unified":"26AA","sheet_x":59,"sheet_y":7,"skin_variations":{}},{"unified":"26AB","sheet_x":59,"sheet_y":8,"skin_variations":{}},{"unified":"26B0-FE0F","sheet_x":59,"sheet_y":9,"skin_variations":{}},{"unified":"26B1-FE0F","sheet_x":59,"sheet_y":10,"skin_variations":{}},{"unified":"26BD","sheet_x":59,"sheet_y":11,"skin_variations":{}},{"unified":"26BE","sheet_x":59,"sheet_y":12,"skin_variations":{}},{"unified":"26C4","sheet_x":59,"sheet_y":13,"skin_variations":{}},{"unified":"26C5","sheet_x":59,"sheet_y":14,"skin_variations":{}},{"unified":"26C8-FE0F","sheet_x":59,"sheet_y":15,"skin_variations":{}},{"unified":"26CE","sheet_x":59,"sheet_y":16,"skin_variations":{}},{"unified":"26CF-FE0F","sheet_x":59,"sheet_y":17,"skin_variations":{}},{"unified":"26D1-FE0F","sheet_x":59,"sheet_y":18,"skin_variations":{}},{"unified":"26D3-FE0F-200D-1F4A5","sheet_x":59,"sheet_y":19,"skin_variations":{}},{"unified":"26D3-FE0F","sheet_x":59,"sheet_y":20,"skin_variations":{}},{"unified":"26D4","sheet_x":59,"sheet_y":21,"skin_variations":{}},{"unified":"26E9-FE0F","sheet_x":59,"sheet_y":22,"skin_variations":{}},{"unified":"26EA","sheet_x":59,"sheet_y":23,"skin_variations":{}},{"unified":"26F0-FE0F","sheet_x":59,"sheet_y":24,"skin_variations":{}},{"unified":"26F1-FE0F","sheet_x":59,"sheet_y":25,"skin_variations":{}},{"unified":"26F2","sheet_x":59,"sheet_y":26,"skin_variations":{}},{"unified":"26F3","sheet_x":59,"sheet_y":27,"skin_variations":{}},{"unified":"26F4-FE0F","sheet_x":59,"sheet_y":28,"skin_variations":{}},{"unified":"26F5","sheet_x":59,"sheet_y":29,"skin_variations":{}},{"unified":"26F7-FE0F","sheet_x":59,"sheet_y":30,"skin_variations":{}},{"unified":"26F8-FE0F","sheet_x":59,"sheet_y":31,"skin_variations":{}},{"unified":"26F9-FE0F-200D-2640-FE0F","sheet_x":59,"sheet_y":32,"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB-200D-2640-FE0F","sheet_x":59,"sheet_y":33},"1F3FC":{"unified":"26F9-1F3FC-200D-2640-FE0F","sheet_x":59,"sheet_y":34},"1F3FD":{"unified":"26F9-1F3FD-200D-2640-FE0F","sheet_x":59,"sheet_y":35},"1F3FE":{"unified":"26F9-1F3FE-200D-2640-FE0F","sheet_x":59,"sheet_y":36},"1F3FF":{"unified":"26F9-1F3FF-200D-2640-FE0F","sheet_x":59,"sheet_y":37}}},{"unified":"26F9-FE0F-200D-2642-FE0F","sheet_x":59,"sheet_y":38,"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB-200D-2642-FE0F","sheet_x":59,"sheet_y":39},"1F3FC":{"unified":"26F9-1F3FC-200D-2642-FE0F","sheet_x":59,"sheet_y":40},"1F3FD":{"unified":"26F9-1F3FD-200D-2642-FE0F","sheet_x":59,"sheet_y":41},"1F3FE":{"unified":"26F9-1F3FE-200D-2642-FE0F","sheet_x":59,"sheet_y":42},"1F3FF":{"unified":"26F9-1F3FF-200D-2642-FE0F","sheet_x":59,"sheet_y":43}}},{"unified":"26F9-FE0F","sheet_x":59,"sheet_y":44,"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB","sheet_x":59,"sheet_y":45},"1F3FC":{"unified":"26F9-1F3FC","sheet_x":59,"sheet_y":46},"1F3FD":{"unified":"26F9-1F3FD","sheet_x":59,"sheet_y":47},"1F3FE":{"unified":"26F9-1F3FE","sheet_x":59,"sheet_y":48},"1F3FF":{"unified":"26F9-1F3FF","sheet_x":59,"sheet_y":49}}},{"unified":"26FA","sheet_x":59,"sheet_y":50,"skin_variations":{}},{"unified":"26FD","sheet_x":59,"sheet_y":51,"skin_variations":{}},{"unified":"2702-FE0F","sheet_x":59,"sheet_y":52,"skin_variations":{}},{"unified":"2705","sheet_x":59,"sheet_y":53,"skin_variations":{}},{"unified":"2708-FE0F","sheet_x":59,"sheet_y":54,"skin_variations":{}},{"unified":"2709-FE0F","sheet_x":59,"sheet_y":55,"skin_variations":{}},{"unified":"270A","sheet_x":59,"sheet_y":56,"skin_variations":{"1F3FB":{"unified":"270A-1F3FB","sheet_x":59,"sheet_y":57},"1F3FC":{"unified":"270A-1F3FC","sheet_x":59,"sheet_y":58},"1F3FD":{"unified":"270A-1F3FD","sheet_x":59,"sheet_y":59},"1F3FE":{"unified":"270A-1F3FE","sheet_x":59,"sheet_y":60},"1F3FF":{"unified":"270A-1F3FF","sheet_x":59,"sheet_y":61}}},{"unified":"270B","sheet_x":60,"sheet_y":0,"skin_variations":{"1F3FB":{"unified":"270B-1F3FB","sheet_x":60,"sheet_y":1},"1F3FC":{"unified":"270B-1F3FC","sheet_x":60,"sheet_y":2},"1F3FD":{"unified":"270B-1F3FD","sheet_x":60,"sheet_y":3},"1F3FE":{"unified":"270B-1F3FE","sheet_x":60,"sheet_y":4},"1F3FF":{"unified":"270B-1F3FF","sheet_x":60,"sheet_y":5}}},{"unified":"270C-FE0F","sheet_x":60,"sheet_y":6,"skin_variations":{"1F3FB":{"unified":"270C-1F3FB","sheet_x":60,"sheet_y":7},"1F3FC":{"unified":"270C-1F3FC","sheet_x":60,"sheet_y":8},"1F3FD":{"unified":"270C-1F3FD","sheet_x":60,"sheet_y":9},"1F3FE":{"unified":"270C-1F3FE","sheet_x":60,"sheet_y":10},"1F3FF":{"unified":"270C-1F3FF","sheet_x":60,"sheet_y":11}}},{"unified":"270D-FE0F","sheet_x":60,"sheet_y":12,"skin_variations":{"1F3FB":{"unified":"270D-1F3FB","sheet_x":60,"sheet_y":13},"1F3FC":{"unified":"270D-1F3FC","sheet_x":60,"sheet_y":14},"1F3FD":{"unified":"270D-1F3FD","sheet_x":60,"sheet_y":15},"1F3FE":{"unified":"270D-1F3FE","sheet_x":60,"sheet_y":16},"1F3FF":{"unified":"270D-1F3FF","sheet_x":60,"sheet_y":17}}},{"unified":"270F-FE0F","sheet_x":60,"sheet_y":18,"skin_variations":{}},{"unified":"2712-FE0F","sheet_x":60,"sheet_y":19,"skin_variations":{}},{"unified":"2714-FE0F","sheet_x":60,"sheet_y":20,"skin_variations":{}},{"unified":"2716-FE0F","sheet_x":60,"sheet_y":21,"skin_variations":{}},{"unified":"271D-FE0F","sheet_x":60,"sheet_y":22,"skin_variations":{}},{"unified":"2721-FE0F","sheet_x":60,"sheet_y":23,"skin_variations":{}},{"unified":"2728","sheet_x":60,"sheet_y":24,"skin_variations":{}},{"unified":"2733-FE0F","sheet_x":60,"sheet_y":25,"skin_variations":{}},{"unified":"2734-FE0F","sheet_x":60,"sheet_y":26,"skin_variations":{}},{"unified":"2744-FE0F","sheet_x":60,"sheet_y":27,"skin_variations":{}},{"unified":"2747-FE0F","sheet_x":60,"sheet_y":28,"skin_variations":{}},{"unified":"274C","sheet_x":60,"sheet_y":29,"skin_variations":{}},{"unified":"274E","sheet_x":60,"sheet_y":30,"skin_variations":{}},{"unified":"2753","sheet_x":60,"sheet_y":31,"skin_variations":{}},{"unified":"2754","sheet_x":60,"sheet_y":32,"skin_variations":{}},{"unified":"2755","sheet_x":60,"sheet_y":33,"skin_variations":{}},{"unified":"2757","sheet_x":60,"sheet_y":34,"skin_variations":{}},{"unified":"2763-FE0F","sheet_x":60,"sheet_y":35,"skin_variations":{}},{"unified":"2764-FE0F-200D-1F525","sheet_x":60,"sheet_y":36,"skin_variations":{}},{"unified":"2764-FE0F-200D-1FA79","sheet_x":60,"sheet_y":37,"skin_variations":{}},{"unified":"2764-FE0F","sheet_x":60,"sheet_y":38,"skin_variations":{}},{"unified":"2795","sheet_x":60,"sheet_y":39,"skin_variations":{}},{"unified":"2796","sheet_x":60,"sheet_y":40,"skin_variations":{}},{"unified":"2797","sheet_x":60,"sheet_y":41,"skin_variations":{}},{"unified":"27A1-FE0F","sheet_x":60,"sheet_y":42,"skin_variations":{}},{"unified":"27B0","sheet_x":60,"sheet_y":43,"skin_variations":{}},{"unified":"27BF","sheet_x":60,"sheet_y":44,"skin_variations":{}},{"unified":"2934-FE0F","sheet_x":60,"sheet_y":45,"skin_variations":{}},{"unified":"2935-FE0F","sheet_x":60,"sheet_y":46,"skin_variations":{}},{"unified":"2B05-FE0F","sheet_x":60,"sheet_y":47,"skin_variations":{}},{"unified":"2B06-FE0F","sheet_x":60,"sheet_y":48,"skin_variations":{}},{"unified":"2B07-FE0F","sheet_x":60,"sheet_y":49,"skin_variations":{}},{"unified":"2B1B","sheet_x":60,"sheet_y":50,"skin_variations":{}},{"unified":"2B1C","sheet_x":60,"sheet_y":51,"skin_variations":{}},{"unified":"2B50","sheet_x":60,"sheet_y":52,"skin_variations":{}},{"unified":"2B55","sheet_x":60,"sheet_y":53,"skin_variations":{}},{"unified":"3030-FE0F","sheet_x":60,"sheet_y":54,"skin_variations":{}},{"unified":"303D-FE0F","sheet_x":60,"sheet_y":55,"skin_variations":{}},{"unified":"3297-FE0F","sheet_x":60,"sheet_y":56,"skin_variations":{}},{"unified":"3299-FE0F","sheet_x":60,"sheet_y":57,"skin_variations":{}}]
\ No newline at end of file
diff --git a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts
index 0a5a4c1d76..d116c6c62c 100644
--- a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts
+++ b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts
@@ -9,13 +9,12 @@ import type {
 import emojiCompressed from './emoji_compressed';
 import { unicodeToFilename } from './unicode_to_filename';
 
-type UnicodeMapping = Record<
-  FilenameData[number][0],
-  {
+type UnicodeMapping = {
+  [key in FilenameData[number][0]]: {
     shortCode: ShortCodesToEmojiDataKey;
     filename: FilenameData[number][number];
-  }
->;
+  };
+};
 
 const [
   shortCodesToEmojiData,
@@ -33,8 +32,11 @@ function processEmojiMapData(
   shortCode?: ShortCodesToEmojiDataKey,
 ) {
   const [native, _filename] = emojiMapData;
-  // filename name can be derived from unicodeToFilename
-  const filename = emojiMapData[1] ?? unicodeToFilename(native);
+  let filename = emojiMapData[1];
+  if (!filename) {
+    // filename name can be derived from unicodeToFilename
+    filename = unicodeToFilename(native);
+  }
   unicodeMapping[native] = {
     shortCode,
     filename,
diff --git a/app/javascript/mastodon/features/emoji/unicode_to_filename_s.js b/app/javascript/mastodon/features/emoji/unicode_to_filename_s.js
index d00563ea6f..3395c77174 100644
--- a/app/javascript/mastodon/features/emoji/unicode_to_filename_s.js
+++ b/app/javascript/mastodon/features/emoji/unicode_to_filename_s.js
@@ -1,6 +1,8 @@
+/* eslint-disable import/no-commonjs --
+   We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
+
 // taken from:
 // https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
-// eslint-disable-next-line import/no-commonjs, no-undef
 exports.unicodeToFilename = (str) => {
   let result = '';
   let charCode = 0;
diff --git a/app/javascript/mastodon/features/emoji/unicode_to_unified_name_s.js b/app/javascript/mastodon/features/emoji/unicode_to_unified_name_s.js
index f5dc6431a4..108b911222 100644
--- a/app/javascript/mastodon/features/emoji/unicode_to_unified_name_s.js
+++ b/app/javascript/mastodon/features/emoji/unicode_to_unified_name_s.js
@@ -1,3 +1,6 @@
+/* eslint-disable import/no-commonjs --
+   We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
+
 function padLeft(str, num) {
   while (str.length < num) {
     str = '0' + str;
@@ -6,7 +9,6 @@ function padLeft(str, num) {
   return str;
 }
 
-// eslint-disable-next-line import/no-commonjs, no-undef
 exports.unicodeToUnifiedName = (str) => {
   let output = '';
 
diff --git a/app/javascript/mastodon/features/emoji_reacted_statuses/index.jsx b/app/javascript/mastodon/features/emoji_reacted_statuses/index.jsx
new file mode 100644
index 0000000000..54f19cb7d0
--- /dev/null
+++ b/app/javascript/mastodon/features/emoji_reacted_statuses/index.jsx
@@ -0,0 +1,114 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { debounce } from 'lodash';
+
+import EmojiReactionIcon from '@/material-icons/400-24px/mood.svg?react';
+import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
+import { fetchEmojiReactedStatuses, expandEmojiReactedStatuses } from 'mastodon/actions/emoji_reactions';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import StatusList from 'mastodon/components/status_list';
+
+const messages = defineMessages({
+  heading: { id: 'column.emoji_reactions', defaultMessage: 'Stamps' },
+});
+
+const mapStateToProps = state => ({
+  statusIds: state.getIn(['status_lists', 'emoji_reactions', 'items']),
+  isLoading: state.getIn(['status_lists', 'emoji_reactions', 'isLoading'], true),
+  hasMore: !!state.getIn(['status_lists', 'emoji_reactions', 'next']),
+});
+
+class EmojiReactions extends ImmutablePureComponent {
+
+  static propTypes = {
+    dispatch: PropTypes.func.isRequired,
+    statusIds: ImmutablePropTypes.list.isRequired,
+    intl: PropTypes.object.isRequired,
+    columnId: PropTypes.string,
+    multiColumn: PropTypes.bool,
+    hasMore: PropTypes.bool,
+    isLoading: PropTypes.bool,
+  };
+
+  componentWillMount () {
+    this.props.dispatch(fetchEmojiReactedStatuses());
+  }
+
+  handlePin = () => {
+    const { columnId, dispatch } = this.props;
+
+    if (columnId) {
+      dispatch(removeColumn(columnId));
+    } else {
+      dispatch(addColumn('EMOJI_REACTIONS', {}));
+    }
+  };
+
+  handleMove = (dir) => {
+    const { columnId, dispatch } = this.props;
+    dispatch(moveColumn(columnId, dir));
+  };
+
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  };
+
+  setRef = c => {
+    this.column = c;
+  };
+
+  handleLoadMore = debounce(() => {
+    this.props.dispatch(expandEmojiReactedStatuses());
+  }, 300, { leading: true });
+
+  render () {
+    const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
+    const pinned = !!columnId;
+
+    const emptyMessage = <FormattedMessage id='empty_column.emoji_reacted_statuses' defaultMessage="You don't have any emoji reacted posts yet. When you emoji react one, it will show up here." />;
+
+    return (
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
+        <ColumnHeader
+          icon='smile-o'
+          iconComponent={EmojiReactionIcon}
+          title={intl.formatMessage(messages.heading)}
+          onPin={this.handlePin}
+          onMove={this.handleMove}
+          onClick={this.handleHeaderClick}
+          pinned={pinned}
+          multiColumn={multiColumn}
+          showBackButton
+        />
+
+        <StatusList
+          trackScroll={!pinned}
+          statusIds={statusIds}
+          scrollKey={`emoji_reacted_statuses-${columnId}`}
+          hasMore={hasMore}
+          isLoading={isLoading}
+          onLoadMore={this.handleLoadMore}
+          emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
+        />
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.heading)}</title>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(EmojiReactions));
diff --git a/app/javascript/mastodon/features/emoji_reacted_statuses/index.tsx b/app/javascript/mastodon/features/emoji_reacted_statuses/index.tsx
deleted file mode 100644
index 58186cbbd7..0000000000
--- a/app/javascript/mastodon/features/emoji_reacted_statuses/index.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { useEffect, useRef, useCallback } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-
-import EmojiReactionIcon from '@/material-icons/400-24px/mood.svg?react';
-import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
-import {
-  fetchEmojiReactedStatuses,
-  expandEmojiReactedStatuses,
-} from 'mastodon/actions/emoji_reactions';
-import { Column } from 'mastodon/components/column';
-import type { ColumnRef } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import StatusList from 'mastodon/components/status_list';
-import { getStatusList } from 'mastodon/selectors';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  heading: { id: 'column.emoji_reactions', defaultMessage: 'Stamps' },
-});
-
-const Favourites: React.FC<{ columnId: string; multiColumn: boolean }> = ({
-  columnId,
-  multiColumn,
-}) => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-  const columnRef = useRef<ColumnRef>(null);
-  const statusIds = useAppSelector((state) =>
-    getStatusList(state, 'emoji_reactions'),
-  );
-  const isLoading = useAppSelector(
-    (state) =>
-      state.status_lists.getIn(
-        ['emoji_reactions', 'isLoading'],
-        true,
-      ) as boolean,
-  );
-  const hasMore = useAppSelector(
-    (state) => !!state.status_lists.getIn(['emoji_reactions', 'next']),
-  );
-
-  useEffect(() => {
-    dispatch(fetchEmojiReactedStatuses());
-  }, [dispatch]);
-
-  const handlePin = useCallback(() => {
-    if (columnId) {
-      dispatch(removeColumn(columnId));
-    } else {
-      dispatch(addColumn('EMOJI_REACTIONS', {}));
-    }
-  }, [dispatch, columnId]);
-
-  const handleMove = useCallback(
-    (dir: number) => {
-      dispatch(moveColumn(columnId, dir));
-    },
-    [dispatch, columnId],
-  );
-
-  const handleHeaderClick = useCallback(() => {
-    columnRef.current?.scrollTop();
-  }, []);
-
-  const handleLoadMore = useCallback(() => {
-    dispatch(expandEmojiReactedStatuses());
-  }, [dispatch]);
-
-  const pinned = !!columnId;
-
-  const emptyMessage = (
-    <FormattedMessage
-      id='empty_column.emoji_reacted_statuses'
-      defaultMessage="You don't have any emoji reacted posts yet. When you emoji react one, it will show up here."
-    />
-  );
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      ref={columnRef}
-      label={intl.formatMessage(messages.heading)}
-    >
-      <ColumnHeader
-        icon='star'
-        iconComponent={EmojiReactionIcon}
-        title={intl.formatMessage(messages.heading)}
-        onPin={handlePin}
-        onMove={handleMove}
-        onClick={handleHeaderClick}
-        pinned={pinned}
-        multiColumn={multiColumn}
-      />
-
-      <StatusList
-        trackScroll={!pinned}
-        statusIds={statusIds}
-        scrollKey={`emoji_reacted_statuses-${columnId}`}
-        hasMore={hasMore}
-        isLoading={isLoading}
-        onLoadMore={handleLoadMore}
-        emptyMessage={emptyMessage}
-        bindToDocument={!multiColumn}
-      />
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default Favourites;
diff --git a/app/javascript/mastodon/features/emoji_reactions/index.jsx b/app/javascript/mastodon/features/emoji_reactions/index.jsx
index 46a1c3388e..a863fc5b47 100644
--- a/app/javascript/mastodon/features/emoji_reactions/index.jsx
+++ b/app/javascript/mastodon/features/emoji_reactions/index.jsx
@@ -10,13 +10,12 @@ import { connect } from 'react-redux';
 
 import { debounce } from 'lodash';
 
-
 import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
 import { fetchEmojiReactions, expandEmojiReactions } from 'mastodon/actions/interactions';
-import { Account } from 'mastodon/components/account';
 import ColumnHeader from 'mastodon/components/column_header';
 import { Icon } from 'mastodon/components/icon';
 import ScrollableList from 'mastodon/components/scrollable_list';
+import AccountContainer from 'mastodon/containers/account_container';
 import Column from 'mastodon/features/ui/components/column';
 
 
@@ -102,11 +101,11 @@ class EmojiReactions extends ImmutablePureComponent {
           bindToDocument={!multiColumn}
         >
           {Object.keys(groups).map((key) =>(
-            <Account key={key} id={key} hideButtons>
+            <AccountContainer key={key} id={key} withNote={false} hideButtons>
               <div style={{ 'maxWidth': '100px' }}>
                 {groups[key].map((value, index2) => <EmojiView key={index2} name={value.name} url={value.url} staticUrl={value.static_url} />)}
               </div>
-            </Account>
+            </AccountContainer>
           ))}
         </ScrollableList>
 
diff --git a/app/javascript/mastodon/features/explore/components/card.jsx b/app/javascript/mastodon/features/explore/components/card.jsx
index 9617781b53..1908648510 100644
--- a/app/javascript/mastodon/features/explore/components/card.jsx
+++ b/app/javascript/mastodon/features/explore/components/card.jsx
@@ -25,7 +25,7 @@ export const Card = ({ id, source }) => {
   const dispatch = useDispatch();
 
   const handleDismiss = useCallback(() => {
-    dispatch(dismissSuggestion({ accountId: id }));
+    dispatch(dismissSuggestion(id));
   }, [id, dispatch]);
 
   let label;
@@ -55,11 +55,11 @@ export const Card = ({ id, source }) => {
       </div>
 
       <div className='explore__suggestions__card__body'>
-        <Link to={`/@${account.get('acct')}`} data-hover-card-account={account.id}><Avatar account={account} size={48} /></Link>
+        <Link to={`/@${account.get('acct')}`}><Avatar account={account} size={48} /></Link>
 
         <div className='explore__suggestions__card__body__main'>
           <div className='explore__suggestions__card__body__main__name-button'>
-            <Link className='explore__suggestions__card__body__main__name-button__name' to={`/@${account.get('acct')}`} data-hover-card-account={account.id}><DisplayName account={account} /></Link>
+            <Link className='explore__suggestions__card__body__main__name-button__name' to={`/@${account.get('acct')}`}><DisplayName account={account} /></Link>
             <IconButton iconComponent={CloseIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
             <FollowButton accountId={account.get('id')} />
           </div>
diff --git a/app/javascript/mastodon/features/explore/components/search_section.jsx b/app/javascript/mastodon/features/explore/components/search_section.jsx
new file mode 100644
index 0000000000..c84e3f7cef
--- /dev/null
+++ b/app/javascript/mastodon/features/explore/components/search_section.jsx
@@ -0,0 +1,20 @@
+import PropTypes from 'prop-types';
+
+import { FormattedMessage } from 'react-intl';
+
+export const SearchSection = ({ title, onClickMore, children }) => (
+  <div className='search-results__section'>
+    <div className='search-results__section__header'>
+      <h3>{title}</h3>
+      {onClickMore && <button onClick={onClickMore}><FormattedMessage id='search_results.see_all' defaultMessage='See all' /></button>}
+    </div>
+
+    {children}
+  </div>
+);
+
+SearchSection.propTypes = {
+  title: PropTypes.node.isRequired,
+  onClickMore: PropTypes.func,
+  children: PropTypes.children,
+};
\ No newline at end of file
diff --git a/app/javascript/mastodon/features/explore/index.jsx b/app/javascript/mastodon/features/explore/index.jsx
new file mode 100644
index 0000000000..83e5df22f8
--- /dev/null
+++ b/app/javascript/mastodon/features/explore/index.jsx
@@ -0,0 +1,114 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+import { NavLink, Switch, Route } from 'react-router-dom';
+
+import { connect } from 'react-redux';
+
+import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
+import SearchIcon from '@/material-icons/400-24px/search.svg?react';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import Search from 'mastodon/features/compose/containers/search_container';
+import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
+import { trendsEnabled } from 'mastodon/initial_state';
+
+import Links from './links';
+import SearchResults from './results';
+import Statuses from './statuses';
+import Suggestions from './suggestions';
+import Tags from './tags';
+
+const messages = defineMessages({
+  title: { id: 'explore.title', defaultMessage: 'Explore' },
+  searchResults: { id: 'explore.search_results', defaultMessage: 'Search results' },
+});
+
+const mapStateToProps = state => ({
+  layout: state.getIn(['meta', 'layout']),
+  isSearching: state.getIn(['search', 'submitted']) || !trendsEnabled,
+});
+
+class Explore extends PureComponent {
+  static propTypes = {
+    identity: identityContextPropShape,
+    intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
+    isSearching: PropTypes.bool,
+  };
+
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  };
+
+  setRef = c => {
+    this.column = c;
+  };
+
+  render() {
+    const { intl, multiColumn, isSearching } = this.props;
+    const { signedIn } = this.props.identity;
+
+    return (
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
+        <ColumnHeader
+          icon={isSearching ? 'search' : 'explore'}
+          iconComponent={isSearching ? SearchIcon : ExploreIcon}
+          title={intl.formatMessage(isSearching ? messages.searchResults : messages.title)}
+          onClick={this.handleHeaderClick}
+          multiColumn={multiColumn}
+        />
+
+        <div className='explore__search-header'>
+          <Search />
+        </div>
+
+        {isSearching ? (
+          <SearchResults />
+        ) : (
+          <>
+            <div className='account__section-headline'>
+              <NavLink exact to='/explore'>
+                <FormattedMessage tagName='div' id='explore.trending_statuses' defaultMessage='Posts' />
+              </NavLink>
+
+              <NavLink exact to='/explore/tags'>
+                <FormattedMessage tagName='div' id='explore.trending_tags' defaultMessage='Hashtags' />
+              </NavLink>
+
+              {signedIn && (
+                <NavLink exact to='/explore/suggestions'>
+                  <FormattedMessage tagName='div' id='explore.suggested_follows' defaultMessage='People' />
+                </NavLink>
+              )}
+
+              <NavLink exact to='/explore/links'>
+                <FormattedMessage tagName='div' id='explore.trending_links' defaultMessage='News' />
+              </NavLink>
+            </div>
+
+            <Switch>
+              <Route path='/explore/tags' component={Tags} />
+              <Route path='/explore/links' component={Links} />
+              <Route path='/explore/suggestions' component={Suggestions} />
+              <Route exact path={['/explore', '/explore/posts', '/search']}>
+                <Statuses multiColumn={multiColumn} />
+              </Route>
+            </Switch>
+
+            <Helmet>
+              <title>{intl.formatMessage(messages.title)}</title>
+              <meta name='robots' content={isSearching ? 'noindex' : 'all'} />
+            </Helmet>
+          </>
+        )}
+      </Column>
+    );
+  }
+
+}
+
+export default withIdentity(connect(mapStateToProps)(injectIntl(Explore)));
diff --git a/app/javascript/mastodon/features/explore/index.tsx b/app/javascript/mastodon/features/explore/index.tsx
deleted file mode 100644
index 671d92d6b4..0000000000
--- a/app/javascript/mastodon/features/explore/index.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import { useCallback, useRef } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { NavLink, Switch, Route } from 'react-router-dom';
-
-import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
-import { Column } from 'mastodon/components/column';
-import type { ColumnRef } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { Search } from 'mastodon/features/compose/components/search';
-import { useIdentity } from 'mastodon/identity_context';
-
-import Links from './links';
-import Statuses from './statuses';
-import Suggestions from './suggestions';
-import Tags from './tags';
-
-const messages = defineMessages({
-  title: { id: 'explore.title', defaultMessage: 'Explore' },
-});
-
-const Explore: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
-  const { signedIn } = useIdentity();
-  const intl = useIntl();
-  const columnRef = useRef<ColumnRef>(null);
-
-  const handleHeaderClick = useCallback(() => {
-    columnRef.current?.scrollTop();
-  }, []);
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      ref={columnRef}
-      label={intl.formatMessage(messages.title)}
-    >
-      <ColumnHeader
-        icon={'explore'}
-        iconComponent={ExploreIcon}
-        title={intl.formatMessage(messages.title)}
-        onClick={handleHeaderClick}
-        multiColumn={multiColumn}
-      />
-
-      <div className='explore__search-header'>
-        <Search singleColumn />
-      </div>
-
-      <div className='account__section-headline'>
-        <NavLink exact to='/explore'>
-          <FormattedMessage
-            tagName='div'
-            id='explore.trending_statuses'
-            defaultMessage='Posts'
-          />
-        </NavLink>
-
-        <NavLink exact to='/explore/tags'>
-          <FormattedMessage
-            tagName='div'
-            id='explore.trending_tags'
-            defaultMessage='Hashtags'
-          />
-        </NavLink>
-
-        {signedIn && (
-          <NavLink exact to='/explore/suggestions'>
-            <FormattedMessage
-              tagName='div'
-              id='explore.suggested_follows'
-              defaultMessage='People'
-            />
-          </NavLink>
-        )}
-
-        <NavLink exact to='/explore/links'>
-          <FormattedMessage
-            tagName='div'
-            id='explore.trending_links'
-            defaultMessage='News'
-          />
-        </NavLink>
-      </div>
-
-      <Switch>
-        <Route path='/explore/tags' component={Tags} />
-        <Route path='/explore/links' component={Links} />
-        <Route path='/explore/suggestions' component={Suggestions} />
-        <Route exact path={['/explore', '/explore/posts']}>
-          <Statuses multiColumn={multiColumn} />
-        </Route>
-      </Switch>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.title)}</title>
-        <meta name='robots' content='all' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default Explore;
diff --git a/app/javascript/mastodon/features/explore/links.jsx b/app/javascript/mastodon/features/explore/links.jsx
index 68cf0283ea..035e5aaad8 100644
--- a/app/javascript/mastodon/features/explore/links.jsx
+++ b/app/javascript/mastodon/features/explore/links.jsx
@@ -45,7 +45,7 @@ class Links extends PureComponent {
 
     const banner = (
       <DismissableBanner id='explore/links'>
-        <FormattedMessage id='dismissable_banner.explore_links' defaultMessage='These news stories are being shared the most on the fediverse today. Newer news stories posted by more different people are ranked higher.' />
+        <FormattedMessage id='dismissable_banner.explore_links' defaultMessage='These are news stories being shared the most on the social web today. Newer news stories posted by more different people are ranked higher.' />
       </DismissableBanner>
     );
 
diff --git a/app/javascript/mastodon/features/explore/results.jsx b/app/javascript/mastodon/features/explore/results.jsx
new file mode 100644
index 0000000000..2ee6e8c7f1
--- /dev/null
+++ b/app/javascript/mastodon/features/explore/results.jsx
@@ -0,0 +1,232 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import { List as ImmutableList } from 'immutable';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
+
+import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react';
+import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
+import TagIcon from '@/material-icons/400-24px/tag.svg?react';
+import { submitSearch, expandSearch } from 'mastodon/actions/search';
+import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
+import { Icon } from 'mastodon/components/icon';
+import ScrollableList from 'mastodon/components/scrollable_list';
+import Account from 'mastodon/containers/account_container';
+import Status from 'mastodon/containers/status_container';
+
+import { SearchSection } from './components/search_section';
+
+const messages = defineMessages({
+  title: { id: 'search_results.title', defaultMessage: 'Search for {q}' },
+});
+
+const mapStateToProps = state => ({
+  isLoading: state.getIn(['search', 'isLoading']),
+  results: state.getIn(['search', 'results']),
+  q: state.getIn(['search', 'searchTerm']),
+  submittedType: state.getIn(['search', 'type']),
+});
+
+const INITIAL_PAGE_LIMIT = 10;
+const INITIAL_DISPLAY = 4;
+
+const hidePeek = list => {
+  if (list.size > INITIAL_PAGE_LIMIT && list.size % INITIAL_PAGE_LIMIT === 1) {
+    return list.skipLast(1);
+  } else {
+    return list;
+  }
+};
+
+const renderAccounts = accounts => hidePeek(accounts).map(id => (
+  <Account key={id} id={id} />
+));
+
+const renderHashtags = hashtags => hidePeek(hashtags).map(hashtag => (
+  <Hashtag key={hashtag.get('name')} hashtag={hashtag} />
+));
+
+const renderStatuses = statuses => hidePeek(statuses).map(id => (
+  <Status key={id} id={id} contextType='explore' />
+));
+
+class Results extends PureComponent {
+
+  static propTypes = {
+    results: ImmutablePropTypes.contains({
+      accounts: ImmutablePropTypes.orderedSet,
+      statuses: ImmutablePropTypes.orderedSet,
+      hashtags: ImmutablePropTypes.orderedSet,
+    }),
+    isLoading: PropTypes.bool,
+    multiColumn: PropTypes.bool,
+    dispatch: PropTypes.func.isRequired,
+    q: PropTypes.string,
+    intl: PropTypes.object,
+    submittedType: PropTypes.oneOf(['accounts', 'statuses', 'hashtags']),
+  };
+
+  state = {
+    type: this.props.submittedType || 'all',
+  };
+
+  static getDerivedStateFromProps(props, state) {
+    if (props.submittedType !== state.type) {
+      return {
+        type: props.submittedType || 'all',
+      };
+    }
+
+    return null;
+  }
+
+  handleSelectAll = () => {
+    const { submittedType, dispatch } = this.props;
+
+    // If we originally searched for a specific type, we need to resubmit
+    // the query to get all types of results
+    if (submittedType) {
+      dispatch(submitSearch());
+    }
+
+    this.setState({ type: 'all' });
+  };
+
+  handleSelectAccounts = () => {
+    const { submittedType, dispatch } = this.props;
+
+    // If we originally searched for something else (but not everything),
+    // we need to resubmit the query for this specific type
+    if (submittedType !== 'accounts') {
+      dispatch(submitSearch('accounts'));
+    }
+
+    this.setState({ type: 'accounts' });
+  };
+
+  handleSelectHashtags = () => {
+    const { submittedType, dispatch } = this.props;
+
+    // If we originally searched for something else (but not everything),
+    // we need to resubmit the query for this specific type
+    if (submittedType !== 'hashtags') {
+      dispatch(submitSearch('hashtags'));
+    }
+
+    this.setState({ type: 'hashtags' });
+  };
+
+  handleSelectStatuses = () => {
+    const { submittedType, dispatch } = this.props;
+
+    // If we originally searched for something else (but not everything),
+    // we need to resubmit the query for this specific type
+    if (submittedType !== 'statuses') {
+      dispatch(submitSearch('statuses'));
+    }
+
+    this.setState({ type: 'statuses' });
+  };
+
+  handleLoadMoreAccounts = () => this._loadMore('accounts');
+  handleLoadMoreStatuses = () => this._loadMore('statuses');
+  handleLoadMoreHashtags = () => this._loadMore('hashtags');
+
+  _loadMore (type) {
+    const { dispatch } = this.props;
+    dispatch(expandSearch(type));
+  }
+
+  handleLoadMore = () => {
+    const { type } = this.state;
+
+    if (type !== 'all') {
+      this._loadMore(type);
+    }
+  };
+
+  render () {
+    const { intl, isLoading, q, results } = this.props;
+    const { type } = this.state;
+
+    // We request 1 more result than we display so we can tell if there'd be a next page
+    const hasMore = type !== 'all' ? results.get(type, ImmutableList()).size > INITIAL_PAGE_LIMIT && results.get(type).size % INITIAL_PAGE_LIMIT === 1 : false;
+
+    let filteredResults;
+
+    const accounts = results.get('accounts', ImmutableList());
+    const hashtags = results.get('hashtags', ImmutableList());
+    const statuses = results.get('statuses', ImmutableList());
+
+    switch(type) {
+    case 'all':
+      filteredResults = (accounts.size + hashtags.size + statuses.size) > 0 ? (
+        <>
+          {accounts.size > 0 && (
+            <SearchSection key='accounts' title={<><Icon id='users' icon={PeopleIcon} /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>} onClickMore={this.handleLoadMoreAccounts}>
+              {accounts.take(INITIAL_DISPLAY).map(id => <Account key={id} id={id} />)}
+            </SearchSection>
+          )}
+
+          {hashtags.size > 0 && (
+            <SearchSection key='hashtags' title={<><Icon id='hashtag' icon={TagIcon} /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></>} onClickMore={this.handleLoadMoreHashtags}>
+              {hashtags.take(INITIAL_DISPLAY).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
+            </SearchSection>
+          )}
+
+          {statuses.size > 0 && (
+            <SearchSection key='statuses' title={<><Icon id='quote-right' icon={FindInPageIcon} /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></>} onClickMore={this.handleLoadMoreStatuses}>
+              {statuses.take(INITIAL_DISPLAY).map(id => <Status key={id} id={id} contextType='explore' />)}
+            </SearchSection>
+          )}
+        </>
+      ) : [];
+      break;
+    case 'accounts':
+      filteredResults = renderAccounts(accounts);
+      break;
+    case 'hashtags':
+      filteredResults = renderHashtags(hashtags);
+      break;
+    case 'statuses':
+      filteredResults = renderStatuses(statuses);
+      break;
+    }
+
+    return (
+      <>
+        <div className='account__section-headline'>
+          <button onClick={this.handleSelectAll} className={type === 'all' ? 'active' : undefined}><FormattedMessage id='search_results.all' defaultMessage='All' /></button>
+          <button onClick={this.handleSelectAccounts} className={type === 'accounts' ? 'active' : undefined}><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></button>
+          <button onClick={this.handleSelectHashtags} className={type === 'hashtags' ? 'active' : undefined}><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></button>
+          <button onClick={this.handleSelectStatuses} className={type === 'statuses' ? 'active' : undefined}><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></button>
+        </div>
+
+        <div className='explore__search-results' data-nosnippet>
+          <ScrollableList
+            scrollKey='search-results'
+            isLoading={isLoading}
+            onLoadMore={this.handleLoadMore}
+            hasMore={hasMore}
+            emptyMessage={<FormattedMessage id='search_results.nothing_found' defaultMessage='Could not find anything for these search terms' />}
+            bindToDocument
+          >
+            {filteredResults}
+          </ScrollableList>
+        </div>
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.title, { q })}</title>
+        </Helmet>
+      </>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(Results));
diff --git a/app/javascript/mastodon/features/explore/statuses.jsx b/app/javascript/mastodon/features/explore/statuses.jsx
index 2fe2a7d81b..0054cda753 100644
--- a/app/javascript/mastodon/features/explore/statuses.jsx
+++ b/app/javascript/mastodon/features/explore/statuses.jsx
@@ -58,7 +58,7 @@ class Statuses extends PureComponent {
     return (
       <StatusList
         trackScroll
-        prepend={<DismissableBanner id='explore/statuses'><FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These posts from across the fediverse are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' /></DismissableBanner>}
+        prepend={<DismissableBanner id='explore/statuses'><FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' /></DismissableBanner>}
         alwaysPrepend
         timelineId='explore'
         contextType='explore'
diff --git a/app/javascript/mastodon/features/explore/suggestions.jsx b/app/javascript/mastodon/features/explore/suggestions.jsx
index b469a15252..101ec0d195 100644
--- a/app/javascript/mastodon/features/explore/suggestions.jsx
+++ b/app/javascript/mastodon/features/explore/suggestions.jsx
@@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
 
 import { withRouter } from 'react-router-dom';
 
+import ImmutablePropTypes from 'react-immutable-proptypes';
 import { connect } from 'react-redux';
 
 import { fetchSuggestions } from 'mastodon/actions/suggestions';
@@ -14,15 +15,15 @@ import { WithRouterPropTypes } from 'mastodon/utils/react_router';
 import { Card } from './components/card';
 
 const mapStateToProps = state => ({
-  suggestions: state.suggestions.items,
-  isLoading: state.suggestions.isLoading,
+  suggestions: state.getIn(['suggestions', 'items']),
+  isLoading: state.getIn(['suggestions', 'isLoading']),
 });
 
 class Suggestions extends PureComponent {
 
   static propTypes = {
     isLoading: PropTypes.bool,
-    suggestions: PropTypes.array,
+    suggestions: ImmutablePropTypes.list,
     dispatch: PropTypes.func.isRequired,
     ...WithRouterPropTypes,
   };
@@ -31,17 +32,17 @@ class Suggestions extends PureComponent {
     const { dispatch, suggestions, history } = this.props;
 
     // If we're navigating back to the screen, do not trigger a reload
-    if (history.action === 'POP' && suggestions.length > 0) {
+    if (history.action === 'POP' && suggestions.size > 0) {
       return;
     }
 
-    dispatch(fetchSuggestions());
+    dispatch(fetchSuggestions(true));
   }
 
   render () {
     const { isLoading, suggestions } = this.props;
 
-    if (!isLoading && suggestions.length === 0) {
+    if (!isLoading && suggestions.isEmpty()) {
       return (
         <div className='explore__suggestions scrollable scrollable--flex'>
           <div className='empty-column-indicator'>
@@ -55,9 +56,9 @@ class Suggestions extends PureComponent {
       <div className='explore__suggestions scrollable' data-nosnippet>
         {isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => (
           <Card
-            key={suggestion.account_id}
-            id={suggestion.account_id}
-            source={suggestion.sources[0]}
+            key={suggestion.get('account')}
+            id={suggestion.get('account')}
+            source={suggestion.getIn(['sources', 0])}
           />
         ))}
       </div>
diff --git a/app/javascript/mastodon/features/explore/tags.jsx b/app/javascript/mastodon/features/explore/tags.jsx
index b803d5fa1b..90cf3c32a7 100644
--- a/app/javascript/mastodon/features/explore/tags.jsx
+++ b/app/javascript/mastodon/features/explore/tags.jsx
@@ -44,7 +44,7 @@ class Tags extends PureComponent {
 
     const banner = (
       <DismissableBanner id='explore/tags'>
-        <FormattedMessage id='dismissable_banner.explore_tags' defaultMessage='These hashtags are gaining traction on the fediverse today. Hashtags that are used by more different people are ranked higher.' />
+        <FormattedMessage id='dismissable_banner.explore_tags' defaultMessage='These are hashtags that are gaining traction on the social web today. Hashtags that are used by more different people are ranked higher.' />
       </DismissableBanner>
     );
 
diff --git a/app/javascript/mastodon/features/favourited_statuses/index.jsx b/app/javascript/mastodon/features/favourited_statuses/index.jsx
index 9049a20f05..f7d6d14178 100644
--- a/app/javascript/mastodon/features/favourited_statuses/index.jsx
+++ b/app/javascript/mastodon/features/favourited_statuses/index.jsx
@@ -101,7 +101,6 @@ class Favourites extends ImmutablePureComponent {
           onLoadMore={this.handleLoadMore}
           emptyMessage={emptyMessage}
           bindToDocument={!multiColumn}
-          timelineId='favourites'
         />
 
         <Helmet>
diff --git a/app/javascript/mastodon/features/favourited_statuses/index.tsx b/app/javascript/mastodon/features/favourited_statuses/index.tsx
deleted file mode 100644
index 908a8ae4a1..0000000000
--- a/app/javascript/mastodon/features/favourited_statuses/index.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import { useEffect, useRef, useCallback } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-
-import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
-import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
-import {
-  fetchFavouritedStatuses,
-  expandFavouritedStatuses,
-} from 'mastodon/actions/favourites';
-import { Column } from 'mastodon/components/column';
-import type { ColumnRef } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import StatusList from 'mastodon/components/status_list';
-import { getStatusList } from 'mastodon/selectors';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  heading: { id: 'column.favourites', defaultMessage: 'Favorites' },
-});
-
-const Favourites: React.FC<{ columnId: string; multiColumn: boolean }> = ({
-  columnId,
-  multiColumn,
-}) => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-  const columnRef = useRef<ColumnRef>(null);
-  const statusIds = useAppSelector((state) =>
-    getStatusList(state, 'favourites'),
-  );
-  const isLoading = useAppSelector(
-    (state) =>
-      state.status_lists.getIn(['favourites', 'isLoading'], true) as boolean,
-  );
-  const hasMore = useAppSelector(
-    (state) => !!state.status_lists.getIn(['favourites', 'next']),
-  );
-
-  useEffect(() => {
-    dispatch(fetchFavouritedStatuses());
-  }, [dispatch]);
-
-  const handlePin = useCallback(() => {
-    if (columnId) {
-      dispatch(removeColumn(columnId));
-    } else {
-      dispatch(addColumn('FAVOURITES', {}));
-    }
-  }, [dispatch, columnId]);
-
-  const handleMove = useCallback(
-    (dir: number) => {
-      dispatch(moveColumn(columnId, dir));
-    },
-    [dispatch, columnId],
-  );
-
-  const handleHeaderClick = useCallback(() => {
-    columnRef.current?.scrollTop();
-  }, []);
-
-  const handleLoadMore = useCallback(() => {
-    dispatch(expandFavouritedStatuses());
-  }, [dispatch]);
-
-  const pinned = !!columnId;
-
-  const emptyMessage = (
-    <FormattedMessage
-      id='empty_column.favourited_statuses'
-      defaultMessage="You don't have any favorite posts yet. When you favorite one, it will show up here."
-    />
-  );
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      ref={columnRef}
-      label={intl.formatMessage(messages.heading)}
-    >
-      <ColumnHeader
-        icon='star'
-        iconComponent={StarIcon}
-        title={intl.formatMessage(messages.heading)}
-        onPin={handlePin}
-        onMove={handleMove}
-        onClick={handleHeaderClick}
-        pinned={pinned}
-        multiColumn={multiColumn}
-      />
-
-      <StatusList
-        trackScroll={!pinned}
-        statusIds={statusIds}
-        scrollKey={`favourited_statuses-${columnId}`}
-        hasMore={hasMore}
-        isLoading={isLoading}
-        onLoadMore={handleLoadMore}
-        emptyMessage={emptyMessage}
-        bindToDocument={!multiColumn}
-        timelineId='favourites'
-      />
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default Favourites;
diff --git a/app/javascript/mastodon/features/favourites/index.jsx b/app/javascript/mastodon/features/favourites/index.jsx
index 24ec767222..9eeb2a3f92 100644
--- a/app/javascript/mastodon/features/favourites/index.jsx
+++ b/app/javascript/mastodon/features/favourites/index.jsx
@@ -14,11 +14,11 @@ import { debounce } from 'lodash';
 
 import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
 import { fetchFavourites, expandFavourites } from 'mastodon/actions/interactions';
-import { Account } from 'mastodon/components/account';
 import ColumnHeader from 'mastodon/components/column_header';
 import { Icon }  from 'mastodon/components/icon';
 import { LoadingIndicator } from 'mastodon/components/loading_indicator';
 import ScrollableList from 'mastodon/components/scrollable_list';
+import AccountContainer from 'mastodon/containers/account_container';
 import Column from 'mastodon/features/ui/components/column';
 
 const messages = defineMessages({
@@ -89,7 +89,7 @@ class Favourites extends ImmutablePureComponent {
           bindToDocument={!multiColumn}
         >
           {accountIds.map(id =>
-            <Account key={id} id={id} />,
+            <AccountContainer key={id} id={id} withNote={false} />,
           )}
         </ScrollableList>
 
diff --git a/app/javascript/mastodon/features/firehose/index.jsx b/app/javascript/mastodon/features/firehose/index.jsx
index 9a750ae8be..aca088c641 100644
--- a/app/javascript/mastodon/features/firehose/index.jsx
+++ b/app/javascript/mastodon/features/firehose/index.jsx
@@ -133,7 +133,7 @@ const Firehose = ({ feedType, multiColumn }) => {
     <DismissableBanner id='public_timeline'>
       <FormattedMessage
         id='dismissable_banner.public_timeline'
-        defaultMessage='These are the most recent public posts from people on the fediverse that people on {domain} follow.'
+        defaultMessage='These are the most recent public posts from people on the social web that people on {domain} follow.'
         values={{ domain }}
       />
     </DismissableBanner>
diff --git a/app/javascript/mastodon/features/follow_requests/index.jsx b/app/javascript/mastodon/features/follow_requests/index.jsx
index 7d651f2ca6..a8f40a31d0 100644
--- a/app/javascript/mastodon/features/follow_requests/index.jsx
+++ b/app/javascript/mastodon/features/follow_requests/index.jsx
@@ -68,7 +68,7 @@ class FollowRequests extends ImmutablePureComponent {
     );
 
     return (
-      <Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
+      <Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)}>
         <ScrollableList
           scrollKey='follow_requests'
           onLoadMore={this.handleLoadMore}
diff --git a/app/javascript/mastodon/features/followed_tags/index.jsx b/app/javascript/mastodon/features/followed_tags/index.jsx
new file mode 100644
index 0000000000..21248e6de9
--- /dev/null
+++ b/app/javascript/mastodon/features/followed_tags/index.jsx
@@ -0,0 +1,95 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { debounce } from 'lodash';
+
+import TagIcon from '@/material-icons/400-24px/tag.svg?react';
+import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags';
+import ColumnHeader from 'mastodon/components/column_header';
+import { Hashtag } from 'mastodon/components/hashtag';
+import ScrollableList from 'mastodon/components/scrollable_list';
+import Column from 'mastodon/features/ui/components/column';
+
+const messages = defineMessages({
+  heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' },
+});
+
+const mapStateToProps = state => ({
+  hashtags: state.getIn(['followed_tags', 'items']),
+  isLoading: state.getIn(['followed_tags', 'isLoading'], true),
+  hasMore: !!state.getIn(['followed_tags', 'next']),
+});
+
+class FollowedTags extends ImmutablePureComponent {
+
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    hashtags: ImmutablePropTypes.list,
+    isLoading: PropTypes.bool,
+    hasMore: PropTypes.bool,
+    multiColumn: PropTypes.bool,
+  };
+
+  componentDidMount() {
+    this.props.dispatch(fetchFollowedHashtags());
+  }
+
+  handleLoadMore = debounce(() => {
+    this.props.dispatch(expandFollowedHashtags());
+  }, 300, { leading: true });
+
+  render () {
+    const { intl, hashtags, isLoading, hasMore, multiColumn } = this.props;
+
+    const emptyMessage = <FormattedMessage id='empty_column.followed_tags' defaultMessage='You have not followed any hashtags yet. When you do, they will show up here.' />;
+
+    return (
+      <Column bindToDocument={!multiColumn}>
+        <ColumnHeader
+          icon='hashtag'
+          iconComponent={TagIcon}
+          title={intl.formatMessage(messages.heading)}
+          showBackButton
+          multiColumn={multiColumn}
+        />
+
+        <ScrollableList
+          scrollKey='followed_tags'
+          emptyMessage={emptyMessage}
+          hasMore={hasMore}
+          isLoading={isLoading}
+          onLoadMore={this.handleLoadMore}
+          bindToDocument={!multiColumn}
+        >
+          {hashtags.map((hashtag) => (
+            <Hashtag
+              key={hashtag.get('name')}
+              name={hashtag.get('name')}
+              to={`/tags/${hashtag.get('name')}`}
+              withGraph={false}
+              // Taken from ImmutableHashtag. Should maybe refactor ImmutableHashtag to accept more options?
+              people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
+              history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
+            />
+          ))}
+        </ScrollableList>
+
+        <Helmet>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(FollowedTags));
diff --git a/app/javascript/mastodon/features/followed_tags/index.tsx b/app/javascript/mastodon/features/followed_tags/index.tsx
deleted file mode 100644
index 21d63a6fec..0000000000
--- a/app/javascript/mastodon/features/followed_tags/index.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import { useEffect, useState, useCallback, useRef } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-
-import { isFulfilled } from '@reduxjs/toolkit';
-
-import TagIcon from '@/material-icons/400-24px/tag.svg?react';
-import { unfollowHashtag } from 'mastodon/actions/tags_typed';
-import { apiGetFollowedTags } from 'mastodon/api/tags';
-import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
-import { Button } from 'mastodon/components/button';
-import { Column } from 'mastodon/components/column';
-import type { ColumnRef } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { Hashtag } from 'mastodon/components/hashtag';
-import ScrollableList from 'mastodon/components/scrollable_list';
-import { useAppDispatch } from 'mastodon/store';
-
-const messages = defineMessages({
-  heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' },
-});
-
-const FollowedTag: React.FC<{
-  tag: ApiHashtagJSON;
-  onUnfollow: (arg0: string) => void;
-}> = ({ tag, onUnfollow }) => {
-  const dispatch = useAppDispatch();
-  const tagId = tag.name;
-
-  const handleClick = useCallback(() => {
-    void dispatch(unfollowHashtag({ tagId })).then((result) => {
-      if (isFulfilled(result)) {
-        onUnfollow(tagId);
-      }
-
-      return '';
-    });
-  }, [dispatch, onUnfollow, tagId]);
-
-  const people =
-    parseInt(tag.history[0].accounts) +
-    parseInt(tag.history[1]?.accounts ?? '');
-
-  return (
-    <Hashtag
-      name={tag.name}
-      to={`/tags/${tag.name}`}
-      withGraph={false}
-      people={people}
-    >
-      <Button onClick={handleClick}>
-        <FormattedMessage id='account.unfollow' defaultMessage='Unfollow' />
-      </Button>
-    </Hashtag>
-  );
-};
-
-const FollowedTags: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
-  const intl = useIntl();
-  const [tags, setTags] = useState<ApiHashtagJSON[]>([]);
-  const [loading, setLoading] = useState(false);
-  const [next, setNext] = useState<string | undefined>();
-  const hasMore = !!next;
-  const columnRef = useRef<ColumnRef>(null);
-
-  useEffect(() => {
-    setLoading(true);
-
-    void apiGetFollowedTags()
-      .then(({ tags, links }) => {
-        const next = links.refs.find((link) => link.rel === 'next');
-
-        setTags(tags);
-        setLoading(false);
-        setNext(next?.uri);
-
-        return '';
-      })
-      .catch(() => {
-        setLoading(false);
-      });
-  }, [setTags, setLoading, setNext]);
-
-  const handleLoadMore = useCallback(() => {
-    setLoading(true);
-
-    void apiGetFollowedTags(next)
-      .then(({ tags, links }) => {
-        const next = links.refs.find((link) => link.rel === 'next');
-
-        setLoading(false);
-        setTags((previousTags) => [...previousTags, ...tags]);
-        setNext(next?.uri);
-
-        return '';
-      })
-      .catch(() => {
-        setLoading(false);
-      });
-  }, [setTags, setLoading, setNext, next]);
-
-  const handleUnfollow = useCallback(
-    (tagId: string) => {
-      setTags((tags) => tags.filter((tag) => tag.name !== tagId));
-    },
-    [setTags],
-  );
-
-  const handleHeaderClick = useCallback(() => {
-    columnRef.current?.scrollTop();
-  }, []);
-
-  const emptyMessage = (
-    <FormattedMessage
-      id='empty_column.followed_tags'
-      defaultMessage='You have not followed any hashtags yet. When you do, they will show up here.'
-    />
-  );
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      ref={columnRef}
-      label={intl.formatMessage(messages.heading)}
-    >
-      <ColumnHeader
-        icon='hashtag'
-        iconComponent={TagIcon}
-        title={intl.formatMessage(messages.heading)}
-        onClick={handleHeaderClick}
-        multiColumn={multiColumn}
-        showBackButton
-      />
-
-      <ScrollableList
-        scrollKey='followed_tags'
-        emptyMessage={emptyMessage}
-        hasMore={hasMore}
-        isLoading={loading}
-        showLoading={loading && tags.length === 0}
-        onLoadMore={handleLoadMore}
-        trackScroll={!multiColumn}
-        bindToDocument={!multiColumn}
-      >
-        {tags.map((tag) => (
-          <FollowedTag key={tag.name} tag={tag} onUnfollow={handleUnfollow} />
-        ))}
-      </ScrollableList>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default FollowedTags;
diff --git a/app/javascript/mastodon/features/followers/index.jsx b/app/javascript/mastodon/features/followers/index.jsx
index d72ee3d96e..cfd6e9fdc1 100644
--- a/app/javascript/mastodon/features/followers/index.jsx
+++ b/app/javascript/mastodon/features/followers/index.jsx
@@ -8,13 +8,11 @@ import { connect } from 'react-redux';
 
 import { debounce } from 'lodash';
 
-import { Account } from 'mastodon/components/account';
 import { TimelineHint } from 'mastodon/components/timeline_hint';
-import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
 import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
 import { isHideItem, me } from 'mastodon/initial_state';
 import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
-import { getAccountHidden } from 'mastodon/selectors/accounts';
+import { getAccountHidden } from 'mastodon/selectors';
 import { useAppSelector } from 'mastodon/store';
 
 import {
@@ -26,7 +24,9 @@ import {
 import { ColumnBackButton } from '../../components/column_back_button';
 import { LoadingIndicator } from '../../components/loading_indicator';
 import ScrollableList from '../../components/scrollable_list';
+import AccountContainer from '../../containers/account_container';
 import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
+import HeaderContainer from '../account_timeline/containers/header_container';
 import Column from '../ui/components/column';
 
 const mapStateToProps = (state, { params: { acct, id } }) => {
@@ -172,14 +172,14 @@ class Followers extends ImmutablePureComponent {
           hasMore={!forceEmptyState && hasMore}
           isLoading={isLoading}
           onLoadMore={this.handleLoadMore}
-          prepend={<AccountHeader accountId={this.props.accountId} hideTabs />}
+          prepend={<HeaderContainer accountId={this.props.accountId} hideTabs />}
           alwaysPrepend
           append={remoteMessage}
           emptyMessage={emptyMessage}
           bindToDocument={!multiColumn}
         >
           {forceEmptyState ? [] : accountIds.map(id =>
-            <Account key={id} id={id} />,
+            <AccountContainer key={id} id={id} withNote={false} />,
           )}
         </ScrollableList>
       </Column>
diff --git a/app/javascript/mastodon/features/following/index.jsx b/app/javascript/mastodon/features/following/index.jsx
index fc2cea763f..6ebc8e41d6 100644
--- a/app/javascript/mastodon/features/following/index.jsx
+++ b/app/javascript/mastodon/features/following/index.jsx
@@ -8,13 +8,11 @@ import { connect } from 'react-redux';
 
 import { debounce } from 'lodash';
 
-import { Account } from 'mastodon/components/account';
 import { TimelineHint } from 'mastodon/components/timeline_hint';
-import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
 import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
 import { isHideItem, me } from 'mastodon/initial_state';
 import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
-import { getAccountHidden } from 'mastodon/selectors/accounts';
+import { getAccountHidden } from 'mastodon/selectors';
 import { useAppSelector } from 'mastodon/store';
 
 import {
@@ -26,7 +24,9 @@ import {
 import { ColumnBackButton } from '../../components/column_back_button';
 import { LoadingIndicator } from '../../components/loading_indicator';
 import ScrollableList from '../../components/scrollable_list';
+import AccountContainer from '../../containers/account_container';
 import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
+import HeaderContainer from '../account_timeline/containers/header_container';
 import Column from '../ui/components/column';
 
 const mapStateToProps = (state, { params: { acct, id } }) => {
@@ -170,14 +170,14 @@ class Following extends ImmutablePureComponent {
           hasMore={!forceEmptyState && hasMore}
           isLoading={isLoading}
           onLoadMore={this.handleLoadMore}
-          prepend={<AccountHeader accountId={this.props.accountId} hideTabs />}
+          prepend={<HeaderContainer accountId={this.props.accountId} hideTabs />}
           alwaysPrepend
           append={remoteMessage}
           emptyMessage={emptyMessage}
           bindToDocument={!multiColumn}
         >
           {forceEmptyState ? [] : filteredAccountIds.map(id =>
-            <Account key={id} id={id} />,
+            <AccountContainer key={id} id={id} withNote={false} />,
           )}
         </ScrollableList>
       </Column>
diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.jsx b/app/javascript/mastodon/features/getting_started/components/announcements.jsx
index f5f593860f..3c0b53b9e7 100644
--- a/app/javascript/mastodon/features/getting_started/components/announcements.jsx
+++ b/app/javascript/mastodon/features/getting_started/components/announcements.jsx
@@ -1,5 +1,5 @@
 import PropTypes from 'prop-types';
-import { PureComponent, useCallback, useMemo } from 'react';
+import { PureComponent } from 'react';
 
 import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
 
@@ -9,7 +9,8 @@ import { withRouter } from 'react-router-dom';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
-import { animated, useTransition } from '@react-spring/web';
+import TransitionMotion from 'react-motion/lib/TransitionMotion';
+import spring from 'react-motion/lib/spring';
 import ReactSwipeableViews from 'react-swipeable-views';
 
 import elephantUIPlane from '@/images/elephant_ui_plane.svg';
@@ -84,7 +85,7 @@ class ContentWithRouter extends ImmutablePureComponent {
       }
 
       link.setAttribute('target', '_blank');
-      link.setAttribute('rel', 'noopener');
+      link.setAttribute('rel', 'noopener noreferrer');
     }
   }
 
@@ -238,76 +239,72 @@ class Reaction extends ImmutablePureComponent {
     }
 
     return (
-      <animated.button className={classNames('reactions-bar__item', { active: reaction.get('me') })} onClick={this.handleClick} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} title={`:${shortCode}:`} style={this.props.style}>
+      <button className={classNames('reactions-bar__item', { active: reaction.get('me') })} onClick={this.handleClick} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} title={`:${shortCode}:`} style={this.props.style}>
         <span className='reactions-bar__item__emoji'><Emoji hovered={this.state.hovered} emoji={reaction.get('name')} emojiMap={this.props.emojiMap} /></span>
         <span className='reactions-bar__item__count'><AnimatedNumber value={reaction.get('count')} /></span>
-      </animated.button>
+      </button>
     );
   }
 
 }
 
-const ReactionsBar = ({
-  announcementId,
-  reactions,
-  emojiMap,
-  addReaction,
-  removeReaction,
-}) => {
-  const visibleReactions = useMemo(() => reactions.filter(x => x.get('count') > 0).toArray(), [reactions]);
+class ReactionsBar extends ImmutablePureComponent {
 
-  const handleEmojiPick = useCallback((emoji) => {
-    addReaction(announcementId, emoji.native.replaceAll(/:/g, ''));
-  }, [addReaction, announcementId]);
+  static propTypes = {
+    announcementId: PropTypes.string.isRequired,
+    reactions: ImmutablePropTypes.list.isRequired,
+    addReaction: PropTypes.func.isRequired,
+    removeReaction: PropTypes.func.isRequired,
+    emojiMap: ImmutablePropTypes.map.isRequired,
+  };
 
-  const transitions = useTransition(visibleReactions, {
-    from: {
-      scale: 0,
-    },
-    enter: {
-      scale: 1,
-    },
-    leave: {
-      scale: 0,
-    },
-    immediate: reduceMotion,
-    keys: visibleReactions.map(x => x.get('name')),
-  });
+  handleEmojiPick = data => {
+    const { addReaction, announcementId } = this.props;
+    addReaction(announcementId, data.native.replace(/:/g, ''));
+  };
 
-  return (
-    <div
-      className={classNames('reactions-bar', {
-        'reactions-bar--empty': visibleReactions.length === 0
-      })}
-    >
-      {transitions(({ scale }, reaction) => (
-        <Reaction
-          key={reaction.get('name')}
-          reaction={reaction}
-          style={{ transform: scale.to((s) => `scale(${s})`) }}
-          addReaction={addReaction}
-          removeReaction={removeReaction}
-          announcementId={announcementId}
-          emojiMap={emojiMap}
-        />
-      ))}
+  willEnter () {
+    return { scale: reduceMotion ? 1 : 0 };
+  }
 
-      {visibleReactions.length < 8 && (
-        <EmojiPickerDropdown
-          onPickEmoji={handleEmojiPick}
-          button={<Icon id='plus' icon={AddIcon} />}
-        />
-      )}
-    </div>
-  );
-};
-ReactionsBar.propTypes = {
-  announcementId: PropTypes.string.isRequired,
-  reactions: ImmutablePropTypes.list.isRequired,
-  addReaction: PropTypes.func.isRequired,
-  removeReaction: PropTypes.func.isRequired,
-  emojiMap: ImmutablePropTypes.map.isRequired,
-};
+  willLeave () {
+    return { scale: reduceMotion ? 0 : spring(0, { stiffness: 170, damping: 26 }) };
+  }
+
+  render () {
+    const { reactions } = this.props;
+    const visibleReactions = reactions.filter(x => x.get('count') > 0);
+
+    const styles = visibleReactions.map(reaction => ({
+      key: reaction.get('name'),
+      data: reaction,
+      style: { scale: reduceMotion ? 1 : spring(1, { stiffness: 150, damping: 13 }) },
+    })).toArray();
+
+    return (
+      <TransitionMotion styles={styles} willEnter={this.willEnter} willLeave={this.willLeave}>
+        {items => (
+          <div className={classNames('reactions-bar', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
+            {items.map(({ key, data, style }) => (
+              <Reaction
+                key={key}
+                reaction={data}
+                style={{ transform: `scale(${style.scale})`, position: style.scale < 0.5 ? 'absolute' : 'static' }}
+                announcementId={this.props.announcementId}
+                addReaction={this.props.addReaction}
+                removeReaction={this.props.removeReaction}
+                emojiMap={this.props.emojiMap}
+              />
+            ))}
+
+            {visibleReactions.size < 8 && <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={<Icon id='plus' icon={AddIcon} />} />}
+          </div>
+        )}
+      </TransitionMotion>
+    );
+  }
+
+}
 
 class Announcement extends ImmutablePureComponent {
 
@@ -338,29 +335,15 @@ class Announcement extends ImmutablePureComponent {
     const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at'));
     const now = new Date();
     const hasTimeRange = startsAt && endsAt;
+    const skipYear = hasTimeRange && startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear();
+    const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
     const skipTime = announcement.get('all_day');
 
-    let timestamp = null;
-    if (hasTimeRange) {
-      const skipYear = startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear();
-      const skipEndDate = startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
-      timestamp = (
-        <>
-          <FormattedDate value={startsAt} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} />
-        </>
-      );
-    } else {
-      const publishedAt = new Date(announcement.get('published_at'));
-      timestamp = (
-        <FormattedDate value={publishedAt} year={publishedAt.getFullYear() === now.getFullYear() ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} />
-      );
-    }
-
     return (
       <div className='announcements__item'>
         <strong className='announcements__item__range'>
           <FormattedMessage id='announcement.announcement' defaultMessage='Announcement' />
-          <span> · {timestamp}</span>
+          {hasTimeRange && <span> · <FormattedDate value={startsAt} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /></span>}
         </strong>
 
         <Content announcement={announcement} />
diff --git a/app/javascript/mastodon/features/getting_started/index.jsx b/app/javascript/mastodon/features/getting_started/index.jsx
index 48ce17881a..c5987ada8d 100644
--- a/app/javascript/mastodon/features/getting_started/index.jsx
+++ b/app/javascript/mastodon/features/getting_started/index.jsx
@@ -14,8 +14,6 @@ import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?re
 import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
 import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
 import ModerationIcon from '@/material-icons/400-24px/gavel.svg?react';
-import HashtagIcon from '@/material-icons/400-24px/tag.svg?react';
-import Directory from '@/material-icons/400-24px/group.svg?react';
 import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
 import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
 import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
@@ -29,7 +27,7 @@ import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
 import { fetchFollowRequests } from 'mastodon/actions/accounts';
 import Column from 'mastodon/components/column';
 import ColumnHeader from 'mastodon/components/column_header';
-import { LinkFooter } from 'mastodon/features/ui/components/link_footer';
+import LinkFooter from 'mastodon/features/ui/components/link_footer';
 import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
 import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions';
 
@@ -44,8 +42,6 @@ const messages = defineMessages({
   home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
   notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
   public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
-  followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed Hashtags' },
-  directory: { id: 'navigation_bar.directory', defaultMessage: 'Profile directory' },
   settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
   community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
   deep_timeline: { id: 'navigation_bar.deep_timeline', defaultMessage: 'Deep timeline' },
@@ -148,9 +144,8 @@ class GettingStarted extends ImmutablePureComponent {
         <ColumnLink key='direct' icon='at' iconComponent={AlternateEmailIcon} text={intl.formatMessage(messages.direct)} to='/conversations' />,
         <ColumnLink key='bookmark' icon='bookmarks' iconComponent={BookmarksIcon} text={intl.formatMessage(messages.bookmarks)} to='/bookmark_categories' />,
         <ColumnLink key='favourites' icon='star' iconComponent={StarIcon} text={intl.formatMessage(messages.favourites)} to='/favourites' />,
-        <ColumnLink key='followed_tags' icon='tag' iconComponent={followed_tagsIcon} text={intl.formatMessage(messages.followed_tags)} to='/followed_tags' />,
         <ColumnLink key='lists' icon='list-ul' iconComponent={ListAltIcon} text={intl.formatMessage(messages.lists)} to='/lists' />,
-        <ColumnLink key='antennas' icon='wifi' iconComponent={AntennaIcon} text={intl.formatMessage(messages.antennas)} to='/antennas' />,
+        <ColumnLink key='antennas' icon='wifi' iconComponent={AntennaIcon} text={intl.formatMessage(messages.antennas)} to='/antennasw' />,
         <ColumnLink key='circles' icon='user-circle' iconComponent={CirclesIcon} text={intl.formatMessage(messages.circles)} to='/circles' />,
       );
 
diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx
new file mode 100644
index 0000000000..d2fe8851f6
--- /dev/null
+++ b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx
@@ -0,0 +1,79 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+
+import { Button } from 'mastodon/components/button';
+import { ShortNumber } from 'mastodon/components/short_number';
+
+const messages = defineMessages({
+  followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' },
+  unfollowHashtag: { id: 'hashtag.unfollow', defaultMessage: 'Unfollow hashtag' },
+});
+
+const usesRenderer = (displayNumber, pluralReady) => (
+  <FormattedMessage
+    id='hashtag.counter_by_uses'
+    defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}}'
+    values={{
+      count: pluralReady,
+      counter: <strong>{displayNumber}</strong>,
+    }}
+  />
+);
+
+const peopleRenderer = (displayNumber, pluralReady) => (
+  <FormattedMessage
+    id='hashtag.counter_by_accounts'
+    defaultMessage='{count, plural, one {{counter} participant} other {{counter} participants}}'
+    values={{
+      count: pluralReady,
+      counter: <strong>{displayNumber}</strong>,
+    }}
+  />
+);
+
+const usesTodayRenderer = (displayNumber, pluralReady) => (
+  <FormattedMessage
+    id='hashtag.counter_by_uses_today'
+    defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}} today'
+    values={{
+      count: pluralReady,
+      counter: <strong>{displayNumber}</strong>,
+    }}
+  />
+);
+
+export const HashtagHeader = injectIntl(({ tag, intl, disabled, onClick }) => {
+  if (!tag) {
+    return null;
+  }
+
+  const [uses, people] = tag.get('history').reduce((arr, day) => [arr[0] + day.get('uses') * 1, arr[1] + day.get('accounts') * 1], [0, 0]);
+  const dividingCircle = <span aria-hidden>{' · '}</span>;
+
+  return (
+    <div className='hashtag-header'>
+      <div className='hashtag-header__header'>
+        <h1>#{tag.get('name')}</h1>
+        <Button onClick={onClick} text={intl.formatMessage(tag.get('following') ? messages.unfollowHashtag : messages.followHashtag)} disabled={disabled} />
+      </div>
+
+      <div>
+        <ShortNumber value={uses} renderer={usesRenderer} />
+        {dividingCircle}
+        <ShortNumber value={people} renderer={peopleRenderer} />
+        {dividingCircle}
+        <ShortNumber value={tag.getIn(['history', 0, 'uses']) * 1} renderer={usesTodayRenderer} />
+      </div>
+    </div>
+  );
+});
+
+HashtagHeader.propTypes = {
+  tag: ImmutablePropTypes.map,
+  disabled: PropTypes.bool,
+  onClick: PropTypes.func,
+  intl: PropTypes.object,
+};
diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx
deleted file mode 100644
index b7c1ea02d9..0000000000
--- a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import { useCallback, useMemo, useState, useEffect } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { isFulfilled } from '@reduxjs/toolkit';
-
-import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
-import {
-  fetchHashtag,
-  followHashtag,
-  unfollowHashtag,
-} from 'mastodon/actions/tags_typed';
-import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
-import { Button } from 'mastodon/components/button';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
-import { ShortNumber } from 'mastodon/components/short_number';
-import { useIdentity } from 'mastodon/identity_context';
-import { PERMISSION_MANAGE_TAXONOMIES } from 'mastodon/permissions';
-import { useAppDispatch } from 'mastodon/store';
-
-const messages = defineMessages({
-  followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' },
-  unfollowHashtag: {
-    id: 'hashtag.unfollow',
-    defaultMessage: 'Unfollow hashtag',
-  },
-  adminModeration: {
-    id: 'hashtag.admin_moderation',
-    defaultMessage: 'Open moderation interface for #{name}',
-  },
-});
-
-const usesRenderer = (displayNumber: React.ReactNode, pluralReady: number) => (
-  <FormattedMessage
-    id='hashtag.counter_by_uses'
-    defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}}'
-    values={{
-      count: pluralReady,
-      counter: <strong>{displayNumber}</strong>,
-    }}
-  />
-);
-
-const peopleRenderer = (
-  displayNumber: React.ReactNode,
-  pluralReady: number,
-) => (
-  <FormattedMessage
-    id='hashtag.counter_by_accounts'
-    defaultMessage='{count, plural, one {{counter} participant} other {{counter} participants}}'
-    values={{
-      count: pluralReady,
-      counter: <strong>{displayNumber}</strong>,
-    }}
-  />
-);
-
-const usesTodayRenderer = (
-  displayNumber: React.ReactNode,
-  pluralReady: number,
-) => (
-  <FormattedMessage
-    id='hashtag.counter_by_uses_today'
-    defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}} today'
-    values={{
-      count: pluralReady,
-      counter: <strong>{displayNumber}</strong>,
-    }}
-  />
-);
-
-export const HashtagHeader: React.FC<{
-  tagId: string;
-}> = ({ tagId }) => {
-  const intl = useIntl();
-  const { signedIn, permissions } = useIdentity();
-  const dispatch = useAppDispatch();
-  const [tag, setTag] = useState<ApiHashtagJSON>();
-
-  useEffect(() => {
-    void dispatch(fetchHashtag({ tagId })).then((result) => {
-      if (isFulfilled(result)) {
-        setTag(result.payload);
-      }
-
-      return '';
-    });
-  }, [dispatch, tagId, setTag]);
-
-  const menu = useMemo(() => {
-    const tmp = [];
-
-    if (
-      tag &&
-      signedIn &&
-      (permissions & PERMISSION_MANAGE_TAXONOMIES) ===
-        PERMISSION_MANAGE_TAXONOMIES
-    ) {
-      tmp.push({
-        text: intl.formatMessage(messages.adminModeration, { name: tag.id }),
-        href: `/admin/tags/${tag.id}`,
-      });
-    }
-
-    return tmp;
-  }, [signedIn, permissions, intl, tag]);
-
-  const handleFollow = useCallback(() => {
-    if (!signedIn || !tag) {
-      return;
-    }
-
-    if (tag.following) {
-      setTag((hashtag) => hashtag && { ...hashtag, following: false });
-
-      void dispatch(unfollowHashtag({ tagId })).then((result) => {
-        if (isFulfilled(result)) {
-          setTag(result.payload);
-        }
-
-        return '';
-      });
-    } else {
-      setTag((hashtag) => hashtag && { ...hashtag, following: true });
-
-      void dispatch(followHashtag({ tagId })).then((result) => {
-        if (isFulfilled(result)) {
-          setTag(result.payload);
-        }
-
-        return '';
-      });
-    }
-  }, [dispatch, setTag, signedIn, tag, tagId]);
-
-  if (!tag) {
-    return null;
-  }
-
-  const [uses, people] = tag.history.reduce(
-    (arr, day) => [
-      arr[0] + parseInt(day.uses),
-      arr[1] + parseInt(day.accounts),
-    ],
-    [0, 0],
-  );
-  const dividingCircle = <span aria-hidden>{' · '}</span>;
-
-  return (
-    <div className='hashtag-header'>
-      <div className='hashtag-header__header'>
-        <h1>#{tag.name}</h1>
-
-        <div className='hashtag-header__header__buttons'>
-          {menu.length > 0 && (
-            <Dropdown
-              disabled={menu.length === 0}
-              items={menu}
-              icon='ellipsis-v'
-              iconComponent={MoreHorizIcon}
-            />
-          )}
-
-          <Button
-            onClick={handleFollow}
-            text={intl.formatMessage(
-              tag.following ? messages.unfollowHashtag : messages.followHashtag,
-            )}
-            disabled={!signedIn}
-          />
-        </div>
-      </div>
-
-      <div>
-        <ShortNumber value={uses} renderer={usesRenderer} />
-        {dividingCircle}
-        <ShortNumber value={people} renderer={peopleRenderer} />
-        {dividingCircle}
-        <ShortNumber
-          value={parseInt(tag.history[0].uses)}
-          renderer={usesTodayRenderer}
-        />
-      </div>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.jsx b/app/javascript/mastodon/features/hashtag_timeline/index.jsx
index ab3c32e6a7..42a668859e 100644
--- a/app/javascript/mastodon/features/hashtag_timeline/index.jsx
+++ b/app/javascript/mastodon/features/hashtag_timeline/index.jsx
@@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
 
 import { Helmet } from 'react-helmet';
 
+import ImmutablePropTypes from 'react-immutable-proptypes';
 import { connect } from 'react-redux';
 
 import { isEqual } from 'lodash';
@@ -12,6 +13,7 @@ import { isEqual } from 'lodash';
 import TagIcon from '@/material-icons/400-24px/tag.svg?react';
 import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
 import { connectHashtagStream } from 'mastodon/actions/streaming';
+import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/tags';
 import { expandHashtagTimeline, clearTimeline } from 'mastodon/actions/timelines';
 import Column from 'mastodon/components/column';
 import ColumnHeader from 'mastodon/components/column_header';
@@ -24,6 +26,7 @@ import ColumnSettingsContainer from './containers/column_settings_container';
 
 const mapStateToProps = (state, props) => ({
   hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}${props.params.local ? ':local' : ''}`, 'unread']) > 0,
+  tag: state.getIn(['tags', props.params.id]),
 });
 
 class HashtagTimeline extends PureComponent {
@@ -35,6 +38,7 @@ class HashtagTimeline extends PureComponent {
     columnId: PropTypes.string,
     dispatch: PropTypes.func.isRequired,
     hasUnread: PropTypes.bool,
+    tag: ImmutablePropTypes.map,
     multiColumn: PropTypes.bool,
   };
 
@@ -126,6 +130,7 @@ class HashtagTimeline extends PureComponent {
 
     this._subscribe(dispatch, id, tags, local);
     dispatch(expandHashtagTimeline(id, { tags, local }));
+    dispatch(fetchHashtag(id));
   }
 
   componentDidMount () {
@@ -157,10 +162,27 @@ class HashtagTimeline extends PureComponent {
     dispatch(expandHashtagTimeline(id, { maxId, tags, local }));
   };
 
+  handleFollow = () => {
+    const { dispatch, params, tag } = this.props;
+    const { id } = params;
+    const { signedIn } = this.props.identity;
+
+    if (!signedIn) {
+      return;
+    }
+
+    if (tag.get('following')) {
+      dispatch(unfollowHashtag(id));
+    } else {
+      dispatch(followHashtag(id));
+    }
+  };
+
   render () {
-    const { hasUnread, columnId, multiColumn } = this.props;
+    const { hasUnread, columnId, multiColumn, tag } = this.props;
     const { id, local } = this.props.params;
     const pinned = !!columnId;
+    const { signedIn } = this.props.identity;
 
     return (
       <Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
@@ -180,7 +202,7 @@ class HashtagTimeline extends PureComponent {
         </ColumnHeader>
 
         <StatusListContainer
-          prepend={pinned ? null : <HashtagHeader tagId={id} />}
+          prepend={pinned ? null : <HashtagHeader tag={tag} disabled={!signedIn} onClick={this.handleFollow} />}
           alwaysPrepend
           trackScroll={!pinned}
           scrollKey={`hashtag_timeline-${columnId}`}
diff --git a/app/javascript/mastodon/features/home_timeline/components/column_settings.tsx b/app/javascript/mastodon/features/home_timeline/components/column_settings.tsx
index cb03d38847..3f0525fe57 100644
--- a/app/javascript/mastodon/features/home_timeline/components/column_settings.tsx
+++ b/app/javascript/mastodon/features/home_timeline/components/column_settings.tsx
@@ -17,7 +17,7 @@ export const ColumnSettings: React.FC = () => {
 
   const dispatch = useAppDispatch();
   const onChange = useCallback(
-    (key: string[], checked: boolean) => {
+    (key: string, checked: boolean) => {
       dispatch(changeSetting(['home', ...key], checked));
     },
     [dispatch],
diff --git a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.jsx b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.jsx
new file mode 100644
index 0000000000..3269b5a497
--- /dev/null
+++ b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.jsx
@@ -0,0 +1,217 @@
+import PropTypes from 'prop-types';
+import { useEffect, useCallback, useRef, useState } from 'react';
+
+import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
+
+import { Link } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { useDispatch, useSelector } from 'react-redux';
+
+import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
+import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+import InfoIcon from '@/material-icons/400-24px/info.svg?react';
+import { changeSetting } from 'mastodon/actions/settings';
+import { fetchSuggestions, dismissSuggestion } from 'mastodon/actions/suggestions';
+import { Avatar } from 'mastodon/components/avatar';
+import { DisplayName } from 'mastodon/components/display_name';
+import { FollowButton } from 'mastodon/components/follow_button';
+import { Icon } from 'mastodon/components/icon';
+import { IconButton } from 'mastodon/components/icon_button';
+import { VerifiedBadge } from 'mastodon/components/verified_badge';
+import { domain } from 'mastodon/initial_state';
+
+const messages = defineMessages({
+  follow: { id: 'account.follow', defaultMessage: 'Follow' },
+  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
+  previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
+  next: { id: 'lightbox.next', defaultMessage: 'Next' },
+  dismiss: { id: 'follow_suggestions.dismiss', defaultMessage: "Don't show again" },
+  friendsOfFriendsHint: { id: 'follow_suggestions.hints.friends_of_friends', defaultMessage: 'This profile is popular among the people you follow.' },
+  similarToRecentlyFollowedHint: { id: 'follow_suggestions.hints.similar_to_recently_followed', defaultMessage: 'This profile is similar to the profiles you have most recently followed.' },
+  featuredHint: { id: 'follow_suggestions.hints.featured', defaultMessage: 'This profile has been hand-picked by the {domain} team.' },
+  mostFollowedHint: { id: 'follow_suggestions.hints.most_followed', defaultMessage: 'This profile is one of the most followed on {domain}.'},
+  mostInteractionsHint: { id: 'follow_suggestions.hints.most_interactions', defaultMessage: 'This profile has been recently getting a lot of attention on {domain}.' },
+});
+
+const Source = ({ id }) => {
+  const intl = useIntl();
+
+  let label, hint;
+
+  switch (id) {
+  case 'friends_of_friends':
+    hint = intl.formatMessage(messages.friendsOfFriendsHint);
+    label = <FormattedMessage id='follow_suggestions.personalized_suggestion' defaultMessage='Personalized suggestion' />;
+    break;
+  case 'similar_to_recently_followed':
+    hint = intl.formatMessage(messages.similarToRecentlyFollowedHint);
+    label = <FormattedMessage id='follow_suggestions.personalized_suggestion' defaultMessage='Personalized suggestion' />;
+    break;
+  case 'featured':
+    hint = intl.formatMessage(messages.featuredHint, { domain });
+    label = <FormattedMessage id='follow_suggestions.curated_suggestion' defaultMessage='Staff pick' />;
+    break;
+  case 'most_followed':
+    hint = intl.formatMessage(messages.mostFollowedHint, { domain });
+    label = <FormattedMessage id='follow_suggestions.popular_suggestion' defaultMessage='Popular suggestion' />;
+    break;
+  case 'most_interactions':
+    hint = intl.formatMessage(messages.mostInteractionsHint, { domain });
+    label = <FormattedMessage id='follow_suggestions.popular_suggestion' defaultMessage='Popular suggestion' />;
+    break;
+  }
+
+  return (
+    <div className='inline-follow-suggestions__body__scrollable__card__text-stack__source' title={hint}>
+      <Icon icon={InfoIcon} />
+      {label}
+    </div>
+  );
+};
+
+Source.propTypes = {
+  id: PropTypes.oneOf(['friends_of_friends', 'similar_to_recently_followed', 'featured', 'most_followed', 'most_interactions']),
+};
+
+const Card = ({ id, sources }) => {
+  const intl = useIntl();
+  const account = useSelector(state => state.getIn(['accounts', id]));
+  const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at'));
+  const dispatch = useDispatch();
+
+  const handleDismiss = useCallback(() => {
+    dispatch(dismissSuggestion(id));
+  }, [id, dispatch]);
+
+  return (
+    <div className='inline-follow-suggestions__body__scrollable__card'>
+      <IconButton iconComponent={CloseIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
+
+      <div className='inline-follow-suggestions__body__scrollable__card__avatar'>
+        <Link to={`/@${account.get('acct')}`}><Avatar account={account} size={72} /></Link>
+      </div>
+
+      <div className='inline-follow-suggestions__body__scrollable__card__text-stack'>
+        <Link to={`/@${account.get('acct')}`}><DisplayName account={account} /></Link>
+        {firstVerifiedField ? <VerifiedBadge link={firstVerifiedField.get('value')} /> : <Source id={sources.get(0)} />}
+      </div>
+
+      <FollowButton accountId={id} />
+    </div>
+  );
+};
+
+Card.propTypes = {
+  id: PropTypes.string.isRequired,
+  sources: ImmutablePropTypes.list,
+};
+
+const DISMISSIBLE_ID = 'home/follow-suggestions';
+
+export const InlineFollowSuggestions = ({ hidden }) => {
+  const intl = useIntl();
+  const dispatch = useDispatch();
+  const suggestions = useSelector(state => state.getIn(['suggestions', 'items']));
+  const isLoading = useSelector(state => state.getIn(['suggestions', 'isLoading']));
+  const dismissed = useSelector(state => state.getIn(['settings', 'dismissed_banners', DISMISSIBLE_ID]));
+  const bodyRef = useRef();
+  const [canScrollLeft, setCanScrollLeft] = useState(false);
+  const [canScrollRight, setCanScrollRight] = useState(true);
+
+  useEffect(() => {
+    dispatch(fetchSuggestions());
+  }, [dispatch]);
+
+  useEffect(() => {
+    if (!bodyRef.current) {
+      return;
+    }
+
+    if (getComputedStyle(bodyRef.current).direction === 'rtl') {
+      setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
+      setCanScrollRight(bodyRef.current.scrollLeft < 0);
+    } else {
+      setCanScrollLeft(bodyRef.current.scrollLeft > 0);
+      setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
+    }
+  }, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
+
+  const handleLeftNav = useCallback(() => {
+    bodyRef.current.scrollLeft -= 200;
+  }, [bodyRef]);
+
+  const handleRightNav = useCallback(() => {
+    bodyRef.current.scrollLeft += 200;
+  }, [bodyRef]);
+
+  const handleScroll = useCallback(() => {
+    if (!bodyRef.current) {
+      return;
+    }
+
+    if (getComputedStyle(bodyRef.current).direction === 'rtl') {
+      setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
+      setCanScrollRight(bodyRef.current.scrollLeft < 0);
+    } else {
+      setCanScrollLeft(bodyRef.current.scrollLeft > 0);
+      setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
+    }
+  }, [setCanScrollRight, setCanScrollLeft, bodyRef]);
+
+  const handleDismiss = useCallback(() => {
+    dispatch(changeSetting(['dismissed_banners', DISMISSIBLE_ID], true));
+  }, [dispatch]);
+
+  if (dismissed || (!isLoading && suggestions.isEmpty())) {
+    return null;
+  }
+
+  if (hidden) {
+    return (
+      <div className='inline-follow-suggestions' />
+    );
+  }
+
+  return (
+    <div className='inline-follow-suggestions'>
+      <div className='inline-follow-suggestions__header'>
+        <h3><FormattedMessage id='follow_suggestions.who_to_follow' defaultMessage='Who to follow' /></h3>
+
+        <div className='inline-follow-suggestions__header__actions'>
+          <button className='link-button' onClick={handleDismiss}><FormattedMessage id='follow_suggestions.dismiss' defaultMessage="Don't show again" /></button>
+          <Link to='/explore/suggestions' className='link-button'><FormattedMessage id='follow_suggestions.view_all' defaultMessage='View all' /></Link>
+        </div>
+      </div>
+
+      <div className='inline-follow-suggestions__body'>
+        <div className='inline-follow-suggestions__body__scrollable' ref={bodyRef} onScroll={handleScroll}>
+          {suggestions.map(suggestion => (
+            <Card
+              key={suggestion.get('account')}
+              id={suggestion.get('account')}
+              sources={suggestion.get('sources')}
+            />
+          ))}
+        </div>
+
+        {canScrollLeft && (
+          <button className='inline-follow-suggestions__body__scroll-button left' onClick={handleLeftNav} aria-label={intl.formatMessage(messages.previous)}>
+            <div className='inline-follow-suggestions__body__scroll-button__icon'><Icon icon={ChevronLeftIcon} /></div>
+          </button>
+        )}
+
+        {canScrollRight && (
+          <button className='inline-follow-suggestions__body__scroll-button right' onClick={handleRightNav} aria-label={intl.formatMessage(messages.next)}>
+            <div className='inline-follow-suggestions__body__scroll-button__icon'><Icon icon={ChevronRightIcon} /></div>
+          </button>
+        )}
+      </div>
+    </div>
+  );
+};
+
+InlineFollowSuggestions.propTypes = {
+  hidden: PropTypes.bool,
+};
diff --git a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx
deleted file mode 100644
index 777ffb4e18..0000000000
--- a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx
+++ /dev/null
@@ -1,326 +0,0 @@
-import { useEffect, useCallback, useRef, useState } from 'react';
-
-import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
-
-import { Link } from 'react-router-dom';
-
-import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
-import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
-import CloseIcon from '@/material-icons/400-24px/close.svg?react';
-import InfoIcon from '@/material-icons/400-24px/info.svg?react';
-import { changeSetting } from 'mastodon/actions/settings';
-import {
-  fetchSuggestions,
-  dismissSuggestion,
-} from 'mastodon/actions/suggestions';
-import type { ApiSuggestionSourceJSON } from 'mastodon/api_types/suggestions';
-import { Avatar } from 'mastodon/components/avatar';
-import { DisplayName } from 'mastodon/components/display_name';
-import { FollowButton } from 'mastodon/components/follow_button';
-import { Icon } from 'mastodon/components/icon';
-import { IconButton } from 'mastodon/components/icon_button';
-import { VerifiedBadge } from 'mastodon/components/verified_badge';
-import { domain } from 'mastodon/initial_state';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  follow: { id: 'account.follow', defaultMessage: 'Follow' },
-  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
-  previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
-  next: { id: 'lightbox.next', defaultMessage: 'Next' },
-  dismiss: {
-    id: 'follow_suggestions.dismiss',
-    defaultMessage: "Don't show again",
-  },
-  friendsOfFriendsHint: {
-    id: 'follow_suggestions.hints.friends_of_friends',
-    defaultMessage: 'This profile is popular among the people you follow.',
-  },
-  similarToRecentlyFollowedHint: {
-    id: 'follow_suggestions.hints.similar_to_recently_followed',
-    defaultMessage:
-      'This profile is similar to the profiles you have most recently followed.',
-  },
-  featuredHint: {
-    id: 'follow_suggestions.hints.featured',
-    defaultMessage: 'This profile has been hand-picked by the {domain} team.',
-  },
-  mostFollowedHint: {
-    id: 'follow_suggestions.hints.most_followed',
-    defaultMessage: 'This profile is one of the most followed on {domain}.',
-  },
-  mostInteractionsHint: {
-    id: 'follow_suggestions.hints.most_interactions',
-    defaultMessage:
-      'This profile has been recently getting a lot of attention on {domain}.',
-  },
-});
-
-const Source: React.FC<{
-  id: ApiSuggestionSourceJSON;
-}> = ({ id }) => {
-  const intl = useIntl();
-
-  let label, hint;
-
-  switch (id) {
-    case 'friends_of_friends':
-      hint = intl.formatMessage(messages.friendsOfFriendsHint);
-      label = (
-        <FormattedMessage
-          id='follow_suggestions.personalized_suggestion'
-          defaultMessage='Personalized suggestion'
-        />
-      );
-      break;
-    case 'similar_to_recently_followed':
-      hint = intl.formatMessage(messages.similarToRecentlyFollowedHint);
-      label = (
-        <FormattedMessage
-          id='follow_suggestions.personalized_suggestion'
-          defaultMessage='Personalized suggestion'
-        />
-      );
-      break;
-    case 'featured':
-      hint = intl.formatMessage(messages.featuredHint, { domain });
-      label = (
-        <FormattedMessage
-          id='follow_suggestions.curated_suggestion'
-          defaultMessage='Staff pick'
-        />
-      );
-      break;
-    case 'most_followed':
-      hint = intl.formatMessage(messages.mostFollowedHint, { domain });
-      label = (
-        <FormattedMessage
-          id='follow_suggestions.popular_suggestion'
-          defaultMessage='Popular suggestion'
-        />
-      );
-      break;
-    case 'most_interactions':
-      hint = intl.formatMessage(messages.mostInteractionsHint, { domain });
-      label = (
-        <FormattedMessage
-          id='follow_suggestions.popular_suggestion'
-          defaultMessage='Popular suggestion'
-        />
-      );
-      break;
-  }
-
-  return (
-    <div
-      className='inline-follow-suggestions__body__scrollable__card__text-stack__source'
-      title={hint}
-    >
-      <Icon id='' icon={InfoIcon} />
-      {label}
-    </div>
-  );
-};
-
-const Card: React.FC<{
-  id: string;
-  sources: [ApiSuggestionSourceJSON, ...ApiSuggestionSourceJSON[]];
-}> = ({ id, sources }) => {
-  const intl = useIntl();
-  const account = useAppSelector((state) => state.accounts.get(id));
-  const firstVerifiedField = account?.fields.find((item) => !!item.verified_at);
-  const dispatch = useAppDispatch();
-
-  const handleDismiss = useCallback(() => {
-    void dispatch(dismissSuggestion({ accountId: id }));
-  }, [id, dispatch]);
-
-  return (
-    <div className='inline-follow-suggestions__body__scrollable__card'>
-      <IconButton
-        icon=''
-        iconComponent={CloseIcon}
-        onClick={handleDismiss}
-        title={intl.formatMessage(messages.dismiss)}
-      />
-
-      <div className='inline-follow-suggestions__body__scrollable__card__avatar'>
-        <Link to={`/@${account?.acct}`} data-hover-card-account={account?.id}>
-          <Avatar account={account} size={72} />
-        </Link>
-      </div>
-
-      <div className='inline-follow-suggestions__body__scrollable__card__text-stack'>
-        <Link to={`/@${account?.acct}`} data-hover-card-account={account?.id}>
-          <DisplayName account={account} />
-        </Link>
-        {firstVerifiedField ? (
-          <VerifiedBadge link={firstVerifiedField.value} />
-        ) : (
-          <Source id={sources[0]} />
-        )}
-      </div>
-
-      <FollowButton accountId={id} />
-    </div>
-  );
-};
-
-const DISMISSIBLE_ID = 'home/follow-suggestions';
-
-export const InlineFollowSuggestions: React.FC<{
-  hidden?: boolean;
-}> = ({ hidden }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const suggestions = useAppSelector((state) => state.suggestions.items);
-  const isLoading = useAppSelector((state) => state.suggestions.isLoading);
-  const dismissed = useAppSelector(
-    (state) =>
-      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
-      state.settings.getIn(['dismissed_banners', DISMISSIBLE_ID]) as boolean,
-  );
-  const bodyRef = useRef<HTMLDivElement>(null);
-  const [canScrollLeft, setCanScrollLeft] = useState(false);
-  const [canScrollRight, setCanScrollRight] = useState(true);
-
-  useEffect(() => {
-    void dispatch(fetchSuggestions());
-  }, [dispatch]);
-
-  useEffect(() => {
-    if (!bodyRef.current) {
-      return;
-    }
-
-    if (getComputedStyle(bodyRef.current).direction === 'rtl') {
-      setCanScrollLeft(
-        bodyRef.current.clientWidth - bodyRef.current.scrollLeft <
-          bodyRef.current.scrollWidth,
-      );
-      setCanScrollRight(bodyRef.current.scrollLeft < 0);
-    } else {
-      setCanScrollLeft(bodyRef.current.scrollLeft > 0);
-      setCanScrollRight(
-        bodyRef.current.scrollLeft + bodyRef.current.clientWidth <
-          bodyRef.current.scrollWidth,
-      );
-    }
-  }, [setCanScrollRight, setCanScrollLeft, suggestions]);
-
-  const handleLeftNav = useCallback(() => {
-    if (!bodyRef.current) {
-      return;
-    }
-
-    bodyRef.current.scrollLeft -= 200;
-  }, []);
-
-  const handleRightNav = useCallback(() => {
-    if (!bodyRef.current) {
-      return;
-    }
-
-    bodyRef.current.scrollLeft += 200;
-  }, []);
-
-  const handleScroll = useCallback(() => {
-    if (!bodyRef.current) {
-      return;
-    }
-
-    if (getComputedStyle(bodyRef.current).direction === 'rtl') {
-      setCanScrollLeft(
-        bodyRef.current.clientWidth - bodyRef.current.scrollLeft <
-          bodyRef.current.scrollWidth,
-      );
-      setCanScrollRight(bodyRef.current.scrollLeft < 0);
-    } else {
-      setCanScrollLeft(bodyRef.current.scrollLeft > 0);
-      setCanScrollRight(
-        bodyRef.current.scrollLeft + bodyRef.current.clientWidth <
-          bodyRef.current.scrollWidth,
-      );
-    }
-  }, [setCanScrollRight, setCanScrollLeft]);
-
-  const handleDismiss = useCallback(() => {
-    dispatch(changeSetting(['dismissed_banners', DISMISSIBLE_ID], true));
-  }, [dispatch]);
-
-  if (dismissed || (!isLoading && suggestions.length === 0)) {
-    return null;
-  }
-
-  if (hidden) {
-    return <div className='inline-follow-suggestions' />;
-  }
-
-  return (
-    <div className='inline-follow-suggestions'>
-      <div className='inline-follow-suggestions__header'>
-        <h3>
-          <FormattedMessage
-            id='follow_suggestions.who_to_follow'
-            defaultMessage='Who to follow'
-          />
-        </h3>
-
-        <div className='inline-follow-suggestions__header__actions'>
-          <button className='link-button' onClick={handleDismiss}>
-            <FormattedMessage
-              id='follow_suggestions.dismiss'
-              defaultMessage="Don't show again"
-            />
-          </button>
-          <Link to='/explore/suggestions' className='link-button'>
-            <FormattedMessage
-              id='follow_suggestions.view_all'
-              defaultMessage='View all'
-            />
-          </Link>
-        </div>
-      </div>
-
-      <div className='inline-follow-suggestions__body'>
-        <div
-          className='inline-follow-suggestions__body__scrollable'
-          ref={bodyRef}
-          onScroll={handleScroll}
-        >
-          {suggestions.map((suggestion) => (
-            <Card
-              key={suggestion.account_id}
-              id={suggestion.account_id}
-              sources={suggestion.sources}
-            />
-          ))}
-        </div>
-
-        {canScrollLeft && (
-          <button
-            className='inline-follow-suggestions__body__scroll-button left'
-            onClick={handleLeftNav}
-            aria-label={intl.formatMessage(messages.previous)}
-          >
-            <div className='inline-follow-suggestions__body__scroll-button__icon'>
-              <Icon id='' icon={ChevronLeftIcon} />
-            </div>
-          </button>
-        )}
-
-        {canScrollRight && (
-          <button
-            className='inline-follow-suggestions__body__scroll-button right'
-            onClick={handleRightNav}
-            aria-label={intl.formatMessage(messages.next)}
-          >
-            <div className='inline-follow-suggestions__body__scroll-button__icon'>
-              <Icon id='' icon={ChevronRightIcon} />
-            </div>
-          </button>
-        )}
-      </div>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/interaction_modal/index.jsx b/app/javascript/mastodon/features/interaction_modal/index.jsx
new file mode 100644
index 0000000000..890fe5ea2d
--- /dev/null
+++ b/app/javascript/mastodon/features/interaction_modal/index.jsx
@@ -0,0 +1,427 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
+
+import classNames from 'classnames';
+
+import { connect } from 'react-redux';
+
+import { throttle, escapeRegExp } from 'lodash';
+
+import EmojiReactionIcon from '@/material-icons/400-24px/mood.svg?react';
+import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
+import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
+import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
+import StarIcon from '@/material-icons/400-24px/star.svg?react';
+import { openModal, closeModal } from 'mastodon/actions/modal';
+import api from 'mastodon/api';
+import { Button } from 'mastodon/components/button';
+import { Icon }  from 'mastodon/components/icon';
+import { registrationsOpen, sso_redirect } from 'mastodon/initial_state';
+
+const messages = defineMessages({
+  loginPrompt: { id: 'interaction_modal.login.prompt', defaultMessage: 'Domain of your home server, e.g. mastodon.social' },
+});
+
+const mapStateToProps = (state, { accountId }) => ({
+  displayNameHtml: state.getIn(['accounts', accountId, 'display_name_html']),
+  signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up',
+});
+
+const mapDispatchToProps = (dispatch) => ({
+  onSignupClick() {
+    dispatch(closeModal({
+      modalType: undefined,
+      ignoreFocus: false,
+    }));
+    dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' }));
+  },
+});
+
+const PERSISTENCE_KEY = 'mastodon_home';
+
+const isValidDomain = value => {
+  const url = new URL('https:///path');
+  url.hostname = value;
+  return url.hostname === value;
+};
+
+const valueToDomain = value => {
+  // If the user starts typing an URL
+  if (/^https?:\/\//.test(value)) {
+    try {
+      const url = new URL(value);
+
+      // Consider that if there is a path, the URL is more meaningful than a bare domain
+      if (url.pathname.length > 1) {
+        return '';
+      }
+
+      return url.host;
+    } catch {
+      return undefined;
+    }
+  // If the user writes their full handle including username
+  } else if (value.includes('@')) {
+    if (value.replace(/^@/, '').split('@').length > 2) {
+      return undefined;
+    }
+    return '';
+  }
+
+  return value;
+};
+
+const addInputToOptions = (value, options) => {
+  value = value.trim();
+
+  if (value.includes('.') && isValidDomain(value)) {
+    return [value].concat(options.filter((x) => x !== value));
+  }
+
+  return options;
+};
+
+class LoginForm extends React.PureComponent {
+
+  static propTypes = {
+    resourceUrl: PropTypes.string,
+    intl: PropTypes.object.isRequired,
+  };
+
+  state = {
+    value: localStorage ? (localStorage.getItem(PERSISTENCE_KEY) || '') : '',
+    expanded: false,
+    selectedOption: -1,
+    isLoading: false,
+    isSubmitting: false,
+    error: false,
+    options: [],
+    networkOptions: [],
+  };
+
+  setRef = c => {
+    this.input = c;
+  };
+
+  isValueValid = (value) => {
+    let likelyAcct = false;
+    let url = null;
+
+    if (value.startsWith('/')) {
+      return false;
+    }
+
+    if (value.startsWith('@')) {
+      value = value.slice(1);
+      likelyAcct = true;
+    }
+
+    // The user is in the middle of typing something, do not error out
+    if (value === '') {
+      return true;
+    }
+
+    if (/^https?:\/\//.test(value) && !likelyAcct) {
+      url = value;
+    } else {
+      url = `https://${value}`;
+    }
+
+    try {
+      new URL(url);
+      return true;
+    } catch {
+      return false;
+    }
+  };
+
+  handleChange = ({ target }) => {
+    const error = !this.isValueValid(target.value);
+    this.setState(state => ({ error, value: target.value, isLoading: true, options: addInputToOptions(target.value, state.networkOptions) }), () => this._loadOptions());
+  };
+
+  handleMessage = (event) => {
+    const { resourceUrl } = this.props;
+
+    if (event.origin !== window.origin || event.source !== this.iframeRef.contentWindow) {
+      return;
+    }
+
+    if (event.data?.type === 'fetchInteractionURL-failure') {
+      this.setState({ isSubmitting: false, error: true });
+    } else if (event.data?.type === 'fetchInteractionURL-success') {
+      if (/^https?:\/\//.test(event.data.template)) {
+        try {
+          const url = new URL(event.data.template.replace('{uri}', encodeURIComponent(resourceUrl)));
+
+          if (localStorage) {
+            localStorage.setItem(PERSISTENCE_KEY, event.data.uri_or_domain);
+          }
+
+          window.location.href = url;
+        } catch (e) {
+          console.error(e);
+          this.setState({ isSubmitting: false, error: true });
+        }
+      } else {
+        this.setState({ isSubmitting: false, error: true });
+      }
+    }
+  };
+
+  componentDidMount () {
+    window.addEventListener('message', this.handleMessage);
+  }
+
+  componentWillUnmount () {
+    window.removeEventListener('message', this.handleMessage);
+  }
+
+  handleSubmit = () => {
+    const { value } = this.state;
+
+    this.setState({ isSubmitting: true });
+
+    this.iframeRef.contentWindow.postMessage({
+      type: 'fetchInteractionURL',
+      uri_or_domain: value.trim(),
+    }, window.origin);
+  };
+
+  setIFrameRef = (iframe) => {
+    this.iframeRef = iframe;
+  };
+
+  handleFocus = () => {
+    this.setState({ expanded: true });
+  };
+
+  handleBlur = () => {
+    this.setState({ expanded: false });
+  };
+
+  handleKeyDown = (e) => {
+    const { options, selectedOption } = this.state;
+
+    switch(e.key) {
+    case 'ArrowDown':
+      e.preventDefault();
+
+      if (options.length > 0) {
+        this.setState({ selectedOption: Math.min(selectedOption + 1, options.length - 1) });
+      }
+
+      break;
+    case 'ArrowUp':
+      e.preventDefault();
+
+      if (options.length > 0) {
+        this.setState({ selectedOption: Math.max(selectedOption - 1, -1) });
+      }
+
+      break;
+    case 'Enter':
+      e.preventDefault();
+
+      if (selectedOption === -1) {
+        this.handleSubmit();
+      } else if (options.length > 0) {
+        this.setState({ value: options[selectedOption], error: false }, () => this.handleSubmit());
+      }
+
+      break;
+    }
+  };
+
+  handleOptionClick = e => {
+    const index  = Number(e.currentTarget.getAttribute('data-index'));
+    const option = this.state.options[index];
+
+    e.preventDefault();
+    this.setState({ selectedOption: index, value: option, error: false }, () => this.handleSubmit());
+  };
+
+  _loadOptions = throttle(() => {
+    const { value } = this.state;
+
+    const domain = valueToDomain(value.trim());
+
+    if (typeof domain === 'undefined') {
+      this.setState({ options: [], networkOptions: [], isLoading: false, error: true });
+      return;
+    }
+
+    if (domain.length === 0) {
+      this.setState({ options: [], networkOptions: [], isLoading: false });
+      return;
+    }
+
+    api().get('/api/v1/peers/search', { params: { q: domain } }).then(({ data }) => {
+      if (!data) {
+        data = [];
+      }
+
+      this.setState((state) => ({ networkOptions: data, options: addInputToOptions(state.value, data), isLoading: false }));
+    }).catch(() => {
+      this.setState({ isLoading: false });
+    });
+  }, 200, { leading: true, trailing: true });
+
+  render () {
+    const { intl } = this.props;
+    const { value, expanded, options, selectedOption, error, isSubmitting } = this.state;
+    const domain = (valueToDomain(value) || '').trim();
+    const domainRegExp = new RegExp(`(${escapeRegExp(domain)})`, 'gi');
+    const hasPopOut = domain.length > 0 && options.length > 0;
+
+    return (
+      <div className={classNames('interaction-modal__login', { focused: expanded, expanded: hasPopOut, invalid: error })}>
+
+        <iframe
+          ref={this.setIFrameRef}
+          style={{display: 'none'}}
+          src='/remote_interaction_helper'
+          sandbox='allow-scripts allow-same-origin'
+          title='remote interaction helper'
+        />
+
+        <div className='interaction-modal__login__input'>
+          <input
+            ref={this.setRef}
+            type='text'
+            value={value}
+            placeholder={intl.formatMessage(messages.loginPrompt)}
+            aria-label={intl.formatMessage(messages.loginPrompt)}
+            autoFocus
+            onChange={this.handleChange}
+            onFocus={this.handleFocus}
+            onBlur={this.handleBlur}
+            onKeyDown={this.handleKeyDown}
+            autoComplete='off'
+            autoCapitalize='off'
+            spellCheck='false'
+          />
+
+          <Button onClick={this.handleSubmit} disabled={isSubmitting || error}><FormattedMessage id='interaction_modal.login.action' defaultMessage='Take me home' /></Button>
+        </div>
+
+        {hasPopOut && (
+          <div className='search__popout'>
+            <div className='search__popout__menu'>
+              {options.map((option, i) => (
+                <button key={option} onMouseDown={this.handleOptionClick} data-index={i} className={classNames('search__popout__menu__item', { selected: selectedOption === i })}>
+                  {option.split(domainRegExp).map((part, i) => (
+                    part.toLowerCase() === domain.toLowerCase() ? (
+                      <mark key={i}>
+                        {part}
+                      </mark>
+                    ) : (
+                      <span key={i}>
+                        {part}
+                      </span>
+                    )
+                  ))}
+                </button>
+              ))}
+            </div>
+          </div>
+        )}
+      </div>
+    );
+  }
+
+}
+
+const IntlLoginForm = injectIntl(LoginForm);
+
+class InteractionModal extends React.PureComponent {
+
+  static propTypes = {
+    displayNameHtml: PropTypes.string,
+    url: PropTypes.string,
+    type: PropTypes.oneOf(['reply', 'reblog', 'favourite', 'follow']),
+    onSignupClick: PropTypes.func.isRequired,
+    signupUrl: PropTypes.string.isRequired,
+  };
+
+  handleSignupClick = () => {
+    this.props.onSignupClick();
+  };
+
+  render () {
+    const { url, type, displayNameHtml, signupUrl } = this.props;
+
+    const name = <bdi dangerouslySetInnerHTML={{ __html: displayNameHtml }} />;
+
+    let title, actionDescription, icon;
+
+    switch(type) {
+    case 'reply':
+      icon = <Icon id='reply' icon={ReplyIcon} />;
+      title = <FormattedMessage id='interaction_modal.title.reply' defaultMessage="Reply to {name}'s post" values={{ name }} />;
+      actionDescription = <FormattedMessage id='interaction_modal.description.reply' defaultMessage='With an account on Mastodon, you can respond to this post.' />;
+      break;
+    case 'reblog':
+      icon = <Icon id='retweet' icon={RepeatIcon} />;
+      title = <FormattedMessage id='interaction_modal.title.reblog' defaultMessage="Boost {name}'s post" values={{ name }} />;
+      actionDescription = <FormattedMessage id='interaction_modal.description.reblog' defaultMessage='With an account on Mastodon, you can boost this post to share it with your own followers.' />;
+      break;
+    case 'favourite':
+      icon = <Icon id='star' icon={StarIcon} />;
+      title = <FormattedMessage id='interaction_modal.title.favourite' defaultMessage="Favorite {name}'s post" values={{ name }} />;
+      actionDescription = <FormattedMessage id='interaction_modal.description.favourite' defaultMessage='With an account on Mastodon, you can favorite this post to let the author know you appreciate it and save it for later.' />;
+      break;
+    case 'emoji_reaction':
+      icon = <Icon id='smile-o' icon={EmojiReactionIcon} />;
+      title = <FormattedMessage id='interaction_modal.title.emoji_reaction' defaultMessage="Emoji react {name}'s post" values={{ name }} />;
+      actionDescription = <FormattedMessage id='interaction_modal.description.emoji_reaection' defaultMessage='With an account on Mastodon, you can emoji react this post to let the author know you appreciate it and save it for later.' />;
+      break;
+    case 'follow':
+      icon = <Icon id='user-plus' icon={PersonAddIcon} />;
+      title = <FormattedMessage id='interaction_modal.title.follow' defaultMessage='Follow {name}' values={{ name }} />;
+      actionDescription = <FormattedMessage id='interaction_modal.description.follow' defaultMessage='With an account on Mastodon, you can follow {name} to receive their posts in your home feed.' values={{ name }} />;
+      break;
+    }
+
+    let signupButton;
+
+    if (sso_redirect) {
+      signupButton = (
+        <a href={sso_redirect} data-method='post' className='link-button'>
+          <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
+        </a>
+      );
+    } else if (registrationsOpen) {
+      signupButton = (
+        <a href={signupUrl} className='link-button'>
+          <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
+        </a>
+      );
+    } else {
+      signupButton = (
+        <button className='link-button' onClick={this.handleSignupClick}>
+          <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
+        </button>
+      );
+    }
+
+    return (
+      <div className='modal-root__modal interaction-modal'>
+        <div className='interaction-modal__lead'>
+          <h3><span className='interaction-modal__icon'>{icon}</span> {title}</h3>
+          <p>{actionDescription} <strong><FormattedMessage id='interaction_modal.sign_in' defaultMessage='You are not logged in to this server. Where is your account hosted?' /></strong></p>
+        </div>
+
+        <IntlLoginForm resourceUrl={url} />
+
+        <p className='hint'><FormattedMessage id='interaction_modal.sign_in_hint' defaultMessage="Tip: That's the website where you signed up. If you don't remember, look for the welcome e-mail in your inbox. You can also enter your full username! (e.g. @Mastodon@mastodon.social)" /></p>
+        <p><FormattedMessage id='interaction_modal.no_account_yet' defaultMessage='Not on Mastodon?' /> {signupButton}</p>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(InteractionModal);
diff --git a/app/javascript/mastodon/features/interaction_modal/index.tsx b/app/javascript/mastodon/features/interaction_modal/index.tsx
deleted file mode 100644
index 40d498d1ae..0000000000
--- a/app/javascript/mastodon/features/interaction_modal/index.tsx
+++ /dev/null
@@ -1,581 +0,0 @@
-import { useCallback, useEffect, useState, useRef } from 'react';
-
-import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
-
-import classNames from 'classnames';
-
-import { escapeRegExp } from 'lodash';
-import { useDebouncedCallback } from 'use-debounce';
-
-import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
-import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
-import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
-import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
-import StarIcon from '@/material-icons/400-24px/star.svg?react';
-import { openModal, closeModal } from 'mastodon/actions/modal';
-import { apiRequest } from 'mastodon/api';
-import { Button } from 'mastodon/components/button';
-import { Icon } from 'mastodon/components/icon';
-import {
-  domain as localDomain,
-  registrationsOpen,
-  sso_redirect,
-} from 'mastodon/initial_state';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-const messages = defineMessages({
-  loginPrompt: {
-    id: 'interaction_modal.username_prompt',
-    defaultMessage: 'E.g. {example}',
-  },
-});
-
-interface LoginFormMessage {
-  type:
-    | 'fetchInteractionURL'
-    | 'fetchInteractionURL-failure'
-    | 'fetchInteractionURL-success';
-  uri_or_domain: string;
-  template?: string;
-}
-
-const PERSISTENCE_KEY = 'mastodon_home';
-
-const EXAMPLE_VALUE = 'username@mastodon.social';
-
-const isValidDomain = (value: string) => {
-  const url = new URL('https:///path');
-  url.hostname = value;
-  return url.hostname === value;
-};
-
-const valueToDomain = (value: string): string | null => {
-  // If the user starts typing an URL
-  if (/^https?:\/\//.test(value)) {
-    try {
-      const url = new URL(value);
-
-      return url.host;
-    } catch {
-      return null;
-    }
-    // If the user writes their full handle including username
-  } else if (value.includes('@')) {
-    const [_, domain, ...other] = value.replace(/^@/, '').split('@');
-
-    if (!domain || other.length > 0) {
-      return null;
-    }
-
-    return valueToDomain(domain);
-  }
-
-  return value;
-};
-
-const addInputToOptions = (value: string, options: string[]) => {
-  value = value.trim();
-
-  if (value.includes('.') && isValidDomain(value)) {
-    return [value].concat(options.filter((x) => x !== value));
-  }
-
-  return options;
-};
-
-const isValueValid = (value: string) => {
-  let likelyAcct = false;
-  let url = null;
-
-  if (value.startsWith('/')) {
-    return false;
-  }
-
-  if (value.startsWith('@')) {
-    value = value.slice(1);
-    likelyAcct = true;
-  }
-
-  // The user is in the middle of typing something, do not error out
-  if (value === '') {
-    return true;
-  }
-
-  if (/^https?:\/\//.test(value) && !likelyAcct) {
-    url = value;
-  } else {
-    url = `https://${value}`;
-  }
-
-  try {
-    new URL(url);
-    return true;
-  } catch {
-    return false;
-  }
-};
-
-const sendToFrame = (frame: HTMLIFrameElement | null, value: string): void => {
-  if (valueToDomain(value.trim()) === localDomain) {
-    window.location.href = '/auth/sign_in';
-    return;
-  }
-
-  frame?.contentWindow?.postMessage(
-    {
-      type: 'fetchInteractionURL',
-      uri_or_domain: value.trim(),
-    },
-    window.origin,
-  );
-};
-
-const LoginForm: React.FC<{
-  resourceUrl: string;
-}> = ({ resourceUrl }) => {
-  const intl = useIntl();
-  const [value, setValue] = useState(
-    localStorage.getItem(PERSISTENCE_KEY) ?? '',
-  );
-  const [expanded, setExpanded] = useState(false);
-  const [selectedOption, setSelectedOption] = useState(-1);
-  const [isSubmitting, setIsSubmitting] = useState(false);
-  const [error, setError] = useState(false);
-  const [options, setOptions] = useState<string[]>([]);
-  const [networkOptions, setNetworkOptions] = useState<string[]>([]);
-  const [valueChanged, setValueChanged] = useState(false);
-
-  const inputRef = useRef<HTMLInputElement>(null);
-  const iframeRef = useRef<HTMLIFrameElement>(null);
-  const searchRequestRef = useRef<AbortController | null>(null);
-
-  useEffect(() => {
-    const handleMessage = (event: MessageEvent<LoginFormMessage>) => {
-      if (
-        event.origin !== window.origin ||
-        event.source !== iframeRef.current?.contentWindow
-      ) {
-        return;
-      }
-
-      if (event.data.type === 'fetchInteractionURL-failure') {
-        setIsSubmitting(false);
-        setError(true);
-      } else if (event.data.type === 'fetchInteractionURL-success') {
-        if (event.data.template && /^https?:\/\//.test(event.data.template)) {
-          try {
-            const url = new URL(
-              event.data.template.replace(
-                '{uri}',
-                encodeURIComponent(resourceUrl),
-              ),
-            );
-
-            localStorage.setItem(PERSISTENCE_KEY, event.data.uri_or_domain);
-
-            window.location.href = url.toString();
-          } catch {
-            setIsSubmitting(false);
-            setError(true);
-          }
-        } else {
-          setIsSubmitting(false);
-          setError(true);
-        }
-      }
-    };
-
-    window.addEventListener('message', handleMessage);
-
-    return () => {
-      window.removeEventListener('message', handleMessage);
-    };
-  }, [resourceUrl, setIsSubmitting, setError]);
-
-  const handleSearch = useDebouncedCallback(
-    (value: string) => {
-      if (searchRequestRef.current) {
-        searchRequestRef.current.abort();
-      }
-
-      const domain = valueToDomain(value.trim());
-
-      if (domain === null || domain.length === 0) {
-        setOptions([]);
-        setNetworkOptions([]);
-        return;
-      }
-
-      searchRequestRef.current = new AbortController();
-
-      void apiRequest<string[] | null>('GET', 'v1/peers/search', {
-        signal: searchRequestRef.current.signal,
-        params: {
-          q: domain,
-        },
-      })
-        .then((data) => {
-          setNetworkOptions(data ?? []);
-          setOptions(addInputToOptions(value, data ?? []));
-          return '';
-        })
-        .catch(() => {
-          // Nothing
-        });
-    },
-    500,
-    { leading: true, trailing: true },
-  );
-
-  const handleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setValue(value);
-      setValueChanged(true);
-      setError(!isValueValid(value));
-      setOptions(addInputToOptions(value, networkOptions));
-      handleSearch(value);
-    },
-    [
-      setError,
-      setValue,
-      setValueChanged,
-      setOptions,
-      networkOptions,
-      handleSearch,
-    ],
-  );
-
-  const handleSubmit = useCallback(() => {
-    setIsSubmitting(true);
-    sendToFrame(iframeRef.current, value);
-  }, [setIsSubmitting, value]);
-
-  const handleFocus = useCallback(() => {
-    setExpanded(true);
-  }, [setExpanded]);
-
-  const handleBlur = useCallback(() => {
-    setExpanded(false);
-  }, [setExpanded]);
-
-  const handleKeyDown = useCallback(
-    (e: React.KeyboardEvent) => {
-      const selectedOptionValue = options[selectedOption];
-
-      switch (e.key) {
-        case 'ArrowDown':
-          e.preventDefault();
-
-          if (options.length > 0) {
-            setSelectedOption((selectedOption) =>
-              Math.min(selectedOption + 1, options.length - 1),
-            );
-          }
-
-          break;
-        case 'ArrowUp':
-          e.preventDefault();
-
-          if (options.length > 0) {
-            setSelectedOption((selectedOption) =>
-              Math.max(selectedOption - 1, -1),
-            );
-          }
-
-          break;
-        case 'Enter':
-          e.preventDefault();
-
-          if (selectedOption === -1) {
-            handleSubmit();
-          } else if (options.length > 0 && selectedOptionValue) {
-            setError(false);
-            setValue(selectedOptionValue);
-            setIsSubmitting(true);
-            sendToFrame(iframeRef.current, selectedOptionValue);
-          }
-
-          break;
-      }
-    },
-    [
-      handleSubmit,
-      setSelectedOption,
-      setError,
-      setValue,
-      selectedOption,
-      options,
-    ],
-  );
-
-  const handleOptionClick = useCallback(
-    (e: React.MouseEvent) => {
-      e.preventDefault();
-
-      const index = Number(e.currentTarget.getAttribute('data-index'));
-      const option = options[index];
-
-      if (!option) {
-        return;
-      }
-
-      setSelectedOption(index);
-      setValue(option);
-      setError(false);
-      setIsSubmitting(true);
-      sendToFrame(iframeRef.current, option);
-    },
-    [options, setSelectedOption, setValue, setError],
-  );
-
-  const domain = (valueToDomain(value) ?? '').trim();
-  const domainRegExp = new RegExp(`(${escapeRegExp(domain)})`, 'gi');
-  const hasPopOut = valueChanged && domain.length > 0 && options.length > 0;
-
-  return (
-    <div
-      className={classNames('interaction-modal__login', {
-        focused: expanded,
-        expanded: hasPopOut,
-        invalid: error,
-      })}
-    >
-      <iframe
-        ref={iframeRef}
-        style={{ display: 'none' }}
-        src='/remote_interaction_helper'
-        sandbox='allow-scripts allow-same-origin'
-        title='remote interaction helper'
-      />
-
-      <div className='interaction-modal__login__input'>
-        <input
-          ref={inputRef}
-          type='text'
-          value={value}
-          placeholder={intl.formatMessage(messages.loginPrompt, {
-            example: EXAMPLE_VALUE,
-          })}
-          aria-label={intl.formatMessage(messages.loginPrompt, {
-            example: EXAMPLE_VALUE,
-          })}
-          // eslint-disable-next-line jsx-a11y/no-autofocus
-          autoFocus
-          onChange={handleChange}
-          onFocus={handleFocus}
-          onBlur={handleBlur}
-          onKeyDown={handleKeyDown}
-          autoComplete='off'
-          autoCapitalize='off'
-          spellCheck='false'
-        />
-
-        <Button onClick={handleSubmit} disabled={isSubmitting || error}>
-          <FormattedMessage id='interaction_modal.go' defaultMessage='Go' />
-        </Button>
-      </div>
-
-      {hasPopOut && (
-        <div className='search__popout'>
-          <div className='search__popout__menu'>
-            {options.map((option, i) => (
-              <button
-                key={option}
-                onMouseDown={handleOptionClick}
-                data-index={i}
-                className={classNames('search__popout__menu__item', {
-                  selected: selectedOption === i,
-                })}
-              >
-                {option
-                  .split(domainRegExp)
-                  .map((part, i) =>
-                    part.toLowerCase() === domain.toLowerCase() ? (
-                      <mark key={i}>{part}</mark>
-                    ) : (
-                      <span key={i}>{part}</span>
-                    ),
-                  )}
-              </button>
-            ))}
-          </div>
-        </div>
-      )}
-    </div>
-  );
-};
-
-const InteractionModal: React.FC<{
-  accountId: string;
-  url: string;
-  type: 'reply' | 'reblog' | 'favourite' | 'follow' | 'vote';
-}> = ({ accountId, url, type }) => {
-  const dispatch = useAppDispatch();
-  const displayNameHtml = useAppSelector(
-    (state) => state.accounts.get(accountId)?.display_name_html ?? '',
-  );
-  const signupUrl = useAppSelector(
-    (state) =>
-      (state.server.getIn(['server', 'registrations', 'url'], null) ||
-        '/auth/sign_up') as string,
-  );
-  const name = <bdi dangerouslySetInnerHTML={{ __html: displayNameHtml }} />;
-
-  const handleSignupClick = useCallback(() => {
-    dispatch(
-      closeModal({
-        modalType: undefined,
-        ignoreFocus: false,
-      }),
-    );
-
-    dispatch(
-      openModal({
-        modalType: 'CLOSED_REGISTRATIONS',
-        modalProps: {},
-      }),
-    );
-  }, [dispatch]);
-
-  let title: React.ReactNode,
-    icon: React.ReactNode,
-    actionPrompt: React.ReactNode;
-
-  switch (type) {
-    case 'reply':
-      icon = <Icon id='reply' icon={ReplyIcon} />;
-      title = (
-        <FormattedMessage
-          id='interaction_modal.title.reply'
-          defaultMessage="Reply to {name}'s post"
-          values={{ name }}
-        />
-      );
-      actionPrompt = (
-        <FormattedMessage
-          id='interaction_modal.action.reply'
-          defaultMessage='To continue, you need to reply from your account.'
-        />
-      );
-      break;
-    case 'reblog':
-      icon = <Icon id='retweet' icon={RepeatIcon} />;
-      title = (
-        <FormattedMessage
-          id='interaction_modal.title.reblog'
-          defaultMessage="Boost {name}'s post"
-          values={{ name }}
-        />
-      );
-      actionPrompt = (
-        <FormattedMessage
-          id='interaction_modal.action.reblog'
-          defaultMessage='To continue, you need to reblog from your account.'
-        />
-      );
-      break;
-    case 'favourite':
-      icon = <Icon id='star' icon={StarIcon} />;
-      title = (
-        <FormattedMessage
-          id='interaction_modal.title.favourite'
-          defaultMessage="Favorite {name}'s post"
-          values={{ name }}
-        />
-      );
-      actionPrompt = (
-        <FormattedMessage
-          id='interaction_modal.action.favourite'
-          defaultMessage='To continue, you need to favorite from your account.'
-        />
-      );
-      break;
-    case 'follow':
-      icon = <Icon id='user-plus' icon={PersonAddIcon} />;
-      title = (
-        <FormattedMessage
-          id='interaction_modal.title.follow'
-          defaultMessage='Follow {name}'
-          values={{ name }}
-        />
-      );
-      actionPrompt = (
-        <FormattedMessage
-          id='interaction_modal.action.follow'
-          defaultMessage='To continue, you need to follow from your account.'
-        />
-      );
-      break;
-    case 'vote':
-      icon = <Icon id='tasks' icon={InsertChartIcon} />;
-      title = (
-        <FormattedMessage
-          id='interaction_modal.title.vote'
-          defaultMessage="Vote in {name}'s poll"
-          values={{ name }}
-        />
-      );
-      actionPrompt = (
-        <FormattedMessage
-          id='interaction_modal.action.vote'
-          defaultMessage='To continue, you need to vote from your account.'
-        />
-      );
-      break;
-  }
-
-  let signupButton;
-
-  if (sso_redirect) {
-    signupButton = (
-      <a href={sso_redirect} data-method='post' className='link-button'>
-        <FormattedMessage
-          id='sign_in_banner.create_account'
-          defaultMessage='Create account'
-        />
-      </a>
-    );
-  } else if (registrationsOpen) {
-    signupButton = (
-      <a href={signupUrl} className='link-button'>
-        <FormattedMessage
-          id='sign_in_banner.create_account'
-          defaultMessage='Create account'
-        />
-      </a>
-    );
-  } else {
-    signupButton = (
-      <button className='link-button' onClick={handleSignupClick}>
-        <FormattedMessage
-          id='sign_in_banner.create_account'
-          defaultMessage='Create account'
-        />
-      </button>
-    );
-  }
-
-  return (
-    <div className='modal-root__modal interaction-modal'>
-      <div className='interaction-modal__lead'>
-        <h3>
-          <span className='interaction-modal__icon'>{icon}</span> {title}
-        </h3>
-        <p>{actionPrompt}</p>
-      </div>
-
-      <LoginForm resourceUrl={url} />
-
-      <p>
-        <FormattedMessage
-          id='interaction_modal.no_account_yet'
-          defaultMessage="Don't have an account yet?"
-        />{' '}
-        {signupButton}
-      </p>
-    </div>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default InteractionModal;
diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx b/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx
index d6f8363907..f12eeffe6f 100644
--- a/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx
+++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx
@@ -66,10 +66,6 @@ class KeyboardShortcuts extends ImmutablePureComponent {
                 <td><kbd>enter</kbd>, <kbd>o</kbd></td>
                 <td><FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open status' /></td>
               </tr>
-              <tr>
-                <td><kbd>t</kbd></td>
-                <td><FormattedMessage id='keyboard_shortcuts.translate' defaultMessage='to translate a post' /></td>
-              </tr>
               <tr>
                 <td><kbd>e</kbd></td>
                 <td><FormattedMessage id='keyboard_shortcuts.open_media' defaultMessage='to open media' /></td>
diff --git a/app/javascript/mastodon/features/link_timeline/index.tsx b/app/javascript/mastodon/features/link_timeline/index.tsx
index 1b3f287177..262a21afcc 100644
--- a/app/javascript/mastodon/features/link_timeline/index.tsx
+++ b/app/javascript/mastodon/features/link_timeline/index.tsx
@@ -5,8 +5,7 @@ import { useParams } from 'react-router-dom';
 
 import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
 import { expandLinkTimeline } from 'mastodon/actions/timelines';
-import { Column } from 'mastodon/components/column';
-import type { ColumnRef } from 'mastodon/components/column';
+import Column from 'mastodon/components/column';
 import { ColumnHeader } from 'mastodon/components/column_header';
 import StatusListContainer from 'mastodon/features/ui/containers/status_list_container';
 import type { Card } from 'mastodon/models/status';
@@ -18,7 +17,7 @@ export const LinkTimeline: React.FC<{
   const { url } = useParams<{ url: string }>();
   const decodedUrl = url ? decodeURIComponent(url) : undefined;
   const dispatch = useAppDispatch();
-  const columnRef = useRef<ColumnRef>(null);
+  const columnRef = useRef<Column>(null);
   const firstStatusId = useAppSelector((state) =>
     decodedUrl
       ? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
diff --git a/app/javascript/mastodon/features/list_adder/components/account.jsx b/app/javascript/mastodon/features/list_adder/components/account.jsx
new file mode 100644
index 0000000000..0ecd367a35
--- /dev/null
+++ b/app/javascript/mastodon/features/list_adder/components/account.jsx
@@ -0,0 +1,45 @@
+// Kmyblue tracking marker: copied antenna_adder/account, circle_adder/account
+
+import { injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { Avatar } from '../../../components/avatar';
+import { DisplayName } from '../../../components/display_name';
+import { makeGetAccount } from '../../../selectors';
+
+const makeMapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  const mapStateToProps = (state, { accountId }) => ({
+    account: getAccount(state, accountId),
+  });
+
+  return mapStateToProps;
+};
+
+class Account extends ImmutablePureComponent {
+
+  static propTypes = {
+    account: ImmutablePropTypes.record.isRequired,
+  };
+
+  render () {
+    const { account } = this.props;
+    return (
+      <div className='account'>
+        <div className='account__wrapper'>
+          <div className='account__display-name'>
+            <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
+            <DisplayName account={account} />
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(makeMapStateToProps)(injectIntl(Account));
diff --git a/app/javascript/mastodon/features/list_adder/components/list.jsx b/app/javascript/mastodon/features/list_adder/components/list.jsx
new file mode 100644
index 0000000000..1cb0d50d17
--- /dev/null
+++ b/app/javascript/mastodon/features/list_adder/components/list.jsx
@@ -0,0 +1,82 @@
+// Kmyblue tracking marker: copied antenna_adder/antenna, circle_adder/circle, bookmark_category_adder/bookmark_category
+
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import AddIcon from '@/material-icons/400-24px/add.svg?react';
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
+import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
+import { Icon }  from 'mastodon/components/icon';
+
+import { removeFromListAdder, addToListAdder } from '../../../actions/lists';
+import { IconButton }  from '../../../components/icon_button';
+
+const messages = defineMessages({
+  remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
+  add: { id: 'lists.account.add', defaultMessage: 'Add to list' },
+  exclusive: { id: 'lists.exclusive', defaultMessage: 'Hide list or antenna account posts from home' },
+});
+
+const MapStateToProps = (state, { listId, added }) => ({
+  list: state.get('lists').get(listId),
+  added: typeof added === 'undefined' ? state.getIn(['listAdder', 'lists', 'items']).includes(listId) : added,
+});
+
+const mapDispatchToProps = (dispatch, { listId }) => ({
+  onRemove: () => dispatch(removeFromListAdder(listId)),
+  onAdd: () => dispatch(addToListAdder(listId)),
+});
+
+class List extends ImmutablePureComponent {
+
+  static propTypes = {
+    list: ImmutablePropTypes.map.isRequired,
+    intl: PropTypes.object.isRequired,
+    onRemove: PropTypes.func.isRequired,
+    onAdd: PropTypes.func.isRequired,
+    added: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    added: false,
+  };
+
+  render () {
+    const { list, intl, onRemove, onAdd, added } = this.props;
+
+    let button;
+
+    if (added) {
+      button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
+    } else {
+      button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
+    }
+
+    const exclusiveIcon = list.get('exclusive') && <Icon id='eye-slash' icon={VisibilityOffIcon} title={intl.formatMessage(messages.exclusive)} className='column-link__icon' fixedWidth />;
+
+    return (
+      <div className='list'>
+        <div className='list__wrapper'>
+          <div className='list__display-name'>
+            <Icon id='list-ul' icon={ListAltIcon} className='column-link__icon' />
+            {exclusiveIcon}
+            {list.get('title')}
+          </div>
+
+          <div className='account__relationship'>
+            {button}
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(List));
diff --git a/app/javascript/mastodon/features/list_adder/index.jsx b/app/javascript/mastodon/features/list_adder/index.jsx
new file mode 100644
index 0000000000..0349579b9f
--- /dev/null
+++ b/app/javascript/mastodon/features/list_adder/index.jsx
@@ -0,0 +1,78 @@
+// Kmyblue tracking marker: copied antenna_adder, circle_adder, bookmark_category_adder
+
+import PropTypes from 'prop-types';
+
+import { injectIntl } from 'react-intl';
+
+import { createSelector } from '@reduxjs/toolkit';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { setupListAdder, resetListAdder } from '../../actions/lists';
+import NewListForm from '../lists/components/new_list_form';
+
+import Account from './components/account';
+import List from './components/list';
+// hack
+
+const getOrderedLists = createSelector([state => state.get('lists')], lists => {
+  if (!lists) {
+    return lists;
+  }
+
+  return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
+});
+
+const mapStateToProps = state => ({
+  listIds: getOrderedLists(state).map(list=>list.get('id')),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onInitialize: accountId => dispatch(setupListAdder(accountId)),
+  onReset: () => dispatch(resetListAdder()),
+});
+
+class ListAdder extends ImmutablePureComponent {
+
+  static propTypes = {
+    accountId: PropTypes.string.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    onInitialize: PropTypes.func.isRequired,
+    onReset: PropTypes.func.isRequired,
+    listIds: ImmutablePropTypes.list.isRequired,
+  };
+
+  componentDidMount () {
+    const { onInitialize, accountId } = this.props;
+    onInitialize(accountId);
+  }
+
+  componentWillUnmount () {
+    const { onReset } = this.props;
+    onReset();
+  }
+
+  render () {
+    const { accountId, listIds } = this.props;
+
+    return (
+      <div className='modal-root__modal list-adder'>
+        <div className='list-adder__account'>
+          <Account accountId={accountId} />
+        </div>
+
+        <NewListForm />
+
+
+        <div className='list-adder__lists'>
+          {listIds.map(ListId => <List key={ListId} listId={ListId} />)}
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListAdder));
diff --git a/app/javascript/mastodon/features/list_adder/index.tsx b/app/javascript/mastodon/features/list_adder/index.tsx
deleted file mode 100644
index a5ad760f4b..0000000000
--- a/app/javascript/mastodon/features/list_adder/index.tsx
+++ /dev/null
@@ -1,216 +0,0 @@
-import { useEffect, useState, useCallback } from 'react';
-
-import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
-
-import { isFulfilled } from '@reduxjs/toolkit';
-
-import CloseIcon from '@/material-icons/400-24px/close.svg?react';
-import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
-import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
-import { fetchLists } from 'mastodon/actions/lists';
-import { createList } from 'mastodon/actions/lists_typed';
-import {
-  apiGetAccountLists,
-  apiAddAccountToList,
-  apiRemoveAccountFromList,
-} from 'mastodon/api/lists';
-import type { ApiListJSON } from 'mastodon/api_types/lists';
-import { Button } from 'mastodon/components/button';
-import { CheckBox } from 'mastodon/components/check_box';
-import { Icon } from 'mastodon/components/icon';
-import { IconButton } from 'mastodon/components/icon_button';
-import { getOrderedLists } from 'mastodon/selectors/lists';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  newList: {
-    id: 'lists.new_list_name',
-    defaultMessage: 'New list name',
-  },
-  createList: {
-    id: 'lists.create',
-    defaultMessage: 'Create',
-  },
-  close: {
-    id: 'lightbox.close',
-    defaultMessage: 'Close',
-  },
-});
-
-const ListItem: React.FC<{
-  id: string;
-  title: string;
-  exclusive: boolean;
-  checked: boolean;
-  onChange: (id: string, checked: boolean) => void;
-}> = ({ id, title, exclusive, checked, onChange }) => {
-  const handleChange = useCallback(
-    (e: React.ChangeEvent<HTMLInputElement>) => {
-      onChange(id, e.target.checked);
-    },
-    [id, onChange],
-  );
-
-  return (
-    <label className='lists__item'>
-      <div className='lists__item__title'>
-        <Icon id='list-ul' icon={ListAltIcon} />
-        {exclusive && <Icon id='eye-slash' icon={VisibilityOffIcon} />}
-        <span>{title}</span>
-      </div>
-
-      <CheckBox value={id} checked={checked} onChange={handleChange} />
-    </label>
-  );
-};
-
-const NewListItem: React.FC<{
-  onCreate: (list: ApiListJSON) => void;
-}> = ({ onCreate }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const [title, setTitle] = useState('');
-
-  const handleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setTitle(value);
-    },
-    [setTitle],
-  );
-
-  const handleSubmit = useCallback(() => {
-    if (title.trim().length === 0) {
-      return;
-    }
-
-    void dispatch(createList({ title })).then((result) => {
-      if (isFulfilled(result)) {
-        onCreate(result.payload);
-        setTitle('');
-      }
-
-      return '';
-    });
-  }, [setTitle, dispatch, onCreate, title]);
-
-  return (
-    <form className='lists__item' onSubmit={handleSubmit}>
-      <label className='lists__item__title'>
-        <Icon id='list-ul' icon={ListAltIcon} />
-
-        <input
-          type='text'
-          value={title}
-          onChange={handleChange}
-          maxLength={30}
-          required
-          placeholder={intl.formatMessage(messages.newList)}
-        />
-      </label>
-
-      <Button text={intl.formatMessage(messages.createList)} type='submit' />
-    </form>
-  );
-};
-
-const ListAdder: React.FC<{
-  accountId: string;
-  onClose: () => void;
-}> = ({ accountId, onClose }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const account = useAppSelector((state) => state.accounts.get(accountId));
-  const lists = useAppSelector((state) => getOrderedLists(state));
-  const [listIds, setListIds] = useState<string[]>([]);
-
-  useEffect(() => {
-    dispatch(fetchLists());
-
-    apiGetAccountLists(accountId)
-      .then((data) => {
-        setListIds(data.map((l) => l.id));
-        return '';
-      })
-      .catch(() => {
-        // Nothing
-      });
-  }, [dispatch, setListIds, accountId]);
-
-  const handleToggle = useCallback(
-    (listId: string, checked: boolean) => {
-      if (checked) {
-        setListIds((currentListIds) => [listId, ...currentListIds]);
-
-        apiAddAccountToList(listId, accountId).catch(() => {
-          setListIds((currentListIds) =>
-            currentListIds.filter((id) => id !== listId),
-          );
-        });
-      } else {
-        setListIds((currentListIds) =>
-          currentListIds.filter((id) => id !== listId),
-        );
-
-        apiRemoveAccountFromList(listId, accountId).catch(() => {
-          setListIds((currentListIds) => [listId, ...currentListIds]);
-        });
-      }
-    },
-    [setListIds, accountId],
-  );
-
-  const handleCreate = useCallback(
-    (list: ApiListJSON) => {
-      setListIds((currentListIds) => [list.id, ...currentListIds]);
-
-      apiAddAccountToList(list.id, accountId).catch(() => {
-        setListIds((currentListIds) =>
-          currentListIds.filter((id) => id !== list.id),
-        );
-      });
-    },
-    [setListIds, accountId],
-  );
-
-  return (
-    <div className='modal-root__modal dialog-modal'>
-      <div className='dialog-modal__header'>
-        <IconButton
-          className='dialog-modal__header__close'
-          title={intl.formatMessage(messages.close)}
-          icon='times'
-          iconComponent={CloseIcon}
-          onClick={onClose}
-        />
-
-        <span className='dialog-modal__header__title'>
-          <FormattedMessage
-            id='lists.add_to_lists'
-            defaultMessage='Add {name} to lists'
-            values={{ name: <strong>@{account?.acct}</strong> }}
-          />
-        </span>
-      </div>
-
-      <div className='dialog-modal__content'>
-        <div className='lists-scrollable'>
-          <NewListItem onCreate={handleCreate} />
-
-          {lists.map((list) => (
-            <ListItem
-              key={list.id}
-              id={list.id}
-              title={list.title}
-              exclusive={list.exclusive}
-              checked={listIds.includes(list.id)}
-              onChange={handleToggle}
-            />
-          ))}
-        </div>
-      </div>
-    </div>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default ListAdder;
diff --git a/app/javascript/mastodon/features/list_editor/components/account.jsx b/app/javascript/mastodon/features/list_editor/components/account.jsx
new file mode 100644
index 0000000000..fc67614e10
--- /dev/null
+++ b/app/javascript/mastodon/features/list_editor/components/account.jsx
@@ -0,0 +1,84 @@
+// Kmyblue tracking marker: copied antenna_editor/account, circle_editor/account
+
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import AddIcon from '@/material-icons/400-24px/add.svg?react';
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+
+import { removeFromListEditor, addToListEditor } from '../../../actions/lists';
+import { Avatar } from '../../../components/avatar';
+import { DisplayName } from '../../../components/display_name';
+import { IconButton } from '../../../components/icon_button';
+import { makeGetAccount } from '../../../selectors';
+
+const messages = defineMessages({
+  remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
+  add: { id: 'lists.account.add', defaultMessage: 'Add to list' },
+});
+
+const makeMapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  const mapStateToProps = (state, { accountId, added }) => ({
+    account: getAccount(state, accountId),
+    added: typeof added === 'undefined' ? state.getIn(['listEditor', 'accounts', 'items']).includes(accountId) : added,
+  });
+
+  return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch, { accountId }) => ({
+  onRemove: () => dispatch(removeFromListEditor(accountId)),
+  onAdd: () => dispatch(addToListEditor(accountId)),
+});
+
+class Account extends ImmutablePureComponent {
+
+  static propTypes = {
+    account: ImmutablePropTypes.record.isRequired,
+    intl: PropTypes.object.isRequired,
+    onRemove: PropTypes.func.isRequired,
+    onAdd: PropTypes.func.isRequired,
+    added: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    added: false,
+  };
+
+  render () {
+    const { account, intl, onRemove, onAdd, added } = this.props;
+
+    let button;
+
+    if (added) {
+      button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
+    } else {
+      button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
+    }
+
+    return (
+      <div className='account'>
+        <div className='account__wrapper'>
+          <div className='account__display-name'>
+            <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
+            <DisplayName account={account} />
+          </div>
+
+          <div className='account__relationship'>
+            {button}
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(Account));
diff --git a/app/javascript/mastodon/features/list_editor/components/edit_list_form.jsx b/app/javascript/mastodon/features/list_editor/components/edit_list_form.jsx
new file mode 100644
index 0000000000..e0efadd2cd
--- /dev/null
+++ b/app/javascript/mastodon/features/list_editor/components/edit_list_form.jsx
@@ -0,0 +1,78 @@
+// Kmyblue tracking marker: copied antenna_editor/edit_antenna_form, circle_editor/edit_circle_form, bookmark_category_editor/edit_bookmark_category_form
+
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import CheckIcon from '@/material-icons/400-24px/check.svg?react';
+
+import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
+import { IconButton } from '../../../components/icon_button';
+
+const messages = defineMessages({
+  title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['listEditor', 'title']),
+  disabled: !state.getIn(['listEditor', 'isChanged']) || !state.getIn(['listEditor', 'title']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onChange: value => dispatch(changeListEditorTitle(value)),
+  onSubmit: () => dispatch(submitListEditor(false)),
+});
+
+class ListForm extends PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    disabled: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleSubmit = e => {
+    e.preventDefault();
+    this.props.onSubmit();
+  };
+
+  handleClick = () => {
+    this.props.onSubmit();
+  };
+
+  render () {
+    const { value, disabled, intl } = this.props;
+
+    const title = intl.formatMessage(messages.title);
+
+    return (
+      <form className='column-inline-form' onSubmit={this.handleSubmit}>
+        <input
+          className='setting-text'
+          value={value}
+          onChange={this.handleChange}
+        />
+
+        <IconButton
+          disabled={disabled}
+          icon='check'
+          iconComponent={CheckIcon}
+          title={title}
+          onClick={this.handleClick}
+        />
+      </form>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListForm));
diff --git a/app/javascript/mastodon/features/list_editor/components/search.jsx b/app/javascript/mastodon/features/list_editor/components/search.jsx
new file mode 100644
index 0000000000..8ca89c401c
--- /dev/null
+++ b/app/javascript/mastodon/features/list_editor/components/search.jsx
@@ -0,0 +1,85 @@
+// Kmyblue tracking marker: copied antenna_editor/search, circle_editor/search
+
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import classNames from 'classnames';
+
+import { connect } from 'react-redux';
+
+import CancelIcon from '@/material-icons/400-24px/cancel.svg?react';
+import SearchIcon from '@/material-icons/400-24px/search.svg?react';
+import { Icon }  from 'mastodon/components/icon';
+
+import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists';
+
+const messages = defineMessages({
+  search: { id: 'lists.search', defaultMessage: 'Search among people you follow' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['listEditor', 'suggestions', 'value']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onSubmit: value => dispatch(fetchListSuggestions(value)),
+  onClear: () => dispatch(clearListSuggestions()),
+  onChange: value => dispatch(changeListSuggestions(value)),
+});
+
+class Search extends PureComponent {
+
+  static propTypes = {
+    intl: PropTypes.object.isRequired,
+    value: PropTypes.string.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+    onClear: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleKeyUp = e => {
+    if (e.keyCode === 13) {
+      this.props.onSubmit(this.props.value);
+    }
+  };
+
+  handleClear = () => {
+    this.props.onClear();
+  };
+
+  render () {
+    const { value, intl } = this.props;
+    const hasValue = value.length > 0;
+
+    return (
+      <div className='list-editor__search search'>
+        <label>
+          <span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
+
+          <input
+            className='search__input'
+            type='text'
+            value={value}
+            onChange={this.handleChange}
+            onKeyUp={this.handleKeyUp}
+            placeholder={intl.formatMessage(messages.search)}
+          />
+        </label>
+
+        <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
+          <Icon id='search' icon={SearchIcon} className={classNames({ active: !hasValue })} />
+          <Icon id='times-circle' icon={CancelIcon} aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Search));
diff --git a/app/javascript/mastodon/features/list_editor/index.jsx b/app/javascript/mastodon/features/list_editor/index.jsx
new file mode 100644
index 0000000000..a16fe88a2b
--- /dev/null
+++ b/app/javascript/mastodon/features/list_editor/index.jsx
@@ -0,0 +1,85 @@
+// Kmyblue tracking marker: copied antenna_editor, circle_editor, bookmark_category_statuses
+
+import PropTypes from 'prop-types';
+
+import { injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import spring from 'react-motion/lib/spring';
+
+import { setupListEditor, clearListSuggestions, resetListEditor } from '../../actions/lists';
+import Motion from '../ui/util/optional_motion';
+
+import Account from './components/account';
+import EditListForm from './components/edit_list_form';
+import Search from './components/search';
+
+const mapStateToProps = state => ({
+  accountIds: state.getIn(['listEditor', 'accounts', 'items']),
+  searchAccountIds: state.getIn(['listEditor', 'suggestions', 'items']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onInitialize: listId => dispatch(setupListEditor(listId)),
+  onClear: () => dispatch(clearListSuggestions()),
+  onReset: () => dispatch(resetListEditor()),
+});
+
+class ListEditor extends ImmutablePureComponent {
+
+  static propTypes = {
+    listId: PropTypes.string.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    onInitialize: PropTypes.func.isRequired,
+    onClear: PropTypes.func.isRequired,
+    onReset: PropTypes.func.isRequired,
+    accountIds: ImmutablePropTypes.list.isRequired,
+    searchAccountIds: ImmutablePropTypes.list.isRequired,
+  };
+
+  componentDidMount () {
+    const { onInitialize, listId } = this.props;
+    onInitialize(listId);
+  }
+
+  componentWillUnmount () {
+    const { onReset } = this.props;
+    onReset();
+  }
+
+  render () {
+    const { accountIds, searchAccountIds, onClear } = this.props;
+    const showSearch = searchAccountIds.size > 0;
+
+    return (
+      <div className='modal-root__modal list-editor'>
+        <EditListForm />
+
+        <Search />
+
+        <div className='drawer__pager'>
+          <div className='drawer__inner list-editor__accounts'>
+            {accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
+          </div>
+
+          {showSearch && <div role='button' tabIndex={-1} className='drawer__backdrop' onClick={onClear} />}
+
+          <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
+            {({ x }) => (
+              <div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
+                {searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
+              </div>
+            )}
+          </Motion>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListEditor));
diff --git a/app/javascript/mastodon/features/list_timeline/index.jsx b/app/javascript/mastodon/features/list_timeline/index.jsx
index 9d35c4a10e..fdf7efde04 100644
--- a/app/javascript/mastodon/features/list_timeline/index.jsx
+++ b/app/javascript/mastodon/features/list_timeline/index.jsx
@@ -3,19 +3,21 @@
 import PropTypes from 'prop-types';
 import { PureComponent } from 'react';
 
-import { FormattedMessage } from 'react-intl';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
 
 import { Helmet } from 'react-helmet';
-import { Link, withRouter } from 'react-router-dom';
+import { withRouter } from 'react-router-dom';
 
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { connect } from 'react-redux';
 
+import Toggle from 'react-toggle';
+
 import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
 import EditIcon from '@/material-icons/400-24px/edit.svg?react';
 import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
 import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
-import { fetchList } from 'mastodon/actions/lists';
+import { fetchList, updateList } from 'mastodon/actions/lists';
 import { openModal } from 'mastodon/actions/modal';
 import { connectListStream } from 'mastodon/actions/streaming';
 import { expandListTimeline } from 'mastodon/actions/timelines';
@@ -23,10 +25,17 @@ import Column from 'mastodon/components/column';
 import ColumnHeader from 'mastodon/components/column_header';
 import { Icon }  from 'mastodon/components/icon';
 import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import { RadioButton } from 'mastodon/components/radio_button';
 import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
 import StatusListContainer from 'mastodon/features/ui/containers/status_list_container';
 import { WithRouterPropTypes } from 'mastodon/utils/react_router';
 
+const messages = defineMessages({
+  followed:   { id: 'lists.replies_policy.followed', defaultMessage: 'Any followed user' },
+  none:    { id: 'lists.replies_policy.none', defaultMessage: 'No one' },
+  list:  { id: 'lists.replies_policy.list', defaultMessage: 'Members of the list' },
+});
+
 const mapStateToProps = (state, props) => ({
   list: state.getIn(['lists', props.params.id]),
   hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
@@ -108,6 +117,13 @@ class ListTimeline extends PureComponent {
     this.props.dispatch(expandListTimeline(id, { maxId }));
   };
 
+  handleEditClick = () => {
+    this.props.dispatch(openModal({
+      modalType: 'LIST_EDITOR',
+      modalProps: { listId: this.props.params.id },
+    }));
+  };
+
   handleDeleteClick = () => {
     const { dispatch, columnId } = this.props;
     const { id } = this.props.params;
@@ -117,15 +133,36 @@ class ListTimeline extends PureComponent {
 
   handleEditAntennaClick = (e) => {
     const id = e.currentTarget.getAttribute('data-id');
-    this.props.history.push(`/antennas/${id}/edit`);
+    this.props.history.push(`/antennasw/${id}/edit`);
+  };
+
+  handleRepliesPolicyChange = ({ target }) => {
+    const { dispatch } = this.props;
+    const { id } = this.props.params;
+    dispatch(updateList(id, undefined, false, undefined, target.value, undefined));
+  };
+
+  onExclusiveToggle = ({ target }) => {
+    const { dispatch } = this.props;
+    const { id } = this.props.params;
+    dispatch(updateList(id, undefined, false, target.checked, undefined, undefined));
+  };
+
+  onNotifyToggle = ({ target }) => {
+    const { dispatch } = this.props;
+    const { id } = this.props.params;
+    dispatch(updateList(id, undefined, false, undefined, undefined, target.checked));
   };
 
   render () {
-    const { hasUnread, columnId, multiColumn, list } = this.props;
+    const { hasUnread, columnId, multiColumn, list, intl } = this.props;
     const { id } = this.props.params;
     const pinned = !!columnId;
     const title  = list ? list.get('title') : id;
-    const antennas = list ? (list.get('antennas') ?? []) : [];
+    const replies_policy = list ? list.get('replies_policy') : undefined;
+    const isExclusive = list ? list.get('exclusive') : undefined;
+    const isNotify = list ? list.get('notify') : undefined;
+    const antennas = list ? (list.get('antennas')?.toArray() || []) : [];
 
     if (typeof list === 'undefined') {
       return (
@@ -156,24 +193,54 @@ class ListTimeline extends PureComponent {
         >
           <div className='column-settings'>
             <section className='column-header__links'>
-              <Link to={`/lists/${id}/edit`} className='text-btn column-header__setting-btn'>
+              <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}>
                 <Icon id='pencil' icon={EditIcon} /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
-              </Link>
+              </button>
 
               <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
                 <Icon id='trash' icon={DeleteIcon} /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
               </button>
             </section>
 
+            <section>
+              <div className='setting-toggle'>
+                <Toggle id={`list-${id}-exclusive`} checked={isExclusive} onChange={this.onExclusiveToggle} />
+                <label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
+                  <FormattedMessage id='lists.exclusive' defaultMessage='Hide list or antenna account posts from home' />
+                </label>
+              </div>
+            </section>
+
+            <section className='similar-row'>
+              <div className='setting-toggle'>
+                <Toggle id={`list-${id}-notify`} checked={isNotify} onChange={this.onNotifyToggle} />
+                <label htmlFor={`list-${id}-notify`} className='setting-toggle__label'>
+                  <FormattedMessage id='lists.notify' defaultMessage='Notify these posts' />
+                </label>
+              </div>
+            </section>
+
+            {replies_policy !== undefined && (
+              <section aria-labelledby={`list-${id}-replies-policy`}>
+                <h3 id={`list-${id}-replies-policy`}><FormattedMessage id='lists.replies_policy.title' defaultMessage='Show replies to:' /></h3>
+
+                <div className='column-settings__row'>
+                  { ['none', 'list', 'followed'].map(policy => (
+                    <RadioButton name='order' key={policy} value={policy} label={intl.formatMessage(messages[policy])} checked={replies_policy === policy} onChange={this.handleRepliesPolicyChange} />
+                  ))}
+                </div>
+              </section>
+            )}
+
             { antennas.length > 0 && (
               <section aria-labelledby={`list-${id}-antenna`}>
                 <h3><FormattedMessage id='lists.antennas' defaultMessage='Related antennas:' /></h3>
 
                 <ul className='column-settings__row'>
                   { antennas.map(antenna => (
-                    <li key={antenna.id} className='column-settings__row__antenna'>
-                      <button type='button' className='text-btn column-header__setting-btn' data-id={antenna.id} onClick={this.handleEditAntennaClick}>
-                        <Icon id='pencil' icon={EditIcon} /> {antenna.title}{antenna.stl && ' [STL]'}{antenna.ltl && ' [LTL]'}
+                    <li key={antenna.get('id')} className='column-settings__row__antenna'>
+                      <button type='button' className='text-btn column-header__setting-btn' data-id={antenna.get('id')} onClick={this.handleEditAntennaClick}>
+                        <Icon id='pencil' icon={EditIcon} /> {antenna.get('title')}{antenna.get('stl') && ' [STL]'}
                       </button>
                     </li>
                   ))}
@@ -202,4 +269,4 @@ class ListTimeline extends PureComponent {
 
 }
 
-export default withRouter(connect(mapStateToProps)(ListTimeline));
+export default withRouter(connect(mapStateToProps)(injectIntl(ListTimeline)));
diff --git a/app/javascript/mastodon/features/lists/components/new_list_form.jsx b/app/javascript/mastodon/features/lists/components/new_list_form.jsx
new file mode 100644
index 0000000000..4a76fe7b6d
--- /dev/null
+++ b/app/javascript/mastodon/features/lists/components/new_list_form.jsx
@@ -0,0 +1,82 @@
+// Kmyblue tracking marker: copied antennas/new_antenna_form, circles/new_circle_form, bookmark_categories/new_bookmark_category_form
+
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import { changeListEditorTitle, submitListEditor } from 'mastodon/actions/lists';
+import { Button } from 'mastodon/components/button';
+
+const messages = defineMessages({
+  label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
+  title: { id: 'lists.new.create', defaultMessage: 'Add list' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['listEditor', 'title']),
+  disabled: state.getIn(['listEditor', 'isSubmitting']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onChange: value => dispatch(changeListEditorTitle(value)),
+  onSubmit: () => dispatch(submitListEditor(true)),
+});
+
+class NewListForm extends PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    disabled: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  };
+
+  handleSubmit = e => {
+    e.preventDefault();
+    this.props.onSubmit();
+  };
+
+  handleClick = () => {
+    this.props.onSubmit();
+  };
+
+  render () {
+    const { value, disabled, intl } = this.props;
+
+    const label = intl.formatMessage(messages.label);
+    const title = intl.formatMessage(messages.title);
+
+    return (
+      <form className='column-inline-form' onSubmit={this.handleSubmit}>
+        <label>
+          <span style={{ display: 'none' }}>{label}</span>
+
+          <input
+            className='setting-text'
+            value={value}
+            disabled={disabled}
+            onChange={this.handleChange}
+            placeholder={label}
+          />
+        </label>
+
+        <Button
+          disabled={disabled || !value}
+          text={title}
+          onClick={this.handleClick}
+        />
+      </form>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(NewListForm));
diff --git a/app/javascript/mastodon/features/lists/index.jsx b/app/javascript/mastodon/features/lists/index.jsx
new file mode 100644
index 0000000000..7c918eab97
--- /dev/null
+++ b/app/javascript/mastodon/features/lists/index.jsx
@@ -0,0 +1,98 @@
+// Kmyblue tracking marker: copied antennas, circles, bookmark_categories
+
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import { createSelector } from '@reduxjs/toolkit';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
+import { fetchLists } from 'mastodon/actions/lists';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import ScrollableList from 'mastodon/components/scrollable_list';
+import ColumnLink from 'mastodon/features/ui/components/column_link';
+import ColumnSubheading from 'mastodon/features/ui/components/column_subheading';
+
+import NewListForm from './components/new_list_form';
+
+const messages = defineMessages({
+  heading: { id: 'column.lists', defaultMessage: 'Lists' },
+  subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
+  with_antenna: { id: 'lists.with_antenna', defaultMessage: 'Antenna' },
+});
+
+const getOrderedLists = createSelector([state => state.get('lists')], lists => {
+  if (!lists) {
+    return lists;
+  }
+
+  return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
+});
+
+const mapStateToProps = state => ({
+  lists: getOrderedLists(state),
+});
+
+class Lists extends ImmutablePureComponent {
+
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    lists: ImmutablePropTypes.list,
+    intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
+  };
+
+  UNSAFE_componentWillMount () {
+    this.props.dispatch(fetchLists());
+  }
+
+  render () {
+    const { intl, lists, multiColumn } = this.props;
+
+    if (!lists) {
+      return (
+        <Column>
+          <LoadingIndicator />
+        </Column>
+      );
+    }
+
+    const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
+
+    return (
+      <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.heading)}>
+        <ColumnHeader title={intl.formatMessage(messages.heading)} icon='list-ul' iconComponent={ListAltIcon} multiColumn={multiColumn} />
+
+        <NewListForm />
+
+        <ScrollableList
+          scrollKey='lists'
+          emptyMessage={emptyMessage}
+          prepend={<ColumnSubheading text={intl.formatMessage(messages.subheading)} />}
+          bindToDocument={!multiColumn}
+        >
+          {lists.map(list =>
+            (<ColumnLink key={list.get('id')} to={`/lists/${list.get('id')}`} icon='list-ul' iconComponent={ListAltIcon} text={list.get('title')}
+              badge={(list.get('antennas') && list.get('antennas').size > 0) ? intl.formatMessage(messages.with_antenna) : undefined} />),
+          )}
+        </ScrollableList>
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.heading)}</title>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(Lists));
diff --git a/app/javascript/mastodon/features/lists/index.tsx b/app/javascript/mastodon/features/lists/index.tsx
deleted file mode 100644
index e8f55bc0ad..0000000000
--- a/app/javascript/mastodon/features/lists/index.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import { useEffect, useMemo, useCallback } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { Link } from 'react-router-dom';
-
-import AddIcon from '@/material-icons/400-24px/add.svg?react';
-import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
-import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
-import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
-import { fetchLists } from 'mastodon/actions/lists';
-import { openModal } from 'mastodon/actions/modal';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
-import { Icon } from 'mastodon/components/icon';
-import ScrollableList from 'mastodon/components/scrollable_list';
-import { getOrderedLists } from 'mastodon/selectors/lists';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-const messages = defineMessages({
-  heading: { id: 'column.lists', defaultMessage: 'Lists' },
-  create: { id: 'lists.create_list', defaultMessage: 'Create list' },
-  edit: { id: 'lists.edit', defaultMessage: 'Edit list' },
-  delete: { id: 'lists.delete', defaultMessage: 'Delete list' },
-  more: { id: 'status.more', defaultMessage: 'More' },
-});
-
-const ListItem: React.FC<{
-  id: string;
-  title: string;
-  antennaTitles?: string[];
-}> = ({ id, title, antennaTitles }) => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-
-  const handleDeleteClick = useCallback(() => {
-    dispatch(
-      openModal({
-        modalType: 'CONFIRM_DELETE_LIST',
-        modalProps: {
-          listId: id,
-        },
-      }),
-    );
-  }, [dispatch, id]);
-
-  const menu = useMemo(
-    () => [
-      { text: intl.formatMessage(messages.edit), to: `/lists/${id}/edit` },
-      { text: intl.formatMessage(messages.delete), action: handleDeleteClick },
-    ],
-    [intl, id, handleDeleteClick],
-  );
-
-  return (
-    <div className='lists__item'>
-      <Link to={`/lists/${id}`} className='lists__item__title'>
-        <Icon id='list-ul' icon={ListAltIcon} />
-        <span>
-          {title}
-          {antennaTitles?.map((at) => (
-            <span key={at} className='lists__item__memo'>
-              <FormattedMessage
-                id='lists.memo_related_antenna'
-                defaultMessage='Antenna: "{title}"'
-                values={{ title: at }}
-              />
-            </span>
-          ))}
-        </span>
-      </Link>
-
-      <Dropdown
-        scrollKey='lists'
-        items={menu}
-        icon='ellipsis-h'
-        iconComponent={MoreHorizIcon}
-        title={intl.formatMessage(messages.more)}
-      />
-    </div>
-  );
-};
-
-const Lists: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-  const lists = useAppSelector((state) => getOrderedLists(state));
-
-  useEffect(() => {
-    dispatch(fetchLists());
-  }, [dispatch]);
-
-  const emptyMessage = (
-    <>
-      <span>
-        <FormattedMessage
-          id='lists.no_lists_yet'
-          defaultMessage='No lists yet.'
-        />
-        <br />
-        <FormattedMessage
-          id='lists.create_a_list_to_organize'
-          defaultMessage='Create a new list to organize your Home feed'
-        />
-      </span>
-
-      <SquigglyArrow className='empty-column-indicator__arrow' />
-    </>
-  );
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(messages.heading)}
-    >
-      <ColumnHeader
-        title={intl.formatMessage(messages.heading)}
-        icon='list-ul'
-        iconComponent={ListAltIcon}
-        multiColumn={multiColumn}
-        extraButton={
-          <Link
-            to='/lists/new'
-            className='column-header__button'
-            title={intl.formatMessage(messages.create)}
-            aria-label={intl.formatMessage(messages.create)}
-          >
-            <Icon id='plus' icon={AddIcon} />
-          </Link>
-        }
-      />
-
-      <ScrollableList
-        scrollKey='lists'
-        emptyMessage={emptyMessage}
-        bindToDocument={!multiColumn}
-      >
-        {lists.map((list) => (
-          <ListItem
-            key={list.id}
-            id={list.id}
-            title={list.title}
-            antennaTitles={list.antennas.map((a) => a.title)}
-          />
-        ))}
-      </ScrollableList>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default Lists;
diff --git a/app/javascript/mastodon/features/lists/members.tsx b/app/javascript/mastodon/features/lists/members.tsx
deleted file mode 100644
index dafee3fc2d..0000000000
--- a/app/javascript/mastodon/features/lists/members.tsx
+++ /dev/null
@@ -1,343 +0,0 @@
-import { useCallback, useState, useEffect, useRef } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { useParams, Link } from 'react-router-dom';
-
-import { useDebouncedCallback } from 'use-debounce';
-
-import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
-import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
-import { fetchRelationships } from 'mastodon/actions/accounts';
-import { showAlertForError } from 'mastodon/actions/alerts';
-import { importFetchedAccounts } from 'mastodon/actions/importer';
-import { fetchList } from 'mastodon/actions/lists';
-import { openModal } from 'mastodon/actions/modal';
-import { apiRequest } from 'mastodon/api';
-import { apiFollowAccount } from 'mastodon/api/accounts';
-import {
-  apiGetAccounts,
-  apiAddAccountToList,
-  apiRemoveAccountFromList,
-} from 'mastodon/api/lists';
-import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
-import { Avatar } from 'mastodon/components/avatar';
-import { Button } from 'mastodon/components/button';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { ColumnSearchHeader } from 'mastodon/components/column_search_header';
-import { FollowersCounter } from 'mastodon/components/counters';
-import { DisplayName } from 'mastodon/components/display_name';
-import ScrollableList from 'mastodon/components/scrollable_list';
-import { ShortNumber } from 'mastodon/components/short_number';
-import { VerifiedBadge } from 'mastodon/components/verified_badge';
-import { me } from 'mastodon/initial_state';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  heading: { id: 'column.list_members', defaultMessage: 'Manage list members' },
-  placeholder: {
-    id: 'lists.search',
-    defaultMessage: 'Search',
-  },
-  enterSearch: { id: 'lists.add_to_list', defaultMessage: 'Add to list' },
-  add: { id: 'lists.add_member', defaultMessage: 'Add' },
-  remove: { id: 'lists.remove_member', defaultMessage: 'Remove' },
-  back: { id: 'column_back_button.label', defaultMessage: 'Back' },
-});
-
-type Mode = 'remove' | 'add';
-
-const AccountItem: React.FC<{
-  accountId: string;
-  listId: string;
-  partOfList: boolean;
-  onToggle: (accountId: string) => void;
-}> = ({ accountId, listId, partOfList, onToggle }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const account = useAppSelector((state) => state.accounts.get(accountId));
-  const relationship = useAppSelector((state) =>
-    accountId ? state.relationships.get(accountId) : undefined,
-  );
-  const following =
-    accountId === me || relationship?.following || relationship?.requested;
-
-  useEffect(() => {
-    if (accountId) {
-      dispatch(fetchRelationships([accountId]));
-    }
-  }, [dispatch, accountId]);
-
-  const handleClick = useCallback(() => {
-    if (partOfList) {
-      void apiRemoveAccountFromList(listId, accountId);
-      onToggle(accountId);
-    } else {
-      if (following) {
-        void apiAddAccountToList(listId, accountId);
-        onToggle(accountId);
-      } else {
-        dispatch(
-          openModal({
-            modalType: 'CONFIRM_FOLLOW_TO_LIST',
-            modalProps: {
-              accountId,
-              onConfirm: () => {
-                apiFollowAccount(accountId)
-                  .then(() => apiAddAccountToList(listId, accountId))
-                  .then(() => {
-                    onToggle(accountId);
-                    return '';
-                  })
-                  .catch((err: unknown) => {
-                    dispatch(showAlertForError(err));
-                  });
-              },
-            },
-          }),
-        );
-      }
-    }
-  }, [dispatch, accountId, following, listId, partOfList, onToggle]);
-
-  if (!account) {
-    return null;
-  }
-
-  const firstVerifiedField = account.fields.find((item) => !!item.verified_at);
-
-  return (
-    <div className='account'>
-      <div className='account__wrapper'>
-        <Link
-          key={account.id}
-          className='account__display-name'
-          title={account.acct}
-          to={`/@${account.acct}`}
-          data-hover-card-account={account.id}
-        >
-          <div className='account__avatar-wrapper'>
-            <Avatar account={account} size={36} />
-          </div>
-
-          <div className='account__contents'>
-            <DisplayName account={account} />
-
-            <div className='account__details'>
-              <ShortNumber
-                value={account.followers_count}
-                renderer={FollowersCounter}
-                isHide={account.other_settings.hide_followers_count}
-              />{' '}
-              {firstVerifiedField && (
-                <VerifiedBadge link={firstVerifiedField.value} />
-              )}
-            </div>
-          </div>
-        </Link>
-
-        <div className='account__relationship'>
-          <Button
-            text={intl.formatMessage(
-              partOfList ? messages.remove : messages.add,
-            )}
-            secondary={partOfList}
-            onClick={handleClick}
-          />
-        </div>
-      </div>
-    </div>
-  );
-};
-
-const ListMembers: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const { id } = useParams<{ id: string }>();
-  const intl = useIntl();
-
-  const [searching, setSearching] = useState(false);
-  const [accountIds, setAccountIds] = useState<string[]>([]);
-  const [searchAccountIds, setSearchAccountIds] = useState<string[]>([]);
-  const [loading, setLoading] = useState(true);
-  const [mode, setMode] = useState<Mode>('remove');
-
-  useEffect(() => {
-    if (id) {
-      setLoading(true);
-      dispatch(fetchList(id));
-
-      void apiGetAccounts(id)
-        .then((data) => {
-          dispatch(importFetchedAccounts(data));
-          setAccountIds(data.map((a) => a.id));
-          setLoading(false);
-          return '';
-        })
-        .catch(() => {
-          setLoading(false);
-        });
-    }
-  }, [dispatch, id]);
-
-  const handleSearchClick = useCallback(() => {
-    setMode('add');
-  }, [setMode]);
-
-  const handleDismissSearchClick = useCallback(() => {
-    setMode('remove');
-    setSearching(false);
-  }, [setMode]);
-
-  const handleAccountToggle = useCallback(
-    (accountId: string) => {
-      const partOfList = accountIds.includes(accountId);
-
-      if (partOfList) {
-        setAccountIds(accountIds.filter((id) => id !== accountId));
-      } else {
-        setAccountIds([accountId, ...accountIds]);
-      }
-    },
-    [accountIds, setAccountIds],
-  );
-
-  const searchRequestRef = useRef<AbortController | null>(null);
-
-  const handleSearch = useDebouncedCallback(
-    (value: string) => {
-      if (searchRequestRef.current) {
-        searchRequestRef.current.abort();
-      }
-
-      if (value.trim().length === 0) {
-        setSearching(false);
-        return;
-      }
-
-      setLoading(true);
-
-      searchRequestRef.current = new AbortController();
-
-      void apiRequest<ApiAccountJSON[]>('GET', 'v1/accounts/search', {
-        signal: searchRequestRef.current.signal,
-        params: {
-          q: value,
-          resolve: true,
-        },
-      })
-        .then((data) => {
-          dispatch(importFetchedAccounts(data));
-          setSearchAccountIds(data.map((a) => a.id));
-          setLoading(false);
-          setSearching(true);
-          return '';
-        })
-        .catch(() => {
-          setSearching(true);
-          setLoading(false);
-        });
-    },
-    500,
-    { leading: true, trailing: true },
-  );
-
-  let displayedAccountIds: string[];
-
-  if (mode === 'add' && searching) {
-    displayedAccountIds = searchAccountIds;
-  } else {
-    displayedAccountIds = accountIds;
-  }
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(messages.heading)}
-    >
-      <ColumnHeader
-        title={intl.formatMessage(messages.heading)}
-        icon='list-ul'
-        iconComponent={ListAltIcon}
-        multiColumn={multiColumn}
-        showBackButton
-      />
-
-      <ColumnSearchHeader
-        placeholder={intl.formatMessage(messages.placeholder)}
-        onBack={handleDismissSearchClick}
-        onSubmit={handleSearch}
-        onActivate={handleSearchClick}
-        active={mode === 'add'}
-      />
-
-      <ScrollableList
-        scrollKey='list_members'
-        trackScroll={!multiColumn}
-        bindToDocument={!multiColumn}
-        isLoading={loading}
-        showLoading={loading && displayedAccountIds.length === 0}
-        hasMore={false}
-        footer={
-          <>
-            {displayedAccountIds.length > 0 && <div className='spacer' />}
-
-            <div className='column-footer'>
-              <Link to={`/lists/${id}`} className='button button--block'>
-                <FormattedMessage id='lists.done' defaultMessage='Done' />
-              </Link>
-            </div>
-          </>
-        }
-        emptyMessage={
-          mode === 'remove' ? (
-            <>
-              <span>
-                <FormattedMessage
-                  id='lists.no_members_yet'
-                  defaultMessage='No members yet.'
-                />
-                <br />
-                <FormattedMessage
-                  id='lists.find_users_to_add'
-                  defaultMessage='Find users to add'
-                />
-              </span>
-
-              <SquigglyArrow className='empty-column-indicator__arrow' />
-            </>
-          ) : (
-            <FormattedMessage
-              id='lists.no_results_found'
-              defaultMessage='No results found.'
-            />
-          )
-        }
-      >
-        {displayedAccountIds.map((accountId) => (
-          <AccountItem
-            key={accountId}
-            accountId={accountId}
-            listId={id}
-            partOfList={
-              displayedAccountIds === accountIds ||
-              accountIds.includes(accountId)
-            }
-            onToggle={handleAccountToggle}
-          />
-        ))}
-      </ScrollableList>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.heading)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default ListMembers;
diff --git a/app/javascript/mastodon/features/lists/new.tsx b/app/javascript/mastodon/features/lists/new.tsx
deleted file mode 100644
index e40e63630f..0000000000
--- a/app/javascript/mastodon/features/lists/new.tsx
+++ /dev/null
@@ -1,401 +0,0 @@
-import { useCallback, useState, useEffect } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { useParams, useHistory, Link } from 'react-router-dom';
-
-import { isFulfilled } from '@reduxjs/toolkit';
-
-import Toggle from 'react-toggle';
-
-import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
-import { fetchList } from 'mastodon/actions/lists';
-import { createList, updateList } from 'mastodon/actions/lists_typed';
-import { apiGetAccounts } from 'mastodon/api/lists';
-import type { RepliesPolicyType } from 'mastodon/api_types/lists';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { LoadingIndicator } from 'mastodon/components/loading_indicator';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  edit: { id: 'column.edit_list', defaultMessage: 'Edit list' },
-  create: { id: 'column.create_list', defaultMessage: 'Create list' },
-});
-
-const MembersLink: React.FC<{
-  id: string;
-}> = ({ id }) => {
-  const [count, setCount] = useState(0);
-  const [avatars, setAvatars] = useState<string[]>([]);
-
-  useEffect(() => {
-    void apiGetAccounts(id)
-      .then((data) => {
-        setCount(data.length);
-        setAvatars(data.slice(0, 3).map((a) => a.avatar));
-        return '';
-      })
-      .catch(() => {
-        // Nothing
-      });
-  }, [id, setCount, setAvatars]);
-
-  return (
-    <Link to={`/lists/${id}/members`} className='app-form__link'>
-      <div className='app-form__link__text'>
-        <strong>
-          <FormattedMessage
-            id='lists.list_members'
-            defaultMessage='List members'
-          />
-        </strong>
-        <FormattedMessage
-          id='lists.list_members_count'
-          defaultMessage='{count, plural, one {# member} other {# members}}'
-          values={{ count }}
-        />
-      </div>
-
-      <div className='avatar-pile'>
-        {avatars.map((url) => (
-          <img key={url} src={url} alt='' />
-        ))}
-      </div>
-    </Link>
-  );
-};
-
-const NewList: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const dispatch = useAppDispatch();
-  const { id } = useParams<{ id?: string }>();
-  const intl = useIntl();
-  const history = useHistory();
-
-  const list = useAppSelector((state) =>
-    id ? state.lists.get(id) : undefined,
-  );
-  const [title, setTitle] = useState('');
-  const [exclusive, setExclusive] = useState(false);
-  const [repliesPolicy, setRepliesPolicy] = useState<RepliesPolicyType>('list');
-  const [notify, setNotify] = useState(false);
-  const [favourite, setFavourite] = useState(true);
-  const [submitting, setSubmitting] = useState(false);
-
-  useEffect(() => {
-    if (id) {
-      dispatch(fetchList(id));
-    }
-  }, [dispatch, id]);
-
-  useEffect(() => {
-    if (id && list) {
-      setTitle(list.title);
-      setExclusive(list.exclusive);
-      setRepliesPolicy(list.replies_policy);
-      setNotify(list.notify);
-      setFavourite(list.favourite);
-    }
-  }, [
-    setTitle,
-    setExclusive,
-    setRepliesPolicy,
-    setNotify,
-    setFavourite,
-    id,
-    list,
-  ]);
-
-  const handleTitleChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
-      setTitle(value);
-    },
-    [setTitle],
-  );
-
-  const handleExclusiveChange = useCallback(
-    ({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
-      setExclusive(checked);
-    },
-    [setExclusive],
-  );
-
-  const handleRepliesPolicyChange = useCallback(
-    ({ target: { value } }: React.ChangeEvent<HTMLSelectElement>) => {
-      setRepliesPolicy(value as RepliesPolicyType);
-    },
-    [setRepliesPolicy],
-  );
-
-  const handleNotifyChange = useCallback(
-    ({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
-      setNotify(checked);
-    },
-    [setNotify],
-  );
-
-  const handleFavouriteChange = useCallback(
-    ({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
-      setFavourite(checked);
-    },
-    [setFavourite],
-  );
-
-  const handleSubmit = useCallback(() => {
-    setSubmitting(true);
-
-    if (id) {
-      void dispatch(
-        updateList({
-          id,
-          title,
-          exclusive,
-          replies_policy: repliesPolicy,
-          notify,
-          favourite,
-        }),
-      ).then(() => {
-        setSubmitting(false);
-        return '';
-      });
-    } else {
-      void dispatch(
-        createList({
-          title,
-          exclusive,
-          replies_policy: repliesPolicy,
-          notify,
-          favourite,
-        }),
-      ).then((result) => {
-        setSubmitting(false);
-
-        if (isFulfilled(result)) {
-          history.replace(`/lists/${result.payload.id}/edit`);
-          history.push(`/lists/${result.payload.id}/members`);
-        }
-
-        return '';
-      });
-    }
-  }, [
-    history,
-    dispatch,
-    setSubmitting,
-    id,
-    title,
-    exclusive,
-    repliesPolicy,
-    notify,
-    favourite,
-  ]);
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(id ? messages.edit : messages.create)}
-    >
-      <ColumnHeader
-        title={intl.formatMessage(id ? messages.edit : messages.create)}
-        icon='list-ul'
-        iconComponent={ListAltIcon}
-        multiColumn={multiColumn}
-        showBackButton
-      />
-
-      <div className='scrollable'>
-        <form className='simple_form app-form' onSubmit={handleSubmit}>
-          <div className='fields-group'>
-            <div className='input with_label'>
-              <div className='label_input'>
-                <label htmlFor='list_title'>
-                  <FormattedMessage
-                    id='lists.list_name'
-                    defaultMessage='List name'
-                  />
-                </label>
-
-                <div className='label_input__wrapper'>
-                  <input
-                    id='list_title'
-                    type='text'
-                    value={title}
-                    onChange={handleTitleChange}
-                    maxLength={30}
-                    required
-                    placeholder=' '
-                  />
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <div className='fields-group'>
-            <div className='input with_label'>
-              <div className='label_input'>
-                <label htmlFor='list_replies_policy'>
-                  <FormattedMessage
-                    id='lists.show_replies_to'
-                    defaultMessage='Include replies from list members to'
-                  />
-                </label>
-
-                <div className='label_input__wrapper'>
-                  <select
-                    id='list_replies_policy'
-                    value={repliesPolicy}
-                    onChange={handleRepliesPolicyChange}
-                  >
-                    <FormattedMessage
-                      id='lists.replies_policy.none'
-                      defaultMessage='No one'
-                    >
-                      {(msg) => <option value='none'>{msg}</option>}
-                    </FormattedMessage>
-                    <FormattedMessage
-                      id='lists.replies_policy.list'
-                      defaultMessage='Members of the list'
-                    >
-                      {(msg) => <option value='list'>{msg}</option>}
-                    </FormattedMessage>
-                    <FormattedMessage
-                      id='lists.replies_policy.followed'
-                      defaultMessage='Any followed user'
-                    >
-                      {(msg) => <option value='followed'>{msg}</option>}
-                    </FormattedMessage>
-                  </select>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          {id && (
-            <div className='fields-group'>
-              <MembersLink id={id} />
-            </div>
-          )}
-          {!id && (
-            <div className='fields-group'>
-              <div className='app-form__memo'>
-                <FormattedMessage
-                  id='lists.save_to_edit_member'
-                  defaultMessage='You can edit list members after saving.'
-                />
-              </div>
-            </div>
-          )}
-
-          <div className='fields-group'>
-            {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
-            <label className='app-form__toggle'>
-              <div className='app-form__toggle__label'>
-                <strong>
-                  <FormattedMessage
-                    id='lists.exclusive'
-                    defaultMessage='Hide members in Home'
-                  />
-                </strong>
-                <span className='hint'>
-                  <FormattedMessage
-                    id='lists.exclusive_hint'
-                    defaultMessage='If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.'
-                  />
-                </span>
-              </div>
-
-              <div className='app-form__toggle__toggle'>
-                <div>
-                  <Toggle
-                    checked={exclusive}
-                    onChange={handleExclusiveChange}
-                  />
-                </div>
-              </div>
-            </label>
-          </div>
-
-          <div className='fields-group'>
-            {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
-            <label className='app-form__toggle'>
-              <div className='app-form__toggle__label'>
-                <strong>
-                  <FormattedMessage
-                    id='lists.notify'
-                    defaultMessage='Notify list'
-                  />
-                </strong>
-                <span className='hint'>
-                  <FormattedMessage
-                    id='lists.notify_hint'
-                    defaultMessage='Notify when new post is added.'
-                  />
-                </span>
-              </div>
-
-              <div className='app-form__toggle__toggle'>
-                <div>
-                  <Toggle checked={notify} onChange={handleNotifyChange} />
-                </div>
-              </div>
-            </label>
-          </div>
-
-          <div className='fields-group'>
-            {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
-            <label className='app-form__toggle'>
-              <div className='app-form__toggle__label'>
-                <strong>
-                  <FormattedMessage
-                    id='lists.favourite'
-                    defaultMessage='Favorite'
-                  />
-                </strong>
-                <span className='hint'>
-                  <FormattedMessage
-                    id='lists.favourite_hint'
-                    defaultMessage='When opening the Web Client on a PC, this list appears in the navigation.'
-                  />
-                </span>
-              </div>
-
-              <div className='app-form__toggle__toggle'>
-                <div>
-                  <Toggle
-                    checked={favourite}
-                    onChange={handleFavouriteChange}
-                  />
-                </div>
-              </div>
-            </label>
-          </div>
-
-          <div className='actions'>
-            <button className='button' type='submit'>
-              {submitting ? (
-                <LoadingIndicator />
-              ) : id ? (
-                <FormattedMessage id='lists.save' defaultMessage='Save' />
-              ) : (
-                <FormattedMessage id='lists.create' defaultMessage='Create' />
-              )}
-            </button>
-          </div>
-        </form>
-      </div>
-
-      <Helmet>
-        <title>
-          {intl.formatMessage(id ? messages.edit : messages.create)}
-        </title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default NewList;
diff --git a/app/javascript/mastodon/features/mentioned_users/index.jsx b/app/javascript/mastodon/features/mentioned_users/index.jsx
index 99443b9fa9..f32e38820e 100644
--- a/app/javascript/mastodon/features/mentioned_users/index.jsx
+++ b/app/javascript/mastodon/features/mentioned_users/index.jsx
@@ -11,10 +11,10 @@ import { connect } from 'react-redux';
 import { debounce } from 'lodash';
 
 import { fetchMentionedUsers, expandMentionedUsers } from 'mastodon/actions/interactions';
-import { Account } from 'mastodon/components/account';
 import ColumnHeader from 'mastodon/components/column_header';
 import { LoadingIndicator } from 'mastodon/components/loading_indicator';
 import ScrollableList from 'mastodon/components/scrollable_list';
+import AccountContainer from 'mastodon/containers/account_container';
 import Column from 'mastodon/features/ui/components/column';
 
 const mapStateToProps = (state, props) => ({
@@ -74,7 +74,7 @@ class MentionedUsers extends ImmutablePureComponent {
           bindToDocument={!multiColumn}
         >
           {accountIds.map(id =>
-            <Account key={id} id={id} />,
+            <AccountContainer key={id} id={id} withNote={false} />,
           )}
         </ScrollableList>
 
diff --git a/app/javascript/mastodon/features/mutes/index.jsx b/app/javascript/mastodon/features/mutes/index.jsx
index 5a5711da58..3b50244ea4 100644
--- a/app/javascript/mastodon/features/mutes/index.jsx
+++ b/app/javascript/mastodon/features/mutes/index.jsx
@@ -11,11 +11,11 @@ import { connect } from 'react-redux';
 import { debounce } from 'lodash';
 
 import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react';
-import { Account } from 'mastodon/components/account';
 
 import { fetchMutes, expandMutes } from '../../actions/mutes';
 import { LoadingIndicator } from '../../components/loading_indicator';
 import ScrollableList from '../../components/scrollable_list';
+import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
 
 const messages = defineMessages({
@@ -72,7 +72,7 @@ class Mutes extends ImmutablePureComponent {
           bindToDocument={!multiColumn}
         >
           {accountIds.map(id =>
-            <Account key={id} id={id} defaultAction='mute' />,
+            <AccountContainer key={id} id={id} defaultAction='mute' />,
           )}
         </ScrollableList>
 
diff --git a/app/javascript/mastodon/features/notifications/components/filter_bar.jsx b/app/javascript/mastodon/features/notifications/components/filter_bar.jsx
new file mode 100644
index 0000000000..f5e8762db9
--- /dev/null
+++ b/app/javascript/mastodon/features/notifications/components/filter_bar.jsx
@@ -0,0 +1,140 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
+import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
+import ReferenceIcon from '@/material-icons/400-24px/link.svg?react';
+import EmojiReactionIcon from '@/material-icons/400-24px/mood.svg?react';
+import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
+import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
+import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
+import StarIcon from '@/material-icons/400-24px/star.svg?react';
+import { Icon }  from 'mastodon/components/icon';
+import { enableEmojiReaction } from 'mastodon/initial_state';
+
+const tooltips = defineMessages({
+  mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
+  favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favorites' },
+  emojiReactions: { id: 'notifications.filter.emoji_reactions', defaultMessage: 'Stamps' },
+  boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
+  status_references: { id: 'notifications.filter.status_references', defaultMessage: 'Status references' },
+  polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
+  follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
+  statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
+});
+
+class FilterBar extends PureComponent {
+
+  static propTypes = {
+    selectFilter: PropTypes.func.isRequired,
+    selectedFilter: PropTypes.string.isRequired,
+    advancedMode: PropTypes.bool.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  onClick (notificationType) {
+    return () => this.props.selectFilter(notificationType);
+  }
+
+  render () {
+    const { selectedFilter, advancedMode, intl } = this.props;
+    const renderedElement = !advancedMode ? (
+      <div className='notification__filter-bar'>
+        <button
+          className={selectedFilter === 'all' ? 'active' : ''}
+          onClick={this.onClick('all')}
+        >
+          <FormattedMessage
+            id='notifications.filter.all'
+            defaultMessage='All'
+          />
+        </button>
+        <button
+          className={selectedFilter === 'mention' ? 'active' : ''}
+          onClick={this.onClick('mention')}
+        >
+          <FormattedMessage
+            id='notifications.filter.mentions'
+            defaultMessage='Mentions'
+          />
+        </button>
+      </div>
+    ) : (
+      <div className='notification__filter-bar'>
+        <button
+          className={selectedFilter === 'all' ? 'active' : ''}
+          onClick={this.onClick('all')}
+        >
+          <FormattedMessage
+            id='notifications.filter.all'
+            defaultMessage='All'
+          />
+        </button>
+        <button
+          className={selectedFilter === 'mention' ? 'active' : ''}
+          onClick={this.onClick('mention')}
+          title={intl.formatMessage(tooltips.mentions)}
+        >
+          <Icon id='reply-all' icon={ReplyAllIcon} />
+        </button>
+        <button
+          className={selectedFilter === 'favourite' ? 'active' : ''}
+          onClick={this.onClick('favourite')}
+          title={intl.formatMessage(tooltips.favourites)}
+        >
+          <Icon id='star' icon={StarIcon} />
+        </button>
+        {enableEmojiReaction && (
+          <button
+            className={selectedFilter === 'emoji_reaction' ? 'active' : ''}
+            onClick={this.onClick('emoji_reaction')}
+            title={intl.formatMessage(tooltips.emojiReactions)}
+          >
+            <Icon id='smile-o' icon={EmojiReactionIcon} fixedWidth />
+          </button>
+        )}
+        <button
+          className={selectedFilter === 'reblog' ? 'active' : ''}
+          onClick={this.onClick('reblog')}
+          title={intl.formatMessage(tooltips.boosts)}
+        >
+          <Icon id='retweet' icon={RepeatIcon} />
+        </button>
+        <button
+          className={selectedFilter === 'status_reference' ? 'active' : ''}
+          onClick={this.onClick('status_reference')}
+          title={intl.formatMessage(tooltips.status_references)}
+        >
+          <Icon id='link' icon={ReferenceIcon} fixedWidth />
+        </button>
+        <button
+          className={selectedFilter === 'poll' ? 'active' : ''}
+          onClick={this.onClick('poll')}
+          title={intl.formatMessage(tooltips.polls)}
+        >
+          <Icon id='tasks' icon={InsertChartIcon} />
+        </button>
+        <button
+          className={selectedFilter === 'status' ? 'active' : ''}
+          onClick={this.onClick('status')}
+          title={intl.formatMessage(tooltips.statuses)}
+        >
+          <Icon id='home' icon={HomeIcon} />
+        </button>
+        <button
+          className={selectedFilter === 'follow' ? 'active' : ''}
+          onClick={this.onClick('follow')}
+          title={intl.formatMessage(tooltips.follows)}
+        >
+          <Icon id='user-plus' icon={PersonAddIcon} />
+        </button>
+      </div>
+    );
+    return renderedElement;
+  }
+
+}
+
+export default injectIntl(FilterBar);
diff --git a/app/javascript/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx
index 90c32c5642..7683176c1e 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.jsx
+++ b/app/javascript/mastodon/features/notifications/components/notification.jsx
@@ -20,9 +20,9 @@ import PersonIcon from '@/material-icons/400-24px/person-fill.svg?react';
 import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
 import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
 import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
-import { Account } from 'mastodon/components/account';
 import EmojiView from 'mastodon/components/emoji_view';
 import { Icon }  from 'mastodon/components/icon';
+import AccountContainer from 'mastodon/containers/account_container';
 import StatusContainer from 'mastodon/containers/status_container';
 import { me } from 'mastodon/initial_state';
 import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@@ -153,7 +153,7 @@ class Notification extends ImmutablePureComponent {
             </span>
           </div>
 
-          <Account id={account.get('id')} hidden={this.props.hidden} />
+          <AccountContainer id={account.get('id')} hidden={this.props.hidden} />
         </div>
       </HotKeys>
     );
@@ -173,7 +173,7 @@ class Notification extends ImmutablePureComponent {
             </span>
           </div>
 
-          <FollowRequestContainer id={account.get('id')} hidden={this.props.hidden} />
+          <FollowRequestContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />
         </div>
       </HotKeys>
     );
@@ -533,7 +533,7 @@ class Notification extends ImmutablePureComponent {
             </span>
           </div>
 
-          <Account id={account.get('id')} hidden={this.props.hidden} />
+          <AccountContainer id={account.get('id')} hidden={this.props.hidden} />
         </div>
       </HotKeys>
     );
@@ -590,11 +590,10 @@ class Notification extends ImmutablePureComponent {
       return this.renderStatusReference(notification, link);
     case 'status':
       return this.renderStatus(notification, link);
-    case 'list_status': {
+    case 'list_status':
       const list = notification.get('list');
       const listLink = <bdi><Link className='notification__display-name' href={`/lists/${list.get('id')}`} title={list.get('title')} to={`/lists/${list.get('id')}`}>{list.get('title')}</Link></bdi>;
       return this.renderListStatus(notification, listLink, link);
-    }
     case 'update':
       return this.renderUpdate(notification, link);
     case 'poll':
diff --git a/app/javascript/mastodon/features/notifications/components/notification_request.jsx b/app/javascript/mastodon/features/notifications/components/notification_request.jsx
index 381bb1153f..626929ae50 100644
--- a/app/javascript/mastodon/features/notifications/components/notification_request.jsx
+++ b/app/javascript/mastodon/features/notifications/components/notification_request.jsx
@@ -17,7 +17,7 @@ import { initReport } from 'mastodon/actions/reports';
 import { Avatar } from 'mastodon/components/avatar';
 import { CheckBox } from 'mastodon/components/check_box';
 import { IconButton } from 'mastodon/components/icon_button';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
+import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
 import { makeGetAccount } from 'mastodon/selectors';
 import { toCappedNumber } from 'mastodon/utils/numbers';
 
@@ -105,10 +105,11 @@ export const NotificationRequest = ({ id, accountId, notificationsCount, checked
 
       <div className='notification-request__actions'>
         <IconButton iconComponent={DeleteIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
-        <Dropdown
+        <DropdownMenuContainer
           items={menu}
-          icon='ellipsis-h'
+          icons='ellipsis-h'
           iconComponent={MoreHorizIcon}
+          direction='right'
           title={intl.formatMessage(messages.more)}
         />
       </div>
diff --git a/app/javascript/mastodon/features/notifications/components/relationships_severance_event.jsx b/app/javascript/mastodon/features/notifications/components/relationships_severance_event.jsx
index 65ccd7c276..3075aff31b 100644
--- a/app/javascript/mastodon/features/notifications/components/relationships_severance_event.jsx
+++ b/app/javascript/mastodon/features/notifications/components/relationships_severance_event.jsx
@@ -28,7 +28,7 @@ export const RelationshipsSeveranceEvent = ({ type, target, followingCount, foll
 
       <div className='notification-group__main'>
         <p>{intl.formatMessage(messages[type], { from: <strong>{domain}</strong>, target: <strong>{target}</strong>, followingCount, followersCount })}</p>
-        <a href='/severed_relationships' target='_blank' rel='noopener' className='link-button'><FormattedMessage id='notification.relationships_severance_event.learn_more' defaultMessage='Learn more' /></a>
+        <a href='/severed_relationships' target='_blank' rel='noopener noreferrer' className='link-button'><FormattedMessage id='notification.relationships_severance_event.learn_more' defaultMessage='Learn more' /></a>
       </div>
     </div>
   );
diff --git a/app/javascript/mastodon/features/notifications/components/report.jsx b/app/javascript/mastodon/features/notifications/components/report.jsx
index ed043ae789..52d6bfee9d 100644
--- a/app/javascript/mastodon/features/notifications/components/report.jsx
+++ b/app/javascript/mastodon/features/notifications/components/report.jsx
@@ -55,7 +55,7 @@ class Report extends ImmutablePureComponent {
           </div>
 
           <div className='notification__report__actions'>
-            <a href={`/admin/reports/${report.get('id')}`} className='button' target='_blank' rel='noopener'>{intl.formatMessage(messages.openReport)}</a>
+            <a href={`/admin/reports/${report.get('id')}`} className='button' target='_blank' rel='noopener noreferrer'>{intl.formatMessage(messages.openReport)}</a>
           </div>
         </div>
       </div>
diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
index eddd35df4a..4ac6cfa629 100644
--- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
+++ b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
@@ -3,10 +3,10 @@ import { defineMessages, injectIntl } from 'react-intl';
 import { connect } from 'react-redux';
 
 import { openModal } from 'mastodon/actions/modal';
-import { fetchNotifications , setNotificationsFilter } from 'mastodon/actions/notification_groups';
+import { initializeNotifications } from 'mastodon/actions/notifications_migration';
 
 import { showAlert } from '../../../actions/alerts';
-import { requestBrowserPermission } from '../../../actions/notifications';
+import { setFilter, requestBrowserPermission } from '../../../actions/notifications';
 import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications';
 import { changeSetting } from '../../../actions/settings';
 import ColumnSettings from '../components/column_settings';
@@ -43,7 +43,7 @@ const mapDispatchToProps = (dispatch) => ({
       }
     } else if (path[0] === 'quickFilter') {
       dispatch(changeSetting(['notifications', ...path], checked));
-      dispatch(setNotificationsFilter('all'));
+      dispatch(setFilter('all'));
     } else if (path[0] === 'alerts' && checked && typeof window.Notification !== 'undefined' && Notification.permission !== 'granted') {
       if (checked && typeof window.Notification !== 'undefined' && Notification.permission !== 'granted') {
         dispatch(requestBrowserPermission((permission) => {
@@ -60,7 +60,7 @@ const mapDispatchToProps = (dispatch) => ({
       dispatch(changeSetting(['notifications', ...path], checked));
 
       if(path[0] === 'group' && path[1] === 'follow') {
-        dispatch(fetchNotifications());
+        dispatch(initializeNotifications());
       }
     }
   },
diff --git a/app/javascript/mastodon/features/notifications/containers/filter_bar_container.js b/app/javascript/mastodon/features/notifications/containers/filter_bar_container.js
new file mode 100644
index 0000000000..4e0184cef3
--- /dev/null
+++ b/app/javascript/mastodon/features/notifications/containers/filter_bar_container.js
@@ -0,0 +1,17 @@
+import { connect } from 'react-redux';
+
+import { setFilter } from '../../../actions/notifications';
+import FilterBar from '../components/filter_bar';
+
+const makeMapStateToProps = state => ({
+  selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
+  advancedMode: state.getIn(['settings', 'notifications', 'quickFilter', 'advanced']),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+  selectFilter (newActiveFilter) {
+    dispatch(setFilter(newActiveFilter));
+  },
+});
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(FilterBar);
diff --git a/app/javascript/mastodon/features/notifications/index.jsx b/app/javascript/mastodon/features/notifications/index.jsx
new file mode 100644
index 0000000000..cefbd544b0
--- /dev/null
+++ b/app/javascript/mastodon/features/notifications/index.jsx
@@ -0,0 +1,308 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import { createSelector } from '@reduxjs/toolkit';
+import { List as ImmutableList } from 'immutable';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
+
+import { debounce } from 'lodash';
+
+import DoneAllIcon from '@/material-icons/400-24px/done_all.svg?react';
+import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
+import { compareId } from 'mastodon/compare_id';
+import { Icon }  from 'mastodon/components/icon';
+import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
+import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
+
+import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
+import { submitMarkers } from '../../actions/markers';
+import {
+  expandNotifications,
+  scrollTopNotifications,
+  loadPending,
+  mountNotifications,
+  unmountNotifications,
+  markNotificationsAsRead,
+} from '../../actions/notifications';
+import Column from '../../components/column';
+import ColumnHeader from '../../components/column_header';
+import { LoadGap } from '../../components/load_gap';
+import ScrollableList from '../../components/scrollable_list';
+
+import {
+  FilteredNotificationsBanner,
+  FilteredNotificationsIconButton,
+} from './components/filtered_notifications_banner';
+import NotificationsPermissionBanner from './components/notifications_permission_banner';
+import ColumnSettingsContainer from './containers/column_settings_container';
+import FilterBarContainer from './containers/filter_bar_container';
+import NotificationContainer from './containers/notification_container';
+
+const messages = defineMessages({
+  title: { id: 'column.notifications', defaultMessage: 'Notifications' },
+  markAsRead : { id: 'notifications.mark_as_read', defaultMessage: 'Mark every notification as read' },
+});
+
+const getExcludedTypes = createSelector([
+  state => state.getIn(['settings', 'notifications', 'shows']),
+], (shows) => {
+  return ImmutableList(shows.filter(item => !item).keys());
+});
+
+const getNotifications = createSelector([
+  state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
+  state => state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
+  getExcludedTypes,
+  state => state.getIn(['notifications', 'items']),
+], (showFilterBar, allowedType, excludedTypes, notifications) => {
+  if (!showFilterBar || allowedType === 'all') {
+    // used if user changed the notification settings after loading the notifications from the server
+    // otherwise a list of notifications will come pre-filtered from the backend
+    // we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category
+    return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')));
+  }
+  return notifications.filter(item => item === null || allowedType === item.get('type'));
+});
+
+const mapStateToProps = state => ({
+  notifications: getNotifications(state),
+  isLoading: state.getIn(['notifications', 'isLoading'], 0) > 0,
+  isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0,
+  hasMore: state.getIn(['notifications', 'hasMore']),
+  numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
+  lastReadId: state.getIn(['settings', 'notifications', 'showUnread']) ? state.getIn(['notifications', 'readMarkerId']) : '0',
+  canMarkAsRead: state.getIn(['settings', 'notifications', 'showUnread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
+  needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']),
+});
+
+class Notifications extends PureComponent {
+  static propTypes = {
+    identity: identityContextPropShape,
+    columnId: PropTypes.string,
+    notifications: ImmutablePropTypes.list.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    isLoading: PropTypes.bool,
+    isUnread: PropTypes.bool,
+    multiColumn: PropTypes.bool,
+    hasMore: PropTypes.bool,
+    numPending: PropTypes.number,
+    lastReadId: PropTypes.string,
+    canMarkAsRead: PropTypes.bool,
+    needsNotificationPermission: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    trackScroll: true,
+  };
+
+  UNSAFE_componentWillMount() {
+    this.props.dispatch(mountNotifications());
+  }
+
+  componentWillUnmount () {
+    this.handleLoadOlder.cancel();
+    this.handleScrollToTop.cancel();
+    this.handleScroll.cancel();
+    this.props.dispatch(scrollTopNotifications(false));
+    this.props.dispatch(unmountNotifications());
+  }
+
+  handleLoadGap = (maxId) => {
+    this.props.dispatch(expandNotifications({ maxId }));
+  };
+
+  handleLoadOlder = debounce(() => {
+    const last = this.props.notifications.last();
+    this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }));
+  }, 300, { leading: true });
+
+  handleLoadPending = () => {
+    this.props.dispatch(loadPending());
+  };
+
+  handleScrollToTop = debounce(() => {
+    this.props.dispatch(scrollTopNotifications(true));
+  }, 100);
+
+  handleScroll = debounce(() => {
+    this.props.dispatch(scrollTopNotifications(false));
+  }, 100);
+
+  handlePin = () => {
+    const { columnId, dispatch } = this.props;
+
+    if (columnId) {
+      dispatch(removeColumn(columnId));
+    } else {
+      dispatch(addColumn('NOTIFICATIONS', {}));
+    }
+  };
+
+  handleMove = (dir) => {
+    const { columnId, dispatch } = this.props;
+    dispatch(moveColumn(columnId, dir));
+  };
+
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  };
+
+  setColumnRef = c => {
+    this.column = c;
+  };
+
+  handleMoveUp = id => {
+    const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
+    this._selectChild(elementIndex, true);
+  };
+
+  handleMoveDown = id => {
+    const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
+    this._selectChild(elementIndex, false);
+  };
+
+  _selectChild (index, align_top) {
+    const container = this.column.node;
+    const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
+
+    if (element) {
+      if (align_top && container.scrollTop > element.offsetTop) {
+        element.scrollIntoView(true);
+      } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
+        element.scrollIntoView(false);
+      }
+      element.focus();
+    }
+  }
+
+  handleMarkAsRead = () => {
+    this.props.dispatch(markNotificationsAsRead());
+    this.props.dispatch(submitMarkers({ immediate: true }));
+  };
+
+  render () {
+    const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
+    const pinned = !!columnId;
+    const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
+    const { signedIn } = this.props.identity;
+
+    let scrollableContent = null;
+
+    const filterBarContainer = signedIn
+      ? (<FilterBarContainer />)
+      : null;
+
+    if (isLoading && this.scrollableContent) {
+      scrollableContent = this.scrollableContent;
+    } else if (notifications.size > 0 || hasMore) {
+      scrollableContent = notifications.map((item, index) => item === null ? (
+        <LoadGap
+          key={'gap:' + notifications.getIn([index + 1, 'id'])}
+          disabled={isLoading}
+          param={index > 0 ? notifications.getIn([index - 1, 'id']) : null}
+          onClick={this.handleLoadGap}
+        />
+      ) : (
+        <NotificationContainer
+          key={item.get('id')}
+          notification={item}
+          accountId={item.get('account')}
+          onMoveUp={this.handleMoveUp}
+          onMoveDown={this.handleMoveDown}
+          unread={lastReadId !== '0' && compareId(item.get('id'), lastReadId) > 0}
+        />
+      ));
+    } else {
+      scrollableContent = null;
+    }
+
+    this.scrollableContent = scrollableContent;
+
+    let scrollContainer;
+
+    const prepend = (
+      <>
+        {needsNotificationPermission && <NotificationsPermissionBanner />}
+        <FilteredNotificationsBanner />
+      </>
+    );
+
+    if (signedIn) {
+      scrollContainer = (
+        <ScrollableList
+          scrollKey={`notifications-${columnId}`}
+          trackScroll={!pinned}
+          isLoading={isLoading}
+          showLoading={isLoading && notifications.size === 0}
+          hasMore={hasMore}
+          numPending={numPending}
+          prepend={prepend}
+          alwaysPrepend
+          emptyMessage={emptyMessage}
+          onLoadMore={this.handleLoadOlder}
+          onLoadPending={this.handleLoadPending}
+          onScrollToTop={this.handleScrollToTop}
+          onScroll={this.handleScroll}
+          bindToDocument={!multiColumn}
+        >
+          {scrollableContent}
+        </ScrollableList>
+      );
+    } else {
+      scrollContainer = <NotSignedInIndicator />;
+    }
+
+    const extraButton = (
+      <>
+        <FilteredNotificationsIconButton className='column-header__button' />
+        {canMarkAsRead && (
+          <button
+            aria-label={intl.formatMessage(messages.markAsRead)}
+            title={intl.formatMessage(messages.markAsRead)}
+            onClick={this.handleMarkAsRead}
+            className='column-header__button'
+          >
+            <Icon id='done-all' icon={DoneAllIcon} />
+          </button>
+        )}
+      </>
+    );
+
+    return (
+      <Column bindToDocument={!multiColumn} ref={this.setColumnRef} label={intl.formatMessage(messages.title)}>
+        <ColumnHeader
+          icon='bell'
+          iconComponent={NotificationsIcon}
+          active={isUnread}
+          title={intl.formatMessage(messages.title)}
+          onPin={this.handlePin}
+          onMove={this.handleMove}
+          onClick={this.handleHeaderClick}
+          pinned={pinned}
+          multiColumn={multiColumn}
+          extraButton={extraButton}
+        >
+          <ColumnSettingsContainer />
+        </ColumnHeader>
+
+        {filterBarContainer}
+
+        {scrollContainer}
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.title)}</title>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(withIdentity(injectIntl(Notifications)));
diff --git a/app/javascript/mastodon/features/notifications/requests.jsx b/app/javascript/mastodon/features/notifications/requests.jsx
index b2bdd0ec77..ccaed312b4 100644
--- a/app/javascript/mastodon/features/notifications/requests.jsx
+++ b/app/javascript/mastodon/features/notifications/requests.jsx
@@ -23,7 +23,7 @@ import Column from 'mastodon/components/column';
 import ColumnHeader from 'mastodon/components/column_header';
 import { Icon } from 'mastodon/components/icon';
 import ScrollableList from 'mastodon/components/scrollable_list';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
+import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
 
 import { NotificationRequest } from './components/notification_request';
 import { PolicyControls } from './components/policy_controls';
@@ -126,7 +126,7 @@ const SelectRow = ({selectAllChecked, toggleSelectAll, selectedItems, selectionM
       <div className='column-header__select-row__checkbox'>
         <CheckBox checked={selectAllChecked} indeterminate={selectedCount > 0 && !selectAllChecked} onChange={handleSelectAll} />
       </div>
-      <Dropdown
+      <DropdownMenuContainer
         items={menu}
         icons='ellipsis-h'
         iconComponent={MoreHorizIcon}
@@ -139,7 +139,7 @@ const SelectRow = ({selectAllChecked, toggleSelectAll, selectedItems, selectionM
           </span>
           <Icon id='down' icon={ArrowDropDownIcon} />
         </button>
-      </Dropdown>
+      </DropdownMenuContainer>
       <div className='column-header__select-row__mode-button'>
         <button className='text-btn' tabIndex={0} onClick={handleToggleSelectionMode}>
           {selectionMode ? (
diff --git a/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx b/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx
index 88e65c668b..65ea9b5d5e 100644
--- a/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx
+++ b/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx
@@ -43,7 +43,7 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({
   );
 
   const handleMouseUp = useCallback<React.MouseEventHandler<HTMLDivElement>>(
-    ({ clientX, clientY, target, button, ctrlKey, metaKey }) => {
+    ({ clientX, clientY, target, button }) => {
       const [startX, startY] = clickCoordinatesRef.current ?? [0, 0];
       const [deltaX, deltaY] = [
         Math.abs(clientX - startX),
@@ -64,14 +64,8 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({
         element = element.parentNode as HTMLDivElement | null;
       }
 
-      if (deltaX + deltaY < 5 && account) {
-        const path = `/@${account.acct}/${statusId}`;
-
-        if (button === 0 && !(ctrlKey || metaKey)) {
-          history.push(path);
-        } else if (button === 1 || (button === 0 && (ctrlKey || metaKey))) {
-          window.open(path, '_blank', 'noopener');
-        }
+      if (deltaX + deltaY < 5 && button === 0 && account) {
+        history.push(`/@${account.acct}/${statusId}`);
       }
 
       clickCoordinatesRef.current = null;
diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_annual_report.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_annual_report.tsx
deleted file mode 100644
index 8b92f31add..0000000000
--- a/app/javascript/mastodon/features/notifications_v2/components/notification_annual_report.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { useCallback } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import classNames from 'classnames';
-
-import CelebrationIcon from '@/material-icons/400-24px/celebration.svg?react';
-import { openModal } from 'mastodon/actions/modal';
-import { Icon } from 'mastodon/components/icon';
-import type { NotificationGroupAnnualReport } from 'mastodon/models/notification_group';
-import { useAppDispatch } from 'mastodon/store';
-
-export const NotificationAnnualReport: React.FC<{
-  notification: NotificationGroupAnnualReport;
-  unread: boolean;
-}> = ({ notification: { annualReport }, unread }) => {
-  const dispatch = useAppDispatch();
-  const year = annualReport.year;
-
-  const handleClick = useCallback(() => {
-    dispatch(
-      openModal({
-        modalType: 'ANNUAL_REPORT',
-        modalProps: { year },
-      }),
-    );
-  }, [dispatch, year]);
-
-  return (
-    <div
-      role='button'
-      className={classNames(
-        'notification-group notification-group--link notification-group--annual-report focusable',
-        { 'notification-group--unread': unread },
-      )}
-      tabIndex={0}
-    >
-      <div className='notification-group__icon'>
-        <Icon id='celebration' icon={CelebrationIcon} />
-      </div>
-
-      <div className='notification-group__main'>
-        <p>
-          <FormattedMessage
-            id='notification.annual_report.message'
-            defaultMessage="Your {year} #Wrapstodon awaits! Unveil your year's highlights and memorable moments on Mastodon!"
-            values={{ year }}
-          />
-        </p>
-        <button onClick={handleClick} className='link-button'>
-          <FormattedMessage
-            id='notification.annual_report.view'
-            defaultMessage='View #Wrapstodon'
-          />
-        </button>
-      </div>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_favourite.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_favourite.tsx
index 23ead604ba..49866540e5 100644
--- a/app/javascript/mastodon/features/notifications_v2/components/notification_favourite.tsx
+++ b/app/javascript/mastodon/features/notifications_v2/components/notification_favourite.tsx
@@ -33,34 +33,6 @@ const labelRenderer: LabelRenderer = (displayedName, total, seeMoreHref) => {
   );
 };
 
-const privateLabelRenderer: LabelRenderer = (
-  displayedName,
-  total,
-  seeMoreHref,
-) => {
-  if (total === 1)
-    return (
-      <FormattedMessage
-        id='notification.favourite_pm'
-        defaultMessage='{name} favorited your private mention'
-        values={{ name: displayedName }}
-      />
-    );
-
-  return (
-    <FormattedMessage
-      id='notification.favourite_pm.name_and_others_with_link'
-      defaultMessage='{name} and <a>{count, plural, one {# other} other {# others}}</a> favorited your private mention'
-      values={{
-        name: displayedName,
-        count: total - 1,
-        a: (chunks) =>
-          seeMoreHref ? <Link to={seeMoreHref}>{chunks}</Link> : chunks,
-      }}
-    />
-  );
-};
-
 export const NotificationFavourite: React.FC<{
   notification: NotificationGroupFavourite;
   unread: boolean;
@@ -72,10 +44,6 @@ export const NotificationFavourite: React.FC<{
         ?.acct,
   );
 
-  const isPrivateMention = useAppSelector(
-    (state) => state.statuses.getIn([statusId, 'visibility']) === 'direct',
-  );
-
   return (
     <NotificationGroupWithStatus
       type='favourite'
@@ -85,7 +53,7 @@ export const NotificationFavourite: React.FC<{
       statusId={notification.statusId}
       timestamp={notification.latest_page_notification_at}
       count={notification.notifications_count}
-      labelRenderer={isPrivateMention ? privateLabelRenderer : labelRenderer}
+      labelRenderer={labelRenderer}
       labelSeeMoreHref={
         statusAccount ? `/@${statusAccount}/${statusId}/favourites` : undefined
       }
diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_follow.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_follow.tsx
index 3c6350cf9a..a00f6267c5 100644
--- a/app/javascript/mastodon/features/notifications_v2/components/notification_follow.tsx
+++ b/app/javascript/mastodon/features/notifications_v2/components/notification_follow.tsx
@@ -1,5 +1,3 @@
-import type { JSX } from 'react';
-
 import { FormattedMessage } from 'react-intl';
 
 import { Link } from 'react-router-dom';
@@ -44,11 +42,13 @@ const FollowerCount: React.FC<{ accountId: string }> = ({ accountId }) => {
 
   if (!account) return null;
 
+  const isHide = account.other_settings.hide_followers_count;
+
   return (
     <ShortNumber
       value={account.followers_count}
       renderer={FollowersCounter}
-      isHide={account.other_settings.hide_followers_count}
+      isHide={isHide}
     />
   );
 };
@@ -69,9 +69,7 @@ export const NotificationFollow: React.FC<{
     const account = notification.sampleAccountIds[0];
 
     if (account) {
-      actions = (
-        <FollowButton compact accountId={notification.sampleAccountIds[0]} />
-      );
+      actions = <FollowButton accountId={notification.sampleAccountIds[0]} />;
       additionalContent = <FollowerCount accountId={account} />;
     }
   }
diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx
index 76285bea62..317ed56f42 100644
--- a/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx
+++ b/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx
@@ -9,7 +9,6 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
 
 import { NotificationAdminReport } from './notification_admin_report';
 import { NotificationAdminSignUp } from './notification_admin_sign_up';
-import { NotificationAnnualReport } from './notification_annual_report';
 import { NotificationEmojiReaction } from './notification_emoji_reaction';
 import { NotificationFavourite } from './notification_favourite';
 import { NotificationFollow } from './notification_follow';
@@ -171,14 +170,6 @@ export const NotificationGroup: React.FC<{
         />
       );
       break;
-    case 'annual_report':
-      content = (
-        <NotificationAnnualReport
-          unread={unread}
-          notification={notificationGroup}
-        />
-      );
-      break;
     default:
       return null;
   }
diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx
index 59985bad1c..d19cfc0abf 100644
--- a/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx
+++ b/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx
@@ -1,5 +1,4 @@
 import { useMemo } from 'react';
-import type { JSX } from 'react';
 
 import classNames from 'classnames';
 
diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_list_status.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_list_status.tsx
index 362b61da01..dececeee14 100644
--- a/app/javascript/mastodon/features/notifications_v2/components/notification_list_status.tsx
+++ b/app/javascript/mastodon/features/notifications_v2/components/notification_list_status.tsx
@@ -1,5 +1,3 @@
-import type { JSX } from 'react';
-
 import { FormattedMessage } from 'react-intl';
 
 import { Link } from 'react-router-dom';
diff --git a/app/javascript/mastodon/features/notifications_v2/index.tsx b/app/javascript/mastodon/features/notifications_v2/index.tsx
index bb476fe51f..730d95bcd5 100644
--- a/app/javascript/mastodon/features/notifications_v2/index.tsx
+++ b/app/javascript/mastodon/features/notifications_v2/index.tsx
@@ -36,8 +36,7 @@ import { useAppDispatch, useAppSelector } from 'mastodon/store';
 
 import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
 import { submitMarkers } from '../../actions/markers';
-import { Column } from '../../components/column';
-import type { ColumnRef } from '../../components/column';
+import Column from '../../components/column';
 import { ColumnHeader } from '../../components/column_header';
 import { LoadGap } from '../../components/load_gap';
 import ScrollableList from '../../components/scrollable_list';
@@ -97,7 +96,7 @@ export const Notifications: React.FC<{
     selectNeedsNotificationPermission,
   );
 
-  const columnRef = useRef<ColumnRef>(null);
+  const columnRef = useRef<Column>(null);
 
   const selectChild = useCallback((index: number, alignTop: boolean) => {
     const container = columnRef.current?.node as HTMLElement | undefined;
diff --git a/app/javascript/mastodon/features/notifications_wrapper.jsx b/app/javascript/mastodon/features/notifications_wrapper.jsx
new file mode 100644
index 0000000000..50383d5ebf
--- /dev/null
+++ b/app/javascript/mastodon/features/notifications_wrapper.jsx
@@ -0,0 +1,9 @@
+import Notifications_v2 from 'mastodon/features/notifications_v2';
+
+export const NotificationsWrapper = (props) => {
+  return (
+    <Notifications_v2 {...props} />
+  );
+};
+
+export default NotificationsWrapper;
\ No newline at end of file
diff --git a/app/javascript/mastodon/features/onboarding/components/step.jsx b/app/javascript/mastodon/features/onboarding/components/step.jsx
new file mode 100644
index 0000000000..a2a1653b8a
--- /dev/null
+++ b/app/javascript/mastodon/features/onboarding/components/step.jsx
@@ -0,0 +1,57 @@
+import PropTypes from 'prop-types';
+
+import { Link } from 'react-router-dom';
+
+import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react';
+import CheckIcon from '@/material-icons/400-24px/done.svg?react';
+import { Icon } from 'mastodon/components/icon';
+
+export const Step = ({ label, description, icon, iconComponent, completed, onClick, href, to }) => {
+  const content = (
+    <>
+      <div className='onboarding__steps__item__icon'>
+        <Icon id={icon} icon={iconComponent} />
+      </div>
+
+      <div className='onboarding__steps__item__description'>
+        <h6>{label}</h6>
+        <p>{description}</p>
+      </div>
+
+      <div className={completed ? 'onboarding__steps__item__progress' : 'onboarding__steps__item__go'}>
+        {completed ? <Icon icon={CheckIcon} /> : <Icon icon={ArrowRightAltIcon} />}
+      </div>
+    </>
+  );
+
+  if (href) {
+    return (
+      <a href={href} onClick={onClick} target='_blank' rel='noopener' className='onboarding__steps__item'>
+        {content}
+      </a>
+    );
+  } else if (to) {
+    return (
+      <Link to={to} className='onboarding__steps__item'>
+        {content}
+      </Link>
+    );
+  }
+
+  return (
+    <button onClick={onClick} className='onboarding__steps__item'>
+      {content}
+    </button>
+  );
+};
+
+Step.propTypes = {
+  label: PropTypes.node,
+  description: PropTypes.node,
+  icon: PropTypes.string,
+  iconComponent: PropTypes.func,
+  completed: PropTypes.bool,
+  href: PropTypes.string,
+  to: PropTypes.string,
+  onClick: PropTypes.func,
+};
diff --git a/app/javascript/mastodon/features/onboarding/follows.jsx b/app/javascript/mastodon/features/onboarding/follows.jsx
new file mode 100644
index 0000000000..e23a335c06
--- /dev/null
+++ b/app/javascript/mastodon/features/onboarding/follows.jsx
@@ -0,0 +1,62 @@
+import { useEffect } from 'react';
+
+import { FormattedMessage } from 'react-intl';
+
+import { Link } from 'react-router-dom';
+
+import { useDispatch } from 'react-redux';
+
+
+import { fetchSuggestions } from 'mastodon/actions/suggestions';
+import { markAsPartial } from 'mastodon/actions/timelines';
+import { ColumnBackButton } from 'mastodon/components/column_back_button';
+import { EmptyAccount } from 'mastodon/components/empty_account';
+import Account from 'mastodon/containers/account_container';
+import { useAppSelector } from 'mastodon/store';
+
+export const Follows = () => {
+  const dispatch = useDispatch();
+  const isLoading = useAppSelector(state => state.getIn(['suggestions', 'isLoading']));
+  const suggestions = useAppSelector(state => state.getIn(['suggestions', 'items']));
+
+  useEffect(() => {
+    dispatch(fetchSuggestions(true));
+
+    return () => {
+      dispatch(markAsPartial('home'));
+    };
+  }, [dispatch]);
+
+  let loadedContent;
+
+  if (isLoading) {
+    loadedContent = (new Array(8)).fill().map((_, i) => <EmptyAccount key={i} />);
+  } else if (suggestions.isEmpty()) {
+    loadedContent = <div className='follow-recommendations__empty'><FormattedMessage id='onboarding.follows.empty' defaultMessage='Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.' /></div>;
+  } else {
+    loadedContent = suggestions.map(suggestion => <Account id={suggestion.get('account')} key={suggestion.get('account')} withBio />);
+  }
+
+  return (
+    <>
+      <ColumnBackButton />
+
+      <div className='scrollable privacy-policy'>
+        <div className='column-title'>
+          <h3><FormattedMessage id='onboarding.follows.title' defaultMessage='Popular on Mastodon' /></h3>
+          <p><FormattedMessage id='onboarding.follows.lead' defaultMessage='You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!' /></p>
+        </div>
+
+        <div className='follow-recommendations'>
+          {loadedContent}
+        </div>
+
+        <p className='onboarding__lead'><FormattedMessage id='onboarding.tips.accounts_from_other_servers' defaultMessage='<strong>Did you know?</strong> Since Mastodon is decentralized, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p>
+
+        <div className='onboarding__footer'>
+          <Link className='link-button' to='/start'><FormattedMessage id='onboarding.actions.back' defaultMessage='Take me back' /></Link>
+        </div>
+      </div>
+    </>
+  );
+};
diff --git a/app/javascript/mastodon/features/onboarding/follows.tsx b/app/javascript/mastodon/features/onboarding/follows.tsx
deleted file mode 100644
index 703a4449be..0000000000
--- a/app/javascript/mastodon/features/onboarding/follows.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import { useEffect, useState, useCallback, useRef } from 'react';
-
-import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { Link } from 'react-router-dom';
-
-import { useDebouncedCallback } from 'use-debounce';
-
-import PersonIcon from '@/material-icons/400-24px/person.svg?react';
-import { fetchRelationships } from 'mastodon/actions/accounts';
-import { importFetchedAccounts } from 'mastodon/actions/importer';
-import { fetchSuggestions } from 'mastodon/actions/suggestions';
-import { markAsPartial } from 'mastodon/actions/timelines';
-import { apiRequest } from 'mastodon/api';
-import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
-import { Account } from 'mastodon/components/account';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { ColumnSearchHeader } from 'mastodon/components/column_search_header';
-import ScrollableList from 'mastodon/components/scrollable_list';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-
-const messages = defineMessages({
-  title: {
-    id: 'onboarding.follows.title',
-    defaultMessage: 'Follow people to get started',
-  },
-  search: { id: 'onboarding.follows.search', defaultMessage: 'Search' },
-  back: { id: 'onboarding.follows.back', defaultMessage: 'Back' },
-});
-
-type Mode = 'remove' | 'add';
-
-export const Follows: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const isLoading = useAppSelector((state) => state.suggestions.isLoading);
-  const suggestions = useAppSelector((state) => state.suggestions.items);
-  const [searchAccountIds, setSearchAccountIds] = useState<string[]>([]);
-  const [mode, setMode] = useState<Mode>('remove');
-  const [isLoadingSearch, setIsLoadingSearch] = useState(false);
-  const [isSearching, setIsSearching] = useState(false);
-
-  useEffect(() => {
-    void dispatch(fetchSuggestions());
-
-    return () => {
-      dispatch(markAsPartial('home'));
-    };
-  }, [dispatch]);
-
-  const handleSearchClick = useCallback(() => {
-    setMode('add');
-  }, [setMode]);
-
-  const handleDismissSearchClick = useCallback(() => {
-    setMode('remove');
-    setIsSearching(false);
-  }, [setMode, setIsSearching]);
-
-  const searchRequestRef = useRef<AbortController | null>(null);
-
-  const handleSearch = useDebouncedCallback(
-    (value: string) => {
-      if (searchRequestRef.current) {
-        searchRequestRef.current.abort();
-      }
-
-      if (value.trim().length === 0) {
-        setIsSearching(false);
-        setSearchAccountIds([]);
-        return;
-      }
-
-      setIsSearching(true);
-      setIsLoadingSearch(true);
-
-      searchRequestRef.current = new AbortController();
-
-      void apiRequest<ApiAccountJSON[]>('GET', 'v1/accounts/search', {
-        signal: searchRequestRef.current.signal,
-        params: {
-          q: value,
-        },
-      })
-        .then((data) => {
-          dispatch(importFetchedAccounts(data));
-          dispatch(fetchRelationships(data.map((a) => a.id)));
-          setSearchAccountIds(data.map((a) => a.id));
-          setIsLoadingSearch(false);
-          return '';
-        })
-        .catch(() => {
-          setIsLoadingSearch(false);
-        });
-    },
-    500,
-    { leading: true, trailing: true },
-  );
-
-  let displayedAccountIds: string[];
-
-  if (mode === 'add' && isSearching) {
-    displayedAccountIds = searchAccountIds;
-  } else {
-    displayedAccountIds = suggestions.map(
-      (suggestion) => suggestion.account_id,
-    );
-  }
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(messages.title)}
-    >
-      <ColumnHeader
-        title={intl.formatMessage(messages.title)}
-        icon='person'
-        iconComponent={PersonIcon}
-        multiColumn={multiColumn}
-        showBackButton
-      />
-
-      <ColumnSearchHeader
-        placeholder={intl.formatMessage(messages.search)}
-        onBack={handleDismissSearchClick}
-        onActivate={handleSearchClick}
-        active={mode === 'add'}
-        onSubmit={handleSearch}
-      />
-
-      <ScrollableList
-        scrollKey='follow_recommendations'
-        trackScroll={!multiColumn}
-        bindToDocument={!multiColumn}
-        showLoading={
-          (isLoading || isLoadingSearch) && displayedAccountIds.length === 0
-        }
-        hasMore={false}
-        isLoading={isLoading || isLoadingSearch}
-        footer={
-          <>
-            {displayedAccountIds.length > 0 && <div className='spacer' />}
-
-            <div className='column-footer'>
-              <Link className='button button--block' to='/home'>
-                <FormattedMessage
-                  id='onboarding.follows.done'
-                  defaultMessage='Done'
-                />
-              </Link>
-            </div>
-          </>
-        }
-        emptyMessage={
-          mode === 'remove' ? (
-            <FormattedMessage
-              id='onboarding.follows.empty'
-              defaultMessage='Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.'
-            />
-          ) : (
-            <FormattedMessage
-              id='lists.no_results_found'
-              defaultMessage='No results found.'
-            />
-          )
-        }
-      >
-        {displayedAccountIds.map((accountId) => (
-          <Account id={accountId} key={accountId} withBio />
-        ))}
-      </ScrollableList>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.title)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default Follows;
diff --git a/app/javascript/mastodon/features/onboarding/index.jsx b/app/javascript/mastodon/features/onboarding/index.jsx
new file mode 100644
index 0000000000..1d4649f90b
--- /dev/null
+++ b/app/javascript/mastodon/features/onboarding/index.jsx
@@ -0,0 +1,98 @@
+import { useCallback } from 'react';
+
+import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+import { Link, Switch, Route } from 'react-router-dom';
+
+import { useDispatch } from 'react-redux';
+
+
+import illustration from '@/images/elephant_ui_conversation.svg';
+import AccountCircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
+import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react';
+import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
+import EditNoteIcon from '@/material-icons/400-24px/edit_note.svg?react';
+import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
+import { focusCompose } from 'mastodon/actions/compose';
+import { Icon }  from 'mastodon/components/icon';
+import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
+import Column from 'mastodon/features/ui/components/column';
+import { enableLocalTimeline, me } from 'mastodon/initial_state';
+import { useAppSelector } from 'mastodon/store';
+import { assetHost } from 'mastodon/utils/config';
+
+import { Step } from './components/step';
+import { Follows } from './follows';
+import { Profile } from './profile';
+import { Share } from './share';
+
+const messages = defineMessages({
+  template: { id: 'onboarding.compose.template', defaultMessage: 'Hello #Mastodon!' },
+});
+
+const Onboarding = () => {
+  const account = useAppSelector(state => state.getIn(['accounts', me]));
+  const dispatch = useDispatch();
+  const intl = useIntl();
+
+  const handleComposeClick = useCallback(() => {
+    dispatch(focusCompose(intl.formatMessage(messages.template)));
+  }, [dispatch, intl]);
+
+  return (
+    <Column>
+      {account ? (
+        <Switch>
+          <Route path='/start' exact>
+            <div className='scrollable privacy-policy'>
+              <div className='column-title'>
+                <img src={illustration} alt='' className='onboarding__illustration' />
+                <h3><FormattedMessage id='onboarding.start.title' defaultMessage="You've made it!" /></h3>
+                <p><FormattedMessage id='onboarding.start.lead' defaultMessage="Your new Mastodon account is ready to go. Here's how you can make the most of it:" /></p>
+              </div>
+
+              <div className='onboarding__steps'>
+                <Step to='/start/profile' completed={(!account.get('avatar').endsWith('missing.png')) || (account.get('display_name').length > 0 && account.get('note').length > 0)} icon='address-book-o' iconComponent={AccountCircleIcon} label={<FormattedMessage id='onboarding.steps.setup_profile.title' defaultMessage='Customize your profile' />} description={<FormattedMessage id='onboarding.steps.setup_profile.body' defaultMessage='Others are more likely to interact with you with a filled out profile.' />} />
+                <Step to='/start/follows' completed={(account.get('following_count') * 1) >= 1} icon='user-plus' iconComponent={PersonAddIcon} label={<FormattedMessage id='onboarding.steps.follow_people.title' defaultMessage='Find at least {count, plural, one {one person} other {# people}} to follow' values={{ count: 7 }} />} description={<FormattedMessage id='onboarding.steps.follow_people.body' defaultMessage="You curate your own home feed. Let's fill it with interesting people." />} />
+                <Step onClick={handleComposeClick} completed={(account.get('statuses_count') * 1) >= 1} icon='pencil-square-o' iconComponent={EditNoteIcon} label={<FormattedMessage id='onboarding.steps.publish_status.title' defaultMessage='Make your first post' />} description={<FormattedMessage id='onboarding.steps.publish_status.body' defaultMessage='Say hello to the world.' values={{ emoji: <img className='emojione' alt='🐘' src={`${assetHost}/emoji/1f418.svg`} /> }} />} />
+                <Step to='/start/share' icon='copy' iconComponent={ContentCopyIcon} label={<FormattedMessage id='onboarding.steps.share_profile.title' defaultMessage='Share your profile' />} description={<FormattedMessage id='onboarding.steps.share_profile.body' defaultMessage='Let your friends know how to find you on Mastodon!' />} />
+              </div>
+
+              <p className='onboarding__lead'><FormattedMessage id='onboarding.start.skip' defaultMessage="Don't need help getting started?" /></p>
+
+              <div className='onboarding__links'>
+                <Link to='/explore' className='onboarding__link'>
+                  <FormattedMessage id='onboarding.actions.go_to_explore' defaultMessage='Take me to trending' />
+                  <Icon icon={ArrowRightAltIcon} />
+                </Link>
+
+                {enableLocalTimeline && (
+                  <Link to='/public/local/fixed' className='onboarding__link'>
+                    <FormattedMessage id='onboarding.actions.go_to_local_timeline' defaultMessage='See posts from local' />
+                    <Icon icon={ArrowRightAltIcon} />
+                  </Link>
+                )}
+
+                <Link to='/home' className='onboarding__link'>
+                  <FormattedMessage id='onboarding.actions.go_to_home' defaultMessage='Take me to my home feed' />
+                  <Icon icon={ArrowRightAltIcon} />
+                </Link>
+              </div>
+            </div>
+          </Route>
+
+          <Route path='/start/profile' component={Profile} />
+          <Route path='/start/follows' component={Follows} />
+          <Route path='/start/share' component={Share} />
+        </Switch>
+      ) : <NotSignedInIndicator />}
+
+      <Helmet>
+        <meta name='robots' content='noindex' />
+      </Helmet>
+    </Column>
+  );
+};
+
+export default Onboarding;
diff --git a/app/javascript/mastodon/features/onboarding/profile.jsx b/app/javascript/mastodon/features/onboarding/profile.jsx
new file mode 100644
index 0000000000..14250ae39b
--- /dev/null
+++ b/app/javascript/mastodon/features/onboarding/profile.jsx
@@ -0,0 +1,162 @@
+import { useState, useMemo, useCallback, createRef } from 'react';
+
+import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
+
+import classNames from 'classnames';
+import { useHistory } from 'react-router-dom';
+
+
+import { useDispatch } from 'react-redux';
+
+import Toggle from 'react-toggle';
+
+import AddPhotoAlternateIcon from '@/material-icons/400-24px/add_photo_alternate.svg?react';
+import EditIcon from '@/material-icons/400-24px/edit.svg?react';
+import { updateAccount } from 'mastodon/actions/accounts';
+import { Button } from 'mastodon/components/button';
+import { ColumnBackButton } from 'mastodon/components/column_back_button';
+import { Icon } from 'mastodon/components/icon';
+import { LoadingIndicator } from 'mastodon/components/loading_indicator';
+import { me } from 'mastodon/initial_state';
+import { useAppSelector } from 'mastodon/store';
+import { unescapeHTML } from 'mastodon/utils/html';
+
+const messages = defineMessages({
+  uploadHeader: { id: 'onboarding.profile.upload_header', defaultMessage: 'Upload profile header' },
+  uploadAvatar: { id: 'onboarding.profile.upload_avatar', defaultMessage: 'Upload profile picture' },
+});
+
+const nullIfMissing = path => path.endsWith('missing.png') ? null : path;
+
+export const Profile = () => {
+  const account = useAppSelector(state => state.getIn(['accounts', me]));
+  const [displayName, setDisplayName] = useState(account.get('display_name'));
+  const [note, setNote] = useState(unescapeHTML(account.get('note')));
+  const [avatar, setAvatar] = useState(null);
+  const [header, setHeader] = useState(null);
+  const [discoverable, setDiscoverable] = useState(account.get('discoverable'));
+  const [isSaving, setIsSaving] = useState(false);
+  const [errors, setErrors] = useState();
+  const avatarFileRef = createRef();
+  const headerFileRef = createRef();
+  const dispatch = useDispatch();
+  const intl = useIntl();
+  const history = useHistory();
+
+  const handleDisplayNameChange = useCallback(e => {
+    setDisplayName(e.target.value);
+  }, [setDisplayName]);
+
+  const handleNoteChange = useCallback(e => {
+    setNote(e.target.value);
+  }, [setNote]);
+
+  const handleDiscoverableChange = useCallback(e => {
+    setDiscoverable(e.target.checked);
+  }, [setDiscoverable]);
+
+  const handleAvatarChange = useCallback(e => {
+    setAvatar(e.target?.files?.[0]);
+  }, [setAvatar]);
+
+  const handleHeaderChange = useCallback(e => {
+    setHeader(e.target?.files?.[0]);
+  }, [setHeader]);
+
+  const avatarPreview = useMemo(() => avatar ? URL.createObjectURL(avatar) : nullIfMissing(account.get('avatar')), [avatar, account]);
+  const headerPreview = useMemo(() => header ? URL.createObjectURL(header) : nullIfMissing(account.get('header')), [header, account]);
+
+  const handleSubmit = useCallback(() => {
+    setIsSaving(true);
+
+    dispatch(updateAccount({
+      displayName,
+      note,
+      avatar,
+      header,
+      discoverable,
+      indexable: discoverable,
+    })).then(() => history.push('/start/follows')).catch(err => {
+      setIsSaving(false);
+      setErrors(err.response.data.details);
+    });
+  }, [dispatch, displayName, note, avatar, header, discoverable, history]);
+
+  return (
+    <>
+      <ColumnBackButton />
+
+      <div className='scrollable privacy-policy'>
+        <div className='column-title'>
+          <h3><FormattedMessage id='onboarding.profile.title' defaultMessage='Profile setup' /></h3>
+          <p><FormattedMessage id='onboarding.profile.lead' defaultMessage='You can always complete this later in the settings, where even more customization options are available.' /></p>
+        </div>
+
+        <div className='simple_form'>
+          <div className='onboarding__profile'>
+            <label className={classNames('app-form__header-input', { selected: !!headerPreview, invalid: !!errors?.header })} title={intl.formatMessage(messages.uploadHeader)}>
+              <input
+                type='file'
+                hidden
+                ref={headerFileRef}
+                accept='image/*'
+                onChange={handleHeaderChange}
+              />
+
+              {headerPreview && <img src={headerPreview} alt='' />}
+
+              <Icon icon={headerPreview ? EditIcon : AddPhotoAlternateIcon} />
+            </label>
+
+            <label className={classNames('app-form__avatar-input', { selected: !!avatarPreview, invalid: !!errors?.avatar })} title={intl.formatMessage(messages.uploadAvatar)}>
+              <input
+                type='file'
+                hidden
+                ref={avatarFileRef}
+                accept='image/*'
+                onChange={handleAvatarChange}
+              />
+
+              {avatarPreview && <img src={avatarPreview} alt='' />}
+
+              <Icon icon={avatarPreview ? EditIcon : AddPhotoAlternateIcon} />
+            </label>
+          </div>
+
+          <div className={classNames('input with_block_label', { field_with_errors: !!errors?.display_name })}>
+            <label htmlFor='display_name'><FormattedMessage id='onboarding.profile.display_name' defaultMessage='Display name' /></label>
+            <span className='hint'><FormattedMessage id='onboarding.profile.display_name_hint' defaultMessage='Your full name or your fun name…' /></span>
+            <div className='label_input'>
+              <input id='display_name' type='text' value={displayName} onChange={handleDisplayNameChange} maxLength={30} />
+            </div>
+          </div>
+
+          <div className={classNames('input with_block_label', { field_with_errors: !!errors?.note })}>
+            <label htmlFor='note'><FormattedMessage id='onboarding.profile.note' defaultMessage='Bio' /></label>
+            <span className='hint'><FormattedMessage id='onboarding.profile.note_hint' defaultMessage='You can @mention other people or #hashtags…' /></span>
+            <div className='label_input'>
+              <textarea id='note' value={note} onChange={handleNoteChange} maxLength={500} />
+            </div>
+          </div>
+
+          <label className='app-form__toggle'>
+            <div className='app-form__toggle__label'>
+              <strong><FormattedMessage id='onboarding.profile.discoverable' defaultMessage='Make my profile discoverable' /></strong> <span className='recommended'><FormattedMessage id='recommended' defaultMessage='Recommended' /></span>
+              <span className='hint'><FormattedMessage id='onboarding.profile.discoverable_hint' defaultMessage='When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.' /></span>
+            </div>
+
+            <div className='app-form__toggle__toggle'>
+              <div>
+                <Toggle checked={discoverable} onChange={handleDiscoverableChange} />
+              </div>
+            </div>
+          </label>
+        </div>
+
+        <div className='onboarding__footer'>
+          <Button block onClick={handleSubmit} disabled={isSaving}>{isSaving ? <LoadingIndicator /> : <FormattedMessage id='onboarding.profile.save_and_continue' defaultMessage='Save and continue' />}</Button>
+        </div>
+      </div>
+    </>
+  );
+};
diff --git a/app/javascript/mastodon/features/onboarding/profile.tsx b/app/javascript/mastodon/features/onboarding/profile.tsx
deleted file mode 100644
index d9b394acfb..0000000000
--- a/app/javascript/mastodon/features/onboarding/profile.tsx
+++ /dev/null
@@ -1,333 +0,0 @@
-import { useState, useMemo, useCallback, createRef } from 'react';
-
-import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
-
-import classNames from 'classnames';
-import { Helmet } from 'react-helmet';
-import { useHistory } from 'react-router-dom';
-
-import Toggle from 'react-toggle';
-
-import AddPhotoAlternateIcon from '@/material-icons/400-24px/add_photo_alternate.svg?react';
-import EditIcon from '@/material-icons/400-24px/edit.svg?react';
-import PersonIcon from '@/material-icons/400-24px/person.svg?react';
-import { updateAccount } from 'mastodon/actions/accounts';
-import { closeOnboarding } from 'mastodon/actions/onboarding';
-import { Button } from 'mastodon/components/button';
-import { Column } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { Icon } from 'mastodon/components/icon';
-import { LoadingIndicator } from 'mastodon/components/loading_indicator';
-import { me } from 'mastodon/initial_state';
-import { useAppSelector, useAppDispatch } from 'mastodon/store';
-import { unescapeHTML } from 'mastodon/utils/html';
-
-const messages = defineMessages({
-  title: {
-    id: 'onboarding.profile.title',
-    defaultMessage: 'Profile setup',
-  },
-  uploadHeader: {
-    id: 'onboarding.profile.upload_header',
-    defaultMessage: 'Upload profile header',
-  },
-  uploadAvatar: {
-    id: 'onboarding.profile.upload_avatar',
-    defaultMessage: 'Upload profile picture',
-  },
-});
-
-const nullIfMissing = (path: string) =>
-  path.endsWith('missing.png') ? null : path;
-
-interface ApiAccountErrors {
-  display_name?: unknown;
-  note?: unknown;
-  avatar?: unknown;
-  header?: unknown;
-}
-
-export const Profile: React.FC<{
-  multiColumn?: boolean;
-}> = ({ multiColumn }) => {
-  const account = useAppSelector((state) =>
-    me ? state.accounts.get(me) : undefined,
-  );
-  const [displayName, setDisplayName] = useState(account?.display_name ?? '');
-  const [note, setNote] = useState(
-    account ? (unescapeHTML(account.note) ?? '') : '',
-  );
-  const [avatar, setAvatar] = useState<File>();
-  const [header, setHeader] = useState<File>();
-  const [discoverable, setDiscoverable] = useState(
-    account?.discoverable ?? true,
-  );
-  const [isSaving, setIsSaving] = useState(false);
-  const [errors, setErrors] = useState<ApiAccountErrors>();
-  const avatarFileRef = createRef<HTMLInputElement>();
-  const headerFileRef = createRef<HTMLInputElement>();
-  const dispatch = useAppDispatch();
-  const intl = useIntl();
-  const history = useHistory();
-
-  const handleDisplayNameChange = useCallback(
-    (e: React.ChangeEvent<HTMLInputElement>) => {
-      setDisplayName(e.target.value);
-    },
-    [setDisplayName],
-  );
-
-  const handleNoteChange = useCallback(
-    (e: React.ChangeEvent<HTMLTextAreaElement>) => {
-      setNote(e.target.value);
-    },
-    [setNote],
-  );
-
-  const handleDiscoverableChange = useCallback(
-    (e: React.ChangeEvent<HTMLInputElement>) => {
-      setDiscoverable(e.target.checked);
-    },
-    [setDiscoverable],
-  );
-
-  const handleAvatarChange = useCallback(
-    (e: React.ChangeEvent<HTMLInputElement>) => {
-      setAvatar(e.target.files?.[0]);
-    },
-    [setAvatar],
-  );
-
-  const handleHeaderChange = useCallback(
-    (e: React.ChangeEvent<HTMLInputElement>) => {
-      setHeader(e.target.files?.[0]);
-    },
-    [setHeader],
-  );
-
-  const avatarPreview = useMemo(
-    () =>
-      avatar
-        ? URL.createObjectURL(avatar)
-        : nullIfMissing(account?.avatar ?? 'missing.png'),
-    [avatar, account],
-  );
-  const headerPreview = useMemo(
-    () =>
-      header
-        ? URL.createObjectURL(header)
-        : nullIfMissing(account?.header ?? 'missing.png'),
-    [header, account],
-  );
-
-  const handleSubmit = useCallback(() => {
-    setIsSaving(true);
-
-    dispatch(
-      updateAccount({
-        displayName,
-        note,
-        avatar,
-        header,
-        discoverable,
-        indexable: discoverable,
-      }),
-    )
-      .then(() => {
-        history.push('/start/follows');
-        dispatch(closeOnboarding());
-        return '';
-      })
-      // eslint-disable-next-line @typescript-eslint/use-unknown-in-catch-callback-variable
-      .catch((err) => {
-        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
-        if (err.response) {
-          // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
-          const { details }: { details: ApiAccountErrors } = err.response.data;
-          setErrors(details);
-        }
-
-        setIsSaving(false);
-      });
-  }, [dispatch, displayName, note, avatar, header, discoverable, history]);
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(messages.title)}
-    >
-      <ColumnHeader
-        title={intl.formatMessage(messages.title)}
-        icon='person'
-        iconComponent={PersonIcon}
-        multiColumn={multiColumn}
-      />
-
-      <div className='scrollable scrollable--flex'>
-        <div className='simple_form app-form'>
-          <div className='onboarding__profile'>
-            <label
-              className={classNames('app-form__header-input', {
-                selected: !!headerPreview,
-                invalid: !!errors?.header,
-              })}
-              title={intl.formatMessage(messages.uploadHeader)}
-            >
-              <input
-                type='file'
-                hidden
-                ref={headerFileRef}
-                accept='image/*'
-                onChange={handleHeaderChange}
-              />
-
-              {headerPreview && <img src={headerPreview} alt='' />}
-
-              <Icon
-                id=''
-                icon={headerPreview ? EditIcon : AddPhotoAlternateIcon}
-              />
-            </label>
-
-            <label
-              className={classNames('app-form__avatar-input', {
-                selected: !!avatarPreview,
-                invalid: !!errors?.avatar,
-              })}
-              title={intl.formatMessage(messages.uploadAvatar)}
-            >
-              <input
-                type='file'
-                hidden
-                ref={avatarFileRef}
-                accept='image/*'
-                onChange={handleAvatarChange}
-              />
-
-              {avatarPreview && <img src={avatarPreview} alt='' />}
-
-              <Icon
-                id=''
-                icon={avatarPreview ? EditIcon : AddPhotoAlternateIcon}
-              />
-            </label>
-          </div>
-
-          <div className='fields-group'>
-            <div
-              className={classNames('input with_block_label', {
-                field_with_errors: !!errors?.display_name,
-              })}
-            >
-              <label htmlFor='display_name'>
-                <FormattedMessage
-                  id='onboarding.profile.display_name'
-                  defaultMessage='Display name'
-                />
-              </label>
-              <span className='hint'>
-                <FormattedMessage
-                  id='onboarding.profile.display_name_hint'
-                  defaultMessage='Your full name or your fun name…'
-                />
-              </span>
-              <div className='label_input'>
-                <input
-                  id='display_name'
-                  type='text'
-                  value={displayName}
-                  onChange={handleDisplayNameChange}
-                  maxLength={30}
-                />
-              </div>
-            </div>
-          </div>
-
-          <div className='fields-group'>
-            <div
-              className={classNames('input with_block_label', {
-                field_with_errors: !!errors?.note,
-              })}
-            >
-              <label htmlFor='note'>
-                <FormattedMessage
-                  id='onboarding.profile.note'
-                  defaultMessage='Bio'
-                />
-              </label>
-              <span className='hint'>
-                <FormattedMessage
-                  id='onboarding.profile.note_hint'
-                  defaultMessage='You can @mention other people or #hashtags…'
-                />
-              </span>
-              <div className='label_input'>
-                <textarea
-                  id='note'
-                  value={note}
-                  onChange={handleNoteChange}
-                  maxLength={500}
-                />
-              </div>
-            </div>
-          </div>
-
-          <label className='app-form__toggle'>
-            <div className='app-form__toggle__label'>
-              <strong>
-                <FormattedMessage
-                  id='onboarding.profile.discoverable'
-                  defaultMessage='Make my profile discoverable'
-                />
-              </strong>{' '}
-              <span className='recommended'>
-                <FormattedMessage
-                  id='recommended'
-                  defaultMessage='Recommended'
-                />
-              </span>
-              <span className='hint'>
-                <FormattedMessage
-                  id='onboarding.profile.discoverable_hint'
-                  defaultMessage='When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.'
-                />
-              </span>
-            </div>
-
-            <div className='app-form__toggle__toggle'>
-              <div>
-                <Toggle
-                  checked={discoverable}
-                  onChange={handleDiscoverableChange}
-                />
-              </div>
-            </div>
-          </label>
-        </div>
-
-        <div className='spacer' />
-
-        <div className='column-footer'>
-          <Button block onClick={handleSubmit} disabled={isSaving}>
-            {isSaving ? (
-              <LoadingIndicator />
-            ) : (
-              <FormattedMessage
-                id='onboarding.profile.save_and_continue'
-                defaultMessage='Save and continue'
-              />
-            )}
-          </Button>
-        </div>
-      </div>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.title)}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default Profile;
diff --git a/app/javascript/mastodon/features/onboarding/share.jsx b/app/javascript/mastodon/features/onboarding/share.jsx
new file mode 100644
index 0000000000..9c720e9074
--- /dev/null
+++ b/app/javascript/mastodon/features/onboarding/share.jsx
@@ -0,0 +1,120 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
+
+import classNames from 'classnames';
+import { Link } from 'react-router-dom';
+
+
+import SwipeableViews from 'react-swipeable-views';
+
+import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react';
+import { ColumnBackButton } from 'mastodon/components/column_back_button';
+import { CopyPasteText } from 'mastodon/components/copy_paste_text';
+import { Icon }  from 'mastodon/components/icon';
+import { me, domain } from 'mastodon/initial_state';
+import { useAppSelector } from 'mastodon/store';
+
+const messages = defineMessages({
+  shareableMessage: { id: 'onboarding.share.message', defaultMessage: 'I\'m {username} on #Mastodon! Come follow me at {url}' },
+});
+
+class TipCarousel extends PureComponent {
+
+  static propTypes = {
+    children: PropTypes.node,
+  };
+
+  state = {
+    index: 0,
+  };
+
+  handleSwipe = index => {
+    this.setState({ index });
+  };
+
+  handleChangeIndex = e => {
+    this.setState({ index: Number(e.currentTarget.getAttribute('data-index')) });
+  };
+
+  handleKeyDown = e => {
+    switch(e.key) {
+    case 'ArrowLeft':
+      e.preventDefault();
+      this.setState(({ index }, { children }) => ({ index: Math.abs(index - 1) % children.length }));
+      break;
+    case 'ArrowRight':
+      e.preventDefault();
+      this.setState(({ index }, { children }) => ({ index: (index + 1) % children.length }));
+      break;
+    }
+  };
+
+  render () {
+    const { children } = this.props;
+    const { index } = this.state;
+
+    return (
+      <div className='tip-carousel' tabIndex='0' onKeyDown={this.handleKeyDown}>
+        <SwipeableViews onChangeIndex={this.handleSwipe} index={index} enableMouseEvents tabIndex='-1'>
+          {children}
+        </SwipeableViews>
+
+        <div className='media-modal__pagination'>
+          {children.map((_, i) => (
+            <button key={i} className={classNames('media-modal__page-dot', { active: i === index })} data-index={i} onClick={this.handleChangeIndex}>
+              {i + 1}
+            </button>
+          ))}
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export const Share = () => {
+  const account = useAppSelector(state => state.getIn(['accounts', me]));
+  const intl = useIntl();
+  const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href;
+
+  return (
+    <>
+      <ColumnBackButton />
+
+      <div className='scrollable privacy-policy'>
+        <div className='column-title'>
+          <h3><FormattedMessage id='onboarding.share.title' defaultMessage='Share your profile' /></h3>
+          <p><FormattedMessage id='onboarding.share.lead' defaultMessage='Let people know how they can find you on Mastodon!' /></p>
+        </div>
+
+        <CopyPasteText value={intl.formatMessage(messages.shareableMessage, { username: `@${account.get('username')}@${domain}`, url })} />
+
+        <TipCarousel>
+          <div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.verification' defaultMessage='<strong>Did you know?</strong> You can verify your account by putting a link to your Mastodon profile on your own website and adding the website to your profile. No fees or documents necessary!'  values={{ strong: chunks => <strong>{chunks}</strong> }}  /></p></div>
+          <div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.migration' defaultMessage='<strong>Did you know?</strong> If you feel like {domain} is not a great server choice for you in the future, you can move to another Mastodon server without losing your followers. You can even host your own server!' values={{ domain, strong: chunks => <strong>{chunks}</strong> }} /></p></div>
+          <div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.2fa' defaultMessage='<strong>Did you know?</strong> You can secure your account by setting up two-factor authentication in your account settings. It works with any TOTP app of your choice, no phone number necessary!'  values={{ strong: chunks => <strong>{chunks}</strong> }}  /></p></div>
+        </TipCarousel>
+
+        <p className='onboarding__lead'><FormattedMessage id='onboarding.share.next_steps' defaultMessage='Possible next steps:' /></p>
+
+        <div className='onboarding__links'>
+          <Link to='/home' className='onboarding__link'>
+            <FormattedMessage id='onboarding.actions.go_to_home' defaultMessage='Take me to my home feed' />
+            <Icon icon={ArrowRightAltIcon} />
+          </Link>
+
+          <Link to='/explore' className='onboarding__link'>
+            <FormattedMessage id='onboarding.actions.go_to_explore' defaultMessage='Take me to trending' />
+            <Icon icon={ArrowRightAltIcon} />
+          </Link>
+        </div>
+
+        <div className='onboarding__footer'>
+          <Link className='link-button' to='/start'><FormattedMessage id='onboarding.action.back' defaultMessage='Take me back' /></Link>
+        </div>
+      </div>
+    </>
+  );
+};
diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx
index a5923d4b46..ec6d52c8cd 100644
--- a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx
+++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx
@@ -33,7 +33,6 @@ const messages = defineMessages({
   cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
   cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
   favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
-  removeFavourite: { id: 'status.remove_favourite', defaultMessage: 'Remove from favorites' },
   open: { id: 'status.open', defaultMessage: 'Expand this status' },
 });
 
@@ -178,13 +177,11 @@ class Footer extends ImmutablePureComponent {
       reblogIconComponent = RepeatDisabledIcon;
     }
 
-    const favouriteTitle = intl.formatMessage(status.get('favourited') ? messages.removeFavourite : messages.favourite);
-
     return (
       <div className='picture-in-picture__footer'>
         <IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
         <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate}  active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
-        <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={favouriteTitle} icon='star' iconComponent={StarIcon} onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
+        <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={StarIcon} onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
         {withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' iconComponent={OpenInNewIcon} onClick={this.handleOpenClick} href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} />}
       </div>
     );
diff --git a/app/javascript/mastodon/features/picture_in_picture/index.tsx b/app/javascript/mastodon/features/picture_in_picture/index.tsx
index 9bae1b5545..51b72f9725 100644
--- a/app/javascript/mastodon/features/picture_in_picture/index.tsx
+++ b/app/javascript/mastodon/features/picture_in_picture/index.tsx
@@ -2,7 +2,7 @@ import { useCallback } from 'react';
 
 import { removePictureInPicture } from 'mastodon/actions/picture_in_picture';
 import Audio from 'mastodon/features/audio';
-import { Video } from 'mastodon/features/video';
+import Video from 'mastodon/features/video';
 import { useAppDispatch, useAppSelector } from 'mastodon/store/typed_functions';
 
 import Footer from './components/footer';
@@ -35,10 +35,6 @@ export const PictureInPicture: React.FC = () => {
     accentColor,
   } = pipState;
 
-  if (!src) {
-    return null;
-  }
-
   let player;
 
   switch (type) {
@@ -46,10 +42,11 @@ export const PictureInPicture: React.FC = () => {
       player = (
         <Video
           src={src}
-          startTime={currentTime}
-          startVolume={volume}
-          startMuted={muted}
-          startPlaying
+          currentTime={currentTime}
+          volume={volume}
+          muted={muted}
+          autoPlay
+          inline
           alwaysVisible
         />
       );
diff --git a/app/javascript/mastodon/features/privacy_policy/index.jsx b/app/javascript/mastodon/features/privacy_policy/index.jsx
new file mode 100644
index 0000000000..d420546e4f
--- /dev/null
+++ b/app/javascript/mastodon/features/privacy_policy/index.jsx
@@ -0,0 +1,65 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { FormattedMessage, FormattedDate, injectIntl, defineMessages } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import api from 'mastodon/api';
+import Column from 'mastodon/components/column';
+import { Skeleton } from 'mastodon/components/skeleton';
+
+const messages = defineMessages({
+  title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' },
+});
+
+class PrivacyPolicy extends PureComponent {
+
+  static propTypes = {
+    intl: PropTypes.object,
+    multiColumn: PropTypes.bool,
+  };
+
+  state = {
+    content: null,
+    lastUpdated: null,
+    isLoading: true,
+  };
+
+  componentDidMount () {
+    api().get('/api/v1/instance/privacy_policy').then(({ data }) => {
+      this.setState({ content: data.content, lastUpdated: data.updated_at, isLoading: false });
+    }).catch(() => {
+      this.setState({ isLoading: false });
+    });
+  }
+
+  render () {
+    const { intl, multiColumn } = this.props;
+    const { isLoading, content, lastUpdated } = this.state;
+
+    return (
+      <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}>
+        <div className='scrollable privacy-policy'>
+          <div className='column-title'>
+            <h3><FormattedMessage id='privacy_policy.title' defaultMessage='Privacy Policy' /></h3>
+            <p><FormattedMessage id='privacy_policy.last_updated' defaultMessage='Last updated {date}' values={{ date: isLoading ? <Skeleton width='10ch' /> : <FormattedDate value={lastUpdated} year='numeric' month='short' day='2-digit' /> }} /></p>
+          </div>
+
+          <div
+            className='privacy-policy__body prose'
+            dangerouslySetInnerHTML={{ __html: content }}
+          />
+        </div>
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.title)}</title>
+          <meta name='robots' content='all' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default injectIntl(PrivacyPolicy);
diff --git a/app/javascript/mastodon/features/privacy_policy/index.tsx b/app/javascript/mastodon/features/privacy_policy/index.tsx
deleted file mode 100644
index cd6f9f3b2b..0000000000
--- a/app/javascript/mastodon/features/privacy_policy/index.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { useState, useEffect } from 'react';
-
-import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-
-import { apiGetPrivacyPolicy } from 'mastodon/api/instance';
-import type { ApiPrivacyPolicyJSON } from 'mastodon/api_types/instance';
-import { Column } from 'mastodon/components/column';
-import { FormattedDateWrapper } from 'mastodon/components/formatted_date';
-import { Skeleton } from 'mastodon/components/skeleton';
-
-const messages = defineMessages({
-  title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' },
-});
-
-const PrivacyPolicy: React.FC<{
-  multiColumn: boolean;
-}> = ({ multiColumn }) => {
-  const intl = useIntl();
-  const [response, setResponse] = useState<ApiPrivacyPolicyJSON>();
-  const [loading, setLoading] = useState(true);
-
-  useEffect(() => {
-    apiGetPrivacyPolicy()
-      .then((data) => {
-        setResponse(data);
-        setLoading(false);
-        return '';
-      })
-      .catch(() => {
-        setLoading(false);
-      });
-  }, []);
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(messages.title)}
-    >
-      <div className='scrollable privacy-policy'>
-        <div className='column-title'>
-          <h3>
-            <FormattedMessage
-              id='privacy_policy.title'
-              defaultMessage='Privacy Policy'
-            />
-          </h3>
-          <p>
-            <FormattedMessage
-              id='privacy_policy.last_updated'
-              defaultMessage='Last updated {date}'
-              values={{
-                date: loading ? (
-                  <Skeleton width='10ch' />
-                ) : (
-                  <FormattedDateWrapper
-                    value={response?.updated_at}
-                    year='numeric'
-                    month='short'
-                    day='2-digit'
-                  />
-                ),
-              }}
-            />
-          </p>
-        </div>
-
-        {response && (
-          <div
-            className='privacy-policy__body prose'
-            dangerouslySetInnerHTML={{ __html: response.content }}
-          />
-        )}
-      </div>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.title)}</title>
-        <meta name='robots' content='all' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default PrivacyPolicy;
diff --git a/app/javascript/mastodon/features/public_timeline/index.jsx b/app/javascript/mastodon/features/public_timeline/index.jsx
index aa5a02645d..91351901f5 100644
--- a/app/javascript/mastodon/features/public_timeline/index.jsx
+++ b/app/javascript/mastodon/features/public_timeline/index.jsx
@@ -142,7 +142,7 @@ class PublicTimeline extends PureComponent {
         </ColumnHeader>
 
         <StatusListContainer
-          prepend={<DismissableBanner id='public_timeline'><FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on the fediverse that people on {domain} follow.' values={{ domain }} /></DismissableBanner>}
+          prepend={<DismissableBanner id='public_timeline'><FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on the social web that people on {domain} follow.' values={{ domain }} /></DismissableBanner>}
           timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`}
           onLoadMore={this.handleLoadMore}
           trackScroll={!pinned}
diff --git a/app/javascript/mastodon/features/reblogs/index.jsx b/app/javascript/mastodon/features/reblogs/index.jsx
index 8bde919a0f..3d1fc94cb7 100644
--- a/app/javascript/mastodon/features/reblogs/index.jsx
+++ b/app/javascript/mastodon/features/reblogs/index.jsx
@@ -11,13 +11,13 @@ import { connect } from 'react-redux';
 import { debounce } from 'lodash';
 
 import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
-import { Account } from 'mastodon/components/account';
 import { Icon }  from 'mastodon/components/icon';
 
 import { fetchReblogs, expandReblogs } from '../../actions/interactions';
 import ColumnHeader from '../../components/column_header';
 import { LoadingIndicator } from '../../components/loading_indicator';
 import ScrollableList from '../../components/scrollable_list';
+import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
 
 const messages = defineMessages({
@@ -88,7 +88,7 @@ class Reblogs extends ImmutablePureComponent {
           bindToDocument={!multiColumn}
         >
           {accountIds.map(id =>
-            <Account key={id} id={id} />,
+            <AccountContainer key={id} id={id} withNote={false} />,
           )}
         </ScrollableList>
 
diff --git a/app/javascript/mastodon/features/search/components/search_section.tsx b/app/javascript/mastodon/features/search/components/search_section.tsx
deleted file mode 100644
index ae0c129676..0000000000
--- a/app/javascript/mastodon/features/search/components/search_section.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-export const SearchSection: React.FC<{
-  title: React.ReactNode;
-  onClickMore?: () => void;
-  children: React.ReactNode;
-}> = ({ title, onClickMore, children }) => (
-  <div className='search-results__section'>
-    <div className='search-results__section__header'>
-      <h3>{title}</h3>
-      {onClickMore && (
-        <button onClick={onClickMore}>
-          <FormattedMessage
-            id='search_results.see_all'
-            defaultMessage='See all'
-          />
-        </button>
-      )}
-    </div>
-
-    {children}
-  </div>
-);
diff --git a/app/javascript/mastodon/features/search/index.tsx b/app/javascript/mastodon/features/search/index.tsx
deleted file mode 100644
index 8ef64413f9..0000000000
--- a/app/javascript/mastodon/features/search/index.tsx
+++ /dev/null
@@ -1,304 +0,0 @@
-import { useCallback, useEffect, useRef } from 'react';
-
-import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-
-import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react';
-import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
-import SearchIcon from '@/material-icons/400-24px/search.svg?react';
-import TagIcon from '@/material-icons/400-24px/tag.svg?react';
-import { submitSearch, expandSearch } from 'mastodon/actions/search';
-import type { ApiSearchType } from 'mastodon/api_types/search';
-import { Account } from 'mastodon/components/account';
-import { Column } from 'mastodon/components/column';
-import type { ColumnRef } from 'mastodon/components/column';
-import { ColumnHeader } from 'mastodon/components/column_header';
-import { CompatibilityHashtag as Hashtag } from 'mastodon/components/hashtag';
-import { Icon } from 'mastodon/components/icon';
-import ScrollableList from 'mastodon/components/scrollable_list';
-import Status from 'mastodon/containers/status_container';
-import { Search } from 'mastodon/features/compose/components/search';
-import { useSearchParam } from 'mastodon/hooks/useSearchParam';
-import type { Hashtag as HashtagType } from 'mastodon/models/tags';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-import { SearchSection } from './components/search_section';
-
-const messages = defineMessages({
-  title: { id: 'search_results.title', defaultMessage: 'Search for "{q}"' },
-});
-
-const INITIAL_PAGE_LIMIT = 10;
-const INITIAL_DISPLAY = 4;
-
-const hidePeek = <T,>(list: T[]) => {
-  if (
-    list.length > INITIAL_PAGE_LIMIT &&
-    list.length % INITIAL_PAGE_LIMIT === 1
-  ) {
-    return list.slice(0, -2);
-  } else {
-    return list;
-  }
-};
-
-const renderAccounts = (accountIds: string[]) =>
-  hidePeek<string>(accountIds).map((id) => <Account key={id} id={id} />);
-
-const renderHashtags = (hashtags: HashtagType[]) =>
-  hidePeek<HashtagType>(hashtags).map((hashtag) => (
-    <Hashtag key={hashtag.name} hashtag={hashtag} />
-  ));
-
-const renderStatuses = (statusIds: string[]) =>
-  hidePeek<string>(statusIds).map((id) => (
-    // @ts-expect-error inferred props are wrong
-    <Status key={id} id={id} contextType='explore' />
-  ));
-
-type SearchType = 'all' | ApiSearchType;
-
-const typeFromParam = (param?: string): SearchType => {
-  if (param && ['all', 'accounts', 'statuses', 'hashtags'].includes(param)) {
-    return param as SearchType;
-  } else {
-    return 'all';
-  }
-};
-
-export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
-  multiColumn,
-}) => {
-  const columnRef = useRef<ColumnRef>(null);
-  const intl = useIntl();
-  const [q] = useSearchParam('q');
-  const [type, setType] = useSearchParam('type');
-  const isLoading = useAppSelector((state) => state.search.loading);
-  const results = useAppSelector((state) => state.search.results);
-  const dispatch = useAppDispatch();
-  const mappedType = typeFromParam(type);
-  const trimmedValue = q?.trim() ?? '';
-
-  useEffect(() => {
-    if (trimmedValue.length > 0) {
-      void dispatch(
-        submitSearch({
-          q: trimmedValue,
-          type: mappedType === 'all' ? undefined : mappedType,
-        }),
-      );
-    }
-  }, [dispatch, trimmedValue, mappedType]);
-
-  const handleHeaderClick = useCallback(() => {
-    columnRef.current?.scrollTop();
-  }, []);
-
-  const handleSelectAll = useCallback(() => {
-    setType(null);
-  }, [setType]);
-
-  const handleSelectAccounts = useCallback(() => {
-    setType('accounts');
-  }, [setType]);
-
-  const handleSelectHashtags = useCallback(() => {
-    setType('hashtags');
-  }, [setType]);
-
-  const handleSelectStatuses = useCallback(() => {
-    setType('statuses');
-  }, [setType]);
-
-  const handleLoadMore = useCallback(() => {
-    if (mappedType !== 'all') {
-      void dispatch(expandSearch({ type: mappedType }));
-    }
-  }, [dispatch, mappedType]);
-
-  // We request 1 more result than we display so we can tell if there'd be a next page
-  const hasMore =
-    mappedType !== 'all' && results
-      ? results[mappedType].length > INITIAL_PAGE_LIMIT &&
-        results[mappedType].length % INITIAL_PAGE_LIMIT === 1
-      : false;
-
-  let filteredResults;
-
-  if (results) {
-    switch (mappedType) {
-      case 'all':
-        filteredResults =
-          results.accounts.length +
-            results.hashtags.length +
-            results.statuses.length >
-          0 ? (
-            <>
-              {results.accounts.length > 0 && (
-                <SearchSection
-                  key='accounts'
-                  title={
-                    <>
-                      <Icon id='users' icon={PeopleIcon} />
-                      <FormattedMessage
-                        id='search_results.accounts'
-                        defaultMessage='Profiles'
-                      />
-                    </>
-                  }
-                  onClickMore={handleSelectAccounts}
-                >
-                  {results.accounts.slice(0, INITIAL_DISPLAY).map((id) => (
-                    <Account key={id} id={id} />
-                  ))}
-                </SearchSection>
-              )}
-
-              {results.hashtags.length > 0 && (
-                <SearchSection
-                  key='hashtags'
-                  title={
-                    <>
-                      <Icon id='hashtag' icon={TagIcon} />
-                      <FormattedMessage
-                        id='search_results.hashtags'
-                        defaultMessage='Hashtags'
-                      />
-                    </>
-                  }
-                  onClickMore={handleSelectHashtags}
-                >
-                  {results.hashtags.slice(0, INITIAL_DISPLAY).map((hashtag) => (
-                    <Hashtag key={hashtag.name} hashtag={hashtag} />
-                  ))}
-                </SearchSection>
-              )}
-
-              {results.statuses.length > 0 && (
-                <SearchSection
-                  key='statuses'
-                  title={
-                    <>
-                      <Icon id='quote-right' icon={FindInPageIcon} />
-                      <FormattedMessage
-                        id='search_results.statuses'
-                        defaultMessage='Posts'
-                      />
-                    </>
-                  }
-                  onClickMore={handleSelectStatuses}
-                >
-                  {results.statuses.slice(0, INITIAL_DISPLAY).map((id) => (
-                    // @ts-expect-error inferred props are wrong
-                    <Status key={id} id={id} contextType='explore' />
-                  ))}
-                </SearchSection>
-              )}
-            </>
-          ) : (
-            []
-          );
-        break;
-      case 'accounts':
-        filteredResults = renderAccounts(results.accounts);
-        break;
-      case 'hashtags':
-        filteredResults = renderHashtags(results.hashtags);
-        break;
-      case 'statuses':
-        filteredResults = renderStatuses(results.statuses);
-        break;
-    }
-  }
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      ref={columnRef}
-      label={intl.formatMessage(messages.title, { q })}
-    >
-      <ColumnHeader
-        icon={'search'}
-        iconComponent={SearchIcon}
-        title={intl.formatMessage(messages.title, { q })}
-        onClick={handleHeaderClick}
-        multiColumn={multiColumn}
-      />
-
-      <div className='explore__search-header'>
-        <Search singleColumn initialValue={trimmedValue} />
-      </div>
-
-      <div className='account__section-headline'>
-        <button
-          onClick={handleSelectAll}
-          className={mappedType === 'all' ? 'active' : undefined}
-        >
-          <FormattedMessage id='search_results.all' defaultMessage='All' />
-        </button>
-        <button
-          onClick={handleSelectAccounts}
-          className={mappedType === 'accounts' ? 'active' : undefined}
-        >
-          <FormattedMessage
-            id='search_results.accounts'
-            defaultMessage='Profiles'
-          />
-        </button>
-        <button
-          onClick={handleSelectHashtags}
-          className={mappedType === 'hashtags' ? 'active' : undefined}
-        >
-          <FormattedMessage
-            id='search_results.hashtags'
-            defaultMessage='Hashtags'
-          />
-        </button>
-        <button
-          onClick={handleSelectStatuses}
-          className={mappedType === 'statuses' ? 'active' : undefined}
-        >
-          <FormattedMessage
-            id='search_results.statuses'
-            defaultMessage='Posts'
-          />
-        </button>
-      </div>
-
-      <div className='explore__search-results' data-nosnippet>
-        <ScrollableList
-          scrollKey='search-results'
-          isLoading={isLoading}
-          showLoading={isLoading && !results}
-          onLoadMore={handleLoadMore}
-          hasMore={hasMore}
-          emptyMessage={
-            trimmedValue.length > 0 ? (
-              <FormattedMessage
-                id='search_results.no_results'
-                defaultMessage='No results.'
-              />
-            ) : (
-              <FormattedMessage
-                id='search_results.no_search_yet'
-                defaultMessage='Try searching for posts, profiles or hashtags.'
-              />
-            )
-          }
-          bindToDocument
-        >
-          {filteredResults}
-        </ScrollableList>
-      </div>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.title, { q })}</title>
-        <meta name='robots' content='noindex' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default SearchResults;
diff --git a/app/javascript/mastodon/features/standalone/compose/index.jsx b/app/javascript/mastodon/features/standalone/compose/index.jsx
index 3aff78ffee..241d9aadfd 100644
--- a/app/javascript/mastodon/features/standalone/compose/index.jsx
+++ b/app/javascript/mastodon/features/standalone/compose/index.jsx
@@ -1,12 +1,12 @@
-import { AlertsController } from 'mastodon/components/alerts_controller';
 import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
 import LoadingBarContainer from 'mastodon/features/ui/containers/loading_bar_container';
 import ModalContainer from 'mastodon/features/ui/containers/modal_container';
+import NotificationsContainer from 'mastodon/features/ui/containers/notifications_container';
 
 const Compose = () => (
   <>
     <ComposeFormContainer autoFocus withoutNavigation />
-    <AlertsController />
+    <NotificationsContainer />
     <ModalContainer />
     <LoadingBarContainer className='loading-bar' />
   </>
diff --git a/app/javascript/mastodon/features/standalone/status/index.tsx b/app/javascript/mastodon/features/standalone/status/index.tsx
index 8d1b831467..d5cb7e7f40 100644
--- a/app/javascript/mastodon/features/standalone/status/index.tsx
+++ b/app/javascript/mastodon/features/standalone/status/index.tsx
@@ -6,11 +6,11 @@ import { useEffect, useCallback } from 'react';
 
 import { Provider } from 'react-redux';
 
+import { useRenderSignal } from 'mastodon/../hooks/useRenderSignal';
 import { fetchStatus, toggleStatusSpoilers } from 'mastodon/actions/statuses';
 import { hydrateStore } from 'mastodon/actions/store';
 import { Router } from 'mastodon/components/router';
 import { DetailedStatus } from 'mastodon/features/status/components/detailed_status';
-import { useRenderSignal } from 'mastodon/hooks/useRenderSignal';
 import initialState from 'mastodon/initial_state';
 import { IntlProvider } from 'mastodon/locales';
 import { makeGetStatus, makeGetPictureInPicture } from 'mastodon/selectors';
@@ -61,7 +61,7 @@ const Embed: React.FC<{ id: string }> = ({ id }) => {
         className='embed__overlay'
         href={permalink}
         target='_blank'
-        rel='noopener'
+        rel='noreferrer noopener'
         aria-label=''
       />
     </div>
diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx
index f56a616b9d..86aa4ba699 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.jsx
+++ b/app/javascript/mastodon/features/status/components/action_bar.jsx
@@ -27,7 +27,7 @@ import { WithRouterPropTypes } from 'mastodon/utils/react_router';
 
 
 import { IconButton } from '../../../components/icon_button';
-import { Dropdown } from 'mastodon/components/dropdown_menu';
+import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
 import { enableEmojiReaction , bookmarkCategoryNeeded, me, isHideItem, boostMenu, boostModal } from '../../../initial_state';
 import EmojiPickerDropdown from '../../compose/containers/emoji_picker_dropdown_container';
 
@@ -46,10 +46,8 @@ const messages = defineMessages({
   cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
   cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
   favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
-  removeFavourite: { id: 'status.remove_favourite', defaultMessage: 'Remove from favorites' },
   bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
   bookmark_category: { id: 'status.bookmark_category', defaultMessage: 'Bookmark category' },
-  removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
   more: { id: 'status.more', defaultMessage: 'More' },
   mute: { id: 'status.mute', defaultMessage: 'Mute @{name}' },
   muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
@@ -406,9 +404,6 @@ class ActionBar extends PureComponent {
       <div className='detailed-status__button__blank' />
     )) || null;
 
-    const bookmarkTitle = intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark);
-    const favouriteTitle = intl.formatMessage(status.get('favourited') ? messages.removeFavourite : messages.favourite);
-
     return (
       <div className='detailed-status__action-bar'>
         <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent}  onClick={this.handleReplyClick} /></div>
@@ -416,7 +411,7 @@ class ActionBar extends PureComponent {
           <div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} /></div>
         ) : (
           <div className='detailed-status__button'>
-            <Dropdown
+            <DropdownMenuContainer
               className={classNames({ reblogPrivate })}
               icon='retweet'
               iconComponent={reblogIconComponent}
@@ -429,12 +424,12 @@ class ActionBar extends PureComponent {
             />
           </div>
         )}
-        <div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={favouriteTitle} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} /></div>
-        <div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={bookmarkTitle} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /></div>
+        <div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} /></div>
+        <div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /></div>
         {emojiPickerDropdown}
 
         <div className='detailed-status__action-bar-dropdown'>
-          <Dropdown icon='ellipsis-h' iconComponent={MoreHorizIcon} status={status} items={menu} direction='left' title={intl.formatMessage(messages.more)} />
+          <DropdownMenuContainer icon='ellipsis-h' iconComponent={MoreHorizIcon} status={status} items={menu} direction='left' title={intl.formatMessage(messages.more)} />
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx
index f8d5a26aff..ee1fbe0f8f 100644
--- a/app/javascript/mastodon/features/status/components/card.jsx
+++ b/app/javascript/mastodon/features/status/components/card.jsx
@@ -8,7 +8,7 @@ import { FormattedMessage } from 'react-intl';
 import classNames from 'classnames';
 
 
-import { is } from 'immutable';
+import Immutable from 'immutable';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 
 import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react';
@@ -73,7 +73,7 @@ export default class Card extends PureComponent {
   };
 
   UNSAFE_componentWillReceiveProps (nextProps) {
-    if (!is(this.props.card, nextProps.card)) {
+    if (!Immutable.is(this.props.card, nextProps.card)) {
       this.setState({ embedded: false, previewLoaded: false });
     }
 
@@ -208,7 +208,7 @@ export default class Card extends PureComponent {
               <div className='status-card__actions' onClick={this.handleEmbedClick} role='none'>
                 <div>
                   <button type='button' onClick={this.handleEmbedClick}><Icon id='play' icon={PlayArrowIcon} /></button>
-                  <a href={card.get('url')} onClick={this.handleExternalLinkClick} target='_blank' rel='noopener'><Icon id='external-link' icon={OpenInNewIcon} /></a>
+                  <a href={card.get('url')} onClick={this.handleExternalLinkClick} target='_blank' rel='noopener noreferrer'><Icon id='external-link' icon={OpenInNewIcon} /></a>
                 </div>
               </div>
             ) : spoilerButton}
@@ -219,7 +219,7 @@ export default class Card extends PureComponent {
       return (
         <div className={classNames('status-card', { expanded: largeImage })} ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}>
           {embed}
-          <a href={card.get('url')} target='_blank' rel='noopener'>{description}</a>
+          <a href={card.get('url')} target='_blank' rel='noopener noreferrer'>{description}</a>
         </div>
       );
     } else if (card.get('image')) {
@@ -239,7 +239,7 @@ export default class Card extends PureComponent {
 
     return (
       <>
-        <a href={card.get('url')} className={classNames('status-card', { expanded: largeImage, bottomless: showAuthor })} target='_blank' rel='noopener' ref={this.setRef}>
+        <a href={card.get('url')} className={classNames('status-card', { expanded: largeImage, bottomless: showAuthor })} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
           {embed}
           {description}
         </a>
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.tsx b/app/javascript/mastodon/features/status/components/detailed_status.tsx
index e0a68ad263..9c85ecf264 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.tsx
+++ b/app/javascript/mastodon/features/status/components/detailed_status.tsx
@@ -6,7 +6,7 @@
 import type { CSSProperties } from 'react';
 import { useState, useRef, useCallback } from 'react';
 
-import { FormattedMessage } from 'react-intl';
+import { FormattedDate, FormattedMessage } from 'react-intl';
 
 import classNames from 'classnames';
 import { Link } from 'react-router-dom';
@@ -14,9 +14,7 @@ import { Link } from 'react-router-dom';
 import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
 import { AnimatedNumber } from 'mastodon/components/animated_number';
 import { ContentWarning } from 'mastodon/components/content_warning';
-import { EditedTimestamp } from 'mastodon/components/edited_timestamp';
-import { FilterWarning } from 'mastodon/components/filter_warning';
-import { FormattedDateWrapper } from 'mastodon/components/formatted_date';
+import EditedTimestamp from 'mastodon/components/edited_timestamp';
 import type { StatusLike } from 'mastodon/components/hashtag_bar';
 import { getHashtagBarForStatus } from 'mastodon/components/hashtag_bar';
 import { Icon } from 'mastodon/components/icon';
@@ -24,7 +22,6 @@ import { IconLogo } from 'mastodon/components/logo';
 import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
 import { SearchabilityIcon } from 'mastodon/components/searchability_icon';
 import { VisibilityIcon } from 'mastodon/components/visibility_icon';
-import { Video } from 'mastodon/features/video';
 import { enableEmojiReaction, isHideItem } from 'mastodon/initial_state';
 
 import { Avatar } from '../../../components/avatar';
@@ -35,6 +32,7 @@ import StatusEmojiReactionsBar from '../../../components/status_emoji_reactions_
 import CompactedStatusContainer from '../../../containers/compacted_status_container';
 import Audio from '../../audio';
 import scheduleIdleTask from '../../ui/util/schedule_idle_task';
+import Video from '../../video';
 
 import Card from './card';
 
@@ -42,6 +40,7 @@ interface VideoModalOptions {
   startTime: number;
   autoPlay?: boolean;
   defaultVolume: number;
+  componentIndex: number;
 }
 
 export const DetailedStatus: React.FC<{
@@ -54,7 +53,6 @@ export const DetailedStatus: React.FC<{
   domain: string;
   showMedia?: boolean;
   withLogo?: boolean;
-  overrideDisplayName?: React.ReactNode;
   pictureInPicture: any;
   onToggleHidden?: (status: any) => void;
   onToggleMediaVisibility?: () => void;
@@ -71,7 +69,6 @@ export const DetailedStatus: React.FC<{
   domain,
   showMedia,
   withLogo,
-  overrideDisplayName,
   pictureInPicture,
   onToggleMediaVisibility,
   onToggleHidden,
@@ -81,7 +78,6 @@ export const DetailedStatus: React.FC<{
 }) => {
   const properStatus = status?.get('reblog') ?? status;
   const [height, setHeight] = useState(0);
-  const [showDespiteFilter, setShowDespiteFilter] = useState(false);
   const nodeRef = useRef<HTMLDivElement>();
 
   const handleOpenVideo = useCallback(
@@ -94,10 +90,6 @@ export const DetailedStatus: React.FC<{
     [onOpenVideo, status],
   );
 
-  const handleFilterToggle = useCallback(() => {
-    setShowDespiteFilter(!showDespiteFilter);
-  }, [showDespiteFilter, setShowDespiteFilter]);
-
   const handleExpandedToggle = useCallback(() => {
     if (onToggleHidden) onToggleHidden(status);
   }, [onToggleHidden, status]);
@@ -186,7 +178,6 @@ export const DetailedStatus: React.FC<{
           onOpenMedia={onOpenMedia}
           visible={showMedia}
           onToggleVisibility={onToggleMediaVisibility}
-          matchedFilters={status.get('matched_media_filters')}
         />
       );
     } else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
@@ -213,7 +204,6 @@ export const DetailedStatus: React.FC<{
           blurhash={attachment.get('blurhash')}
           height={150}
           onToggleVisibility={onToggleMediaVisibility}
-          matchedFilters={status.get('matched_media_filters')}
         />
       );
     } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
@@ -231,11 +221,12 @@ export const DetailedStatus: React.FC<{
           src={attachment.get('url')}
           alt={description}
           lang={language}
+          width={300}
+          height={150}
           onOpenVideo={handleOpenVideo}
           sensitive={status.get('sensitive')}
           visible={showMedia}
           onToggleVisibility={onToggleMediaVisibility}
-          matchedFilters={status.get('matched_media_filters')}
         />
       );
     }
@@ -376,12 +367,8 @@ export const DetailedStatus: React.FC<{
   const { statusContentProps, hashtagBar } = getHashtagBarForStatus(
     status as StatusLike,
   );
-
-  const matchedFilters = status.get('matched_filters');
-
   const expanded =
-    (!matchedFilters || showDespiteFilter) &&
-    (!status.get('hidden') || status.get('spoiler_text').length === 0);
+    !status.get('hidden') || status.get('spoiler_text').length === 0;
 
   const quote = !muted && status.get('quote_id') && (
     <>
@@ -416,11 +403,7 @@ export const DetailedStatus: React.FC<{
           <div className='detailed-status__display-avatar'>
             <Avatar account={status.get('account')} size={46} />
           </div>
-
-          {overrideDisplayName ?? (
-            <DisplayName account={status.get('account')} localDomain={domain} />
-          )}
-
+          <DisplayName account={status.get('account')} localDomain={domain} />
           {withLogo && (
             <>
               <div className='spacer' />
@@ -429,26 +412,17 @@ export const DetailedStatus: React.FC<{
           )}
         </Link>
 
-        {matchedFilters && (
-          <FilterWarning
-            title={matchedFilters.join(', ')}
-            expanded={showDespiteFilter}
-            onClick={handleFilterToggle}
+        {status.get('spoiler_text').length > 0 && (
+          <ContentWarning
+            text={
+              status.getIn(['translation', 'spoilerHtml']) ||
+              status.get('spoilerHtml')
+            }
+            expanded={expanded}
+            onClick={handleExpandedToggle}
           />
         )}
 
-        {status.get('spoiler_text').length > 0 &&
-          (!matchedFilters || showDespiteFilter) && (
-            <ContentWarning
-              text={
-                status.getIn(['translation', 'spoilerHtml']) ||
-                status.get('spoilerHtml')
-              }
-              expanded={expanded}
-              onClick={handleExpandedToggle}
-            />
-          )}
-
         {expanded && (
           <>
             <StatusContent
@@ -472,7 +446,7 @@ export const DetailedStatus: React.FC<{
               target='_blank'
               rel='noopener noreferrer'
             >
-              <FormattedDateWrapper
+              <FormattedDate
                 value={new Date(status.get('created_at') as string)}
                 year='numeric'
                 month='short'
diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx
index e9d0c9f8d6..1a6a5888f3 100644
--- a/app/javascript/mastodon/features/status/index.jsx
+++ b/app/javascript/mastodon/features/status/index.jsx
@@ -7,7 +7,7 @@ import { Helmet } from 'react-helmet';
 import { withRouter } from 'react-router-dom';
 
 import { createSelector } from '@reduxjs/toolkit';
-import { List as ImmutableList } from 'immutable';
+import Immutable from 'immutable';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { connect } from 'react-redux';
@@ -87,7 +87,7 @@ const makeMapStateToProps = () => {
   const getPictureInPicture = makeGetPictureInPicture();
 
   const getReferenceIds = createSelector([
-    (state, { id }) => state.getIn(['contexts', 'references', id]) || ImmutableList(),
+    (state, { id }) => state.getIn(['contexts', 'references', id]) || Immutable.List(),
   ], (references) => {
     return references;
   });
@@ -96,7 +96,7 @@ const makeMapStateToProps = () => {
     (_, { id }) => id,
     state => state.getIn(['contexts', 'inReplyTos']),
   ], (statusId, inReplyTos) => {
-    let ancestorsIds = ImmutableList();
+    let ancestorsIds = Immutable.List();
     ancestorsIds = ancestorsIds.withMutations(mutable => {
       let id = statusId;
 
@@ -143,15 +143,15 @@ const makeMapStateToProps = () => {
       });
     }
 
-    return ImmutableList(descendantsIds);
+    return Immutable.List(descendantsIds);
   });
 
   const mapStateToProps = (state, props) => {
-    const status = getStatus(state, { id: props.params.statusId, contextType: 'detailed' });
+    const status = getStatus(state, { id: props.params.statusId });
 
-    let ancestorsIds   = ImmutableList();
-    let descendantsIds = ImmutableList();
-    let referenceIds   = ImmutableList();
+    let ancestorsIds   = Immutable.List();
+    let descendantsIds = Immutable.List();
+    let referenceIds   = Immutable.List();
 
     if (status) {
       ancestorsIds   = getAncestorsIds(state, { id: status.get('in_reply_to_id') });
@@ -536,10 +536,6 @@ class Status extends ImmutablePureComponent {
     this.handleToggleMediaVisibility();
   };
 
-  handleHotkeyTranslate = () => {
-    this.handleTranslate(this.props.status);
-  };
-
   handleMoveUp = id => {
     const { status, ancestorsIds, descendantsIds, referenceIds } = this.props;
 
@@ -730,7 +726,6 @@ class Status extends ImmutablePureComponent {
       toggleHidden: this.handleHotkeyToggleHidden,
       toggleSensitive: this.handleHotkeyToggleSensitive,
       openMedia: this.handleHotkeyOpenMedia,
-      onTranslate: this.handleHotkeyTranslate,
     };
 
     return (
diff --git a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx
index 895a2686e8..0531346f91 100644
--- a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx
+++ b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx
@@ -23,7 +23,7 @@ const getAccountLanguages = createSelector([
   (state, accountId) => state.getIn(['timelines', `account:${accountId}`, 'items'], ImmutableList()),
   state => state.get('statuses'),
 ], (statusIds, statuses) =>
-  ImmutableSet(statusIds.map(statusId => statuses.get(statusId)).filter(status => !status.get('reblog')).map(status => status.get('language'))));
+  new ImmutableSet(statusIds.map(statusId => statuses.get(statusId)).filter(status => !status.get('reblog')).map(status => status.get('language'))));
 
 const mapStateToProps = (state, { accountId }) => ({
   acct: state.getIn(['accounts', accountId, 'acct']),
diff --git a/app/javascript/mastodon/features/terms_of_service/index.tsx b/app/javascript/mastodon/features/terms_of_service/index.tsx
deleted file mode 100644
index 8ef64fc515..0000000000
--- a/app/javascript/mastodon/features/terms_of_service/index.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import { useState, useEffect } from 'react';
-
-import {
-  FormattedMessage,
-  FormattedDate,
-  useIntl,
-  defineMessages,
-} from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { Link, useParams } from 'react-router-dom';
-
-import { apiGetTermsOfService } from 'mastodon/api/instance';
-import type { ApiTermsOfServiceJSON } from 'mastodon/api_types/instance';
-import { Column } from 'mastodon/components/column';
-import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
-
-const messages = defineMessages({
-  title: { id: 'terms_of_service.title', defaultMessage: 'Terms of Service' },
-});
-
-interface Params {
-  date?: string;
-}
-
-const TermsOfService: React.FC<{
-  multiColumn: boolean;
-}> = ({ multiColumn }) => {
-  const intl = useIntl();
-  const { date } = useParams<Params>();
-  const [response, setResponse] = useState<ApiTermsOfServiceJSON>();
-  const [loading, setLoading] = useState(true);
-
-  useEffect(() => {
-    apiGetTermsOfService(date)
-      .then((data) => {
-        setResponse(data);
-        setLoading(false);
-        return '';
-      })
-      .catch(() => {
-        setLoading(false);
-      });
-  }, [date]);
-
-  if (!loading && !response) {
-    return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
-  }
-
-  return (
-    <Column
-      bindToDocument={!multiColumn}
-      label={intl.formatMessage(messages.title)}
-    >
-      <div className='scrollable privacy-policy'>
-        <div className='column-title'>
-          <h3>
-            <FormattedMessage
-              id='terms_of_service.title'
-              defaultMessage='Terms of Service'
-            />
-          </h3>
-          <p className='prose'>
-            {response?.effective ? (
-              <FormattedMessage
-                id='privacy_policy.last_updated'
-                defaultMessage='Last updated {date}'
-                values={{
-                  date: (
-                    <FormattedDate
-                      value={response.effective_date}
-                      year='numeric'
-                      month='short'
-                      day='2-digit'
-                    />
-                  ),
-                }}
-              />
-            ) : (
-              <FormattedMessage
-                id='terms_of_service.effective_as_of'
-                defaultMessage='Effective as of {date}'
-                values={{
-                  date: (
-                    <FormattedDate
-                      value={response?.effective_date}
-                      year='numeric'
-                      month='short'
-                      day='2-digit'
-                    />
-                  ),
-                }}
-              />
-            )}
-
-            {response?.succeeded_by && (
-              <>
-                {' · '}
-                <Link to={`/terms-of-service/${response.succeeded_by}`}>
-                  <FormattedMessage
-                    id='terms_of_service.upcoming_changes_on'
-                    defaultMessage='Upcoming changes on {date}'
-                    values={{
-                      date: (
-                        <FormattedDate
-                          value={response.succeeded_by}
-                          year='numeric'
-                          month='short'
-                          day='2-digit'
-                        />
-                      ),
-                    }}
-                  />
-                </Link>
-              </>
-            )}
-          </p>
-        </div>
-
-        {response && (
-          <div
-            className='privacy-policy__body prose'
-            dangerouslySetInnerHTML={{ __html: response.content }}
-          />
-        )}
-      </div>
-
-      <Helmet>
-        <title>{intl.formatMessage(messages.title)}</title>
-        <meta name='robots' content='all' />
-      </Helmet>
-    </Column>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default TermsOfService;
diff --git a/app/javascript/mastodon/features/ui/components/actions_modal.jsx b/app/javascript/mastodon/features/ui/components/actions_modal.jsx
index 851f828b4e..4e0879580b 100644
--- a/app/javascript/mastodon/features/ui/components/actions_modal.jsx
+++ b/app/javascript/mastodon/features/ui/components/actions_modal.jsx
@@ -24,7 +24,7 @@ export default class ActionsModal extends ImmutablePureComponent {
 
     return (
       <li key={`${text}-${i}`}>
-        <a href={href} target='_blank' rel='noopener' onClick={this.props.onClick} data-index={i} className={classNames({ active })}>
+        <a href={href} target='_blank' rel='noopener noreferrer' onClick={this.props.onClick} data-index={i} className={classNames({ active })}>
           {icon && <IconButton title={text} icon={icon} iconComponent={iconComponent} role='presentation' tabIndex={-1} inverted />}
           <div>
             <div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div>
diff --git a/app/javascript/mastodon/features/ui/components/annual_report_modal.tsx b/app/javascript/mastodon/features/ui/components/annual_report_modal.tsx
deleted file mode 100644
index 8c39c0b3aa..0000000000
--- a/app/javascript/mastodon/features/ui/components/annual_report_modal.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useEffect } from 'react';
-
-import { AnnualReport } from 'mastodon/features/annual_report';
-
-const AnnualReportModal: React.FC<{
-  year: string;
-  onChangeBackgroundColor: (arg0: string) => void;
-}> = ({ year, onChangeBackgroundColor }) => {
-  useEffect(() => {
-    onChangeBackgroundColor('var(--indigo-1)');
-  }, [onChangeBackgroundColor]);
-
-  return (
-    <div className='modal-root__modal annual-report-modal'>
-      <AnnualReport year={year} />
-    </div>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default AnnualReportModal;
diff --git a/app/javascript/mastodon/features/ui/components/audio_modal.jsx b/app/javascript/mastodon/features/ui/components/audio_modal.jsx
index dfefe689d8..5baed2b3f9 100644
--- a/app/javascript/mastodon/features/ui/components/audio_modal.jsx
+++ b/app/javascript/mastodon/features/ui/components/audio_modal.jsx
@@ -4,7 +4,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { connect } from 'react-redux';
 
-import { getAverageFromBlurhash } from 'mastodon/blurhash';
 import Audio from 'mastodon/features/audio';
 import Footer from 'mastodon/features/picture_in_picture/components/footer';
 
@@ -27,18 +26,6 @@ class AudioModal extends ImmutablePureComponent {
     onChangeBackgroundColor: PropTypes.func.isRequired,
   };
 
-  componentDidMount () {
-    const { media, onChangeBackgroundColor } = this.props;
-
-    const backgroundColor = getAverageFromBlurhash(media.get('blurhash'));
-
-    onChangeBackgroundColor(backgroundColor || { r: 255, g: 255, b: 255 });
-  }
-
-  componentWillUnmount () {
-    this.props.onChangeBackgroundColor(null);
-  }
-
   render () {
     const { media, status, accountStaticAvatar, onClose } = this.props;
     const options = this.props.options || {};
diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.tsx b/app/javascript/mastodon/features/ui/components/boost_modal.tsx
index 72e1594af9..b17dd516da 100644
--- a/app/javascript/mastodon/features/ui/components/boost_modal.tsx
+++ b/app/javascript/mastodon/features/ui/components/boost_modal.tsx
@@ -28,6 +28,7 @@ export const BoostModal: React.FC<{
   const intl = useIntl();
 
   const defaultPrivacy = useAppSelector(
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
     (state) => state.compose.get('default_privacy') as StatusVisibility,
   );
 
@@ -129,8 +130,6 @@ export const BoostModal: React.FC<{
                 ? messages.cancel_reblog
                 : messages.reblog,
             )}
-            /* eslint-disable-next-line jsx-a11y/no-autofocus -- We are in the modal */
-            autoFocus
           />
         </div>
       </div>
diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx b/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx
index beaf185638..eb6682d77b 100644
--- a/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx
+++ b/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx
@@ -9,7 +9,58 @@ import { Link } from 'react-router-dom';
 
 import { Button } from 'mastodon/components/button';
 import Column from 'mastodon/components/column';
-import { GIF } from 'mastodon/components/gif';
+import { autoPlayGif } from 'mastodon/initial_state';
+
+class GIF extends PureComponent {
+
+  static propTypes = {
+    src: PropTypes.string.isRequired,
+    staticSrc: PropTypes.string.isRequired,
+    className: PropTypes.string,
+    animate: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    animate: autoPlayGif,
+  };
+
+  state = {
+    hovering: false,
+  };
+
+  handleMouseEnter = () => {
+    const { animate } = this.props;
+
+    if (!animate) {
+      this.setState({ hovering: true });
+    }
+  };
+
+  handleMouseLeave = () => {
+    const { animate } = this.props;
+
+    if (!animate) {
+      this.setState({ hovering: false });
+    }
+  };
+
+  render () {
+    const { src, staticSrc, className, animate } = this.props;
+    const { hovering } = this.state;
+
+    return (
+      <img
+        className={className}
+        src={(hovering || animate) ? src : staticSrc}
+        alt=''
+        role='presentation'
+        onMouseEnter={this.handleMouseEnter}
+        onMouseLeave={this.handleMouseLeave}
+      />
+    );
+  }
+
+}
 
 class CopyButton extends PureComponent {
 
diff --git a/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx b/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx
new file mode 100644
index 0000000000..d1c9e36883
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx
@@ -0,0 +1,56 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
+
+import { IconButton } from '../../../components/icon_button';
+
+const messages = defineMessages({
+  error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this component.' },
+  retry: { id: 'bundle_modal_error.retry', defaultMessage: 'Try again' },
+  close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' },
+});
+
+class BundleModalError extends PureComponent {
+
+  static propTypes = {
+    onRetry: PropTypes.func.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleRetry = () => {
+    this.props.onRetry();
+  };
+
+  render () {
+    const { onClose, intl: { formatMessage } } = this.props;
+
+    // Keep the markup in sync with <ModalLoading />
+    // (make sure they have the same dimensions)
+    return (
+      <div className='modal-root__modal error-modal'>
+        <div className='error-modal__body'>
+          <IconButton title={formatMessage(messages.retry)} icon='refresh' iconComponent={RefreshIcon} onClick={this.handleRetry} size={64} />
+          {formatMessage(messages.error)}
+        </div>
+
+        <div className='error-modal__footer'>
+          <div>
+            <button
+              onClick={onClose}
+              className='error-modal__nav onboarding-modal__skip'
+            >
+              {formatMessage(messages.close)}
+            </button>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default injectIntl(BundleModalError);
diff --git a/app/javascript/mastodon/features/ui/components/column_link.jsx b/app/javascript/mastodon/features/ui/components/column_link.jsx
index fe7da5fdb0..e6f0b74995 100644
--- a/app/javascript/mastodon/features/ui/components/column_link.jsx
+++ b/app/javascript/mastodon/features/ui/components/column_link.jsx
@@ -16,7 +16,7 @@ const ColumnLink = ({ icon, activeIcon, iconComponent, activeIconComponent, text
 
   if (href) {
     return (
-      <a href={href} className={className} data-method={method} {...other}>
+      <a href={href} className={className} data-method={method} title={text} {...other}>
         {active ? activeIconElement : iconElement}
         <span>{text}</span>
         {badgeElement}
@@ -24,7 +24,7 @@ const ColumnLink = ({ icon, activeIcon, iconComponent, activeIconComponent, text
     );
   } else {
     return (
-      <NavLink to={to} className={className} exact {...other}>
+      <NavLink to={to} className={className} title={text} exact {...other}>
         {active ? activeIconElement : iconElement}
         <span>{text}</span>
         {badgeElement}
diff --git a/app/javascript/mastodon/features/ui/components/column_loading.tsx b/app/javascript/mastodon/features/ui/components/column_loading.tsx
index 8b20e76ffb..d9563dda7a 100644
--- a/app/javascript/mastodon/features/ui/components/column_loading.tsx
+++ b/app/javascript/mastodon/features/ui/components/column_loading.tsx
@@ -1,4 +1,4 @@
-import { Column } from 'mastodon/components/column';
+import Column from 'mastodon/components/column';
 import { ColumnHeader } from 'mastodon/components/column_header';
 import type { Props as ColumnHeaderProps } from 'mastodon/components/column_header';
 
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.jsx b/app/javascript/mastodon/features/ui/components/columns_area.jsx
index f01e8997c3..eed170a974 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.jsx
+++ b/app/javascript/mastodon/features/ui/components/columns_area.jsx
@@ -8,7 +8,7 @@ import { scrollRight } from '../../../scroll';
 import BundleContainer from '../containers/bundle_container';
 import {
   Compose,
-  Notifications,
+  NotificationsWrapper,
   HomeTimeline,
   CommunityTimeline,
   PublicTimeline,
@@ -35,7 +35,7 @@ import NavigationPanel from './navigation_panel';
 const componentMap = {
   'COMPOSE': Compose,
   'HOME': HomeTimeline,
-  'NOTIFICATIONS': Notifications,
+  'NOTIFICATIONS': NotificationsWrapper,
   'PUBLIC': PublicTimeline,
   'REMOTE': PublicTimeline,
   'COMMUNITY': CommunityTimeline,
diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.jsx b/app/javascript/mastodon/features/ui/components/compose_panel.jsx
index e622c8859a..18321cbe63 100644
--- a/app/javascript/mastodon/features/ui/components/compose_panel.jsx
+++ b/app/javascript/mastodon/features/ui/components/compose_panel.jsx
@@ -5,11 +5,12 @@ import { connect } from 'react-redux';
 
 import { changeComposing, mountCompose, unmountCompose } from 'mastodon/actions/compose';
 import ServerBanner from 'mastodon/components/server_banner';
-import { Search } from 'mastodon/features/compose/components/search';
 import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
-import { LinkFooter } from 'mastodon/features/ui/components/link_footer';
+import SearchContainer from 'mastodon/features/compose/containers/search_container';
 import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
 
+import LinkFooter from './link_footer';
+
 class ComposePanel extends PureComponent {
   static propTypes = {
     identity: identityContextPropShape,
@@ -41,7 +42,7 @@ class ComposePanel extends PureComponent {
 
     return (
       <div className='compose-panel' onFocus={this.onFocus}>
-        <Search openInRoute />
+        <SearchContainer openInRoute />
 
         {!signedIn && (
           <>
diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx
index 929b1a240f..ab567c697a 100644
--- a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx
+++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx
@@ -56,6 +56,14 @@ export const ConfirmationModal: React.FC<
 
       <div className='safety-action-modal__bottom'>
         <div className='safety-action-modal__actions'>
+          {secondary && (
+            <>
+              <Button onClick={handleSecondary}>{secondary}</Button>
+
+              <div className='spacer' />
+            </>
+          )}
+
           <button onClick={handleCancel} className='link-button'>
             <FormattedMessage
               id='confirmation_modal.cancel'
@@ -63,15 +71,6 @@ export const ConfirmationModal: React.FC<
             />
           </button>
 
-          {secondary && (
-            <>
-              <div className='spacer' />
-              <button onClick={handleSecondary} className='link-button'>
-                {secondary}
-              </button>
-            </>
-          )}
-
           {/* eslint-disable-next-line jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */}
           <Button onClick={handleClick} autoFocus>
             {confirm}
diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/delete_antenna.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/delete_antenna.tsx
deleted file mode 100644
index d497043048..0000000000
--- a/app/javascript/mastodon/features/ui/components/confirmation_modals/delete_antenna.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { useCallback } from 'react';
-
-import { defineMessages, useIntl } from 'react-intl';
-
-import { useHistory } from 'react-router';
-
-import { deleteAntenna } from 'mastodon/actions/antennas';
-import { removeColumn } from 'mastodon/actions/columns';
-import { useAppDispatch } from 'mastodon/store';
-
-import type { BaseConfirmationModalProps } from './confirmation_modal';
-import { ConfirmationModal } from './confirmation_modal';
-
-const messages = defineMessages({
-  deleteAntennaTitle: {
-    id: 'confirmations.delete_antenna.title',
-    defaultMessage: 'Delete antenna?',
-  },
-  deleteAntennaMessage: {
-    id: 'confirmations.delete_antenna.message',
-    defaultMessage: 'Are you sure you want to permanently delete this antenna?',
-  },
-  deleteAntennaConfirm: {
-    id: 'confirmations.delete_antenna.confirm',
-    defaultMessage: 'Delete',
-  },
-});
-
-export const ConfirmDeleteAntennaModal: React.FC<
-  {
-    antennaId: string;
-    columnId: string;
-  } & BaseConfirmationModalProps
-> = ({ antennaId, columnId, onClose }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const history = useHistory();
-
-  const onConfirm = useCallback(() => {
-    dispatch(deleteAntenna(antennaId));
-
-    if (columnId) {
-      dispatch(removeColumn(columnId));
-    } else {
-      history.push('/antennas');
-    }
-  }, [dispatch, history, columnId, antennaId]);
-
-  return (
-    <ConfirmationModal
-      title={intl.formatMessage(messages.deleteAntennaTitle)}
-      message={intl.formatMessage(messages.deleteAntennaMessage)}
-      confirm={intl.formatMessage(messages.deleteAntennaConfirm)}
-      onConfirm={onConfirm}
-      onClose={onClose}
-    />
-  );
-};
diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/delete_bookmark_category.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/delete_bookmark_category.tsx
deleted file mode 100644
index ae78f99246..0000000000
--- a/app/javascript/mastodon/features/ui/components/confirmation_modals/delete_bookmark_category.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { useCallback } from 'react';
-
-import { defineMessages, useIntl } from 'react-intl';
-
-import { useHistory } from 'react-router';
-
-import { deleteBookmarkCategory } from 'mastodon/actions/bookmark_categories';
-import { removeColumn } from 'mastodon/actions/columns';
-import { useAppDispatch } from 'mastodon/store';
-
-import type { BaseConfirmationModalProps } from './confirmation_modal';
-import { ConfirmationModal } from './confirmation_modal';
-
-const messages = defineMessages({
-  deleteBookmarkCategoryTitle: {
-    id: 'confirmations.delete_bookmark_category.title',
-    defaultMessage: 'Delete category?',
-  },
-  deleteBookmarkCategoryMessage: {
-    id: 'confirmations.delete_bookmark_category.message',
-    defaultMessage:
-      'Are you sure you want to permanently delete this category?',
-  },
-  deleteBookmarkCategoryConfirm: {
-    id: 'confirmations.delete_bookmark_category.confirm',
-    defaultMessage: 'Delete',
-  },
-});
-
-export const ConfirmDeleteBookmarkCategoryModal: React.FC<
-  {
-    bookmark_categoryId: string;
-    columnId: string;
-  } & BaseConfirmationModalProps
-> = ({ bookmark_categoryId, columnId, onClose }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const history = useHistory();
-
-  const onConfirm = useCallback(() => {
-    dispatch(deleteBookmarkCategory(bookmark_categoryId));
-
-    if (columnId) {
-      dispatch(removeColumn(columnId));
-    } else {
-      history.push('/bookmark_categories');
-    }
-  }, [dispatch, history, columnId, bookmark_categoryId]);
-
-  return (
-    <ConfirmationModal
-      title={intl.formatMessage(messages.deleteBookmarkCategoryTitle)}
-      message={intl.formatMessage(messages.deleteBookmarkCategoryMessage)}
-      confirm={intl.formatMessage(messages.deleteBookmarkCategoryConfirm)}
-      onConfirm={onConfirm}
-      onClose={onClose}
-    />
-  );
-};
diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/delete_circle.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/delete_circle.tsx
deleted file mode 100644
index c765e28773..0000000000
--- a/app/javascript/mastodon/features/ui/components/confirmation_modals/delete_circle.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { useCallback } from 'react';
-
-import { defineMessages, useIntl } from 'react-intl';
-
-import { useHistory } from 'react-router';
-
-import { deleteCircle } from 'mastodon/actions/circles';
-import { removeColumn } from 'mastodon/actions/columns';
-import { useAppDispatch } from 'mastodon/store';
-
-import type { BaseConfirmationModalProps } from './confirmation_modal';
-import { ConfirmationModal } from './confirmation_modal';
-
-const messages = defineMessages({
-  deleteCircleTitle: {
-    id: 'confirmations.delete_circle.title',
-    defaultMessage: 'Delete circle?',
-  },
-  deleteCircleMessage: {
-    id: 'confirmations.delete_circle.message',
-    defaultMessage: 'Are you sure you want to permanently delete this circle?',
-  },
-  deleteCircleConfirm: {
-    id: 'confirmations.delete_circle.confirm',
-    defaultMessage: 'Delete',
-  },
-});
-
-export const ConfirmDeleteCircleModal: React.FC<
-  {
-    circleId: string;
-    columnId: string;
-  } & BaseConfirmationModalProps
-> = ({ circleId, columnId, onClose }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const history = useHistory();
-
-  const onConfirm = useCallback(() => {
-    dispatch(deleteCircle(circleId));
-
-    if (columnId) {
-      dispatch(removeColumn(columnId));
-    } else {
-      history.push('/circles');
-    }
-  }, [dispatch, history, columnId, circleId]);
-
-  return (
-    <ConfirmationModal
-      title={intl.formatMessage(messages.deleteCircleTitle)}
-      message={intl.formatMessage(messages.deleteCircleMessage)}
-      confirm={intl.formatMessage(messages.deleteCircleConfirm)}
-      onConfirm={onConfirm}
-      onClose={onClose}
-    />
-  );
-};
diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/follow_to_list.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/follow_to_list.tsx
deleted file mode 100644
index b862a29827..0000000000
--- a/app/javascript/mastodon/features/ui/components/confirmation_modals/follow_to_list.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
-
-import { useAppSelector } from 'mastodon/store';
-
-import type { BaseConfirmationModalProps } from './confirmation_modal';
-import { ConfirmationModal } from './confirmation_modal';
-
-const messages = defineMessages({
-  title: {
-    id: 'confirmations.follow_to_list.title',
-    defaultMessage: 'Follow user?',
-  },
-  confirm: {
-    id: 'confirmations.follow_to_list.confirm',
-    defaultMessage: 'Follow and add to list',
-  },
-});
-
-export const ConfirmFollowToListModal: React.FC<
-  {
-    accountId: string;
-    onConfirm: () => void;
-  } & BaseConfirmationModalProps
-> = ({ accountId, onConfirm, onClose }) => {
-  const intl = useIntl();
-  const account = useAppSelector((state) => state.accounts.get(accountId));
-
-  return (
-    <ConfirmationModal
-      title={intl.formatMessage(messages.title)}
-      message={
-        <FormattedMessage
-          id='confirmations.follow_to_list.message'
-          defaultMessage='You need to be following {name} to add them to a list.'
-          values={{ name: <strong>@{account?.acct}</strong> }}
-        />
-      }
-      confirm={intl.formatMessage(messages.confirm)}
-      onConfirm={onConfirm}
-      onClose={onClose}
-    />
-  );
-};
diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts b/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts
index d3b648e20e..912c99a393 100644
--- a/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts
+++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts
@@ -1,13 +1,8 @@
 export { ConfirmationModal } from './confirmation_modal';
 export { ConfirmDeleteStatusModal } from './delete_status';
 export { ConfirmDeleteListModal } from './delete_list';
-export { ConfirmDeleteAntennaModal } from './delete_antenna';
-export { ConfirmDeleteCircleModal } from './delete_circle';
-export { ConfirmDeleteBookmarkCategoryModal } from './delete_bookmark_category';
 export { ConfirmReplyModal } from './reply';
 export { ConfirmEditStatusModal } from './edit_status';
 export { ConfirmUnfollowModal } from './unfollow';
 export { ConfirmClearNotificationsModal } from './clear_notifications';
 export { ConfirmLogOutModal } from './log_out';
-export { ConfirmFollowToListModal } from './follow_to_list';
-export { ConfirmMissingAltTextModal } from './missing_alt_text';
diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/missing_alt_text.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/missing_alt_text.tsx
deleted file mode 100644
index 057a1191d9..0000000000
--- a/app/javascript/mastodon/features/ui/components/confirmation_modals/missing_alt_text.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { useCallback } from 'react';
-
-import { defineMessages, useIntl } from 'react-intl';
-
-import type { Map as ImmutableMap, List as ImmutableList } from 'immutable';
-
-import { submitCompose } from 'mastodon/actions/compose';
-import { openModal } from 'mastodon/actions/modal';
-import type { MediaAttachment } from 'mastodon/models/media_attachment';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-import type { BaseConfirmationModalProps } from './confirmation_modal';
-import { ConfirmationModal } from './confirmation_modal';
-
-const messages = defineMessages({
-  title: {
-    id: 'confirmations.missing_alt_text.title',
-    defaultMessage: 'Add alt text?',
-  },
-  confirm: {
-    id: 'confirmations.missing_alt_text.confirm',
-    defaultMessage: 'Add alt text',
-  },
-  message: {
-    id: 'confirmations.missing_alt_text.message',
-    defaultMessage:
-      'Your post contains media without alt text. Adding descriptions helps make your content accessible to more people.',
-  },
-  secondary: {
-    id: 'confirmations.missing_alt_text.secondary',
-    defaultMessage: 'Post anyway',
-  },
-});
-
-export const ConfirmMissingAltTextModal: React.FC<
-  BaseConfirmationModalProps
-> = ({ onClose }) => {
-  const intl = useIntl();
-  const dispatch = useAppDispatch();
-  const mediaId = useAppSelector(
-    (state) =>
-      (
-        (state.compose as ImmutableMap<string, unknown>).get(
-          'media_attachments',
-        ) as ImmutableList<MediaAttachment>
-      )
-        .find(
-          (media) =>
-            ['image', 'gifv'].includes(media.get('type') as string) &&
-            ((media.get('description') ?? '') as string).length === 0,
-        )
-        ?.get('id') as string,
-  );
-
-  const handleConfirm = useCallback(() => {
-    dispatch(
-      openModal({
-        modalType: 'FOCAL_POINT',
-        modalProps: {
-          mediaId,
-        },
-      }),
-    );
-  }, [dispatch, mediaId]);
-
-  const handleSecondary = useCallback(() => {
-    dispatch(submitCompose());
-  }, [dispatch]);
-
-  return (
-    <ConfirmationModal
-      title={intl.formatMessage(messages.title)}
-      message={intl.formatMessage(messages.message)}
-      confirm={intl.formatMessage(messages.confirm)}
-      secondary={intl.formatMessage(messages.secondary)}
-      onConfirm={handleConfirm}
-      onSecondary={handleSecondary}
-      onClose={onClose}
-    />
-  );
-};
diff --git a/app/javascript/mastodon/features/ui/components/embed_modal.tsx b/app/javascript/mastodon/features/ui/components/embed_modal.tsx
index b78d5b64c4..8f623e62b5 100644
--- a/app/javascript/mastodon/features/ui/components/embed_modal.tsx
+++ b/app/javascript/mastodon/features/ui/components/embed_modal.tsx
@@ -101,7 +101,6 @@ const EmbedModal: React.FC<{
           />
 
           <iframe
-            // eslint-disable-next-line @typescript-eslint/no-deprecated
             frameBorder='0'
             ref={iframeRef}
             sandbox='allow-scripts allow-same-origin'
diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx
new file mode 100644
index 0000000000..7adfc208e7
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx
@@ -0,0 +1,438 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
+
+import classNames from 'classnames';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import Textarea from 'react-textarea-autosize';
+import { length } from 'stringz';
+// eslint-disable-next-line import/extensions
+import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js';
+
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+import { Button } from 'mastodon/components/button';
+import { GIFV } from 'mastodon/components/gifv';
+import { IconButton } from 'mastodon/components/icon_button';
+import Audio from 'mastodon/features/audio';
+import { CharacterCounter } from 'mastodon/features/compose/components/character_counter';
+import { UploadProgress } from 'mastodon/features/compose/components/upload_progress';
+import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
+import { me } from 'mastodon/initial_state';
+import { assetHost } from 'mastodon/utils/config';
+
+import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from '../../../actions/compose';
+import Video, { getPointerPosition } from '../../video';
+
+const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
+  apply: { id: 'upload_modal.apply', defaultMessage: 'Apply' },
+  applying: { id: 'upload_modal.applying', defaultMessage: 'Applying…' },
+  placeholder: { id: 'upload_modal.description_placeholder', defaultMessage: 'A quick brown fox jumps over the lazy dog' },
+  chooseImage: { id: 'upload_modal.choose_image', defaultMessage: 'Choose image' },
+  discardMessage: { id: 'confirmations.discard_edit_media.message', defaultMessage: 'You have unsaved changes to the media description or preview, discard them anyway?' },
+  discardConfirm: { id: 'confirmations.discard_edit_media.confirm', defaultMessage: 'Discard' },
+});
+
+const mapStateToProps = (state, { id }) => ({
+  media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
+  account: state.getIn(['accounts', me]),
+  isUploadingThumbnail: state.getIn(['compose', 'isUploadingThumbnail']),
+  description: state.getIn(['compose', 'media_modal', 'description']),
+  lang: state.getIn(['compose', 'language']),
+  focusX: state.getIn(['compose', 'media_modal', 'focusX']),
+  focusY: state.getIn(['compose', 'media_modal', 'focusY']),
+  dirty: state.getIn(['compose', 'media_modal', 'dirty']),
+  is_changing_upload: state.getIn(['compose', 'is_changing_upload']),
+});
+
+const mapDispatchToProps = (dispatch, { id }) => ({
+
+  onSave: (description, x, y) => {
+    dispatch(changeUploadCompose(id, { description, focus: `${x.toFixed(2)},${y.toFixed(2)}` }));
+  },
+
+  onChangeDescription: (description) => {
+    dispatch(onChangeMediaDescription(description));
+  },
+
+  onChangeFocus: (focusX, focusY) => {
+    dispatch(onChangeMediaFocus(focusX, focusY));
+  },
+
+  onSelectThumbnail: files => {
+    dispatch(uploadThumbnail(id, files[0]));
+  },
+
+});
+
+const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******')
+  .replace(/\n/g, ' ')
+  .replace(/\*\*\*\*\*\*/g, '\n\n');
+
+class ImageLoader extends PureComponent {
+
+  static propTypes = {
+    src: PropTypes.string.isRequired,
+    width: PropTypes.number,
+    height: PropTypes.number,
+  };
+
+  state = {
+    loading: true,
+  };
+
+  componentDidMount() {
+    const image = new Image();
+    image.addEventListener('load', () => this.setState({ loading: false }));
+    image.src = this.props.src;
+  }
+
+  render () {
+    const { loading } = this.state;
+
+    if (loading) {
+      return <canvas width={this.props.width} height={this.props.height} />;
+    } else {
+      return <img {...this.props} alt='' />;
+    }
+  }
+
+}
+
+class FocalPointModal extends ImmutablePureComponent {
+
+  static propTypes = {
+    media: ImmutablePropTypes.map.isRequired,
+    account: ImmutablePropTypes.record.isRequired,
+    isUploadingThumbnail: PropTypes.bool,
+    onSave: PropTypes.func.isRequired,
+    onChangeDescription: PropTypes.func.isRequired,
+    onChangeFocus: PropTypes.func.isRequired,
+    onSelectThumbnail: PropTypes.func.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  state = {
+    dragging: false,
+    dirty: false,
+    progress: 0,
+    loading: true,
+    ocrStatus: '',
+  };
+
+  componentWillUnmount () {
+    document.removeEventListener('mousemove', this.handleMouseMove);
+    document.removeEventListener('mouseup', this.handleMouseUp);
+  }
+
+  handleMouseDown = e => {
+    document.addEventListener('mousemove', this.handleMouseMove);
+    document.addEventListener('mouseup', this.handleMouseUp);
+
+    this.updatePosition(e);
+    this.setState({ dragging: true });
+  };
+
+  handleTouchStart = e => {
+    document.addEventListener('touchmove', this.handleMouseMove);
+    document.addEventListener('touchend', this.handleTouchEnd);
+
+    this.updatePosition(e);
+    this.setState({ dragging: true });
+  };
+
+  handleMouseMove = e => {
+    this.updatePosition(e);
+  };
+
+  handleMouseUp = () => {
+    document.removeEventListener('mousemove', this.handleMouseMove);
+    document.removeEventListener('mouseup', this.handleMouseUp);
+
+    this.setState({ dragging: false });
+  };
+
+  handleTouchEnd = () => {
+    document.removeEventListener('touchmove', this.handleMouseMove);
+    document.removeEventListener('touchend', this.handleTouchEnd);
+
+    this.setState({ dragging: false });
+  };
+
+  updatePosition = e => {
+    const { x, y } = getPointerPosition(this.node, e);
+    const focusX   = (x - .5) *  2;
+    const focusY   = (y - .5) * -2;
+
+    this.props.onChangeFocus(focusX, focusY);
+  };
+
+  handleChange = e => {
+    this.props.onChangeDescription(e.target.value);
+  };
+
+  handleKeyDown = (e) => {
+    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
+      this.props.onChangeDescription(e.target.value);
+      this.handleSubmit(e);
+    }
+  };
+
+  handleSubmit = (e) => {
+    e.preventDefault();
+    e.stopPropagation();
+    this.props.onSave(this.props.description, this.props.focusX, this.props.focusY);
+  };
+
+  getCloseConfirmationMessage = () => {
+    const { intl, dirty } = this.props;
+
+    if (dirty) {
+      return {
+        message: intl.formatMessage(messages.discardMessage),
+        confirm: intl.formatMessage(messages.discardConfirm),
+      };
+    } else {
+      return null;
+    }
+  };
+
+  setRef = c => {
+    this.node = c;
+  };
+
+  handleTextDetection = () => {
+    this._detectText();
+  };
+
+  _detectText = (refreshCache = false) => {
+    const { media } = this.props;
+
+    this.setState({ detecting: true });
+
+    fetchTesseract().then(({ createWorker }) => {
+      const worker = createWorker({
+        workerPath: tesseractWorkerPath,
+        corePath: tesseractCorePath,
+        langPath: `${assetHost}/ocr/lang-data`,
+        logger: ({ status, progress }) => {
+          if (status === 'recognizing text') {
+            this.setState({ ocrStatus: 'detecting', progress });
+          } else {
+            this.setState({ ocrStatus: 'preparing', progress });
+          }
+        },
+        cacheMethod: refreshCache ? 'refresh' : 'write',
+      });
+
+      let media_url = media.get('url');
+
+      if (window.URL && URL.createObjectURL) {
+        try {
+          media_url = URL.createObjectURL(media.get('file'));
+        } catch (error) {
+          console.error(error);
+        }
+      }
+
+      return (async () => {
+        await worker.load();
+        await worker.loadLanguage('eng');
+        await worker.initialize('eng');
+        const { data: { text } } = await worker.recognize(media_url);
+        this.setState({ detecting: false });
+        this.props.onChangeDescription(removeExtraLineBreaks(text));
+        await worker.terminate();
+      })().catch((e) => {
+        if (refreshCache) {
+          throw e;
+        } else {
+          this._detectText(true);
+        }
+      });
+    }).catch((e) => {
+      console.error(e);
+      this.setState({ detecting: false });
+    });
+  };
+
+  handleThumbnailChange = e => {
+    if (e.target.files.length > 0) {
+      this.props.onSelectThumbnail(e.target.files);
+    }
+  };
+
+  setFileInputRef = c => {
+    this.fileInput = c;
+  };
+
+  handleFileInputClick = () => {
+    this.fileInput.click();
+  };
+
+  render () {
+    const { media, intl, account, onClose, isUploadingThumbnail, description, lang, focusX, focusY, dirty, is_changing_upload } = this.props;
+    const { dragging, detecting, progress, ocrStatus } = this.state;
+    const x = (focusX /  2) + .5;
+    const y = (focusY / -2) + .5;
+
+    const width  = media.getIn(['meta', 'original', 'width']) || null;
+    const height = media.getIn(['meta', 'original', 'height']) || null;
+    const focals = ['image', 'gifv'].includes(media.get('type'));
+    const thumbnailable = ['audio', 'video'].includes(media.get('type'));
+
+    const previewRatio  = 16/9;
+    const previewWidth  = 200;
+    const previewHeight = previewWidth / previewRatio;
+
+    let descriptionLabel = null;
+
+    if (media.get('type') === 'audio') {
+      descriptionLabel = <FormattedMessage id='upload_form.audio_description' defaultMessage='Describe for people who are hard of hearing' />;
+    } else if (media.get('type') === 'video') {
+      descriptionLabel = <FormattedMessage id='upload_form.video_description' defaultMessage='Describe for people who are deaf, hard of hearing, blind or have low vision' />;
+    } else {
+      descriptionLabel = <FormattedMessage id='upload_form.description' defaultMessage='Describe for people who are blind or have low vision' />;
+    }
+
+    let ocrMessage = '';
+    if (ocrStatus === 'detecting') {
+      ocrMessage = <FormattedMessage id='upload_modal.analyzing_picture' defaultMessage='Analyzing picture…' />;
+    } else {
+      ocrMessage = <FormattedMessage id='upload_modal.preparing_ocr' defaultMessage='Preparing OCR…' />;
+    }
+
+    return (
+      <div className='modal-root__modal report-modal' style={{ maxWidth: 960 }}>
+        <div className='report-modal__target'>
+          <IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' iconComponent={CloseIcon} onClick={onClose} size={20} />
+          <FormattedMessage id='upload_modal.edit_media' defaultMessage='Edit media' />
+        </div>
+
+        <div className='report-modal__container'>
+          <form className='report-modal__comment' onSubmit={this.handleSubmit} >
+            {focals && <p><FormattedMessage id='upload_modal.hint' defaultMessage='Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.' /></p>}
+
+            {thumbnailable && (
+              <>
+                <label className='setting-text-label' htmlFor='upload-modal__thumbnail'><FormattedMessage id='upload_form.thumbnail' defaultMessage='Change thumbnail' /></label>
+
+                <Button disabled={isUploadingThumbnail || !media.get('unattached')} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} />
+
+                <label>
+                  <span style={{ display: 'none' }}>{intl.formatMessage(messages.chooseImage)}</span>
+
+                  <input
+                    id='upload-modal__thumbnail'
+                    ref={this.setFileInputRef}
+                    type='file'
+                    accept='image/png,image/jpeg'
+                    onChange={this.handleThumbnailChange}
+                    style={{ display: 'none' }}
+                    disabled={isUploadingThumbnail || is_changing_upload}
+                  />
+                </label>
+
+                <hr className='setting-divider' />
+              </>
+            )}
+
+            <label className='setting-text-label' htmlFor='upload-modal__description'>
+              {descriptionLabel}
+            </label>
+
+            <div className='setting-text__wrapper'>
+              <Textarea
+                id='upload-modal__description'
+                className='setting-text light'
+                value={detecting ? '…' : description}
+                lang={lang}
+                onChange={this.handleChange}
+                onKeyDown={this.handleKeyDown}
+                disabled={detecting || is_changing_upload}
+                autoFocus
+              />
+
+              <div className='setting-text__modifiers'>
+                <UploadProgress progress={progress * 100} active={detecting} icon='file-text-o' message={ocrMessage} />
+              </div>
+            </div>
+
+            <div className='setting-text__toolbar'>
+              <button
+                type='button'
+                disabled={detecting || media.get('type') !== 'image' || is_changing_upload}
+                className='link-button'
+                onClick={this.handleTextDetection}
+              >
+                <FormattedMessage id='upload_modal.detect_text' defaultMessage='Detect text from picture' />
+              </button>
+              <CharacterCounter max={1500} text={detecting ? '' : description} />
+            </div>
+
+            <Button
+              type='submit'
+              disabled={!dirty || detecting || isUploadingThumbnail || length(description) > 1500 || is_changing_upload}
+              text={intl.formatMessage(is_changing_upload ? messages.applying : messages.apply)}
+            />
+          </form>
+
+          <div className='focal-point-modal__content'>
+            {focals && (
+              <div className={classNames('focal-point', { dragging })} ref={this.setRef} onMouseDown={this.handleMouseDown} onTouchStart={this.handleTouchStart}>
+                {media.get('type') === 'image' && <ImageLoader src={media.get('url')} width={width} height={height} alt='' />}
+                {media.get('type') === 'gifv' && <GIFV src={media.get('url')} key={media.get('url')} width={width} height={height} />}
+
+                <div className='focal-point__preview'>
+                  <strong><FormattedMessage id='upload_modal.preview_label' defaultMessage='Preview ({ratio})' values={{ ratio: '16:9' }} /></strong>
+                  <div style={{ width: previewWidth, height: previewHeight, backgroundImage: `url(${media.get('preview_url')})`, backgroundSize: 'cover', backgroundPosition: `${x * 100}% ${y * 100}%` }} />
+                </div>
+
+                <div className='focal-point__reticle' style={{ top: `${y * 100}%`, left: `${x * 100}%` }} />
+                <div className='focal-point__overlay' />
+              </div>
+            )}
+
+            {media.get('type') === 'video' && (
+              <Video
+                preview={media.get('preview_url')}
+                frameRate={media.getIn(['meta', 'original', 'frame_rate'])}
+                blurhash={media.get('blurhash')}
+                src={media.get('url')}
+                detailed
+                inline
+                editable
+              />
+            )}
+
+            {media.get('type') === 'audio' && (
+              <Audio
+                src={media.get('url')}
+                duration={media.getIn(['meta', 'original', 'duration'], 0)}
+                height={150}
+                poster={media.get('preview_url') || account.get('avatar_static')}
+                backgroundColor={media.getIn(['meta', 'colors', 'background'])}
+                foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
+                accentColor={media.getIn(['meta', 'colors', 'accent'])}
+                editable
+              />
+            )}
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps, null, {
+  forwardRef: true,
+})(injectIntl(FocalPointModal, { forwardRef: true }));
diff --git a/app/javascript/mastodon/features/ui/components/hashtag_menu_controller.tsx b/app/javascript/mastodon/features/ui/components/hashtag_menu_controller.tsx
deleted file mode 100644
index 6707b24672..0000000000
--- a/app/javascript/mastodon/features/ui/components/hashtag_menu_controller.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
-
-import { useIntl, defineMessages } from 'react-intl';
-
-import { useLocation } from 'react-router-dom';
-
-import Overlay from 'react-overlays/Overlay';
-import type {
-  OffsetValue,
-  UsePopperOptions,
-} from 'react-overlays/esm/usePopper';
-
-import { DropdownMenu } from 'mastodon/components/dropdown_menu';
-import { useAppSelector } from 'mastodon/store';
-
-const messages = defineMessages({
-  browseHashtag: {
-    id: 'hashtag.browse',
-    defaultMessage: 'Browse posts in #{hashtag}',
-  },
-  browseHashtagFromAccount: {
-    id: 'hashtag.browse_from_account',
-    defaultMessage: 'Browse posts from @{name} in #{hashtag}',
-  },
-  muteHashtag: { id: 'hashtag.mute', defaultMessage: 'Mute #{hashtag}' },
-});
-
-const offset = [5, 5] as OffsetValue;
-const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
-
-const isHashtagLink = (
-  element: HTMLAnchorElement | null,
-): element is HTMLAnchorElement => {
-  if (!element) {
-    return false;
-  }
-
-  return element.matches('[data-menu-hashtag]');
-};
-
-interface TargetParams {
-  hashtag?: string;
-  accountId?: string;
-}
-
-export const HashtagMenuController: React.FC = () => {
-  const intl = useIntl();
-  const [open, setOpen] = useState(false);
-  const [{ accountId, hashtag }, setTargetParams] = useState<TargetParams>({});
-  const targetRef = useRef<HTMLAnchorElement | null>(null);
-  const location = useLocation();
-  const account = useAppSelector((state) =>
-    accountId ? state.accounts.get(accountId) : undefined,
-  );
-
-  useEffect(() => {
-    setOpen(false);
-    targetRef.current = null;
-  }, [setOpen, location]);
-
-  useEffect(() => {
-    const handleClick = (e: MouseEvent) => {
-      const target = (e.target as HTMLElement).closest('a');
-
-      if (e.button !== 0 || e.ctrlKey || e.metaKey) {
-        return;
-      }
-
-      if (!isHashtagLink(target)) {
-        return;
-      }
-
-      const hashtag = target.text.replace(/^#/, '');
-      const accountId = target.getAttribute('data-menu-hashtag');
-
-      if (!hashtag || !accountId) {
-        return;
-      }
-
-      e.preventDefault();
-      e.stopPropagation();
-      targetRef.current = target;
-      setOpen(true);
-      setTargetParams({ hashtag, accountId });
-    };
-
-    document.addEventListener('click', handleClick, { capture: true });
-
-    return () => {
-      document.removeEventListener('click', handleClick);
-    };
-  }, [setTargetParams, setOpen]);
-
-  const handleClose = useCallback(() => {
-    setOpen(false);
-    targetRef.current = null;
-  }, [setOpen]);
-
-  const menu = useMemo(
-    () => [
-      {
-        text: intl.formatMessage(messages.browseHashtag, {
-          hashtag,
-        }),
-        to: `/tags/${hashtag}`,
-      },
-      {
-        text: intl.formatMessage(messages.browseHashtagFromAccount, {
-          hashtag,
-          name: account?.username,
-        }),
-        to: `/@${account?.acct}/tagged/${hashtag}`,
-      },
-      null,
-      {
-        text: intl.formatMessage(messages.muteHashtag, {
-          hashtag,
-        }),
-        href: '/filters',
-        dangerous: true,
-      },
-    ],
-    [intl, hashtag, account],
-  );
-
-  if (!open) {
-    return null;
-  }
-
-  return (
-    <Overlay
-      show={open}
-      offset={offset}
-      placement='bottom'
-      flip
-      target={targetRef}
-      popperConfig={popperConfig}
-    >
-      {({ props, arrowProps, placement }) => (
-        <div {...props}>
-          <div className={`dropdown-animation dropdown-menu ${placement}`}>
-            <div
-              className={`dropdown-menu__arrow ${placement}`}
-              {...arrowProps}
-            />
-
-            <DropdownMenu
-              items={menu}
-              onClose={handleClose}
-              openedViaKeyboard={false}
-            />
-          </div>
-        </div>
-      )}
-    </Overlay>
-  );
-};
diff --git a/app/javascript/mastodon/features/ui/components/image_loader.jsx b/app/javascript/mastodon/features/ui/components/image_loader.jsx
new file mode 100644
index 0000000000..b1417deda7
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/image_loader.jsx
@@ -0,0 +1,175 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import classNames from 'classnames';
+
+import { LoadingBar } from 'react-redux-loading-bar';
+
+import ZoomableImage from './zoomable_image';
+
+export default class ImageLoader extends PureComponent {
+
+  static propTypes = {
+    alt: PropTypes.string,
+    lang: PropTypes.string,
+    src: PropTypes.string.isRequired,
+    previewSrc: PropTypes.string,
+    width: PropTypes.number,
+    height: PropTypes.number,
+    onClick: PropTypes.func,
+    zoomedIn: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    alt: '',
+    lang: '',
+    width: null,
+    height: null,
+  };
+
+  state = {
+    loading: true,
+    error: false,
+    width: null,
+  };
+
+  removers = [];
+  canvas = null;
+
+  get canvasContext() {
+    if (!this.canvas) {
+      return null;
+    }
+    this._canvasContext = this._canvasContext || this.canvas.getContext('2d');
+    return this._canvasContext;
+  }
+
+  componentDidMount () {
+    this.loadImage(this.props);
+  }
+
+  UNSAFE_componentWillReceiveProps (nextProps) {
+    if (this.props.src !== nextProps.src) {
+      this.loadImage(nextProps);
+    }
+  }
+
+  componentWillUnmount () {
+    this.removeEventListeners();
+  }
+
+  loadImage (props) {
+    this.removeEventListeners();
+    this.setState({ loading: true, error: false });
+    Promise.all([
+      props.previewSrc && this.loadPreviewCanvas(props),
+      this.hasSize() && this.loadOriginalImage(props),
+    ].filter(Boolean))
+      .then(() => {
+        this.setState({ loading: false, error: false });
+        this.clearPreviewCanvas();
+      })
+      .catch(() => this.setState({ loading: false, error: true }));
+  }
+
+  loadPreviewCanvas = ({ previewSrc, width, height }) => new Promise((resolve, reject) => {
+    const image = new Image();
+    const removeEventListeners = () => {
+      image.removeEventListener('error', handleError);
+      image.removeEventListener('load', handleLoad);
+    };
+    const handleError = () => {
+      removeEventListeners();
+      reject();
+    };
+    const handleLoad = () => {
+      removeEventListeners();
+      this.canvasContext.drawImage(image, 0, 0, width, height);
+      resolve();
+    };
+    image.addEventListener('error', handleError);
+    image.addEventListener('load', handleLoad);
+    image.src = previewSrc;
+    this.removers.push(removeEventListeners);
+  });
+
+  clearPreviewCanvas () {
+    const { width, height } = this.canvas;
+    this.canvasContext.clearRect(0, 0, width, height);
+  }
+
+  loadOriginalImage = ({ src }) => new Promise((resolve, reject) => {
+    const image = new Image();
+    const removeEventListeners = () => {
+      image.removeEventListener('error', handleError);
+      image.removeEventListener('load', handleLoad);
+    };
+    const handleError = () => {
+      removeEventListeners();
+      reject();
+    };
+    const handleLoad = () => {
+      removeEventListeners();
+      resolve();
+    };
+    image.addEventListener('error', handleError);
+    image.addEventListener('load', handleLoad);
+    image.src = src;
+    this.removers.push(removeEventListeners);
+  });
+
+  removeEventListeners () {
+    this.removers.forEach(listeners => listeners());
+    this.removers = [];
+  }
+
+  hasSize () {
+    const { width, height } = this.props;
+    return typeof width === 'number' && typeof height === 'number';
+  }
+
+  setCanvasRef = c => {
+    this.canvas = c;
+    if (c) this.setState({ width: c.offsetWidth });
+  };
+
+  render () {
+    const { alt, lang, src, width, height, onClick, zoomedIn } = this.props;
+    const { loading } = this.state;
+
+    const className = classNames('image-loader', {
+      'image-loader--loading': loading,
+      'image-loader--amorphous': !this.hasSize(),
+    });
+
+    return (
+      <div className={className}>
+        {loading ? (
+          <>
+            <div className='loading-bar__container' style={{ width: this.state.width || width }}>
+              <LoadingBar className='loading-bar' loading={1} />
+            </div>
+
+            <canvas
+              className='image-loader__preview-canvas'
+              ref={this.setCanvasRef}
+              width={width}
+              height={height}
+            />
+          </>
+        ) : (
+          <ZoomableImage
+            alt={alt}
+            lang={lang}
+            src={src}
+            onClick={onClick}
+            width={width}
+            height={height}
+            zoomedIn={zoomedIn}
+          />
+        )}
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/ui/components/image_modal.jsx b/app/javascript/mastodon/features/ui/components/image_modal.jsx
new file mode 100644
index 0000000000..f08ce15342
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/image_modal.jsx
@@ -0,0 +1,65 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import classNames from 'classnames';
+
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+import { IconButton } from 'mastodon/components/icon_button';
+
+import ImageLoader from './image_loader';
+
+const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
+});
+
+class ImageModal extends PureComponent {
+
+  static propTypes = {
+    src: PropTypes.string.isRequired,
+    alt: PropTypes.string.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  state = {
+    navigationHidden: false,
+  };
+
+  toggleNavigation = () => {
+    this.setState(prevState => ({
+      navigationHidden: !prevState.navigationHidden,
+    }));
+  };
+
+  render () {
+    const { intl, src, alt, onClose } = this.props;
+    const { navigationHidden } = this.state;
+
+    const navigationClassName = classNames('media-modal__navigation', {
+      'media-modal__navigation--hidden': navigationHidden,
+    });
+
+    return (
+      <div className='modal-root__modal media-modal'>
+        <div className='media-modal__closer' role='presentation' onClick={onClose} >
+          <ImageLoader
+            src={src}
+            width={400}
+            height={400}
+            alt={alt}
+            onClick={this.toggleNavigation}
+          />
+        </div>
+
+        <div className={navigationClassName}>
+          <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' iconComponent={CloseIcon} onClick={onClose} size={40} />
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default injectIntl(ImageModal);
diff --git a/app/javascript/mastodon/features/ui/components/image_modal.tsx b/app/javascript/mastodon/features/ui/components/image_modal.tsx
deleted file mode 100644
index fa94cfcc3c..0000000000
--- a/app/javascript/mastodon/features/ui/components/image_modal.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { useCallback, useState } from 'react';
-
-import { defineMessages, useIntl } from 'react-intl';
-
-import classNames from 'classnames';
-
-import CloseIcon from '@/material-icons/400-24px/close.svg?react';
-import { IconButton } from 'mastodon/components/icon_button';
-
-import { ZoomableImage } from './zoomable_image';
-
-const messages = defineMessages({
-  close: { id: 'lightbox.close', defaultMessage: 'Close' },
-});
-
-export const ImageModal: React.FC<{
-  src: string;
-  alt: string;
-  onClose: () => void;
-}> = ({ src, alt, onClose }) => {
-  const intl = useIntl();
-  const [navigationHidden, setNavigationHidden] = useState(false);
-
-  const toggleNavigation = useCallback(() => {
-    setNavigationHidden((prevState) => !prevState);
-  }, [setNavigationHidden]);
-
-  const navigationClassName = classNames('media-modal__navigation', {
-    'media-modal__navigation--hidden': navigationHidden,
-  });
-
-  return (
-    <div className='modal-root__modal media-modal'>
-      <div
-        className='media-modal__closer'
-        role='presentation'
-        onClick={onClose}
-      >
-        <ZoomableImage
-          src={src}
-          width={400}
-          height={400}
-          alt={alt}
-          onClick={toggleNavigation}
-        />
-      </div>
-
-      <div className={navigationClassName}>
-        <div className='media-modal__buttons'>
-          <IconButton
-            className='media-modal__close'
-            title={intl.formatMessage(messages.close)}
-            icon='times'
-            iconComponent={CloseIcon}
-            onClick={onClose}
-          />
-        </div>
-      </div>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/ui/components/link_footer.jsx b/app/javascript/mastodon/features/ui/components/link_footer.jsx
new file mode 100644
index 0000000000..49b21c2e48
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/link_footer.jsx
@@ -0,0 +1,95 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { FormattedMessage, injectIntl } from 'react-intl';
+
+import { Link } from 'react-router-dom';
+
+import { connect } from 'react-redux';
+
+import { openModal } from 'mastodon/actions/modal';
+import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
+import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'mastodon/initial_state';
+import { PERMISSION_INVITE_USERS } from 'mastodon/permissions';
+
+const mapDispatchToProps = (dispatch) => ({
+  onLogout () {
+    dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
+
+  },
+});
+
+class LinkFooter extends PureComponent {
+  static propTypes = {
+    identity: identityContextPropShape,
+    multiColumn: PropTypes.bool,
+    onLogout: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleLogoutClick = e => {
+    e.preventDefault();
+    e.stopPropagation();
+
+    this.props.onLogout();
+
+    return false;
+  };
+
+  render () {
+    const { signedIn, permissions } = this.props.identity;
+    const { multiColumn } = this.props;
+
+    const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS);
+    const canProfileDirectory = profileDirectory;
+
+    const DividingCircle = <span aria-hidden>{' · '}</span>;
+
+    return (
+      <div className='link-footer'>
+        <p>
+          <strong>{domain}</strong>:
+          {' '}
+          <Link to='/about' target={multiColumn ? '_blank' : undefined}><FormattedMessage id='footer.about' defaultMessage='About' /></Link>
+          {statusPageUrl && (
+            <>
+              {DividingCircle}
+              <a href={statusPageUrl} target='_blank' rel='noopener'><FormattedMessage id='footer.status' defaultMessage='Status' /></a>
+            </>
+          )}
+          {canInvite && (
+            <>
+              {DividingCircle}
+              <a href='/invites' target='_blank'><FormattedMessage id='footer.invite' defaultMessage='Invite people' /></a>
+            </>
+          )}
+          {canProfileDirectory && (
+            <>
+              {DividingCircle}
+              <Link to='/directory'><FormattedMessage id='footer.directory' defaultMessage='Profiles directory' /></Link>
+            </>
+          )}
+          {DividingCircle}
+          <Link to='/privacy-policy' target={multiColumn ? '_blank' : undefined}><FormattedMessage id='footer.privacy_policy' defaultMessage='Privacy policy' /></Link>
+        </p>
+
+        <p>
+          <strong>Mastodon</strong>:
+          {' '}
+          <a href='https://joinmastodon.org' target='_blank'><FormattedMessage id='footer.about' defaultMessage='About' /></a>
+          {DividingCircle}
+          <a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='footer.get_app' defaultMessage='Get the app' /></a>
+          {DividingCircle}
+          <Link to='/keyboard-shortcuts'><FormattedMessage id='footer.keyboard_shortcuts' defaultMessage='Keyboard shortcuts' /></Link>
+          {DividingCircle}
+          <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='footer.source_code' defaultMessage='View source code' /></a>
+          {DividingCircle}
+          <span className='version'>v{version}</span>
+        </p>
+      </div>
+    );
+  }
+
+}
+
+export default injectIntl(withIdentity(connect(null, mapDispatchToProps)(LinkFooter)));
diff --git a/app/javascript/mastodon/features/ui/components/link_footer.tsx b/app/javascript/mastodon/features/ui/components/link_footer.tsx
deleted file mode 100644
index cbfb6a3114..0000000000
--- a/app/javascript/mastodon/features/ui/components/link_footer.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-import { Link } from 'react-router-dom';
-
-import {
-  domain,
-  version,
-  source_url,
-  statusPageUrl,
-  profile_directory as canProfileDirectory,
-  termsOfServiceEnabled,
-} from 'mastodon/initial_state';
-
-const DividingCircle: React.FC = () => <span aria-hidden>{' · '}</span>;
-
-export const LinkFooter: React.FC<{
-  multiColumn: boolean;
-}> = ({ multiColumn }) => {
-  return (
-    <div className='link-footer'>
-      <p>
-        <strong>{domain}</strong>:{' '}
-        <Link to='/about' target={multiColumn ? '_blank' : undefined}>
-          <FormattedMessage id='footer.about' defaultMessage='About' />
-        </Link>
-        {statusPageUrl && (
-          <>
-            <DividingCircle />
-            <a href={statusPageUrl} target='_blank' rel='noopener'>
-              <FormattedMessage id='footer.status' defaultMessage='Status' />
-            </a>
-          </>
-        )}
-        {canProfileDirectory && (
-          <>
-            <DividingCircle />
-            <Link to='/directory'>
-              <FormattedMessage
-                id='footer.directory'
-                defaultMessage='Profiles directory'
-              />
-            </Link>
-          </>
-        )}
-        <DividingCircle />
-        <Link
-          to='/privacy-policy'
-          target={multiColumn ? '_blank' : undefined}
-          rel='privacy-policy'
-        >
-          <FormattedMessage
-            id='footer.privacy_policy'
-            defaultMessage='Privacy policy'
-          />
-        </Link>
-        {termsOfServiceEnabled && (
-          <>
-            <DividingCircle />
-            <Link
-              to='/terms-of-service'
-              target={multiColumn ? '_blank' : undefined}
-              rel='terms-of-service'
-            >
-              <FormattedMessage
-                id='footer.terms_of_service'
-                defaultMessage='Terms of service'
-              />
-            </Link>
-          </>
-        )}
-      </p>
-
-      <p>
-        <strong>Mastodon</strong>:{' '}
-        <a href='https://joinmastodon.org' target='_blank' rel='noopener'>
-          <FormattedMessage id='footer.about' defaultMessage='About' />
-        </a>
-        <DividingCircle />
-        <a href='https://joinmastodon.org/apps' target='_blank' rel='noopener'>
-          <FormattedMessage id='footer.get_app' defaultMessage='Get the app' />
-        </a>
-        <DividingCircle />
-        <Link to='/keyboard-shortcuts'>
-          <FormattedMessage
-            id='footer.keyboard_shortcuts'
-            defaultMessage='Keyboard shortcuts'
-          />
-        </Link>
-        <DividingCircle />
-        <a href={source_url} rel='noopener' target='_blank'>
-          <FormattedMessage
-            id='footer.source_code'
-            defaultMessage='View source code'
-          />
-        </a>
-        <DividingCircle />
-        <span className='version'>v{version}</span>
-      </p>
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/ui/components/list_panel.jsx b/app/javascript/mastodon/features/ui/components/list_panel.jsx
index 46cc84e662..b3435b7c93 100644
--- a/app/javascript/mastodon/features/ui/components/list_panel.jsx
+++ b/app/javascript/mastodon/features/ui/components/list_panel.jsx
@@ -16,7 +16,7 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => {
     return lists;
   }
 
-  return lists.toList().filter(item => !!item && item.get('favourite')).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
+  return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
 });
 
 const getOrderedAntennas = createSelector([state => state.get('antennas')], antennas => {
@@ -24,7 +24,7 @@ const getOrderedAntennas = createSelector([state => state.get('antennas')], ante
     return antennas;
   }
 
-  return antennas.toList().filter(item => !!item && item.get('favourite') && item.get('title') !== undefined).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
+  return antennas.toList().filter(item => !!item && !item.get('insert_feeds')).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
 });
 
 export const ListPanel = () => {
@@ -50,7 +50,7 @@ export const ListPanel = () => {
         <ColumnLink icon='list-ul' key={list.get('id')} iconComponent={ListAltIcon} activeIconComponent={ListAltActiveIcon} text={list.get('title')} to={`/lists/${list.get('id')}`} transparent />
       ))}
       {antennas && antennas.map(antenna => (
-        <ColumnLink icon='wifi' key={antenna.get('id')} iconComponent={AntennaIcon} activeIconComponent={AntennaIcon} text={antenna.get('title')} to={`/antennas/${antenna.get('id')}`} transparent />
+        <ColumnLink icon='wifi' key={antenna.get('id')} iconComponent={AntennaIcon} activeIconComponent={AntennaIcon} text={antenna.get('title')} to={`/antennast/${antenna.get('id')}`} transparent />
       ))}
     </div>
   );
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.jsx b/app/javascript/mastodon/features/ui/components/media_modal.jsx
index 9eb08ce9c3..d69ceba539 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.jsx
+++ b/app/javascript/mastodon/features/ui/components/media_modal.jsx
@@ -19,10 +19,10 @@ import { GIFV } from 'mastodon/components/gifv';
 import { Icon }  from 'mastodon/components/icon';
 import { IconButton } from 'mastodon/components/icon_button';
 import Footer from 'mastodon/features/picture_in_picture/components/footer';
-import { Video } from 'mastodon/features/video';
+import Video from 'mastodon/features/video';
 import { disableSwiping } from 'mastodon/initial_state';
 
-import { ZoomableImage } from './zoomable_image';
+import ImageLoader from './image_loader';
 
 const messages = defineMessages({
   close: { id: 'lightbox.close', defaultMessage: 'Close' },
@@ -59,12 +59,6 @@ class MediaModal extends ImmutablePureComponent {
     }));
   };
 
-  handleZoomChange = (zoomedIn) => {
-    this.setState({
-      zoomedIn,
-    });
-  };
-
   handleSwipe = (index) => {
     this.setState({
       index: index % this.props.media.size,
@@ -171,26 +165,23 @@ class MediaModal extends ImmutablePureComponent {
     const leftNav  = media.size > 1 && <button tabIndex={0} className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' icon={ChevronLeftIcon} /></button>;
     const rightNav = media.size > 1 && <button tabIndex={0} className='media-modal__nav  media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' icon={ChevronRightIcon} /></button>;
 
-    const content = media.map((image, idx) => {
+    const content = media.map((image) => {
       const width  = image.getIn(['meta', 'original', 'width']) || null;
       const height = image.getIn(['meta', 'original', 'height']) || null;
       const description = image.getIn(['translation', 'description']) || image.get('description');
 
       if (image.get('type') === 'image') {
         return (
-          <ZoomableImage
+          <ImageLoader
+            previewSrc={image.get('preview_url')}
             src={image.get('url')}
-            blurhash={image.get('blurhash')}
             width={width}
             height={height}
             alt={description}
             lang={lang}
             key={image.get('url')}
             onClick={this.handleToggleNavigation}
-            onDoubleClick={this.handleZoomClick}
-            onClose={onClose}
-            onZoomChange={this.handleZoomChange}
-            zoomedIn={zoomedIn && idx === index}
+            zoomedIn={zoomedIn}
           />
         );
       } else if (image.get('type') === 'video') {
@@ -205,9 +196,9 @@ class MediaModal extends ImmutablePureComponent {
             height={image.get('height')}
             frameRate={image.getIn(['meta', 'original', 'frame_rate'])}
             aspectRatio={`${image.getIn(['meta', 'original', 'width'])} / ${image.getIn(['meta', 'original', 'height'])}`}
-            startTime={currentTime || 0}
-            startPlaying={autoPlay || false}
-            startVolume={volume || 1}
+            currentTime={currentTime || 0}
+            autoPlay={autoPlay || false}
+            volume={volume || 1}
             onCloseVideo={onClose}
             detailed
             alt={description}
@@ -271,7 +262,7 @@ class MediaModal extends ImmutablePureComponent {
             onChangeIndex={this.handleSwipe}
             onTransitionEnd={this.handleTransitionEnd}
             index={index}
-            disabled={disableSwiping || zoomedIn}
+            disabled={disableSwiping}
           >
             {content}
           </ReactSwipeableViews>
diff --git a/app/javascript/mastodon/features/ui/components/modal_loading.jsx b/app/javascript/mastodon/features/ui/components/modal_loading.jsx
new file mode 100644
index 0000000000..7d19e73513
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/modal_loading.jsx
@@ -0,0 +1,18 @@
+import { LoadingIndicator } from '../../../components/loading_indicator';
+
+// Keep the markup in sync with <BundleModalError />
+// (make sure they have the same dimensions)
+const ModalLoading = () => (
+  <div className='modal-root__modal error-modal'>
+    <div className='error-modal__body'>
+      <LoadingIndicator />
+    </div>
+    <div className='error-modal__footer'>
+      <div>
+        <button className='error-modal__nav onboarding-modal__skip' />
+      </div>
+    </div>
+  </div>
+);
+
+export default ModalLoading;
diff --git a/app/javascript/mastodon/features/ui/components/modal_placeholder.tsx b/app/javascript/mastodon/features/ui/components/modal_placeholder.tsx
deleted file mode 100644
index 13ec6ca2c8..0000000000
--- a/app/javascript/mastodon/features/ui/components/modal_placeholder.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { useCallback } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import { Button } from 'mastodon/components/button';
-import { GIF } from 'mastodon/components/gif';
-import { LoadingIndicator } from 'mastodon/components/loading_indicator';
-
-export const ModalPlaceholder: React.FC<{
-  loading: boolean;
-  onClose: (arg0: string | undefined, arg1: boolean) => void;
-  onRetry?: () => void;
-}> = ({ loading, onClose, onRetry }) => {
-  const handleClose = useCallback(() => {
-    onClose(undefined, false);
-  }, [onClose]);
-
-  const handleRetry = useCallback(() => {
-    if (onRetry) onRetry();
-  }, [onRetry]);
-
-  return (
-    <div className='modal-root__modal modal-placeholder' aria-busy={loading}>
-      {loading ? (
-        <LoadingIndicator />
-      ) : (
-        <div className='modal-placeholder__error'>
-          <GIF
-            src='/oops.gif'
-            staticSrc='/oops.png'
-            className='modal-placeholder__error__image'
-          />
-
-          <div className='modal-placeholder__error__message'>
-            <p>
-              <FormattedMessage
-                id='bundle_modal_error.message'
-                defaultMessage='Something went wrong while loading this screen.'
-              />
-            </p>
-
-            <div className='modal-placeholder__error__message__actions'>
-              <Button onClick={handleRetry}>
-                <FormattedMessage
-                  id='bundle_modal_error.retry'
-                  defaultMessage='Try again'
-                />
-              </Button>
-              <Button onClick={handleClose} className='button button-tertiary'>
-                <FormattedMessage
-                  id='bundle_modal_error.close'
-                  defaultMessage='Close'
-                />
-              </Button>
-            </div>
-          </div>
-        </div>
-      )}
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx
index 4dac46681d..f5532ae318 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.jsx
+++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx
@@ -4,15 +4,17 @@ import { PureComponent } from 'react';
 import { Helmet } from 'react-helmet';
 
 import Base from 'mastodon/components/modal_root';
-import { AltTextModal } from 'mastodon/features/alt_text_modal';
 import {
   MuteModal,
   BlockModal,
   DomainBlockModal,
   ReportModal,
   EmbedModal,
+  ListEditor,
   ListAdder,
+  AntennaEditor,
   AntennaAdder,
+  CircleEditor,
   CircleAdder,
   BookmarkCategoryAdder,
   CompareHistoryModal,
@@ -21,7 +23,6 @@ import {
   SubscribedLanguagesModal,
   ClosedRegistrationsModal,
   IgnoreNotificationsModal,
-  AnnualReportModal,
 } from 'mastodon/features/ui/util/async-components';
 import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
 
@@ -30,24 +31,21 @@ import BundleContainer from '../containers/bundle_container';
 import ActionsModal from './actions_modal';
 import AudioModal from './audio_modal';
 import { BoostModal } from './boost_modal';
+import BundleModalError from './bundle_modal_error';
 import {
   ConfirmationModal,
   ConfirmDeleteStatusModal,
   ConfirmDeleteListModal,
-  ConfirmDeleteAntennaModal,
-  ConfirmDeleteCircleModal,
-  ConfirmDeleteBookmarkCategoryModal,
   ConfirmReplyModal,
   ConfirmEditStatusModal,
   ConfirmUnfollowModal,
   ConfirmClearNotificationsModal,
   ConfirmLogOutModal,
-  ConfirmFollowToListModal,
-  ConfirmMissingAltTextModal,
 } from './confirmation_modals';
-import { ImageModal } from './image_modal';
+import FocalPointModal from './focal_point_modal';
+import ImageModal from './image_modal';
 import MediaModal from './media_modal';
-import { ModalPlaceholder } from './modal_placeholder';
+import ModalLoading from './modal_loading';
 import VideoModal from './video_modal';
 
 export const MODAL_COMPONENTS = {
@@ -59,23 +57,21 @@ export const MODAL_COMPONENTS = {
   'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
   'CONFIRM_DELETE_STATUS': () => Promise.resolve({ default: ConfirmDeleteStatusModal }),
   'CONFIRM_DELETE_LIST': () => Promise.resolve({ default: ConfirmDeleteListModal }),
-  'CONFIRM_DELETE_ANTENNA': () => Promise.resolve({ default: ConfirmDeleteAntennaModal }),
-  'CONFIRM_DELETE_CIRCLE': () => Promise.resolve({ default: ConfirmDeleteCircleModal }),
-  'CONFIRM_DELETE_BOOKMARK_CATEGORY': () => Promise.resolve({ default: ConfirmDeleteBookmarkCategoryModal }),
   'CONFIRM_REPLY': () => Promise.resolve({ default: ConfirmReplyModal }),
   'CONFIRM_EDIT_STATUS': () => Promise.resolve({ default: ConfirmEditStatusModal }),
   'CONFIRM_UNFOLLOW': () => Promise.resolve({ default: ConfirmUnfollowModal }),
   'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }),
   'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
-  'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
-  'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
   'MUTE': MuteModal,
   'BLOCK': BlockModal,
   'DOMAIN_BLOCK': DomainBlockModal,
   'REPORT': ReportModal,
   'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
   'EMBED': EmbedModal,
-  'FOCAL_POINT': () => Promise.resolve({ default: AltTextModal }),
+  'LIST_EDITOR': ListEditor,
+  'ANTENNA_EDITOR': AntennaEditor,
+  'CIRCLE_EDITOR': CircleEditor,
+  'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
   'LIST_ADDER': ListAdder,
   'ANTENNA_ADDER': AntennaAdder,
   'CIRCLE_ADDER': CircleAdder,
@@ -86,7 +82,6 @@ export const MODAL_COMPONENTS = {
   'INTERACTION': InteractionModal,
   'CLOSED_REGISTRATIONS': ClosedRegistrationsModal,
   'IGNORE_NOTIFICATIONS': IgnoreNotificationsModal,
-  'ANNUAL_REPORT': AnnualReportModal,
 };
 
 export default class ModalRoot extends PureComponent {
@@ -120,16 +115,14 @@ export default class ModalRoot extends PureComponent {
     this.setState({ backgroundColor: color });
   };
 
-  renderLoading = () => {
-    const { onClose } = this.props;
-
-    return <ModalPlaceholder loading onClose={onClose} />;
+  renderLoading = modalId => () => {
+    return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
   };
 
   renderError = (props) => {
     const { onClose } = this.props;
 
-    return <ModalPlaceholder {...props} onClose={onClose} />;
+    return <BundleModalError {...props} onClose={onClose} />;
   };
 
   handleClose = (ignoreFocus = false) => {
@@ -151,9 +144,10 @@ export default class ModalRoot extends PureComponent {
       <Base backgroundColor={backgroundColor} onClose={this.handleClose} ignoreFocus={ignoreFocus}>
         {visible && (
           <>
-            <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading} error={this.renderError} renderDelay={200}>
+            <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
               {(SpecificComponent) => {
-                return <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={this.setModalRef} />;
+                const ref = typeof SpecificComponent !== 'function' ? this.setModalRef : undefined;
+                return <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={ref} />;
               }}
             </BundleContainer>
 
diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
index f3bf123415..c733dbbcbc 100644
--- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
+++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
@@ -1,7 +1,10 @@
 import PropTypes from 'prop-types';
 import { Component, useEffect } from 'react';
+
 import { defineMessages, injectIntl, useIntl } from 'react-intl';
+
 import { Link } from 'react-router-dom';
+
 import { useSelector, useDispatch } from 'react-redux';
 
 import CirclesIcon from '@/material-icons/400-24px/account_circle-fill.svg?react';
@@ -10,8 +13,6 @@ import BookmarksActiveIcon from '@/material-icons/400-24px/bookmarks-fill.svg?re
 import BookmarksIcon from '@/material-icons/400-24px/bookmarks.svg?react';
 import ExploreActiveIcon from '@/material-icons/400-24px/explore-fill.svg?react';
 import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
-import HashtagIcon from '@/material-icons/400-24px/tag.svg?react';
-import DirectoryIcon from '@/material-icons/400-24px/group.svg?react';
 import ModerationIcon from '@/material-icons/400-24px/gavel.svg?react';
 import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
 import HomeActiveIcon from '@/material-icons/400-24px/home-fill.svg?react';
@@ -30,7 +31,6 @@ import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
 import StarActiveIcon from '@/material-icons/400-24px/star-fill.svg?react';
 import StarIcon from '@/material-icons/400-24px/star.svg?react';
 import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
-
 import { fetchFollowRequests } from 'mastodon/actions/accounts';
 import { IconWithBadge } from 'mastodon/components/icon_with_badge';
 import { WordmarkLogo } from 'mastodon/components/logo';
@@ -50,8 +50,6 @@ const messages = defineMessages({
   home: { id: 'tabs_bar.home', defaultMessage: 'Home' },
   notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
   explore: { id: 'explore.title', defaultMessage: 'Explore' },
-  followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
-  directory: { id: 'navigation_bar.directory', defaultMessage: 'Profiles directory' },
   local: { id: 'column.local', defaultMessage: 'Local' },
   deepLocal: { id: 'column.deep_local', defaultMessage: 'Deep' },
   firehose: { id: 'column.firehose', defaultMessage: 'Live feeds' },
@@ -73,6 +71,7 @@ const messages = defineMessages({
 });
 
 const NotificationsLink = () => {
+
   const count = useSelector(selectUnreadNotificationGroupsCount);
   const intl = useIntl();
 
@@ -123,27 +122,32 @@ class NavigationPanel extends Component {
   };
 
   isAntennasActive = (match, location) => {
-    return (match || location.pathname.startsWith('/antennas'));
+    return (match || location.pathname.startsWith('/antennast'));
   };
 
   render () {
     const { intl } = this.props;
     const { signedIn, disabledAccountId, permissions } = this.props.identity;
 
-    const explorer = trendsEnabled ? (
+    const explorer = (trendsEnabled ? (
       <ColumnLink transparent to='/explore' icon='explore' iconComponent={ExploreIcon} activeIconComponent={ExploreActiveIcon} text={intl.formatMessage(messages.explore)} />
     ) : (
       <ColumnLink transparent to='/search' icon='search' iconComponent={SearchIcon} text={intl.formatMessage(messages.search)} />
-    );
+    ));
+    
+    let banner = undefined;
 
-    const banner = transientSingleColumn ? (
-      <div className='switch-to-advanced'>
-        {intl.formatMessage(messages.openedInClassicInterface)}{' '}
-        <a href={`/deck${location.pathname}`} className='switch-to-advanced__toggle'>
-          {intl.formatMessage(messages.advancedInterface)}
-        </a>
-      </div>
-    ) : null;
+    if (transientSingleColumn) {
+      banner = (
+        <div className='switch-to-advanced'>
+          {intl.formatMessage(messages.openedInClassicInterface)}
+          {" "}
+          <a href={`/deck${location.pathname}`} className='switch-to-advanced__toggle'>
+            {intl.formatMessage(messages.advancedInterface)}
+          </a>
+        </div>
+      );
+    }
 
     return (
       <div className='navigation-panel'>
@@ -151,59 +155,90 @@ class NavigationPanel extends Component {
           <Link to='/' className='column-link column-link--logo'><WordmarkLogo /></Link>
         </div>
 
-        {banner && <div className='navigation-panel__banner'>{banner}</div>}
+        {banner &&
+          <div className='navigation-panel__banner'>
+            {banner}
+          </div>
+        }
 
         <div className='navigation-panel__menu'>
           {signedIn && (
             <>
               <ColumnLink transparent to='/home' icon='home' iconComponent={HomeIcon} activeIconComponent={HomeActiveIcon} text={intl.formatMessage(messages.home)} />
               <NotificationsLink />
-              {enableLocalTimeline && <ColumnLink transparent to='/public/local/fixed' icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.local)} />}
-              {enableDtlMenu && dtlTag && <ColumnLink transparent to={`/tags/${dtlTag}`} icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.deepLocal)} />}
-              <ColumnLink transparent to='/public' isActive={this.isFirehoseActive} icon='globe' iconComponent={PublicIcon} text={intl.formatMessage(messages.firehose)} />
+            </>
+          )}
+
+          {signedIn && enableLocalTimeline && (
+            <ColumnLink transparent to='/public/local/fixed' icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.local)} />
+          )}
+
+          {signedIn && enableDtlMenu && dtlTag && (
+            <ColumnLink transparent to={`/tags/${dtlTag}`} icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.deepLocal)} />
+          )}
+
+          {!signedIn && explorer}
+
+          {signedIn && (
+            <ColumnLink transparent to='/public' isActive={this.isFirehoseActive} icon='globe' iconComponent={PublicIcon} text={intl.formatMessage(messages.firehose)} />
+          )}
+
+          {(!signedIn && timelinePreview) && (
+            <ColumnLink transparent to={enableLocalTimeline ? '/public/local' : '/public'} isActive={this.isFirehoseActive} icon='globe' iconComponent={PublicIcon} text={intl.formatMessage(messages.firehose)} />
+          )}
+
+          {signedIn && (
+            <>
               <ListPanel />
               <hr />
+            </>
+          )}
+
+          {signedIn && (
+            <>
               <ColumnLink transparent to='/lists' icon='list-ul' iconComponent={ListAltIcon} activeIconComponent={ListAltActiveIcon} text={intl.formatMessage(messages.lists)} />
-              <ColumnLink transparent to='/antennas' icon='wifi' iconComponent={AntennaIcon} text={intl.formatMessage(messages.antennas)} isActive={this.isAntennasActive} />
+              <ColumnLink transparent to='/antennasw' icon='wifi' iconComponent={AntennaIcon} text={intl.formatMessage(messages.antennas)} isActive={this.isAntennasActive} />
               <ColumnLink transparent to='/circles' icon='user-circle' iconComponent={CirclesIcon} text={intl.formatMessage(messages.circles)} />
-              <ColumnLink transparent to='/followed_tags' icon='tag' iconComponent={HashtagIcon} text={intl.formatMessage(messages.followed_tags)} />
-              <ColumnLink transparent to='/directory' icon='group' iconComponent={DirectoryIcon} text={intl.formatMessage(messages.directory)} />
               <FollowRequestsLink />
               <ColumnLink transparent to='/conversations' icon='at' iconComponent={AlternateEmailIcon} text={intl.formatMessage(messages.direct)} />
-              {explorer}
+            </>
+          )}
+
+          {signedIn && explorer}
+
+          {signedIn && (
+            <>
               <ColumnLink transparent to='/bookmark_categories' icon='bookmarks' iconComponent={BookmarksIcon} activeIconComponent={BookmarksActiveIcon} text={intl.formatMessage(messages.bookmarks)} />
-              {!isHideItem('favourite_menu') && <ColumnLink transparent to='/favourites' icon='star' iconComponent={StarIcon} activeIconComponent={StarActiveIcon} text={intl.formatMessage(messages.favourites)} />}
+              { !isHideItem('favourite_menu') && <ColumnLink transparent to='/favourites' icon='star' iconComponent={StarIcon} activeIconComponent={StarActiveIcon} text={intl.formatMessage(messages.favourites)} /> }
               <hr />
+
               <ColumnLink transparent href='/settings/preferences' icon='cog' iconComponent={SettingsIcon} text={intl.formatMessage(messages.preferences)} />
+
               {canManageReports(permissions) && <ColumnLink transparent href='/admin/reports' icon='flag' iconComponent={ModerationIcon} text={intl.formatMessage(messages.moderation)} />}
               {canViewAdminDashboard(permissions) && <ColumnLink transparent href='/admin/dashboard' icon='tachometer' iconComponent={AdministrationIcon} text={intl.formatMessage(messages.administration)} />}
             </>
           )}
 
           {!signedIn && (
-            <>
-              {explorer}
-              {(timelinePreview || enableLocalTimeline) && (
-                <ColumnLink transparent to={enableLocalTimeline ? '/public/local' : '/public'} isActive={this.isFirehoseActive} icon='globe' iconComponent={PublicIcon} text={intl.formatMessage(messages.firehose)} />
-              )}
-              <div className='navigation-panel__sign-in-banner'>
-                <hr />
-                {disabledAccountId ? <DisabledAccountBanner /> : <SignInBanner />}
-              </div>
-            </>
+            <div className='navigation-panel__sign-in-banner'>
+              <hr />
+              { disabledAccountId ? <DisabledAccountBanner /> : <SignInBanner /> }
+            </div>
           )}
-        </div>
 
-        <div className='navigation-panel__legal'>
-          <hr />
-          <ColumnLink transparent to='/about' icon='ellipsis-h' iconComponent={MoreHorizIcon} text={intl.formatMessage(messages.about)} />
+          <div className='navigation-panel__legal'>
+            <hr />
+            <ColumnLink transparent to='/about' icon='ellipsis-h' iconComponent={MoreHorizIcon} text={intl.formatMessage(messages.about)} />
+          </div>
         </div>
 
         <div className='flex-spacer' />
+
         <NavigationPortal />
       </div>
     );
   }
+
 }
 
 export default injectIntl(withIdentity(NavigationPanel));
diff --git a/app/javascript/mastodon/features/ui/components/upload_area.jsx b/app/javascript/mastodon/features/ui/components/upload_area.jsx
new file mode 100644
index 0000000000..b2702d35ef
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/upload_area.jsx
@@ -0,0 +1,55 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { FormattedMessage } from 'react-intl';
+
+import spring from 'react-motion/lib/spring';
+
+import Motion from '../util/optional_motion';
+
+export default class UploadArea extends PureComponent {
+
+  static propTypes = {
+    active: PropTypes.bool,
+    onClose: PropTypes.func,
+  };
+
+  handleKeyUp = (e) => {
+    const keyCode = e.keyCode;
+    if (this.props.active) {
+      switch(keyCode) {
+      case 27:
+        e.preventDefault();
+        e.stopPropagation();
+        this.props.onClose();
+        break;
+      }
+    }
+  };
+
+  componentDidMount () {
+    window.addEventListener('keyup', this.handleKeyUp, false);
+  }
+
+  componentWillUnmount () {
+    window.removeEventListener('keyup', this.handleKeyUp);
+  }
+
+  render () {
+    const { active } = this.props;
+
+    return (
+      <Motion defaultStyle={{ backgroundOpacity: 0, backgroundScale: 0.95 }} style={{ backgroundOpacity: spring(active ? 1 : 0, { stiffness: 150, damping: 15 }), backgroundScale: spring(active ? 1 : 0.95, { stiffness: 200, damping: 3 }) }}>
+        {({ backgroundOpacity, backgroundScale }) => (
+          <div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}>
+            <div className='upload-area__drop'>
+              <div className='upload-area__background' style={{ transform: `scale(${backgroundScale})` }} />
+              <div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div>
+            </div>
+          </div>
+        )}
+      </Motion>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/ui/components/upload_area.tsx b/app/javascript/mastodon/features/ui/components/upload_area.tsx
deleted file mode 100644
index 87ac090e7e..0000000000
--- a/app/javascript/mastodon/features/ui/components/upload_area.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { useCallback, useEffect } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import { animated, config, useSpring } from '@react-spring/web';
-
-import { reduceMotion } from 'mastodon/initial_state';
-
-interface UploadAreaProps {
-  active?: boolean;
-  onClose: () => void;
-}
-
-export const UploadArea: React.FC<UploadAreaProps> = ({ active, onClose }) => {
-  const handleKeyUp = useCallback(
-    (e: KeyboardEvent) => {
-      if (active && e.key === 'Escape') {
-        e.preventDefault();
-        e.stopPropagation();
-        onClose();
-      }
-    },
-    [active, onClose],
-  );
-
-  useEffect(() => {
-    window.addEventListener('keyup', handleKeyUp, false);
-
-    return () => {
-      window.removeEventListener('keyup', handleKeyUp);
-    };
-  }, [handleKeyUp]);
-
-  const wrapperAnimStyles = useSpring({
-    from: {
-      opacity: 0,
-    },
-    to: {
-      opacity: 1,
-    },
-    reverse: !active,
-    immediate: reduceMotion,
-  });
-  const backgroundAnimStyles = useSpring({
-    from: {
-      transform: 'scale(0.95)',
-    },
-    to: {
-      transform: 'scale(1)',
-    },
-    reverse: !active,
-    config: config.wobbly,
-    immediate: reduceMotion,
-  });
-
-  return (
-    <animated.div
-      className='upload-area'
-      style={{
-        ...wrapperAnimStyles,
-        visibility: active ? 'visible' : 'hidden',
-      }}
-    >
-      <div className='upload-area__drop'>
-        <animated.div
-          className='upload-area__background'
-          style={backgroundAnimStyles}
-        />
-        <div className='upload-area__content'>
-          <FormattedMessage
-            id='upload_area.title'
-            defaultMessage='Drag & drop to upload'
-          />
-        </div>
-      </div>
-    </animated.div>
-  );
-};
diff --git a/app/javascript/mastodon/features/ui/components/video_modal.jsx b/app/javascript/mastodon/features/ui/components/video_modal.jsx
index ed58d642a4..cc166d8bc5 100644
--- a/app/javascript/mastodon/features/ui/components/video_modal.jsx
+++ b/app/javascript/mastodon/features/ui/components/video_modal.jsx
@@ -6,7 +6,7 @@ import { connect } from 'react-redux';
 
 import { getAverageFromBlurhash } from 'mastodon/blurhash';
 import Footer from 'mastodon/features/picture_in_picture/components/footer';
-import { Video } from 'mastodon/features/video';
+import Video from 'mastodon/features/video';
 
 const mapStateToProps = (state, { statusId }) => ({
   status: state.getIn(['statuses', statusId]),
@@ -37,10 +37,6 @@ class VideoModal extends ImmutablePureComponent {
     }
   }
 
-  componentWillUnmount () {
-    this.props.onChangeBackgroundColor(null);
-  }
-
   render () {
     const { media, status, onClose } = this.props;
     const options = this.props.options || {};
@@ -56,9 +52,9 @@ class VideoModal extends ImmutablePureComponent {
             aspectRatio={`${media.getIn(['meta', 'original', 'width'])} / ${media.getIn(['meta', 'original', 'height'])}`}
             blurhash={media.get('blurhash')}
             src={media.get('url')}
-            startTime={options.startTime}
-            startPlaying={options.autoPlay}
-            startVolume={options.defaultVolume}
+            currentTime={options.startTime}
+            autoPlay={options.autoPlay}
+            volume={options.defaultVolume}
             onCloseVideo={onClose}
             autoFocus
             detailed
diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.jsx b/app/javascript/mastodon/features/ui/components/zoomable_image.jsx
new file mode 100644
index 0000000000..c4129bf260
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/zoomable_image.jsx
@@ -0,0 +1,402 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+const MIN_SCALE = 1;
+const MAX_SCALE = 4;
+const NAV_BAR_HEIGHT = 66;
+
+const getMidpoint = (p1, p2) => ({
+  x: (p1.clientX + p2.clientX) / 2,
+  y: (p1.clientY + p2.clientY) / 2,
+});
+
+const getDistance = (p1, p2) =>
+  Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2));
+
+const clamp = (min, max, value) => Math.min(max, Math.max(min, value));
+
+// Normalizing mousewheel speed across browsers
+// copy from: https://github.com/facebookarchive/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js
+const normalizeWheel = event => {
+  // Reasonable defaults
+  const PIXEL_STEP = 10;
+  const LINE_HEIGHT = 40;
+  const PAGE_HEIGHT = 800;
+
+  let sX = 0,
+    sY = 0, // spinX, spinY
+    pX = 0,
+    pY = 0; // pixelX, pixelY
+
+  // Legacy
+  if ('detail' in event) {
+    sY = event.detail;
+  }
+  if ('wheelDelta' in event) {
+    sY = -event.wheelDelta / 120;
+  }
+  if ('wheelDeltaY' in event) {
+    sY = -event.wheelDeltaY / 120;
+  }
+  if ('wheelDeltaX' in event) {
+    sX = -event.wheelDeltaX / 120;
+  }
+
+  // side scrolling on FF with DOMMouseScroll
+  if ('axis' in event && event.axis === event.HORIZONTAL_AXIS) {
+    sX = sY;
+    sY = 0;
+  }
+
+  pX = sX * PIXEL_STEP;
+  pY = sY * PIXEL_STEP;
+
+  if ('deltaY' in event) {
+    pY = event.deltaY;
+  }
+  if ('deltaX' in event) {
+    pX = event.deltaX;
+  }
+
+  if ((pX || pY) && event.deltaMode) {
+    if (event.deltaMode === 1) { // delta in LINE units
+      pX *= LINE_HEIGHT;
+      pY *= LINE_HEIGHT;
+    } else { // delta in PAGE units
+      pX *= PAGE_HEIGHT;
+      pY *= PAGE_HEIGHT;
+    }
+  }
+
+  // Fall-back if spin cannot be determined
+  if (pX && !sX) {
+    sX = (pX < 1) ? -1 : 1;
+  }
+  if (pY && !sY) {
+    sY = (pY < 1) ? -1 : 1;
+  }
+
+  return {
+    spinX: sX,
+    spinY: sY,
+    pixelX: pX,
+    pixelY: pY,
+  };
+};
+
+class ZoomableImage extends PureComponent {
+
+  static propTypes = {
+    alt: PropTypes.string,
+    lang: PropTypes.string,
+    src: PropTypes.string.isRequired,
+    width: PropTypes.number,
+    height: PropTypes.number,
+    onClick: PropTypes.func,
+    zoomedIn: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    alt: '',
+    lang: '',
+    width: null,
+    height: null,
+  };
+
+  state = {
+    scale: MIN_SCALE,
+    zoomMatrix: {
+      type: null, // 'width' 'height'
+      fullScreen: null, // bool
+      rate: null, // full screen scale rate
+      clientWidth: null,
+      clientHeight: null,
+      offsetWidth: null,
+      offsetHeight: null,
+      clientHeightFixed: null,
+      scrollTop: null,
+      scrollLeft: null,
+      translateX: null,
+      translateY: null,
+    },
+    dragPosition: { top: 0, left: 0, x: 0, y: 0 },
+    dragged: false,
+    lockScroll: { x: 0, y: 0 },
+    lockTranslate: { x: 0, y: 0 },
+  };
+
+  removers = [];
+  container = null;
+  image = null;
+  lastTouchEndTime = 0;
+  lastDistance = 0;
+
+  componentDidMount () {
+    let handler = this.handleTouchStart;
+    this.container.addEventListener('touchstart', handler);
+    this.removers.push(() => this.container.removeEventListener('touchstart', handler));
+    handler = this.handleTouchMove;
+    // on Chrome 56+, touch event listeners will default to passive
+    // https://www.chromestatus.com/features/5093566007214080
+    this.container.addEventListener('touchmove', handler, { passive: false });
+    this.removers.push(() => this.container.removeEventListener('touchend', handler));
+
+    handler = this.mouseDownHandler;
+    this.container.addEventListener('mousedown', handler);
+    this.removers.push(() => this.container.removeEventListener('mousedown', handler));
+
+    handler = this.mouseWheelHandler;
+    this.container.addEventListener('wheel', handler);
+    this.removers.push(() => this.container.removeEventListener('wheel', handler));
+    // Old Chrome
+    this.container.addEventListener('mousewheel', handler);
+    this.removers.push(() => this.container.removeEventListener('mousewheel', handler));
+    // Old Firefox
+    this.container.addEventListener('DOMMouseScroll', handler);
+    this.removers.push(() => this.container.removeEventListener('DOMMouseScroll', handler));
+
+    this._initZoomMatrix();
+  }
+
+  componentWillUnmount () {
+    this._removeEventListeners();
+  }
+
+  componentDidUpdate (prevProps) {
+    if (prevProps.zoomedIn !== this.props.zoomedIn) {
+      this._toggleZoom();
+    }
+  }
+
+  _removeEventListeners () {
+    this.removers.forEach(listeners => listeners());
+    this.removers = [];
+  }
+
+  mouseWheelHandler = e => {
+    e.preventDefault();
+
+    const event = normalizeWheel(e);
+
+    if (this.state.zoomMatrix.type === 'width') {
+      // full width, scroll vertical
+      this.container.scrollTop = Math.max(this.container.scrollTop + event.pixelY, this.state.lockScroll.y);
+    } else {
+      // full height, scroll horizontal
+      this.container.scrollLeft = Math.max(this.container.scrollLeft + event.pixelY, this.state.lockScroll.x);
+    }
+
+    // lock horizontal scroll
+    this.container.scrollLeft = Math.max(this.container.scrollLeft + event.pixelX, this.state.lockScroll.x);
+  };
+
+  mouseDownHandler = e => {
+    this.setState({ dragPosition: {
+      left: this.container.scrollLeft,
+      top: this.container.scrollTop,
+      // Get the current mouse position
+      x: e.clientX,
+      y: e.clientY,
+    } });
+
+    this.image.addEventListener('mousemove', this.mouseMoveHandler);
+    this.image.addEventListener('mouseup', this.mouseUpHandler);
+  };
+
+  mouseMoveHandler = e => {
+    const dx = e.clientX - this.state.dragPosition.x;
+    const dy = e.clientY - this.state.dragPosition.y;
+
+    this.container.scrollLeft = Math.max(this.state.dragPosition.left - dx, this.state.lockScroll.x);
+    this.container.scrollTop = Math.max(this.state.dragPosition.top - dy, this.state.lockScroll.y);
+
+    this.setState({ dragged: true });
+  };
+
+  mouseUpHandler = () => {
+    this.image.removeEventListener('mousemove', this.mouseMoveHandler);
+    this.image.removeEventListener('mouseup', this.mouseUpHandler);
+  };
+
+  handleTouchStart = e => {
+    if (e.touches.length !== 2) return;
+
+    this.lastDistance = getDistance(...e.touches);
+  };
+
+  handleTouchMove = e => {
+    const { scrollTop, scrollHeight, clientHeight } = this.container;
+    if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) {
+      // prevent propagating event to MediaModal
+      e.stopPropagation();
+      return;
+    }
+    if (e.touches.length !== 2) return;
+
+    e.preventDefault();
+    e.stopPropagation();
+
+    const distance = getDistance(...e.touches);
+    const midpoint = getMidpoint(...e.touches);
+    const _MAX_SCALE = Math.max(MAX_SCALE, this.state.zoomMatrix.rate);
+    const scale = clamp(MIN_SCALE, _MAX_SCALE, this.state.scale * distance / this.lastDistance);
+
+    this._zoom(scale, midpoint);
+
+    this.lastMidpoint = midpoint;
+    this.lastDistance = distance;
+  };
+
+  _zoom(nextScale, midpoint) {
+    const { scale, zoomMatrix } = this.state;
+    const { scrollLeft, scrollTop } = this.container;
+
+    // math memo:
+    // x = (scrollLeft + midpoint.x) / scrollWidth
+    // x' = (nextScrollLeft + midpoint.x) / nextScrollWidth
+    // scrollWidth = clientWidth * scale
+    // scrollWidth' = clientWidth * nextScale
+    // Solve x = x' for nextScrollLeft
+    const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x;
+    const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y;
+
+    this.setState({ scale: nextScale }, () => {
+      this.container.scrollLeft = nextScrollLeft;
+      this.container.scrollTop = nextScrollTop;
+      // reset the translateX/Y constantly
+      if (nextScale < zoomMatrix.rate) {
+        this.setState({
+          lockTranslate: {
+            x: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateX * ((nextScale - MIN_SCALE) / (zoomMatrix.rate - MIN_SCALE)),
+            y: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateY * ((nextScale - MIN_SCALE) / (zoomMatrix.rate - MIN_SCALE)),
+          },
+        });
+      }
+    });
+  }
+
+  handleClick = e => {
+    // don't propagate event to MediaModal
+    e.stopPropagation();
+    const dragged = this.state.dragged;
+    this.setState({ dragged: false });
+    if (dragged) return;
+    const handler = this.props.onClick;
+    if (handler) handler();
+  };
+
+  handleMouseDown = e => {
+    e.preventDefault();
+  };
+
+  _initZoomMatrix = () => {
+    const { width, height } = this.props;
+    const { clientWidth, clientHeight } = this.container;
+    const { offsetWidth, offsetHeight } = this.image;
+    const clientHeightFixed = clientHeight - NAV_BAR_HEIGHT;
+
+    const type = width / height < clientWidth / clientHeightFixed ? 'width' : 'height';
+    const fullScreen = type === 'width' ?  width > clientWidth : height > clientHeightFixed;
+    const rate = type === 'width' ? Math.min(clientWidth, width) / offsetWidth : Math.min(clientHeightFixed, height) / offsetHeight;
+    const scrollTop = type === 'width' ?  (clientHeight - offsetHeight) / 2 - NAV_BAR_HEIGHT : (clientHeightFixed - offsetHeight) / 2;
+    const scrollLeft = (clientWidth - offsetWidth) / 2;
+    const translateX = type === 'width' ? (width - offsetWidth) / (2 * rate) : 0;
+    const translateY = type === 'height' ? (height - offsetHeight) / (2 * rate) : 0;
+
+    this.setState({
+      zoomMatrix: {
+        type: type,
+        fullScreen: fullScreen,
+        rate: rate,
+        clientWidth: clientWidth,
+        clientHeight: clientHeight,
+        offsetWidth: offsetWidth,
+        offsetHeight: offsetHeight,
+        clientHeightFixed: clientHeightFixed,
+        scrollTop: scrollTop,
+        scrollLeft: scrollLeft,
+        translateX: translateX,
+        translateY: translateY,
+      },
+    });
+  };
+
+  _toggleZoom () {
+    const { scale, zoomMatrix } = this.state;
+
+    if ( scale >= zoomMatrix.rate ) {
+      this.setState({
+        scale: MIN_SCALE,
+        lockScroll: {
+          x: 0,
+          y: 0,
+        },
+        lockTranslate: {
+          x: 0,
+          y: 0,
+        },
+      }, () => {
+        this.container.scrollLeft = 0;
+        this.container.scrollTop = 0;
+      });
+    } else {
+      this.setState({
+        scale: zoomMatrix.rate,
+        lockScroll: {
+          x: zoomMatrix.scrollLeft,
+          y: zoomMatrix.scrollTop,
+        },
+        lockTranslate: {
+          x: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateX,
+          y: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateY,
+        },
+      }, () => {
+        this.container.scrollLeft = zoomMatrix.scrollLeft;
+        this.container.scrollTop = zoomMatrix.scrollTop;
+      });
+    }
+  }
+
+  setContainerRef = c => {
+    this.container = c;
+  };
+
+  setImageRef = c => {
+    this.image = c;
+  };
+
+  render () {
+    const { alt, lang, src, width, height } = this.props;
+    const { scale, lockTranslate, dragged } = this.state;
+    const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll';
+    const cursor = scale === MIN_SCALE ? null : (dragged ? 'grabbing' : 'grab');
+
+    return (
+      <div
+        className='zoomable-image'
+        ref={this.setContainerRef}
+        style={{ overflow, cursor, userSelect: 'none' }}
+      >
+        <img
+          role='presentation'
+          ref={this.setImageRef}
+          alt={alt}
+          title={alt}
+          lang={lang}
+          src={src}
+          width={width}
+          height={height}
+          style={{
+            transform: `scale(${scale}) translate(-${lockTranslate.x}px, -${lockTranslate.y}px)`,
+            transformOrigin: '0 0',
+          }}
+          draggable={false}
+          onClick={this.handleClick}
+          onMouseDown={this.handleMouseDown}
+        />
+      </div>
+    );
+  }
+}
+
+export default ZoomableImage;
diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.tsx b/app/javascript/mastodon/features/ui/components/zoomable_image.tsx
deleted file mode 100644
index 85e29e6aea..0000000000
--- a/app/javascript/mastodon/features/ui/components/zoomable_image.tsx
+++ /dev/null
@@ -1,319 +0,0 @@
-import { useState, useCallback, useRef, useEffect } from 'react';
-
-import classNames from 'classnames';
-
-import { useSpring, animated, config } from '@react-spring/web';
-import { createUseGesture, dragAction, pinchAction } from '@use-gesture/react';
-
-import { Blurhash } from 'mastodon/components/blurhash';
-import { LoadingIndicator } from 'mastodon/components/loading_indicator';
-
-const MIN_SCALE = 1;
-const MAX_SCALE = 4;
-const DOUBLE_CLICK_THRESHOLD = 250;
-
-interface ZoomMatrix {
-  containerWidth: number;
-  containerHeight: number;
-  imageWidth: number;
-  imageHeight: number;
-  initialScale: number;
-}
-
-const createZoomMatrix = (
-  container: HTMLElement,
-  image: HTMLImageElement,
-  fullWidth: number,
-  fullHeight: number,
-): ZoomMatrix => {
-  const { clientWidth, clientHeight } = container;
-  const { offsetWidth, offsetHeight } = image;
-
-  const type =
-    fullWidth / fullHeight < clientWidth / clientHeight ? 'width' : 'height';
-
-  const initialScale =
-    type === 'width'
-      ? Math.min(clientWidth, fullWidth) / offsetWidth
-      : Math.min(clientHeight, fullHeight) / offsetHeight;
-
-  return {
-    containerWidth: clientWidth,
-    containerHeight: clientHeight,
-    imageWidth: offsetWidth,
-    imageHeight: offsetHeight,
-    initialScale,
-  };
-};
-
-const useGesture = createUseGesture([dragAction, pinchAction]);
-
-const getBounds = (zoomMatrix: ZoomMatrix | null, scale: number) => {
-  if (!zoomMatrix || scale === MIN_SCALE) {
-    return {
-      left: -Infinity,
-      right: Infinity,
-      top: -Infinity,
-      bottom: Infinity,
-    };
-  }
-
-  const { containerWidth, containerHeight, imageWidth, imageHeight } =
-    zoomMatrix;
-
-  const bounds = {
-    left: -Math.max(imageWidth * scale - containerWidth, 0) / 2,
-    right: Math.max(imageWidth * scale - containerWidth, 0) / 2,
-    top: -Math.max(imageHeight * scale - containerHeight, 0) / 2,
-    bottom: Math.max(imageHeight * scale - containerHeight, 0) / 2,
-  };
-
-  return bounds;
-};
-
-interface ZoomableImageProps {
-  alt?: string;
-  lang?: string;
-  src: string;
-  width: number;
-  height: number;
-  onClick?: () => void;
-  onDoubleClick?: () => void;
-  onClose?: () => void;
-  onZoomChange?: (zoomedIn: boolean) => void;
-  zoomedIn?: boolean;
-  blurhash?: string;
-}
-
-export const ZoomableImage: React.FC<ZoomableImageProps> = ({
-  alt = '',
-  lang = '',
-  src,
-  width,
-  height,
-  onClick,
-  onDoubleClick,
-  onClose,
-  onZoomChange,
-  zoomedIn,
-  blurhash,
-}) => {
-  useEffect(() => {
-    const handler = (e: Event) => {
-      e.preventDefault();
-    };
-
-    document.addEventListener('gesturestart', handler);
-    document.addEventListener('gesturechange', handler);
-    document.addEventListener('gestureend', handler);
-
-    return () => {
-      document.removeEventListener('gesturestart', handler);
-      document.removeEventListener('gesturechange', handler);
-      document.removeEventListener('gestureend', handler);
-    };
-  }, []);
-
-  const [dragging, setDragging] = useState(false);
-  const [loaded, setLoaded] = useState(false);
-  const [error, setError] = useState(false);
-
-  const containerRef = useRef<HTMLDivElement>(null);
-  const imageRef = useRef<HTMLImageElement>(null);
-  const doubleClickTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>();
-  const zoomMatrixRef = useRef<ZoomMatrix | null>(null);
-
-  const [style, api] = useSpring(() => ({
-    x: 0,
-    y: 0,
-    scale: 1,
-    onRest: {
-      scale({ value }) {
-        if (!onZoomChange) {
-          return;
-        }
-        if (value === MIN_SCALE) {
-          onZoomChange(false);
-        } else {
-          onZoomChange(true);
-        }
-      },
-    },
-  }));
-
-  useGesture(
-    {
-      onDrag({
-        pinching,
-        cancel,
-        active,
-        last,
-        offset: [x, y],
-        velocity: [, vy],
-        direction: [, dy],
-        tap,
-      }) {
-        if (tap) {
-          if (!doubleClickTimeoutRef.current) {
-            doubleClickTimeoutRef.current = setTimeout(() => {
-              onClick?.();
-              doubleClickTimeoutRef.current = null;
-            }, DOUBLE_CLICK_THRESHOLD);
-          } else {
-            clearTimeout(doubleClickTimeoutRef.current);
-            doubleClickTimeoutRef.current = null;
-            onDoubleClick?.();
-          }
-
-          return;
-        }
-
-        if (!zoomedIn) {
-          // Swipe up/down to dismiss parent
-          if (last) {
-            if ((vy > 0.5 && dy !== 0) || Math.abs(y) > 150) {
-              onClose?.();
-            }
-
-            void api.start({ y: 0, config: config.wobbly });
-            return;
-          } else if (dy !== 0) {
-            void api.start({ y, immediate: true });
-            return;
-          }
-
-          cancel();
-          return;
-        }
-
-        if (pinching) {
-          cancel();
-          return;
-        }
-
-        if (active) {
-          setDragging(true);
-        } else {
-          setDragging(false);
-        }
-
-        void api.start({ x, y });
-      },
-
-      onPinch({ origin: [ox, oy], first, movement: [ms], offset: [s], memo }) {
-        if (!imageRef.current) {
-          return;
-        }
-
-        if (first) {
-          const { width, height, x, y } =
-            imageRef.current.getBoundingClientRect();
-          const tx = ox - (x + width / 2);
-          const ty = oy - (y + height / 2);
-
-          memo = [style.x.get(), style.y.get(), tx, ty];
-        }
-
-        const x = memo[0] - (ms - 1) * memo[2]; // eslint-disable-line @typescript-eslint/no-unsafe-member-access
-        const y = memo[1] - (ms - 1) * memo[3]; // eslint-disable-line @typescript-eslint/no-unsafe-member-access
-
-        void api.start({ scale: s, x, y });
-
-        return memo as [number, number, number, number];
-      },
-    },
-    {
-      target: imageRef,
-      drag: {
-        from: () => [style.x.get(), style.y.get()],
-        filterTaps: true,
-        bounds: () => getBounds(zoomMatrixRef.current, style.scale.get()),
-        rubberband: true,
-      },
-      pinch: {
-        scaleBounds: {
-          min: MIN_SCALE,
-          max: MAX_SCALE,
-        },
-        rubberband: true,
-      },
-    },
-  );
-
-  useEffect(() => {
-    if (!loaded || !containerRef.current || !imageRef.current) {
-      return;
-    }
-
-    zoomMatrixRef.current = createZoomMatrix(
-      containerRef.current,
-      imageRef.current,
-      width,
-      height,
-    );
-
-    if (!zoomedIn) {
-      void api.start({ scale: MIN_SCALE, x: 0, y: 0 });
-    } else if (style.scale.get() === MIN_SCALE) {
-      void api.start({ scale: zoomMatrixRef.current.initialScale, x: 0, y: 0 });
-    }
-  }, [api, style.scale, zoomedIn, width, height, loaded]);
-
-  const handleClick = useCallback((e: React.MouseEvent) => {
-    // This handler exists to cancel the onClick handler on the media modal which would
-    // otherwise close the modal. It cannot be used for actual click handling because
-    // we don't know if the user is about to pan the image or not.
-
-    e.preventDefault();
-    e.stopPropagation();
-  }, []);
-
-  const handleLoad = useCallback(() => {
-    setLoaded(true);
-  }, [setLoaded]);
-
-  const handleError = useCallback(() => {
-    setError(true);
-  }, [setError]);
-
-  return (
-    <div
-      className={classNames('zoomable-image', {
-        'zoomable-image--zoomed-in': zoomedIn,
-        'zoomable-image--error': error,
-        'zoomable-image--dragging': dragging,
-      })}
-      ref={containerRef}
-    >
-      {!loaded && blurhash && (
-        <div
-          className='zoomable-image__preview'
-          style={{
-            aspectRatio: `${width}/${height}`,
-            height: `min(${height}px, 100%)`,
-          }}
-        >
-          <Blurhash hash={blurhash} />
-        </div>
-      )}
-
-      <animated.img
-        style={style}
-        role='presentation'
-        ref={imageRef}
-        alt={alt}
-        title={alt}
-        lang={lang}
-        src={src}
-        width={width}
-        height={height}
-        draggable={false}
-        onLoad={handleLoad}
-        onError={handleError}
-        onClickCapture={handleClick}
-      />
-
-      {!loaded && !error && <LoadingIndicator />}
-    </div>
-  );
-};
diff --git a/app/javascript/mastodon/features/ui/containers/modal_container.js b/app/javascript/mastodon/features/ui/containers/modal_container.js
index fe87380431..63c568f847 100644
--- a/app/javascript/mastodon/features/ui/containers/modal_container.js
+++ b/app/javascript/mastodon/features/ui/containers/modal_container.js
@@ -16,7 +16,6 @@ const mapDispatchToProps = dispatch => ({
     if (confirmationMessage) {
       dispatch(
         openModal({
-          previousModalProps: confirmationMessage.props,
           modalType: 'CONFIRM',
           modalProps: {
             message: confirmationMessage.message,
@@ -25,8 +24,7 @@ const mapDispatchToProps = dispatch => ({
               modalType: undefined,
               ignoreFocus: { ignoreFocus },
             })),
-          },
-        }),
+          } }),
       );
     } else {
       dispatch(closeModal({
diff --git a/app/javascript/mastodon/features/ui/containers/notifications_container.js b/app/javascript/mastodon/features/ui/containers/notifications_container.js
new file mode 100644
index 0000000000..b8aa9bc461
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/containers/notifications_container.js
@@ -0,0 +1,20 @@
+import { injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import { NotificationStack } from 'react-notification';
+
+import { dismissAlert } from 'mastodon/actions/alerts';
+import { getAlerts } from 'mastodon/selectors';
+
+const mapStateToProps = (state, { intl }) => ({
+  notifications: getAlerts(state, { intl }),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+  onDismiss (alert) {
+    dispatch(dismissAlert(alert));
+  },
+});
+
+export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NotificationStack));
diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx
index 7d190b3ce7..bf3527d10e 100644
--- a/app/javascript/mastodon/features/ui/index.jsx
+++ b/app/javascript/mastodon/features/ui/index.jsx
@@ -13,9 +13,8 @@ import { HotKeys } from 'react-hotkeys';
 
 import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
 import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
-import { fetchNotifications } from 'mastodon/actions/notification_groups';
+import { initializeNotifications } from 'mastodon/actions/notifications_migration';
 import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
-import { AlertsController } from 'mastodon/components/alerts_controller';
 import { HoverCardController } from 'mastodon/components/hover_card_controller';
 import { PictureInPicture } from 'mastodon/features/picture_in_picture';
 import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
@@ -30,11 +29,11 @@ import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding
 
 import BundleColumnError from './components/bundle_column_error';
 import Header from './components/header';
-import { UploadArea } from './components/upload_area';
-import { HashtagMenuController } from './components/hashtag_menu_controller';
+import UploadArea from './components/upload_area';
 import ColumnsAreaContainer from './containers/columns_area_container';
 import LoadingBarContainer from './containers/loading_bar_container';
 import ModalContainer from './containers/modal_container';
+import NotificationsContainer from './containers/notifications_container';
 import {
   Compose,
   Status,
@@ -54,7 +53,7 @@ import {
   DirectTimeline,
   HashtagTimeline,
   AntennaTimeline,
-  Notifications,
+  NotificationsWrapper,
   NotificationRequests,
   NotificationRequest,
   FollowRequests,
@@ -66,33 +65,22 @@ import {
   FollowedTags,
   LinkTimeline,
   ListTimeline,
-  Lists,
-  ListEdit,
-  ListMembers,
   Blocks,
   DomainBlocks,
   Mutes,
   PinnedStatuses,
+  Lists,
   Antennas,
   Circles,
   CircleStatuses,
   AntennaSetting,
   Directory,
-  OnboardingProfile,
-  OnboardingFollows,
   Explore,
-  Search,
+  ReactionDeck,
+  Onboarding,
   About,
   PrivacyPolicy,
   CommunityTimeline,
-  AntennaEdit,
-  AntennaMembers,
-  CircleEdit,
-  CircleMembers,
-  BookmarkCategoryEdit,
-  ReactionDeck,
-  TermsOfService,
-  AccountFeatured,
 } from './util/async-components';
 import { ColumnsContextProvider } from './util/columns_context';
 import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
@@ -112,7 +100,6 @@ const mapStateToProps = state => ({
   hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
   canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < state.getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']),
   firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
-  newAccount: !state.getIn(['accounts', me, 'note']) && !state.getIn(['accounts', me, 'bot']) && state.getIn(['accounts', me, 'following_count'], 0) === 0 && state.getIn(['accounts', me, 'statuses_count'], 0) === 0,
   username: state.getIn(['accounts', me, 'username']),
 });
 
@@ -148,7 +135,6 @@ const keyMap = {
   toggleHidden: 'x',
   toggleSensitive: 'h',
   openMedia: 'e',
-  onTranslate: 't',
 };
 
 class SwitchingColumnsArea extends PureComponent {
@@ -157,7 +143,6 @@ class SwitchingColumnsArea extends PureComponent {
     children: PropTypes.node,
     location: PropTypes.object,
     singleColumn: PropTypes.bool,
-    forceOnboarding: PropTypes.bool,
   };
 
   UNSAFE_componentWillMount () {
@@ -188,16 +173,14 @@ class SwitchingColumnsArea extends PureComponent {
   };
 
   render () {
-    const { children, singleColumn, forceOnboarding } = this.props;
+    const { children, singleColumn } = this.props;
     const { signedIn } = this.props.identity;
     const pathName = this.props.location.pathname;
 
     let redirect;
 
     if (signedIn) {
-      if (forceOnboarding) {
-        redirect = <Redirect from='/' to='/start' exact />;
-      } else if (singleColumn) {
+      if (singleColumn) {
         redirect = <Redirect from='/' to='/home' exact />;
       } else {
         redirect = <Redirect from='/' to='/deck/getting-started' exact />;
@@ -226,7 +209,6 @@ class SwitchingColumnsArea extends PureComponent {
             <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
             <WrappedRoute path='/about' component={About} content={children} />
             <WrappedRoute path='/privacy-policy' component={PrivacyPolicy} content={children} />
-            <WrappedRoute path='/terms-of-service/:date?' component={TermsOfService} content={children} />
 
             <WrappedRoute path={['/home', '/timelines/home']} component={HomeTimeline} content={children} />
             <Redirect from='/timelines/public' to='/public' exact />
@@ -238,43 +220,28 @@ class SwitchingColumnsArea extends PureComponent {
             <WrappedRoute path={['/conversations', '/timelines/direct']} component={DirectTimeline} content={children} />
             <WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
             <WrappedRoute path='/links/:url' component={LinkTimeline} content={children} />
-            <WrappedRoute path='/lists/new' component={ListEdit} content={children} />
-            <WrappedRoute path='/lists/:id/edit' component={ListEdit} content={children} />
-            <WrappedRoute path='/lists/:id/members' component={ListMembers} content={children} />
             <WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
-            <WrappedRoute path='/antennas/new' component={AntennaEdit} content={children} />
-            <WrappedRoute path='/antennas/:id/edit' component={AntennaEdit} content={children} />
-            <WrappedRoute path='/antennas/:id/members' component={AntennaMembers} content={children} />
-            <WrappedRoute path='/antennas/:id/exclude_members' component={AntennaMembers} componentParams={{ isExclude: true }} content={children} />
-            <WrappedRoute path='/antennas/:id/filtering' component={AntennaSetting} content={children} />
-            <WrappedRoute path='/antennas/:id' component={AntennaTimeline} content={children} />
-            <WrappedRoute path='/circles/new' component={CircleEdit} content={children} />
-            <WrappedRoute path='/circles/:id/edit' component={CircleEdit} content={children} />
-            <WrappedRoute path='/circles/:id/members' component={CircleMembers} content={children} />
-            <WrappedRoute path='/circles/:id' component={CircleStatuses} content={children} />
-            <WrappedRoute path='/bookmark_categories/new' component={BookmarkCategoryEdit} content={children} />
-            <WrappedRoute path='/bookmark_categories/:id/edit' component={BookmarkCategoryEdit} content={children} />
-            <WrappedRoute path='/bookmark_categories/:id' component={BookmarkCategoryStatuses} content={children} />
-            <WrappedRoute path='/notifications' component={Notifications} content={children} exact />
+            <WrappedRoute path='/antennasw/:id' component={AntennaSetting} content={children} />
+            <WrappedRoute path='/antennast/:id' component={AntennaTimeline} content={children} />
+            <WrappedRoute path='/notifications' component={NotificationsWrapper} content={children} exact />
             <WrappedRoute path='/notifications/requests' component={NotificationRequests} content={children} exact />
             <WrappedRoute path='/notifications/requests/:id' component={NotificationRequest} content={children} exact />
             <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
             <WrappedRoute path='/emoji_reactions' component={EmojiReactedStatuses} content={children} />
 
             <WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
+            <WrappedRoute path='/bookmark_categories/:id' component={BookmarkCategoryStatuses} content={children} />
+            <WrappedRoute path='/bookmark_categories' component={BookmarkCategories} content={children} />
             <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
             
             <WrappedRoute path='/reaction_deck' component={ReactionDeck} content={children} />
 
-            <WrappedRoute path={['/start', '/start/profile']} exact component={OnboardingProfile} content={children} />
-            <WrappedRoute path='/start/follows' component={OnboardingFollows} content={children} />
+            <WrappedRoute path='/start' component={Onboarding} content={children} />
             <WrappedRoute path='/directory' component={Directory} content={children} />
-            <WrappedRoute path='/explore' component={Explore} content={children} />
-            <WrappedRoute path='/search' component={Search} content={children} />
+            <WrappedRoute path={['/explore', '/search']} component={Explore} content={children} />
             <WrappedRoute path={['/publish', '/statuses/new']} component={Compose} content={children} />
 
             <WrappedRoute path={['/@:acct', '/accounts/:id']} exact component={AccountTimeline} content={children} />
-            <WrappedRoute path={['/@:acct/featured', '/accounts/:id/featured']} component={AccountFeatured} content={children} />
             <WrappedRoute path='/@:acct/tagged/:tagged?' exact component={AccountTimeline} content={children} />
             <WrappedRoute path={['/@:acct/with_replies', '/accounts/:id/with_replies']} component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
             <WrappedRoute path={['/accounts/:id/followers', '/users/:acct/followers', '/@:acct/followers']} component={Followers} content={children} />
@@ -302,9 +269,9 @@ class SwitchingColumnsArea extends PureComponent {
             <WrappedRoute path='/followed_tags' component={FollowedTags} content={children} />
             <WrappedRoute path='/mutes' component={Mutes} content={children} />
             <WrappedRoute path='/lists' component={Lists} content={children} />
-            <WrappedRoute path='/antennas' component={Antennas} content={children} />
+            <WrappedRoute path='/antennasw' component={Antennas} content={children} />
+            <WrappedRoute path='/circles/:id' component={CircleStatuses} content={children} />
             <WrappedRoute path='/circles' component={Circles} content={children} />
-            <WrappedRoute path='/bookmark_categories' component={BookmarkCategories} content={children} />
 
             <Route component={BundleColumnError} />
           </WrappedSwitch>
@@ -327,7 +294,6 @@ class UI extends PureComponent {
     intl: PropTypes.object.isRequired,
     layout: PropTypes.string.isRequired,
     firstLaunch: PropTypes.bool,
-    newAccount: PropTypes.bool,
     username: PropTypes.string,
     ...WithRouterPropTypes,
   };
@@ -470,7 +436,7 @@ class UI extends PureComponent {
     if (signedIn) {
       this.props.dispatch(fetchMarkers());
       this.props.dispatch(expandHomeTimeline());
-      this.props.dispatch(fetchNotifications());
+      this.props.dispatch(initializeNotifications());
       this.props.dispatch(fetchServerTranslationLanguages());
 
       setTimeout(() => this.props.dispatch(fetchServer()), 3000);
@@ -546,9 +512,7 @@ class UI extends PureComponent {
     }
   };
 
-  handleHotkeyBack = e => {
-    e.preventDefault();
-
+  handleHotkeyBack = () => {
     const { history } = this.props;
 
     if (history.location?.state?.fromMastodon) {
@@ -624,7 +588,7 @@ class UI extends PureComponent {
 
   render () {
     const { draggingOver } = this.state;
-    const { children, isComposing, location, layout, firstLaunch, newAccount } = this.props;
+    const { children, isComposing, location, layout } = this.props;
 
     const handlers = {
       help: this.handleHotkeyToggleHelp,
@@ -654,14 +618,13 @@ class UI extends PureComponent {
         <div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
           <Header />
 
-          <SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'} forceOnboarding={firstLaunch && newAccount}>
+          <SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
             {children}
           </SwitchingColumnsArea>
 
           {layout !== 'mobile' && <PictureInPicture />}
-          <AlertsController />
+          <NotificationsContainer />
           {!disableHoverCards && <HoverCardController />}
-          <HashtagMenuController />
           <LoadingBarContainer className='loading-bar' />
           <ModalContainer />
           <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js
index 21dc902ae5..706b67cf75 100644
--- a/app/javascript/mastodon/features/ui/util/async-components.js
+++ b/app/javascript/mastodon/features/ui/util/async-components.js
@@ -7,7 +7,15 @@ export function Compose () {
 }
 
 export function Notifications () {
-  return import(/* webpackChunkName: "features/notifications" */'../../notifications_v2');
+  return import(/* webpackChunkName: "features/notifications_v1" */'../../notifications');
+}
+
+export function Notifications_v2 () {
+  return import(/* webpackChunkName: "features/notifications_v2" */'../../notifications_v2');
+}
+
+export function NotificationsWrapper () {
+  return import(/* webpackChunkName: "features/notifications" */'../../notifications_wrapper');
 }
 
 export function HomeTimeline () {
@@ -82,10 +90,6 @@ export function AccountGallery () {
   return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery');
 }
 
-export function AccountFeatured() {
-  return import(/* webpackChunkName: "features/account_featured" */'../../account_featured');
-}
-
 export function Followers () {
   return import(/* webpackChunkName: "features/followers" */'../../followers');
 }
@@ -190,6 +194,10 @@ export function EmbedModal () {
   return import(/* webpackChunkName: "modals/embed_modal" */'../components/embed_modal');
 }
 
+export function ListEditor () {
+  return import(/* webpackChunkName: "features/list_editor" */'../../list_editor');
+}
+
 export function ListAdder () {
   return import(/*webpackChunkName: "features/list_adder" */'../../list_adder');
 }
@@ -198,10 +206,22 @@ export function AntennaAdder () {
   return import(/*webpackChunkName: "features/antenna_adder" */'../../antenna_adder');
 }
 
+export function AntennaEditor () {
+  return import(/*webpackChunkName: "features/antenna_editor" */'../../antenna_editor');
+}
+
 export function CircleAdder () {
   return import(/*webpackChunkName: "features/circle_adder" */'../../circle_adder');
 }
 
+export function CircleEditor () {
+  return import(/*webpackChunkName: "features/circle_editor" */'../../circle_editor');
+}
+
+export function AntennaSetting () {
+  return import(/*webpackChunkName: "features/antenna_setting" */'../../antenna_setting');
+}
+
 export function Tesseract () {
   return import(/*webpackChunkName: "tesseract" */'tesseract.js');
 }
@@ -214,12 +234,8 @@ export function Directory () {
   return import(/* webpackChunkName: "features/directory" */'../../directory');
 }
 
-export function OnboardingProfile () {
-  return import(/* webpackChunkName: "features/onboarding" */'../../onboarding/profile');
-}
-
-export function OnboardingFollows () {
-  return import(/* webpackChunkName: "features/onboarding" */'../../onboarding/follows');
+export function Onboarding () {
+  return import(/* webpackChunkName: "features/onboarding" */'../../onboarding');
 }
 
 export function ReactionDeck () {
@@ -234,10 +250,6 @@ export function Explore () {
   return import(/* webpackChunkName: "features/explore" */'../../explore');
 }
 
-export function Search () {
-  return import(/* webpackChunkName: "features/explore" */'../../search');
-}
-
 export function FilterModal () {
   return import(/*webpackChunkName: "modals/filter_modal" */'../components/filter_modal');
 }
@@ -262,10 +274,6 @@ export function PrivacyPolicy () {
   return import(/*webpackChunkName: "features/privacy_policy" */'../../privacy_policy');
 }
 
-export function TermsOfService () {
-  return import(/*webpackChunkName: "features/terms_of_service" */'../../terms_of_service');
-}
-
 export function NotificationRequests () {
   return import(/*webpackChunkName: "features/notifications/requests" */'../../notifications/requests');
 }
@@ -277,39 +285,3 @@ export function NotificationRequest () {
 export function LinkTimeline () {
   return import(/*webpackChunkName: "features/link_timeline" */'../../link_timeline');
 }
-
-export function AnnualReportModal () {
-  return import(/*webpackChunkName: "modals/annual_report_modal" */'../components/annual_report_modal');
-}
-
-export function ListEdit () {
-  return import(/*webpackChunkName: "features/lists" */'../../lists/new');
-}
-
-export function ListMembers () {
-  return import(/* webpackChunkName: "features/lists" */'../../lists/members');
-}
-
-export function AntennaEdit () {
-  return import(/*webpackChunkName: "features/antennas" */'../../antennas/new');
-}
-
-export function AntennaMembers () {
-  return import(/* webpackChunkName: "features/antennas" */'../../antennas/members');
-}
-
-export function AntennaSetting () {
-  return import(/*webpackChunkName: "features/antennas/filtering" */'../../antennas/filtering');
-}
-
-export function CircleEdit () {
-  return import(/*webpackChunkName: "features/circles" */'../../circles/new');
-}
-
-export function CircleMembers () {
-  return import(/* webpackChunkName: "features/circles" */'../../circles/members');
-}
-
-export function BookmarkCategoryEdit () {
-  return import(/*webpackChunkName: "features/bookmark_categories" */'../../bookmark_categories/new');
-}
diff --git a/app/javascript/mastodon/features/ui/util/fullscreen.js b/app/javascript/mastodon/features/ui/util/fullscreen.js
new file mode 100644
index 0000000000..cf5d0cf98d
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/util/fullscreen.js
@@ -0,0 +1,46 @@
+// APIs for normalizing fullscreen operations. Note that Edge uses
+// the WebKit-prefixed APIs currently (as of Edge 16).
+
+export const isFullscreen = () => document.fullscreenElement ||
+  document.webkitFullscreenElement ||
+  document.mozFullScreenElement;
+
+export const exitFullscreen = () => {
+  if (document.exitFullscreen) {
+    document.exitFullscreen();
+  } else if (document.webkitExitFullscreen) {
+    document.webkitExitFullscreen();
+  } else if (document.mozCancelFullScreen) {
+    document.mozCancelFullScreen();
+  }
+};
+
+export const requestFullscreen = el => {
+  if (el.requestFullscreen) {
+    el.requestFullscreen();
+  } else if (el.webkitRequestFullscreen) {
+    el.webkitRequestFullscreen();
+  } else if (el.mozRequestFullScreen) {
+    el.mozRequestFullScreen();
+  }
+};
+
+export const attachFullscreenListener = (listener) => {
+  if ('onfullscreenchange' in document) {
+    document.addEventListener('fullscreenchange', listener);
+  } else if ('onwebkitfullscreenchange' in document) {
+    document.addEventListener('webkitfullscreenchange', listener);
+  } else if ('onmozfullscreenchange' in document) {
+    document.addEventListener('mozfullscreenchange', listener);
+  }
+};
+
+export const detachFullscreenListener = (listener) => {
+  if ('onfullscreenchange' in document) {
+    document.removeEventListener('fullscreenchange', listener);
+  } else if ('onwebkitfullscreenchange' in document) {
+    document.removeEventListener('webkitfullscreenchange', listener);
+  } else if ('onmozfullscreenchange' in document) {
+    document.removeEventListener('mozfullscreenchange', listener);
+  }
+};
diff --git a/app/javascript/mastodon/features/ui/util/fullscreen.ts b/app/javascript/mastodon/features/ui/util/fullscreen.ts
deleted file mode 100644
index 796b9e6515..0000000000
--- a/app/javascript/mastodon/features/ui/util/fullscreen.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-// APIs for normalizing fullscreen operations. Note that Edge uses
-// the WebKit-prefixed APIs currently (as of Edge 16).
-
-interface DocumentWithFullscreen extends Document {
-  mozFullScreenElement?: Element;
-  webkitFullscreenElement?: Element;
-  mozCancelFullScreen?: () => void;
-  webkitExitFullscreen?: () => void;
-}
-
-interface HTMLElementWithFullscreen extends HTMLElement {
-  mozRequestFullScreen?: () => void;
-  webkitRequestFullscreen?: () => void;
-}
-
-export const isFullscreen = () => {
-  const d = document as DocumentWithFullscreen;
-
-  return !!(
-    d.fullscreenElement ??
-    d.webkitFullscreenElement ??
-    d.mozFullScreenElement
-  );
-};
-
-export const exitFullscreen = () => {
-  const d = document as DocumentWithFullscreen;
-
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-  if (d.exitFullscreen) {
-    void d.exitFullscreen();
-  } else if (d.webkitExitFullscreen) {
-    d.webkitExitFullscreen();
-  } else if (d.mozCancelFullScreen) {
-    d.mozCancelFullScreen();
-  }
-};
-
-export const requestFullscreen = (el: HTMLElementWithFullscreen | null) => {
-  if (!el) {
-    return;
-  }
-
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-  if (el.requestFullscreen) {
-    void el.requestFullscreen();
-  } else if (el.webkitRequestFullscreen) {
-    el.webkitRequestFullscreen();
-  } else if (el.mozRequestFullScreen) {
-    el.mozRequestFullScreen();
-  }
-};
-
-export const attachFullscreenListener = (listener: () => void) => {
-  const d = document as DocumentWithFullscreen;
-
-  if ('onfullscreenchange' in d) {
-    d.addEventListener('fullscreenchange', listener);
-  } else if ('onwebkitfullscreenchange' in d) {
-    // @ts-expect-error This is valid on some browsers
-    d.addEventListener('webkitfullscreenchange', listener); // eslint-disable-line @typescript-eslint/no-unsafe-call
-  } else if ('onmozfullscreenchange' in d) {
-    // @ts-expect-error This is valid on some browsers
-    d.addEventListener('mozfullscreenchange', listener); // eslint-disable-line @typescript-eslint/no-unsafe-call
-  }
-};
-
-export const detachFullscreenListener = (listener: () => void) => {
-  const d = document as DocumentWithFullscreen;
-
-  if ('onfullscreenchange' in d) {
-    d.removeEventListener('fullscreenchange', listener);
-  } else if ('onwebkitfullscreenchange' in d) {
-    // @ts-expect-error This is valid on some browsers
-    d.removeEventListener('webkitfullscreenchange', listener); // eslint-disable-line @typescript-eslint/no-unsafe-call
-  } else if ('onmozfullscreenchange' in d) {
-    // @ts-expect-error This is valid on some browsers
-    d.removeEventListener('mozfullscreenchange', listener); // eslint-disable-line @typescript-eslint/no-unsafe-call
-  }
-};
diff --git a/app/javascript/mastodon/features/ui/util/optional_motion.js b/app/javascript/mastodon/features/ui/util/optional_motion.js
new file mode 100644
index 0000000000..0b6d4d97f7
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/util/optional_motion.js
@@ -0,0 +1,7 @@
+import Motion from 'react-motion/lib/Motion';
+
+import { reduceMotion } from '../../../initial_state';
+
+import ReducedMotion from './reduced_motion';
+
+export default reduceMotion ? ReducedMotion : Motion;
diff --git a/app/javascript/mastodon/features/ui/util/reduced_motion.jsx b/app/javascript/mastodon/features/ui/util/reduced_motion.jsx
new file mode 100644
index 0000000000..fd044497f8
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/util/reduced_motion.jsx
@@ -0,0 +1,45 @@
+// Like react-motion's Motion, but reduces all animations to cross-fades
+// for the benefit of users with motion sickness.
+import PropTypes from 'prop-types';
+import { Component } from 'react';
+
+import Motion from 'react-motion/lib/Motion';
+
+const stylesToKeep = ['opacity', 'backgroundOpacity'];
+
+const extractValue = (value) => {
+  // This is either an object with a "val" property or it's a number
+  return (typeof value === 'object' && value && 'val' in value) ? value.val : value;
+};
+
+class ReducedMotion extends Component {
+
+  static propTypes = {
+    defaultStyle: PropTypes.object,
+    style: PropTypes.object,
+    children: PropTypes.func,
+  };
+
+  render() {
+
+    const { style, defaultStyle, children } = this.props;
+
+    Object.keys(style).forEach(key => {
+      if (stylesToKeep.includes(key)) {
+        return;
+      }
+      // If it's setting an x or height or scale or some other value, we need
+      // to preserve the end-state value without actually animating it
+      style[key] = defaultStyle[key] = extractValue(style[key]);
+    });
+
+    return (
+      <Motion style={style} defaultStyle={defaultStyle}>
+        {children}
+      </Motion>
+    );
+  }
+
+}
+
+export default ReducedMotion;
diff --git a/app/javascript/mastodon/features/video/components/hotkey_indicator.tsx b/app/javascript/mastodon/features/video/components/hotkey_indicator.tsx
deleted file mode 100644
index 1abbb96fc0..0000000000
--- a/app/javascript/mastodon/features/video/components/hotkey_indicator.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { useIntl } from 'react-intl';
-import type { MessageDescriptor } from 'react-intl';
-
-import { useTransition, animated } from '@react-spring/web';
-
-import { Icon } from 'mastodon/components/icon';
-import type { IconProp } from 'mastodon/components/icon';
-
-export interface HotkeyEvent {
-  key: number;
-  icon: IconProp;
-  label: MessageDescriptor;
-}
-
-export const HotkeyIndicator: React.FC<{
-  events: HotkeyEvent[];
-  onDismiss: (e: HotkeyEvent) => void;
-}> = ({ events, onDismiss }) => {
-  const intl = useIntl();
-
-  const transitions = useTransition(events, {
-    from: { opacity: 0 },
-    keys: (item) => item.key,
-    enter: [{ opacity: 1 }],
-    leave: [{ opacity: 0 }],
-    onRest: (_result, _ctrl, item) => {
-      onDismiss(item);
-    },
-  });
-
-  return (
-    <>
-      {transitions((style, item) => (
-        <animated.div className='video-player__hotkey-indicator' style={style}>
-          <Icon id='' icon={item.icon} />
-          <span className='video-player__hotkey-indicator__label'>
-            {intl.formatMessage(item.label)}
-          </span>
-        </animated.div>
-      ))}
-    </>
-  );
-};
diff --git a/app/javascript/mastodon/features/video/index.jsx b/app/javascript/mastodon/features/video/index.jsx
new file mode 100644
index 0000000000..89a8ba560a
--- /dev/null
+++ b/app/javascript/mastodon/features/video/index.jsx
@@ -0,0 +1,663 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import classNames from 'classnames';
+
+import { is } from 'immutable';
+
+import { throttle } from 'lodash';
+
+import FullscreenIcon from '@/material-icons/400-24px/fullscreen.svg?react';
+import FullscreenExitIcon from '@/material-icons/400-24px/fullscreen_exit.svg?react';
+import PauseIcon from '@/material-icons/400-24px/pause.svg?react';
+import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react';
+import RectangleIcon from '@/material-icons/400-24px/rectangle.svg?react';
+import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
+import VolumeOffIcon from '@/material-icons/400-24px/volume_off-fill.svg?react';
+import VolumeUpIcon from '@/material-icons/400-24px/volume_up-fill.svg?react';
+import { Blurhash } from 'mastodon/components/blurhash';
+import { Icon }  from 'mastodon/components/icon';
+import { playerSettings } from 'mastodon/settings';
+
+import { displayMedia, useBlurhash } from '../../initial_state';
+import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
+
+const messages = defineMessages({
+  play: { id: 'video.play', defaultMessage: 'Play' },
+  pause: { id: 'video.pause', defaultMessage: 'Pause' },
+  mute: { id: 'video.mute', defaultMessage: 'Mute sound' },
+  unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
+  hide: { id: 'video.hide', defaultMessage: 'Hide video' },
+  expand: { id: 'video.expand', defaultMessage: 'Expand video' },
+  close: { id: 'video.close', defaultMessage: 'Close video' },
+  fullscreen: { id: 'video.fullscreen', defaultMessage: 'Full screen' },
+  exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' },
+});
+
+export const formatTime = secondsNum => {
+  let hours   = Math.floor(secondsNum / 3600);
+  let minutes = Math.floor((secondsNum - (hours * 3600)) / 60);
+  let seconds = secondsNum - (hours * 3600) - (minutes * 60);
+
+  if (hours   < 10) hours   = '0' + hours;
+  if (minutes < 10) minutes = '0' + minutes;
+  if (seconds < 10) seconds = '0' + seconds;
+
+  return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`;
+};
+
+export const findElementPosition = el => {
+  let box;
+
+  if (el.getBoundingClientRect && el.parentNode) {
+    box = el.getBoundingClientRect();
+  }
+
+  if (!box) {
+    return {
+      left: 0,
+      top: 0,
+    };
+  }
+
+  const docEl = document.documentElement;
+  const body  = document.body;
+
+  const clientLeft = docEl.clientLeft || body.clientLeft || 0;
+  const scrollLeft = window.pageXOffset || body.scrollLeft;
+  const left       = (box.left + scrollLeft) - clientLeft;
+
+  const clientTop = docEl.clientTop || body.clientTop || 0;
+  const scrollTop = window.pageYOffset || body.scrollTop;
+  const top       = (box.top + scrollTop) - clientTop;
+
+  return {
+    left: Math.round(left),
+    top: Math.round(top),
+  };
+};
+
+export const getPointerPosition = (el, event) => {
+  const position = {};
+  const box = findElementPosition(el);
+  const boxW = el.offsetWidth;
+  const boxH = el.offsetHeight;
+  const boxY = box.top;
+  const boxX = box.left;
+
+  let pageY = event.pageY;
+  let pageX = event.pageX;
+
+  if (event.changedTouches) {
+    pageX = event.changedTouches[0].pageX;
+    pageY = event.changedTouches[0].pageY;
+  }
+
+  position.y = Math.max(0, Math.min(1, (pageY - boxY) / boxH));
+  position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
+
+  return position;
+};
+
+export const fileNameFromURL = str => {
+  const url      = new URL(str);
+  const pathname = url.pathname;
+  const index    = pathname.lastIndexOf('/');
+
+  return pathname.slice(index + 1);
+};
+
+class Video extends PureComponent {
+
+  static propTypes = {
+    preview: PropTypes.string,
+    frameRate: PropTypes.string,
+    aspectRatio: PropTypes.string,
+    src: PropTypes.string.isRequired,
+    alt: PropTypes.string,
+    lang: PropTypes.string,
+    sensitive: PropTypes.bool,
+    currentTime: PropTypes.number,
+    onOpenVideo: PropTypes.func,
+    onCloseVideo: PropTypes.func,
+    detailed: PropTypes.bool,
+    editable: PropTypes.bool,
+    alwaysVisible: PropTypes.bool,
+    visible: PropTypes.bool,
+    onToggleVisibility: PropTypes.func,
+    deployPictureInPicture: PropTypes.func,
+    intl: PropTypes.object.isRequired,
+    blurhash: PropTypes.string,
+    autoPlay: PropTypes.bool,
+    volume: PropTypes.number,
+    muted: PropTypes.bool,
+    componentIndex: PropTypes.number,
+    autoFocus: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    frameRate: '25',
+  };
+
+  state = {
+    currentTime: 0,
+    duration: 0,
+    volume: 0.5,
+    paused: true,
+    dragging: false,
+    fullscreen: false,
+    hovered: false,
+    muted: false,
+    revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
+  };
+
+  setPlayerRef = c => {
+    this.player = c;
+  };
+
+  setVideoRef = c => {
+    this.video = c;
+
+    if (this.video) {
+      this.setState({ volume: this.video.volume, muted: this.video.muted });
+    }
+  };
+
+  setSeekRef = c => {
+    this.seek = c;
+  };
+
+  setVolumeRef = c => {
+    this.volume = c;
+  };
+
+  handleClickRoot = e => e.stopPropagation();
+
+  handlePlay = () => {
+    this.setState({ paused: false });
+    this._updateTime();
+  };
+
+  handlePause = () => {
+    this.setState({ paused: true });
+  };
+
+  _updateTime () {
+    requestAnimationFrame(() => {
+      if (!this.video) return;
+
+      this.handleTimeUpdate();
+
+      if (!this.state.paused) {
+        this._updateTime();
+      }
+    });
+  }
+
+  handleTimeUpdate = () => {
+    this.setState({
+      currentTime: this.video.currentTime,
+      duration:this.video.duration,
+    });
+  };
+
+  handleVolumeMouseDown = e => {
+    document.addEventListener('mousemove', this.handleMouseVolSlide, true);
+    document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
+    document.addEventListener('touchmove', this.handleMouseVolSlide, true);
+    document.addEventListener('touchend', this.handleVolumeMouseUp, true);
+
+    this.handleMouseVolSlide(e);
+
+    e.preventDefault();
+    e.stopPropagation();
+  };
+
+  handleVolumeMouseUp = () => {
+    document.removeEventListener('mousemove', this.handleMouseVolSlide, true);
+    document.removeEventListener('mouseup', this.handleVolumeMouseUp, true);
+    document.removeEventListener('touchmove', this.handleMouseVolSlide, true);
+    document.removeEventListener('touchend', this.handleVolumeMouseUp, true);
+  };
+
+  handleMouseVolSlide = throttle(e => {
+    const { x } = getPointerPosition(this.volume, e);
+
+    if(!isNaN(x)) {
+      this.setState((state) => ({ volume: x, muted: state.muted && x === 0 }), () => {
+        this._syncVideoToVolumeState(x);
+        this._saveVolumeState(x);
+      });
+    }
+  }, 15);
+
+  handleMouseDown = e => {
+    document.addEventListener('mousemove', this.handleMouseMove, true);
+    document.addEventListener('mouseup', this.handleMouseUp, true);
+    document.addEventListener('touchmove', this.handleMouseMove, true);
+    document.addEventListener('touchend', this.handleMouseUp, true);
+
+    this.setState({ dragging: true });
+    this.video.pause();
+    this.handleMouseMove(e);
+
+    e.preventDefault();
+    e.stopPropagation();
+  };
+
+  handleMouseUp = () => {
+    document.removeEventListener('mousemove', this.handleMouseMove, true);
+    document.removeEventListener('mouseup', this.handleMouseUp, true);
+    document.removeEventListener('touchmove', this.handleMouseMove, true);
+    document.removeEventListener('touchend', this.handleMouseUp, true);
+
+    this.setState({ dragging: false });
+    this.video.play();
+  };
+
+  handleMouseMove = throttle(e => {
+    const { x } = getPointerPosition(this.seek, e);
+    const currentTime = this.video.duration * x;
+
+    if (!isNaN(currentTime)) {
+      this.setState({ currentTime }, () => {
+        this.video.currentTime = currentTime;
+      });
+    }
+  }, 15);
+
+  seekBy (time) {
+    const currentTime = this.video.currentTime + time;
+
+    if (!isNaN(currentTime)) {
+      this.setState({ currentTime }, () => {
+        this.video.currentTime = currentTime;
+      });
+    }
+  }
+
+  handleVideoKeyDown = e => {
+    // On the video element or the seek bar, we can safely use the space bar
+    // for playback control because there are no buttons to press
+
+    if (e.key === ' ') {
+      e.preventDefault();
+      e.stopPropagation();
+      this.togglePlay();
+    }
+  };
+
+  handleKeyDown = e => {
+    const frameTime = 1 / this.getFrameRate();
+
+    switch(e.key) {
+    case 'k':
+      e.preventDefault();
+      e.stopPropagation();
+      this.togglePlay();
+      break;
+    case 'm':
+      e.preventDefault();
+      e.stopPropagation();
+      this.toggleMute();
+      break;
+    case 'f':
+      e.preventDefault();
+      e.stopPropagation();
+      this.toggleFullscreen();
+      break;
+    case 'j':
+      e.preventDefault();
+      e.stopPropagation();
+      this.seekBy(-10);
+      break;
+    case 'l':
+      e.preventDefault();
+      e.stopPropagation();
+      this.seekBy(10);
+      break;
+    case ',':
+      e.preventDefault();
+      e.stopPropagation();
+      this.seekBy(-frameTime);
+      break;
+    case '.':
+      e.preventDefault();
+      e.stopPropagation();
+      this.seekBy(frameTime);
+      break;
+    }
+
+    // If we are in fullscreen mode, we don't want any hotkeys
+    // interacting with the UI that's not visible
+
+    if (this.state.fullscreen) {
+      e.preventDefault();
+      e.stopPropagation();
+
+      if (e.key === 'Escape') {
+        exitFullscreen();
+      }
+    }
+  };
+
+  togglePlay = () => {
+    if (this.state.paused) {
+      this.setState({ paused: false }, () => this.video.play());
+    } else {
+      this.setState({ paused: true }, () => this.video.pause());
+    }
+  };
+
+  toggleFullscreen = () => {
+    if (isFullscreen()) {
+      exitFullscreen();
+    } else {
+      requestFullscreen(this.player);
+    }
+  };
+
+  componentDidMount () {
+    document.addEventListener('fullscreenchange', this.handleFullscreenChange, true);
+    document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
+    document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, true);
+    document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
+
+    window.addEventListener('scroll', this.handleScroll);
+
+    this._syncVideoFromLocalStorage();
+  }
+
+  componentWillUnmount () {
+    window.removeEventListener('scroll', this.handleScroll);
+
+    document.removeEventListener('fullscreenchange', this.handleFullscreenChange, true);
+    document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
+    document.removeEventListener('mozfullscreenchange', this.handleFullscreenChange, true);
+    document.removeEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
+
+    if (!this.state.paused && this.video && this.props.deployPictureInPicture) {
+      this.props.deployPictureInPicture('video', {
+        src: this.props.src,
+        currentTime: this.video.currentTime,
+        muted: this.video.muted,
+        volume: this.video.volume,
+      });
+    }
+  }
+
+  UNSAFE_componentWillReceiveProps (nextProps) {
+    if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
+      this.setState({ revealed: nextProps.visible });
+    }
+  }
+
+  componentDidUpdate (prevProps, prevState) {
+    if (prevState.revealed && !this.state.revealed && this.video) {
+      this.video.pause();
+    }
+  }
+
+  handleScroll = throttle(() => {
+    if (!this.video) {
+      return;
+    }
+
+    const { top, height } = this.video.getBoundingClientRect();
+    const inView = (top <= (window.innerHeight || document.documentElement.clientHeight)) && (top + height >= 0);
+
+    if (!this.state.paused && !inView) {
+      this.video.pause();
+
+      if (this.props.deployPictureInPicture) {
+        this.props.deployPictureInPicture('video', {
+          src: this.props.src,
+          currentTime: this.video.currentTime,
+          muted: this.video.muted,
+          volume: this.video.volume,
+        });
+      }
+
+      this.setState({ paused: true });
+    }
+  }, 150, { trailing: true });
+
+  handleFullscreenChange = () => {
+    this.setState({ fullscreen: isFullscreen() });
+  };
+
+  handleMouseEnter = () => {
+    this.setState({ hovered: true });
+  };
+
+  handleMouseLeave = () => {
+    this.setState({ hovered: false });
+  };
+
+  toggleMute = () => {
+    const muted = !(this.video.muted || this.state.volume === 0);
+
+    this.setState((state) => ({ muted, volume: Math.max(state.volume || 0.5, 0.05) }), () => {
+      this._syncVideoToVolumeState();
+      this._saveVolumeState();
+    });
+  };
+
+  _syncVideoToVolumeState = (volume = null, muted = null) => {
+    if (!this.video) {
+      return;
+    }
+
+    this.video.volume = volume ?? this.state.volume;
+    this.video.muted = muted ?? this.state.muted;
+  };
+
+  _saveVolumeState = (volume = null, muted = null) => {
+    playerSettings.set('volume', volume ?? this.state.volume);
+    playerSettings.set('muted', muted ?? this.state.muted);
+  };
+
+  _syncVideoFromLocalStorage = () => {
+    this.setState({ volume: playerSettings.get('volume') ?? 0.5, muted: playerSettings.get('muted') ?? false }, () => {
+      this._syncVideoToVolumeState();
+    });
+  };
+
+  toggleReveal = () => {
+    if (this.props.onToggleVisibility) {
+      this.props.onToggleVisibility();
+    } else {
+      this.setState({ revealed: !this.state.revealed });
+    }
+  };
+
+  handleLoadedData = () => {
+    const { currentTime, volume, muted, autoPlay } = this.props;
+
+    if (currentTime) {
+      this.video.currentTime = currentTime;
+    }
+
+    if (volume !== undefined) {
+      this.video.volume = volume;
+    }
+
+    if (muted !== undefined) {
+      this.video.muted = muted;
+    }
+
+    if (autoPlay) {
+      this.video.play();
+    }
+  };
+
+  handleProgress = () => {
+    const lastTimeRange = this.video.buffered.length - 1;
+
+    if (lastTimeRange > -1) {
+      this.setState({ buffer: Math.ceil(this.video.buffered.end(lastTimeRange) / this.video.duration * 100) });
+    }
+  };
+
+  handleVolumeChange = () => {
+    this.setState({ volume: this.video.volume, muted: this.video.muted });
+    this._saveVolumeState(this.video.volume, this.video.muted);
+  };
+
+  handleOpenVideo = () => {
+    this.video.pause();
+
+    this.props.onOpenVideo(this.props.lang, {
+      startTime: this.video.currentTime,
+      autoPlay: !this.state.paused,
+      defaultVolume: this.state.volume,
+      componentIndex: this.props.componentIndex,
+    });
+  };
+
+  handleCloseVideo = () => {
+    this.video.pause();
+    this.props.onCloseVideo();
+  };
+
+  getFrameRate () {
+    if (this.props.frameRate && isNaN(this.props.frameRate)) {
+      // The frame rate is returned as a fraction string so we
+      // need to convert it to a number
+
+      return this.props.frameRate.split('/').reduce((p, c) => p / c);
+    }
+
+    return this.props.frameRate;
+  }
+
+  render () {
+    const { preview, src, aspectRatio, onOpenVideo, onCloseVideo, intl, alt, lang, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
+    const { currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, revealed } = this.state;
+    const progress = Math.min((currentTime / duration) * 100, 100);
+    const muted = this.state.muted || volume === 0;
+
+    let preload;
+
+    if (this.props.currentTime || fullscreen || dragging) {
+      preload = 'auto';
+    } else if (detailed) {
+      preload = 'metadata';
+    } else {
+      preload = 'none';
+    }
+
+    let warning;
+
+    if (sensitive) {
+      warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
+    } else {
+      warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
+    }
+
+    // The outer wrapper is necessary to avoid reflowing the layout when going into full screen
+    return (
+      <div style={{ aspectRatio }}>
+        <div
+          role='menuitem'
+          className={classNames('video-player', { inactive: !revealed, detailed, fullscreen, editable })}
+          style={{ aspectRatio }}
+          ref={this.setPlayerRef}
+          onMouseEnter={this.handleMouseEnter}
+          onMouseLeave={this.handleMouseLeave}
+          onClick={this.handleClickRoot}
+          onKeyDown={this.handleKeyDown}
+          tabIndex={0}
+        >
+          <Blurhash
+            hash={blurhash}
+            className={classNames('media-gallery__preview', {
+              'media-gallery__preview--hidden': revealed,
+            })}
+            dummy={!useBlurhash}
+          />
+
+          {(revealed || editable) && <video
+            ref={this.setVideoRef}
+            src={src}
+            poster={preview}
+            preload={preload}
+            role='button'
+            tabIndex={0}
+            aria-label={alt}
+            title={alt}
+            lang={lang}
+            onClick={this.togglePlay}
+            onKeyDown={this.handleVideoKeyDown}
+            onPlay={this.handlePlay}
+            onPause={this.handlePause}
+            onLoadedData={this.handleLoadedData}
+            onProgress={this.handleProgress}
+            onVolumeChange={this.handleVolumeChange}
+            style={{ width: '100%' }}
+          />}
+
+          <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
+            <button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
+              <span className='spoiler-button__overlay__label'>
+                {warning}
+                <span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
+              </span>
+            </button>
+          </div>
+
+          <div className={classNames('video-player__controls', { active: paused || hovered })}>
+            <div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
+              <div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} />
+              <div className='video-player__seek__progress' style={{ width: `${progress}%` }} />
+
+              <span
+                className={classNames('video-player__seek__handle', { active: dragging })}
+                tabIndex={0}
+                style={{ left: `${progress}%` }}
+                onKeyDown={this.handleVideoKeyDown}
+              />
+            </div>
+
+            <div className='video-player__buttons-bar'>
+              <div className='video-player__buttons left'>
+                <button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay} autoFocus={autoFocus}><Icon id={paused ? 'play' : 'pause'} icon={paused ? PlayArrowIcon : PauseIcon} /></button>
+                <button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} icon={muted ? VolumeOffIcon : VolumeUpIcon} /></button>
+
+                <div className={classNames('video-player__volume', { active: this.state.hovered })} onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
+                  <div className='video-player__volume__current' style={{ width: `${muted ? 0 : volume * 100}%` }} />
+
+                  <span
+                    className={classNames('video-player__volume__handle')}
+                    tabIndex={0}
+                    style={{ left: `${muted ? 0 : volume * 100}%` }}
+                  />
+                </div>
+
+                {(detailed || fullscreen) && (
+                  <span className='video-player__time'>
+                    <span className='video-player__time-current'>{formatTime(Math.floor(currentTime))}</span>
+                    <span className='video-player__time-sep'>/</span>
+                    <span className='video-player__time-total'>{formatTime(Math.floor(duration))}</span>
+                  </span>
+                )}
+              </div>
+
+              <div className='video-player__buttons right'>
+                {(!onCloseVideo && !editable && !fullscreen && !this.props.alwaysVisible) && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' icon={VisibilityOffIcon} /></button>}
+                {(!fullscreen && onOpenVideo) && <button type='button' title={intl.formatMessage(messages.expand)} aria-label={intl.formatMessage(messages.expand)} className='player-button' onClick={this.handleOpenVideo}><Icon id='expand' icon={RectangleIcon} /></button>}
+                {onCloseVideo && <button type='button' title={intl.formatMessage(messages.close)} aria-label={intl.formatMessage(messages.close)} className='player-button' onClick={this.handleCloseVideo}><Icon id='compress' icon={FullscreenExitIcon} /></button>}
+                <button type='button' title={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} className='player-button' onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} icon={fullscreen ? FullscreenExitIcon : FullscreenIcon} /></button>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
+
+export default injectIntl(Video);
diff --git a/app/javascript/mastodon/features/video/index.tsx b/app/javascript/mastodon/features/video/index.tsx
deleted file mode 100644
index fca6fd0250..0000000000
--- a/app/javascript/mastodon/features/video/index.tsx
+++ /dev/null
@@ -1,1045 +0,0 @@
-import { useEffect, useCallback, useRef, useState } from 'react';
-
-import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
-
-import classNames from 'classnames';
-
-import { useSpring, animated, config } from '@react-spring/web';
-import { throttle } from 'lodash';
-
-import Forward5Icon from '@/material-icons/400-24px/forward_5-fill.svg?react';
-import FullscreenIcon from '@/material-icons/400-24px/fullscreen.svg?react';
-import FullscreenExitIcon from '@/material-icons/400-24px/fullscreen_exit.svg?react';
-import PauseIcon from '@/material-icons/400-24px/pause-fill.svg?react';
-import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react';
-import RectangleIcon from '@/material-icons/400-24px/rectangle.svg?react';
-import Replay5Icon from '@/material-icons/400-24px/replay_5-fill.svg?react';
-import VolumeDownIcon from '@/material-icons/400-24px/volume_down-fill.svg?react';
-import VolumeOffIcon from '@/material-icons/400-24px/volume_off-fill.svg?react';
-import VolumeUpIcon from '@/material-icons/400-24px/volume_up-fill.svg?react';
-import { Blurhash } from 'mastodon/components/blurhash';
-import { Icon } from 'mastodon/components/icon';
-import { SpoilerButton } from 'mastodon/components/spoiler_button';
-import {
-  isFullscreen,
-  requestFullscreen,
-  exitFullscreen,
-  attachFullscreenListener,
-  detachFullscreenListener,
-} from 'mastodon/features/ui/util/fullscreen';
-import {
-  displayMedia,
-  useBlurhash,
-  reduceMotion,
-} from 'mastodon/initial_state';
-import { playerSettings } from 'mastodon/settings';
-
-import { HotkeyIndicator } from './components/hotkey_indicator';
-import type { HotkeyEvent } from './components/hotkey_indicator';
-
-const messages = defineMessages({
-  play: { id: 'video.play', defaultMessage: 'Play' },
-  pause: { id: 'video.pause', defaultMessage: 'Pause' },
-  mute: { id: 'video.mute', defaultMessage: 'Mute' },
-  unmute: { id: 'video.unmute', defaultMessage: 'Unmute' },
-  hide: { id: 'video.hide', defaultMessage: 'Hide video' },
-  expand: { id: 'video.expand', defaultMessage: 'Expand video' },
-  close: { id: 'video.close', defaultMessage: 'Close video' },
-  fullscreen: { id: 'video.fullscreen', defaultMessage: 'Full screen' },
-  exit_fullscreen: {
-    id: 'video.exit_fullscreen',
-    defaultMessage: 'Exit full screen',
-  },
-  volumeUp: { id: 'video.volume_up', defaultMessage: 'Volume up' },
-  volumeDown: { id: 'video.volume_down', defaultMessage: 'Volume down' },
-  skipForward: { id: 'video.skip_forward', defaultMessage: 'Skip forward' },
-  skipBackward: { id: 'video.skip_backward', defaultMessage: 'Skip backward' },
-});
-
-const DOUBLE_CLICK_THRESHOLD = 250;
-const HOVER_FADE_DELAY = 4000;
-
-export const formatTime = (secondsNum: number) => {
-  const hours = Math.floor(secondsNum / 3600);
-  const minutes = Math.floor((secondsNum - hours * 3600) / 60);
-  const seconds = secondsNum - hours * 3600 - minutes * 60;
-
-  const formattedHours = `${hours < 10 ? '0' : ''}${hours}`;
-  const formattedMinutes = `${minutes < 10 ? '0' : ''}${minutes}`;
-  const formattedSeconds = `${seconds < 10 ? '0' : ''}${seconds}`;
-
-  return (
-    (formattedHours === '00' ? '' : `${formattedHours}:`) +
-    `${formattedMinutes}:${formattedSeconds}`
-  );
-};
-
-export const findElementPosition = (el: HTMLElement) => {
-  const box = el.getBoundingClientRect();
-  const docEl = document.documentElement;
-  const body = document.body;
-
-  const clientLeft = docEl.clientLeft || body.clientLeft || 0;
-  const scrollLeft = window.scrollX || body.scrollLeft;
-  const left = box.left + scrollLeft - clientLeft;
-
-  const clientTop = docEl.clientTop || body.clientTop || 0;
-  const scrollTop = window.scrollY || body.scrollTop;
-  const top = box.top + scrollTop - clientTop;
-
-  return {
-    left: Math.round(left),
-    top: Math.round(top),
-  };
-};
-
-export const getPointerPosition = (
-  el: HTMLElement | null,
-  event: MouseEvent,
-) => {
-  if (!el) {
-    return {
-      y: 0,
-      x: 0,
-    };
-  }
-
-  const box = findElementPosition(el);
-  const boxW = el.offsetWidth;
-  const boxH = el.offsetHeight;
-  const boxY = box.top;
-  const boxX = box.left;
-
-  const { pageY, pageX } = event;
-
-  return {
-    y: Math.max(0, Math.min(1, (pageY - boxY) / boxH)),
-    x: Math.max(0, Math.min(1, (pageX - boxX) / boxW)),
-  };
-};
-
-export const fileNameFromURL = (str: string) => {
-  const url = new URL(str);
-  const pathname = url.pathname;
-  const index = pathname.lastIndexOf('/');
-
-  return pathname.slice(index + 1);
-};
-
-const frameRateAsNumber = (frameRate: string): number => {
-  if (frameRate.includes('/')) {
-    return frameRate
-      .split('/')
-      .map((c) => parseInt(c))
-      .reduce((p, c) => p / c);
-  }
-
-  return parseInt(frameRate);
-};
-
-const persistVolume = (volume: number, muted: boolean) => {
-  playerSettings.set('volume', volume);
-  playerSettings.set('muted', muted);
-};
-
-const restoreVolume = (video: HTMLVideoElement) => {
-  const volume = (playerSettings.get('volume') as number | undefined) ?? 0.5;
-  const muted = (playerSettings.get('muted') as boolean | undefined) ?? false;
-
-  video.volume = volume;
-  video.muted = muted;
-};
-
-let hotkeyEventId = 0;
-
-const registerHotkeyEvent = (
-  setHotkeyEvents: React.Dispatch<React.SetStateAction<HotkeyEvent[]>>,
-  event: Omit<HotkeyEvent, 'key'>,
-) => {
-  setHotkeyEvents(() => [{ key: hotkeyEventId++, ...event }]);
-};
-
-export const Video: React.FC<{
-  preview?: string;
-  frameRate?: string;
-  aspectRatio?: string;
-  src: string;
-  alt?: string;
-  lang?: string;
-  sensitive?: boolean;
-  onOpenVideo?: (options: {
-    startTime: number;
-    autoPlay: boolean;
-    defaultVolume: number;
-  }) => void;
-  onCloseVideo?: () => void;
-  detailed?: boolean;
-  editable?: boolean;
-  alwaysVisible?: boolean;
-  visible?: boolean;
-  onToggleVisibility?: () => void;
-  deployPictureInPicture?: (
-    type: string,
-    mediaProps: {
-      src: string;
-      muted: boolean;
-      volume: number;
-      currentTime: number;
-    },
-  ) => void;
-  blurhash?: string;
-  startPlaying?: boolean;
-  startTime?: number;
-  startVolume?: number;
-  startMuted?: boolean;
-  matchedFilters?: string[];
-}> = ({
-  preview,
-  frameRate = '25',
-  aspectRatio,
-  src,
-  alt = '',
-  lang,
-  sensitive,
-  onOpenVideo,
-  onCloseVideo,
-  detailed,
-  editable,
-  alwaysVisible,
-  visible,
-  onToggleVisibility,
-  deployPictureInPicture,
-  blurhash,
-  startPlaying,
-  startTime,
-  startVolume,
-  startMuted,
-  matchedFilters,
-}) => {
-  const intl = useIntl();
-  const [currentTime, setCurrentTime] = useState(0);
-  const [duration, setDuration] = useState(0);
-  const [volume, setVolume] = useState(0.5);
-  const [paused, setPaused] = useState(true);
-  const [dragging, setDragging] = useState(false);
-  const [fullscreen, setFullscreen] = useState(false);
-  const [hovered, setHovered] = useState(false);
-  const [muted, setMuted] = useState(false);
-  const [revealed, setRevealed] = useState(false);
-  const [hotkeyEvents, setHotkeyEvents] = useState<HotkeyEvent[]>([]);
-
-  const playerRef = useRef<HTMLDivElement>(null);
-  const videoRef = useRef<HTMLVideoElement | null>(null);
-  const seekRef = useRef<HTMLDivElement>(null);
-  const volumeRef = useRef<HTMLDivElement>(null);
-  const doubleClickTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>();
-  const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>();
-
-  const [style, api] = useSpring(() => ({
-    progress: '0%',
-    buffer: '0%',
-    volume: '0%',
-  }));
-
-  const handleVideoRef = useCallback(
-    (c: HTMLVideoElement | null) => {
-      if (videoRef.current && !videoRef.current.paused && c === null) {
-        deployPictureInPicture?.('video', {
-          src: src,
-          currentTime: videoRef.current.currentTime,
-          muted: videoRef.current.muted,
-          volume: videoRef.current.volume,
-        });
-      }
-
-      videoRef.current = c;
-
-      if (videoRef.current) {
-        restoreVolume(videoRef.current);
-        setVolume(videoRef.current.volume);
-        setMuted(videoRef.current.muted);
-        void api.start({
-          volume: `${videoRef.current.volume * 100}%`,
-          immediate: reduceMotion,
-        });
-      }
-    },
-    [api, setVolume, setMuted, src, deployPictureInPicture],
-  );
-
-  const togglePlay = useCallback(() => {
-    if (!videoRef.current) {
-      return;
-    }
-
-    if (videoRef.current.paused) {
-      void videoRef.current.play();
-    } else {
-      videoRef.current.pause();
-    }
-  }, []);
-
-  const toggleFullscreen = useCallback(() => {
-    if (isFullscreen()) {
-      exitFullscreen();
-    } else {
-      requestFullscreen(playerRef.current);
-    }
-  }, []);
-
-  const toggleMute = useCallback(() => {
-    if (!videoRef.current) {
-      return;
-    }
-
-    const effectivelyMuted =
-      videoRef.current.muted || videoRef.current.volume === 0;
-
-    if (effectivelyMuted) {
-      videoRef.current.muted = false;
-
-      if (videoRef.current.volume === 0) {
-        videoRef.current.volume = 0.05;
-      }
-    } else {
-      videoRef.current.muted = true;
-    }
-  }, []);
-
-  const handleClickRoot = useCallback((e: React.MouseEvent) => {
-    // Stop clicks within the video player e.g. closing parent modal
-    e.stopPropagation();
-  }, []);
-
-  const handleClick = useCallback(() => {
-    if (!doubleClickTimeoutRef.current) {
-      doubleClickTimeoutRef.current = setTimeout(() => {
-        registerHotkeyEvent(setHotkeyEvents, {
-          icon: videoRef.current?.paused ? PlayArrowIcon : PauseIcon,
-          label: videoRef.current?.paused ? messages.play : messages.pause,
-        });
-        togglePlay();
-        doubleClickTimeoutRef.current = null;
-      }, DOUBLE_CLICK_THRESHOLD);
-    } else {
-      clearTimeout(doubleClickTimeoutRef.current);
-      doubleClickTimeoutRef.current = null;
-      registerHotkeyEvent(setHotkeyEvents, {
-        icon: isFullscreen() ? FullscreenExitIcon : FullscreenIcon,
-        label: isFullscreen() ? messages.exit_fullscreen : messages.fullscreen,
-      });
-      toggleFullscreen();
-    }
-  }, [setHotkeyEvents, togglePlay, toggleFullscreen]);
-
-  const handlePlay = useCallback(() => {
-    setPaused(false);
-  }, [setPaused]);
-
-  const handlePause = useCallback(() => {
-    setPaused(true);
-  }, [setPaused]);
-
-  useEffect(() => {
-    let nextFrame: ReturnType<typeof requestAnimationFrame>;
-
-    const updateProgress = () => {
-      nextFrame = requestAnimationFrame(() => {
-        if (videoRef.current) {
-          void api.start({
-            progress: `${(videoRef.current.currentTime / videoRef.current.duration) * 100}%`,
-            immediate: reduceMotion,
-            config: config.stiff,
-          });
-        }
-
-        updateProgress();
-      });
-    };
-
-    updateProgress();
-
-    return () => {
-      cancelAnimationFrame(nextFrame);
-    };
-  }, [api]);
-
-  useEffect(() => {
-    if (!videoRef.current) {
-      return;
-    }
-
-    videoRef.current.volume = volume;
-    videoRef.current.muted = muted;
-  }, [volume, muted]);
-
-  useEffect(() => {
-    if (typeof visible !== 'undefined') {
-      setRevealed(visible);
-    } else {
-      setRevealed(
-        displayMedia === 'show_all' ||
-          (displayMedia !== 'hide_all' && !sensitive),
-      );
-    }
-  }, [visible, sensitive]);
-
-  useEffect(() => {
-    if (!revealed && videoRef.current) {
-      videoRef.current.pause();
-    }
-  }, [revealed]);
-
-  useEffect(() => {
-    const handleFullscreenChange = () => {
-      setFullscreen(isFullscreen());
-    };
-
-    const handleScroll = throttle(
-      () => {
-        if (!videoRef.current) {
-          return;
-        }
-
-        const { top, height } = videoRef.current.getBoundingClientRect();
-        const inView =
-          top <=
-            (window.innerHeight || document.documentElement.clientHeight) &&
-          top + height >= 0;
-
-        if (!videoRef.current.paused && !inView) {
-          videoRef.current.pause();
-
-          deployPictureInPicture?.('video', {
-            src: src,
-            currentTime: videoRef.current.currentTime,
-            muted: videoRef.current.muted,
-            volume: videoRef.current.volume,
-          });
-        }
-      },
-      150,
-      { trailing: true },
-    );
-
-    attachFullscreenListener(handleFullscreenChange);
-    window.addEventListener('scroll', handleScroll);
-
-    return () => {
-      window.removeEventListener('scroll', handleScroll);
-      detachFullscreenListener(handleFullscreenChange);
-    };
-  }, [setPaused, setFullscreen, src, deployPictureInPicture]);
-
-  const handleTimeUpdate = useCallback(() => {
-    if (!videoRef.current) {
-      return;
-    }
-
-    setCurrentTime(videoRef.current.currentTime);
-  }, [setCurrentTime]);
-
-  const handleVolumeMouseDown = useCallback(
-    (e: React.MouseEvent) => {
-      const handleVolumeMouseUp = () => {
-        document.removeEventListener('mousemove', handleVolumeMouseMove, true);
-        document.removeEventListener('mouseup', handleVolumeMouseUp, true);
-      };
-
-      const handleVolumeMouseMove = (e: MouseEvent) => {
-        if (!volumeRef.current || !videoRef.current) {
-          return;
-        }
-
-        const { x } = getPointerPosition(volumeRef.current, e);
-
-        if (!isNaN(x)) {
-          videoRef.current.volume = x;
-          videoRef.current.muted = x > 0 ? false : true;
-          void api.start({ volume: `${x * 100}%`, immediate: true });
-        }
-      };
-
-      document.addEventListener('mousemove', handleVolumeMouseMove, true);
-      document.addEventListener('mouseup', handleVolumeMouseUp, true);
-
-      handleVolumeMouseMove(e.nativeEvent);
-
-      e.preventDefault();
-      e.stopPropagation();
-    },
-    [api],
-  );
-
-  const handleSeekMouseDown = useCallback(
-    (e: React.MouseEvent) => {
-      const handleSeekMouseUp = () => {
-        document.removeEventListener('mousemove', handleSeekMouseMove, true);
-        document.removeEventListener('mouseup', handleSeekMouseUp, true);
-
-        setDragging(false);
-        void videoRef.current?.play();
-      };
-
-      const handleSeekMouseMove = (e: MouseEvent) => {
-        if (!seekRef.current || !videoRef.current) {
-          return;
-        }
-
-        const { x } = getPointerPosition(seekRef.current, e);
-        const newTime = videoRef.current.duration * x;
-
-        if (!isNaN(newTime)) {
-          videoRef.current.currentTime = newTime;
-          void api.start({ progress: `${x * 100}%`, immediate: true });
-        }
-      };
-
-      document.addEventListener('mousemove', handleSeekMouseMove, true);
-      document.addEventListener('mouseup', handleSeekMouseUp, true);
-
-      setDragging(true);
-      videoRef.current?.pause();
-      handleSeekMouseMove(e.nativeEvent);
-
-      e.preventDefault();
-      e.stopPropagation();
-    },
-    [setDragging, api],
-  );
-
-  const seekBy = (time: number) => {
-    if (!videoRef.current) {
-      return;
-    }
-
-    const newTime = videoRef.current.currentTime + time;
-
-    if (!isNaN(newTime)) {
-      videoRef.current.currentTime = newTime;
-    }
-  };
-
-  const updateVolumeBy = (step: number) => {
-    if (!videoRef.current) {
-      return;
-    }
-
-    const newVolume = videoRef.current.volume + step;
-
-    if (!isNaN(newVolume)) {
-      videoRef.current.volume = newVolume;
-      videoRef.current.muted = newVolume > 0 ? false : true;
-    }
-  };
-
-  const handleVideoKeyDown = useCallback(
-    (e: React.KeyboardEvent) => {
-      // On the video element or the seek bar, we can safely use the space bar
-      // for playback control because there are no buttons to press
-
-      if (e.key === ' ') {
-        e.preventDefault();
-        e.stopPropagation();
-        registerHotkeyEvent(setHotkeyEvents, {
-          icon: videoRef.current?.paused ? PlayArrowIcon : PauseIcon,
-          label: videoRef.current?.paused ? messages.play : messages.pause,
-        });
-        togglePlay();
-      }
-    },
-    [setHotkeyEvents, togglePlay],
-  );
-
-  const handleKeyDown = useCallback(
-    (e: React.KeyboardEvent) => {
-      const frameTime = 1 / frameRateAsNumber(frameRate);
-
-      switch (e.key) {
-        case 'k':
-        case ' ':
-          e.preventDefault();
-          e.stopPropagation();
-          registerHotkeyEvent(setHotkeyEvents, {
-            icon: videoRef.current?.paused ? PlayArrowIcon : PauseIcon,
-            label: videoRef.current?.paused ? messages.play : messages.pause,
-          });
-          togglePlay();
-          break;
-        case 'm':
-          e.preventDefault();
-          e.stopPropagation();
-          registerHotkeyEvent(setHotkeyEvents, {
-            icon: videoRef.current?.muted ? VolumeUpIcon : VolumeOffIcon,
-            label: videoRef.current?.muted ? messages.unmute : messages.mute,
-          });
-          toggleMute();
-          break;
-        case 'f':
-          e.preventDefault();
-          e.stopPropagation();
-          registerHotkeyEvent(setHotkeyEvents, {
-            icon: isFullscreen() ? FullscreenExitIcon : FullscreenIcon,
-            label: isFullscreen()
-              ? messages.exit_fullscreen
-              : messages.fullscreen,
-          });
-          toggleFullscreen();
-          break;
-        case 'j':
-        case 'ArrowLeft':
-          e.preventDefault();
-          e.stopPropagation();
-          registerHotkeyEvent(setHotkeyEvents, {
-            icon: Replay5Icon,
-            label: messages.skipBackward,
-          });
-          seekBy(-5);
-          break;
-        case 'l':
-        case 'ArrowRight':
-          e.preventDefault();
-          e.stopPropagation();
-          registerHotkeyEvent(setHotkeyEvents, {
-            icon: Forward5Icon,
-            label: messages.skipForward,
-          });
-          seekBy(5);
-          break;
-        case ',':
-          e.preventDefault();
-          e.stopPropagation();
-          seekBy(-frameTime);
-          break;
-        case '.':
-          e.preventDefault();
-          e.stopPropagation();
-          seekBy(frameTime);
-          break;
-        case 'ArrowUp':
-          e.preventDefault();
-          e.stopPropagation();
-          registerHotkeyEvent(setHotkeyEvents, {
-            icon: VolumeUpIcon,
-            label: messages.volumeUp,
-          });
-          updateVolumeBy(0.15);
-          break;
-        case 'ArrowDown':
-          e.preventDefault();
-          e.stopPropagation();
-          registerHotkeyEvent(setHotkeyEvents, {
-            icon: VolumeDownIcon,
-            label: messages.volumeDown,
-          });
-          updateVolumeBy(-0.15);
-          break;
-      }
-
-      // If we are in fullscreen mode, we don't want any hotkeys
-      // interacting with the UI that's not visible
-
-      if (fullscreen) {
-        e.preventDefault();
-        e.stopPropagation();
-
-        if (e.key === 'Escape') {
-          setHotkeyEvents((events) => [
-            ...events,
-            {
-              key: hotkeyEventId++,
-              icon: FullscreenExitIcon,
-              label: messages.exit_fullscreen,
-            },
-          ]);
-          exitFullscreen();
-        }
-      }
-    },
-    [
-      setHotkeyEvents,
-      togglePlay,
-      toggleFullscreen,
-      toggleMute,
-      fullscreen,
-      frameRate,
-    ],
-  );
-
-  const handleMouseEnter = useCallback(() => {
-    setHovered(true);
-
-    if (hoverTimeoutRef.current) {
-      clearTimeout(hoverTimeoutRef.current);
-    }
-
-    hoverTimeoutRef.current = setTimeout(() => {
-      setHovered(false);
-    }, HOVER_FADE_DELAY);
-  }, [setHovered]);
-
-  const handleMouseMove = useCallback(() => {
-    setHovered(true);
-
-    if (hoverTimeoutRef.current) {
-      clearTimeout(hoverTimeoutRef.current);
-    }
-
-    hoverTimeoutRef.current = setTimeout(() => {
-      setHovered(false);
-    }, HOVER_FADE_DELAY);
-  }, [setHovered]);
-
-  const handleMouseLeave = useCallback(() => {
-    setHovered(false);
-  }, [setHovered]);
-
-  const toggleReveal = useCallback(() => {
-    if (onToggleVisibility) {
-      onToggleVisibility();
-    } else {
-      setRevealed((value) => !value);
-    }
-  }, [setRevealed, onToggleVisibility]);
-
-  const handleLoadedData = useCallback(() => {
-    if (!videoRef.current) {
-      return;
-    }
-
-    setDuration(videoRef.current.duration);
-
-    if (typeof startTime !== 'undefined') {
-      videoRef.current.currentTime = startTime;
-    }
-
-    if (typeof startVolume !== 'undefined') {
-      videoRef.current.volume = startVolume;
-    }
-
-    if (typeof startMuted !== 'undefined') {
-      videoRef.current.muted = startMuted;
-    }
-
-    if (startPlaying) {
-      void videoRef.current.play();
-    }
-  }, [setDuration, startTime, startVolume, startMuted, startPlaying]);
-
-  const handleProgress = useCallback(() => {
-    if (!videoRef.current) {
-      return;
-    }
-
-    const lastTimeRange = videoRef.current.buffered.length - 1;
-
-    if (lastTimeRange > -1) {
-      void api.start({
-        buffer: `${Math.ceil(videoRef.current.buffered.end(lastTimeRange) / videoRef.current.duration) * 100}%`,
-        immediate: reduceMotion,
-      });
-    }
-  }, [api]);
-
-  const handleVolumeChange = useCallback(() => {
-    if (!videoRef.current) {
-      return;
-    }
-
-    setVolume(videoRef.current.volume);
-    setMuted(videoRef.current.muted);
-
-    void api.start({
-      volume: `${videoRef.current.muted ? 0 : videoRef.current.volume * 100}%`,
-      immediate: reduceMotion,
-    });
-
-    persistVolume(videoRef.current.volume, videoRef.current.muted);
-  }, [api, setVolume, setMuted]);
-
-  const handleOpenVideo = useCallback(() => {
-    if (!videoRef.current) {
-      return;
-    }
-
-    const wasPaused = videoRef.current.paused;
-
-    videoRef.current.pause();
-
-    onOpenVideo?.({
-      startTime: videoRef.current.currentTime,
-      autoPlay: !wasPaused,
-      defaultVolume: videoRef.current.volume,
-    });
-  }, [onOpenVideo]);
-
-  const handleCloseVideo = useCallback(() => {
-    if (!videoRef.current) {
-      return;
-    }
-
-    videoRef.current.pause();
-
-    onCloseVideo?.();
-  }, [onCloseVideo]);
-
-  const handleHotkeyEventDismiss = useCallback(
-    ({ key }: HotkeyEvent) => {
-      setHotkeyEvents((events) => events.filter((e) => e.key !== key));
-    },
-    [setHotkeyEvents],
-  );
-
-  const progress = Math.min((currentTime / duration) * 100, 100);
-  const effectivelyMuted = muted || volume === 0;
-
-  let preload;
-
-  if (startTime || fullscreen || dragging) {
-    preload = 'auto';
-  } else if (detailed) {
-    preload = 'metadata';
-  } else {
-    preload = 'none';
-  }
-
-  // The outer wrapper is necessary to avoid reflowing the layout when going into full screen
-  return (
-    <div>
-      <div
-        role='menuitem'
-        className={classNames('video-player', {
-          inactive: !revealed,
-          detailed,
-          fullscreen,
-          editable,
-        })}
-        style={{ aspectRatio }}
-        ref={playerRef}
-        onMouseEnter={handleMouseEnter}
-        onMouseMove={handleMouseMove}
-        onMouseLeave={handleMouseLeave}
-        onClick={handleClickRoot}
-        onKeyDown={handleKeyDown}
-        tabIndex={0}
-      >
-        {blurhash && (
-          <Blurhash
-            hash={blurhash}
-            className={classNames('media-gallery__preview', {
-              'media-gallery__preview--hidden': revealed,
-            })}
-            dummy={!useBlurhash}
-          />
-        )}
-
-        {(revealed || editable) && (
-          <video /* eslint-disable-line jsx-a11y/media-has-caption */
-            ref={handleVideoRef}
-            src={src}
-            poster={preview}
-            preload={preload}
-            role='button'
-            tabIndex={0}
-            aria-label={alt}
-            title={alt}
-            lang={lang}
-            onClick={handleClick}
-            onKeyDown={handleVideoKeyDown}
-            onPlay={handlePlay}
-            onPause={handlePause}
-            onLoadedData={handleLoadedData}
-            onProgress={handleProgress}
-            onTimeUpdate={handleTimeUpdate}
-            onVolumeChange={handleVolumeChange}
-            style={{ width: '100%' }}
-          />
-        )}
-
-        <HotkeyIndicator
-          events={hotkeyEvents}
-          onDismiss={handleHotkeyEventDismiss}
-        />
-
-        <SpoilerButton
-          hidden={revealed || editable}
-          sensitive={sensitive ?? false}
-          onClick={toggleReveal}
-          matchedFilters={matchedFilters}
-        />
-
-        {!onCloseVideo &&
-          !editable &&
-          !fullscreen &&
-          !alwaysVisible &&
-          revealed && (
-            <div
-              className={classNames('media-gallery__actions', {
-                active: paused || hovered,
-              })}
-            >
-              <button
-                className='media-gallery__actions__pill'
-                onClick={toggleReveal}
-              >
-                <FormattedMessage
-                  id='media_gallery.hide'
-                  defaultMessage='Hide'
-                />
-              </button>
-            </div>
-          )}
-
-        <div
-          className={classNames('video-player__controls', {
-            active: paused || hovered,
-          })}
-        >
-          <div
-            className='video-player__seek'
-            role='slider'
-            aria-valuemin={0}
-            aria-valuenow={progress}
-            aria-valuemax={100}
-            onMouseDown={handleSeekMouseDown}
-            onKeyDown={handleVideoKeyDown}
-            tabIndex={0}
-            ref={seekRef}
-          >
-            <animated.div
-              className='video-player__seek__buffer'
-              style={{ width: style.buffer }}
-            />
-            <animated.div
-              className='video-player__seek__progress'
-              style={{ width: style.progress }}
-            />
-
-            <animated.span
-              className={classNames('video-player__seek__handle', {
-                active: dragging,
-              })}
-              style={{ left: style.progress }}
-            />
-          </div>
-
-          <div className='video-player__buttons-bar'>
-            <div className='video-player__buttons left'>
-              <button
-                type='button'
-                title={intl.formatMessage(
-                  paused ? messages.play : messages.pause,
-                )}
-                aria-label={intl.formatMessage(
-                  paused ? messages.play : messages.pause,
-                )}
-                className='player-button'
-                onClick={togglePlay}
-              >
-                <Icon
-                  id={paused ? 'play' : 'pause'}
-                  icon={paused ? PlayArrowIcon : PauseIcon}
-                />
-              </button>
-              <button
-                type='button'
-                title={intl.formatMessage(
-                  effectivelyMuted ? messages.unmute : messages.mute,
-                )}
-                aria-label={intl.formatMessage(
-                  muted ? messages.unmute : messages.mute,
-                )}
-                className='player-button'
-                onClick={toggleMute}
-              >
-                <Icon
-                  id={effectivelyMuted ? 'volume-off' : 'volume-up'}
-                  icon={effectivelyMuted ? VolumeOffIcon : VolumeUpIcon}
-                />
-              </button>
-
-              <div
-                className={classNames('video-player__volume', {
-                  active: hovered,
-                })}
-                role='slider'
-                aria-valuemin={0}
-                aria-valuenow={effectivelyMuted ? 0 : volume * 100}
-                aria-valuemax={100}
-                onMouseDown={handleVolumeMouseDown}
-                ref={volumeRef}
-                tabIndex={0}
-              >
-                <animated.div
-                  className='video-player__volume__current'
-                  style={{ width: style.volume }}
-                />
-
-                <animated.span
-                  className={classNames('video-player__volume__handle')}
-                  style={{ left: style.volume }}
-                />
-              </div>
-
-              {(detailed || fullscreen) && (
-                <span className='video-player__time'>
-                  <span className='video-player__time-current'>
-                    {formatTime(Math.floor(currentTime))}
-                  </span>
-                  <span className='video-player__time-sep'>/</span>
-                  <span className='video-player__time-total'>
-                    {formatTime(Math.floor(duration))}
-                  </span>
-                </span>
-              )}
-            </div>
-
-            <div className='video-player__buttons right'>
-              {!fullscreen && onOpenVideo && (
-                <button
-                  type='button'
-                  title={intl.formatMessage(messages.expand)}
-                  aria-label={intl.formatMessage(messages.expand)}
-                  className='player-button'
-                  onClick={handleOpenVideo}
-                >
-                  <Icon id='expand' icon={RectangleIcon} />
-                </button>
-              )}
-              {onCloseVideo && (
-                <button
-                  type='button'
-                  title={intl.formatMessage(messages.close)}
-                  aria-label={intl.formatMessage(messages.close)}
-                  className='player-button'
-                  onClick={handleCloseVideo}
-                >
-                  <Icon id='compress' icon={FullscreenExitIcon} />
-                </button>
-              )}
-              <button
-                type='button'
-                title={intl.formatMessage(
-                  fullscreen ? messages.exit_fullscreen : messages.fullscreen,
-                )}
-                aria-label={intl.formatMessage(
-                  fullscreen ? messages.exit_fullscreen : messages.fullscreen,
-                )}
-                className='player-button'
-                onClick={toggleFullscreen}
-              >
-                <Icon
-                  id={fullscreen ? 'compress' : 'arrows-alt'}
-                  icon={fullscreen ? FullscreenExitIcon : FullscreenIcon}
-                />
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export
-export default Video;
diff --git a/app/javascript/mastodon/hooks/useAccountId.ts b/app/javascript/mastodon/hooks/useAccountId.ts
deleted file mode 100644
index 1cc819ca59..0000000000
--- a/app/javascript/mastodon/hooks/useAccountId.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { useEffect } from 'react';
-
-import { useParams } from 'react-router';
-
-import { fetchAccount, lookupAccount } from 'mastodon/actions/accounts';
-import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
-import { useAppDispatch, useAppSelector } from 'mastodon/store';
-
-interface Params {
-  acct?: string;
-  id?: string;
-}
-
-export function useAccountId() {
-  const { acct, id } = useParams<Params>();
-  const accountId = useAppSelector(
-    (state) =>
-      id ??
-      (state.accounts_map.get(normalizeForLookup(acct)) as string | undefined),
-  );
-
-  const account = useAppSelector((state) =>
-    accountId ? state.accounts.get(accountId) : undefined,
-  );
-  const isAccount = !!account;
-
-  const dispatch = useAppDispatch();
-  useEffect(() => {
-    if (!accountId) {
-      dispatch(lookupAccount(acct));
-    } else if (!isAccount) {
-      dispatch(fetchAccount(accountId));
-    }
-  }, [dispatch, accountId, acct, isAccount]);
-
-  return accountId;
-}
diff --git a/app/javascript/mastodon/hooks/useAccountVisibility.ts b/app/javascript/mastodon/hooks/useAccountVisibility.ts
deleted file mode 100644
index 55651af5a0..0000000000
--- a/app/javascript/mastodon/hooks/useAccountVisibility.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { getAccountHidden } from 'mastodon/selectors/accounts';
-import { useAppSelector } from 'mastodon/store';
-
-export function useAccountVisibility(accountId?: string) {
-  const blockedBy = useAppSelector(
-    (state) => !!state.relationships.getIn([accountId, 'blocked_by'], false),
-  );
-  const suspended = useAppSelector(
-    (state) => !!state.accounts.getIn([accountId, 'suspended'], false),
-  );
-  const hidden = useAppSelector((state) =>
-    accountId ? Boolean(getAccountHidden(state, accountId)) : false,
-  );
-
-  return {
-    blockedBy,
-    suspended,
-    hidden,
-  };
-}
diff --git a/app/javascript/mastodon/hooks/useSelectableClick.ts b/app/javascript/mastodon/hooks/useSelectableClick.ts
deleted file mode 100644
index c8f16f0b0f..0000000000
--- a/app/javascript/mastodon/hooks/useSelectableClick.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { useRef, useCallback } from 'react';
-
-type Position = [number, number];
-
-export const useSelectableClick = (
-  onClick: React.MouseEventHandler,
-  maxDelta = 5,
-) => {
-  const clickPositionRef = useRef<Position | null>(null);
-
-  const handleMouseDown = useCallback((e: React.MouseEvent) => {
-    clickPositionRef.current = [e.clientX, e.clientY];
-  }, []);
-
-  const handleMouseUp = useCallback(
-    (e: React.MouseEvent) => {
-      if (!clickPositionRef.current) {
-        return;
-      }
-
-      const [startX, startY] = clickPositionRef.current;
-      const [deltaX, deltaY] = [
-        Math.abs(e.clientX - startX),
-        Math.abs(e.clientY - startY),
-      ];
-
-      let element: EventTarget | null = e.target;
-
-      while (element && element instanceof HTMLElement) {
-        if (
-          element.localName === 'button' ||
-          element.localName === 'a' ||
-          element.localName === 'label'
-        ) {
-          return;
-        }
-
-        element = element.parentNode;
-      }
-
-      if (
-        deltaX + deltaY < maxDelta &&
-        (e.button === 0 || e.button === 1) &&
-        e.detail >= 1
-      ) {
-        onClick(e);
-      }
-
-      clickPositionRef.current = null;
-    },
-    [maxDelta, onClick],
-  );
-
-  return [handleMouseDown, handleMouseUp] as const;
-};
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index d16d4d64ee..ecb99f38d7 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -13,7 +13,6 @@
  *   | 'recent_emojis'
  *   | 'relationships'
  *   | 'status_reference_unavailable_server'
- *   | 'avatar_on_filter'
  * } HideItemsDefinition
  */
 
@@ -31,7 +30,6 @@
  * @property {boolean} bookmark_category_needed
  * @property {boolean=} boost_modal
  * @property {boolean=} delete_modal
- * @property {boolean=} missing_alt_text_modal
  * @property {boolean=} disable_swiping
  * @property {boolean=} disable_hover_cards
  * @property {string=} disabled_account_id
@@ -71,8 +69,6 @@
  * @property {boolean=} use_pending_items
  * @property {string} version
  * @property {string} sso_redirect
- * @property {string} status_page_url
- * @property {boolean} terms_of_service_enabled
  */
 
 /**
@@ -131,7 +127,6 @@ export const autoPlayGif = getMeta('auto_play_gif');
 export const bookmarkCategoryNeeded = getMeta('bookmark_category_needed');
 export const boostModal = getMeta('boost_modal');
 export const deleteModal = getMeta('delete_modal');
-export const missingAltTextModal = getMeta('missing_alt_text_modal');
 export const disableSwiping = getMeta('disable_swiping');
 export const disableHoverCards = getMeta('disable_hover_cards');
 export const disabledAccountId = getMeta('disabled_account_id');
@@ -168,21 +163,11 @@ export const trendsAsLanding = getMeta('trends_as_landing_page');
 export const useBlurhash = getMeta('use_blurhash');
 export const usePendingItems = getMeta('use_pending_items');
 export const version = getMeta('version');
+export const languages = initialState?.languages;
 export const criticalUpdatesPending = initialState?.critical_updates_pending;
+// @ts-expect-error
 export const statusPageUrl = getMeta('status_page_url');
 export const sso_redirect = getMeta('sso_redirect');
-export const termsOfServiceEnabled = getMeta('terms_of_service_enabled');
-
-const displayNames = Intl.DisplayNames && new Intl.DisplayNames(getMeta('locale'), {
-  type: 'language',
-  fallback: 'none',
-  languageDisplay: 'standard',
-});
-
-export const languages = initialState?.languages?.map(lang => {
-  // zh-YUE is not a valid CLDR unicode_language_id
-  return [lang[0], displayNames?.of(lang[0].replace('zh-YUE', 'yue')) || lang[1], lang[2]];
-});
 
 /**
  * @returns {string | undefined}
diff --git a/app/javascript/mastodon/locales/af.json b/app/javascript/mastodon/locales/af.json
index d930785658..a506b99654 100644
--- a/app/javascript/mastodon/locales/af.json
+++ b/app/javascript/mastodon/locales/af.json
@@ -71,6 +71,7 @@
   "bundle_column_error.return": "Keer terug na die tuisblad",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Sluit",
+  "bundle_modal_error.message": "Die laai van die komponent het iewers skeefgeloop.",
   "bundle_modal_error.retry": "Probeer weer",
   "closed_registrations_modal.find_another_server": "Vind 'n ander bediener",
   "closed_registrations_modal.preamble": "Omdat Mastodon gedesentraliseer is, kan jy op hierdie bediener enigiemand volg en met enigiemand gesels, al is jou rekening op ‘n ander bediener. Jy kan selfs jou eie bediener by die netwerk voeg!",
@@ -131,6 +132,8 @@
   "directory.local": "Slegs van {domain}",
   "disabled_account_banner.account_settings": "Rekeninginstellings",
   "disabled_account_banner.text": "Jou rekening {disabledAccount} is tans gedeaktiveer.",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Bed hierdie plasing op jou webblad in met die kode wat jy hier onder kan kopieer.",
   "embed.preview": "Dit sal so lyk:",
   "emoji_button.activity": "Aktiwiteit",
@@ -151,7 +154,9 @@
   "empty_column.hashtag": "Daar is nog niks vir hierdie hutsetiket nie.",
   "empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
   "empty_column.list": "Hierdie lys is nog leeg. Nuwe plasings deur lyslede sal voortaan hier verskyn.",
+  "empty_column.lists": "Jy het nog geen lyste nie. Wanneer jy een skep, sal dit hier vertoon.",
   "empty_column.notifications": "Jy het nog geen kennisgewings nie. Interaksie van ander mense met jou, sal hier vertoon.",
+  "explore.search_results": "Soekresultate",
   "explore.suggested_follows": "Mense",
   "explore.trending_links": "Nuus",
   "filter_modal.added.settings_link": "instellings bladsy",
@@ -161,6 +166,7 @@
   "footer.about": "Oor",
   "footer.directory": "Profielgids",
   "footer.get_app": "Kry die app",
+  "footer.invite": "Nooi ander",
   "footer.keyboard_shortcuts": "Kortpadsleutels",
   "footer.privacy_policy": "Privaatheidsbeleid",
   "footer.source_code": "Wys bronkode",
@@ -172,6 +178,8 @@
   "hashtag.column_settings.tag_toggle": "Voeg meer etikette by hierdie kolom",
   "hashtag.follow": "Volg hutsetiket",
   "home.column_settings.show_reblogs": "Wys aangestuurde plasings",
+  "interaction_modal.description.reblog": "Met 'n rekening op Mastodon kan jy hierdie plasing aanstuur om dit met jou volgers te deel.",
+  "interaction_modal.description.reply": "Met 'n rekening op Mastodon kan jy op hierdie plasing reageer.",
   "interaction_modal.title.follow": "Volg {name}",
   "interaction_modal.title.reblog": "Stuur {name} se plasing aan",
   "interaction_modal.title.reply": "Reageer op {name} se plasing",
@@ -214,8 +222,15 @@
   "limited_account_hint.action": "Vertoon profiel in elk geval",
   "limited_account_hint.title": "Hierdie profiel is deur moderators van {domain} versteek.",
   "link_preview.author": "Deur {name}",
+  "lists.account.add": "Voeg by lys",
+  "lists.account.remove": "Verwyder vanaf lys",
   "lists.delete": "Verwyder lys",
   "lists.edit": "Redigeer lys",
+  "lists.edit.submit": "Verander titel",
+  "lists.new.create": "Voeg lys by",
+  "lists.new.title_placeholder": "Nuwe lys titel",
+  "lists.search": "Soek tussen mense wat jy volg",
+  "lists.subheading": "Jou lyste",
   "moved_to_account_banner.text": "Jou rekening {disabledAccount} is tans gedeaktiveer omdat jy na {movedToAccount} verhuis het.",
   "navigation_bar.about": "Oor",
   "navigation_bar.bookmarks": "Boekmerke",
@@ -241,10 +256,24 @@
   "notifications.permission_denied_alert": "Lessenaarkennisgewings kan nie geaktiveer word nie omdat 'n webblaaier toegewing voorheen geweier was",
   "notifications_permission_banner.enable": "Aktiveer lessenaarkennissgewings",
   "notifications_permission_banner.how_to_control": "Om kennisgewings te ontvang wanner Mastodon nie oop is nie, aktiveer lessenaarkennisgewings. Jy kan beheer watter spesifieke tipe interaksies lessenaarkennisgewings genereer deur die {icon} knoppie hier bo sodra hulle geaktiveer is.",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "privacy.change": "Verander privaatheid van plasing",
   "privacy.public.short": "Publiek",
   "privacy_policy.last_updated": "Laaste bywerking op {date}",
   "privacy_policy.title": "Privaatheidsbeleid",
+  "regeneration_indicator.sublabel": "Jou tuis-voer word voorberei!",
   "reply_indicator.cancel": "Kanselleer",
   "report.placeholder": "Type or paste additional comments",
   "report.submit": "Submit report",
@@ -254,7 +283,9 @@
   "search.search_or_paste": "Soek of plak URL",
   "search_results.all": "Alles",
   "search_results.hashtags": "Hutsetiket",
+  "search_results.nothing_found": "Hierdie soekwoorde lewer niks op nie",
   "search_results.statuses": "Plasings",
+  "search_results.title": "Soek {q}",
   "server_banner.administered_by": "Administrasie deur:",
   "sign_in_banner.sign_in": "Sign in",
   "status.admin_status": "Open hierdie plasing as moderator",
@@ -277,6 +308,11 @@
   "tabs_bar.home": "Tuis",
   "tabs_bar.notifications": "Kennisgewings",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "Uploading…",
-  "video.fullscreen": "Volskerm"
+  "video.fullscreen": "Volskerm",
+  "video.mute": "Klank afskakel",
+  "video.unmute": "Klank aanskakel"
 }
diff --git a/app/javascript/mastodon/locales/an.json b/app/javascript/mastodon/locales/an.json
index 49b6f41eac..be303985ee 100644
--- a/app/javascript/mastodon/locales/an.json
+++ b/app/javascript/mastodon/locales/an.json
@@ -25,6 +25,7 @@
   "account.endorse": "Amostrar en perfil",
   "account.featured_tags.last_status_at": "Zaguera publicación lo {date}",
   "account.featured_tags.last_status_never": "Sin publicacions",
+  "account.featured_tags.title": "Etiquetas destacadas de {name}",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
   "account.followers.empty": "Encara no sigue dengún a este usuario.",
@@ -80,6 +81,7 @@
   "bundle_column_error.routing.body": "No se podió trobar la pachina solicitada. Yes seguro que la URL en a barra d'adrezas ye correcta?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Zarrar",
+  "bundle_modal_error.message": "Bella cosa salió malament en cargar este component.",
   "bundle_modal_error.retry": "Intenta-lo de nuevo",
   "closed_registrations.other_server_instructions": "Como Mastodon ye descentralizau, puetz creyar una cuenta en unatro servidor y seguir interactuando con este.",
   "closed_registrations_modal.description": "La creyación d'una cuenta en {domain} no ye posible actualment, pero tiene en cuenta que no amenestes una cuenta especificament en {domain} pa usar Mastodon.",
@@ -153,6 +155,8 @@
   "disabled_account_banner.text": "La tuya cuenta {disabledAccount} ye actualment deshabilitada.",
   "dismissable_banner.community_timeline": "Estas son las publicacions publicas mas recients de personas que las suyas cuentas son alochadas en {domain}.",
   "dismissable_banner.dismiss": "Descartar",
+  "dismissable_banner.explore_links": "Estas noticias son estando discutidas per personas en este y atros servidors d'o ret descentralizau en este momento.",
+  "dismissable_banner.explore_tags": "Estas tendencias son ganando popularidat entre la chent en este y atros servidors d'o ret descentralizau en este momento.",
   "embed.instructions": "Anyade esta publicación a lo tuyo puesto web con o siguient codigo.",
   "embed.preview": "Asinas ye como se veyerá:",
   "emoji_button.activity": "Actividat",
@@ -182,6 +186,7 @@
   "empty_column.hashtag": "No i hai cosa en este hashtag encara.",
   "empty_column.home": "La tuya linia temporal ye vueda! Sigue a mas personas pa replenar-la. {suggestions}",
   "empty_column.list": "No i hai cosa en esta lista encara. Quan miembros d'esta lista publiquen nuevos estatus, estes amaneixerán qui.",
+  "empty_column.lists": "No tiens garra lista. Quan en crees una, s'amostrará aquí.",
   "empty_column.mutes": "Encara no has silenciau a garra usuario.",
   "empty_column.notifications": "No tiens garra notificación encara. Interactúa con atros pa empecipiar una conversación.",
   "empty_column.public": "No i hai cosa aquí! Escribe bella cosa publicament, u sigue usuarios d'atras instancias manualment pa emplir-lo",
@@ -191,6 +196,7 @@
   "error.unexpected_crash.next_steps_addons": "Intenta deshabilitar-los y recarga la pachina. Si ixo no aduya, podrías usar Mastodon a traviés d'un navegador web diferent u aplicación nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar lo seguimiento de pila en o portafuellas",
   "errors.unexpected_crash.report_issue": "Informar d'un problema/error",
+  "explore.search_results": "Resultaus de busqueda",
   "explore.title": "Explorar",
   "explore.trending_links": "Noticias",
   "explore.trending_statuses": "Publicacions",
@@ -217,6 +223,7 @@
   "footer.about": "Sobre",
   "footer.directory": "Directorio de perfils",
   "footer.get_app": "Obtener l'aplicación",
+  "footer.invite": "Convidar chent",
   "footer.keyboard_shortcuts": "Alcorces de teclau",
   "footer.privacy_policy": "Politica de privacidat",
   "footer.source_code": "Veyer codigo fuent",
@@ -237,6 +244,9 @@
   "home.column_settings.show_replies": "Amostrar respuestas",
   "home.hide_announcements": "Amagar anuncios",
   "home.show_announcements": "Amostrar anuncios",
+  "interaction_modal.description.follow": "Con una cuenta en Mastodon, puetz seguir {name} pa recibir las suyas publicacions en a tuya linia temporal d'inicio.",
+  "interaction_modal.description.reblog": "Con una cuenta en Mastodon, puetz empentar esta publicación pa compartir-la con os tuyos propios seguidores.",
+  "interaction_modal.description.reply": "Con una cuenta en Mastodon, puetz responder a esta publicación.",
   "interaction_modal.on_another_server": "En un servidor diferent",
   "interaction_modal.on_this_server": "En este servidor",
   "interaction_modal.title.follow": "Seguir a {name}",
@@ -282,11 +292,19 @@
   "lightbox.previous": "Anterior",
   "limited_account_hint.action": "Amostrar perfil de totz modos",
   "limited_account_hint.title": "Este perfil ha estau amagau per los moderadors de {domain}.",
+  "lists.account.add": "Anyadir a lista",
+  "lists.account.remove": "Sacar de lista",
   "lists.delete": "Borrar lista",
   "lists.edit": "Editar lista",
+  "lists.edit.submit": "Cambiar titol",
+  "lists.new.create": "Anyadir lista",
+  "lists.new.title_placeholder": "Titol d'a nueva lista",
   "lists.replies_policy.followed": "Qualsequier usuario seguiu",
   "lists.replies_policy.list": "Miembros d'a lista",
   "lists.replies_policy.none": "Dengún",
+  "lists.replies_policy.title": "Amostrar respuestas a:",
+  "lists.search": "Buscar entre la chent a la quala sigues",
+  "lists.subheading": "Las tuyas listas",
   "load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
   "moved_to_account_banner.text": "La tuya cuenta {disabledAccount} ye actualment deshabilitada perque t'has mudau a {movedToAccount}.",
   "navigation_bar.about": "Sobre",
@@ -350,6 +368,19 @@
   "notifications_permission_banner.enable": "Habilitar notificacions d'escritorio",
   "notifications_permission_banner.how_to_control": "Pa recibir notificacions quan Mastodon no sía ubierto, habilite las notificacions d'escritorio. Puetz controlar con precisión qué tipos d'interaccions cheneran notificacions d'escritorio a traviés d'o botón {icon} d'alto una vegada que sían habilitadas.",
   "notifications_permission_banner.title": "Nunca te pierdas cosa",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "Restaurar",
   "poll.closed": "Zarrada",
   "poll.refresh": "Actualizar",
@@ -365,6 +396,8 @@
   "privacy_policy.last_updated": "Ultima vegada actualizau {date}",
   "privacy_policy.title": "Politica de Privacidat",
   "refresh": "Actualizar",
+  "regeneration_indicator.label": "Cargando…",
+  "regeneration_indicator.sublabel": "La tuya historia d'inicio se ye preparando!",
   "relative_time.days": "{number} d",
   "relative_time.full.days": "fa {number, plural, one {# día} other {# días}}",
   "relative_time.full.hours": "fa {number, plural, one {# hora} other {# horas}}",
@@ -424,7 +457,9 @@
   "search_popout.full_text_search_logged_out_message": "Nomás disponible iniciando la sesión.",
   "search_results.all": "Totz",
   "search_results.hashtags": "Etiquetas",
+  "search_results.nothing_found": "No se podió trobar cosa pa estes termins de busqueda",
   "search_results.statuses": "Publicacions",
+  "search_results.title": "Buscar {q}",
   "server_banner.about_active_users": "Usuarios activos en o servidor entre los zaguers 30 días (Usuarios Activos Mensuals)",
   "server_banner.active_users": "usuarios activos",
   "server_banner.administered_by": "Administrau per:",
@@ -496,7 +531,21 @@
   "upload_button.label": "Puyar imachens, un vido u un fichero d'audio",
   "upload_error.limit": "Limite de puyada de fichers excedido.",
   "upload_error.poll": "Puyada de fichers no permitida con enqüestas.",
+  "upload_form.audio_description": "Describir pa personas con problemas auditivos",
+  "upload_form.description": "Describir pa los usuarios con dificultat visual",
   "upload_form.edit": "Editar",
+  "upload_form.thumbnail": "Cambiar miniatura",
+  "upload_form.video_description": "Describir pa personas con problemas auditivos u visuals",
+  "upload_modal.analyzing_picture": "Analisando imachen…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.applying": "Aplicando…",
+  "upload_modal.choose_image": "Triar imachen",
+  "upload_modal.description_placeholder": "Una rapida rabosa marrón blinca sobre lo can perezoso",
+  "upload_modal.detect_text": "Detectar texto d'a imachen",
+  "upload_modal.edit_media": "Editar multimedia",
+  "upload_modal.hint": "Faiga clic u arrociegue lo cerclo en a vista previa pa triar lo punto focal que siempre será a la vista en totas las miniaturas.",
+  "upload_modal.preparing_ocr": "Preparando OCR…",
+  "upload_modal.preview_label": "Vista previa ({ratio})",
   "upload_progress.label": "Puyando...",
   "upload_progress.processing": "Procesando…",
   "video.close": "Zarrar video",
@@ -505,6 +554,8 @@
   "video.expand": "Expandir video",
   "video.fullscreen": "Pantalla completa",
   "video.hide": "Amagar video",
+  "video.mute": "Silenciar son",
   "video.pause": "Pausar",
-  "video.play": "Reproducir"
+  "video.play": "Reproducir",
+  "video.unmute": "Deixar de silenciar son"
 }
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index 326dd8fbc5..419212a4bf 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -29,6 +29,7 @@
   "account.endorse": "أوصِ به على صفحتك الشخصية",
   "account.featured_tags.last_status_at": "آخر منشور في {date}",
   "account.featured_tags.last_status_never": "لا توجد رسائل",
+  "account.featured_tags.title": "وسوم {name} المميَّزة",
   "account.follow": "متابعة",
   "account.follow_back": "تابعه بالمثل",
   "account.followers": "مُتابِعون",
@@ -84,9 +85,7 @@
   "alert.unexpected.message": "لقد طرأ خطأ غير متوقّع.",
   "alert.unexpected.title": "المعذرة!",
   "alt_text_badge.title": "نص بديل",
-  "alt_text_modal.cancel": "إلغاء",
   "announcement.announcement": "إعلان",
-  "annual_report.summary.archetype.booster": "The cool-hunter",
   "attachments_list.unprocessed": "(غير معالَج)",
   "audio.hide": "إخفاء المقطع الصوتي",
   "block_modal.remote_users_caveat": "سوف نطلب من الخادم {domain} أن يحترم قرارك، لكن الالتزام غير مضمون لأن بعض الخواديم قد تتعامل مع نصوص الكتل بشكل مختلف. قد تظل المنشورات العامة مرئية للمستخدمين غير المسجلين الدخول.",
@@ -110,6 +109,7 @@
   "bundle_column_error.routing.body": "تعذر العثور على الصفحة المطلوبة. هل أنت متأكد من أنّ الرابط التشعبي URL في شريط العناوين صحيح؟",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "إغلاق",
+  "bundle_modal_error.message": "لقد حدث خطأ ما أثناء تحميل هذا العنصر.",
   "bundle_modal_error.retry": "إعادة المُحاولة",
   "closed_registrations.other_server_instructions": "بما أن ماستدون لامركزي، يمكنك إنشاء حساب على خادم آخر للاستمرار في التفاعل مع هذا الخادم.",
   "closed_registrations_modal.description": "لا يمكن إنشاء حساب على {domain} حاليا، ولكن على فكرة لست بحاجة إلى حساب على {domain} بذاته لاستخدام ماستدون.",
@@ -120,16 +120,13 @@
   "column.blocks": "المُستَخدِمون المَحظورون",
   "column.bookmarks": "الفواصل المرجعية",
   "column.community": "الخيط الزمني المحلي",
-  "column.create_list": "إنشاء القائمة",
   "column.direct": "الإشارات الخاصة",
   "column.directory": "تَصَفُّحُ المَلفات الشخصية",
   "column.domain_blocks": "النطاقات المحظورة",
-  "column.edit_list": "تعديل القائمة",
   "column.favourites": "المفضلة",
   "column.firehose": "الموجزات الحية",
   "column.follow_requests": "طلبات المتابعة",
   "column.home": "الرئيسية",
-  "column.list_members": "إدارة أعضاء القائمة",
   "column.lists": "القوائم",
   "column.mutes": "المُستَخدِمون المَكتومون",
   "column.notifications": "الإشعارات",
@@ -142,7 +139,6 @@
   "column_header.pin": "تثبيت",
   "column_header.show_settings": "إظهار الإعدادات",
   "column_header.unpin": "إلغاء التَّثبيت",
-  "column_search.cancel": "إلغاء",
   "column_subheading.settings": "الإعدادات",
   "community.column_settings.local_only": "المحلي فقط",
   "community.column_settings.media_only": "الوسائط فقط",
@@ -161,7 +157,7 @@
   "compose_form.poll.duration": "مُدَّة اِستطلاع الرأي",
   "compose_form.poll.multiple": "متعدد الخيارات",
   "compose_form.poll.option_placeholder": "الخيار {number}",
-  "compose_form.poll.single": "خيار واحد",
+  "compose_form.poll.single": "اختر واحدا",
   "compose_form.poll.switch_to_multiple": "تغيِير الاستطلاع للسماح باِخيارات مُتعدِّدة",
   "compose_form.poll.switch_to_single": "تغيِير الاستطلاع للسماح باِخيار واحد فقط",
   "compose_form.poll.type": "الطراز",
@@ -216,6 +212,10 @@
   "disabled_account_banner.text": "حسابك {disabledAccount} معطل حاليا.",
   "dismissable_banner.community_timeline": "هذه هي أحدث المنشورات العامة من أشخاص تُستضاف حساباتهم على {domain}.",
   "dismissable_banner.dismiss": "رفض",
+  "dismissable_banner.explore_links": "هذه هي القصص الإخبارية الأكثر مشاركة على الشبكة الاجتماعية اليوم. القصص الإخبارية الأحدث التي تنشرها أشخاص مختلفة هي مصنفة في الأعلى.",
+  "dismissable_banner.explore_statuses": "هذه هي المنشورات الرائجة على الشبكات الاجتماعيّة اليوم. تظهر المنشورات المعاد نشرها والحائزة على مفضّلات أكثر في مرتبة عليا.",
+  "dismissable_banner.explore_tags": "هذه هي الوسوم تكتسب جذب الاهتمام حاليًا على الويب الاجتماعي. الوسوم التي يستخدمها مختلف الناس تحتل مرتبة عليا.",
+  "dismissable_banner.public_timeline": "هذه هي أحدث المنشورات العامة من الناس على الشبكة الاجتماعية التي يتبعها الناس على {domain}.",
   "domain_block_modal.block": "حظر الخادم",
   "domain_block_modal.block_account_instead": "أحجب @{name} بدلاً من ذلك",
   "domain_block_modal.they_can_interact_with_old_posts": "يمكن للأشخاص من هذا الخادم التفاعل مع منشوراتك القديمة.",
@@ -270,6 +270,7 @@
   "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
   "empty_column.home": "إنّ الخيط الزمني لصفحتك الرئيسة فارغ. قم بمتابعة المزيد من الناس كي يمتلأ.",
   "empty_column.list": "هذه القائمة فارغة مؤقتا و لكن سوف تمتلئ تدريجيا عندما يبدأ الأعضاء المُنتَمين إليها بنشر منشورات.",
+  "empty_column.lists": "ليس عندك أية قائمة بعد. سوف تظهر قوائمك هنا إن قمت بإنشاء واحدة.",
   "empty_column.mutes": "لم تقم بكتم أي مستخدم بعد.",
   "empty_column.notification_requests": "لا يوجد شيء هنا. عندما تتلقى إشعارات جديدة، سوف تظهر هنا وفقًا لإعداداتك.",
   "empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
@@ -280,6 +281,7 @@
   "error.unexpected_crash.next_steps_addons": "حاول تعطيلهم وإنعاش الصفحة. إن لم ينجح ذلك، يمكنك دائمًا استخدام ماستدون عبر متصفح آخر أو تطبيق أصلي.",
   "errors.unexpected_crash.copy_stacktrace": "انسخ تتبع الارتباطات إلى الحافظة",
   "errors.unexpected_crash.report_issue": "الإبلاغ عن خلل",
+  "explore.search_results": "نتائج البحث",
   "explore.suggested_follows": "أشخاص",
   "explore.title": "استكشف",
   "explore.trending_links": "المُستجدّات",
@@ -327,11 +329,11 @@
   "footer.about": "عن",
   "footer.directory": "دليل الصفحات التعريفية",
   "footer.get_app": "احصل على التطبيق",
+  "footer.invite": "دعوة أشخاص",
   "footer.keyboard_shortcuts": "اختصارات لوحة المفاتيح",
   "footer.privacy_policy": "سياسة الخصوصية",
   "footer.source_code": "الاطلاع على الشفرة المصدرية",
   "footer.status": "الحالة",
-  "footer.terms_of_service": "شروط الخدمة",
   "generic.saved": "تم الحفظ",
   "getting_started.heading": "استعدّ للبدء",
   "hashtag.column_header.tag_mode.all": "و {additional}",
@@ -365,9 +367,17 @@
   "ignore_notifications_modal.ignore": "تجاهل الإشعارات",
   "ignore_notifications_modal.limited_accounts_title": "تجاهل الإشعارات من الحسابات التي هي تحت الإشراف؟",
   "ignore_notifications_modal.new_accounts_title": "تجاهل الإشعارات الصادرة من الحسابات الجديدة؟",
-  "interaction_modal.no_account_yet": "لا تملك حساباً بعد؟",
+  "interaction_modal.description.favourite": "بفضل حساب على ماستدون، يمكنك إضافة هذا المنشور إلى مفضلتك لإبلاغ الناشر عن تقديرك وكذا للاحتفاظ بالمنشور إلى وقت لاحق.",
+  "interaction_modal.description.follow": "بفضل حساب في ماستدون، يمكنك متابعة {name} وتلقي منشوراته في موجزات خيطك الرئيس.",
+  "interaction_modal.description.reblog": "مع حساب في ماستدون، يمكنك تعزيز هذا المنشور ومشاركته مع مُتابِعيك.",
+  "interaction_modal.description.reply": "مع حساب في ماستدون، يمكنك الرد على هذا المنشور.",
+  "interaction_modal.login.action": "خذني إلى خادمي",
+  "interaction_modal.login.prompt": "نطاق الخادم الخاص بك، على سبيل المثال mastodon.social",
+  "interaction_modal.no_account_yet": "ليست على ماستدون بعد؟",
   "interaction_modal.on_another_server": "على خادم مختلف",
   "interaction_modal.on_this_server": "على هذا الخادم",
+  "interaction_modal.sign_in": "لم تقم بتسجيل الدخول إلى هذا الخادم. أين هو مستضاف حسابك؟",
+  "interaction_modal.sign_in_hint": "تلميح: هذا هو الموقع الذي أنشأت فيه حسابك. إن لم تتذكّر/ين اسم الموقع، يمكنك البحث عن الرسالة الترحيبيّة في بريدك الإلكتروني. كما يمكنك أيضاً استخدام اسم المستخدم/ـة الكامل! (مثلاً: @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "إضافة منشور {name} إلى المفضلة",
   "interaction_modal.title.follow": "اتبع {name}",
   "interaction_modal.title.reblog": "إعادة نشر منشور {name}",
@@ -416,21 +426,20 @@
   "limited_account_hint.title": "تم إخفاء هذا الملف الشخصي من قبل مشرفي {domain}.",
   "link_preview.author": "مِن {name}",
   "link_preview.more_from_author": "المزيد من {name}",
-  "lists.add_member": "إضافة",
-  "lists.add_to_list": "إضافة إلى القائمة",
-  "lists.add_to_lists": "إضافة {name} إلى القوائم",
-  "lists.create": "إنشاء",
-  "lists.create_list": "إنشاء قائمة",
+  "lists.account.add": "أضف إلى القائمة",
+  "lists.account.remove": "احذف من القائمة",
   "lists.delete": "احذف القائمة",
-  "lists.done": "تمّ",
   "lists.edit": "عدّل القائمة",
-  "lists.exclusive": "إخفاء الأعضاء في الصفحة الرئيسية",
-  "lists.remove_member": "إزالة",
+  "lists.edit.submit": "تعديل العنوان",
+  "lists.exclusive": "إخفاء هذه المنشورات من الخيط الرئيسي",
+  "lists.new.create": "إضافة قائمة",
+  "lists.new.title_placeholder": "عنوان القائمة الجديدة",
   "lists.replies_policy.followed": "أي مستخدم متابَع",
   "lists.replies_policy.list": "أعضاء القائمة",
   "lists.replies_policy.none": "لا أحد",
-  "lists.save": "حفظ",
-  "lists.search": "بحث",
+  "lists.replies_policy.title": "عرض الردود لـ:",
+  "lists.search": "إبحث في قائمة الحسابات التي تُتابِعها",
+  "lists.subheading": "قوائمك",
   "load_pending": "{count, plural, one {# عنصر جديد} other {# عناصر جديدة}}",
   "loading_indicator.label": "جاري التحميل…",
   "media_gallery.hide": "إخفاء",
@@ -565,17 +574,44 @@
   "notifications_permission_banner.enable": "تفعيل إشعارات سطح المكتب",
   "notifications_permission_banner.how_to_control": "لتلقي الإشعارات عندما لا يكون ماستدون مفتوح، قم بتفعيل إشعارات سطح المكتب، يمكنك التحكم بدقة في أنواع التفاعلات التي تولد إشعارات سطح المكتب من خلال زر الـ{icon} أعلاه بمجرد تفعيلها.",
   "notifications_permission_banner.title": "لا تفوت شيئاً أبداً",
+  "onboarding.action.back": "تراجع",
+  "onboarding.actions.back": "تراجع",
+  "onboarding.actions.go_to_explore": "خذني إلى المتداولة",
+  "onboarding.actions.go_to_home": "خذني إلى وصلات خيطي الرئيس",
+  "onboarding.compose.template": "مرحبا #ماستدون!",
   "onboarding.follows.empty": "نأسف، لا يمكن عرض نتائج في الوقت الحالي. جرب البحث أو انتقل لصفحة الاستكشاف لإيجاد أشخاص للمتابعة، أو حاول مرة أخرى.",
+  "onboarding.follows.lead": "مقتطفات خيطك الرئيس هي الطريقة الأساسية لتجربة ماستدون. كلما زاد عدد الأشخاص الذين تتبعهم، كلما زاد خيط أخبارك نشاطا وإثارة للاهتمام. بداية، إليك بعض الاقتراحات:",
+  "onboarding.follows.title": "أضفِ طابعا شخصيا على موجزات خيطك الرئيس",
   "onboarding.profile.discoverable": "اجعل ملفي الشخصي قابلاً للاكتشاف",
   "onboarding.profile.discoverable_hint": "عندما تختار تفعيل إمكانية الاكتشاف على ماستدون، قد تظهر منشوراتك في نتائج البحث والمواضيع الرائجة، وقد يتم اقتراح ملفك الشخصي لأشخاص ذوي اهتمامات مماثلة معك.",
   "onboarding.profile.display_name": "الاسم العلني",
   "onboarding.profile.display_name_hint": "اسمك الكامل أو اسمك المرح…",
+  "onboarding.profile.lead": "يمكنك دائمًا إكمال ذلك لاحقًا في الإعدادات، حيث يتوفر المزيد من خيارات التخصيص.",
   "onboarding.profile.note": "نبذة عنك",
   "onboarding.profile.note_hint": "يمكنك @ذِكر أشخاص آخرين أو استعمال #الوسوم…",
   "onboarding.profile.save_and_continue": "حفظ و إستمرار",
   "onboarding.profile.title": "إعداد الملف الشخصي",
   "onboarding.profile.upload_avatar": "تحميل صورة الملف الشخصي",
   "onboarding.profile.upload_header": "تحميل رأسية الملف الشخصي",
+  "onboarding.share.lead": "اسمح للأشخاص بمعرفة إمكانية الوصول إليك على ماستدون!",
+  "onboarding.share.message": "أنا {username} في #Mastodon! تعال لمتابعتي على {url}",
+  "onboarding.share.next_steps": "الخطوات المحتملة التالية:",
+  "onboarding.share.title": "شارك ملفك التعريفي",
+  "onboarding.start.lead": "أنت الآن جزء من ماستدون، منصة إعلامية اجتماعية فريدة من نوعها ولا مركزية حيث أنت - وليست الخوارزميات - من يقوم بضبط تجربتك الخاصة. دعنا نبدأ على هذه الحدود الاجتماعية الجديدة:",
+  "onboarding.start.skip": "ألست بحاجة للمساعدة للبداية؟",
+  "onboarding.start.title": "لقد نجحت!",
+  "onboarding.steps.follow_people.body": "إن متابعة الأشخاص المثيرين للاهتمام هي غاية ماستدون.",
+  "onboarding.steps.follow_people.title": "أضفِ طابعا شخصيا على خيطك الرئيس",
+  "onboarding.steps.publish_status.body": "قل مرحبا للعالَم عبر نصّ أو صور أو فيديوهات أو استطلاعات رأي {emoji}",
+  "onboarding.steps.publish_status.title": "قم بإنشاء أول منشور لك",
+  "onboarding.steps.setup_profile.body": "قم بتعزيز تفاعلاتك عبر الحصول على مِلَفّ شخصي شامل.",
+  "onboarding.steps.setup_profile.title": "قم بتخصيص ملفك التعريفي",
+  "onboarding.steps.share_profile.body": "أخبر أصدقائك بكيفية العثور عليك على ماستدون",
+  "onboarding.steps.share_profile.title": "شارك مِلَفّ ماستدون التعريفي الخاص بك",
+  "onboarding.tips.2fa": "<strong>هل تعلم؟</strong> يمكنك تأمين حسابك عن طريق إعداد المصادقة ذات عاملين في إعدادات حسابك. تعمل مع أي تطبيق TOTP من اختيارك، لا حاجة لرقم هاتف!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>هل تعلم؟</strong> لأن ماستدون لامركزية فإن بعض الحسابات التي تصادفها ستكون مستضافة على خوادم غير خادمك. ومع ذلك يمكنك التفاعل معها بسلاسة! خادمهم هو النصف الآخر من اسم المستخدم خاصتهم!",
+  "onboarding.tips.migration": "<strong>هل تعلم؟</strong> إذا شعرت بأن {domain} ليس خياراً ممتازاً لك في المستقبل، فيمكنك الانتقال إلى خادم ماستدون آخر دون خسارة متابعيك. يمكنك حتى استضافة خادمك الخاص!",
+  "onboarding.tips.verification": "<strong>هل تعلم؟</strong> يمكنك تأكيد حسابك عبر وضع رابط إلى ملف ماستدون الشخصي الخاص بك في موقعك الخاص وإضافة رابط موقعك على ملفك الشخصي. لا حاجة لأي رسوم أو مستندات!",
   "password_confirmation.exceeds_maxlength": "تأكيد كلمة المرور يتجاوز الحد الأقصى لطول كلمة المرور",
   "password_confirmation.mismatching": "تأكيد كلمة المرور غير مطابق",
   "picture_in_picture.restore": "ضعها مرة أخرى",
@@ -591,6 +627,7 @@
   "poll_button.remove_poll": "إزالة استطلاع الرأي",
   "privacy.change": "اضبط خصوصية المنشور",
   "privacy.direct.long": "كل من ذُكر في المنشور",
+  "privacy.direct.short": "أشخاص محددون",
   "privacy.private.long": "متابعيك فقط",
   "privacy.private.short": "للمتابِعين",
   "privacy.public.long": "أي شخص على أو خارج ماستدون",
@@ -602,6 +639,8 @@
   "privacy_policy.title": "سياسة الخصوصية",
   "recommended": "موصى به",
   "refresh": "أنعِش",
+  "regeneration_indicator.label": "جارٍ التحميل…",
+  "regeneration_indicator.sublabel": "جارٍ تجهيز موجزات خيطك الرئيس!",
   "relative_time.days": "{number}ي",
   "relative_time.full.days": "منذ {number, plural, zero {} one {# يوم} two {# يومين} few {# أيام} many {# أيام} other {# يوم}}",
   "relative_time.full.hours": "منذ {number, plural, zero {} one {ساعة واحدة} two {ساعتَيْن} few {# ساعات} many {# ساعة} other {# ساعة}}",
@@ -685,8 +724,10 @@
   "search_results.accounts": "الصفحات التعريفية",
   "search_results.all": "الكل",
   "search_results.hashtags": "الوُسوم",
+  "search_results.nothing_found": "تعذر العثور على نتائج تتضمن هذه المصطلحات",
   "search_results.see_all": "رؤية الكل",
   "search_results.statuses": "المنشورات",
+  "search_results.title": "البحث عن {q}",
   "server_banner.about_active_users": "الأشخاص الذين يستخدمون هذا الخادم خلال الأيام الثلاثين الأخيرة (المستخدمون النشطون شهريًا)",
   "server_banner.active_users": "مستخدم نشط",
   "server_banner.administered_by": "يُديره:",
@@ -757,7 +798,6 @@
   "subscribed_languages.target": "تغيير اللغات المشتركة لـ {target}",
   "tabs_bar.home": "الرئيسية",
   "tabs_bar.notifications": "الإشعارات",
-  "terms_of_service.title": "شروط الخدمة",
   "time_remaining.days": "{number, plural, one {# يوم} other {# أيام}} متبقية",
   "time_remaining.hours": "{number, plural, one {# ساعة} other {# ساعات}} متبقية",
   "time_remaining.minutes": "{number, plural, one {# دقيقة} other {# دقائق}} متبقية",
@@ -773,7 +813,21 @@
   "upload_button.label": "إضافة وسائط",
   "upload_error.limit": "لقد تم بلوغ الحد الأقصى المسموح به لإرسال الملفات.",
   "upload_error.poll": "لا يمكن إدراج ملفات في استطلاعات الرأي.",
+  "upload_form.audio_description": "وصف للأشخاص ذي قِصر السمع",
+  "upload_form.description": "وصف للمعاقين بصريا",
   "upload_form.edit": "تعديل",
+  "upload_form.thumbnail": "غيّر الصورة المصغرة",
+  "upload_form.video_description": "وصف للمعاقين بصريا أو لِذي قِصر السمع",
+  "upload_modal.analyzing_picture": "جارٍ فحص الصورة…",
+  "upload_modal.apply": "طبّق",
+  "upload_modal.applying": "جارٍ التطبيق…",
+  "upload_modal.choose_image": "اختر صورة",
+  "upload_modal.description_placeholder": "نصٌّ حكيمٌ لهُ سِرٌّ قاطِعٌ وَذُو شَأنٍ عَظيمٍ مكتوبٌ على ثوبٍ أخضرَ ومُغلفٌ بجلدٍ أزرق",
+  "upload_modal.detect_text": "اكتشف النص مِن الصورة",
+  "upload_modal.edit_media": "تعديل الوسائط",
+  "upload_modal.hint": "اضغط أو اسحب الدائرة على خانة المعاينة لاختيار نقطة التركيز التي ستُعرَض دائمًا على كل المصغرات.",
+  "upload_modal.preparing_ocr": "جار إعداد OCR (تعرف ضوئي على الرموز)…",
+  "upload_modal.preview_label": "معاينة ({ratio})",
   "upload_progress.label": "يرفع...",
   "upload_progress.processing": "تتم المعالجة…",
   "username.taken": "اسم المستخدم هذا مأخوذ. الرجاء محاولة اسم اخر",
@@ -783,6 +837,8 @@
   "video.expand": "توسيع الفيديو",
   "video.fullscreen": "ملء الشاشة",
   "video.hide": "إخفاء الفيديو",
+  "video.mute": "كتم الصوت",
   "video.pause": "إيقاف مؤقت",
-  "video.play": "تشغيل"
+  "video.play": "تشغيل",
+  "video.unmute": "تشغيل الصوت"
 }
diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json
index 5edce9a4d8..01430c9e2d 100644
--- a/app/javascript/mastodon/locales/ast.json
+++ b/app/javascript/mastodon/locales/ast.json
@@ -27,6 +27,7 @@
   "account.enable_notifications": "Avisame cuando @{name} espublice artículos",
   "account.endorse": "Destacar nel perfil",
   "account.featured_tags.last_status_never": "Nun hai nenguna publicación",
+  "account.featured_tags.title": "Etiquetes destacaes de: {name}",
   "account.follow": "Siguir",
   "account.follow_back": "Siguir tamién",
   "account.followers": "Siguidores",
@@ -68,15 +69,7 @@
   "alert.unexpected.message": "Prodúxose un error inesperáu.",
   "alert.unexpected.title": "¡Meca!",
   "alt_text_badge.title": "Testu alternativu",
-  "alt_text_modal.add_alt_text": "Amestar testu alternativu",
-  "alt_text_modal.cancel": "Encaboxar",
-  "alt_text_modal.done": "Fecho",
   "announcement.announcement": "Anunciu",
-  "annual_report.summary.followers.followers": "siguidores",
-  "annual_report.summary.here_it_is": "Equí ta'l to resume de {year}:",
-  "annual_report.summary.highlighted_post.possessive": "de {name}",
-  "annual_report.summary.new_posts.new_posts": "artículos nuevos",
-  "annual_report.summary.thanks": "Gracies por ser parte de Mastodon!",
   "attachments_list.unprocessed": "(ensin procesar)",
   "block_modal.show_less": "Amosar menos",
   "block_modal.show_more": "Amosar más",
@@ -92,6 +85,7 @@
   "bundle_column_error.routing.body": "Nun se pudo atopar la páxina solicitada. ¿De xuru que la URL de la barra de direiciones ta bien escrita?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Zarrar",
+  "bundle_modal_error.message": "Asocedió daqué malo mentanto se cargaba esti componente.",
   "bundle_modal_error.retry": "Retentar",
   "closed_registrations.other_server_instructions": "Darréu que Mastodon ye una rede social descentralizada, pues crear una cuenta n'otru sirvidor y siguir interactuando con esti.",
   "closed_registrations_modal.description": "Anguaño nun ye posible crear cuentes en {domain}, mas ten en cuenta que nun precises una cuenta nesti sirvidor pa usar Mastodon.",
@@ -102,10 +96,8 @@
   "column.blocks": "Perfiles bloquiaos",
   "column.bookmarks": "Marcadores",
   "column.community": "Llinia de tiempu llocal",
-  "column.create_list": "Crear llista",
   "column.direct": "Menciones privaes",
   "column.domain_blocks": "Dominios bloquiaos",
-  "column.edit_list": "Editar la llista",
   "column.favourites": "Favoritos",
   "column.firehose": "Feed en direuto",
   "column.follow_requests": "Solicitúes de siguimientu",
@@ -121,7 +113,6 @@
   "column_header.pin": "Fixar",
   "column_header.show_settings": "Amosar la configuración",
   "column_header.unpin": "Lliberar",
-  "column_search.cancel": "Encaboxar",
   "column_subheading.settings": "Configuración",
   "community.column_settings.media_only": "Namás el conteníu multimedia",
   "community.column_settings.remote_only": "Namás lo remoto",
@@ -151,12 +142,9 @@
   "confirmations.discard_edit_media.confirm": "Escartar",
   "confirmations.edit.confirm": "Editar",
   "confirmations.edit.message": "La edición va sobrescribir el mensaxe que tas escribiendo. ¿De xuru que quies siguir?",
-  "confirmations.follow_to_list.title": "¿Siguir al usuariu?",
   "confirmations.logout.confirm": "Zarrar la sesión",
   "confirmations.logout.message": "¿De xuru que quies zarrar la sesión?",
   "confirmations.logout.title": "¿Quies zarrar la sesión?",
-  "confirmations.missing_alt_text.confirm": "Amestar testu alternativu",
-  "confirmations.missing_alt_text.title": "¿Quies amestar testu alternativu?",
   "confirmations.redraft.confirm": "Desaniciar y reeditar",
   "confirmations.redraft.title": "¿Desaniciar y reeditar la publicación?",
   "confirmations.reply.confirm": "Responder",
@@ -181,6 +169,8 @@
   "disabled_account_banner.account_settings": "Axustes de la cuenta",
   "dismissable_banner.community_timeline": "Esta seición contién los artículos públicos más actuales de los perfiles agospiaos nel dominiu {domain}.",
   "dismissable_banner.dismiss": "Escartar",
+  "dismissable_banner.explore_tags": "Esta seición contién les etiquetes del fediversu que tán ganando popularidá güei. Les etiquetes más usaes polos perfiles apaecen no cimero.",
+  "dismissable_banner.public_timeline": "Esta seición contién los artículos más nuevos de les persones na web social que les persones de {domain} siguen.",
   "domain_block_modal.block": "Bloquiar el sirvidor",
   "domain_block_modal.they_cant_follow": "Naide d'esti sirvidor pue siguite.",
   "domain_block_modal.title": "Bloquiar el dominiu?",
@@ -211,6 +201,7 @@
   "empty_column.hashtag": "Entá nun hai nada con esta etiqueta.",
   "empty_column.home": "¡La to llinia de tiempu ta balera! Sigui a cuentes pa enllenala.",
   "empty_column.list": "Nun hai nada nesta llista. Cuando los perfiles d'esta llista espublicen artículos nuevos, apaecen equí.",
+  "empty_column.lists": "Nun tienes nenguna llista. Cuando crees dalguna, apaez equí.",
   "empty_column.mutes": "Nun tienes nengún perfil colos avisos desactivaos.",
   "empty_column.notifications": "Nun tienes nengún avisu. Cuando otros perfiles interactúen contigo, apaez equí.",
   "empty_column.public": "¡Equí nun hai nada! Escribi daqué públicamente o sigui a perfiles d'otros sirvidores pa enllenar esta seición",
@@ -218,6 +209,7 @@
   "error.unexpected_crash.explanation_addons": "Esta páxina nun se pudo amosar correutamente. Ye probable que dalgún complementu del restolador o dalguna ferramienta de traducción automática produxere esti error.",
   "error.unexpected_crash.next_steps": "Prueba a anovar la páxina. Si nun sirve, ye posible que tovía seyas a usar Mastodon pente otru restolador o una aplicación nativa.",
   "error.unexpected_crash.next_steps_addons": "Prueba a desactivalos y a anovar la páxina. Si nun sirve, ye posible que tovía seyas a usar Mastodon pente otru restolador o una aplicación nativa.",
+  "explore.search_results": "Resultaos de la busca",
   "explore.suggested_follows": "Perfiles",
   "explore.title": "Esploración",
   "explore.trending_links": "Noticies",
@@ -256,11 +248,11 @@
   "footer.about": "Tocante a",
   "footer.directory": "Direutoriu de perfiles",
   "footer.get_app": "Consiguir l'aplicación",
+  "footer.invite": "Convidar a persones",
   "footer.keyboard_shortcuts": "Atayos del tecláu",
   "footer.privacy_policy": "Política de privacidá",
   "footer.source_code": "Ver el códigu fonte",
   "footer.status": "Estáu",
-  "footer.terms_of_service": "Términos del serviciu",
   "generic.saved": "Guardóse",
   "getting_started.heading": "Comienzu",
   "hashtag.column_header.tag_mode.all": "y {additional}",
@@ -279,14 +271,13 @@
   "home.column_settings.show_replies": "Amosar les rempuestes",
   "home.pending_critical_update.body": "¡Anueva'l sirvidor de Mastodon namás que puedas!",
   "home.show_announcements": "Amosar anuncios",
-  "info_button.label": "Ayuda",
-  "interaction_modal.go": "Dir",
-  "interaction_modal.no_account_yet": "¿Tovía nun tienes una cuenta?",
+  "interaction_modal.description.follow": "Con una cuenta de Mastodon, pues siguir a {name} pa recibir los artículos de so nel to feed d'aniciu.",
+  "interaction_modal.description.reblog": "Con una cuenta de Mastodon, pues compartir esta publicación colos perfiles que te sigan.",
+  "interaction_modal.description.reply": "Con una cuenta de Mastodon, pues responder a esta publicación.",
   "interaction_modal.on_another_server": "N'otru sirvidor",
   "interaction_modal.on_this_server": "Nesti sirvidor",
   "interaction_modal.title.follow": "Siguir a {name}",
   "interaction_modal.title.reply": "Rempuesta a la publicación de: {name}",
-  "interaction_modal.title.vote": "Vota na encuesta de {name}",
   "intervals.full.days": "{number, plural, one {# día} other {# díes}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# hores}}",
   "intervals.full.minutes": "{number, plural, one {# minutu} other {# minutos}}",
@@ -325,21 +316,19 @@
   "limited_account_hint.action": "Amosar el perfil de toes toes",
   "link_preview.author": "Por {name}",
   "link_preview.more_from_author": "Más de {name}",
-  "lists.add_member": "Amestar",
-  "lists.add_to_list": "Amestar a la llista",
-  "lists.add_to_lists": "Amestar {name} a la llista",
-  "lists.create": "Crear",
-  "lists.create_list": "Crear llista",
+  "lists.account.add": "Amestar a la llista",
+  "lists.account.remove": "Desaniciar de la llista",
   "lists.delete": "Desaniciar la llista",
-  "lists.done": "Fecho",
   "lists.edit": "Editar la llista",
-  "lists.list_name": "Nome de la llista",
-  "lists.no_lists_yet": "Ensin llistes tovía.",
+  "lists.edit.submit": "Camudar el títulu",
+  "lists.new.create": "Amestar la llista",
+  "lists.new.title_placeholder": "Títulu",
   "lists.replies_policy.followed": "Cualesquier perfil siguíu",
   "lists.replies_policy.list": "Perfiles de la llista",
   "lists.replies_policy.none": "Naide",
-  "lists.save": "Guardar",
-  "lists.search": "Buscar",
+  "lists.replies_policy.title": "Amosar les rempuestes a:",
+  "lists.search": "Buscar ente los perfiles que sigues",
+  "lists.subheading": "Les tos llistes",
   "load_pending": "{count, plural, one {# elementu nuevu} other {# elementos nuevos}}",
   "loading_indicator.label": "Cargando…",
   "navigation_bar.about": "Tocante a",
@@ -408,9 +397,12 @@
   "notifications.permission_required": "Los avisos d'escritoriu nun tán disponibles porque nun se concedió'l permisu riquíu.",
   "notifications.policy.accept": "Aceptar",
   "notifications.policy.accept_hint": "Amosar n'avisos",
-  "onboarding.follows.done": "Fecho",
   "onboarding.profile.note": "Biografía",
   "onboarding.profile.note_hint": "Pues @mentar a otros perfiles o poner #etiquetes…",
+  "onboarding.start.lead": "Yá yes parte de Mastodon, una plataforma social multimedia descentralizada onde tu y non un algoritmu, personalices la to esperiencia. Vamos presentate esti llugar social nuevu:",
+  "onboarding.start.skip": "¿Nun precises ayuda pa comenzar?",
+  "onboarding.steps.follow_people.body": "Mastodon trata namás de siguir a cuentes interesantes.",
+  "onboarding.steps.publish_status.body": "Saluda al mundu con semeyes, vídeos, testu o encuestes {emoji}",
   "password_confirmation.exceeds_maxlength": "La contraseña de confirmación supera la llongura de caráuteres máxima",
   "password_confirmation.mismatching": "La contraseña de confirmación nun concasa",
   "poll.closed": "Finó",
@@ -422,12 +414,13 @@
   "poll_button.add_poll": "Amestar una encuesta",
   "poll_button.remove_poll": "Quitar la encuesta",
   "privacy.change": "Configurar la privacidá de la publicación",
-  "privacy.direct.short": "Mención privada",
+  "privacy.direct.short": "Perfiles específicos",
   "privacy.private.short": "Siguidores",
   "privacy.public.short": "Publicación pública",
   "privacy_policy.last_updated": "Data del últimu anovamientu: {date}",
   "privacy_policy.title": "Política de privacidá",
   "refresh": "Anovar",
+  "regeneration_indicator.label": "Cargando…",
   "relative_time.days": "{number} d",
   "relative_time.full.days": "hai {number, plural, one {# día} other {# díes}}",
   "relative_time.full.hours": "hai {number, plural, one {# hora} other {# hores}}",
@@ -500,8 +493,10 @@
   "search_results.accounts": "Perfiles",
   "search_results.all": "Too",
   "search_results.hashtags": "Etiquetes",
+  "search_results.nothing_found": "Nun se pudo atopar nada con esos términos de busca",
   "search_results.see_all": "Ver too",
   "search_results.statuses": "Artículos",
+  "search_results.title": "Busca de: {q}",
   "server_banner.is_one_of_many": "{domain} ye unu de los munchos sirvidores independientes de Mastodon que pues usar pa participar nel fediversu.",
   "server_banner.server_stats": "Estadístiques del sirvidor:",
   "sign_in_banner.create_account": "Crear una cuenta",
@@ -553,7 +548,6 @@
   "subscribed_languages.save": "Guardar los cambeos",
   "tabs_bar.home": "Aniciu",
   "tabs_bar.notifications": "Avisos",
-  "terms_of_service.title": "Términos del serviciu",
   "time_remaining.days": "{number, plural, one {Queda # día} other {Queden # díes}}",
   "time_remaining.hours": "{number, plural, one {Queda # hora} other {Queden # hores}}",
   "time_remaining.minutes": "{number, plural, one {Queda # minutu} other {Queden # minutos}}",
@@ -567,7 +561,14 @@
   "upload_area.title": "Arrastra y suelta pa xubir",
   "upload_button.label": "Amestar ficheros multimedia",
   "upload_error.poll": "La xuba de ficheros nun ta permitida coles encuestes.",
+  "upload_form.audio_description": "Describi'l conteníu pa persones sordes y/o ciegues",
   "upload_form.edit": "Editar",
+  "upload_modal.analyzing_picture": "Analizando la semeya…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.applying": "Aplicando…",
+  "upload_modal.detect_text": "Detectar el testu de la semeya",
+  "upload_modal.edit_media": "Edición",
+  "upload_modal.hint": "Calca o arrastra'l círculu de la previsualización pa escoyer el puntu d'enfoque que siempre va tar a la vista en toles miniatures.",
   "upload_progress.label": "Xubiendo…",
   "upload_progress.processing": "Procesando…",
   "video.close": "Zarrar el videu",
@@ -576,6 +577,8 @@
   "video.expand": "Espander el videu",
   "video.fullscreen": "Pantalla completa",
   "video.hide": "Esconder el videu",
+  "video.mute": "Desactivar el soníu",
   "video.pause": "Posar",
-  "video.play": "Reproducir"
+  "video.play": "Reproducir",
+  "video.unmute": "Activar el soníu"
 }
diff --git a/app/javascript/mastodon/locales/az.json b/app/javascript/mastodon/locales/az.json
index 550312f31d..3b2d8fa68d 100644
--- a/app/javascript/mastodon/locales/az.json
+++ b/app/javascript/mastodon/locales/az.json
@@ -29,6 +29,7 @@
   "account.endorse": "Profildə seçilmişlərə əlavə et",
   "account.featured_tags.last_status_at": "Son paylaşım {date} tarixində olub",
   "account.featured_tags.last_status_never": "Paylaşım yoxdur",
+  "account.featured_tags.title": "{name} istifadəçisinin seçilmiş heşteqləri",
   "account.follow": "İzlə",
   "account.follow_back": "Sən də izlə",
   "account.followers": "İzləyicilər",
@@ -85,33 +86,7 @@
   "alert.unexpected.message": "Bilinməyən bir xəta baş verdi.",
   "alert.unexpected.title": "Ah!",
   "alt_text_badge.title": "Alternativ mətn",
-  "alt_text_modal.add_alt_text": "Alternativ mətn əlavə et",
-  "alt_text_modal.add_text_from_image": "Şəkildəki mətni əlavə et",
-  "alt_text_modal.cancel": "İmtina",
-  "alt_text_modal.change_thumbnail": "Miniatürü dəyişdir",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Eşitmə məhdudiyyətli insanlar üçün bunu izah et…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Görmə məhdudiyyətli insanlar üçün bunu təsvir et…",
-  "alt_text_modal.done": "Oldu",
   "announcement.announcement": "Elan",
-  "annual_report.summary.archetype.booster": "Trend ovçusu",
-  "annual_report.summary.archetype.lurker": "Lurker",
-  "annual_report.summary.archetype.oracle": "Orakl",
-  "annual_report.summary.archetype.pollster": "Sorğu ustası",
-  "annual_report.summary.archetype.replier": "Sosial kəpənək",
-  "annual_report.summary.followers.followers": "izləyici",
-  "annual_report.summary.followers.total": "Cəmi {count}",
-  "annual_report.summary.here_it_is": "{year} icmalınız:",
-  "annual_report.summary.highlighted_post.by_favourites": "ən çox sevilən postu",
-  "annual_report.summary.highlighted_post.by_reblogs": "ən çox gücləndirilən paylaşımı",
-  "annual_report.summary.highlighted_post.by_replies": "ən çox cavabı olan paylaşımı",
-  "annual_report.summary.highlighted_post.possessive": "{name} istifadəçisinin",
-  "annual_report.summary.most_used_app.most_used_app": "ən çox istifadə etdiyi tətbiq",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "ən çox istifadə etdiyi heşteq",
-  "annual_report.summary.most_used_hashtag.none": "Yoxdur",
-  "annual_report.summary.new_posts.new_posts": "yeni paylaşım",
-  "annual_report.summary.percentile.text": "<topLabel>Bu sizi {domain} istifadəçilərinin ilk</topLabel><percentage></percentage><bottomLabel>qoyur.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Bunu Berniyə deməyəcəyik.",
-  "annual_report.summary.thanks": "Mastodonun bir parçası olduğunuz üçün təşəkkür edirik!",
   "attachments_list.unprocessed": "(emal edilməyib)",
   "audio.hide": "Audionu gizlət",
   "block_modal.remote_users_caveat": "Biz {domain} serverindən qərarınıza hörmət etməsini xahiş edəcəyik. Bununla belə, bəzi serverlər blokları fərqli şəkildə idarə edə bildiyi üçün uyğunluğa zəmanət verilmir. İctimai paylaşımlar hələ də daxil olmayan istifadəçilərə görünə bilər.",
@@ -135,7 +110,6 @@
   "bundle_column_error.routing.body": "Tələb olunan səhifəni tapmaq mümkün olmadı. Ünvan çubuğundakı URL-nin düzgün olduğuna əminsiniz?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Bağla",
-  "bundle_modal_error.message": "Bu ekranı yükləyərkən xəta baş verdi.",
   "bundle_modal_error.retry": "Yenidən cəhd et",
   "closed_registrations.other_server_instructions": "Mastodon desentralizasiya edilmiş olduğu üçün başqa bir serverdə hesab yarada və hələ də bu serverdən istifadə edə bilərsiniz.",
   "closed_registrations_modal.description": "{domain} serverində hesab yaratmaq hazırda mümkün deyil, lakin nəzərə alın ki, Mastodondan istifadə etmək üçün xüsusi olaraq {domain} serverində hesaba ehtiyacınız yoxdur.",
@@ -146,16 +120,13 @@
   "column.blocks": "Bloklanmış istifadəçilər",
   "column.bookmarks": "Əlfəcinlər",
   "column.community": "Lokal zaman qrafiki",
-  "column.create_list": "Siyahı yarat",
   "column.direct": "Fərdi teqlər",
   "column.directory": "Profillər arasında gəz",
   "column.domain_blocks": "Bloklanmış domenlər",
-  "column.edit_list": "Siyahını redaktə et",
   "column.favourites": "Sevimlilər",
   "column.firehose": "Canlı lentlər",
   "column.follow_requests": "İzləyici sorğuları",
   "column.home": "Ana səhifə",
-  "column.list_members": "Siyahı üzvlərini idarə et",
   "column.lists": "Siyahılar",
   "column.mutes": "Səssizləşdirilmiş istifadəçilər",
   "column.notifications": "Bildirişlər",
@@ -168,7 +139,6 @@
   "column_header.pin": "Bərkit",
   "column_header.show_settings": "Parametrləri göstər",
   "column_header.unpin": "Bərkitmə",
-  "column_search.cancel": "İmtina",
   "column_subheading.settings": "Parametrlər",
   "community.column_settings.local_only": "Sadəcə lokalda",
   "community.column_settings.media_only": "Sadəcə media",
@@ -187,7 +157,6 @@
   "compose_form.poll.duration": "Sorğunun müddəti",
   "compose_form.poll.multiple": "Çoxlu cavab",
   "compose_form.poll.option_placeholder": "Seçim {number}",
-  "compose_form.poll.single": "Tək cavab",
   "compose_form.poll.switch_to_multiple": "Çoxsaylı cavablara icazə vermək üçün sorğunu redaktə et",
   "compose_form.poll.switch_to_single": "Tək cavaba icazə vermək üçün sorğunu redaktə et",
   "compose_form.poll.type": "Stil",
@@ -211,16 +180,9 @@
   "confirmations.edit.confirm": "Redaktə et",
   "confirmations.edit.message": "Redaktə etmək hazırda tərtib etdiyiniz mesajın üzərinə yazacaq. Davam etmək istədiyinizə əminsiniz?",
   "confirmations.edit.title": "Paylaşım yenidə yazılsın?",
-  "confirmations.follow_to_list.confirm": "İzlə və siyahıya əlavə et",
-  "confirmations.follow_to_list.message": "{name} istifadəçisini siyahıya əlavə etmək üçün onu izləməlisiniz.",
-  "confirmations.follow_to_list.title": "İstifadəçini izlə?",
   "confirmations.logout.confirm": "Çıxış et",
   "confirmations.logout.message": "Çıxmaq istədiyinizə əminsiniz?",
   "confirmations.logout.title": "Çıxış edilsin?",
-  "confirmations.missing_alt_text.confirm": "Alternativ mətn əlavə et",
-  "confirmations.missing_alt_text.message": "Paylaşımınız alternativ mətn ehtiva etmir. Təsvir əlavə etmək onun daha çox insan üçün əlçatan olmasına kömək edir.",
-  "confirmations.missing_alt_text.secondary": "Yenə də paylaş",
-  "confirmations.missing_alt_text.title": "Alternativ mətn əlavə edilsin?",
   "confirmations.mute.confirm": "Səssizləşdir",
   "confirmations.redraft.confirm": "Sil və qaralamaya köçür",
   "confirmations.redraft.message": "Bu paylaşımı silmək və qaralamaya köçürmək istədiyinizə əminsiniz? Bəyənmələr və gücləndirmələr itəcək və orijinal paylaşıma olan cavablar tənha qalacaq.",
@@ -249,10 +211,6 @@
   "disabled_account_banner.text": "Sizin hesabınız {disabledAccount} hal-hazırda deaktiv edilib.",
   "dismissable_banner.community_timeline": "Bunlar, hesabları {domain} serverində yerləşən insanların ən son ictimai paylaşımlarıdır.",
   "dismissable_banner.dismiss": "Bağla",
-  "dismissable_banner.explore_links": "Bu xəbərlər bu gün fediversedə ən çox paylaşılır. Daha fərqli insanlar tərəfindən dərc edilən daha yeni xəbərlər daha yuxarıda sıralanır.",
-  "dismissable_banner.explore_statuses": "Fediversedən olan bu paylaşımlar bu gün maraq qazanır. Daha çox gücləndirici və bəyənmə olan daha yeni paylaşımlar daha yuxarıda sıralanır.",
-  "dismissable_banner.explore_tags": "Bu heşteqlər fediverse-də trend olublar. Daha çox fərqli insanlar tərəfindən istifadə olunan heşteqlər daha yuxarıda sıralanır.",
-  "dismissable_banner.public_timeline": "Bunlar, {domain} saytında insanların izlədiyi fediversedəki insanların ən son ictimai paylaşımlarıdır.",
   "domain_block_modal.block": "Serveri blokla",
   "domain_block_modal.block_account_instead": "@{name} istifadəçisini blokla",
   "domain_block_modal.they_can_interact_with_old_posts": "Bu serverdən olan insanlar köhnə paylaşımlarınızla əlaqə qura bilər.",
diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json
index 9011fdfd63..7cca261172 100644
--- a/app/javascript/mastodon/locales/be.json
+++ b/app/javascript/mastodon/locales/be.json
@@ -29,6 +29,7 @@
   "account.endorse": "Паказваць у профілі",
   "account.featured_tags.last_status_at": "Апошні допіс ад {date}",
   "account.featured_tags.last_status_never": "Няма допісаў",
+  "account.featured_tags.title": "Тэгі, выбраныя {name}",
   "account.follow": "Падпісацца",
   "account.follow_back": "Падпісацца ў адказ",
   "account.followers": "Падпісчыкі",
@@ -85,27 +86,7 @@
   "alert.unexpected.message": "Узнікла нечаканая памылка.",
   "alert.unexpected.title": "Вой!",
   "alt_text_badge.title": "Альтэрнатыўны тэкст",
-  "alt_text_modal.done": "Гатова",
   "announcement.announcement": "Аб'ява",
-  "annual_report.summary.archetype.booster": "Трэнда-сьледнік",
-  "annual_report.summary.archetype.lurker": "Назіральнік",
-  "annual_report.summary.archetype.oracle": "Аракул",
-  "annual_report.summary.archetype.pollster": "Апытвальнік",
-  "annual_report.summary.archetype.replier": "Душа кампанійі",
-  "annual_report.summary.followers.followers": "падпісанты",
-  "annual_report.summary.followers.total": "Усяго {count}",
-  "annual_report.summary.here_it_is": "Вось вашыя вынікі {year} году:",
-  "annual_report.summary.highlighted_post.by_favourites": "самы ўпадабаны допіс",
-  "annual_report.summary.highlighted_post.by_reblogs": "самы пашыраны допіс",
-  "annual_report.summary.highlighted_post.by_replies": "самы каментаваны допіс",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "самая выкарыстоўваная аплікацыя",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "самы выкарыстоўваны гэштаґ",
-  "annual_report.summary.most_used_hashtag.none": "Няма",
-  "annual_report.summary.new_posts.new_posts": "новыя допісы",
-  "annual_report.summary.percentile.text": "<topLabel>Мэта месьціць вас у топ</topLabel><percentage></percentage><bottomLabel> карыстальнікаў {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Мы ня скажам аб гэтым Сіняпальцаму.",
-  "annual_report.summary.thanks": "Дзякуй за ўдзел у Mastodon!",
   "attachments_list.unprocessed": "(неапрацаваны)",
   "audio.hide": "Схаваць аўдыя",
   "block_modal.remote_users_caveat": "Мы папросім сервер {domain} паважаць ваш выбар. Аднак гэта не гарантуецца, паколькі некаторыя серверы могуць апрацоўваць блакіроўкі іншым чынам. Публічныя паведамленні могуць заставацца бачнымі для ананімных карыстальнікаў.",
@@ -129,7 +110,7 @@
   "bundle_column_error.routing.body": "Запытаная старонка не знойдзена. Вы ўпэўнены, што URL у адрасным радку правільны?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Закрыць",
-  "bundle_modal_error.message": "Падчас загрузкі гэтага экрана штосьці пайшло ня так.",
+  "bundle_modal_error.message": "Нешта пайшло не так падчас загрузкі гэтага кампанента.",
   "bundle_modal_error.retry": "Паспрабуйце зноў",
   "closed_registrations.other_server_instructions": "Паколькі Mastodon дэцэнтралізаваны, вы можаце стварыць уліковы запіс на іншым серверы і працягваць узаемадзейнічаць з ім.",
   "closed_registrations_modal.description": "Стварэнне ўліковага запісу на {domain} цяпер немагчыма. Заўважце, што няма неабходнасці мець уліковы запіс менавіта на {domain}, каб выкарыстоўваць Mastodon.",
@@ -140,16 +121,13 @@
   "column.blocks": "Заблакіраваныя карыстальнікі",
   "column.bookmarks": "Закладкі",
   "column.community": "Лакальная стужка",
-  "column.create_list": "Стварыць спіс",
   "column.direct": "Асабістыя згадванні",
   "column.directory": "Праглядзець профілі",
   "column.domain_blocks": "Заблакіраваныя дамены",
-  "column.edit_list": "Рэдагаваць спіс",
   "column.favourites": "Упадабанае",
   "column.firehose": "Стужкі",
   "column.follow_requests": "Запыты на падпіску",
   "column.home": "Галоўная",
-  "column.list_members": "Кіраванне ўдзельнікамі спісу",
   "column.lists": "Спісы",
   "column.mutes": "Ігнараваныя карыстальнікі",
   "column.notifications": "Апавяшчэнні",
@@ -162,7 +140,6 @@
   "column_header.pin": "Замацаваць",
   "column_header.show_settings": "Паказаць налады",
   "column_header.unpin": "Адмацаваць",
-  "column_search.cancel": "Скасаваць",
   "column_subheading.settings": "Налады",
   "community.column_settings.local_only": "Толькі лакальныя",
   "community.column_settings.media_only": "Толькі медыя",
@@ -205,13 +182,9 @@
   "confirmations.edit.confirm": "Рэдагаваць",
   "confirmations.edit.message": "Калі вы зменіце зараз, гэта ператрэ паведамленне, якое вы пішаце. Вы ўпэўнены, што хочаце працягнуць?",
   "confirmations.edit.title": "Замяніць допіс?",
-  "confirmations.follow_to_list.confirm": "Падпісацца й дадаць у сьпіс",
-  "confirmations.follow_to_list.message": "Вы мусіце быць падпісаныя на {name} каб дадаць яго ў сьпіс.",
-  "confirmations.follow_to_list.title": "Падпісацца на карыстальніка?",
   "confirmations.logout.confirm": "Выйсці",
   "confirmations.logout.message": "Вы ўпэўненыя, што хочаце выйсці?",
   "confirmations.logout.title": "Выйсці?",
-  "confirmations.missing_alt_text.title": "Дадаць апісаньне?",
   "confirmations.mute.confirm": "Ігнараваць",
   "confirmations.redraft.confirm": "Выдаліць і перапісаць",
   "confirmations.redraft.message": "Вы ўпэўнены, што хочаце выдаліць допіс і перапісаць яго? Упадабанні і пашырэнні згубяцца, а адказы да арыгінальнага допісу асірацеюць.",
@@ -240,10 +213,10 @@
   "disabled_account_banner.text": "Ваш уліковы запіс {disabledAccount} часова адключаны.",
   "dismissable_banner.community_timeline": "Гэта самыя апошнія допісы ад людзей, уліковыя запісы якіх размяшчаюцца на {domain}.",
   "dismissable_banner.dismiss": "Адхіліць",
-  "dismissable_banner.explore_links": "Аб гэтых навінах сёньня кажуць на гэтым і йншых сэрвэрах у фэдывёрсу.",
-  "dismissable_banner.explore_statuses": "Гэтыя самыя папулярныя сёньняшнія допісы ў фэдывёрсе.",
-  "dismissable_banner.explore_tags": "Гэтыя гэштаґі набіраюць папулярнасьць у фэдывёрсе сёньня. Гэштаґі якія выкарыстоўваюцца большай колькасьцю людзей зьяўляюцца вышэй у сьпісе.",
-  "dismissable_banner.public_timeline": "Гэта самыя новыя публічныя допісы ад карыстальнікаў фэдывёрсу на якіх падпісаныя карыстальнікі {domain}.",
+  "dismissable_banner.explore_links": "Гэтыя навіны абмяркоўваюцца цяпер на гэтым і іншых серверах дэцэнтралізаванай сеткі.",
+  "dismissable_banner.explore_statuses": "Допісы з гэтага і іншых сервераў дэцэнтралізаванай сеткі, якія набіраюць папулярнасць прама зараз.",
+  "dismissable_banner.explore_tags": "Гэтыя хэштэгі зараз набіраюць папулярнасць сярод людзей на гэтым і іншых серверах дэцэнтралізаванай сеткі",
+  "dismissable_banner.public_timeline": "Гэта апошнія публічныя допісы людзей з усей сеткі, за якімі сочаць карыстальнікі {domain}.",
   "domain_block_modal.block": "Заблакіраваць сервер",
   "domain_block_modal.block_account_instead": "Заблакіраваць @{name} замест гэтага",
   "domain_block_modal.they_can_interact_with_old_posts": "Людзі з гэтага сервера змогуць узаемадзейнічаць з вашымі старымі допісамі.",
@@ -300,6 +273,7 @@
   "empty_column.hashtag": "Па гэтаму хэштэгу пакуль што нічога няма.",
   "empty_column.home": "Галоўная стужка пустая! Падпішыцеся на іншых людзей, каб запоўніць яе. {suggestions}",
   "empty_column.list": "У гэтым спісе пакуль што нічога няма. Калі члены лісту апублікуюць новыя запісы, яны з'явяцца тут.",
+  "empty_column.lists": "Як толькі вы створыце новы спіс ён будзе захоўвацца тут, але пакуль што тут пуста.",
   "empty_column.mutes": "Вы яшчэ нікога не ігнаруеце.",
   "empty_column.notification_requests": "Чысціня! Тут нічога няма. Калі вы будзеце атрымліваць новыя апавяшчэння, яны будуць з'яўляцца тут у адпаведнасці з вашымі наладамі.",
   "empty_column.notifications": "У вас няма ніякіх апавяшчэнняў. Калі іншыя людзі ўзаемадзейнічаюць з вамі, вы ўбачыце гэта тут.",
@@ -310,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Паспрабуйце выключыць іх і аднавіць старонку. Калі гэта не дапаможа, вы можаце карыстацца Мастадонт праз другі браўзер ці аплікацыю.",
   "errors.unexpected_crash.copy_stacktrace": "Дадаць дыягнастычны стэк у буфер абмену",
   "errors.unexpected_crash.report_issue": "Паведаміць аб праблеме",
+  "explore.search_results": "Вынікі пошуку",
   "explore.suggested_follows": "Людзі",
   "explore.title": "Агляд",
   "explore.trending_links": "Навіны",
@@ -359,14 +334,13 @@
   "footer.about": "Пра нас",
   "footer.directory": "Дырэкторыя профіляў",
   "footer.get_app": "Спампаваць праграму",
+  "footer.invite": "Запрасіць людзей",
   "footer.keyboard_shortcuts": "Спалучэнні клавіш",
   "footer.privacy_policy": "Палітыка прыватнасці",
   "footer.source_code": "Прагледзець зыходны код",
   "footer.status": "Статус",
-  "footer.terms_of_service": "Умовы абслугоўваньня",
   "generic.saved": "Захавана",
   "getting_started.heading": "Пачатак працы",
-  "hashtag.admin_moderation": "Адкрыць інтэрфэйс мадаратара для #{name}",
   "hashtag.column_header.tag_mode.all": "і {additional}",
   "hashtag.column_header.tag_mode.any": "або {additional}",
   "hashtag.column_header.tag_mode.none": "без {additional}",
@@ -408,11 +382,17 @@
   "ignore_notifications_modal.not_followers_title": "Ігнараваць паведамленьні ад людзей, якія ня падпісаныя на вас?",
   "ignore_notifications_modal.not_following_title": "Ігнараваць апавяшчэнні ад людзей на якіх вы не падпісаны?",
   "ignore_notifications_modal.private_mentions_title": "Ігнараваць паведамленьні аб непажаданых прыватных згадках?",
-  "info_button.label": "Даведка",
-  "interaction_modal.action.favourite": "Каб працягнуць, вы мусіце ўпадабаць з вашага ўліковага запісу.",
-  "interaction_modal.action.follow": "Каб працягнуць, вы мусіце падпісацца з вашага ўліковага запісу.",
+  "interaction_modal.description.favourite": "Маючы ўліковы запіс Mastodon, вы можаце ўпадабаць гэты допіс, каб паведаміць аўтару, што ён вам падабаецца, і захаваць яго на будучыню.",
+  "interaction_modal.description.follow": "Маючы акаўнт у Mastodon, вы можаце падпісацца на {name}, каб бачыць яго/яе допісы ў сваёй хатняй стужцы.",
+  "interaction_modal.description.reblog": "З уліковым запісам Mastodon, вы можаце пашырыць гэты пост, каб падзяліцца ім са сваімі падпісчыкамі.",
+  "interaction_modal.description.reply": "Маючы акаўнт у Mastodon, вы можаце адказаць на гэты пост.",
+  "interaction_modal.login.action": "Вярніце мяне дадому",
+  "interaction_modal.login.prompt": "Дамен вашага хатняга сервера, напрыклад, mastodon.social",
+  "interaction_modal.no_account_yet": "Яшчэ не ў Mastodon?",
   "interaction_modal.on_another_server": "На іншым серверы",
   "interaction_modal.on_this_server": "На гэтым серверы",
+  "interaction_modal.sign_in": "Вы не выканалі ўваход на гэтым серверы. Дзе размешчаны ваш уліковы запіс?",
+  "interaction_modal.sign_in_hint": "Падказка: гэта сайт, на якім вы зарэгістраваліся. Калі вы не памятаеце, знайдзіце ліст у паштовай скрыні. Вы таксама можаце ўвесці сваё поўнае імя карыстальніка! (напрыклад, @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Упадабаць допіс {name}",
   "interaction_modal.title.follow": "Падпісацца на {name}",
   "interaction_modal.title.reblog": "Пашырыць допіс ад {name}",
@@ -462,17 +442,20 @@
   "link_preview.author": "Ад {name}",
   "link_preview.more_from_author": "Больш ад {name}",
   "link_preview.shares": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}",
-  "lists.add_member": "Дадаць",
-  "lists.create": "Стварыць",
-  "lists.create_list": "Стварыць спіс",
+  "lists.account.add": "Дадаць да спісу",
+  "lists.account.remove": "Выдаліць са спісу",
   "lists.delete": "Выдаліць спіс",
   "lists.edit": "Рэдагаваць спіс",
-  "lists.remove_member": "Выдаліць",
+  "lists.edit.submit": "Змяніць назву",
+  "lists.exclusive": "Схаваць гэтыя допісы з галоўнай старонкі",
+  "lists.new.create": "Дадаць спіс",
+  "lists.new.title_placeholder": "Назва новага спіса",
   "lists.replies_policy.followed": "Любы карыстальнік, на якога вы падпісаліся",
   "lists.replies_policy.list": "Удзельнікі гэтага спісу",
   "lists.replies_policy.none": "Нікога",
-  "lists.save": "Захаваць",
-  "lists.search": "Пошук",
+  "lists.replies_policy.title": "Паказваць адказы:",
+  "lists.search": "Шукайце сярод людзей, на якіх Вы падпісаны",
+  "lists.subheading": "Вашыя спісы",
   "load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}",
   "loading_indicator.label": "Загрузка…",
   "media_gallery.hide": "Схаваць",
@@ -503,7 +486,6 @@
   "navigation_bar.follows_and_followers": "Падпіскі і падпісчыкі",
   "navigation_bar.lists": "Спісы",
   "navigation_bar.logout": "Выйсці",
-  "navigation_bar.moderation": "Мадэрацыя",
   "navigation_bar.mutes": "Ігнараваныя карыстальнікі",
   "navigation_bar.opened_in_classic_interface": "Допісы, уліковыя запісы і іншыя спецыфічныя старонкі па змоўчанні адчыняюцца ў класічным вэб-інтэрфейсе.",
   "navigation_bar.personal": "Асабістае",
@@ -612,21 +594,44 @@
   "notifications_permission_banner.enable": "Уключыць апавяшчэнні на працоўным стале",
   "notifications_permission_banner.how_to_control": "Каб атрымліваць апавяшчэнні, калі Mastodon не адкрыты, уключыце апавяшчэнні працоўнага стала. Вы зможаце дакладна кантраляваць, якія падзеі будуць ствараць апавяшчэнні з дапамогай {icon} кнопкі, як толькі яны будуць уключаны.",
   "notifications_permission_banner.title": "Не прапусціце нічога",
-  "onboarding.follows.back": "Назад",
-  "onboarding.follows.done": "Гатова",
+  "onboarding.action.back": "Прыняць мяне назад",
+  "onboarding.actions.back": "Прыняць мяне назад",
+  "onboarding.actions.go_to_explore": "Паглядзіце, што ў трэндзе",
+  "onboarding.actions.go_to_home": "Перайдзіце на свой хатні канал",
+  "onboarding.compose.template": "Прывітанне, #Mastodon!",
   "onboarding.follows.empty": "На жаль, зараз немагчыма паказаць вынікі. Вы можаце паспрабаваць выкарыстоўваць пошук і праглядзець старонку агляду, каб знайсці людзей, на якіх можна падпісацца, або паўтарыце спробу пазней.",
-  "onboarding.follows.search": "Пошук",
-  "onboarding.follows.title": "Падпішыцеся каб пачаць",
+  "onboarding.follows.lead": "Вы самі ствараеце свой хатні канал. Чым больш людзей вы падпішаце, тым больш актыўна і цікавей гэта будзе. Гэтыя профілі могуць стаць добрай адпраўной кропкай — вы заўсёды можаце адмяніць падпіску на іх пазней!",
+  "onboarding.follows.title": "Папулярна на Mastodon",
   "onboarding.profile.discoverable": "Зрабіць мой профіль бачным",
   "onboarding.profile.discoverable_hint": "Калі вы звяртаецеся да адкрытасці на Mastodon, вашы паведамленні могуць з'яўляцца ў выніках пошуку і тэндэнцый, а ваш профіль можа быць прапанаваны людзям з такімі ж інтарэсамі.",
   "onboarding.profile.display_name": "Бачнае імя",
   "onboarding.profile.display_name_hint": "Ваша поўнае імя або ваш псеўданім…",
+  "onboarding.profile.lead": "Вы заўсёды можаце выканаць гэта пазней у Наладах, дзе даступна яшчэ больш параметраў.",
   "onboarding.profile.note": "Біяграфія",
   "onboarding.profile.note_hint": "Вы можаце @згадаць іншых людзей або выкарыстоўваць #хэштэгі…",
   "onboarding.profile.save_and_continue": "Захаваць і працягнуць",
   "onboarding.profile.title": "Налады профілю",
   "onboarding.profile.upload_avatar": "Загрузіць фота профілю",
   "onboarding.profile.upload_header": "Загрузіць шапку профілю",
+  "onboarding.share.lead": "Дайце людзям ведаць, як яны могуць знайсці вас на Mastodon!",
+  "onboarding.share.message": "Я {username} на #Mastodon! Сачыце за мной на {url}",
+  "onboarding.share.next_steps": "Магчымыя наступныя крокі:",
+  "onboarding.share.title": "Абагульце свой профіль",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "Вы зрабілі гэта!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Зрабіце свой першы допіс",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Абагульць ваш профіль у Mastodon",
+  "onboarding.tips.2fa": "<strong>Ці вы ведаеце?</strong> Вы можаце абараніць свой уліковы запіс, усталяваўшы двухфактарную аўтэнтыфікацыю ў наладах уліковага запісу. Яна працуе з любой праграмай TOTP на ваш выбар, нумар тэлефона не патрэбны!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Ці вы ведаеце?</strong> Паколькі Mastodon дэцэнтралізаваны, некаторыя профілі, якія вам трапляюцца, будуць размяшчацца на іншых серверах, адрозных ад вашага. І ўсё ж вы можаце бесперашкодна ўзаемадзейнічаць з імі! Іх сервер пазначаны ў другой палове імя карыстальніка!",
+  "onboarding.tips.migration": "<strong>Ці вы ведаеце?</strong> Калі вы адчуваеце, што {domain} не з'яўляецца для вас лепшым выбарам у будучыні, вы можаце перайсці на іншы сервер Mastodon, не губляючы сваіх падпісчыкаў. Вы нават можаце стварыць свой уласны сервер!",
+  "onboarding.tips.verification": "<strong>Ці вы ведаеце?</strong> Вы можаце пацвердзіць свой уліковы запіс, размясціўшы спасылку на свой профіль Mastodon на сваім вэб-сайце і дадаўшы вэб-сайт у свой профіль. Ніякіх збораў і дакументаў не патрабуецца!",
   "password_confirmation.exceeds_maxlength": "Пароль пацьверджання перавышае максімальна дапушчальную даўжыню",
   "password_confirmation.mismatching": "Пароль пацьверджання не супадае",
   "picture_in_picture.restore": "Вярніце назад",
@@ -642,6 +647,7 @@
   "poll_button.remove_poll": "Выдаліць апытанне",
   "privacy.change": "Змяніць прыватнасць допісу",
   "privacy.direct.long": "Усе згаданыя ў допісе",
+  "privacy.direct.short": "Канкрэтныя людзі",
   "privacy.private.long": "Толькі вашыя падпісчыкі",
   "privacy.private.short": "Падпісчыкі",
   "privacy.public.long": "Усе, хто ёсць і каго няма ў Mastodon",
@@ -653,8 +659,8 @@
   "privacy_policy.title": "Палітыка канфідэнцыйнасці",
   "recommended": "Рэкамендуем",
   "refresh": "Абнавiць",
-  "regeneration_indicator.please_stand_by": "Калі ласка, пачакайце.",
-  "regeneration_indicator.preparing_your_home_feed": "Рыхтуем вашую стужку…",
+  "regeneration_indicator.label": "Загрузка…",
+  "regeneration_indicator.sublabel": "Пачакайце, рыхтуем вашу стужку!",
   "relative_time.days": "{number} д",
   "relative_time.full.days": "{number, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}} таму",
   "relative_time.full.hours": "{number, plural, one {# гадзіна} few {# гадзіны} many {# гадзін} other {# гадзіны}} таму",
@@ -738,11 +744,10 @@
   "search_results.accounts": "Профілі",
   "search_results.all": "Усё",
   "search_results.hashtags": "Хэштэгі",
-  "search_results.no_results": "Анічога ня знойдзена.",
-  "search_results.no_search_yet": "Паспрабуйце пашукаць допісы, профілі або гэштаґі.",
+  "search_results.nothing_found": "Па дадзенаму запыту нічога не знойдзена",
   "search_results.see_all": "Праглядзець усе",
   "search_results.statuses": "Допісы",
-  "search_results.title": "Шукаць \"{q}\"",
+  "search_results.title": "Пошук {q}",
   "server_banner.about_active_users": "Людзі, якія карыстаюцца гэтым сервера на працягу апошніх 30 дзён (Штомесячна Актыўныя Карыстальнікі)",
   "server_banner.active_users": "актыўныя карыстальнікі",
   "server_banner.administered_by": "Адміністратар:",
@@ -815,7 +820,6 @@
   "subscribed_languages.target": "Змяніць мовы падпіскі для {target}",
   "tabs_bar.home": "Галоўная",
   "tabs_bar.notifications": "Апавяшчэнні",
-  "terms_of_service.title": "Умовы абслугоўваньня",
   "time_remaining.days": "{number, plural, one {застаўся # дзень} few {засталося # дні} many {засталося # дзён} other {засталося # дня}}",
   "time_remaining.hours": "{number, plural, one {засталася # гадзіна} few {засталося # гадзіны} many {засталося # гадзін} other {засталося # гадзіны}}",
   "time_remaining.minutes": "{number, plural, one {засталася # хвіліна} few {засталося # хвіліны} many {засталося # хвілін} other {засталося # хвіліны}}",
@@ -831,7 +835,21 @@
   "upload_button.label": "Дадаць выяву, відэа- ці аўдыяфайл",
   "upload_error.limit": "Перавышана колькасць файлаў.",
   "upload_error.poll": "Немагчыма прымацаваць файл да апытання.",
+  "upload_form.audio_description": "Апісанне для людзей з парушэннямі слыху",
+  "upload_form.description": "Апісаць для людзей са слабым зрокам",
   "upload_form.edit": "Рэдагаваць",
+  "upload_form.thumbnail": "Змяніць мініяцюру",
+  "upload_form.video_description": "Апісанне для людзей з парушэннямі зроку і слыху",
+  "upload_modal.analyzing_picture": "Аналіз выявы…",
+  "upload_modal.apply": "Ужыць",
+  "upload_modal.applying": "Ужываецца…",
+  "upload_modal.choose_image": "Выбраць выяву",
+  "upload_modal.description_placeholder": "У рудога вераб’я ў сховішчы пад фатэлем ляжаць нейкія гаючыя зёлкі",
+  "upload_modal.detect_text": "Распазнаць тэкст з выявы",
+  "upload_modal.edit_media": "Рэдагаваць медыя",
+  "upload_modal.hint": "Націсніце ці перацягніце кружок на перадпрагляд каб выбраць фокусную кропку, што заўсёды будзе бачна на мініяцюрах.",
+  "upload_modal.preparing_ocr": "Падрыхтоўка OCR…",
+  "upload_modal.preview_label": "Перадпрагляд ({ratio})",
   "upload_progress.label": "Запампоўванне...",
   "upload_progress.processing": "Апрацоўка…",
   "username.taken": "Гэта імя карыстальніка занята. Паспрабуйце іншае",
@@ -841,6 +859,8 @@
   "video.expand": "Разгарнуць відэа",
   "video.fullscreen": "Увесь экран",
   "video.hide": "Схаваць відэа",
+  "video.mute": "Адключыць гук",
   "video.pause": "Паўза",
-  "video.play": "Прайграць"
+  "video.play": "Прайграць",
+  "video.unmute": "Уключыць гук"
 }
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index 2e4c8593d4..25a20185c1 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Редактиране на профила",
   "account.enable_notifications": "Известяване при публикуване от @{name}",
   "account.endorse": "Представи в профила",
-  "account.featured": "Препоръчано",
-  "account.featured.hashtags": "Хаштагове",
-  "account.featured.posts": "Публикации",
   "account.featured_tags.last_status_at": "Последна публикация на {date}",
   "account.featured_tags.last_status_never": "Няма публикации",
+  "account.featured_tags.title": "Главни хаштагове на {name}",
   "account.follow": "Последване",
   "account.follow_back": "Последване взаимно",
   "account.followers": "Последователи",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} публикация} other {{counter} публикации}}",
   "account.unblock": "Отблокиране на @{name}",
   "account.unblock_domain": "Отблокиране на домейн {domain}",
-  "account.unblock_domain_short": "Отблокиране",
   "account.unblock_short": "Отблокиране",
   "account.unendorse": "Не включвайте в профила",
   "account.unfollow": "Стоп на следването",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Възникна неочаквана грешка.",
   "alert.unexpected.title": "Опаа!",
   "alt_text_badge.title": "Алтернативен текст",
-  "alt_text_modal.add_alt_text": "Добавяне на алтернативен текст",
-  "alt_text_modal.add_text_from_image": "Добавяне на текст от образ",
-  "alt_text_modal.cancel": "Отказ",
-  "alt_text_modal.change_thumbnail": "Промяна на миниобраза",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Опишете това за хора със слухови увреждания…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Опишете това за хора със зрителни увреждания…",
-  "alt_text_modal.done": "Готово",
   "announcement.announcement": "Оповестяване",
-  "annual_report.summary.archetype.booster": "Якият подсилвател",
-  "annual_report.summary.archetype.lurker": "Дебнещото",
-  "annual_report.summary.archetype.oracle": "Оракул",
-  "annual_report.summary.archetype.pollster": "Анкетьорче",
-  "annual_report.summary.archetype.replier": "Социална пеперуда",
-  "annual_report.summary.followers.followers": "последователи",
-  "annual_report.summary.followers.total": "{count} общо",
-  "annual_report.summary.here_it_is": "Ето преглед на вашата {year} година:",
-  "annual_report.summary.highlighted_post.by_favourites": "най-правено като любима публикация",
-  "annual_report.summary.highlighted_post.by_reblogs": "най-подсилваната публикация",
-  "annual_report.summary.highlighted_post.by_replies": "публикация с най-много отговори",
-  "annual_report.summary.highlighted_post.possessive": "на {name}",
-  "annual_report.summary.most_used_app.most_used_app": "най-употребявано приложение",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "най-употребяван хаштаг",
-  "annual_report.summary.most_used_hashtag.none": "Няма",
-  "annual_report.summary.new_posts.new_posts": "нови публикации",
-  "annual_report.summary.percentile.text": "<topLabel>Това ви слага най-отгоре</topLabel><percentage></percentage><bottomLabel>сред потребителите на {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Няма да кажем на Бърни Сандърс.",
-  "annual_report.summary.thanks": "Благодарим, че сте част от Mastodon!",
   "attachments_list.unprocessed": "(необработено)",
   "audio.hide": "Скриване на звука",
   "block_modal.remote_users_caveat": "Ще приканим сървъра {domain} да уважава решението ви. За съжаление не можем да гарантираме това защото някои сървъри могат да третират блокиранията по различен начин. Публичните постове може да продължат да бъдат видими за потребители, които не са се регистрирали.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "Заявената страница не може да се намери. Сигурни ли сте, че URL адресът в адресната лента е правилен?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Затваряне",
-  "bundle_modal_error.message": "Нещо се обърка, зареждайки този екран.",
+  "bundle_modal_error.message": "Нещо се обърка, зареждайки компонента.",
   "bundle_modal_error.retry": "Нов опит",
   "closed_registrations.other_server_instructions": "Oткак e децентрализиранa Mastodon, може да създадете акаунт на друг сървър и още може да взаимодействате с този.",
   "closed_registrations_modal.description": "Създаването на акаунт в {domain} сега не е възможно, но обърнете внимание, че нямате нужда от акаунт конкретно на {domain}, за да ползвате Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Блокирани потребители",
   "column.bookmarks": "Отметки",
   "column.community": "Локална хронология",
-  "column.create_list": "Създаване на списък",
   "column.direct": "Частни споменавания",
   "column.directory": "Разглеждане на профили",
   "column.domain_blocks": "Блокирани домейни",
-  "column.edit_list": "Промяна на списъка",
   "column.favourites": "Любими",
   "column.firehose": "Инфоканали на живо",
   "column.follow_requests": "Заявки за последване",
   "column.home": "Начало",
-  "column.list_members": "Управление на списъка с участници",
   "column.lists": "Списъци",
   "column.mutes": "Заглушени потребители",
   "column.notifications": "Известия",
@@ -172,7 +140,6 @@
   "column_header.pin": "Закачане",
   "column_header.show_settings": "Показване на настройките",
   "column_header.unpin": "Разкачане",
-  "column_search.cancel": "Отказ",
   "column_subheading.settings": "Настройки",
   "community.column_settings.local_only": "Само локално",
   "community.column_settings.media_only": "Само мултимедия",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Времетраене на анкетата",
   "compose_form.poll.multiple": "Множествен избор",
   "compose_form.poll.option_placeholder": "Избор {number}",
-  "compose_form.poll.single": "Единичен избор",
+  "compose_form.poll.single": "Подберете нещо",
   "compose_form.poll.switch_to_multiple": "Промяна на анкетата, за да се позволят множество възможни избора",
   "compose_form.poll.switch_to_single": "Промяна на анкетата, за да се позволи един възможен избор",
   "compose_form.poll.type": "Стил",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Редактиране",
   "confirmations.edit.message": "Редактирането сега ще замени съобщението, което в момента съставяте. Сигурни ли сте, че искате да продължите?",
   "confirmations.edit.title": "Презаписвате ли публикацията?",
-  "confirmations.follow_to_list.confirm": "Последване и добавяне в списък",
-  "confirmations.follow_to_list.message": "Трябва да последвате {name}, за да добавите лицето към списък.",
-  "confirmations.follow_to_list.title": "Последвате ли потребителя?",
   "confirmations.logout.confirm": "Излизане",
   "confirmations.logout.message": "Наистина ли искате да излезете?",
   "confirmations.logout.title": "Излизате ли от системата?",
-  "confirmations.missing_alt_text.confirm": "Добавяне на алтернативен текст",
-  "confirmations.missing_alt_text.message": "Вашата публикация съдържа мултимедия без алтернативен текст. Добавянето на описание помага да направите съдържанието си по-достъпно за повече хора.",
-  "confirmations.missing_alt_text.secondary": "Все пак да се публикува",
-  "confirmations.missing_alt_text.title": "Добавяте ли алтернативен текст?",
   "confirmations.mute.confirm": "Заглушаване",
   "confirmations.redraft.confirm": "Изтриване и преработване",
   "confirmations.redraft.message": "Наистина ли искате да изтриете тази публикация и да я направите чернова? Означаванията като любими и подсилванията ще се изгубят, а и отговорите към първоначалната публикация ще осиротеят.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Вашият акаунт {disabledAccount} сега е изключен.",
   "dismissable_banner.community_timeline": "Ето най-скорошните публични публикации от хора, чиито акаунти са разположени в {domain}.",
   "dismissable_banner.dismiss": "Отхвърляне",
-  "dismissable_banner.explore_links": "Тези новинарски истории са най-споделяните във федивселената днес. По-нови новинарски истории, публикувани от повече различни хора са класирани по-напред.",
-  "dismissable_banner.explore_statuses": "Има публикации из федивселената, които днес набират популярност. По-новите публикации с повече подсилвания и любими са класирани по-високо.",
-  "dismissable_banner.explore_tags": "Тези хаштагове днес набират популярност. Хаштагове, употребявани от повече различни хора са класирани по-напред.",
-  "dismissable_banner.public_timeline": "Ето най-новите обществени публикации от хора във федивселената, която хората в {domain} следват.",
+  "dismissable_banner.explore_links": "Има новинарски истории, които са най-споделяните в социалната мрежа днес. По-нови новинарски истории, публикувани от повече различни хора са класирани по-напред.",
+  "dismissable_banner.explore_statuses": "Има публикации през социалната мрежа, които днес набират популярност. По-новите публикации с повече подсилвания и любими са класирани по-високо.",
+  "dismissable_banner.explore_tags": "Тези хаштагове сега набират популярност сред хората в този и други сървъри на децентрализирата мрежа.",
+  "dismissable_banner.public_timeline": "Ето най-новите обществени публикации от хора в социална мрежа, която хората в {domain} следват.",
   "domain_block_modal.block": "Блокиране на сървър",
   "domain_block_modal.block_account_instead": "Вместо това блокиране на @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Хората от този сървър могат да взаимодействат с ваши стари публикации.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Резултати от търсене",
   "emoji_button.symbols": "Символи",
   "emoji_button.travel": "Пътуване и места",
-  "empty_column.account_featured": "Списъкът е празен",
   "empty_column.account_hides_collections": "Този потребител е избрал да не дава тази информация",
   "empty_column.account_suspended": "Спрян акаунт",
   "empty_column.account_timeline": "Тук няма публикации!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Още няма нищо в този хаштаг.",
   "empty_column.home": "Вашата начална хронология е празна! Последвайте повече хора, за да се запълни.",
   "empty_column.list": "Все още списъкът е празен. Членуващите на списъка, публикуващи нови публикации, ще се появят тук.",
+  "empty_column.lists": "Все още нямате списъци. Когато създадете такъв, той ще се покаже тук.",
   "empty_column.mutes": "Още не сте заглушавали потребители.",
   "empty_column.notification_requests": "Всичко е чисто! Тук няма нищо. Получавайки нови известия, те ще се появят тук според настройките ви.",
   "empty_column.notifications": "Все още нямате известия. Взаимодействайте с другите, за да започнете разговора.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Опитайте се да ги изключите и да опресните страницата. Ако това не помогне, то още може да използвате Mastodon чрез различен браузър или приложение.",
   "errors.unexpected_crash.copy_stacktrace": "Копиране на трасето на стека в буферната памет",
   "errors.unexpected_crash.report_issue": "Сигнал за проблем",
+  "explore.search_results": "Резултати от търсенето",
   "explore.suggested_follows": "Хора",
   "explore.title": "Разглеждане",
   "explore.trending_links": "Новини",
@@ -373,16 +334,13 @@
   "footer.about": "Относно",
   "footer.directory": "Директория на профилите",
   "footer.get_app": "Вземане на приложението",
+  "footer.invite": "Поканване на хора",
   "footer.keyboard_shortcuts": "Клавишни комбинации",
   "footer.privacy_policy": "Политика за поверителност",
   "footer.source_code": "Преглед на изходния код",
   "footer.status": "Състояние",
-  "footer.terms_of_service": "Условия на услугата",
   "generic.saved": "Запазено",
   "getting_started.heading": "Първи стъпки",
-  "hashtag.admin_moderation": "Отваряне на модериращия интерфейс за #{name}",
-  "hashtag.browse": "Разглеждане на публикации в #{hashtag}",
-  "hashtag.browse_from_account": "Разглеждане на публикации от @{name} из #{hashtag}",
   "hashtag.column_header.tag_mode.all": "и {additional}",
   "hashtag.column_header.tag_mode.any": "или {additional}",
   "hashtag.column_header.tag_mode.none": "без {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} публикация} other {{counter} публикации}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} публикация} other {{counter} публикации}} днес",
   "hashtag.follow": "Следване на хаштаг",
-  "hashtag.mute": "Заглушаване на #{hashtag}",
   "hashtag.unfollow": "Спиране на следване на хаштаг",
   "hashtags.and_other": "…и {count, plural, other {# още}}",
   "hints.profiles.followers_may_be_missing": "Последователи за този профил може да липсват.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Пренебрегвате ли известията от хора, които не са ви последвали?",
   "ignore_notifications_modal.not_following_title": "Пренебрегвате ли известията от хора, които не сте последвали?",
   "ignore_notifications_modal.private_mentions_title": "Пренебрегвате ли известия от непоискани лични споменавания?",
-  "info_button.label": "Помощ",
-  "info_button.what_is_alt_text": "<h1>Какво е алтернативен текст?</h1> <p>Алтернативният текст осигурява описания на изображение за хора със зрителни увреждания, връзки с ниска честотна лента или търсещите допълнителен контекст.</p> <p>Може да подобрите достъпността и разбираемостта за всеки, пишейки ясен, кратък и обективен алтернативен текст.</p> <ul> <li>Уловете важните елементи</li> <li>Обобщете текста в образите</li> <li>Употребявайте правилна структура на изречението</li> <li>Избягвайте излишна информация</li> <li>Съсредоточете се върху тенденциите и ключови констатации в сложни онагледявания (като диаграми и карти)</li> </ul>",
-  "interaction_modal.action.favourite": "Трябва да направите любимо от акаунта си, за да продължите.",
-  "interaction_modal.action.follow": "Трябва да последвате от акаунта си, за да продължите.",
-  "interaction_modal.action.reblog": "Трябва да разпространите нечий блог от акаунта си, за да продължите.",
-  "interaction_modal.action.reply": "Трябва да отговорите от акаунта си, за да продължите.",
-  "interaction_modal.action.vote": "Трябва да гласувате от акаунта си, за да продължите.",
-  "interaction_modal.go": "Напред",
-  "interaction_modal.no_account_yet": "Още ли нямате акаунт?",
+  "interaction_modal.description.favourite": "Имайки акаунт в Mastodon, може да сложите тази публикации в любими, за да позволите на автора да узнае, че я цените и да я запазите за по-късно.",
+  "interaction_modal.description.follow": "С акаунт в Mastodon може да последвате {name}, за да получавате публикациите от този акаунт в началния си инфоканал.",
+  "interaction_modal.description.reblog": "С акаунт в Mastodon може да подсилите тази публикация, за да я споделите с последователите си.",
+  "interaction_modal.description.reply": "С акаунт в Mastodon може да добавите отговор към тази публикация.",
+  "interaction_modal.login.action": "Към началото",
+  "interaction_modal.login.prompt": "Домейнът на сървъра ви, примерно, mastodon.social",
+  "interaction_modal.no_account_yet": "Още ли не сте в Mastodon?",
   "interaction_modal.on_another_server": "На различен сървър",
   "interaction_modal.on_this_server": "На този сървър",
+  "interaction_modal.sign_in": "Не сте влезли в този сървър. Къде се хоства акаунтът ви?",
+  "interaction_modal.sign_in_hint": "Съвет: Ето уебсайта, където сте се регистрирали. Ако не помните, то погледнете е-писмо за добре дошли във входящата си поща. Може също да въведете пълното си потребителско име! (примерно: @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Означавам публикация на {name} като любима",
   "interaction_modal.title.follow": "Последване на {name}",
   "interaction_modal.title.reblog": "Подсилване на публикацията на {name}",
   "interaction_modal.title.reply": "Отговаряне на публикацията на {name}",
-  "interaction_modal.title.vote": "Гласувайте в анкетата на {name}",
-  "interaction_modal.username_prompt": "Напр. {example}",
   "intervals.full.days": "{number, plural, one {# ден} other {# дни}}",
   "intervals.full.hours": "{number, plural, one {# час} other {# часа}}",
   "intervals.full.minutes": "{number, plural, one {# минута} other {# минути}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Показване/скриване на текст зад предупреждение на съдържание",
   "keyboard_shortcuts.toggle_sensitivity": "Показване/скриване на мултимедията",
   "keyboard_shortcuts.toot": "Начало на нова публикация",
-  "keyboard_shortcuts.translate": "за превод на публикация",
   "keyboard_shortcuts.unfocus": "Разфокусиране на текстовото поле за съставяне/търсене",
   "keyboard_shortcuts.up": "Преместване нагоре в списъка",
   "lightbox.close": "Затваряне",
@@ -490,32 +444,20 @@
   "link_preview.author": "От {name}",
   "link_preview.more_from_author": "Още от {name}",
   "link_preview.shares": "{count, plural, one {{counter} публикация} other {{counter} публикации}}",
-  "lists.add_member": "Добавяне",
-  "lists.add_to_list": "Добавяне в списък",
-  "lists.add_to_lists": "Добавяне на {name} в списъци",
-  "lists.create": "Създаване",
-  "lists.create_a_list_to_organize": "Сътворете нов списък, за да организирате инфоканала си на Начало",
-  "lists.create_list": "Създаване на списък",
+  "lists.account.add": "Добавяне към списък",
+  "lists.account.remove": "Премахване от списъка",
   "lists.delete": "Изтриване на списъка",
-  "lists.done": "Готово",
   "lists.edit": "Промяна на списъка",
-  "lists.exclusive": "Скриване на членуващи в Начало",
-  "lists.exclusive_hint": "Ако някой е в този списък, то скрийте го в инфоканала си на Начало, за да избегнете виждането на публикациите му два пъти.",
-  "lists.find_users_to_add": "Намерете потребители за добавяне",
-  "lists.list_members": "Списък членуващи",
-  "lists.list_members_count": "{count, plural, one {# членуващ} other {# членуващи}}",
-  "lists.list_name": "Име на списък",
-  "lists.new_list_name": "Ново име на списък",
-  "lists.no_lists_yet": "Още няма списъци.",
-  "lists.no_members_yet": "Още няма членуващи.",
-  "lists.no_results_found": "Няма намерени резултати.",
-  "lists.remove_member": "Премахване",
+  "lists.edit.submit": "Промяна на заглавие",
+  "lists.exclusive": "Скриване на тези публикации от началото",
+  "lists.new.create": "Добавяне на списък",
+  "lists.new.title_placeholder": "Ново заглавие на списъка",
   "lists.replies_policy.followed": "Някой последван потребител",
   "lists.replies_policy.list": "Членуващите в списъка",
   "lists.replies_policy.none": "Никого",
-  "lists.save": "Запазване",
-  "lists.search": "Търсене",
-  "lists.show_replies_to": "Включва отговори от членуващи в списъка до",
+  "lists.replies_policy.title": "Показване на отговори на:",
+  "lists.search": "Търсене измежду последваните",
+  "lists.subheading": "Вашите списъци",
   "load_pending": "{count, plural, one {# нов елемент} other {# нови елемента}}",
   "loading_indicator.label": "Зареждане…",
   "media_gallery.hide": "Скриване",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} докладва {target}",
   "notification.admin.sign_up": "{name} се регистрира",
   "notification.admin.sign_up.name_and_others": "{name} и {count, plural, one {# друг} other {# други}} се регистрираха",
-  "notification.annual_report.message": "#Wrapstodon за {year} година ви очаква! Свалете булото на изтъкнатото и паметните моменти за годината си в Mastodon!",
-  "notification.annual_report.view": "Преглед на #Wrapstodon",
   "notification.favourite": "{name} направи любима публикацията ви",
   "notification.favourite.name_and_others_with_link": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> направиха любима ваша публикация",
-  "notification.favourite_pm": "{name} хареса вашето лично споменаване",
-  "notification.favourite_pm.name_and_others_with_link": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> харесаха вашето частно споменаване",
   "notification.follow": "{name} ви последва",
   "notification.follow.name_and_others": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> ви последваха",
   "notification.follow_request": "{name} поиска да ви последва",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Включване на известията на работния плот",
   "notifications_permission_banner.how_to_control": "За да получавате известия, когато Mastodon не е отворен, включете известията на работния плот. Може да управлявате точно кои видове взаимодействия пораждат известия на работния плот чрез бутона {icon} по-горе, след като бъдат включени.",
   "notifications_permission_banner.title": "Никога не пропускайте нищо",
-  "onboarding.follows.back": "Назад",
-  "onboarding.follows.done": "Готово",
+  "onboarding.action.back": "Върнете ме обратно",
+  "onboarding.actions.back": "Върнете ме обратно",
+  "onboarding.actions.go_to_explore": "Виж тенденции",
+  "onboarding.actions.go_to_home": "Към началния ми инфоканал",
+  "onboarding.compose.template": "Здравейте, #Mastodon!",
   "onboarding.follows.empty": "За съжаление, в момента не могат да се показват резултати. Може да опитате посредством търсене или сърфиране да разгледате страницата, за да намерите хора за последване, или опитайте пак по-късно.",
-  "onboarding.follows.search": "Търсене",
-  "onboarding.follows.title": "Последвайте хора, за да започнете",
+  "onboarding.follows.lead": "Може да бъдете куратор на началния си инфоканал. Последвайки повече хора, по-деен и по-интересен ще става. Тези профили може да са добра начална точка, от която винаги по-късно да спрете да следвате!",
+  "onboarding.follows.title": "Популярно в Mastodon",
   "onboarding.profile.discoverable": "Правене на моя профил откриваем",
   "onboarding.profile.discoverable_hint": "Включвайки откриваемостта в Mastodon, вашите публикации може да се появят при резултатите от търсене и изгряващи неща, и вашия профил може да бъде предложен на хора с подобни интереси като вашите.",
-  "onboarding.profile.display_name": "Показвано име",
+  "onboarding.profile.display_name": "Името на показ",
   "onboarding.profile.display_name_hint": "Вашето пълно име или псевдоним…",
+  "onboarding.profile.lead": "Винаги може да завършите това по-късно в настройките, където дори има повече възможности за настройване.",
   "onboarding.profile.note": "Биография",
   "onboarding.profile.note_hint": "Може да @споменавате други хора или #хаштагове…",
   "onboarding.profile.save_and_continue": "Запазване и продължаване",
   "onboarding.profile.title": "Настройване на профила",
   "onboarding.profile.upload_avatar": "Качване на снимка на профила",
   "onboarding.profile.upload_header": "Качване на заглавка на профила",
+  "onboarding.share.lead": "Позволете на хората да знаят, че могат да ви намерят в Mastodon!",
+  "onboarding.share.message": "Аз съм {username} в #Mastodon! Елате да ме последвате при {url}",
+  "onboarding.share.next_steps": "Възможни следващи стъпки:",
+  "onboarding.share.title": "Споделяне на профила ви",
+  "onboarding.start.lead": "Вашият нов акаунт в Mastodon е готов за употреба. Ето как може да се възползвате по най-добрия начин от него:",
+  "onboarding.start.skip": "Желаете ли да прескочите?",
+  "onboarding.start.title": "Успяхте!",
+  "onboarding.steps.follow_people.body": "Може да бъдете куратор на инфоканала си. Хайде да го запълним с интересни хора.",
+  "onboarding.steps.follow_people.title": "Персонализиране на началния ви инфоканал",
+  "onboarding.steps.publish_status.body": "Поздравете целия свят.",
+  "onboarding.steps.publish_status.title": "Направете първата си публикация",
+  "onboarding.steps.setup_profile.body": "Подсилете взаимодействията си, имайки изчерпателен профил.",
+  "onboarding.steps.setup_profile.title": "Пригодете профила си",
+  "onboarding.steps.share_profile.body": "Позволете на приятелите си да знаят как да ви намират в Mastodon!",
+  "onboarding.steps.share_profile.title": "Споделяне на профила ви",
+  "onboarding.tips.2fa": "<strong>Знаете ли, че?</strong> Може да защитите акаунта си, настройвайки двуфакторното удостоверяване в настройките на акаунта си. То работи с всяко приложение TOTP по ваш избор, не е необходим номер телефона!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Знаете ли, че?</strong> Откак Mastodon е децентрализиран, някои профили, които срещате ще бъдат разположени на сървъри различен от вашия. И още може да взаимодействате с тях безпроблемно! Сървърът им е втората половина от потребителското им име!",
+  "onboarding.tips.migration": "<strong>Знаете ли, че?</strong> Ако се чувствате, че {domain} не е чудесен избор на сървър в бъдуще, може да се преместите на друг сървър на Mastodon без да загубите последователите си. Дори може да сте съдържатели на свой собствен сървър!",
+  "onboarding.tips.verification": "<strong>Знаете ли, че?</strong> Може да потвърдите акаунта си, слагайки връзка към профила си в Mastodon на уебсайта си и добавите уебсайта в профила си. Не са необходими документи или такси!",
   "password_confirmation.exceeds_maxlength": "Потвърждаването на паролата превишава максимално допустимата дължина за парола",
   "password_confirmation.mismatching": "Потвърждаването на паролата не съвпада",
   "picture_in_picture.restore": "Връщане обратно",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Премахване на анкета",
   "privacy.change": "Промяна на поверителността на публикация",
   "privacy.direct.long": "Споменатите в публикацията",
-  "privacy.direct.short": "Частно споменаване",
+  "privacy.direct.short": "Определени хора",
   "privacy.private.long": "Само последователите ви",
   "privacy.private.short": "Последователи",
   "privacy.public.long": "Всеки във и извън Mastodon",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Политика за поверителност",
   "recommended": "Препоръчано",
   "refresh": "Опресняване",
-  "regeneration_indicator.please_stand_by": "Изчакайте.",
-  "regeneration_indicator.preparing_your_home_feed": "Подготовка на вашия инфоканал начало…",
+  "regeneration_indicator.label": "Зареждане…",
+  "regeneration_indicator.sublabel": "Подготовка на началния ви инфоканал!",
   "relative_time.days": "{number} д.",
   "relative_time.full.days": "преди {number, plural, one {# ден} other {# дни}}",
   "relative_time.full.hours": "преди {number, plural, one {# час} other {# часа}}",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Профили",
   "search_results.all": "Всичко",
   "search_results.hashtags": "Хаштагове",
-  "search_results.no_results": "Няма намерени резултати.",
-  "search_results.no_search_yet": "Опитайте да потърсите постове, профили или хаштагове.",
+  "search_results.nothing_found": "Не може да се намери каквото и да било за тези термини при търсене",
   "search_results.see_all": "Поглед на всички",
   "search_results.statuses": "Публикации",
-  "search_results.title": "Търсене на \"{q}\"",
+  "search_results.title": "Търсене за {q}",
   "server_banner.about_active_users": "Ползващите сървъра през последните 30 дни (дейните месечно потребители)",
   "server_banner.active_users": "дейни потребители",
   "server_banner.administered_by": "Администрира се от:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Още никого не е подсилвал публикацията. Подсилващият ще се покаже тук.",
   "status.redraft": "Изтриване и преработване",
   "status.remove_bookmark": "Премахване на отметката",
-  "status.remove_favourite": "Премахване от любими",
   "status.replied_in_thread": "Отговорено в нишката",
   "status.replied_to": "В отговор до {name}",
   "status.reply": "Отговор",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Промяна на абонираните езици за {target}",
   "tabs_bar.home": "Начало",
   "tabs_bar.notifications": "Известия",
-  "terms_of_service.effective_as_of": "В сила от {date}",
-  "terms_of_service.title": "Условия на услугата",
-  "terms_of_service.upcoming_changes_on": "Предстоящи промени на {date}",
   "time_remaining.days": "{number, plural, one {остава # ден} other {остават # дни}}",
   "time_remaining.hours": "{number, plural, one {остава # час} other {остават # часа}}",
   "time_remaining.minutes": "{number, plural, one {остава # минута} other {остават # минути}}",
@@ -897,12 +853,26 @@
   "upload_button.label": "Добавете файл с образ, видео или звук",
   "upload_error.limit": "Превишено ограничението за качване на файлове.",
   "upload_error.poll": "Качването на файлове не е позволено с анкети.",
+  "upload_form.audio_description": "Опишете за хора, които са глухи или трудно чуват",
+  "upload_form.description": "Опишете за хора, които са слепи или имат слабо зрение",
   "upload_form.drag_and_drop.instructions": "Натиснете интервал или enter, за да подберете мултимедийно прикачване. Провлачвайки, ползвайте клавишите със стрелки, за да премествате мултимедията във всяка дадена посока. Натиснете пак интервал или enter, за да се стовари мултимедийното прикачване в новото си положение или натиснете Esc за отмяна.",
   "upload_form.drag_and_drop.on_drag_cancel": "Провлачването е отменено. Мултимедийното прикачване {item} е спуснато.",
   "upload_form.drag_and_drop.on_drag_end": "Мултимедийното прикачване {item} е спуснато.",
   "upload_form.drag_and_drop.on_drag_over": "Мултимедийното прикачване {item} е преместено.",
   "upload_form.drag_and_drop.on_drag_start": "Избрано мултимедийно прикачване {item}.",
   "upload_form.edit": "Редактиране",
+  "upload_form.thumbnail": "Промяна на миниобраза",
+  "upload_form.video_description": "Опишете за хора, които са глухи или трудно чуват, слепи или имат слабо зрение",
+  "upload_modal.analyzing_picture": "Снимков анализ…",
+  "upload_modal.apply": "Прилагане",
+  "upload_modal.applying": "Прилагане…",
+  "upload_modal.choose_image": "Избор на образ",
+  "upload_modal.description_placeholder": "Ах, чудна българска земьо, полюшвай цъфтящи жита",
+  "upload_modal.detect_text": "Откриване на текст от картина",
+  "upload_modal.edit_media": "Промяна на мултимедия",
+  "upload_modal.hint": "Щракнете или плъзнете кръга на визуализацията, за да изберете фокусна точка, която винаги ще бъде видима на всички миниатюри.",
+  "upload_modal.preparing_ocr": "Подготовка за оптично разпознаване на знаци…",
+  "upload_modal.preview_label": "Нагледно ({ratio})",
   "upload_progress.label": "Качване...",
   "upload_progress.processing": "Обработка…",
   "username.taken": "Това потребителско име е взето. Опитайте друго",
@@ -912,12 +882,8 @@
   "video.expand": "Разгъване на видеото",
   "video.fullscreen": "Цял екран",
   "video.hide": "Скриване на видеото",
-  "video.mute": "Заглушаване",
+  "video.mute": "Обеззвучаване",
   "video.pause": "Пауза",
   "video.play": "Пускане",
-  "video.skip_backward": "Прескок назад",
-  "video.skip_forward": "Прескок напред",
-  "video.unmute": "Без заглушаване",
-  "video.volume_down": "Намаляване на звука",
-  "video.volume_up": "Увеличаване на звука"
+  "video.unmute": "Включване на звука"
 }
diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json
index ec0f4eb447..3d55fc81fc 100644
--- a/app/javascript/mastodon/locales/bn.json
+++ b/app/javascript/mastodon/locales/bn.json
@@ -29,6 +29,7 @@
   "account.endorse": "প্রোফাইলে ফিচার করুন",
   "account.featured_tags.last_status_at": "{date} এ সর্বশেষ পোস্ট",
   "account.featured_tags.last_status_never": "কোনো পোস্ট নেই",
+  "account.featured_tags.title": "{name} এর ফিচার করা Hashtag সমূহ",
   "account.follow": "অনুসরণ",
   "account.follow_back": "তাকে অনুসরণ করো",
   "account.followers": "অনুসরণকারী",
@@ -93,6 +94,7 @@
   "bundle_column_error.routing.body": "অনুরোধ করা পৃষ্ঠা খুঁজে পাওয়া যাবে না। আপনি কি নিশ্চিত যে ঠিকানা বারে ইউআরএলটি সঠিক?",
   "bundle_column_error.routing.title": "৪০৪",
   "bundle_modal_error.close": "বন্ধ করুন",
+  "bundle_modal_error.message": "এই অংশটি দেখাতে যেয়ে কোনো সমস্যা হয়েছে।.",
   "bundle_modal_error.retry": "আবার চেষ্টা করুন",
   "closed_registrations.other_server_instructions": "মাস্টোডন বিকেন্দ্রীভূত হওয়ায়, আপনি অন্য সার্ভারে একটি অ্যাকাউন্ট তৈরি করতে পারেন এবং এখনও এটির সাথে যোগাযোগ করতে পারেন।",
   "closed_registrations_modal.description": "{domain} এ একটি অ্যাকাউন্ট তৈরি করা বর্তমানে সম্ভব নয়, তবে দয়া করে মনে রাখবেন যে ম্যাস্টোডন ব্যবহার করার জন্য আপনার বিশেষভাবে {domain} এ কোনো অ্যাকাউন্টের প্রয়োজন নেই৷",
@@ -174,6 +176,8 @@
   "disabled_account_banner.account_settings": "একাউন্ট সেটিংস",
   "disabled_account_banner.text": "আপনার একাউন্ট {disabledAccount} বর্তমানে বন্ধ করা.",
   "dismissable_banner.dismiss": "সরাও",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "এই লেখাটি আপনার ওয়েবসাইটে যুক্ত করতে নিচের কোডটি বেবহার করুন।",
   "embed.preview": "সেটা দেখতে এরকম হবে:",
   "emoji_button.activity": "কার্যকলাপ",
@@ -201,6 +205,7 @@
   "empty_column.hashtag": "এই হেসটাগে এখনো কিছু নেই।",
   "empty_column.home": "আপনার বাড়ির সময়রেখা এখনো খালি!  {public} এ ঘুরে আসুন অথবা অনুসন্ধান বেবহার করে শুরু করতে পারেন এবং অন্য ব্যবহারকারীদের সাথে সাক্ষাৎ করতে পারেন।",
   "empty_column.list": "এই তালিকাতে এখনো কিছু নেই. যখন এই তালিকায় থাকা ব্যবহারকারী নতুন কিছু লিখবে, সেগুলো এখানে পাওয়া যাবে।",
+  "empty_column.lists": "আপনার এখনো কোনো তালিকা তৈরী নেই। যদি বা যখন তৈরী করেন, সেগুলো এখানে পাওয়া যাবে।",
   "empty_column.mutes": "আপনি এখনো কোনো ব্যবহারকারীকে নিঃশব্দ করেননি।",
   "empty_column.notifications": "আপনার এখনো কোনো প্রজ্ঞাপন নেই। কথোপকথন শুরু করতে,  অন্যদের সাথে মেলামেশা করতে পারেন।",
   "empty_column.public": "এখানে এখনো কিছু নেই! প্রকাশ্য ভাবে কিছু লিখুন বা অন্য সার্ভার থেকে কাওকে অনুসরণ করে এই জায়গা ভরে ফেলুন",
@@ -275,9 +280,16 @@
   "lightbox.next": "পরবর্তী",
   "lightbox.previous": "পূর্ববর্তী",
   "link_preview.author": "{name} এর লিখা",
+  "lists.account.add": "তালিকাতে যুক্ত করতে",
+  "lists.account.remove": "তালিকা থেকে বাদ দিতে",
   "lists.delete": "তালিকা মুছে ফেলতে",
   "lists.edit": "তালিকা সম্পাদনা করতে",
+  "lists.edit.submit": "শিরোনাম সম্পাদনা করতে",
+  "lists.new.create": "তালিকাতে যুক্ত করতে",
+  "lists.new.title_placeholder": "তালিকার নতুন শিরোনাম দিতে",
   "lists.replies_policy.none": "কেউ না",
+  "lists.search": "যাদের অনুসরণ করেন তাদের ভেতরে খুঁজুন",
+  "lists.subheading": "আপনার তালিকা",
   "load_pending": "{count, plural, one {# নতুন জিনিস} other {# নতুন জিনিস}}",
   "navigation_bar.about": "পরিচিতি",
   "navigation_bar.blocks": "বন্ধ করা ব্যবহারকারী",
@@ -326,6 +338,21 @@
   "notifications.filter.mentions": "উল্লেখিত",
   "notifications.filter.polls": "নির্বাচনের ফলাফল",
   "notifications.group": "{count} প্রজ্ঞাপন",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
+  "onboarding.tips.accounts_from_other_servers": "<strong>তুমি কি জানতে?</strong> যেহেতু মাস্টোডন বিকেন্দ্রীভূত, কিছু অ্যাকাউন্ট তোমার নিজের ছাড়া অন্য কোনো সার্ভারে থাকতে পারে। অথচ তুমি তাদের সাথে কোনো সমস্যা ছাড়াই কথা বলতে পারছো! তাদের সার্ভার তাদের ব্যবহারকারী নামের দ্বিতীয় অর্ধাংশ!",
+  "onboarding.tips.migration": "<strong>তুমি কি জানো?</strong> {domain} তোমার পছন্দ না হলে, ভবিষ্যতে তুমি অন্য কোনো সার্ভারে যেতে পারো তোমার অনুসরণকারীদেরকে না হারিয়েই। এমনকি তুমি নিজের সার্ভারও তৈরি করতে পারো!",
   "picture_in_picture.restore": "ফিরত রাখো",
   "poll.closed": "বন্ধ",
   "poll.refresh": "বদলেছে কিনা দেখতে",
@@ -339,6 +366,8 @@
   "privacy.change": "লেখার গোপনীয়তা অবস্থা ঠিক করতে",
   "privacy.public.short": "সর্বজনীন প্রকাশ্য",
   "refresh": "সতেজ করা",
+  "regeneration_indicator.label": "আসছে…",
+  "regeneration_indicator.sublabel": "আপনার বাড়ির-সময়রেখা প্রস্তূত করা হচ্ছে!",
   "relative_time.days": "{number} দিন",
   "relative_time.full.just_now": "এইমাত্র",
   "relative_time.hours": "{number} ঘন্টা",
@@ -427,7 +456,19 @@
   "upload_button.label": "ছবি বা ভিডিও যুক্ত করতে (এসব ধরণের: JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "যা যুক্ত করতে চাচ্ছেন সেটি বেশি বড়, এখানকার সর্বাধিকের মেমোরির উপরে চলে গেছে।",
   "upload_error.poll": "নির্বাচনক্ষেত্রে কোনো ফাইল যুক্ত করা যাবেনা।",
+  "upload_form.audio_description": "শ্রবণশক্তি লোকদের জন্য বর্ণনা করুন",
+  "upload_form.description": "যারা দেখতে পায়না তাদের জন্য এটা বর্ণনা করতে",
   "upload_form.edit": "সম্পাদন",
+  "upload_form.thumbnail": "থাম্বনেল পরিবর্তন করুন",
+  "upload_form.video_description": "শ্রবণশক্তি হ্রাস বা চাক্ষুষ প্রতিবন্ধী ব্যক্তিদের জন্য বর্ণনা করুন",
+  "upload_modal.analyzing_picture": "চিত্র বিশ্লেষণ করা হচ্ছে…",
+  "upload_modal.apply": "প্রয়োগ করুন",
+  "upload_modal.applying": "প্রয়োগ করা হচ্ছে…",
+  "upload_modal.choose_image": "ছবি নির্বাচন করুন",
+  "upload_modal.detect_text": "ছবি থেকে পাঠ্য সনাক্ত করুন",
+  "upload_modal.edit_media": "মিডিয়া সম্পাদনা করুন",
+  "upload_modal.hint": "একটি দৃশ্যমান পয়েন্ট নির্বাচন করুন ক্লিক অথবা টানার মাধ্যমে যেটি সবময় সব থাম্বনেলে দেখা যাবে।",
+  "upload_modal.preview_label": "পূর্বরূপ({ratio})",
   "upload_progress.label": "যুক্ত করতে পাঠানো হচ্ছে...",
   "video.close": "ভিডিওটি বন্ধ করতে",
   "video.download": "ফাইলটি ডাউনলোড করুন",
@@ -435,6 +476,8 @@
   "video.expand": "ভিডিওটি বড়ো করতে",
   "video.fullscreen": "পূর্ণ পর্দা করতে",
   "video.hide": "ভিডিওটি লুকাতে",
+  "video.mute": "শব্দ বন্ধ করতে",
   "video.pause": "থামাতে",
-  "video.play": "শুরু করতে"
+  "video.play": "শুরু করতে",
+  "video.unmute": "শব্দ চালু করতে"
 }
diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json
index 51e3723d19..0944c74f9e 100644
--- a/app/javascript/mastodon/locales/br.json
+++ b/app/javascript/mastodon/locales/br.json
@@ -28,6 +28,7 @@
   "account.endorse": "Lakaat war-wel war ar profil",
   "account.featured_tags.last_status_at": "Toud diwezhañ : {date}",
   "account.featured_tags.last_status_never": "Embannadur ebet",
+  "account.featured_tags.title": "Hashtagoù pennañ {name}",
   "account.follow": "Heuliañ",
   "account.follow_back": "Heuliañ d'ho tro",
   "account.followers": "Tud koumanantet",
@@ -80,14 +81,7 @@
   "alert.rate_limited.title": "Feur bevennet",
   "alert.unexpected.message": "Ur fazi dic'hortozet zo degouezhet.",
   "alert.unexpected.title": "Hopala !",
-  "alt_text_modal.cancel": "Nullañ",
-  "alt_text_modal.change_thumbnail": "Kemmañ ar velvenn",
-  "alt_text_modal.done": "Graet",
   "announcement.announcement": "Kemennad",
-  "annual_report.summary.followers.followers": "heulier",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_hashtag.none": "Hini ebet",
-  "annual_report.summary.new_posts.new_posts": "toudoù nevez",
   "attachments_list.unprocessed": "(ket meret)",
   "audio.hide": "Kuzhat ar c'hleved",
   "block_modal.show_less": "Diskouez nebeutoc'h",
@@ -103,6 +97,7 @@
   "bundle_column_error.routing.body": "N'haller ket kavout ar bajenn goulennet. Sur oc'h eo reizh an URL er varrenn chomlec'hioù?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Serriñ",
+  "bundle_modal_error.message": "Degouezhet ez eus bet ur fazi en ur gargañ an elfenn-mañ.",
   "bundle_modal_error.retry": "Klask en-dro",
   "closed_registrations.other_server_instructions": "Peogwir ez eo Mastodon digreizennet e c'heller krouiñ ur gont war ur servijer all ha kenderc'hel da zaremprediñ gant hemañ.",
   "closed_registrations_modal.description": "N'eo ket posupl krouiñ ur gont war {domain} evit ar mare, met n'ho peus ket ezhomm ur gont war {domain} dre ret evit ober gant Mastodon.",
@@ -113,11 +108,9 @@
   "column.blocks": "Implijer·ezed·ien berzet",
   "column.bookmarks": "Sinedoù",
   "column.community": "Red-amzer lec'hel",
-  "column.create_list": "Krouiñ ul listenn",
   "column.direct": "Menegoù prevez",
   "column.directory": "Mont a-dreuz ar profiloù",
   "column.domain_blocks": "Domani berzet",
-  "column.edit_list": "Kemmañ al listenn",
   "column.favourites": "Muiañ-karet",
   "column.firehose": "Redoù war-eeun",
   "column.follow_requests": "Rekedoù heuliañ",
@@ -134,7 +127,6 @@
   "column_header.pin": "Spilhennañ",
   "column_header.show_settings": "Diskouez an arventennoù",
   "column_header.unpin": "Dispilhennañ",
-  "column_search.cancel": "Nullañ",
   "column_subheading.settings": "Arventennoù",
   "community.column_settings.local_only": "Nemet lec'hel",
   "community.column_settings.media_only": "Nemet Mediaoù",
@@ -153,6 +145,7 @@
   "compose_form.poll.duration": "Pad ar sontadeg",
   "compose_form.poll.multiple": "Meur a choaz",
   "compose_form.poll.option_placeholder": "Choaz {number}",
+  "compose_form.poll.single": "Dibabit unan",
   "compose_form.poll.switch_to_multiple": "Kemmañ ar sontadeg evit aotren meur a zibab",
   "compose_form.poll.switch_to_single": "Kemmañ ar sontadeg evit aotren un dibab hepken",
   "compose_form.poll.type": "Neuz",
@@ -168,12 +161,9 @@
   "confirmations.delete.message": "Ha sur oc'h e fell deoc'h dilemel an toud-mañ ?",
   "confirmations.delete_list.confirm": "Dilemel",
   "confirmations.delete_list.message": "Ha sur eo hoc'h eus c'hoant da zilemel ar roll-mañ da vat ?",
-  "confirmations.delete_list.title": "Dilemel al listenn?",
   "confirmations.discard_edit_media.confirm": "Nac'hañ",
   "confirmations.discard_edit_media.message": "Bez ez eus kemmoù n'int ket enrollet e deskrivadur ar media pe ar rakwel, nullañ anezho evelato?",
   "confirmations.edit.confirm": "Kemmañ",
-  "confirmations.edit.message": "Kemmañ bremañ a zilamo ar gemennadenn emaoc'h o skrivañ. Sur e oc'h e fell deoc'h kenderc'hel ganti?",
-  "confirmations.follow_to_list.title": "Heuliañ an implijer·ez?",
   "confirmations.logout.confirm": "Digevreañ",
   "confirmations.logout.message": "Ha sur oc'h e fell deoc'h digevreañ ?",
   "confirmations.mute.confirm": "Kuzhat",
@@ -198,6 +188,8 @@
   "disabled_account_banner.text": "Ho kont {disabledAccount} zo divev evit bremañ.",
   "dismissable_banner.community_timeline": "Setu toudoù foran nevesañ an dud a zo herberc’hiet o c'hontoù gant {domain}.",
   "dismissable_banner.dismiss": "Diverkañ",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "domain_pill.server": "Dafariad",
   "domain_pill.username": "Anv-implijer",
   "embed.instructions": "Enframmit an toud-mañ en ho lec'hienn en ur eilañ ar c'hod amañ-dindan.",
@@ -230,6 +222,7 @@
   "empty_column.hashtag": "N'eus netra en hashtag-mañ c'hoazh.",
   "empty_column.home": "Goullo eo ho red-amzer degemer! Kit da weladenniñ {public} pe implijit ar c'hlask evit kregiñ ganti ha kejañ gant implijer·ien·ezed all.",
   "empty_column.list": "Goullo eo al listenn-mañ evit c'hoazh. Pa vo embannet toudoù nevez gant e izili e teuint war wel amañ.",
+  "empty_column.lists": "N'ho peus roll ebet c'hoazh. Pa vo krouet unan ganeoc'h e vo diskouezet amañ.",
   "empty_column.mutes": "N'ho peus kuzhet implijer ebet c'hoazh.",
   "empty_column.notifications": "N'ho peus kemenn ebet c'hoazh. Grit gant implijer·ezed·ien all evit loc'hañ ar gomz.",
   "empty_column.public": "N'eus netra amañ! Skrivit un dra bennak foran pe heuilhit implijer·ien·ezed eus dafariadoù all evit leuniañ",
@@ -239,6 +232,7 @@
   "error.unexpected_crash.next_steps_addons": "Klaskit azbevaat ar bajenn. Ma n'ez a ket en-dro e c'hallit klask ober gant Mastodon dre ur merdeer disheñvel pe dre an arload genidik.",
   "errors.unexpected_crash.copy_stacktrace": "Eilañ ar roudoù diveugañ er golver",
   "errors.unexpected_crash.report_issue": "Danevellañ ur fazi",
+  "explore.search_results": "Disoc'hoù an enklask",
   "explore.suggested_follows": "Tud",
   "explore.title": "Furchal",
   "explore.trending_links": "Keleier",
@@ -271,14 +265,13 @@
   "footer.about": "Diwar-benn",
   "footer.directory": "Kavlec'h ar profiloù",
   "footer.get_app": "Pellgargañ an arload",
+  "footer.invite": "Pediñ tud",
   "footer.keyboard_shortcuts": "Berradennoù klavier",
   "footer.privacy_policy": "Reolennoù prevezded",
   "footer.source_code": "Gwelet ar c'hod mammenn",
   "footer.status": "Statud",
-  "footer.terms_of_service": "Divizoù implijout hollek",
   "generic.saved": "Enrollet",
   "getting_started.heading": "Loc'hañ",
-  "hashtag.admin_moderation": "Digeriñ an etrefas evezhiañ evit #{name}",
   "hashtag.column_header.tag_mode.all": "ha(g) {additional}",
   "hashtag.column_header.tag_mode.any": "pe {additional}",
   "hashtag.column_header.tag_mode.none": "hep {additional}",
@@ -299,6 +292,10 @@
   "home.pending_critical_update.body": "Hizivait ho servijer Mastodon kerkent ha ma c'hallit mar plij!",
   "home.pending_critical_update.link": "Gwelet an hizivadennoù",
   "home.show_announcements": "Diskouez ar c'hemennoù",
+  "interaction_modal.description.follow": "Gant ur gont Mastodon e c'hellit heuliañ {name} evit resev an toudoù a embann war ho red degemer.",
+  "interaction_modal.description.reblog": "Gant ur gont Mastodon e c'hellit skignañ an toud-mañ evit rannañ anezhañ gant ho heulierien·ezed.",
+  "interaction_modal.description.reply": "Gant ur gont Mastodon e c'hellit respont d'an toud-mañ.",
+  "interaction_modal.no_account_yet": "N'emañ ket war vMastodon?",
   "interaction_modal.on_another_server": "War ur servijer all",
   "interaction_modal.on_this_server": "War ar servijer-mañ",
   "interaction_modal.title.favourite": "Ouzhpennañ embannadur {name} d'ar re vuiañ-karet",
@@ -348,17 +345,19 @@
   "limited_account_hint.action": "Diskouez an aelad memes tra",
   "limited_account_hint.title": "Kuzhet eo bet ar profil-mañ gant an evezhierien eus {domain}.",
   "link_preview.author": "Gant {name}",
-  "lists.add_member": "Ouzhpennañ",
-  "lists.add_to_list": "Ouzhpennañ d'al listenn",
-  "lists.create": "Krouiñ",
-  "lists.create_list": "Krouiñ ul listenn",
+  "lists.account.add": "Ouzhpennañ d'al listenn",
+  "lists.account.remove": "Lemel kuit eus al listenn",
   "lists.delete": "Dilemel al listenn",
-  "lists.done": "Graet",
   "lists.edit": "Kemmañ al listenn",
-  "lists.list_name": "Anv al listenn",
+  "lists.edit.submit": "Cheñch an titl",
+  "lists.new.create": "Ouzhpennañ ul listenn",
+  "lists.new.title_placeholder": "Titl nevez al listenn",
   "lists.replies_policy.followed": "Pep implijer.ez heuliet",
   "lists.replies_policy.list": "Izili ar roll",
   "lists.replies_policy.none": "Den ebet",
+  "lists.replies_policy.title": "Diskouez ar respontoù:",
+  "lists.search": "Klask e-touez tud heuliet ganeoc'h",
+  "lists.subheading": "Ho listennoù",
   "load_pending": "{count, plural, one {# dra nevez} other {# dra nevez}}",
   "loading_indicator.label": "O kargañ…",
   "navigation_bar.about": "Diwar-benn",
@@ -390,17 +389,11 @@
   "notification.follow": "heuliañ a ra {name} ac'hanoc'h",
   "notification.follow.name_and_others": "{name} <a>{count, plural, one {hag # den all} two {ha # zen all} few {ha # den all} many {ha # den all} other {ha # den all}}</a> zo o heuliañ ac'hanoc'h",
   "notification.follow_request": "Gant {name} eo bet goulennet ho heuliañ",
-  "notification.label.reply": "Respont",
   "notification.moderation-warning.learn_more": "Gouzout hiroc'h",
   "notification.own_poll": "Echu eo ho sontadeg",
   "notification.reblog": "Gant {name} eo bet skignet ho toud",
-  "notification.relationships_severance_event.learn_more": "Gouzout hiroc'h",
   "notification.status": "Emañ {name} o paouez toudañ",
   "notification.update": "Gant {name} ez eus bet kemmet un toud",
-  "notification_requests.accept": "Asantiñ",
-  "notification_requests.dismiss": "Diverkañ",
-  "notification_requests.edit_selection": "Kemmañ",
-  "notification_requests.exit_selection": "Graet",
   "notifications.clear": "Skarzhañ ar c'hemennoù",
   "notifications.clear_confirmation": "Ha sur oc'h e fell deoc'h skarzhañ ho holl kemennoù ?",
   "notifications.column_settings.admin.report": "Disklêriadurioù nevez :",
@@ -433,23 +426,37 @@
   "notifications.permission_denied": "Kemennoù war ar burev n'int ket hegerz rak pedadenn aotren ar merdeer a zo bet nullet araok",
   "notifications.permission_denied_alert": "Kemennoù wa ar burev na c'hellont ket bezañ lezelet, rak aotre ar merdeer a zo bet nac'het a-raok",
   "notifications.permission_required": "Kemennoù war ar burev n'int ket hegerz abalamour d'an aotre rekis n'eo ket bet roet.",
-  "notifications.policy.accept": "Asantiñ",
-  "notifications.policy.accept_hint": "Diskouez er c’hemennoù",
-  "notifications.policy.drop": "Tremen e-bioù",
-  "notifications.policy.filter": "Silañ",
   "notifications.policy.filter_new_accounts_title": "Kontoù nevez",
   "notifications_permission_banner.enable": "Lezel kemennoù war ar burev",
   "notifications_permission_banner.how_to_control": "Evit reseviñ kemennoù pa ne vez ket digoret Mastodon, lezelit kemennoù war ar burev. Gallout a rit kontrollañ peseurt eskemmoù a c'henel kemennoù war ar burev gant ar {icon} nozelenn a-us kentre ma'z int lezelet.",
   "notifications_permission_banner.title": "Na vankit netra morse",
-  "onboarding.follows.back": "Distreiñ",
-  "onboarding.follows.done": "Graet",
-  "onboarding.follows.search": "Klask",
+  "onboarding.action.back": "Distreiñ",
+  "onboarding.actions.back": "Distreiñ",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Mont d'ho red degemer",
+  "onboarding.compose.template": "Salud #Mastodon!",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
   "onboarding.profile.display_name": "Anv diskouezet",
   "onboarding.profile.display_name_hint": "Hoc'h anv klok pe hoc'h anv fentus…",
   "onboarding.profile.note": "Berr-ha-berr",
   "onboarding.profile.note_hint": "Gallout a rit @menegiñ tud all pe #hashtagoù…",
   "onboarding.profile.save_and_continue": "Enrollañ ha kenderc'hel",
   "onboarding.profile.upload_avatar": "Enporzhiañ ur skeudenn profil",
+  "onboarding.share.lead": "Roit da c'houzout d'an dud e c'hallont ho kavout war vMastondon!",
+  "onboarding.share.message": "Me a zo {username} war #Mastodon! Heuilhit ac'hanon war {url}",
+  "onboarding.share.title": "Skignañ ho profil",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "Deuet oc'h a-benn!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Grit hoc'h embannadur kentañ",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Rannit ho kont Mastodon",
   "password_confirmation.mismatching": "Disheñvel eo an daou c'her-termen-se",
   "picture_in_picture.restore": "Adlakaat",
   "poll.closed": "Serret",
@@ -463,12 +470,15 @@
   "poll_button.add_poll": "Ouzhpennañ ur sontadeg",
   "poll_button.remove_poll": "Dilemel ar sontadeg",
   "privacy.change": "Cheñch prevezded an embannadur",
+  "privacy.direct.short": "Tud resis",
   "privacy.private.short": "Heulierien",
   "privacy.public.short": "Publik",
   "privacy_policy.last_updated": "Hizivadenn ziwezhañ {date}",
   "privacy_policy.title": "Reolennoù Prevezded",
   "recommended": "Erbedet",
   "refresh": "Freskaat",
+  "regeneration_indicator.label": "O kargañ…",
+  "regeneration_indicator.sublabel": "War brientiñ emañ ho red degemer!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# devezh} two {# zevezh} few {# devezh} many {# a devezh} other {# devezh}} zo",
   "relative_time.full.hours": "{number, plural, one {# eurvezh} two {# eurvezh} few {# eurvezh} many {# eur} other {# eurvezh}} zo",
@@ -542,9 +552,10 @@
   "search_results.accounts": "Profiloù",
   "search_results.all": "Pep tra",
   "search_results.hashtags": "Hashtagoù",
-  "search_results.no_results": "Disoc'h ebet.",
+  "search_results.nothing_found": "Disoc'h ebet gant ar gerioù-se",
   "search_results.see_all": "Gwelet pep tra",
   "search_results.statuses": "Toudoù",
+  "search_results.title": "Klask {q}",
   "server_banner.active_users": "implijerien·ezed oberiant",
   "server_banner.administered_by": "Meret gant :",
   "server_banner.server_stats": "Stadegoù ar servijer :",
@@ -607,7 +618,6 @@
   "subscribed_languages.target": "Cheñch ar yezhoù koumanantet evit {target}",
   "tabs_bar.home": "Degemer",
   "tabs_bar.notifications": "Kemennoù",
-  "terms_of_service.title": "Divizoù implijout",
   "time_remaining.days": "{number, plural,one {# devezh} other {# a zevezh}} a chom",
   "time_remaining.hours": "{number, plural, one {# eurvezh} other{# eurvezh}} a chom",
   "time_remaining.minutes": "{number, plural, one {# munut} other{# a vunut}} a chom",
@@ -623,7 +633,21 @@
   "upload_button.label": "Ouzhpennañ ur media",
   "upload_error.limit": "Bevenn evit pellgargañ restroù a zo distremenet.",
   "upload_error.poll": "Pellgargañ restroù n'eo ket aotreet gant sontadegoù.",
+  "upload_form.audio_description": "Diskrivañ evit tud a zo kollet o c'hlev",
+  "upload_form.description": "Diskrivañ evit tud a zo kollet o gweled",
   "upload_form.edit": "Kemmañ",
+  "upload_form.thumbnail": "Kemmañ ar velvenn",
+  "upload_form.video_description": "Diskrivañ evit tud a zo kollet o gweled pe o c'hlev",
+  "upload_modal.analyzing_picture": "O tielfennañ ar skeudenn…",
+  "upload_modal.apply": "Arloañ",
+  "upload_modal.applying": "Oc'h arloañ…",
+  "upload_modal.choose_image": "Dibab ur skeudenn",
+  "upload_modal.description_placeholder": "Ul louarn gell mibin a zo o lammat a-zioc'h ar c'hi lezirek",
+  "upload_modal.detect_text": "Dinoiñ testenn diouzh ar skeudenn",
+  "upload_modal.edit_media": "Embann ar media",
+  "upload_modal.hint": "Klikit pe tennit ar c'helc'h war ar rakwel evit dibab al lerc'h kengreizel a vo gwelet atav war an holl melvennoù.",
+  "upload_modal.preparing_ocr": "Oc'h aozañ OCR…",
+  "upload_modal.preview_label": "Rakwel ({ratio})",
   "upload_progress.label": "O pellgargañ...",
   "upload_progress.processing": "War ober…",
   "username.taken": "Tapet eo an anv implijer-mañ dija. Klaskit skrivañ unan all",
@@ -633,6 +657,8 @@
   "video.expand": "Ledanaat ar video",
   "video.fullscreen": "Skramm a-bezh",
   "video.hide": "Kuzhat ar video",
+  "video.mute": "Paouez gant ar son",
   "video.pause": "Paouez",
-  "video.play": "Lenn"
+  "video.play": "Lenn",
+  "video.unmute": "Lakaat ar son en-dro"
 }
diff --git a/app/javascript/mastodon/locales/bs.json b/app/javascript/mastodon/locales/bs.json
index 9fbed54565..d06054ee58 100644
--- a/app/javascript/mastodon/locales/bs.json
+++ b/app/javascript/mastodon/locales/bs.json
@@ -11,6 +11,8 @@
   "compose_form.spoiler.marked": "Text is hidden behind warning",
   "compose_form.spoiler.unmarked": "Text is not hidden",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "empty_column.account_timeline": "No posts found",
   "empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
@@ -49,6 +51,19 @@
   "navigation_bar.domain_blocks": "Hidden domains",
   "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
   "notification.reblog": "{name} boosted your status",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "privacy.change": "Adjust status privacy",
   "report.placeholder": "Type or paste additional comments",
   "report.submit": "Submit report",
@@ -61,5 +76,8 @@
   "status.open": "Expand this status",
   "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "Uploading…"
 }
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 60e3066d0e..cf986f8205 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Edita el perfil",
   "account.enable_notifications": "Notifica'm els tuts de @{name}",
   "account.endorse": "Recomana en el perfil",
-  "account.featured": "Destacat",
-  "account.featured.hashtags": "Etiquetes",
-  "account.featured.posts": "Publicacions",
   "account.featured_tags.last_status_at": "Darrer tut el {date}",
   "account.featured_tags.last_status_never": "No hi ha tuts",
+  "account.featured_tags.title": "etiquetes destacades de {name}",
   "account.follow": "Segueix",
   "account.follow_back": "Segueix tu també",
   "account.followers": "Seguidors",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} publicació} other {{counter} publicacions}}",
   "account.unblock": "Desbloca @{name}",
   "account.unblock_domain": "Desbloca el domini {domain}",
-  "account.unblock_domain_short": "Desbloca",
   "account.unblock_short": "Desbloca",
   "account.unendorse": "No recomanis en el perfil",
   "account.unfollow": "Deixa de seguir",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "S'ha produït un error inesperat.",
   "alert.unexpected.title": "Vaja!",
   "alt_text_badge.title": "Text alternatiu",
-  "alt_text_modal.add_alt_text": "Afegiu text alternatiu",
-  "alt_text_modal.add_text_from_image": "Afegiu text d'una imatge",
-  "alt_text_modal.cancel": "Cancel·la",
-  "alt_text_modal.change_thumbnail": "Canvia la miniatura",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Descriviu això per a persones amb problemes d'audició…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Descriviu això per a persones amb problemes visuals…",
-  "alt_text_modal.done": "Fet",
   "announcement.announcement": "Anunci",
-  "annual_report.summary.archetype.booster": "Sempre a la moda",
-  "annual_report.summary.archetype.lurker": "Tot ho llegeix",
-  "annual_report.summary.archetype.oracle": "L'Oracle",
-  "annual_report.summary.archetype.pollster": "Tot són enquestes",
-  "annual_report.summary.archetype.replier": "Tot ho respon",
-  "annual_report.summary.followers.followers": "seguidors",
-  "annual_report.summary.followers.total": "{count} en total",
-  "annual_report.summary.here_it_is": "El repàs del vostre {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "la publicació més afavorida",
-  "annual_report.summary.highlighted_post.by_reblogs": "la publicació més impulsada",
-  "annual_report.summary.highlighted_post.by_replies": "la publicació amb més respostes",
-  "annual_report.summary.highlighted_post.possessive": "de {name}",
-  "annual_report.summary.most_used_app.most_used_app": "l'aplicació més utilitzada",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "l'etiqueta més utilitzada",
-  "annual_report.summary.most_used_hashtag.none": "Cap",
-  "annual_report.summary.new_posts.new_posts": "publicacions noves",
-  "annual_report.summary.percentile.text": "<topLabel>Que us posa al</topLabel><percentage></percentage><bottomLabel>capdamunt dels usuaris de {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "No li ho direm al Bernie.",
-  "annual_report.summary.thanks": "Gràcies per formar part de Mastodon!",
   "attachments_list.unprocessed": "(sense processar)",
   "audio.hide": "Amaga l'àudio",
   "block_modal.remote_users_caveat": "Li demanarem al servidor {domain} que respecti la vostra decisió, tot i que no podem garantir-ho, ja que alguns servidors gestionen de forma diferent els blocatges. És possible que els usuaris no autenticats puguin veure les publicacions públiques.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "No es pot trobar la pàgina sol·licitada. Segur que l'enllaç que has introduït és correcte?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Tanca",
-  "bundle_modal_error.message": "S'ha produït un error en carregar aquesta pantalla.",
+  "bundle_modal_error.message": "S'ha produït un error en carregar aquest component.",
   "bundle_modal_error.retry": "Torna-ho a provar",
   "closed_registrations.other_server_instructions": "Com que Mastodon és descentralitzat, pots crear un compte en un altre servidor i continuar interactuant amb aquest.",
   "closed_registrations_modal.description": "No es pot crear un compte a {domain} ara mateix, però tingues en compte que no necessites específicament un compte a {domain} per a usar Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Usuaris blocats",
   "column.bookmarks": "Marcadors",
   "column.community": "Línia de temps local",
-  "column.create_list": "Crea una llista",
   "column.direct": "Mencions privades",
   "column.directory": "Navega pels perfils",
   "column.domain_blocks": "Dominis blocats",
-  "column.edit_list": "Edita la llista",
   "column.favourites": "Favorits",
   "column.firehose": "Tuts en directe",
   "column.follow_requests": "Peticions de seguir-te",
   "column.home": "Inici",
-  "column.list_members": "Gestiona els membres de la llista",
   "column.lists": "Llistes",
   "column.mutes": "Usuaris silenciats",
   "column.notifications": "Notificacions",
@@ -172,7 +140,6 @@
   "column_header.pin": "Fixa",
   "column_header.show_settings": "Mostra la configuració",
   "column_header.unpin": "Desfixa",
-  "column_search.cancel": "Cancel·la",
   "column_subheading.settings": "Configuració",
   "community.column_settings.local_only": "Només local",
   "community.column_settings.media_only": "Només contingut",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Durada de l'enquesta",
   "compose_form.poll.multiple": "Opcions múltiples",
   "compose_form.poll.option_placeholder": "Opció {number}",
-  "compose_form.poll.single": "Única opció",
+  "compose_form.poll.single": "Trieu-ne una",
   "compose_form.poll.switch_to_multiple": "Canvia l’enquesta per a permetre múltiples opcions",
   "compose_form.poll.switch_to_single": "Canvia l’enquesta per a permetre una única opció",
   "compose_form.poll.type": "Estil",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Edita",
   "confirmations.edit.message": "Editant ara sobreescriuràs el missatge que estàs editant. Segur que vols continuar?",
   "confirmations.edit.title": "Sobreescriure la publicació?",
-  "confirmations.follow_to_list.confirm": "Seguir i afegir a una llista",
-  "confirmations.follow_to_list.message": "Cal seguir {name} per a afegir-lo a una llista.",
-  "confirmations.follow_to_list.title": "Seguir l'usuari?",
   "confirmations.logout.confirm": "Tanca la sessió",
   "confirmations.logout.message": "Segur que vols tancar la sessió?",
   "confirmations.logout.title": "Tancar la sessió?",
-  "confirmations.missing_alt_text.confirm": "Afegiu un text alternatiu",
-  "confirmations.missing_alt_text.message": "La vostra publicació té contingut sense text alternatiu. Afegir-hi descripcions la farà accessible a més persones.",
-  "confirmations.missing_alt_text.secondary": "Publica-la igualment",
-  "confirmations.missing_alt_text.title": "Hi voleu afegir text alternatiu?",
   "confirmations.mute.confirm": "Silencia",
   "confirmations.redraft.confirm": "Esborra i reescriu",
   "confirmations.redraft.message": "Segur que vols eliminar aquest tut i tornar a escriure'l? Es perdran tots els impulsos i els favorits, i les respostes al tut original quedaran aïllades.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "El teu compte {disabledAccount} està desactivat.",
   "dismissable_banner.community_timeline": "Aquests són els tuts públics més recents d'usuaris amb els seus comptes a {domain}.",
   "dismissable_banner.dismiss": "Ometre",
-  "dismissable_banner.explore_links": "Aquestes històries noves són les més compartides avui al Fedivers. Les històries noves publicades per més persones diferents es classifiquen amunt.",
-  "dismissable_banner.explore_statuses": "Aquestes publicacions d'arreu del Fedivers estan atraient l'atenció avui. Les publicacions noves amb més impulsos i favorits es classifiquen amunt.",
-  "dismissable_banner.explore_tags": "Aquestes etiquetes estan atraient l'atenció avui. Les etiquetes que fan servir més persones diferents es classifiquen amunt.",
-  "dismissable_banner.public_timeline": "Aquestes són les publicacions més recents al Fedivers que segueixen gent a {domain}.",
+  "dismissable_banner.explore_links": "Gent d'aquest i d'altres servidors de la xarxa descentralitzada estan comentant ara mateix aquestes notícies.",
+  "dismissable_banner.explore_statuses": "Aquests son els tuts de la xarxa descentralitzada que guanyen atenció ara mateix. Els tuts més nous amb més impulsos i favorits tenen millor rànquing.",
+  "dismissable_banner.explore_tags": "Aquestes etiquetes estan guanyant ara mateix l'atenció dels usuaris d'aquest i altres servidors de la xarxa descentralitzada.",
+  "dismissable_banner.public_timeline": "Aquests son els tuts públics més recents de les persones a la web social que les persones de {domain} segueixen.",
   "domain_block_modal.block": "Bloca el servidor",
   "domain_block_modal.block_account_instead": "En lloc d'això, bloca @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Els usuaris d'aquest servidor poden interactuar amb les vostres publicacions antigues.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Resultats de la cerca",
   "emoji_button.symbols": "Símbols",
   "emoji_button.travel": "Viatges i llocs",
-  "empty_column.account_featured": "Aquesta llista està buida",
   "empty_column.account_hides_collections": "Aquest usuari ha decidit no mostrar aquesta informació",
   "empty_column.account_suspended": "Compte suspès",
   "empty_column.account_timeline": "No hi ha tuts aquí!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Encara no hi ha res en aquesta etiqueta.",
   "empty_column.home": "La teva línia de temps és buida! Segueix més gent per a emplenar-la. {suggestions}",
   "empty_column.list": "Encara no hi ha res en aquesta llista. Quan els membres facin nous tuts, apareixeran aquí.",
+  "empty_column.lists": "Encara no tens cap llista. Quan en facis una, apareixerà aquí.",
   "empty_column.mutes": "Encara no has silenciat cap usuari.",
   "empty_column.notification_requests": "Tot net, ja no hi ha res aquí! Quan rebeu notificacions noves, segons la vostra configuració, apareixeran aquí.",
   "empty_column.notifications": "Encara no tens notificacions. Quan altre gent interactuï amb tu, les veuràs aquí.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Prova de desactivar-los i actualitza la pàgina. Si això no serveix, és possible que encara puguis fer servir Mastodon amb un altre navegador o una aplicació nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Copia stacktrace al porta-retalls",
   "errors.unexpected_crash.report_issue": "Informa d'un problema",
+  "explore.search_results": "Resultats de la cerca",
   "explore.suggested_follows": "Persones",
   "explore.title": "Explora",
   "explore.trending_links": "Notícies",
@@ -373,14 +334,13 @@
   "footer.about": "Quant a",
   "footer.directory": "Directori de perfils",
   "footer.get_app": "Aconsegueix l'app",
+  "footer.invite": "Convida persones",
   "footer.keyboard_shortcuts": "Dreceres de teclat",
   "footer.privacy_policy": "Política de privadesa",
   "footer.source_code": "Mostra el codi font",
   "footer.status": "Estat",
-  "footer.terms_of_service": "Condicions de servei",
   "generic.saved": "Desat",
   "getting_started.heading": "Primeres passes",
-  "hashtag.admin_moderation": "Obre la interfície de moderació per a #{name}",
   "hashtag.column_header.tag_mode.all": "i {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
   "hashtag.column_header.tag_mode.none": "sense {additional}",
@@ -394,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} tut} other {{counter} tuts}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} tut} other {{counter} tuts}} avui",
   "hashtag.follow": "Segueix l'etiqueta",
-  "hashtag.mute": "Silencia #{hashtag}",
   "hashtag.unfollow": "Deixa de seguir l'etiqueta",
   "hashtags.and_other": "…i {count, plural, other {# més}}",
   "hints.profiles.followers_may_be_missing": "Es poden haver perdut seguidors d'aquest perfil.",
@@ -423,22 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Voleu ignorar les notificacions de qui no us segueix?",
   "ignore_notifications_modal.not_following_title": "Voleu ignorar les notificacions de qui no seguiu?",
   "ignore_notifications_modal.private_mentions_title": "Voleu ignorar les notificacions de mencions privades no sol·licitades?",
-  "info_button.label": "Ajuda",
-  "interaction_modal.action.favourite": "Per a continuar heu d'afavorir des del vostre compte.",
-  "interaction_modal.action.follow": "Per a continuar heu de seguir des del vostre compte.",
-  "interaction_modal.action.reblog": "Per a continuar heu d'impulsar des del vostre compte.",
-  "interaction_modal.action.reply": "Per a continuar heu de respondre des del vostre compte.",
-  "interaction_modal.action.vote": "Per a continuar heu de votar des del vostre compte.",
-  "interaction_modal.go": "Endavant",
-  "interaction_modal.no_account_yet": "Encara no teniu cap compte?",
+  "interaction_modal.description.favourite": "Amb un compte a Mastodon pots afavorir aquest tut perquè l'autor sàpiga que t'ha agradat i desar-lo per a més endavant.",
+  "interaction_modal.description.follow": "Amb un compte a Mastodon, pots seguir a {name} per a rebre els seus tuts en la teva línia de temps d'Inici.",
+  "interaction_modal.description.reblog": "Amb un compte a Mastodon, pots impulsar aquest tut per a compartir-lo amb els teus seguidors.",
+  "interaction_modal.description.reply": "Amb un compte a Mastodon, pots respondre aquest tut.",
+  "interaction_modal.login.action": "Torna a l'inici",
+  "interaction_modal.login.prompt": "Domini del teu servidor domèstic, p.ex. mastodon.social",
+  "interaction_modal.no_account_yet": "No a Mastodon?",
   "interaction_modal.on_another_server": "A un altre servidor",
   "interaction_modal.on_this_server": "En aquest servidor",
+  "interaction_modal.sign_in": "No has iniciat sessió en aquest servidor. On tens el teu compte?",
+  "interaction_modal.sign_in_hint": "Ajuda: Aquesta és la web on vas registrar-te. Si no ho recordes, mira el correu electrònic de benvinguda en la teva safata d'entrada. També pots introduïr el teu nom d'usuari complet! (per ex. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Afavoreix el tut de {name}",
   "interaction_modal.title.follow": "Segueix {name}",
   "interaction_modal.title.reblog": "Impulsa el tut de {name}",
   "interaction_modal.title.reply": "Respon al tut de {name}",
-  "interaction_modal.title.vote": "Voteu l'enquesta de {name}",
-  "interaction_modal.username_prompt": "P. ex. {example}",
   "intervals.full.days": "{number, plural, one {# dia} other {# dies}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# hores}}",
   "intervals.full.minutes": "{number, plural, one {# minut} other {# minuts}}",
@@ -474,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Mostra/amaga el text marcat com a sensible",
   "keyboard_shortcuts.toggle_sensitivity": "Mostra/amaga contingut",
   "keyboard_shortcuts.toot": "Escriu un nou tut",
-  "keyboard_shortcuts.translate": "per a traduir una publicació",
   "keyboard_shortcuts.unfocus": "Descentra l'àrea de composició de text/cerca",
   "keyboard_shortcuts.up": "Apuja a la llista",
   "lightbox.close": "Tanca",
@@ -487,32 +444,20 @@
   "link_preview.author": "Per {name}",
   "link_preview.more_from_author": "Més de {name}",
   "link_preview.shares": "{count, plural, one {{counter} publicació} other {{counter} publicacions}}",
-  "lists.add_member": "Afegeix",
-  "lists.add_to_list": "Afegeix a la llista",
-  "lists.add_to_lists": "Afegeix {name} a les llistes",
-  "lists.create": "Crea",
-  "lists.create_a_list_to_organize": "Creeu una nova llista per a organitzar la pantalla d'inici",
-  "lists.create_list": "Crea una llista",
+  "lists.account.add": "Afegeix a la llista",
+  "lists.account.remove": "Elimina de la llista",
   "lists.delete": "Elimina la llista",
-  "lists.done": "Fet",
   "lists.edit": "Edita la llista",
-  "lists.exclusive": "Amaga membres a Inici",
-  "lists.exclusive_hint": "Si algú és a la llista, amagueu-los de la pantalla d'inici, per a no veure'n les publicacions duplicades.",
-  "lists.find_users_to_add": "Troba usuaris per a afegir",
-  "lists.list_members": "Membres de la llista",
-  "lists.list_members_count": "{count, plural, one {# membre} other {# membres}}",
-  "lists.list_name": "Nom de la llista",
-  "lists.new_list_name": "Nom de la nova llista",
-  "lists.no_lists_yet": "Encara no hi ha cap llista.",
-  "lists.no_members_yet": "Encara no hi ha membres.",
-  "lists.no_results_found": "No s'han trobat resultats.",
-  "lists.remove_member": "Elimina",
+  "lists.edit.submit": "Canvia el títol",
+  "lists.exclusive": "Amaga aquests tuts a Inici",
+  "lists.new.create": "Afegeix una llista",
+  "lists.new.title_placeholder": "Nou títol de la llista",
   "lists.replies_policy.followed": "Qualsevol usuari que segueixis",
   "lists.replies_policy.list": "Membres de la llista",
   "lists.replies_policy.none": "Ningú",
-  "lists.save": "Desa",
-  "lists.search": "Cerca",
-  "lists.show_replies_to": "Inclou respostes de membres de la llista a",
+  "lists.replies_policy.title": "Mostra respostes a:",
+  "lists.search": "Cerca entre les persones que segueixes",
+  "lists.subheading": "Les teves llistes",
   "load_pending": "{count, plural, one {# element nou} other {# elements nous}}",
   "loading_indicator.label": "Es carrega…",
   "media_gallery.hide": "Amaga",
@@ -561,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} ha reportat {target}",
   "notification.admin.sign_up": "{name} s'ha registrat",
   "notification.admin.sign_up.name_and_others": "{name} i {count, plural, one {# altre} other {# altres}} s'han registrat",
-  "notification.annual_report.message": "El vostre {year} #Wrapstodon t'espera. Desveleu els vostres moments més memorables a Mastodon!",
-  "notification.annual_report.view": "Visualitzeu #Wrapstodon",
   "notification.favourite": "{name} ha afavorit el teu tut",
   "notification.favourite.name_and_others_with_link": "{name} i <a>{count, plural, one {# altre} other {# altres}}</a> han afavorit la vostra publicació",
-  "notification.favourite_pm": "{name} ha afavorit la vostra menció privada",
-  "notification.favourite_pm.name_and_others_with_link": "{name} i <a>{count, plural, one {un altre} other {# altres}}</a> han afavorit la vostra menció",
   "notification.follow": "{name} et segueix",
   "notification.follow.name_and_others": "{name} i <a>{count, plural, one {# altre} other {# altres}}</a> us han seguit",
   "notification.follow_request": "{name} ha sol·licitat de seguir-te",
@@ -671,21 +612,44 @@
   "notifications_permission_banner.enable": "Activa les notificacions d’escriptori",
   "notifications_permission_banner.how_to_control": "Per a rebre notificacions quan Mastodon no és obert cal activar les notificacions d’escriptori. Pots controlar amb precisió quins tipus d’interaccions generen notificacions d’escriptori després d’activar el botó {icon} de dalt.",
   "notifications_permission_banner.title": "No et perdis mai res",
-  "onboarding.follows.back": "Enrere",
-  "onboarding.follows.done": "Fet",
+  "onboarding.action.back": "Porta'm enrere",
+  "onboarding.actions.back": "Porta'm enrere",
+  "onboarding.actions.go_to_explore": "Mira què és tendència",
+  "onboarding.actions.go_to_home": "Aneu a la vostra pantalla d'inici",
+  "onboarding.compose.template": "Hola Mastodon!",
   "onboarding.follows.empty": "Malauradament, cap resultat pot ser mostrat ara mateix. Pots provar de fer servir la cerca o visitar la pàgina Explora per a trobar gent a qui seguir o provar-ho de nou més tard.",
-  "onboarding.follows.search": "Cerca",
-  "onboarding.follows.title": "Seguiu gent per a començar",
+  "onboarding.follows.lead": "La vostra pantalla d'inici és la manera principal d'experimentar Mastodon. Com més gent seguiu, més activa i interessant serà. Per a començar, alguns suggeriments:",
+  "onboarding.follows.title": "Personalitzeu la pantalla d'inci",
   "onboarding.profile.discoverable": "Fes el meu perfil descobrible",
   "onboarding.profile.discoverable_hint": "En acceptar d'ésser descobert a Mastodon els teus missatges poden aparèixer dins les tendències i els resultats de cerques, i el teu perfil es pot suggerir a qui tingui interessos semblants als teus.",
   "onboarding.profile.display_name": "Nom que es mostrarà",
   "onboarding.profile.display_name_hint": "El teu nom complet o el teu malnom…",
+  "onboarding.profile.lead": "Sempre ho pots completar més endavant a la configuració, on hi ha encara més opcions disponibles.",
   "onboarding.profile.note": "Biografia",
   "onboarding.profile.note_hint": "Pots @mencionar altra gent o #etiquetes…",
   "onboarding.profile.save_and_continue": "Desa i continua",
   "onboarding.profile.title": "Configuració del perfil",
   "onboarding.profile.upload_avatar": "Importa una foto de perfil",
   "onboarding.profile.upload_header": "Importa una capçalera de perfil",
+  "onboarding.share.lead": "Permet que la gent sàpiga com trobar-te a Mastodon!",
+  "onboarding.share.message": "Sóc {username} a #Mastodon! Vine i segueix-me a {url}",
+  "onboarding.share.next_steps": "Possibles passes següents:",
+  "onboarding.share.title": "Comparteix el teu perfil",
+  "onboarding.start.lead": "El teu nou compte ja està preparat a Mastodon, la xarxa social on tu—no un algorisme—té tot el control. Aquí tens com en pots treure tot el suc:",
+  "onboarding.start.skip": "Vols saltar-te tota la resta?",
+  "onboarding.start.title": "Llestos!",
+  "onboarding.steps.follow_people.body": "Mastodon va de seguir a gent interessant.",
+  "onboarding.steps.follow_people.title": "Personalitzeu la pantalla d'inici",
+  "onboarding.steps.publish_status.body": "Saluda al món amb text, fotos, vídeos o enquestes {emoji}",
+  "onboarding.steps.publish_status.title": "Fes el teu primer tut",
+  "onboarding.steps.setup_profile.body": "És més fàcil que altres interactuïn amb tu si tens un perfil complet.",
+  "onboarding.steps.setup_profile.title": "Personalitza el perfil",
+  "onboarding.steps.share_profile.body": "Fer saber als teus amics com trobar-te a Mastodon",
+  "onboarding.steps.share_profile.title": "Comparteix el teu perfil",
+  "onboarding.tips.2fa": "<strong>Ho sabies?</strong> Pots securitzar el teu compte activant l'autenticació de doble factor en la configuració del teu perfil. Funciona amb qualsevol aplicació TOTP de la teva elecció, no cal número de telèfon!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Ho sabies?</strong> Com Mastodon és descentralitzat, et pots trobar amb perfils que són a servidors diferents del teu. I, tanmateix, també hi pots interactuar sense cap problema! El servidor és la segona part del seu nom d'usuari!",
+  "onboarding.tips.migration": "<strong>Ho sabies?</strong> Si et sembla que {domain} no és una bona elecció de servidor per a tu en el futur, pots moure't a un altre servidor Mastodon sense perdre els teus seguidors. Fins i tot pots tenir el teu propi servidor!",
+  "onboarding.tips.verification": "<strong>Ho sabies?</strong> Pots verificar el teu compte posant un enllaç al teu perfil a Mastodon en la teva pàgina web i afegint la adreça d'aquesta web en el teu perfil. Sense cap mena de tarifa o document!",
   "password_confirmation.exceeds_maxlength": "La confirmació de la contrasenya excedeix la longitud màxima",
   "password_confirmation.mismatching": "La confirmació de contrasenya no és coincident",
   "picture_in_picture.restore": "Retorna’l",
@@ -701,7 +665,7 @@
   "poll_button.remove_poll": "Elimina l'enquesta",
   "privacy.change": "Canvia la privacitat del tut",
   "privacy.direct.long": "Tothom mencionat a la publicació",
-  "privacy.direct.short": "Menció privada",
+  "privacy.direct.short": "Persones concretes",
   "privacy.private.long": "Només els vostres seguidors",
   "privacy.private.short": "Seguidors",
   "privacy.public.long": "Tothom dins o fora Mastodon",
@@ -713,8 +677,8 @@
   "privacy_policy.title": "Política de privadesa",
   "recommended": "Recomanat",
   "refresh": "Actualitza",
-  "regeneration_indicator.please_stand_by": "Espereu.",
-  "regeneration_indicator.preparing_your_home_feed": "Pantalla d'inici en preparació…",
+  "regeneration_indicator.label": "Es carrega…",
+  "regeneration_indicator.sublabel": "Es prepara la vostra pantalla d'Inici!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "fa {number, plural, one {# dia} other {# dies}}",
   "relative_time.full.hours": "fa {number, plural, one {# hora} other {# hores}}",
@@ -798,11 +762,10 @@
   "search_results.accounts": "Perfils",
   "search_results.all": "Tots",
   "search_results.hashtags": "Etiquetes",
-  "search_results.no_results": "Cap resultat.",
-  "search_results.no_search_yet": "Proveu de cercar publicacions, perfils o etiquetes.",
+  "search_results.nothing_found": "No s'ha pogut trobar res per a aquests termes de cerca",
   "search_results.see_all": "Veure'ls tots",
   "search_results.statuses": "Tuts",
-  "search_results.title": "Cerca de “{q}”",
+  "search_results.title": "Cerca de {q}",
   "server_banner.about_active_users": "Gent que ha fet servir aquest servidor en els darrers 30 dies (Usuaris Actius Mensuals)",
   "server_banner.active_users": "usuaris actius",
   "server_banner.administered_by": "Administrat per:",
@@ -854,7 +817,6 @@
   "status.reblogs.empty": "Encara no ha impulsat ningú aquest tut. Quan algú ho faci, apareixerà aquí.",
   "status.redraft": "Esborra i reescriu",
   "status.remove_bookmark": "Elimina el marcador",
-  "status.remove_favourite": "Elimina dels preferits",
   "status.replied_in_thread": "Respost al fil",
   "status.replied_to": "En resposta a {name}",
   "status.reply": "Respon",
@@ -876,9 +838,6 @@
   "subscribed_languages.target": "Canvia les llengües subscrites per a {target}",
   "tabs_bar.home": "Inici",
   "tabs_bar.notifications": "Notificacions",
-  "terms_of_service.effective_as_of": "En vigor a partir de {date}",
-  "terms_of_service.title": "Condicions de servei",
-  "terms_of_service.upcoming_changes_on": "Propers canvis el {date}",
   "time_remaining.days": "{number, plural, one {# dia restant} other {# dies restants}}",
   "time_remaining.hours": "{number, plural, one {# hora restant} other {# hores restants}}",
   "time_remaining.minutes": "{number, plural, one {# minut restant} other {# minuts restants}}",
@@ -894,12 +853,26 @@
   "upload_button.label": "Afegeix imatges, un vídeo o un fitxer d'àudio",
   "upload_error.limit": "S'ha superat el límit de càrrega d'arxius.",
   "upload_error.poll": "No es permet carregar fitxers a les enquestes.",
+  "upload_form.audio_description": "Descriu-ho per a persones amb problemes d'audició",
+  "upload_form.description": "Descriu-ho per a persones amb problemes de visió",
   "upload_form.drag_and_drop.instructions": "Per a agafar un fitxer multimèdia adjunt, premeu l'espai o la tecla Enter. Mentre l'arrossegueu, utilitzeu les fletxes per a moure l'adjunt en qualsevol direcció. Premeu espai o Enter un altre cop per a deixar-lo anar a la seva nova posició, o premeu la tecla d'escapament per cancel·lar.",
   "upload_form.drag_and_drop.on_drag_cancel": "S'ha cancel·lat l'arrossegament. S'ha deixat anar l'adjunt multimèdia {item}.",
   "upload_form.drag_and_drop.on_drag_end": "S'ha deixat anar l'adjunt multimèdia {item}.",
   "upload_form.drag_and_drop.on_drag_over": "S'ha mogut l'adjunt multimèdia {item}.",
   "upload_form.drag_and_drop.on_drag_start": "S'ha agafat l'adjunt multimèdia {item}.",
   "upload_form.edit": "Edita",
+  "upload_form.thumbnail": "Canvia la miniatura",
+  "upload_form.video_description": "Descriu-ho per a persones amb problemes de visió o audició",
+  "upload_modal.analyzing_picture": "S'analitza la imatge…",
+  "upload_modal.apply": "Aplica",
+  "upload_modal.applying": "S'aplica…",
+  "upload_modal.choose_image": "Tria la imatge",
+  "upload_modal.description_placeholder": "Setze jutges d'un jutjat mengen fetge d'un penjat",
+  "upload_modal.detect_text": "Detecta el text de la imatge",
+  "upload_modal.edit_media": "Edita el Mèdia",
+  "upload_modal.hint": "Fes clic o arrossega el cercle en la previsualització per a triar el punt focal que sempre serà visible en totes les miniatures.",
+  "upload_modal.preparing_ocr": "Es prepara l'OCR…",
+  "upload_modal.preview_label": "Previsualitza ({ratio})",
   "upload_progress.label": "Pujant...",
   "upload_progress.processing": "En procés…",
   "username.taken": "Aquest nom d'usuari ja està agafat. Prova un altre",
@@ -912,9 +885,5 @@
   "video.mute": "Silencia",
   "video.pause": "Pausa",
   "video.play": "Reprodueix",
-  "video.skip_backward": "Salta enrere",
-  "video.skip_forward": "Salta endavant",
-  "video.unmute": "Deixa de silenciar",
-  "video.volume_down": "Abaixa el volum",
-  "video.volume_up": "Apuja el volum"
+  "video.unmute": "Activa el so"
 }
diff --git a/app/javascript/mastodon/locales/ckb.json b/app/javascript/mastodon/locales/ckb.json
index 31f2dbbc11..bea6e5ceec 100644
--- a/app/javascript/mastodon/locales/ckb.json
+++ b/app/javascript/mastodon/locales/ckb.json
@@ -28,6 +28,7 @@
   "account.endorse": "ناساندن لە پرۆفایل",
   "account.featured_tags.last_status_at": "دوایین پۆست لە {date}",
   "account.featured_tags.last_status_never": "هیچ پۆستێک نییە",
+  "account.featured_tags.title": "هاشتاگە تایبەتەکانی {name}",
   "account.follow": "بەدواداچوون",
   "account.follow_back": "فۆڵۆو بکەنەوە",
   "account.followers": "شوێنکەوتووان",
@@ -94,6 +95,7 @@
   "bundle_column_error.routing.body": "پەیجی داواکراو ناتوانرێت بدۆزرێتەوە. ئایا دڵنیای کە URL ی ناو ناونیشانەکان ڕاستە?",
   "bundle_column_error.routing.title": "٤٠٤",
   "bundle_modal_error.close": "داخستن",
+  "bundle_modal_error.message": "هەڵەیەک ڕوویدا لەکاتی بارکردنی ئەم پێکهاتەیە.",
   "bundle_modal_error.retry": "دووبارە تاقی بکەوە",
   "closed_registrations.other_server_instructions": "بەو پێیەی ماستۆدۆن لامەرکەزییە، دەتوانیت ئەکاونتێک لەسەر سێرڤەرێکی تر دروست بکەیت و هێشتا کارلێک لەگەڵ ئەم سێرڤەرەدا بکەیت.",
   "closed_registrations_modal.description": "دروستکردنی ئەکاونت لەسەر {domain} لە ئێستادا ناتوانرێت، بەڵام تکایە ئەوەت لەبەرچاو بێت کە پێویستت بە ئەکاونتێک نییە بە تایبەتی لەسەر {domain} بۆ بەکارهێنانی ماستۆدۆن.",
@@ -141,6 +143,7 @@
   "compose_form.poll.duration": "ماوەی ڕاپرسی",
   "compose_form.poll.multiple": "فرە هەڵبژاردە",
   "compose_form.poll.option_placeholder": "بژاردەی {number}",
+  "compose_form.poll.single": "یەکێك هەلبژێرە",
   "compose_form.poll.switch_to_multiple": "ڕاپرسی بگۆڕە بۆ ڕێگەدان بە چەند هەڵبژاردنێک",
   "compose_form.poll.switch_to_single": "گۆڕینی ڕاپرسی بۆ ڕێگەدان بە تاکە هەڵبژاردنێک",
   "compose_form.poll.type": "ستایڵ",
@@ -185,6 +188,9 @@
   "disabled_account_banner.text": "ئەکاونتەکەت {disabledAccount} لە ئێستادا لەکارخراوە.",
   "dismissable_banner.community_timeline": "ئەمانە دوایین پۆستی گشتی ئەو کەسانەن کە ئەکاونتەکانیان لەلایەن {domain}ەوە هۆست کراوە.",
   "dismissable_banner.dismiss": "بەلاوە نان",
+  "dismissable_banner.explore_links": "ئەم هەواڵانە لە ئێستادا لەلایەن کەسانێکەوە لەسەر ئەم سێرڤەرە و سێرڤەرەکانی تری تۆڕی لامەرکەزی باس دەکرێن.",
+  "dismissable_banner.explore_statuses": "ئەمانە پۆستەکانن لە سەرانسەری وێبی کۆمەڵایەتی کە ئەمڕۆ کێشکردنیان بەدەستهێناوە. پۆستە نوێیەکان کە بووست و فەڤریتی زیاتریان هەیە ڕیزبەندی بەرزتریان هەیە.",
+  "dismissable_banner.explore_tags": "ئەم هاشتاگانە لە ئێستادا لە نێو خەڵکی سەر ئەم سێرڤەرە و سێرڤەرەکانی تری تۆڕی لامەرکەزیدا جێگەی خۆیان دەگرن.",
   "embed.instructions": "ئەم توتە بنچین بکە لەسەر وێب سایتەکەت بە کۆپیکردنی کۆدەکەی خوارەوە.",
   "embed.preview": "ئەمە ئەو شتەیە کە لە شێوەی خۆی دەچێت:",
   "emoji_button.activity": "چالاکی",
@@ -216,6 +222,7 @@
   "empty_column.hashtag": "هێشتا هیچ شتێک لەم هاشتاگەدا نییە.",
   "empty_column.home": "تایم لاینی ماڵەوەت بەتاڵە! سەردانی {public} بکە یان گەڕان بەکاربێنە بۆ دەستپێکردن و بینینی بەکارهێنەرانی تر.",
   "empty_column.list": "هێشتا هیچ شتێک لەم لیستەدا نییە. کاتێک ئەندامانی ئەم لیستە دەنگی نوێ بڵاودەکەن، لێرە دەردەکەون.",
+  "empty_column.lists": "تۆ هێشتا هیچ لیستت دروست نەکردووە، کاتێک دانەیەک دروست دەکەیت، لێرە پیشان دەدرێت.",
   "empty_column.mutes": "تۆ هێشتا هیچ بەکارهێنەرێکت بێدەنگ نەکردووە.",
   "empty_column.notifications": "تۆ هێشتا هیچ ئاگانامێکت نیە. چالاکی لەگەڵ کەسانی دیکە بکە بۆ دەستپێکردنی گفتوگۆکە.",
   "empty_column.public": "لێرە هیچ نییە! شتێک بە ئاشکرا بنووسە(بەگشتی)، یان بە دەستی شوێن بەکارهێنەران بکەوە لە ڕاژەکانی ترەوە بۆ پڕکردنەوەی",
@@ -225,6 +232,7 @@
   "error.unexpected_crash.next_steps_addons": "هەوڵدە لەکاریان بخەیت و لاپەڕەکە تازە بکەوە. ئەگەر ئەمە یارمەتیدەر نەبوو، لەوانەیە هێشتا بتوانیت ماستۆدۆن بەکاربێنیت لە ڕێگەی وێبگەڕەکانی دیکە یان نەرمەکالاکانی ئەسڵی.",
   "errors.unexpected_crash.copy_stacktrace": "کۆپیکردنی ستێکتراسی بۆ کلیپ بۆرد",
   "errors.unexpected_crash.report_issue": "کێشەی گوزارشت",
+  "explore.search_results": "ئەنجامەکانی گەڕان",
   "explore.suggested_follows": "خەڵک",
   "explore.title": "گەڕان",
   "explore.trending_links": "هەواڵەکان",
@@ -260,6 +268,7 @@
   "footer.about": "دەربارە",
   "footer.directory": "ڕابەری پەڕەی ناساندن",
   "footer.get_app": "بەرنامەکە بەدەست بێنە",
+  "footer.invite": "بانگهێشتکردنی خەڵک",
   "footer.keyboard_shortcuts": "کورتەڕێکانی تەختەکلیک",
   "footer.privacy_policy": "سیاسەتی تایبەتمەندێتی",
   "footer.source_code": "پیشاندانی کۆدی سەرچاوە",
@@ -282,6 +291,9 @@
   "home.column_settings.show_replies": "وەڵامدانەوەکان پیشان بدە",
   "home.hide_announcements": "شاردنەوەی راگەیەنراوەکان",
   "home.show_announcements": "پیشاندانی راگەیەنراوەکان",
+  "interaction_modal.description.follow": "بە هەژمارێک لەسەر ماستدۆن، ئەتوانیت شوێن {name} بکەویت بۆ ئەوەی بڵاوکراوەکانی بگاتە پەڕەی سەرەکیت.",
+  "interaction_modal.description.reblog": "بە هەژمارێک لەسەر ماستدۆن، ئەتوانیت ئەم بڵاوکراوەیە بەرزبکەیتەوە تاوەکو بەژداری پێبکەیت لەگەل شوێنکەوتوانت.",
+  "interaction_modal.description.reply": "بە هەژمارێک لەسەر ماستدۆن، ئەتوانیت وەڵامی ئەم بڵاوکراوەیە بدەیتەوە.",
   "interaction_modal.on_another_server": "لەسەر ڕاژەیەکی جیا",
   "interaction_modal.on_this_server": "لەسەر ئەم ڕاژەیە",
   "interaction_modal.title.follow": "دوای {name} بکەوە",
@@ -327,11 +339,19 @@
   "lightbox.previous": "پێشوو",
   "limited_account_hint.action": "بەهەر حاڵ پڕۆفایلی پیشان بدە",
   "limited_account_hint.title": "ئەم پرۆفایلە لەلایەن بەڕێوەبەرانی {domain} شاراوەتەوە.",
+  "lists.account.add": "زیادکردن بۆ لیست",
+  "lists.account.remove": "لابردن لە لیست",
   "lists.delete": "سڕینەوەی لیست",
   "lists.edit": "دەستکاری لیست",
+  "lists.edit.submit": "گۆڕینی ناونیشان",
+  "lists.new.create": "زیادکردنی لیست",
+  "lists.new.title_placeholder": "ناونیشانی لیستی نوێ",
   "lists.replies_policy.followed": "هەر بەکارهێنەرێکی بەدواکەوتوو",
   "lists.replies_policy.list": "ئەندامانی لیستەکە",
   "lists.replies_policy.none": "هیچکەس",
+  "lists.replies_policy.title": "پیشاندانی وەڵامەکان بۆ:",
+  "lists.search": "بگەڕێ لەناو ئەو کەسانەی کە شوێنیان کەوتویت",
+  "lists.subheading": "لیستەکانت",
   "load_pending": "{count, plural, one {# بەڕگەی نوێ} other {# بەڕگەی نوێ}}",
   "moved_to_account_banner.text": "ئەکاونتەکەت {disabledAccount} لە ئێستادا لەکارخراوە چونکە تۆ چوویتە {movedToAccount}.",
   "navigation_bar.about": "دەربارە",
@@ -397,6 +417,19 @@
   "notifications_permission_banner.enable": "چالاککردنی ئاگانامەکانی دێسکتۆپ",
   "notifications_permission_banner.how_to_control": "بۆ وەرگرتنی ئاگانامەکان کاتێک ماستۆدۆن نەکراوەیە، ئاگانامەکانی دێسکتۆپ چالاک بکە. دەتوانیت بە وردی کۆنترۆڵی جۆری کارلێکەکان بکەیت کە ئاگانامەکانی دێسکتۆپ دروست دەکەن لە ڕێگەی دوگمەی {icon} لەسەرەوە کاتێک چالاک دەکرێن.",
   "notifications_permission_banner.title": "هەرگیز شتێک لە دەست مەدە",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "بیگەڕێنەوە",
   "poll.closed": "دابخە",
   "poll.refresh": "نوێکردنەوە",
@@ -412,6 +445,8 @@
   "privacy_policy.last_updated": "دوایین نوێکردنەوە {date}",
   "privacy_policy.title": "سیاسەتی تایبەتێتی",
   "refresh": "نوێکردنەوە",
+  "regeneration_indicator.label": "بارکردن…",
+  "regeneration_indicator.sublabel": "ڕاگەیەنەری ماڵەوەت ئامادە دەکرێت!",
   "relative_time.days": "{number}ڕۆژ",
   "relative_time.full.days": "{number, plural, one {# ڕۆژ} other {# ڕۆژ}} ماوە",
   "relative_time.full.hours": "{number, plural, one {# کاتژمێر} other {# کاتژمێر}} ماوە",
@@ -479,7 +514,9 @@
   "search_results.accounts": "پرۆفایلەکان",
   "search_results.all": "هەموو",
   "search_results.hashtags": "هەشتاگ",
+  "search_results.nothing_found": "هیچ بۆ ئەم زاراوە گەڕانانە نەدۆزراوەتەوە",
   "search_results.statuses": "توتەکان",
+  "search_results.title": "گەڕان بەدوای {q}",
   "server_banner.about_active_users": "ئەو کەسانەی لە ماوەی ٣٠ ڕۆژی ڕابردوودا ئەم سێرڤەرە بەکاردەهێنن (بەکارهێنەرانی چالاک مانگانە)",
   "server_banner.active_users": "بەکارهێنەرانی چالاک",
   "server_banner.administered_by": "بەڕێوەبردن لەلایەن:",
@@ -553,7 +590,21 @@
   "upload_button.label": "وێنە، ڤیدیۆ یان پەیامی دەنگی زیاد بکە",
   "upload_error.limit": "سنووری بەرزکردنەوەت بەزاندووە.",
   "upload_error.poll": "فایل و ڕاپرسی پێکەوە ڕێپێنەدراون.",
+  "upload_form.audio_description": "پەیامەکەت بۆ نابیستەکان",
+  "upload_form.description": "پەیامەکەت بۆ نابیناکان",
   "upload_form.edit": "دەستکاری",
+  "upload_form.thumbnail": "گۆڕانی وینۆچکە",
+  "upload_form.video_description": "پەیامەکەت بۆ نابیست و نابیناکان",
+  "upload_modal.analyzing_picture": "وێنەکە شی دەکرێتەوە…",
+  "upload_modal.apply": "بیسەپێنە",
+  "upload_modal.applying": "داواکاری…",
+  "upload_modal.choose_image": "وێنە هەڵبژێرە",
+  "upload_modal.description_placeholder": "بە دڵ کەین با بە نەشئەی مەی غوباری میحنەتی دونیا",
+  "upload_modal.detect_text": "نووسینی ناو وێنەکە دەستنیشان بکە",
+  "upload_modal.edit_media": "دەستکاریکردنی میدیا",
+  "upload_modal.hint": "گەر وێنە چکۆلە یان بڕاوەبێت، خاڵی ناوەندی دیار دەکەوێت. خاڵی ناوەندی وێنە بە کرتە یان جێبەجیکردنی رێکبخەن.",
+  "upload_modal.preparing_ocr": "نووسینەکە دەستنیشان دەکرێت…",
+  "upload_modal.preview_label": "پێشبینین ({ratio})",
   "upload_progress.label": "بار دەکرێت...",
   "upload_progress.processing": "جێبەجێکردن...",
   "video.close": "داخستنی ڤیدیۆ",
@@ -562,6 +613,8 @@
   "video.expand": "ڤیدیۆفراوان بکە",
   "video.fullscreen": "پڕپیشانگەر",
   "video.hide": "شاردنەوەی ڤیدیۆ",
+  "video.mute": "دەنگی کپ",
   "video.pause": "وەستان",
-  "video.play": "لێی بدە"
+  "video.play": "لێی بدە",
+  "video.unmute": "بێدەنگی مەکە"
 }
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index c61b8484b4..043061769b 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -43,6 +43,7 @@
   "boost_modal.combo": "Pudete appughjà nant'à {combo} per saltà quessa a prussima volta",
   "bundle_column_error.retry": "Pruvà torna",
   "bundle_modal_error.close": "Chjudà",
+  "bundle_modal_error.message": "C'hè statu un prublemu caricandu st'elementu.",
   "bundle_modal_error.retry": "Pruvà torna",
   "column.blocks": "Utilizatori bluccati",
   "column.bookmarks": "Segnalibri",
@@ -102,6 +103,8 @@
   "directory.local": "Solu da {domain}",
   "directory.new_arrivals": "Ultimi arrivi",
   "directory.recently_active": "Attività ricente",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Integrà stu statutu à u vostru situ cù u codice quì sottu.",
   "embed.preview": "Hà da parè à quessa:",
   "emoji_button.activity": "Attività",
@@ -129,6 +132,7 @@
   "empty_column.hashtag": "Ùn c'hè ancu nunda quì.",
   "empty_column.home": "A vostr'accolta hè viota! Pudete andà nant'à {public} o pruvà a ricerca per truvà parsone da siguità.",
   "empty_column.list": "Ùn c'hè ancu nunda quì. Quandu membri di sta lista manderanu novi statuti, i vidarete quì.",
+  "empty_column.lists": "Ùn avete manc'una lista. Quandu farete una, sarà mustrata quì.",
   "empty_column.mutes": "Per avà ùn avete manc'un utilizatore piattatu.",
   "empty_column.notifications": "Ùn avete ancu nisuna nutificazione. Interact with others to start the conversation.",
   "empty_column.public": "Ùn c'hè nunda quì! Scrivete qualcosa in pubblicu o seguitate utilizatori d'altri servori per empie a linea pubblica",
@@ -194,11 +198,19 @@
   "lightbox.close": "Chjudà",
   "lightbox.next": "Siguente",
   "lightbox.previous": "Pricidente",
+  "lists.account.add": "Aghjunghje à a lista",
+  "lists.account.remove": "Toglie di a lista",
   "lists.delete": "Toglie a lista",
   "lists.edit": "Mudificà a lista",
+  "lists.edit.submit": "Cambià u titulu",
+  "lists.new.create": "Aghjunghje",
+  "lists.new.title_placeholder": "Titulu di a lista",
   "lists.replies_policy.followed": "Tutti i vostri abbunamenti",
   "lists.replies_policy.list": "Membri di a lista",
   "lists.replies_policy.none": "Nimu",
+  "lists.replies_policy.title": "Vede e risposte à:",
+  "lists.search": "Circà indè i vostr'abbunamenti",
+  "lists.subheading": "E vo liste",
   "load_pending": "{count, plural, one {# entrata nova} other {# entrate nove}}",
   "navigation_bar.blocks": "Utilizatori bluccati",
   "navigation_bar.bookmarks": "Segnalibri",
@@ -250,6 +262,19 @@
   "notifications_permission_banner.enable": "Attivà e nutificazione nant'à l'urdinatore",
   "notifications_permission_banner.how_to_control": "Per riceve nutificazione quandu Mastodon ùn hè micca aperta, attivate e nutificazione nant'à l'urdinatore. Pudete decide quali tippi d'interazione anu da mandà ste nutificazione cù u buttone {icon} quì sopra quandu saranu attivate.",
   "notifications_permission_banner.title": "Ùn mancate mai nunda",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "Rimette in piazza",
   "poll.closed": "Chjosu",
   "poll.refresh": "Attualizà",
@@ -263,6 +288,8 @@
   "privacy.change": "Mudificà a cunfidenzialità di u statutu",
   "privacy.public.short": "Pubblicu",
   "refresh": "Attualizà",
+  "regeneration_indicator.label": "Caricamentu…",
+  "regeneration_indicator.sublabel": "Priparazione di a vostra pagina d'accolta!",
   "relative_time.days": "{number}ghj",
   "relative_time.hours": "{number}o",
   "relative_time.just_now": "avà",
@@ -333,7 +360,21 @@
   "upload_button.label": "Aghjunghje un media",
   "upload_error.limit": "Limita di caricamentu di fugliali trapassata.",
   "upload_error.poll": "Ùn si pò micca caricà fugliali cù i scandagli.",
+  "upload_form.audio_description": "Discrizzione per i ciochi",
+  "upload_form.description": "Discrizzione per i malvistosi",
   "upload_form.edit": "Mudificà",
+  "upload_form.thumbnail": "Cambià vignetta",
+  "upload_form.video_description": "Discrizzione per i ciochi o cechi",
+  "upload_modal.analyzing_picture": "Analisi di u ritrattu…",
+  "upload_modal.apply": "Affettà",
+  "upload_modal.applying": "Appiegazione…",
+  "upload_modal.choose_image": "Cambià ritrattu",
+  "upload_modal.description_placeholder": "Chì tempi brevi ziu, quandu solfeghji",
+  "upload_modal.detect_text": "Ditettà testu da u ritrattu",
+  "upload_modal.edit_media": "Cambià media",
+  "upload_modal.hint": "Cliccate o sguillate u chjerchju nant'à a vista per sceglie u puntu fucale chì sarà sempre in vista indè tutte e miniature.",
+  "upload_modal.preparing_ocr": "Priparazione di l'OCR…",
+  "upload_modal.preview_label": "Vista ({ratio})",
   "upload_progress.label": "Caricamentu...",
   "video.close": "Chjudà a video",
   "video.download": "Scaricà fugliale",
@@ -341,6 +382,8 @@
   "video.expand": "Ingrandà a video",
   "video.fullscreen": "Pienu screnu",
   "video.hide": "Piattà a video",
+  "video.mute": "Surdina",
   "video.pause": "Pausa",
-  "video.play": "Lettura"
+  "video.play": "Lettura",
+  "video.unmute": "Caccià a surdina"
 }
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index 0d8653d412..c1384407f5 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Upravit profil",
   "account.enable_notifications": "Oznamovat mi příspěvky @{name}",
   "account.endorse": "Zvýraznit na profilu",
-  "account.featured": "Doporučené",
-  "account.featured.hashtags": "Hashtagy",
-  "account.featured.posts": "Příspěvky",
   "account.featured_tags.last_status_at": "Poslední příspěvek {date}",
   "account.featured_tags.last_status_never": "Žádné příspěvky",
+  "account.featured_tags.title": "Hlavní hashtagy uživatele {name}",
   "account.follow": "Sledovat",
   "account.follow_back": "Také sledovat",
   "account.followers": "Sledující",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} many {{counter} příspěvků} other {{counter} příspěvků}}",
   "account.unblock": "Odblokovat @{name}",
   "account.unblock_domain": "Odblokovat doménu {domain}",
-  "account.unblock_domain_short": "Odblokovat",
   "account.unblock_short": "Odblokovat",
   "account.unendorse": "Nezvýrazňovat na profilu",
   "account.unfollow": "Přestat sledovat",
@@ -82,40 +79,14 @@
   "admin.dashboard.retention.cohort_size": "Noví uživatelé",
   "admin.impact_report.instance_accounts": "Profily účtů, které by byli odstaněny",
   "admin.impact_report.instance_followers": "Sledující, o které by naši uživatelé přišli",
-  "admin.impact_report.instance_follows": "Sledující, o které by jejich uživatelé přišli",
+  "admin.impact_report.instance_follows": "Sledující, o které by naši uživatelé přišli",
   "admin.impact_report.title": "Shrnutí dopadu",
   "alert.rate_limited.message": "Zkuste to prosím znovu po {retry_time, time, medium}.",
   "alert.rate_limited.title": "Spojení omezena",
   "alert.unexpected.message": "Objevila se neočekávaná chyba.",
   "alert.unexpected.title": "Jejda!",
   "alt_text_badge.title": "Popisek",
-  "alt_text_modal.add_alt_text": "Přidat popisek",
-  "alt_text_modal.add_text_from_image": "Přidat text z obrázku",
-  "alt_text_modal.cancel": "Zrušit",
-  "alt_text_modal.change_thumbnail": "Změnit náhled",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Popište to pro osoby se sluchovým postižením…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Popište to pro osoby se zrakovým postižením…",
-  "alt_text_modal.done": "Hotovo",
   "announcement.announcement": "Oznámení",
-  "annual_report.summary.archetype.booster": "Lovec obsahu",
-  "annual_report.summary.archetype.lurker": "Špión",
-  "annual_report.summary.archetype.oracle": "Vědma",
-  "annual_report.summary.archetype.pollster": "Průzkumník",
-  "annual_report.summary.archetype.replier": "Sociální motýlek",
-  "annual_report.summary.followers.followers": "sledujících",
-  "annual_report.summary.followers.total": "{count} celkem",
-  "annual_report.summary.here_it_is": "Zde je tvůj rok {year} v přehledu:",
-  "annual_report.summary.highlighted_post.by_favourites": "nejvíce oblíbený příspěvek",
-  "annual_report.summary.highlighted_post.by_reblogs": "nejvíce boostovaný příspěvek",
-  "annual_report.summary.highlighted_post.by_replies": "příspěvek s nejvíce odpověďmi",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "nejpoužívanější aplikace",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "nejpoužívanější hashtag",
-  "annual_report.summary.most_used_hashtag.none": "Žádné",
-  "annual_report.summary.new_posts.new_posts": "nové příspěvky",
-  "annual_report.summary.percentile.text": "<topLabel>To vás umisťuje do horních</topLabel><percentage></percentage><bottomLabel> uživatelů domény {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "To, že jste zdejší smetánka, zůstane mezi námi ;).",
-  "annual_report.summary.thanks": "Děkujeme, že jste součástí Mastodonu!",
   "attachments_list.unprocessed": "(nezpracováno)",
   "audio.hide": "Skrýt zvuk",
   "block_modal.remote_users_caveat": "Požádáme server {domain}, aby respektoval vaše rozhodnutí. Úplné dodržování nastavení však není zaručeno, protože některé servery mohou řešit blokování různě. Veřejné příspěvky mohou být stále viditelné pro nepřihlášené uživatele.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "Požadovaná stránka nebyla nalezena. Opravdu je URL v adresním řádku správně?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Zavřít",
-  "bundle_modal_error.message": "Něco se pokazilo při načítání této obrazovky.",
+  "bundle_modal_error.message": "Při načítání tohoto komponentu se něco pokazilo.",
   "bundle_modal_error.retry": "Zkusit znovu",
   "closed_registrations.other_server_instructions": "Protože Mastodon je decentralizovaný, můžete si vytvořit účet na jiném serveru a přesto komunikovat s tímto serverem.",
   "closed_registrations_modal.description": "V současné době není možné vytvořit účet na {domain}, ale mějte prosím na paměti, že k používání Mastodonu nepotřebujete účet konkrétně na {domain}.",
@@ -150,16 +121,13 @@
   "column.blocks": "Blokovaní uživatelé",
   "column.bookmarks": "Záložky",
   "column.community": "Místní časová osa",
-  "column.create_list": "Vytvořit seznam",
   "column.direct": "Soukromé zmínky",
   "column.directory": "Prozkoumat profily",
   "column.domain_blocks": "Blokované domény",
-  "column.edit_list": "Upravit seznam",
   "column.favourites": "Oblíbené",
   "column.firehose": "Živé kanály",
   "column.follow_requests": "Žádosti o sledování",
   "column.home": "Domů",
-  "column.list_members": "Spravovat členy seznamu",
   "column.lists": "Seznamy",
   "column.mutes": "Skrytí uživatelé",
   "column.notifications": "Oznámení",
@@ -172,7 +140,6 @@
   "column_header.pin": "Připnout",
   "column_header.show_settings": "Zobrazit nastavení",
   "column_header.unpin": "Odepnout",
-  "column_search.cancel": "Zrušit",
   "column_subheading.settings": "Nastavení",
   "community.column_settings.local_only": "Pouze místní",
   "community.column_settings.media_only": "Pouze média",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Doba trvání ankety",
   "compose_form.poll.multiple": "Výběr z více možností",
   "compose_form.poll.option_placeholder": "Volba {number}",
-  "compose_form.poll.single": "Jediná volba",
+  "compose_form.poll.single": "Vyber jednu",
   "compose_form.poll.switch_to_multiple": "Povolit u ankety výběr více voleb",
   "compose_form.poll.switch_to_single": "Povolit u ankety výběr pouze jedné volby",
   "compose_form.poll.type": "Styl",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Upravit",
   "confirmations.edit.message": "Editovat teď znamená přepsání zprávy, kterou právě tvoříte. Opravdu chcete pokračovat?",
   "confirmations.edit.title": "Přepsat příspěvek?",
-  "confirmations.follow_to_list.confirm": "Sledovat a přidat do seznamu",
-  "confirmations.follow_to_list.message": "Musíte {name} sledovat, abyste je přidali do seznamu.",
-  "confirmations.follow_to_list.title": "Sledovat uživatele?",
   "confirmations.logout.confirm": "Odhlásit se",
   "confirmations.logout.message": "Opravdu se chcete odhlásit?",
   "confirmations.logout.title": "Odhlásit se?",
-  "confirmations.missing_alt_text.confirm": "Přidat popisek",
-  "confirmations.missing_alt_text.message": "Váš příspěvek obsahuje média bez popisku. Jejich přidání pomáhá zpřístupnit váš obsah většímu počtu lidí.",
-  "confirmations.missing_alt_text.secondary": "Přesto odeslat",
-  "confirmations.missing_alt_text.title": "Přidat popisek?",
   "confirmations.mute.confirm": "Skrýt",
   "confirmations.redraft.confirm": "Smazat a přepsat",
   "confirmations.redraft.message": "Jste si jistí, že chcete odstranit tento příspěvek a vytvořit z něj koncept? Oblíbené a boosty budou ztraceny a odpovědi na původní příspěvek ztratí kontext.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Váš účet {disabledAccount} je momentálně deaktivován.",
   "dismissable_banner.community_timeline": "Toto jsou nejnovější veřejné příspěvky od lidí, jejichž účty hostuje {domain}.",
   "dismissable_banner.dismiss": "Zavřít",
-  "dismissable_banner.explore_links": "Tyto zprávy jsou dnes nejvíce sdíleny ve fediversu. Novější novinky publikované více různými lidmi jsou v pořadí vyšší.",
-  "dismissable_banner.explore_statuses": "Tyto příspěvky napříč fediversem dnes získávají na popularitě. Novější příspěvky s více boosty a oblíbenými jsou výše v pořadí.",
-  "dismissable_banner.explore_tags": "Tyto hashtagy dnes na fediversu získávají na popularitě. Hashtagy, které používá více různých lidí, jsou řazeny výše.",
-  "dismissable_banner.public_timeline": "Toto jsou nejnovější veřejné příspěvky od lidí na fediversu, které lidé na {domain} sledují.",
+  "dismissable_banner.explore_links": "O těchto zprávách hovoří lidé na tomto a dalších serverech decentralizované sítě právě teď.",
+  "dismissable_banner.explore_statuses": "Toto jsou příspěvky ze sociálních sítí, které dnes získávají na popularitě. Novější příspěvky s větším počtem boostů a oblíbení jsou hodnoceny výše.",
+  "dismissable_banner.explore_tags": "Tyto hashtagy právě teď získávají na popularitě mezi lidmi na tomto a dalších serverech decentralizované sítě.",
+  "dismissable_banner.public_timeline": "Toto jsou nejnovější veřejné příspěvky od lidí na sociální síti, které sledují lidé na {domain}.",
   "domain_block_modal.block": "Blokovat server",
   "domain_block_modal.block_account_instead": "Raději blokovat @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Lidé z tohoto serveru mohou interagovat s vašimi starými příspěvky.",
@@ -264,18 +224,18 @@
   "domain_block_modal.they_wont_know": "Nebude vědět, že je zablokován*a.",
   "domain_block_modal.title": "Blokovat doménu?",
   "domain_block_modal.you_will_lose_num_followers": "Ztratíte {followersCount, plural, one {{followersCountDisplay} sledujícího} few {{followersCountDisplay} sledující} many {{followersCountDisplay} sledujících} other {{followersCountDisplay} sledujících}} a {followingCount, plural, one {{followingCountDisplay} sledovaného} few {{followingCountDisplay} sledované} many {{followingCountDisplay} sledovaných} other {{followingCountDisplay} sledovaných}}.",
-  "domain_block_modal.you_will_lose_relationships": "Ztratíte všechny sledující a sledované z tohoto serveru.",
+  "domain_block_modal.you_will_lose_relationships": "Ztratíte všechny sledující a lidi, které sledujete z tohoto serveru.",
   "domain_block_modal.you_wont_see_posts": "Neuvidíte příspěvky ani upozornění od uživatelů z tohoto serveru.",
-  "domain_pill.activitypub_lets_connect": "Umožňuje vám spojit se a komunikovat s lidmi nejen na Mastodonu, ale i na jiných sociálních aplikacích.",
+  "domain_pill.activitypub_lets_connect": "Umožňuje vám spojit se a komunikovat s lidmi nejen na Mastodonu, ale i s dalšími sociálními aplikacemi.",
   "domain_pill.activitypub_like_language": "ActivityPub je jako jazyk, kterým Mastodon mluví s jinými sociálními sítěmi.",
   "domain_pill.server": "Server",
   "domain_pill.their_handle": "Handle:",
-  "domain_pill.their_server": "Jejich digitální domov, kde žijí všechny jejich příspěvky.",
-  "domain_pill.their_username": "Jejich jedinečný identifikátor na jejich serveru. Je možné, že na jiných serverech jsou uživatelé se stejným uživatelským jménem.",
+  "domain_pill.their_server": "Jejich digitální domov, kde žijí jejich všechny příspěvky.",
+  "domain_pill.their_username": "Jejich jedinečný identikátor na jejich serveru. Je možné najít uživatele se stejným uživatelským jménem na jiných serverech.",
   "domain_pill.username": "Uživatelské jméno",
   "domain_pill.whats_in_a_handle": "Co obsahuje handle?",
-  "domain_pill.who_they_are": "Protože handly říkají kdo je kdo a také kde, je možné interagovat s lidmi napříč sociálním webem, skládajícím se z <button>platforem postavených na ActivityPub</button>.",
-  "domain_pill.who_you_are": "Protože handle říká kdo jsi a kde jsi, mohou s tebou komunikovat lidé napříč sociálním webem, skládajícím se z <button>platforem postavených na ActivityPub</button>.",
+  "domain_pill.who_they_are": "Protože handle říkají kdo je kdo a také kde, je možné interagovat s lidmi napříč sociálními weby <button>platforem postavených na ActivityPub</button>.",
+  "domain_pill.who_you_are": "Protože handle říká kdo jsi a kde jsi, mohou s tebou lidé komunikovat napříč sociálními weby <button>platforem postavených na ActivityPub</button>.",
   "domain_pill.your_handle": "Tvůj handle:",
   "domain_pill.your_server": "Tvůj digitální domov, kde žijí všechny tvé příspěvky. Nelíbí se ti? Kdykoliv se přesuň na jiný server a vezmi si sebou i své sledující.",
   "domain_pill.your_username": "Tvůj jedinečný identifikátor na tomto serveru. Je možné najít uživatele se stejným uživatelským jménem na jiných serverech.",
@@ -296,8 +256,7 @@
   "emoji_button.search_results": "Výsledky hledání",
   "emoji_button.symbols": "Symboly",
   "emoji_button.travel": "Cestování a místa",
-  "empty_column.account_featured": "Tento seznam je prázdný",
-  "empty_column.account_hides_collections": "Tento uživatel se rozhodl tuto informaci nezveřejňovat",
+  "empty_column.account_hides_collections": "Tento uživatel se rozhodl nezveřejňovat tuto informaci",
   "empty_column.account_suspended": "Účet je pozastaven",
   "empty_column.account_timeline": "Nejsou tu žádné příspěvky!",
   "empty_column.account_unavailable": "Profil není dostupný",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Pod tímto hashtagem zde zatím nic není.",
   "empty_column.home": "Vaše domovská časová osa je prázdná! Naplňte ji sledováním dalších lidí.",
   "empty_column.list": "V tomto seznamu zatím nic není. Až nějaký člen z tohoto seznamu zveřejní nový příspěvek, objeví se zde.",
+  "empty_column.lists": "Zatím nemáte žádné seznamy. Až nějaký vytvoříte, zobrazí se zde.",
   "empty_column.mutes": "Zatím jste neskryli žádného uživatele.",
   "empty_column.notification_requests": "Vyčištěno! Nic tu není. Jakmile obdržíš nové notifikace, objeví se zde podle tvého nastavení.",
   "empty_column.notifications": "Zatím nemáte žádná oznámení. Až s vámi někdo bude interagovat, uvidíte to zde.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Zkuste je vypnout a stránku obnovit. Pokud to nepomůže, zkuste otevřít Mastodon v jiném prohlížeči nebo nativní aplikaci.",
   "errors.unexpected_crash.copy_stacktrace": "Zkopírovat stacktrace do schránky",
   "errors.unexpected_crash.report_issue": "Nahlásit problém",
+  "explore.search_results": "Výsledky hledání",
   "explore.suggested_follows": "Lidé",
   "explore.title": "Objevit",
   "explore.trending_links": "Zprávy",
@@ -373,16 +334,13 @@
   "footer.about": "O aplikaci",
   "footer.directory": "Adresář profilů",
   "footer.get_app": "Získat aplikaci",
+  "footer.invite": "Pozvat lidi",
   "footer.keyboard_shortcuts": "Klávesové zkratky",
   "footer.privacy_policy": "Zásady ochrany osobních údajů",
   "footer.source_code": "Zobrazit zdrojový kód",
   "footer.status": "Stav",
-  "footer.terms_of_service": "Obchodní podmínky",
   "generic.saved": "Uloženo",
   "getting_started.heading": "Začínáme",
-  "hashtag.admin_moderation": "Otevřít moderátorské rozhraní pro #{name}",
-  "hashtag.browse": "Procházet příspěvky na #{hashtag}",
-  "hashtag.browse_from_account": "Procházet příspěvky od @{name} v #{hashtag}",
   "hashtag.column_header.tag_mode.all": "a {additional}",
   "hashtag.column_header.tag_mode.any": "nebo {additional}",
   "hashtag.column_header.tag_mode.none": "bez {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} other {{counter} příspěvků}}",
   "hashtag.counter_by_uses_today": "Dnes {count, plural, one {{counter} příspěvek} few {{counter} příspěvky} other {{counter} příspěvků}}",
   "hashtag.follow": "Sledovat hashtag",
-  "hashtag.mute": "Skrýt #{hashtag}",
   "hashtag.unfollow": "Přestat sledovat hashtag",
   "hashtags.and_other": "…a {count, plural, one {# další} few {# další} other {# dalších}}",
   "hints.profiles.followers_may_be_missing": "Sledující mohou pro tento profil chybět.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignorovat oznámení od lidí, kteří vás nesledují?",
   "ignore_notifications_modal.not_following_title": "Ignorovat oznámení od lidí, které nesledujete?",
   "ignore_notifications_modal.private_mentions_title": "Ignorovat oznámení z nevyžádaných soukromých zmínek?",
-  "info_button.label": "Nápověda",
-  "info_button.what_is_alt_text": "<h1>Co je to alt text?</h1> <p>Alt text poskytuje popis obrázků pro lidi se zrakovými postižením, špatným připojením něbo těm, kteří potřebují více kontextu.</p> <p>Můžete zlepšit přístupnost a porozumění napsáním jasného, stručného a objektivního alt textu.</p> <ul> <li>Zachyťte důležité prvky</li> <li>Shrňte text v obrázku</li> <li>Použijte pravidelnou větnou skladbu</li> <li>Vyhněte se nadbytečným informacím</li> <li>U komplexních vizualizací (diagramy, mapy...) se zaměřte na trendy a klíčová zjištění</li> </ul>",
-  "interaction_modal.action.favourite": "Chcete-li pokračovat, musíte oblíbit z vašeho účtu.",
-  "interaction_modal.action.follow": "Chcete-li pokračovat, musíte sledovat z vašeho účtu.",
-  "interaction_modal.action.reblog": "Chcete-li pokračovat, musíte dát boost z vašeho účtu.",
-  "interaction_modal.action.reply": "Chcete-li pokračovat, musíte odpovědět z vašeho účtu.",
-  "interaction_modal.action.vote": "Chcete-li pokračovat, musíte hlasovat z vašeho účtu.",
-  "interaction_modal.go": "Přejít",
-  "interaction_modal.no_account_yet": "Ještě nemáte účet?",
+  "interaction_modal.description.favourite": "Pokud máte účet na Mastodonu, můžete tento příspěvek označit jako oblíbený a dát tak autorovi najevo, že si ho vážíte, a uložit si ho na později.",
+  "interaction_modal.description.follow": "S účtem na Mastodonu můžete sledovat uživatele {name} a přijímat příspěvky ve vašem domovském kanálu.",
+  "interaction_modal.description.reblog": "S účtem na Mastodonu můžete boostnout tento příspěvek a sdílet jej s vlastními sledujícími.",
+  "interaction_modal.description.reply": "S účtem na Mastodonu můžete odpovědět na tento příspěvek.",
+  "interaction_modal.login.action": "Domů",
+  "interaction_modal.login.prompt": "Doména vašeho domovského serveru, např. mastodon.social",
+  "interaction_modal.no_account_yet": "Nejste na Mastodonu?",
   "interaction_modal.on_another_server": "Na jiném serveru",
   "interaction_modal.on_this_server": "Na tomto serveru",
+  "interaction_modal.sign_in": "Nejste přihlášeni k tomuto serveru. Kde je váš účet hostován?",
+  "interaction_modal.sign_in_hint": "Tip: To je stránka, na které jste se zaregistrovali. Pokud si ji nepamatujete, vyhledejte ve své e-mailové schránce uvítací e-mail. Můžete také zadat své celé uživatelské jméno! (např. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Oblíbit si příspěvek od uživatele {name}",
   "interaction_modal.title.follow": "Sledovat {name}",
   "interaction_modal.title.reblog": "Boostnout příspěvek uživatele {name}",
   "interaction_modal.title.reply": "Odpovědět na příspěvek uživatele {name}",
-  "interaction_modal.title.vote": "Hlasujte v anketě {name}",
-  "interaction_modal.username_prompt": "např. {example}",
   "intervals.full.days": "{number, plural, one {# den} few {# dny} many {# dní} other {# dní}}",
   "intervals.full.hours": "{number, plural, one {# hodina} few {# hodiny} many {# hodin} other {# hodin}}",
   "intervals.full.minutes": "{number, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Zobrazit/skrýt text za varováním o obsahu",
   "keyboard_shortcuts.toggle_sensitivity": "Zobrazit/skrýt média",
   "keyboard_shortcuts.toot": "Začít nový příspěvek",
-  "keyboard_shortcuts.translate": "k přeložení příspěvku",
   "keyboard_shortcuts.unfocus": "Zrušit zaměření na nový příspěvek/hledání",
   "keyboard_shortcuts.up": "Posunout v seznamu nahoru",
   "lightbox.close": "Zavřít",
@@ -490,39 +444,27 @@
   "link_preview.author": "Podle {name}",
   "link_preview.more_from_author": "Více od {name}",
   "link_preview.shares": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} many {{counter} příspěvků} other {{counter} příspěvků}}",
-  "lists.add_member": "Přidat",
-  "lists.add_to_list": "Přidat do seznamu",
-  "lists.add_to_lists": "Přidat {name} do seznamů",
-  "lists.create": "Vytvořit",
-  "lists.create_a_list_to_organize": "Vytvořte nový seznam pro organizaci vašeho domovského kanálu",
-  "lists.create_list": "Vytvořit seznam",
+  "lists.account.add": "Přidat do seznamu",
+  "lists.account.remove": "Odebrat ze seznamu",
   "lists.delete": "Smazat seznam",
-  "lists.done": "Hotovo",
   "lists.edit": "Upravit seznam",
-  "lists.exclusive": "Skrýt členy na domovském kanálu",
-  "lists.exclusive_hint": "Pokud je někdo na tomto seznamu, skryjte jej ve vašem domovském kanálu, abyste se vyhnuli dvojímu vidění jejich příspěvků.",
-  "lists.find_users_to_add": "Najít uživatele, které chcete přidat",
-  "lists.list_members": "Členové seznamu",
-  "lists.list_members_count": "{count, plural, one {# člen} few {# členové} many {# členů} other {# členů}}",
-  "lists.list_name": "Název seznamu",
-  "lists.new_list_name": "Název nového seznamu",
-  "lists.no_lists_yet": "Zatím žádné seznamy.",
-  "lists.no_members_yet": "Zatím žádní členové.",
-  "lists.no_results_found": "Nebyly nalezeny žádné výsledky.",
-  "lists.remove_member": "Odstranit",
+  "lists.edit.submit": "Změnit název",
+  "lists.exclusive": "Skrýt tyto příspěvky z domovské stránky",
+  "lists.new.create": "Přidat seznam",
+  "lists.new.title_placeholder": "Název nového seznamu",
   "lists.replies_policy.followed": "Sledovaným uživatelům",
   "lists.replies_policy.list": "Členům seznamu",
   "lists.replies_policy.none": "Nikomu",
-  "lists.save": "Uložit",
-  "lists.search": "Hledat",
-  "lists.show_replies_to": "Zahrnout odpovědi od členů seznamu pro",
+  "lists.replies_policy.title": "Odpovědi zobrazovat:",
+  "lists.search": "Hledejte mezi lidmi, které sledujete",
+  "lists.subheading": "Vaše seznamy",
   "load_pending": "{count, plural, one {# nová položka} few {# nové položky} many {# nových položek} other {# nových položek}}",
   "loading_indicator.label": "Načítání…",
   "media_gallery.hide": "Skrýt",
   "moved_to_account_banner.text": "Váš účet {disabledAccount} je momentálně deaktivován, protože jste se přesunul/a na {movedToAccount}.",
-  "mute_modal.hide_from_notifications": "Skrýt z oznámení",
+  "mute_modal.hide_from_notifications": "Skrýt z notifikací",
   "mute_modal.hide_options": "Skrýt možnosti",
-  "mute_modal.indefinite": "Dokud je neodeberu ze ztišených",
+  "mute_modal.indefinite": "Dokud je neodkryju",
   "mute_modal.show_options": "Zobrazit možnosti",
   "mute_modal.they_can_mention_and_follow": "Mohou vás zmínit a sledovat, ale neuvidíte je.",
   "mute_modal.they_wont_know": "Nebudou vědět, že byli skryti.",
@@ -531,7 +473,7 @@
   "mute_modal.you_wont_see_posts": "Stále budou moci vidět vaše příspěvky, ale vy jejich neuvidíte.",
   "navigation_bar.about": "O aplikaci",
   "navigation_bar.administration": "Administrace",
-  "navigation_bar.advanced_interface": "Otevřít v pokročilém webovém rozhraní",
+  "navigation_bar.advanced_interface": "Otevřít pokročilé webové rozhraní",
   "navigation_bar.blocks": "Blokovaní uživatelé",
   "navigation_bar.bookmarks": "Záložky",
   "navigation_bar.community_timeline": "Místní časová osa",
@@ -560,16 +502,12 @@
   "notification.admin.report": "Uživatel {name} nahlásil {target}",
   "notification.admin.report_account": "{name} nahlásil {count, plural, one {jeden příspěvek} few {# příspěvky} many {# příspěvků} other {# příspěvků}} od {target} za {category}",
   "notification.admin.report_account_other": "{name} nahlásil {count, plural, one {jeden příspěvek} few {# příspěvky} many {# příspěvků} other {# příspěvků}} od {target}",
-  "notification.admin.report_statuses": "{name} nahlásili {target} za {category}",
-  "notification.admin.report_statuses_other": "{name} nahlásili {target}",
+  "notification.admin.report_statuses": "{name} nahlásil {target} za {category}",
+  "notification.admin.report_statuses_other": "{name} nahlásil {target}",
   "notification.admin.sign_up": "Uživatel {name} se zaregistroval",
   "notification.admin.sign_up.name_and_others": "{name} a {count, plural, one {# další} few {# další} many {# dalších} other {# dalších}} se zaregistrovali",
-  "notification.annual_report.message": "Váš #Wrapstodon {year} na Vás čeká! Podívejte se, jak vypadal tento Váš rok na Mastodonu!",
-  "notification.annual_report.view": "Zobrazit #Wrapstodon",
-  "notification.favourite": "{name} si oblíbil váš příspěvek",
+  "notification.favourite": "Uživatel {name} si oblíbil váš příspěvek",
   "notification.favourite.name_and_others_with_link": "{name} a {count, plural, one {<a># další</a> si oblíbil} few {<a># další</a> si oblíbili} other {<a># dalších</a> si oblíbilo}} Váš příspěvek",
-  "notification.favourite_pm": "{name} si oblíbil vaši soukromou zmínku",
-  "notification.favourite_pm.name_and_others_with_link": "{name} a {count, plural, one {<a># další</a> si oblíbil} few {<a># další</a> si oblíbili} other {<a># dalších</a> si oblíbilo}} Vaši soukromou zmínku",
   "notification.follow": "Uživatel {name} vás začal sledovat",
   "notification.follow.name_and_others": "{name} a {count, plural, one {<a># další</a> Vás začal sledovat} few {<a># další</a> Vás začali sledovat} other {<a># dalších</a> Vás začalo sledovat}}",
   "notification.follow_request": "Uživatel {name} požádal o povolení vás sledovat",
@@ -581,11 +519,11 @@
   "notification.mention": "Zmínka",
   "notification.mentioned_you": "{name} vás zmínil",
   "notification.moderation-warning.learn_more": "Zjistit více",
-  "notification.moderation_warning": "Obdrželi jste varování od moderátorů",
+  "notification.moderation_warning": "Obdrželi jste moderační varování",
   "notification.moderation_warning.action_delete_statuses": "Některé z vašich příspěvků byly odstraněny.",
   "notification.moderation_warning.action_disable": "Váš účet je zablokován.",
   "notification.moderation_warning.action_mark_statuses_as_sensitive": "Některé z vašich příspěvků byly označeny jako citlivé.",
-  "notification.moderation_warning.action_none": "Váš účet obdržel varování od moderátorů.",
+  "notification.moderation_warning.action_none": "Váš účet obdržel moderační varování.",
   "notification.moderation_warning.action_sensitive": "Vaše příspěvky budou od nynějška označeny jako citlivé.",
   "notification.moderation_warning.action_silence": "Váš účet byl omezen.",
   "notification.moderation_warning.action_suspend": "Váš účet byl pozastaven.",
@@ -617,7 +555,7 @@
   "notification_requests.maximize": "Maximalizovat",
   "notification_requests.minimize_banner": "Minimalizovat banner filtrovaných oznámení",
   "notification_requests.notifications_from": "Oznámení od {name}",
-  "notification_requests.title": "Filtrovaná oznámení",
+  "notification_requests.title": "Vyfiltrovaná oznámení",
   "notification_requests.view": "Zobrazit oznámení",
   "notifications.clear": "Vyčistit oznámení",
   "notifications.clear_confirmation": "Opravdu chcete trvale smazat všechna vaše oznámení?",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Povolit oznámení na ploše",
   "notifications_permission_banner.how_to_control": "Chcete-li dostávat oznámení, i když nemáte Mastodon otevřený, povolte oznámení na ploše. Můžete si zvolit, o kterých druzích interakcí chcete být oznámením na ploše informování pod tlačítkem {icon} výše.",
   "notifications_permission_banner.title": "Nenechte si nic uniknout",
-  "onboarding.follows.back": "Zpět",
-  "onboarding.follows.done": "Hotovo",
-  "onboarding.follows.empty": "Bohužel, žádné výsledky nelze momentálně zobrazit. Můžete zkusit najít uživatele ke sledování za pomocí vyhledávání nebo na stránce „Objevit“, nebo to zkuste znovu později.",
-  "onboarding.follows.search": "Hledat",
-  "onboarding.follows.title": "Sledujte lidi a začněte",
+  "onboarding.action.back": "Vrátit se zpět",
+  "onboarding.actions.back": "Vrátit se zpět",
+  "onboarding.actions.go_to_explore": "Podívejte se, co je populární",
+  "onboarding.actions.go_to_home": "Přejít na svůj domovský feed",
+  "onboarding.compose.template": "Ahoj #Mastodon!",
+  "onboarding.follows.empty": "Bohužel, žádné výsledky nelze momentálně zobrazit. Můžete zkusit vyhledat nebo procházet stránku s průzkumem a najít lidi, kteří budou sledovat, nebo to zkuste znovu později.",
+  "onboarding.follows.lead": "Domovský kanál je hlavní metodou zažívání Mastodonu. Čím více lidí sledujete, tím aktivnější a zajímavější bude. Pro začnutí, zde máte několik návrhů:",
+  "onboarding.follows.title": "Přispůsobit vlastní domovský kanál",
   "onboarding.profile.discoverable": "Udělat svůj profil vyhledatelným",
   "onboarding.profile.discoverable_hint": "Když se rozhodnete být vyhledatelný na Mastodonu, vaše příspěvky se mohou objevit ve výsledcích vyhledávání a v populárních, a váš profil může být navrhován lidem s podobnými zájmy.",
   "onboarding.profile.display_name": "Zobrazované jméno",
   "onboarding.profile.display_name_hint": "Vaše celé jméno nebo přezdívka…",
+  "onboarding.profile.lead": "Toto můžete vždy dokončit později v nastavení, kde je k dispozici ještě více možností přizpůsobení.",
   "onboarding.profile.note": "O vás",
   "onboarding.profile.note_hint": "Můžete @zmínit jiné osoby nebo #hashtagy…",
   "onboarding.profile.save_and_continue": "Uložit a pokračovat",
   "onboarding.profile.title": "Nastavení profilu",
   "onboarding.profile.upload_avatar": "Nahrát profilový obrázek",
   "onboarding.profile.upload_header": "Nahrát hlavičku profilu",
+  "onboarding.share.lead": "Dejte lidem vědět, jak vás mohou najít na Mastodonu!",
+  "onboarding.share.message": "Jsem {username} na #Mastodonu! Pojď mě sledovat na {url}",
+  "onboarding.share.next_steps": "Možné další kroky:",
+  "onboarding.share.title": "Sdílejte svůj profil",
+  "onboarding.start.lead": "Nyní jste součástí Mastodonu, unikátní sociální sítě, kde vy - ne algoritmus - vytváří vaše vlastní prožitky. Začněte na této nové sociální platformě:",
+  "onboarding.start.skip": "Nepotřebujete pomoci začít?",
+  "onboarding.start.title": "Dokázali jste to!",
+  "onboarding.steps.follow_people.body": "Mastodon je o sledování zajimavých lidí.",
+  "onboarding.steps.follow_people.title": "Přispůsobit vlastní domovský kanál",
+  "onboarding.steps.publish_status.body": "Řekněte světu ahoj s pomocí textem, fotografiemi, videami nebo anketami {emoji}",
+  "onboarding.steps.publish_status.title": "Vytvořte svůj první příspěvek",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Přizpůsobit svůj profil",
+  "onboarding.steps.share_profile.body": "Dejte blízkým lidem vědět, jak vás mohou najít na Mastodonu",
+  "onboarding.steps.share_profile.title": "Sdílejte svůj profil",
+  "onboarding.tips.2fa": "<strong>Víte, že?</strong> Svůj účet můžete zabezpečit nastavením dvoufaktorového ověřování v nastavení účtu. Funguje s jakoukoli TOTP aplikací podle vašeho výběru, telefonní číslo není nutné!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Víte, že?</strong> Protože je Mastodon decentralizovaný, některé profily, na které narazíte, budou hostovány na jiných serverech, než je ten váš. A přesto s nimi můžete bezproblémově komunikovat! Jejich server se nachází v druhé polovině uživatelského jména!",
+  "onboarding.tips.migration": "<strong>Víte, že?</strong> Pokud máte pocit, že {domain} pro vás v budoucnu není vhodnou volbou, můžete se přesunout na jiný Mastodon server, aniž byste přišli o své sledující. Můžete dokonce hostovat svůj vlastní server!",
+  "onboarding.tips.verification": "<strong>Víte, že?</strong> Svůj účet můžete ověřit tak, že na své webové stránky umístíte odkaz na váš Mastodon profil a odkaz na stránku přidáte do svého profilu. Nejsou k tomu potřeba žádné poplatky ani dokumenty!",
   "password_confirmation.exceeds_maxlength": "Potvrzení hesla překračuje maximální povolenou délku hesla",
   "password_confirmation.mismatching": "Zadaná hesla se neshodují",
   "picture_in_picture.restore": "Vrátit zpět",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Odebrat anketu",
   "privacy.change": "Změnit soukromí příspěvku",
   "privacy.direct.long": "Všichni zmínění v příspěvku",
-  "privacy.direct.short": "Soukromá zmínka",
+  "privacy.direct.short": "Vybraní lidé",
   "privacy.private.long": "Jen vaši sledující",
   "privacy.private.short": "Sledující",
   "privacy.public.long": "Kdokoliv na Mastodonu i mimo něj",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Zásady ochrany osobních údajů",
   "recommended": "Doporučeno",
   "refresh": "Obnovit",
-  "regeneration_indicator.please_stand_by": "Počkej prosím.",
-  "regeneration_indicator.preparing_your_home_feed": "Připravujeme vaší domovskou stránku…",
+  "regeneration_indicator.label": "Načítání…",
+  "regeneration_indicator.sublabel": "Váš domovský kanál se připravuje!",
   "relative_time.days": "{number} d",
   "relative_time.full.days": "před {number, plural, one {# dnem} few {# dny} many {# dny} other {# dny}}",
   "relative_time.full.hours": "před {number, plural, one {# hodinou} few {# hodinami} many {# hodinami} other {# hodinami}}",
@@ -780,7 +741,7 @@
   "report_notification.categories.spam": "Spam",
   "report_notification.categories.spam_sentence": "spam",
   "report_notification.categories.violation": "Porušení pravidla",
-  "report_notification.categories.violation_sentence": "porušení pravidel",
+  "report_notification.categories.violation_sentence": "porušení pravidla",
   "report_notification.open": "Otevřít hlášení",
   "search.no_recent_searches": "Žádná nedávná vyhledávání",
   "search.placeholder": "Hledat",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Profily",
   "search_results.all": "Vše",
   "search_results.hashtags": "Hashtagy",
-  "search_results.no_results": "Nelze najít.",
-  "search_results.no_search_yet": "Zkuste vyhledat příspěvky, profily nebo hashtagy.",
+  "search_results.nothing_found": "Pro tyto hledané výrazy nebylo nic nenalezeno",
   "search_results.see_all": "Zobrazit vše",
   "search_results.statuses": "Příspěvky",
-  "search_results.title": "Hledat \"{q}\"",
+  "search_results.title": "Hledat {q}",
   "server_banner.about_active_users": "Lidé používající tento server během posledních 30 dní (měsíční aktivní uživatelé)",
   "server_banner.active_users": "aktivní uživatelé",
   "server_banner.administered_by": "Spravováno:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Tento příspěvek ještě nikdo neboostnul. Pokud to někdo udělá, zobrazí se zde.",
   "status.redraft": "Smazat a přepsat",
   "status.remove_bookmark": "Odstranit ze záložek",
-  "status.remove_favourite": "Odebrat z oblíbených",
   "status.replied_in_thread": "Odpověděli ve vlákně",
   "status.replied_to": "Odpověděl/a uživateli {name}",
   "status.reply": "Odpovědět",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Změnit odebírané jazyky na {target}",
   "tabs_bar.home": "Domů",
   "tabs_bar.notifications": "Oznámení",
-  "terms_of_service.effective_as_of": "Platné od {date}",
-  "terms_of_service.title": "Podmínky služby",
-  "terms_of_service.upcoming_changes_on": "Nadcházející změny v {date}",
   "time_remaining.days": "{number, plural, one {Zbývá # den} few {Zbývají # dny} many {Zbývá # dní} other {Zbývá # dní}}",
   "time_remaining.hours": "{number, plural, one {Zbývá # hodina} few {Zbývají # hodiny} many {Zbývá # hodin} other {Zbývá # hodin}}",
   "time_remaining.minutes": "{number, plural, one {Zbývá # minuta} few {Zbývají # minuty} many {Zbývá # minut} other {Zbývá # minut}}",
@@ -897,12 +853,26 @@
   "upload_button.label": "Přidat obrázky, video nebo audio soubor",
   "upload_error.limit": "Byl překročen limit nahraných souborů.",
   "upload_error.poll": "Nahrávání souborů není povoleno s anketami.",
+  "upload_form.audio_description": "Popis pro sluchově postižené",
+  "upload_form.description": "Popis pro zrakově postižené",
   "upload_form.drag_and_drop.instructions": "Chcete-li zvednout přílohu, stiskněte mezerník nebo enter. Při přetažení použijte klávesnicové šipky k přesunutí mediální přílohy v libovolném směru. Stiskněte mezerník nebo enter pro vložení přílohy do nové pozice, nebo stiskněte Esc pro ukončení.",
   "upload_form.drag_and_drop.on_drag_cancel": "Přetažení bylo zrušeno. Příloha {item} byla vrácena.",
   "upload_form.drag_and_drop.on_drag_end": "Příloha {item} byla vrácena.",
   "upload_form.drag_and_drop.on_drag_over": "Příloha {item} byla přesunuta.",
   "upload_form.drag_and_drop.on_drag_start": "Zvednuta příloha {item}.",
   "upload_form.edit": "Upravit",
+  "upload_form.thumbnail": "Změnit miniaturu",
+  "upload_form.video_description": "Popis pro sluchově či zrakově postižené",
+  "upload_modal.analyzing_picture": "Analyzuji obrázek…",
+  "upload_modal.apply": "Použít",
+  "upload_modal.applying": "Aplikuji…",
+  "upload_modal.choose_image": "Vybrat obrázek",
+  "upload_modal.description_placeholder": "Příliš žluťoučký kůň úpěl ďábelské ódy",
+  "upload_modal.detect_text": "Detekovat text z obrázku",
+  "upload_modal.edit_media": "Upravit média",
+  "upload_modal.hint": "Kliknutím na nebo přetáhnutím kruhu na náhledu vyberte oblast, která bude na všech náhledech vždy zobrazen.",
+  "upload_modal.preparing_ocr": "Příprava OCR…",
+  "upload_modal.preview_label": "Náhled ({ratio})",
   "upload_progress.label": "Nahrávání...",
   "upload_progress.processing": "Zpracovávání…",
   "username.taken": "Toto uživatelské jméno je obsazeno. Zkuste jiné",
@@ -912,12 +882,8 @@
   "video.expand": "Rozbalit video",
   "video.fullscreen": "Režim celé obrazovky",
   "video.hide": "Skrýt video",
-  "video.mute": "Ztlumit",
+  "video.mute": "Vypnout zvuk",
   "video.pause": "Pauza",
   "video.play": "Přehrát",
-  "video.skip_backward": "Přeskočit zpět",
-  "video.skip_forward": "Přeskočit vpřed",
-  "video.unmute": "Zrušit ztlumení",
-  "video.volume_down": "Snížit hlasitost",
-  "video.volume_up": "Zvýšit hlasitost"
+  "video.unmute": "Zapnout zvuk"
 }
diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index 3bf10be7fb..53a0cbe536 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -16,24 +16,22 @@
   "account.badges.bot": "Awtomataidd",
   "account.badges.group": "Grŵp",
   "account.block": "Blocio @{name}",
-  "account.block_domain": "Blocio'r parth {domain}",
+  "account.block_domain": "Blocio parth {domain}",
   "account.block_short": "Blocio",
   "account.blocked": "Blociwyd",
   "account.cancel_follow_request": "Tynnu cais i ddilyn",
   "account.copy": "Copïo dolen i'r proffil",
   "account.direct": "Crybwyll yn breifat @{name}",
   "account.disable_notifications": "Stopiwch fy hysbysu pan fydd @{name} yn postio",
-  "account.domain_blocked": "Parth wedi'i rwystro",
+  "account.domain_blocked": "Parth wedi ei flocio",
   "account.edit_profile": "Golygu proffil",
   "account.enable_notifications": "Rhowch wybod i fi pan fydd @{name} yn postio",
   "account.endorse": "Dangos ar fy mhroffil",
-  "account.featured": "Dethol",
-  "account.featured.hashtags": "Hashnodau",
-  "account.featured.posts": "Postiadau",
-  "account.featured_tags.last_status_at": "Y postiad olaf ar {date}",
+  "account.featured_tags.last_status_at": "Y postiad diwethaf ar {date}",
   "account.featured_tags.last_status_never": "Dim postiadau",
+  "account.featured_tags.title": "Prif hashnodau {name}",
   "account.follow": "Dilyn",
-  "account.follow_back": "Dilyn nôl",
+  "account.follow_back": "Dilyn yn ôl",
   "account.followers": "Dilynwyr",
   "account.followers.empty": "Does neb yn dilyn y defnyddiwr hwn eto.",
   "account.followers_counter": "{count, plural, one {{counter} dilynwr} two {{counter} ddilynwr} other {{counter} dilynwyr}}",
@@ -43,7 +41,7 @@
   "account.go_to_profile": "Mynd i'r proffil",
   "account.hide_reblogs": "Cuddio hybiau gan @{name}",
   "account.in_memoriam": "Er Cof.",
-  "account.joined_short": "Ymunodd",
+  "account.joined_short": "Wedi Ymuno",
   "account.languages": "Newid ieithoedd wedi tanysgrifio iddyn nhw",
   "account.link_verified_on": "Gwiriwyd perchnogaeth y ddolen yma ar {date}",
   "account.locked_info": "Mae'r statws preifatrwydd cyfrif hwn wedi'i osod i fod ar glo. Mae'r perchennog yn adolygu'r sawl sy'n gallu eu dilyn.",
@@ -58,17 +56,16 @@
   "account.no_bio": "Dim disgrifiad wedi'i gynnig.",
   "account.open_original_page": "Agor y dudalen wreiddiol",
   "account.posts": "Postiadau",
-  "account.posts_with_replies": "Postiadau ac ymatebion",
+  "account.posts_with_replies": "Postiadau ac atebion",
   "account.report": "Adrodd @{name}",
   "account.requested": "Aros am gymeradwyaeth. Cliciwch er mwyn canslo cais dilyn",
   "account.requested_follow": "Mae {name} wedi gwneud cais i'ch dilyn",
-  "account.share": "Rhannu proffil @{name}",
+  "account.share": "Rhannwch broffil @{name}",
   "account.show_reblogs": "Dangos hybiau gan @{name}",
   "account.statuses_counter": "{count, plural, one {{counter} postiad} two {{counter} bostiad} few {{counter} phostiad} many {{counter} postiad} other {{counter} postiad}}",
-  "account.unblock": "Dadrwystro @{name}",
-  "account.unblock_domain": "Dadrwystro parth {domain}",
-  "account.unblock_domain_short": "Dadrwystro",
-  "account.unblock_short": "Dadrwystro",
+  "account.unblock": "Dadflocio @{name}",
+  "account.unblock_domain": "Dadflocio parth {domain}",
+  "account.unblock_short": "Dadflocio",
   "account.unendorse": "Peidio a'i ddangos ar fy mhroffil",
   "account.unfollow": "Dad-ddilyn",
   "account.unmute": "Dad-dewi {name}",
@@ -88,91 +85,61 @@
   "alert.rate_limited.title": "Cyfradd gyfyngedig",
   "alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
   "alert.unexpected.title": "Wps!",
-  "alt_text_badge.title": "Testun amgen",
-  "alt_text_modal.add_alt_text": "Ychwanegu testun amgen",
-  "alt_text_modal.add_text_from_image": "Ychwanegu testun o'r ddelwedd",
-  "alt_text_modal.cancel": "Diddymu",
-  "alt_text_modal.change_thumbnail": "Newid llun bach",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Disgrifiwch hyn ar gyfer pobl â nam ar eu clyw…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Disgrifiwch hyn ar gyfer pobl â nam ar eu golwg…",
-  "alt_text_modal.done": "Gorffen",
+  "alt_text_badge.title": "Testun Amgen",
   "announcement.announcement": "Cyhoeddiad",
-  "annual_report.summary.archetype.booster": "Y hyrwyddwr",
-  "annual_report.summary.archetype.lurker": "Y crwydryn",
-  "annual_report.summary.archetype.oracle": "Yr oracl",
-  "annual_report.summary.archetype.pollster": "Yr arholwr",
-  "annual_report.summary.archetype.replier": "Y sbardunwr",
-  "annual_report.summary.followers.followers": "dilynwyr",
-  "annual_report.summary.followers.total": "Cyfanswm o{count}",
-  "annual_report.summary.here_it_is": "Dyma eich {year} yn gryno:",
-  "annual_report.summary.highlighted_post.by_favourites": "postiad wedi'i ffefrynu fwyaf",
-  "annual_report.summary.highlighted_post.by_reblogs": "postiad wedi'i hybu fwyaf",
-  "annual_report.summary.highlighted_post.by_replies": "postiad gyda'r nifer fwyaf o atebion",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "ap a ddefnyddiwyd fwyaf",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashnod a ddefnyddiwyd fwyaf",
-  "annual_report.summary.most_used_hashtag.none": "Dim",
-  "annual_report.summary.new_posts.new_posts": "postiadau newydd",
-  "annual_report.summary.percentile.text": "<topLabel>Mae hynny'n eich rhoi chi ymysg y</topLabel><percentage></percentage><bottomLabel>uchaf o ddefnyddwyr {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Fyddwn ni ddim yn dweud wrth Bernie.",
-  "annual_report.summary.thanks": "Diolch am fod yn rhan o Mastodon!",
   "attachments_list.unprocessed": "(heb eu prosesu)",
   "audio.hide": "Cuddio sain",
   "block_modal.remote_users_caveat": "Byddwn yn gofyn i'r gweinydd {domain} barchu eich penderfyniad. Fodd bynnag, nid yw cydymffurfiad wedi'i warantu gan y gall rhai gweinyddwyr drin rhwystro mewn ffyrdd gwahanol. Mae'n bosibl y bydd postiadau cyhoeddus yn dal i fod yn weladwy i ddefnyddwyr nad ydynt wedi mewngofnodi.",
   "block_modal.show_less": "Dangos llai",
   "block_modal.show_more": "Dangos rhagor",
-  "block_modal.they_cant_mention": "Dydyn nhw ddim yn gallu eich crybwyll na'ch dilyn.",
-  "block_modal.they_cant_see_posts": "Dydyn nhw ddim yn gallu gweld eich postiadau a fyddwch chi ddim yn gweld eu rhai nhw.",
-  "block_modal.they_will_know": "Gallan nhw weld eu bod wedi'u rhwystro.",
+  "block_modal.they_cant_mention": "Nid ydynt yn gallu eich crybwyll na'ch dilyn.",
+  "block_modal.they_cant_see_posts": "Nid ydynt yn gallu gweld eich postiadau ac ni fyddwch yn gweld eu rhai hwy.",
+  "block_modal.they_will_know": "Gallant weld eu bod wedi'u rhwystro.",
   "block_modal.title": "Blocio defnyddiwr?",
   "block_modal.you_wont_see_mentions": "Fyddwch chi ddim yn gweld postiadau sy'n sôn amdanyn nhw.",
   "boost_modal.combo": "Mae modd pwyso {combo} er mwyn hepgor hyn tro nesa",
   "boost_modal.reblog": "Hybu postiad?",
   "boost_modal.undo_reblog": "Dad-hybu postiad?",
   "bundle_column_error.copy_stacktrace": "Copïo'r adroddiad gwall",
-  "bundle_column_error.error.body": "Does dim modd cynhyrchu'r dudalen honno. Gall fod oherwydd gwall yn ein cod neu fater cydnawsedd porwr.",
+  "bundle_column_error.error.body": "Nid oedd modd cynhyrchu'r dudalen honno. Gall fod oherwydd gwall yn ein cod neu fater cydnawsedd porwr.",
   "bundle_column_error.error.title": "O na!",
   "bundle_column_error.network.body": "Bu gwall wrth geisio llwytho'r dudalen hon. Gall hyn fod oherwydd anhawster dros-dro gyda'ch cysylltiad gwe neu'r gweinydd hwn.",
   "bundle_column_error.network.title": "Gwall rhwydwaith",
   "bundle_column_error.retry": "Ceisiwch eto",
   "bundle_column_error.return": "Mynd i'r ffrwd gartref",
-  "bundle_column_error.routing.body": "Doedd dim modd canfod y dudalen honno. Ydych chi'n siŵr fod yr URL yn y bar cyfeiriad yn gywir?",
+  "bundle_column_error.routing.body": "Nid oedd modd canfod y dudalen honno. Ydych chi'n siŵr fod yr URL yn y bar cyfeiriad yn gywir?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Cau",
-  "bundle_modal_error.message": "Aeth rhywbeth o'i le wrth lwytho'r sgrin hon.",
+  "bundle_modal_error.message": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
   "bundle_modal_error.retry": "Ceisiwch eto",
   "closed_registrations.other_server_instructions": "Gan fod Mastodon yn ddatganoledig, gallwch greu cyfrif ar weinydd arall a dal i ryngweithio gyda hwn.",
   "closed_registrations_modal.description": "Ar hyn o bryd nid yw'n bosib creu cyfrif ar {domain}, ond cadwch mewn cof nad oes raid i chi gael cyfrif yn benodol ar {domain} i ddefnyddio Mastodon.",
   "closed_registrations_modal.find_another_server": "Dod o hyd i weinydd arall",
   "closed_registrations_modal.preamble": "Mae Mastodon wedi'i ddatganoli, felly does dim gwahaniaeth ble rydych chi'n creu eich cyfrif, byddwch chi'n gallu dilyn a rhyngweithio ag unrhyw un ar y gweinydd hwn. Gallwch hyd yn oed ei gynnal un eich hun!",
-  "closed_registrations_modal.title": "Cofrestru ar Mastodon",
+  "closed_registrations_modal.title": "Ymgofrestru ar Mastodon",
   "column.about": "Ynghylch",
-  "column.blocks": "Defnyddwyr wedi'u rhwystro",
+  "column.blocks": "Defnyddwyr a flociwyd",
   "column.bookmarks": "Llyfrnodau",
   "column.community": "Ffrwd lleol",
-  "column.create_list": "Creu rhestr",
   "column.direct": "Crybwylliadau preifat",
   "column.directory": "Pori proffiliau",
-  "column.domain_blocks": "Parthau wedi'u rhwystro",
-  "column.edit_list": "Golygu rhestr",
+  "column.domain_blocks": "Parthau wedi'u blocio",
   "column.favourites": "Ffefrynnau",
   "column.firehose": "Ffrydiau byw",
   "column.follow_requests": "Ceisiadau dilyn",
   "column.home": "Cartref",
-  "column.list_members": "Rheoli aelodau rhestr",
   "column.lists": "Rhestrau",
   "column.mutes": "Defnyddwyr wedi'u tewi",
   "column.notifications": "Hysbysiadau",
   "column.pins": "Postiadau wedi eu pinio",
   "column.public": "Ffrwd y ffederasiwn",
   "column_back_button.label": "Nôl",
-  "column_header.hide_settings": "Cuddio'r dewisiadau",
+  "column_header.hide_settings": "Cuddio dewisiadau",
   "column_header.moveLeft_settings": "Symud y golofn i'r chwith",
   "column_header.moveRight_settings": "Symud y golofn i'r dde",
   "column_header.pin": "Pinio",
   "column_header.show_settings": "Dangos gosodiadau",
   "column_header.unpin": "Dadbinio",
-  "column_search.cancel": "Diddymu",
   "column_subheading.settings": "Gosodiadau",
   "community.column_settings.local_only": "Lleol yn unig",
   "community.column_settings.media_only": "Cyfryngau yn unig",
@@ -184,26 +151,26 @@
   "compose.saved.body": "Postiad wedi'i gadw.",
   "compose_form.direct_message_warning_learn_more": "Dysgu mwy",
   "compose_form.encryption_warning": "Dyw postiadau ar Mastodon ddim wedi'u hamgryptio o ben i ben. Peidiwch â rhannu unrhyw wybodaeth sensitif dros Mastodon.",
-  "compose_form.hashtag_warning": "Fydd y postiad hwn ddim wedi'i restru o dan unrhyw hashnod gan nad yw'n gyhoeddus. Dim ond postiadau cyhoeddus y mae modd eu chwilio drwy hashnod.",
+  "compose_form.hashtag_warning": "Ni fydd y postiad hwn wedi ei restru o dan unrhyw hashnod gan nad yw'n gyhoeddus. Dim ond postiadau cyhoeddus y mae modd eu chwilio drwy hashnod.",
   "compose_form.lock_disclaimer": "Nid yw eich cyfri wedi'i {locked}. Gall unrhyw un eich dilyn i weld eich postiadau dilynwyr-yn-unig.",
   "compose_form.lock_disclaimer.lock": "wedi ei gloi",
   "compose_form.placeholder": "Beth sydd ar eich meddwl?",
   "compose_form.poll.duration": "Cyfnod pleidlais",
   "compose_form.poll.multiple": "Dewis lluosog",
   "compose_form.poll.option_placeholder": "Dewis {number}",
-  "compose_form.poll.single": "Dewis unigol",
+  "compose_form.poll.single": "Ddewis un",
   "compose_form.poll.switch_to_multiple": "Newid pleidlais i adael mwy nag un dewis",
   "compose_form.poll.switch_to_single": "Newid pleidlais i gyfyngu i un dewis",
   "compose_form.poll.type": "Arddull",
-  "compose_form.publish": "Postio",
+  "compose_form.publish": "Postiad",
   "compose_form.publish_form": "Postiad newydd",
-  "compose_form.reply": "Ymateb",
+  "compose_form.reply": "Ateb",
   "compose_form.save_changes": "Diweddaru",
   "compose_form.spoiler.marked": "Dileu rhybudd cynnwys",
   "compose_form.spoiler.unmarked": "Ychwanegu rhybudd cynnwys",
   "compose_form.spoiler_placeholder": "Rhybudd cynnwys (dewisol)",
   "confirmation_modal.cancel": "Canslo",
-  "confirmations.block.confirm": "Rhwystro",
+  "confirmations.block.confirm": "Blocio",
   "confirmations.delete.confirm": "Dileu",
   "confirmations.delete.message": "Ydych chi'n sicr eich bod eisiau dileu y post hwn?",
   "confirmations.delete.title": "Dileu postiad?",
@@ -215,21 +182,14 @@
   "confirmations.edit.confirm": "Golygu",
   "confirmations.edit.message": "Bydd golygu nawr yn trosysgrifennu'r neges rydych yn ei ysgrifennu ar hyn o bryd. Ydych chi'n siŵr eich bod eisiau gwneud hyn?",
   "confirmations.edit.title": "Trosysgrifo'r postiad?",
-  "confirmations.follow_to_list.confirm": "Dilyn ac ychwanegu at y rhestr",
-  "confirmations.follow_to_list.message": "Mae angen i chi fod yn dilyn {name} i'w ychwanegu at restr.",
-  "confirmations.follow_to_list.title": "Dilyn defnyddiwr?",
   "confirmations.logout.confirm": "Allgofnodi",
   "confirmations.logout.message": "Ydych chi'n siŵr eich bod am allgofnodi?",
   "confirmations.logout.title": "Allgofnodi?",
-  "confirmations.missing_alt_text.confirm": "Ychwanegu testun amgen",
-  "confirmations.missing_alt_text.message": "Mae eich postiad yn cynnwys cyfryngau heb destun amgen. Mae ychwanegu disgrifiadau yn helpu i wneud eich cynnwys yn hygyrch i fwy o bobl.",
-  "confirmations.missing_alt_text.secondary": "Postio beth bynnag",
-  "confirmations.missing_alt_text.title": "Ychwanegu testun amgen?",
   "confirmations.mute.confirm": "Tewi",
   "confirmations.redraft.confirm": "Dileu ac ailddrafftio",
   "confirmations.redraft.message": "Ydych chi wir eisiau'r dileu'r postiad hwn a'i ailddrafftio? Bydd ffefrynnau a hybiau'n cael eu colli, a bydd atebion i'r post gwreiddiol yn mynd yn amddifad.",
   "confirmations.redraft.title": "Dileu & ailddraftio postiad?",
-  "confirmations.reply.confirm": "Ymateb",
+  "confirmations.reply.confirm": "Ateb",
   "confirmations.reply.message": "Bydd ateb nawr yn cymryd lle y neges yr ydych yn cyfansoddi ar hyn o bryd. Ydych chi'n siŵr eich bod am barhau?",
   "confirmations.reply.title": "Trosysgrifo'r postiad?",
   "confirmations.unfollow.confirm": "Dad-ddilyn",
@@ -251,16 +211,16 @@
   "directory.recently_active": "Ar-lein yn ddiweddar",
   "disabled_account_banner.account_settings": "Gosodiadau'r cyfrif",
   "disabled_account_banner.text": "Mae eich cyfrif {disabledAccount} wedi ei analluogi ar hyn o bryd.",
-  "dismissable_banner.community_timeline": "Dyma'r postiadau cyhoeddus diweddaraf gan bobl y caiff eu cyfrifon eu cynnal ar {domain}.",
-  "dismissable_banner.dismiss": "Diystyru",
-  "dismissable_banner.explore_links": "Y straeon newyddion hyn yw'r rhai sy'n cael eu rhannu fwyaf ar y ffedysawd heddiw. Mae straeon newyddion mwy diweddar sy'n cael eu postio gan fwy o amrywiaeth o bobl yn cael eu graddio'n uwch.",
-  "dismissable_banner.explore_statuses": "Mae'r postiadau hyn o bob rhan o'r ffedysawd yn cael mwy o sylw heddiw. Mae postiadau mwy diweddar sydd â mwy o hybu a ffefrynnu'n cael eu graddio'n uwch.",
-  "dismissable_banner.explore_tags": "Mae'r hashnodau hyn ar gynnydd y ffedysawd heddiw. Mae hashnodau sy'n cael eu defnyddio gan fwy o bobl amrywiol yn cael eu graddio'n uwch.",
-  "dismissable_banner.public_timeline": "Dyma'r postiadau cyhoeddus diweddaraf gan bobl ar y ffedysawd y mae pobl ar {domain} yn eu dilyn.",
+  "dismissable_banner.community_timeline": "Dyma'r postiadau cyhoeddus diweddaraf gan bobl sydd â chyfrifon ar {domain}.",
+  "dismissable_banner.dismiss": "Cau",
+  "dismissable_banner.explore_links": "Dyma straeon newyddion sy’n cael eu rhannu fwyaf ar y we gymdeithasol heddiw. Mae'r straeon newyddion diweddaraf sy'n cael eu postio gan fwy o unigolion gwahanol yn cael eu graddio'n uwch.",
+  "dismissable_banner.explore_statuses": "Dyma postiadau o bob gwr o'r we gymdeithasol sy'n derbyn sylw heddiw. Mae postiadau mwy diweddar sydd â mwy o hybiau a ffefrynnau'n cael eu graddio'n uwch.",
+  "dismissable_banner.explore_tags": "Mae'r rhain yn hashnodau sydd ar gynnydd ar y we gymdeithasol heddiw. Mae hashnodau sy'n cael eu defnyddio gan fwy o unigolion gwahanol yn cael eu graddio'n uwch.",
+  "dismissable_banner.public_timeline": "Dyma'r postiadau cyhoeddus diweddaraf gan bobl ar y we gymdeithasol y mae pobl ar {domain} yn eu dilyn.",
   "domain_block_modal.block": "Blocio gweinydd",
   "domain_block_modal.block_account_instead": "Blocio @{name} yn ei le",
   "domain_block_modal.they_can_interact_with_old_posts": "Gall pobl o'r gweinydd hwn ryngweithio â'ch hen bostiadau.",
-  "domain_block_modal.they_cant_follow": "All neb o'r gweinydd hwn eich dilyn.",
+  "domain_block_modal.they_cant_follow": "Ni all neb o'r gweinydd hwn eich dilyn.",
   "domain_block_modal.they_wont_know": "Fyddan nhw ddim yn gwybod eu bod wedi cael eu blocio.",
   "domain_block_modal.title": "Blocio parth?",
   "domain_block_modal.you_will_lose_num_followers": "Byddwch yn colli {followersCount, plural, one {{followersCountDisplay} dilynwr} other {{followersCountDisplay} dilynwyr}} a {followingCount, plural, one {{followingCountDisplay} person rydych yn dilyn} other {{followingCountDisplay} o bobl rydych yn eu dilyn}}.",
@@ -296,15 +256,14 @@
   "emoji_button.search_results": "Canlyniadau chwilio",
   "emoji_button.symbols": "Symbolau",
   "emoji_button.travel": "Teithio a Llefydd",
-  "empty_column.account_featured": "Mae'r rhestr hon yn wag",
   "empty_column.account_hides_collections": "Mae'r defnyddiwr wedi dewis i beidio rhannu'r wybodaeth yma",
   "empty_column.account_suspended": "Cyfrif wedi'i atal",
   "empty_column.account_timeline": "Dim postiadau yma!",
   "empty_column.account_unavailable": "Nid yw'r proffil ar gael",
-  "empty_column.blocks": "Dydych chi heb rwystro unrhyw ddefnyddwyr eto.",
-  "empty_column.bookmarked_statuses": "Does gennych chi ddim unrhyw bostiad wedi'u cadw fel nod tudalen eto. Pan fyddwch yn gosod nod tudalen i un, mi fydd yn ymddangos yma.",
+  "empty_column.blocks": "Nid ydych wedi blocio unrhyw ddefnyddwyr eto.",
+  "empty_column.bookmarked_statuses": "Nid oes gennych unrhyw bostiad wedi'u cadw fel nod tudalen eto. Pan fyddwch yn gosod nod tudalen i un, mi fydd yn ymddangos yma.",
   "empty_column.community": "Mae'r ffrwd lleol yn wag. Beth am ysgrifennu rhywbeth cyhoeddus!",
-  "empty_column.direct": "Does gennych chi unrhyw grybwylliadau preifat eto. Pan fyddwch chi'n anfon neu'n derbyn un, bydd yn ymddangos yma.",
+  "empty_column.direct": "Nid oes gennych unrhyw grybwylliadau preifat eto. Pan fyddwch chi'n anfon neu'n derbyn un, bydd yn ymddangos yma.",
   "empty_column.domain_blocks": "Nid oes unrhyw barthau wedi'u blocio eto.",
   "empty_column.explore_statuses": "Does dim pynciau llosg ar hyn o bryd. Dewch nôl nes ymlaen!",
   "empty_column.favourited_statuses": "Rydych chi heb ffafrio unrhyw bostiadau eto. Pan byddwch chi'n ffafrio un, bydd yn ymddangos yma.",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Nid oes dim ar yr hashnod hwn eto.",
   "empty_column.home": "Mae eich ffrwd gartref yn wag! Dilynwch fwy o bobl i'w llenwi.",
   "empty_column.list": "Does dim yn y rhestr yma eto. Pan fydd aelodau'r rhestr yn cyhoeddi postiad newydd, mi fydd yn ymddangos yma.",
+  "empty_column.lists": "Nid oes gennych unrhyw restrau eto. Pan fyddwch yn creu un, mi fydd yn ymddangos yma.",
   "empty_column.mutes": "Nid ydych wedi tewi unrhyw ddefnyddwyr eto.",
   "empty_column.notification_requests": "Dim i boeni amdano! Does dim byd yma. Pan fyddwch yn derbyn hysbysiadau newydd, byddan nhw'n ymddangos yma yn ôl eich gosodiadau.",
   "empty_column.notifications": "Nid oes gennych unrhyw hysbysiadau eto. Rhyngweithiwch ag eraill i ddechrau'r sgwrs.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Ceisiwch eu hanalluogi ac adnewyddu'r dudalen. Os nad yw hynny'n helpu, efallai y byddwch yn dal i allu defnyddio Mastodon trwy borwr neu ap cynhenid arall.",
   "errors.unexpected_crash.copy_stacktrace": "Copïo'r olrhain stac i'r clipfwrdd",
   "errors.unexpected_crash.report_issue": "Rhoi gwybod am broblem",
+  "explore.search_results": "Canlyniadau chwilio",
   "explore.suggested_follows": "Pobl",
   "explore.title": "Darganfod",
   "explore.trending_links": "Newyddion",
@@ -373,14 +334,13 @@
   "footer.about": "Ynghylch",
   "footer.directory": "Cyfeiriadur proffiliau",
   "footer.get_app": "Lawrlwytho'r ap",
+  "footer.invite": "Gwahodd pobl",
   "footer.keyboard_shortcuts": "Bysellau brys",
   "footer.privacy_policy": "Polisi preifatrwydd",
   "footer.source_code": "Gweld y cod ffynhonnell",
   "footer.status": "Statws",
-  "footer.terms_of_service": "Telerau gwasanaeth",
   "generic.saved": "Wedi'i Gadw",
   "getting_started.heading": "Dechrau",
-  "hashtag.admin_moderation": "Agor rhyngwyneb cymedroli #{name}",
   "hashtag.column_header.tag_mode.all": "a {additional}",
   "hashtag.column_header.tag_mode.any": "neu {additional}",
   "hashtag.column_header.tag_mode.none": "heb {additional}",
@@ -402,10 +362,10 @@
   "hints.profiles.see_more_followers": "Gweld mwy o ddilynwyr ar {domain}",
   "hints.profiles.see_more_follows": "Gweld mwy o 'yn dilyn' ar {domain}",
   "hints.profiles.see_more_posts": "Gweld mwy o bostiadau ar {domain}",
-  "hints.threads.replies_may_be_missing": "Mae'n bosibl y bydd ymatebion gan weinyddion eraill ar goll.",
-  "hints.threads.see_more": "Gweld mwy o ymatebion ar {domain}",
+  "hints.threads.replies_may_be_missing": "Mae'n bosibl y bydd atebion gan weinyddion eraill ar goll.",
+  "hints.threads.see_more": "Gweld mwy o atebion ar {domain}",
   "home.column_settings.show_reblogs": "Dangos hybiau",
-  "home.column_settings.show_replies": "Dangos ymatebion",
+  "home.column_settings.show_replies": "Dangos atebion",
   "home.hide_announcements": "Cuddio cyhoeddiadau",
   "home.pending_critical_update.body": "Diweddarwch eich gweinydd Mastodon cyn gynted â phosibl!",
   "home.pending_critical_update.link": "Gweld diweddariadau",
@@ -422,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Anwybyddu hysbysiadau gan bobl nad ydynt yn eich dilyn?",
   "ignore_notifications_modal.not_following_title": "Anwybyddu hysbysiadau gan bobl nad ydych yn eu dilyn?",
   "ignore_notifications_modal.private_mentions_title": "Anwybyddu hysbysiadau o Grybwylliadau Preifat digymell?",
-  "info_button.label": "Cymorth",
-  "info_button.what_is_alt_text": "<h1>Beth yw testun amgen?</h1><p> Mae Testun Amgen yn darparu disgrifiadau delwedd ar gyfer pobl â nam ar eu golwg, cysylltiadau lled band isel, neu'r rhai sy'n ceisio cyd-destun ychwanegol.</p><p> Gallwch wella hygyrchedd a dealltwriaeth i bawb trwy ysgrifennu testun amgen clir, cryno a gwrthrychol.</p><ul><li> Dal elfennau pwysig</li><li> Crynhoi testun mewn delweddau</li><li> Defnyddiwch strwythur brawddegau rheolaidd</li><li> Osgoi gwybodaeth ddiangen</li><li> Canolbwyntio ar dueddiadau a chanfyddiadau allweddol mewn delweddau cymhleth (fel diagramau neu fapiau)</li></ul>",
-  "interaction_modal.action.favourite": "I barhau, mae angen i chi hoffi o'ch cyfrif.",
-  "interaction_modal.action.follow": "I barhau, mae angen i chi ddilyn o'ch cyfrif.",
-  "interaction_modal.action.reblog": "I barhau, mae angen i chi ail-flogio o'ch cyfrif.",
-  "interaction_modal.action.reply": "I barhau, mae angen i chi ymateb o'ch cyfrif.",
-  "interaction_modal.action.vote": "I barhau, mae angen i chi bleidleisio o'ch cyfrif.",
-  "interaction_modal.go": "Mynd",
-  "interaction_modal.no_account_yet": "Dim cyfrif eto?",
+  "interaction_modal.description.favourite": "Gyda chyfrif ar Mastodon, gallwch chi ffafrio'r postiad hwn er mwyn roi gwybod i'r awdur eich bod chi'n ei werthfawrogi ac yn ei gadw ar gyfer nes ymlaen.",
+  "interaction_modal.description.follow": "Gyda chyfrif ar Mastodon, gallwch ddilyn {name} i dderbyn eu postiadau yn eich ffrwd gartref.",
+  "interaction_modal.description.reblog": "Gyda chyfrif ar Mastodon, gallwch hybu'r postiad hwn i'w rannu â'ch dilynwyr.",
+  "interaction_modal.description.reply": "Gyda chyfrif ar Mastodon, gallwch ymateb i'r postiad hwn.",
+  "interaction_modal.login.action": "Mynd i'm ffrwd gartref",
+  "interaction_modal.login.prompt": "Parth eich gweinydd cartref, e.e. mastodon.social",
+  "interaction_modal.no_account_yet": "Dim ar Mastodon?",
   "interaction_modal.on_another_server": "Ar weinydd gwahanol",
   "interaction_modal.on_this_server": "Ar y gweinydd hwn",
+  "interaction_modal.sign_in": "Nid ydych wedi mewngofnodi i'r gweinydd hwn. Ble mae eich cyfrif yn cael ei gynnal?",
+  "interaction_modal.sign_in_hint": "Awgrym: Dyna'r wefan lle gwnaethoch gofrestru. Os nad ydych yn cofio, edrychwch am yr e-bost croeso yn eich blwch derbyn. Gallwch hefyd nodi eich enw defnyddiwr llawn! (e.e. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Ffafrio postiad {name}",
   "interaction_modal.title.follow": "Dilyn {name}",
   "interaction_modal.title.reblog": "Hybu postiad {name}",
   "interaction_modal.title.reply": "Ymateb i bostiad {name}",
-  "interaction_modal.title.vote": "Pleidleisiwch ym mhleidlais {name}",
-  "interaction_modal.username_prompt": "E.e. {example}",
   "intervals.full.days": "{number, plural, one {# diwrnod} two {# ddiwrnod} other {# diwrnod}}",
   "intervals.full.hours": "{number, plural, one {# awr} other {# o oriau}}",
   "intervals.full.minutes": "{number, plural, one {# funud} other {# o funudau}}",
@@ -466,7 +424,7 @@
   "keyboard_shortcuts.open_media": "Agor cyfryngau",
   "keyboard_shortcuts.pinned": "Agor rhestr postiadau wedi'u pinio",
   "keyboard_shortcuts.profile": "Agor proffil yr awdur",
-  "keyboard_shortcuts.reply": "Ymateb i bostiad",
+  "keyboard_shortcuts.reply": "Ateb i bostiad",
   "keyboard_shortcuts.requests": "Agor rhestr ceisiadau dilyn",
   "keyboard_shortcuts.search": "Ffocysu ar y bar chwilio",
   "keyboard_shortcuts.spoilers": "Dangos/cuddio'r maes CW",
@@ -474,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Dangos/cuddio testun tu ôl i CW",
   "keyboard_shortcuts.toggle_sensitivity": "Dangos/cuddio cyfryngau",
   "keyboard_shortcuts.toot": "Dechrau post newydd",
-  "keyboard_shortcuts.translate": "i gyfieithu postiad",
   "keyboard_shortcuts.unfocus": "Dad-ffocysu ardal cyfansoddi testun/chwilio",
   "keyboard_shortcuts.up": "Symud yn uwch yn y rhestr",
   "lightbox.close": "Cau",
@@ -487,36 +444,24 @@
   "link_preview.author": "Gan {name}",
   "link_preview.more_from_author": "Mwy gan {name}",
   "link_preview.shares": "{count, plural, one {{counter} postiad } two {{counter} bostiad } few {{counter} postiad} many {{counter} postiad} other {{counter} postiad}}",
-  "lists.add_member": "Ychwanegu",
-  "lists.add_to_list": "Ychwanegu at restr",
-  "lists.add_to_lists": "Ychwanegu {name} at restrau",
-  "lists.create": "Creu",
-  "lists.create_a_list_to_organize": "Creu rhestr newydd i drefnu eich llif Cartref",
-  "lists.create_list": "Creu rhestr",
-  "lists.delete": "Dileu'r rhestr",
-  "lists.done": "Wedi gorffen",
-  "lists.edit": "Golygu'r rhestr",
-  "lists.exclusive": "Cuddio aelodau yn y Cartref",
-  "lists.exclusive_hint": "Os oes rhywun ar y rhestr hon, cuddiwch nhw yn eich llif Cartref i osgoi gweld eu postiadau ddwywaith.",
-  "lists.find_users_to_add": "Canfod defnyddwyr i'w hychwanegu",
-  "lists.list_members": "Aelodau rhestr",
-  "lists.list_members_count": "{count, plural, one {# aelod} other {# aelod}}",
-  "lists.list_name": "Enw rhestr",
-  "lists.new_list_name": "Enw rhestr newydd",
-  "lists.no_lists_yet": "Dim rhestrau eto.",
-  "lists.no_members_yet": "Dim aelodau eto.",
-  "lists.no_results_found": "Heb ganfod canlyniadau.",
-  "lists.remove_member": "Tynnu",
+  "lists.account.add": "Ychwanegu at restr",
+  "lists.account.remove": "Tynnu o'r rhestr",
+  "lists.delete": "Dileu rhestr",
+  "lists.edit": "Golygu rhestr",
+  "lists.edit.submit": "Newid teitl",
+  "lists.exclusive": "Cuddio'r postiadau hyn o'r ffrwd gartref",
+  "lists.new.create": "Ychwanegu rhestr",
+  "lists.new.title_placeholder": "Teitl rhestr newydd",
   "lists.replies_policy.followed": "Unrhyw ddefnyddiwr sy'n cael ei ddilyn",
   "lists.replies_policy.list": "Aelodau'r rhestr",
   "lists.replies_policy.none": "Neb",
-  "lists.save": "Cadw",
-  "lists.search": "Chwilio",
-  "lists.show_replies_to": "Cynnwys ymatebion gan aelodau'r rhestr i",
+  "lists.replies_policy.title": "Dangos atebion i:",
+  "lists.search": "Chwilio ymysg pobl rydych yn eu dilyn",
+  "lists.subheading": "Eich rhestrau",
   "load_pending": "{count, plural, one {# eitem newydd} other {# eitem newydd}}",
   "loading_indicator.label": "Yn llwytho…",
   "media_gallery.hide": "Cuddio",
-  "moved_to_account_banner.text": "Mae eich cyfrif {disabledAccount} wedi ei analluogi ar hyn o bryd am i chi symud i {movedToAccount}.",
+  "moved_to_account_banner.text": "Ar hyn y bryd, mae eich cyfrif {disabledAccount} wedi ei analluogi am i chi symud i {movedToAccount}.",
   "mute_modal.hide_from_notifications": "Cuddio rhag hysbysiadau",
   "mute_modal.hide_options": "Cuddio'r dewis",
   "mute_modal.indefinite": "Nes i mi eu dad-dewi",
@@ -554,27 +499,23 @@
   "navigation_bar.search": "Chwilio",
   "navigation_bar.security": "Diogelwch",
   "not_signed_in_indicator.not_signed_in": "Rhaid i chi fewngofnodi i weld yr adnodd hwn.",
-  "notification.admin.report": "Adroddodd {name} {target}",
+  "notification.admin.report": "Adroddwyd ar {name} {target}",
   "notification.admin.report_account": "Adroddodd {name} {count, plural, one {un postiad} other {# postiad}} gan {target} oherwydd {category}",
   "notification.admin.report_account_other": "Adroddodd {name} {count, plural, one {un postiad} two {# bostiad} few {# postiad} other {# postiad}} gan {target}",
   "notification.admin.report_statuses": "Adroddodd {name} {target} ar gyfer {category}",
   "notification.admin.report_statuses_other": "Adroddodd {name} {target}",
   "notification.admin.sign_up": "Cofrestrodd {name}",
   "notification.admin.sign_up.name_and_others": "Cofrestrodd {name} {count, plural, one {ac # arall} other {a # arall}}",
-  "notification.annual_report.message": "Mae eich #Wrapstodon {year} yn aros i chi! Gwelwch eich uchafbwyntiau ac amseroedd i'w cofio o'r flwyddyn hon ar Mastodon!",
-  "notification.annual_report.view": "Gweld #Wrapstodon",
   "notification.favourite": "Ffafriodd {name} eich postiad",
   "notification.favourite.name_and_others_with_link": "Ffafriodd {name} a <a>{count, plural, one {# arall} other {# arall}}</a> eich postiad",
-  "notification.favourite_pm": "Mae {name} wedi ffefrynnu eich cyfeiriad preifat",
-  "notification.favourite_pm.name_and_others_with_link": "Mae {name} a <a>{count, plural, one {# arall} other {# arall}}</a> wedi hoffi'ch crybwylliad preifat",
   "notification.follow": "Dilynodd {name} chi",
   "notification.follow.name_and_others": "Mae {name} a <a>{count, plural, zero {}one {# arall} two {# arall} few {# arall} many {# others} other {# arall}}</a> nawr yn eich dilyn chi",
   "notification.follow_request": "Mae {name} wedi gwneud cais i'ch dilyn",
   "notification.follow_request.name_and_others": "Mae {name} a{count, plural, one {# arall} other {# arall}} wedi gofyn i'ch dilyn chi",
   "notification.label.mention": "Crybwyll",
   "notification.label.private_mention": "Crybwyll preifat",
-  "notification.label.private_reply": "Ymateb preifat",
-  "notification.label.reply": "Ymateb",
+  "notification.label.private_reply": "Ateb preifat",
+  "notification.label.reply": "Ateb",
   "notification.mention": "Crybwyll",
   "notification.mentioned_you": "Rydych wedi'ch crybwyll gan {name}",
   "notification.moderation-warning.learn_more": "Dysgu mwy",
@@ -671,21 +612,44 @@
   "notifications_permission_banner.enable": "Galluogi hysbysiadau bwrdd gwaith",
   "notifications_permission_banner.how_to_control": "I dderbyn hysbysiadau pan nad yw Mastodon ar agor, galluogwch hysbysiadau bwrdd gwaith. Gallwch reoli'n union pa fathau o ryngweithiadau sy'n cynhyrchu hysbysiadau bwrdd gwaith trwy'r botwm {icon} uchod unwaith y byddan nhw wedi'u galluogi.",
   "notifications_permission_banner.title": "Peidiwch â cholli dim",
-  "onboarding.follows.back": "Nôl",
-  "onboarding.follows.done": "Wedi gorffen",
+  "onboarding.action.back": "Ewch â fi nôl",
+  "onboarding.actions.back": "Ewch â fi nôl",
+  "onboarding.actions.go_to_explore": "Gweld y pynciau llosg",
+  "onboarding.actions.go_to_home": "Ewch i'm ffrwd gartref",
+  "onboarding.compose.template": "Helo, #Mastodon!",
   "onboarding.follows.empty": "Yn anffodus, nid oes modd dangos unrhyw ganlyniadau ar hyn o bryd. Gallwch geisio defnyddio chwilio neu bori'r dudalen archwilio i ddod o hyd i bobl i'w dilyn, neu ceisio eto yn nes ymlaen.",
-  "onboarding.follows.search": "Chwilio",
-  "onboarding.follows.title": "Dilynwch bobl i gychwyn arni",
+  "onboarding.follows.lead": "Rydych chi'n curadu eich ffrwd gartref eich hun. Po fwyaf o bobl y byddwch chi'n eu dilyn, y mwyaf egnïol a diddorol fydd hi. Gall y proffiliau hyn fod yn fan cychwyn da - gallwch chi bob amser eu dad-ddilyn yn nes ymlaen:",
+  "onboarding.follows.title": "Personolwch eich ffrwd gartref",
   "onboarding.profile.discoverable": "Gwnewch fy mhroffil yn un y gellir ei ddarganfod",
   "onboarding.profile.discoverable_hint": "Pan fyddwch yn optio i mewn i ddarganfodadwyedd ar Mastodon, gall eich postiadau ymddangos mewn canlyniadau chwilio a threndiau, ac efallai y bydd eich proffil yn cael ei awgrymu i bobl sydd â diddordebau tebyg i chi.",
   "onboarding.profile.display_name": "Enw dangos",
   "onboarding.profile.display_name_hint": "Eich enw llawn neu'ch enw hwyl…",
+  "onboarding.profile.lead": "Gallwch chi bob amser gwblhau hyn yn ddiweddarach yn y gosodiadau, lle mae hyd yn oed mwy o ddewisiadau cyfaddasu ar gael.",
   "onboarding.profile.note": "Bywgraffiad",
   "onboarding.profile.note_hint": "Gallwch @grybwyll pobl eraill neu #hashnodau…",
   "onboarding.profile.save_and_continue": "Cadw a pharhau",
   "onboarding.profile.title": "Gosodiad proffil",
   "onboarding.profile.upload_avatar": "Llwytho llun proffil",
   "onboarding.profile.upload_header": "Llwytho pennyn proffil",
+  "onboarding.share.lead": "Cofiwch ddweud wrth bobl sut y gallan nhw ddod o hyd i chi ar Mastodon!",
+  "onboarding.share.message": "Fi yw {username} ar #Mastodon! Dewch i'm dilyn i yn {url}",
+  "onboarding.share.next_steps": "Camau nesaf posib:",
+  "onboarding.share.title": "Rhannwch eich proffil",
+  "onboarding.start.lead": "Mae eich cyfrif Mastodon newydd yn barod! Dyma sut y gallwch chi wneud y gorau ohono:",
+  "onboarding.start.skip": "Eisiau mynd syth yn eich blaen?",
+  "onboarding.start.title": "Rydych chi wedi cyrraedd!",
+  "onboarding.steps.follow_people.body": "Rydych chi'n curadu eich ffrwd eich hun. Gadewch i ni ei lenwi â phobl ddiddorol.",
+  "onboarding.steps.follow_people.title": "Personolwch eich ffrwd gartref",
+  "onboarding.steps.publish_status.body": "Dywedwch helo wrth y byd gyda thestun, lluniau, fideos neu arolygon barn {emoji}",
+  "onboarding.steps.publish_status.title": "Gwnewch eich postiad cyntaf",
+  "onboarding.steps.setup_profile.body": "Mae eraill yn fwy tebygol o ryngweithio â chi gyda phroffil wedi'i lenwi.",
+  "onboarding.steps.setup_profile.title": "Cyfaddaswch eich proffil",
+  "onboarding.steps.share_profile.body": "Gadewch i'ch ffrindiau wybod sut i ddod o hyd i chi ar Mastodon",
+  "onboarding.steps.share_profile.title": "Rhannwch eich proffil",
+  "onboarding.tips.2fa": "<strong>Oeddech chi'n gwybod?</strong> Gallwch ddiogelu'ch cyfrif trwy osod dilysiad dau ffactor yng ngosodiadau eich cyfrif. Mae'n gweithio gydag unrhyw app TOTP o'ch dewis, nid oes angen rhif ffôn!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Oeddech chi'n gwybod?</strong> Gan fod Mastodon wedi'i ddatganoli, bydd rhai proffiliau y dewch ar eu traws yn cael eu cynnal ar weinyddion heblaw eich un chi. Ac eto gallwch chi ryngweithio â nhw yn hawdd! Mae eu gweinydd yn ail hanner eu henw defnyddiwr!",
+  "onboarding.tips.migration": "<strong>Oeddech chi'n gwybod?</strong> Os ydych chi'n teimlo nad yw {domain} yn ddewis gweinydd gwych i chi i'r dyfodol, gallwch chi symud i weinydd Mastodon arall heb golli'ch dilynwyr. Gallwch chi hyd yn oed gynnal eich gweinydd eich hun!",
+  "onboarding.tips.verification": "<strong>Oeddech chi'n gwybod?</strong> Gallwch wirio'ch cyfrif trwy roi dolen i'ch proffil Mastodon ar eich gwefan eich hun ac ychwanegu'r wefan at eich proffil. Nid oes angen ffioedd na dogfennau!",
   "password_confirmation.exceeds_maxlength": "Mae'r cadarnhad cyfrinair yn fwy nag uchafswm hyd y cyfrinair",
   "password_confirmation.mismatching": "Nid yw'r cadarnhad cyfrinair yn cyfateb",
   "picture_in_picture.restore": "Rhowch ef yn ôl",
@@ -701,7 +665,7 @@
   "poll_button.remove_poll": "Tynnu pleidlais",
   "privacy.change": "Addasu preifatrwdd y post",
   "privacy.direct.long": "Pawb sydd â sôn amdanyn nhw yn y postiad",
-  "privacy.direct.short": "Crybwylliad preifat",
+  "privacy.direct.short": "Pobl benodol",
   "privacy.private.long": "Eich dilynwyr yn unig",
   "privacy.private.short": "Dilynwyr",
   "privacy.public.long": "Unrhyw ar ac oddi ar Mastodon",
@@ -713,8 +677,8 @@
   "privacy_policy.title": "Polisi Preifatrwydd",
   "recommended": "Argymhellwyd",
   "refresh": "Adnewyddu",
-  "regeneration_indicator.please_stand_by": "Arhoswch am dipyn.",
-  "regeneration_indicator.preparing_your_home_feed": "Paratoi eich llif cartref…",
+  "regeneration_indicator.label": "Yn llwytho…",
+  "regeneration_indicator.sublabel": "Mae eich ffrwd cartref yn cael ei pharatoi!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# diwrnod} other {# diwrnod}} yn ôl",
   "relative_time.full.hours": "{number, plural, one {# awr} other {# awr}} yn ôl",
@@ -735,7 +699,7 @@
   "report.categories.other": "Arall",
   "report.categories.spam": "Sbam",
   "report.categories.violation": "Mae cynnwys yn torri un neu fwy o reolau'r gweinydd",
-  "report.category.subtitle": "Dewiswch yr ateb gorau",
+  "report.category.subtitle": "Dewiswch y gyfatebiaeth gorau",
   "report.category.title": "Beth sy'n digwydd gyda'r {type} yma?",
   "report.category.title_account": "proffil",
   "report.category.title_status": "post",
@@ -798,8 +762,7 @@
   "search_results.accounts": "Proffilau",
   "search_results.all": "Popeth",
   "search_results.hashtags": "Hashnodau",
-  "search_results.no_results": "Dim canlyniadau.",
-  "search_results.no_search_yet": "Ceisiwch chwilio am bostiadau, proffiliau neu hashnodau.",
+  "search_results.nothing_found": "Methu dod o hyd i unrhyw beth ar gyfer y termau chwilio hyn",
   "search_results.see_all": "Gweld y cyfan",
   "search_results.statuses": "Postiadau",
   "search_results.title": "Chwilio am {q}",
@@ -854,10 +817,9 @@
   "status.reblogs.empty": "Does neb wedi hybio'r post yma eto. Pan y bydd rhywun yn gwneud, byddent yn ymddangos yma.",
   "status.redraft": "Dileu ac ailddrafftio",
   "status.remove_bookmark": "Tynnu nod tudalen",
-  "status.remove_favourite": "Tynnu o'r ffefrynnau",
   "status.replied_in_thread": "Atebodd mewn edefyn",
   "status.replied_to": "Wedi ateb {name}",
-  "status.reply": "Ymateb",
+  "status.reply": "Ateb",
   "status.replyAll": "Ateb i edefyn",
   "status.report": "Adrodd ar @{name}",
   "status.sensitive_warning": "Cynnwys sensitif",
@@ -876,9 +838,6 @@
   "subscribed_languages.target": "Newid ieithoedd tanysgrifio {target}",
   "tabs_bar.home": "Cartref",
   "tabs_bar.notifications": "Hysbysiadau",
-  "terms_of_service.effective_as_of": "Yn effeithiol ers {date}",
-  "terms_of_service.title": "Telerau Gwasanaeth",
-  "terms_of_service.upcoming_changes_on": "Newidiadau i ddod ar {date}",
   "time_remaining.days": "{number, plural, one {# diwrnod} other {# diwrnod}} ar ôl",
   "time_remaining.hours": "{number, plural, one {# awr} other {# awr}} ar ôl",
   "time_remaining.minutes": "{number, plural, one {# munud} other {# munud}} ar ôl",
@@ -894,12 +853,26 @@
   "upload_button.label": "Ychwanegwch gyfryngau (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Wedi pasio'r uchafswm llwytho.",
   "upload_error.poll": "Nid oes modd llwytho ffeiliau â phleidleisiau.",
+  "upload_form.audio_description": "Disgrifio ar gyfer pobl sydd â cholled clyw",
+  "upload_form.description": "Disgrifio i'r rheini a nam ar ei golwg",
   "upload_form.drag_and_drop.instructions": "I godi atodiad cyfryngau, pwyswch y space neu enter. Wrth lusgo, defnyddiwch y bysellau saeth i symud yr atodiad cyfryngau i unrhyw gyfeiriad penodol. Pwyswch space neu enter eto i ollwng yr atodiad cyfryngau yn ei safle newydd, neu pwyswch escape i ddiddymu.",
   "upload_form.drag_and_drop.on_drag_cancel": "Cafodd llusgo ei ddiddymu. Cafodd atodiad cyfryngau {item} ei ollwng.",
   "upload_form.drag_and_drop.on_drag_end": "Cafodd atodiad cyfryngau {item} ei ollwng.",
   "upload_form.drag_and_drop.on_drag_over": "Symudwyd atodiad cyfryngau {item}.",
   "upload_form.drag_and_drop.on_drag_start": "Atodiad cyfryngau godwyd {item}.",
   "upload_form.edit": "Golygu",
+  "upload_form.thumbnail": "Newid llun bach",
+  "upload_form.video_description": "Disgrifio ar gyfer pobl sydd â cholled clyw neu amhariad golwg",
+  "upload_modal.analyzing_picture": "Yn dadansoddi llun…",
+  "upload_modal.apply": "Gosod",
+  "upload_modal.applying": "Yn gosod…",
+  "upload_modal.choose_image": "Dewis delwedd",
+  "upload_modal.description_placeholder": "Mae ei phen bach llawn jocs, 'run peth a fy nghot golff, rhai dyddiau",
+  "upload_modal.detect_text": "Canfod testun o'r llun",
+  "upload_modal.edit_media": "Golygu cyfryngau",
+  "upload_modal.hint": "Cliciwch neu llusgwch y cylch ar y rhagolwg i ddewis y canolbwynt a fydd bob amser i'w weld ar bob llun bach.",
+  "upload_modal.preparing_ocr": "Yn paratoi OCR…",
+  "upload_modal.preview_label": "Rhagolwg ({ratio})",
   "upload_progress.label": "Yn llwytho...",
   "upload_progress.processing": "Wrthi'n prosesu…",
   "username.taken": "Mae'r enw defnyddiwr hwnnw'n cael ei ddefnyddio eisoes. Rhowch gynnig ar un arall",
@@ -909,12 +882,8 @@
   "video.expand": "Ymestyn fideo",
   "video.fullscreen": "Sgrin llawn",
   "video.hide": "Cuddio fideo",
-  "video.mute": "Tewi",
+  "video.mute": "Diffodd sain",
   "video.pause": "Oedi",
   "video.play": "Chwarae",
-  "video.skip_backward": "Symud nôl",
-  "video.skip_forward": "Symud ymlaen",
-  "video.unmute": "Dad-dewi",
-  "video.volume_down": "Lefel sain i lawr",
-  "video.volume_up": "Lefel sain i fyny"
+  "video.unmute": "Dad-dewi sain"
 }
diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json
index e1d8e7aec2..4a48cc6fa8 100644
--- a/app/javascript/mastodon/locales/da.json
+++ b/app/javascript/mastodon/locales/da.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Redigér profil",
   "account.enable_notifications": "Advisér mig, når @{name} poster",
   "account.endorse": "Fremhæv på profil",
-  "account.featured": "Fremhævet",
-  "account.featured.hashtags": "Hashtags",
-  "account.featured.posts": "Indlæg",
   "account.featured_tags.last_status_at": "Seneste indlæg {date}",
   "account.featured_tags.last_status_never": "Ingen indlæg",
+  "account.featured_tags.title": "{name}s fremhævede etiketter",
   "account.follow": "Følg",
   "account.follow_back": "Følg tilbage",
   "account.followers": "Følgere",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}",
   "account.unblock": "Fjern blokering af @{name}",
   "account.unblock_domain": "Fjern blokering af domænet {domain}",
-  "account.unblock_domain_short": "Afblokér",
   "account.unblock_short": "Fjern blokering",
   "account.unendorse": "Fjern visning på din profil",
   "account.unfollow": "Følg ikke længere",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "En uventet fejl opstod.",
   "alert.unexpected.title": "Ups!",
   "alt_text_badge.title": "Alt text",
-  "alt_text_modal.add_alt_text": "Tilføj alternativ tekst",
-  "alt_text_modal.add_text_from_image": "Tilføj tekst fra billede",
-  "alt_text_modal.cancel": "Afbryd",
-  "alt_text_modal.change_thumbnail": "Skift miniature",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Beskriv dette for personer med nedsat hørelse…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Beskriv dette for personer med nedsat syn…",
-  "alt_text_modal.done": "Færdig",
   "announcement.announcement": "Bekendtgørelse",
-  "annual_report.summary.archetype.booster": "Fremhæveren",
-  "annual_report.summary.archetype.lurker": "Lureren",
-  "annual_report.summary.archetype.oracle": "Oraklet",
-  "annual_report.summary.archetype.pollster": "Afstemningsmageren",
-  "annual_report.summary.archetype.replier": "Den social sommerfugl",
-  "annual_report.summary.followers.followers": "følgere",
-  "annual_report.summary.followers.total": "{count} i alt",
-  "annual_report.summary.here_it_is": "Her er dit {year} i sammendrag:",
-  "annual_report.summary.highlighted_post.by_favourites": "mest favoritmarkerede indlæg",
-  "annual_report.summary.highlighted_post.by_reblogs": "mest fremhævede indlæg",
-  "annual_report.summary.highlighted_post.by_replies": "mest besvarede indlæg",
-  "annual_report.summary.highlighted_post.possessive": "{name}s",
-  "annual_report.summary.most_used_app.most_used_app": "mest benyttede app",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "mest benyttede etiket",
-  "annual_report.summary.most_used_hashtag.none": "Intet",
-  "annual_report.summary.new_posts.new_posts": "nye indlæg",
-  "annual_report.summary.percentile.text": "<topLabel>Det betyder, at man er i top</topLabel><percentage></percentage><bottomLabel>af {domain}-brugere.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Vi fortæller det ikke til Pernille Skipper.",
-  "annual_report.summary.thanks": "Tak for at være en del af Mastodon!",
   "attachments_list.unprocessed": "(ubehandlet)",
   "audio.hide": "Skjul lyd",
   "block_modal.remote_users_caveat": "Serveren {domain} vil blive bedt om at respektere din beslutning. Overholdelse er dog ikke garanteret, da nogle servere kan håndtere blokke forskelligt. Offentlige indlæg kan stadig være synlige for ikke-indloggede brugere.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "Den anmodede side kunne ikke findes. Er du sikker på, at URL'en er korrekt?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Luk",
-  "bundle_modal_error.message": "Noget gik galt under indlæsningen af denne skærm.",
+  "bundle_modal_error.message": "Noget gik galt under indlæsningen af denne komponent.",
   "bundle_modal_error.retry": "Forsøg igen",
   "closed_registrations.other_server_instructions": "Da Mastodon er decentraliseret, kan du oprette en konto på en anden server og stadig interagere med denne.",
   "closed_registrations_modal.description": "Oprettelse af en konto på {domain} er i øjeblikket ikke muligt, men husk på, at du ikke behøver en konto specifikt på {domain} for at bruge Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Blokerede brugere",
   "column.bookmarks": "Bogmærker",
   "column.community": "Lokal tidslinje",
-  "column.create_list": "Opret liste",
   "column.direct": "Private omtaler",
   "column.directory": "Tjek profiler",
   "column.domain_blocks": "Blokerede domæner",
-  "column.edit_list": "Redigér liste",
   "column.favourites": "Favoritter",
-  "column.firehose": "Aktuelt",
+  "column.firehose": "Realtids-strømme",
   "column.follow_requests": "Følgeanmodninger",
   "column.home": "Hjem",
-  "column.list_members": "Håndtér listemedlemmer",
   "column.lists": "Lister",
   "column.mutes": "Skjulte brugere",
   "column.notifications": "Notifikationer",
@@ -172,7 +140,6 @@
   "column_header.pin": "Fastgør",
   "column_header.show_settings": "Vis indstillinger",
   "column_header.unpin": "Frigør",
-  "column_search.cancel": "Afbryd",
   "column_subheading.settings": "Indstillinger",
   "community.column_settings.local_only": "Kun lokalt",
   "community.column_settings.media_only": "Kun medier",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Afstemningens varighed",
   "compose_form.poll.multiple": "Multivalg",
   "compose_form.poll.option_placeholder": "Valgmulighed {number}",
-  "compose_form.poll.single": "Enkeltvalg",
+  "compose_form.poll.single": "Vælg én",
   "compose_form.poll.switch_to_multiple": "Ændr afstemning til flervalgstype",
   "compose_form.poll.switch_to_single": "Ændr afstemning til enkeltvalgstype",
   "compose_form.poll.type": "Stil",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Redigér",
   "confirmations.edit.message": "Redigeres nu, overskrive den besked, der forfattes pt. Fortsæt alligevel?",
   "confirmations.edit.title": "Overskriv indlæg?",
-  "confirmations.follow_to_list.confirm": "Følg og føj til liste",
-  "confirmations.follow_to_list.message": "Man skal følge {name} for at føje vedkommende til en liste.",
-  "confirmations.follow_to_list.title": "Følg bruger?",
   "confirmations.logout.confirm": "Log ud",
   "confirmations.logout.message": "Er du sikker på, at du vil logge ud?",
   "confirmations.logout.title": "Log ud?",
-  "confirmations.missing_alt_text.confirm": "Tilføj alt-tekst",
-  "confirmations.missing_alt_text.message": "Indlægget indeholder medier uden alt-tekst. Tilføjelse af beskrivelser bidrager til at gøre indholdet tilgængeligt for flere brugere.",
-  "confirmations.missing_alt_text.secondary": "Læg op alligevel",
-  "confirmations.missing_alt_text.title": "Tilføj alt-tekst?",
   "confirmations.mute.confirm": "Skjul",
   "confirmations.redraft.confirm": "Slet og omformulér",
   "confirmations.redraft.message": "Sikker på, at dette indlæg skal slettes og omskrives? Favoritter og fremhævelser går tabt, og svar til det oprindelige indlæg mister tilknytningen.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Din konto {disabledAccount} er pt. deaktiveret.",
   "dismissable_banner.community_timeline": "Disse er de seneste offentlige indlæg fra personer med konti hostet af {domain}.",
   "dismissable_banner.dismiss": "Afvis",
-  "dismissable_banner.explore_links": "Disse nyhedshistorier deles mest på fødiverset i dag. Nyere nyhedshistorier lagt op af flere forskellige personer rangeres højere.",
-  "dismissable_banner.explore_statuses": "Disse indlæg på tværs af fødiverset opnår momentum i dag. Nyere indlæg med flere fremhævninger og favoritmærker rangeres højere.",
-  "dismissable_banner.explore_tags": "Disse etiketter opnår momentum på fødiverset i dag. Etiketter brugt af flere forskellige personer rangeres højere.",
-  "dismissable_banner.public_timeline": "Dette er de seneste offentlige indlæg fra personer på fødiverset, som folk på {domain} følger.",
+  "dismissable_banner.explore_links": "Der tales lige nu om disse nyhedshistorier af folk på denne og andre servere i det decentraliserede netværk.",
+  "dismissable_banner.explore_statuses": "Disse indlæg fra diverse sociale netværk vinder fodfæste i dag. Nyere indlæg med flere fremhævelser og favoritter rangeres højere.",
+  "dismissable_banner.explore_tags": "Disse etiketter vinder lige nu fodfæste blandt folk på denne og andre servere i det decentraliserede netværk.",
+  "dismissable_banner.public_timeline": "Dette er de seneste offentlige indlæg fra folk på det sociale netværk, som folk på {domain} følger.",
   "domain_block_modal.block": "Blokér server",
   "domain_block_modal.block_account_instead": "Blokér i stedet @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Folk fra denne server kan interagere med de gamle indlæg.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Søgeresultater",
   "emoji_button.symbols": "Symboler",
   "emoji_button.travel": "Rejser og steder",
-  "empty_column.account_featured": "Denne liste er tom",
   "empty_column.account_hides_collections": "Brugeren har valgt ikke at gøre denne information tilgængelig",
   "empty_column.account_suspended": "Konto suspenderet",
   "empty_column.account_timeline": "Ingen indlæg her!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Der er intet med denne etiket endnu.",
   "empty_column.home": "Din hjemmetidslinje er tom! Følg nogle personer, for at fylde den op.",
   "empty_column.list": "Der er ikke noget på denne liste endnu. Når medlemmer af listen udgiver nye indlæg vil de fremgå her.",
+  "empty_column.lists": "Du har endnu ingen lister. Når du opretter én, vil den fremgå hér.",
   "empty_column.mutes": "Du har endnu ikke skjult nogle brugere.",
   "empty_column.notification_requests": "Alt er klar! Der er intet her. Når der modtages nye notifikationer, fremgår de her jævnfør dine indstillinger.",
   "empty_column.notifications": "Du har endnu ingen notifikationer. Når andre interagerer med dig, vil det fremgå her.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Prøv at deaktivere dem og genindlæse siden. Hvis det ikke hjælper, kan Mastodon muligvis stadig bruges via en anden netlæser eller app.",
   "errors.unexpected_crash.copy_stacktrace": "Kopiér stacktrace til udklipsholderen",
   "errors.unexpected_crash.report_issue": "Anmeld problem",
+  "explore.search_results": "Søgeresultater",
   "explore.suggested_follows": "Personer",
   "explore.title": "Udforsk",
   "explore.trending_links": "Nyheder",
@@ -373,16 +334,13 @@
   "footer.about": "Om",
   "footer.directory": "Profiloversigt",
   "footer.get_app": "Hent appen",
+  "footer.invite": "Invitér personer",
   "footer.keyboard_shortcuts": "Tastaturgenveje",
   "footer.privacy_policy": "Privatlivspolitik",
   "footer.source_code": "Vis kildekode",
   "footer.status": "Status",
-  "footer.terms_of_service": "Tjenestevilkår",
   "generic.saved": "Gemt",
   "getting_started.heading": "Startmenu",
-  "hashtag.admin_moderation": "Åbn modereringsbrugerflade for #{name}",
-  "hashtag.browse": "Gennemse indlæg i #{hashtag}",
-  "hashtag.browse_from_account": "Gennemse indlæg fra @{name} i #{hashtag}",
   "hashtag.column_header.tag_mode.all": "og {additional}",
   "hashtag.column_header.tag_mode.any": "eller {additional}",
   "hashtag.column_header.tag_mode.none": "uden {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}} i dag",
   "hashtag.follow": "Følg etiket",
-  "hashtag.mute": "Tavsgør #{hashtag}",
   "hashtag.unfollow": "Stop med at følge etiket",
   "hashtags.and_other": "…og {count, plural, one {}other {# flere}}",
   "hints.profiles.followers_may_be_missing": "Der kan mangle følgere for denne profil.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignorér notifikationer fra folk, som ikke er følgere?",
   "ignore_notifications_modal.not_following_title": "Ignorér notifikationer fra folk, man ikke følger?",
   "ignore_notifications_modal.private_mentions_title": "Ignorér notifikationer fra uopfordrede Private omtaler?",
-  "info_button.label": "Hjælp",
-  "info_button.what_is_alt_text": "<h1>Hvad er alt-tekst?</h1> <p>Alt-tekst leverer billedbeskrivelser til folk med synsnedsættelser, lav båndbredde-forbindelser eller med ønske om ekstra kontekst.</p> <p>Tilgængelighed og forståelse kan forbedres for alle ved at skrive klar, kortfattet og objektiv alt-tekst.</p> <ul> <li>Fang vigtige elementer</li> <li>Opsummér tekst i billeder</li> <li>Brug almindelig sætningsstruktur</li> <li>Undgå overflødig information</li> <li>Fokusér på tendenser og centrale resultater i kompleks grafik (såsom diagrammer eller kort)</li> </ul>",
-  "interaction_modal.action.favourite": "For at fortsætte, skal du føje til favoritter fra din konto.",
-  "interaction_modal.action.follow": "For at fortsætte, skal man vælge Følg fra sin konto.",
-  "interaction_modal.action.reblog": "For at fortsætte, skal man vælge Fremhæv fra sin konto.",
-  "interaction_modal.action.reply": "For at fortsætte, skal man besvar fra sin konto.",
-  "interaction_modal.action.vote": "For at fortsætte, skal man stemme fra sin konto.",
-  "interaction_modal.go": "Gå",
-  "interaction_modal.no_account_yet": "Har endnu ingen konto?",
+  "interaction_modal.description.favourite": "Med en konto på Mastodon kan dette indlæg gøres til favorit for at lade forfatteren vide, at det værdsættes og gemmes til senere.",
+  "interaction_modal.description.follow": "Med en konto på Mastodon kan du følge {name} for at modtage vedkommendes indlæg i dit hjemmefeed.",
+  "interaction_modal.description.reblog": "Med en konto på Mastodon kan dette indlæg fremhæves så det deles med egne følgere.",
+  "interaction_modal.description.reply": "Med en konto på Mastodon kan dette indlæg besvares.",
+  "interaction_modal.login.action": "Gå til hjemmeserver",
+  "interaction_modal.login.prompt": "Hjemmeserverdomænet, f.eks. mastodon.social",
+  "interaction_modal.no_account_yet": "Ikke på Mastodon?",
   "interaction_modal.on_another_server": "På en anden server",
   "interaction_modal.on_this_server": "På denne server",
+  "interaction_modal.sign_in": "Du er ikke logget ind på denne server. Hvor hostes din konto?",
+  "interaction_modal.sign_in_hint": "Tip: Det er webstedet, hvor du tilmeldte dig. Har du glemt det, så kig efter velkomstmailen i indbakken. Du kan også angive dit fulde brugernavn! (f.eks. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Føj {name}s indlæg til favoritter",
   "interaction_modal.title.follow": "Følg {name}",
   "interaction_modal.title.reblog": "Fremhæv {name}s indlæg",
   "interaction_modal.title.reply": "Besvar {name}s indlæg",
-  "interaction_modal.title.vote": "Stem i {name}s afstemning",
-  "interaction_modal.username_prompt": "F.eks. {example}",
   "intervals.full.days": "{number, plural, one {# dag} other {# dage}}",
   "intervals.full.hours": "{number, plural, one {# time} other {# timer}}",
   "intervals.full.minutes": "{number, plural, one {# minut} other {# minutter}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Vis/skjul tekst bag emnefelt",
   "keyboard_shortcuts.toggle_sensitivity": "Vis/skjul medier",
   "keyboard_shortcuts.toot": "Påbegynd nyt indlæg",
-  "keyboard_shortcuts.translate": "for at oversætte et indlæg",
   "keyboard_shortcuts.unfocus": "Fjern fokus fra tekstskrivningsområde/søgning",
   "keyboard_shortcuts.up": "Flyt opad på listen",
   "lightbox.close": "Luk",
@@ -490,32 +444,20 @@
   "link_preview.author": "Af {name}",
   "link_preview.more_from_author": "Mere fra {name}",
   "link_preview.shares": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}",
-  "lists.add_member": "Tilføj",
-  "lists.add_to_list": "Føj til liste",
-  "lists.add_to_lists": "Føj {name} til lister",
-  "lists.create": "Opret",
-  "lists.create_a_list_to_organize": "Opret en ny liste til organisering af hjemmefeed",
-  "lists.create_list": "Opret liste",
+  "lists.account.add": "Føj til liste",
+  "lists.account.remove": "Fjern fra liste",
   "lists.delete": "Slet liste",
-  "lists.done": "Færdig",
   "lists.edit": "Redigér liste",
-  "lists.exclusive": "Skjul medlemmer i Hjem",
-  "lists.exclusive_hint": "Er nogen er på denne liste, skjul personen i hjemme-feeds for at undgå at se vedkommendes indlæg to gange.",
-  "lists.find_users_to_add": "Find brugere at tilføje",
-  "lists.list_members": "Liste over medlemmer",
-  "lists.list_members_count": "{count, plural, one {# medlem} other {# medlemmer}}",
-  "lists.list_name": "Listetitel",
-  "lists.new_list_name": "Ny listetitel",
-  "lists.no_lists_yet": "Ingen lister endnu.",
-  "lists.no_members_yet": "Ingen medlemmer endnu.",
-  "lists.no_results_found": "Ingen resultater fundet.",
-  "lists.remove_member": "Fjern",
+  "lists.edit.submit": "Skift titel",
+  "lists.exclusive": "Skjul disse indlæg hjemmefra",
+  "lists.new.create": "Tilføj liste",
+  "lists.new.title_placeholder": "Ny listetitel",
   "lists.replies_policy.followed": "Enhver bruger, der følges",
   "lists.replies_policy.list": "Listemedlemmer",
   "lists.replies_policy.none": "Ingen",
-  "lists.save": "Gem",
-  "lists.search": "Søg",
-  "lists.show_replies_to": "Medtag svar fra listemedlemmer til",
+  "lists.replies_policy.title": "Vis svar til:",
+  "lists.search": "Søg blandt personer, som følges",
+  "lists.subheading": "Dine lister",
   "load_pending": "{count, plural, one {# nyt emne} other {# nye emner}}",
   "loading_indicator.label": "Indlæser…",
   "media_gallery.hide": "Skjul",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} anmeldte {target}",
   "notification.admin.sign_up": "{name} tilmeldte sig",
   "notification.admin.sign_up.name_and_others": "{name} og {count, plural, one {# anden} other {# andre}} tilmeldte sig",
-  "notification.annual_report.message": "{year} #Wrapstodon venter! Afslør årets højdepunkter og mindeværdige øjeblikke på Mastodon!",
-  "notification.annual_report.view": "Vis #Wrapstodon",
   "notification.favourite": "{name} føjede dit indlæg til favoritter",
   "notification.favourite.name_and_others_with_link": "{name} og <a>{count, plural, one {# anden} other {# andre}}</a> føjede dit indlæg til favoritter",
-  "notification.favourite_pm": "{name} føjede din private omtale til favoritter",
-  "notification.favourite_pm.name_and_others_with_link": "{name} og <a>{count, plural, one {# anden} other {# andre}}</a> føjede dit indlæg til favoritter",
   "notification.follow": "{name} begyndte at følge dig",
   "notification.follow.name_and_others": "{name} og <a>{count, plural, one {# andre} other {# andre}}</a> begyndte at følge dig",
   "notification.follow_request": "{name} har anmodet om at følge dig",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Aktivér computernotifikationer",
   "notifications_permission_banner.how_to_control": "Aktivér computernotifikationer for at få besked, når Mastodon ikke er åben. Når de er aktiveret, kan man via knappen {icon} ovenfor præcist styre, hvilke typer af interaktioner, som genererer computernotifikationer.",
   "notifications_permission_banner.title": "Gå aldrig glip af noget",
-  "onboarding.follows.back": "Retur",
-  "onboarding.follows.done": "Færdig",
+  "onboarding.action.back": "Gå tilbage",
+  "onboarding.actions.back": "Gå tilbage",
+  "onboarding.actions.go_to_explore": "Se, hvad som trender",
+  "onboarding.actions.go_to_home": "Gå til hjemme-feed'et",
+  "onboarding.compose.template": "Hej #Mastodon!",
   "onboarding.follows.empty": "Ingen resultater tilgængelige pt. Prøv at bruge søgning eller gennemse siden for at finde personer at følge, eller forsøg igen senere.",
-  "onboarding.follows.search": "Søg",
-  "onboarding.follows.title": "Følg folk for at komme i gang",
+  "onboarding.follows.lead": "Man kurerer sin eget hjemme-feed. Jo flere personer man følger, des mere aktiv og interessant vil det være. Disse profiler kan være et godt udgangspunkt – de kan altid fjernes senere!",
+  "onboarding.follows.title": "Populært på Mastodon",
   "onboarding.profile.discoverable": "Gør min profil synlig",
   "onboarding.profile.discoverable_hint": "Når man vælger at være synlig på Mastodon, kan ens indlæg fremgå i søgeresultater og tendenser, og profilen kan blive foreslået til andre med tilsvarende interesse.",
   "onboarding.profile.display_name": "Vist navn",
   "onboarding.profile.display_name_hint": "Dit fulde navn eller dit sjove navn…",
+  "onboarding.profile.lead": "Dette kan altid færdiggøres senere i indstillingerne, hvor endnu flere tilpasningsmuligheder forefindes.",
   "onboarding.profile.note": "Bio",
   "onboarding.profile.note_hint": "Man kan @omtale andre personer eller #etiketter…",
   "onboarding.profile.save_and_continue": "Gem og fortsæt",
   "onboarding.profile.title": "Profilopsætning",
   "onboarding.profile.upload_avatar": "Upload profilbillede",
   "onboarding.profile.upload_header": "Upload profilbanner",
+  "onboarding.share.lead": "Lad folk vide, hvordan de kan finde dig på Mastodon!",
+  "onboarding.share.message": "Jeg er {username} på #Mastodon! Følg mig på {url}",
+  "onboarding.share.next_steps": "Mulige næste trin:",
+  "onboarding.share.title": "Del profilen",
+  "onboarding.start.lead": "Den nye Mastodon konto er klar til brug. Sådan kan man få mest muligt ud af den:",
+  "onboarding.start.skip": "Vil springe længere frem?",
+  "onboarding.start.title": "Du klarede det!",
+  "onboarding.steps.follow_people.body": "Man kurerer sit eget feed. Lad os fylde det med interessante personer.",
+  "onboarding.steps.follow_people.title": "Følg {count, plural, one {en person} other {# personer}}",
+  "onboarding.steps.publish_status.body": "Sig hej til verden med tekst, billeder, videoer eller afstemninger {emoji}",
+  "onboarding.steps.publish_status.title": "Skriv dit første indlæg",
+  "onboarding.steps.setup_profile.body": "Andre er mere tilbøjelige til at interagere, hvis man har udfyldt sin profil.",
+  "onboarding.steps.setup_profile.title": "Tilpas profilen",
+  "onboarding.steps.share_profile.body": "Lad vennerne vide, hvordan de finder dig på Mastodon!",
+  "onboarding.steps.share_profile.title": "Del profilen",
+  "onboarding.tips.2fa": "<strong>Vidste du?</strong> Man kan sikre sin konto ved at opsætte tofaktorgodkendelse i kontoindstillingerne. Det virker med enhver valgt TOTP-app, intet telefonnummer nødvendigt!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Vidste du?</strong> Da Mastodon er decentraliseret, vil nogle af de profiler, man støder på, være hostet på andre servere end ens egen. Alligevel kan man interagere med dem problemfrit! Deres servernavn udgør anden halvdel af deres brugernavn!",
+  "onboarding.tips.migration": "<strong>Vidste du?</strong> Synes man ikke, at {domain} er et godt servervalg fremadrettet, kan man flytte til en anden Mastodon-server uden at miste sine følgere. Man kan endda hoste sin egen server!",
+  "onboarding.tips.verification": "<strong>Vidste du det?</strong> Man kan bekræfte sin konto ved at placere sit Mastodon-profillink på sin egen websted og føje webstedet til sin profil. Ingen gebyrer eller dokumenter påkrævet!",
   "password_confirmation.exceeds_maxlength": "Adgangskodebekræftelse overstiger maks. adgangskodelængde",
   "password_confirmation.mismatching": "Adgangskodebekræftelse matcher ikke",
   "picture_in_picture.restore": "Indsæt det igen",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Fjern afstemning",
   "privacy.change": "Tilpas indlægsfortrolighed",
   "privacy.direct.long": "Alle omtalt i indlægget",
-  "privacy.direct.short": "Privat omtale",
+  "privacy.direct.short": "Bestemte personer",
   "privacy.private.long": "Kun dine følgere",
   "privacy.private.short": "Følgere",
   "privacy.public.long": "Alle på og udenfor Mastodon",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Privatlivspolitik",
   "recommended": "Anbefalet",
   "refresh": "Genindlæs",
-  "regeneration_indicator.please_stand_by": "Vent venligst.",
-  "regeneration_indicator.preparing_your_home_feed": "Forbereder hjemmestrømmen…",
+  "regeneration_indicator.label": "Indlæser…",
+  "regeneration_indicator.sublabel": "Din hjemmetidslinje klargøres!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# dag} other {# dage}} siden",
   "relative_time.full.hours": "{number, plural, one {# time} other {# timer}} siden",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Profiler",
   "search_results.all": "Alle",
   "search_results.hashtags": "Etiketter",
-  "search_results.no_results": "Ingen resultater.",
-  "search_results.no_search_yet": "Prøv at søge efter indlæg, profiler eller etiketter.",
+  "search_results.nothing_found": "Ingen resultater for disse søgeord",
   "search_results.see_all": "Vis alle",
   "search_results.statuses": "Indlæg",
-  "search_results.title": "Søg efter \"{q}\"",
+  "search_results.title": "Søg efter {q}",
   "server_banner.about_active_users": "Folk, som brugte denne server de seneste 30 dage (månedlige aktive brugere)",
   "server_banner.active_users": "aktive brugere",
   "server_banner.administered_by": "Håndteres af:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Ingen har endnu fremhævet dette indlæg. Når nogen gør, vil det fremgå hér.",
   "status.redraft": "Slet og omformulér",
   "status.remove_bookmark": "Fjern bogmærke",
-  "status.remove_favourite": "Fjern fra favoritter",
   "status.replied_in_thread": "Svaret i tråd",
   "status.replied_to": "Svarede {name}",
   "status.reply": "Besvar",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Skift abonnementssprog for {target}",
   "tabs_bar.home": "Hjem",
   "tabs_bar.notifications": "Notifikationer",
-  "terms_of_service.effective_as_of": "Gældende pr. {date}",
-  "terms_of_service.title": "Tjenestevilkår",
-  "terms_of_service.upcoming_changes_on": "Kommende ændringer pr. {date}",
   "time_remaining.days": "{number, plural, one {# dag} other {# dage}} tilbage",
   "time_remaining.hours": "{number, plural, one {# time} other {# timer}} tilbage",
   "time_remaining.minutes": "{number, plural, one {# minut} other {# minutter}} tilbage",
@@ -897,12 +853,26 @@
   "upload_button.label": "Tilføj billed-, video- eller lydfil(er)",
   "upload_error.limit": "Grænse for filupload nået.",
   "upload_error.poll": "Filupload ikke tilladt for afstemninger.",
+  "upload_form.audio_description": "Beskrivelse til hørehæmmede",
+  "upload_form.description": "Beskrivelse til svagtseende",
   "upload_form.drag_and_drop.instructions": "For at opsamle en medievedhæftning, tryk på Mellemrum eller Retur. Mens der trækkes, benyt piletasterne til at flytte medievedhæftningen i en given retning. Tryk på Mellemrum eller Retur igen for at slippe medievedhæftningen på den nye position, eller tryk på Escape for at afbryde.",
   "upload_form.drag_and_drop.on_drag_cancel": "Træk blev afbrudt. Medievedhæftningen {item} blev sluppet.",
   "upload_form.drag_and_drop.on_drag_end": "Medievedhæftningen {item} er sluppet.",
   "upload_form.drag_and_drop.on_drag_over": "Medievedhæftningen {item} er flyttet.",
   "upload_form.drag_and_drop.on_drag_start": "Opsamlede medievedhæftningen {item}.",
   "upload_form.edit": "Redigér",
+  "upload_form.thumbnail": "Skift miniature",
+  "upload_form.video_description": "Beskrivelse for hørehæmmede eller synshandicappede personer",
+  "upload_modal.analyzing_picture": "Analyserer billede…",
+  "upload_modal.apply": "Anvend",
+  "upload_modal.applying": "Effektuerer…",
+  "upload_modal.choose_image": "Vælg billede",
+  "upload_modal.description_placeholder": "En hurtig brun ræv hopper over den dovne hund",
+  "upload_modal.detect_text": "Detektér tekst i billede",
+  "upload_modal.edit_media": "Redigér medie",
+  "upload_modal.hint": "Klik eller træk cirklen i forhåndsvisningen for at vælge det fokuspunkt, der altid vil være synligt på alle miniaturer.",
+  "upload_modal.preparing_ocr": "Klargør OCR…",
+  "upload_modal.preview_label": "Forhåndsvisning ({ratio})",
   "upload_progress.label": "Uploader...",
   "upload_progress.processing": "Behandler…",
   "username.taken": "Brugernavnet er taget. Prøv et andet",
@@ -912,12 +882,8 @@
   "video.expand": "Udvid video",
   "video.fullscreen": "Fuldskærm",
   "video.hide": "Skjul video",
-  "video.mute": "Slå lyd fra",
+  "video.mute": "Sluk for lyden",
   "video.pause": "Sæt på pause",
   "video.play": "Afspil",
-  "video.skip_backward": "Overspring baglæns",
-  "video.skip_forward": "Overspring fremad",
-  "video.unmute": "Slå lyd tl",
-  "video.volume_down": "Lydstyrke ned",
-  "video.volume_up": "Lydstyrke op"
+  "video.unmute": "Tænd for lyden"
 }
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index 0d5f2f2104..b528531e88 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Profil bearbeiten",
   "account.enable_notifications": "Benachrichtige mich wenn @{name} etwas postet",
   "account.endorse": "Im Profil empfehlen",
-  "account.featured": "Empfohlen",
-  "account.featured.hashtags": "Hashtags",
-  "account.featured.posts": "Beiträge",
   "account.featured_tags.last_status_at": "Letzter Beitrag am {date}",
   "account.featured_tags.last_status_never": "Keine Beiträge",
+  "account.featured_tags.title": "Von {name} vorgestellte Hashtags",
   "account.follow": "Folgen",
   "account.follow_back": "Ebenfalls folgen",
   "account.followers": "Follower",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}",
   "account.unblock": "{name} nicht mehr blockieren",
   "account.unblock_domain": "Blockierung von {domain} aufheben",
-  "account.unblock_domain_short": "Entsperren",
   "account.unblock_short": "Blockierung aufheben",
   "account.unendorse": "Im Profil nicht mehr empfehlen",
   "account.unfollow": "Entfolgen",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Ein unerwarteter Fehler ist aufgetreten.",
   "alert.unexpected.title": "Oha!",
   "alt_text_badge.title": "Bildbeschreibung",
-  "alt_text_modal.add_alt_text": "Bildbeschreibung hinzufügen",
-  "alt_text_modal.add_text_from_image": "Text aus Bild hinzufügen",
-  "alt_text_modal.cancel": "Abbrechen",
-  "alt_text_modal.change_thumbnail": "Vorschaubild ändern",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Beschreibe den Inhalt für Menschen mit Schwerhörigkeit …",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Beschreibe den Inhalt für Menschen mit Sehschwäche …",
-  "alt_text_modal.done": "Fertig",
   "announcement.announcement": "Ankündigung",
-  "annual_report.summary.archetype.booster": "Trendjäger*in",
-  "annual_report.summary.archetype.lurker": "Beobachter*in",
-  "annual_report.summary.archetype.oracle": "Universaltalent",
-  "annual_report.summary.archetype.pollster": "Meinungsforscher*in",
-  "annual_report.summary.archetype.replier": "Sozialer Schmetterling",
-  "annual_report.summary.followers.followers": "Follower",
-  "annual_report.summary.followers.total": "{count} insgesamt",
-  "annual_report.summary.here_it_is": "Dein Jahresrückblick für {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "am häufigsten favorisierter Beitrag",
-  "annual_report.summary.highlighted_post.by_reblogs": "am häufigsten geteilter Beitrag",
-  "annual_report.summary.highlighted_post.by_replies": "Beitrag mit den meisten Antworten",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "am häufigsten verwendete App",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "am häufigsten verwendeter Hashtag",
-  "annual_report.summary.most_used_hashtag.none": "Keiner",
-  "annual_report.summary.new_posts.new_posts": "neue Beiträge",
-  "annual_report.summary.percentile.text": "<topLabel>Damit gehörst du zu den obersten</topLabel><percentage></percentage><bottomLabel>der Nutzer*innen auf {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Wir werden Bernie nichts verraten.",
-  "annual_report.summary.thanks": "Danke, dass du Teil von Mastodon bist!",
   "attachments_list.unprocessed": "(ausstehend)",
   "audio.hide": "Audio ausblenden",
   "block_modal.remote_users_caveat": "Wir werden den Server {domain} bitten, deine Entscheidung zu respektieren. Allerdings kann nicht garantiert werden, dass sie eingehalten wird, weil einige Server Blockierungen unterschiedlich handhaben können. Öffentliche Beiträge können für nicht angemeldete Nutzer*innen weiterhin sichtbar sein.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "Die angeforderte Seite konnte nicht gefunden werden. Bist du dir sicher, dass die URL in der Adressleiste korrekt ist?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Schließen",
-  "bundle_modal_error.message": "Beim Laden des Inhalts ist etwas schiefgelaufen.",
+  "bundle_modal_error.message": "Beim Laden dieser Komponente ist etwas schiefgelaufen.",
   "bundle_modal_error.retry": "Erneut versuchen",
   "closed_registrations.other_server_instructions": "Da Mastodon dezentralisiert ist, kannst du ein Konto auf einem anderen Server erstellen und trotzdem mit diesem Server interagieren.",
   "closed_registrations_modal.description": "Das Anlegen eines Kontos auf {domain} ist derzeit nicht möglich, aber bedenke, dass du kein extra Konto auf {domain} benötigst, um Mastodon nutzen zu können.",
@@ -150,16 +121,13 @@
   "column.blocks": "Blockierte Profile",
   "column.bookmarks": "Lesezeichen",
   "column.community": "Lokale Timeline",
-  "column.create_list": "Liste erstellen",
   "column.direct": "Private Erwähnungen",
   "column.directory": "Profile durchsuchen",
   "column.domain_blocks": "Blockierte Domains",
-  "column.edit_list": "Liste bearbeiten",
   "column.favourites": "Favoriten",
   "column.firehose": "Live-Feeds",
   "column.follow_requests": "Follower-Anfragen",
   "column.home": "Startseite",
-  "column.list_members": "Listenmitglieder verwalten",
   "column.lists": "Listen",
   "column.mutes": "Stummgeschaltete Profile",
   "column.notifications": "Benachrichtigungen",
@@ -172,7 +140,6 @@
   "column_header.pin": "Anheften",
   "column_header.show_settings": "Einstellungen anzeigen",
   "column_header.unpin": "Lösen",
-  "column_search.cancel": "Abbrechen",
   "column_subheading.settings": "Einstellungen",
   "community.column_settings.local_only": "Nur lokal",
   "community.column_settings.media_only": "Nur Beiträge mit Medien",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Bearbeiten",
   "confirmations.edit.message": "Das Bearbeiten überschreibt die Nachricht, die du gerade verfasst. Möchtest du wirklich fortfahren?",
   "confirmations.edit.title": "Beitrag überschreiben?",
-  "confirmations.follow_to_list.confirm": "Folgen und zur Liste hinzufügen",
-  "confirmations.follow_to_list.message": "Du musst {name} folgen, um das Profil zu einer Liste hinzufügen zu können.",
-  "confirmations.follow_to_list.title": "Profil folgen?",
   "confirmations.logout.confirm": "Abmelden",
   "confirmations.logout.message": "Möchtest du dich wirklich abmelden?",
   "confirmations.logout.title": "Abmelden?",
-  "confirmations.missing_alt_text.confirm": "Bildbeschreibung hinzufügen",
-  "confirmations.missing_alt_text.message": "Dein Beitrag enthält Medien ohne Bildbeschreibung. Mit Alt-Texten erreichst du auch Menschen mit einer Sehschwäche.",
-  "confirmations.missing_alt_text.secondary": "Trotzdem veröffentlichen",
-  "confirmations.missing_alt_text.title": "Bildbeschreibung hinzufügen?",
   "confirmations.mute.confirm": "Stummschalten",
   "confirmations.redraft.confirm": "Löschen und neu erstellen",
   "confirmations.redraft.message": "Möchtest du diesen Beitrag wirklich löschen und neu verfassen? Alle Favoriten sowie die bisher geteilten Beiträge werden verloren gehen und Antworten auf den ursprünglichen Beitrag verlieren den Zusammenhang.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Dein Konto {disabledAccount} ist derzeit deaktiviert.",
   "dismissable_banner.community_timeline": "Das sind die neuesten öffentlichen Beiträge von Profilen, deren Konten von {domain} verwaltet werden.",
   "dismissable_banner.dismiss": "Ablehnen",
-  "dismissable_banner.explore_links": "Diese Nachrichten werden heute am häufigsten im Fediverse geteilt. Neuere Nachrichten, die von vielen verschiedenen Profilen geteilt wurden, erscheinen weiter oben.",
-  "dismissable_banner.explore_statuses": "Diese Beiträge sind heute im Fediverse sehr beliebt. Neuere Beiträge, die häufiger geteilt und favorisiert wurden, erscheinen weiter oben.",
-  "dismissable_banner.explore_tags": "Diese Hashtags sind heute im Fediverse sehr beliebt. Hashtags, die von vielen verschiedenen Profilen verwendet werden, erscheinen weiter oben.",
-  "dismissable_banner.public_timeline": "Das sind die neuesten öffentlichen Beiträge von Profilen im Fediverse, denen Leute auf {domain} folgen.",
+  "dismissable_banner.explore_links": "Diese Nachrichten werden heute am häufigsten im Social Web geteilt. Neuere Nachrichten, die von vielen verschiedenen Profilen geteilt wurden, erscheinen weiter oben.",
+  "dismissable_banner.explore_statuses": "Diese Beiträge sind heute im Social Web sehr beliebt. Neuere Beiträge, die häufiger geteilt und favorisiert wurden, erscheinen weiter oben.",
+  "dismissable_banner.explore_tags": "Diese Hashtags sind heute im Social Web sehr beliebt. Hashtags, die von vielen verschiedenen Profilen verwendet werden, erscheinen weiter oben.",
+  "dismissable_banner.public_timeline": "Das sind die neuesten öffentlichen Beiträge von Profilen im Social Web, denen Leute auf {domain} folgen.",
   "domain_block_modal.block": "Server blockieren",
   "domain_block_modal.block_account_instead": "Stattdessen @{name} blockieren",
   "domain_block_modal.they_can_interact_with_old_posts": "Profile von diesem Server werden mit deinen älteren Beiträgen interagieren können.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Suchergebnisse",
   "emoji_button.symbols": "Symbole",
   "emoji_button.travel": "Reisen & Orte",
-  "empty_column.account_featured": "Diese Liste ist leer",
   "empty_column.account_hides_collections": "Das Konto hat sich dazu entschieden, diese Information nicht zu veröffentlichen",
   "empty_column.account_suspended": "Konto gesperrt",
   "empty_column.account_timeline": "Keine Beiträge vorhanden!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.",
   "empty_column.home": "Die Timeline deiner Startseite ist leer! Folge mehr Leuten, um sie zu füllen.",
   "empty_column.list": "Diese Liste ist derzeit leer. Wenn Konten auf dieser Liste neue Beiträge veröffentlichen, werden sie hier erscheinen.",
+  "empty_column.lists": "Du hast noch keine Listen. Sobald du eine anlegst, wird sie hier erscheinen.",
   "empty_column.mutes": "Du hast keine Profile stummgeschaltet.",
   "empty_column.notification_requests": "Alles klar! Hier gibt es nichts. Wenn Sie neue Mitteilungen erhalten, werden diese entsprechend Ihren Einstellungen hier angezeigt.",
   "empty_column.notifications": "Du hast noch keine Benachrichtigungen. Sobald andere Personen mit dir interagieren, wirst du hier darüber informiert.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Versuche, das Add-on oder Übersetzungswerkzeug zu deaktivieren und lade die Seite anschließend neu. Sollte das Problem weiter bestehen, kannst du das Webinterface von Mastodon vermutlich über einen anderen Browser erreichen – oder du verwendest eine mobile (native) App.",
   "errors.unexpected_crash.copy_stacktrace": "Fehlerdiagnose in die Zwischenablage kopieren",
   "errors.unexpected_crash.report_issue": "Fehler melden",
+  "explore.search_results": "Suchergebnisse",
   "explore.suggested_follows": "Profile",
   "explore.title": "Entdecken",
   "explore.trending_links": "Neuigkeiten",
@@ -373,16 +334,13 @@
   "footer.about": "Über",
   "footer.directory": "Profilverzeichnis",
   "footer.get_app": "App herunterladen",
+  "footer.invite": "Leute einladen",
   "footer.keyboard_shortcuts": "Tastenkombinationen",
   "footer.privacy_policy": "Datenschutzerklärung",
   "footer.source_code": "Quellcode anzeigen",
   "footer.status": "Status",
-  "footer.terms_of_service": "Nutzungsbedingungen",
   "generic.saved": "Gespeichert",
   "getting_started.heading": "Auf gehts!",
-  "hashtag.admin_moderation": "#{name} moderieren",
-  "hashtag.browse": "Beiträge mit #{hashtag} suchen",
-  "hashtag.browse_from_account": "Beiträge von @{name} mit #{hashtag} suchen",
   "hashtag.column_header.tag_mode.all": "und {additional}",
   "hashtag.column_header.tag_mode.any": "oder {additional}",
   "hashtag.column_header.tag_mode.none": "ohne {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}} heute",
   "hashtag.follow": "Hashtag folgen",
-  "hashtag.mute": "#{hashtag} stummschalten",
   "hashtag.unfollow": "Hashtag entfolgen",
   "hashtags.and_other": "… und {count, plural, one{# weiterer} other {# weitere}}",
   "hints.profiles.followers_may_be_missing": "Möglicherweise werden für dieses Profil nicht alle Follower angezeigt.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Benachrichtigungen von Profilen ignorieren, die dir nicht folgen?",
   "ignore_notifications_modal.not_following_title": "Benachrichtigungen von Profilen ignorieren, denen du nicht folgst?",
   "ignore_notifications_modal.private_mentions_title": "Benachrichtigungen von unerwünschten privaten Erwähnungen ignorieren?",
-  "info_button.label": "Hilfe",
-  "info_button.what_is_alt_text": "<h1>Was ist Alt-Text?</h1> <p>Alt-Text bietet Bildbeschreibungen für Personen mit einer Sehschwäche, einer schlechten Internetverbindung und für alle, die zusätzlichen Kontext möchten.</p> <p>Du kannst die Zugänglichkeit und die Verständlichkeit für alle verbessern, indem du eine klare, genaue und objektive Bildbeschreibung hinzufügst.</p> <ul> <li>Erfasse wichtige Elemente</li> <li>Fasse Text in Bildern zusammen</li> <li>Verwende einen korrekten Satzbau</li> <li>Vermeide unwichtige Informationen</li> <li>Konzentriere dich bei komplexen Darstellungen (z. B. Diagramme oder Karten) auf Trends und wichtige Erkenntnisse</li> </ul>",
-  "interaction_modal.action.favourite": "Du musst von deinem Konto aus favorisieren, um fortzufahren.",
-  "interaction_modal.action.follow": "Du musst von deinem Konto aus folgen, um fortzufahren.",
-  "interaction_modal.action.reblog": "Du musst von deinem Konto aus teilen, um fortzufahren.",
-  "interaction_modal.action.reply": "Du musst von deinem Konto aus antworten, um fortzufahren.",
-  "interaction_modal.action.vote": "Du musst von deinem Konto aus abstimmen, um fortzufahren.",
-  "interaction_modal.go": "Los",
-  "interaction_modal.no_account_yet": "Du hast noch kein Konto?",
+  "interaction_modal.description.favourite": "Mit einem Mastodon-Konto kannst du diesen Beitrag favorisieren, um deine Wertschätzung auszudrücken, und ihn für einen späteren Zeitpunkt speichern.",
+  "interaction_modal.description.follow": "Mit einem Mastodon-Konto kannst du {name} folgen, um die Beiträge auf deiner Startseite zu sehen.",
+  "interaction_modal.description.reblog": "Mit einem Mastodon-Konto kannst du die Reichweite dieses Beitrags erhöhen, indem du ihn mit deinen Followern teilst.",
+  "interaction_modal.description.reply": "Mit einem Mastodon-Konto kannst du auf diesen Beitrag antworten.",
+  "interaction_modal.login.action": "Zurück zur Startseite",
+  "interaction_modal.login.prompt": "Adresse deines Servers, z. B. mastodon.social",
+  "interaction_modal.no_account_yet": "Nicht auf Mastodon?",
   "interaction_modal.on_another_server": "Auf einem anderen Server",
   "interaction_modal.on_this_server": "Auf diesem Server",
+  "interaction_modal.sign_in": "Du bist auf diesem Server nicht angemeldet. Auf welchem Server wird dein Konto verwaltet?",
+  "interaction_modal.sign_in_hint": "Hinweis: Hierbei handelt es sich um die Website, auf der du dich registriert hast. Wenn du dich nicht mehr daran erinnerst, dann kannst du sie in der Willkommens-E-Mail nachsehen. Du kannst auch deinen vollständigen Profilnamen eingeben! (z. B. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Beitrag von {name} favorisieren",
   "interaction_modal.title.follow": "Folge {name}",
   "interaction_modal.title.reblog": "Beitrag von {name} teilen",
   "interaction_modal.title.reply": "Auf Beitrag von {name} antworten",
-  "interaction_modal.title.vote": "An der Umfrage von {name} teilnehmen",
-  "interaction_modal.username_prompt": "z. B. {example}",
   "intervals.full.days": "{number, plural, one {# Tag} other {# Tage}}",
   "intervals.full.hours": "{number, plural, one {# Stunde} other {# Stunden}}",
   "intervals.full.minutes": "{number, plural, one {# Minute} other {# Minuten}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Beitragstext hinter der Inhaltswarnung anzeigen/ausblenden",
   "keyboard_shortcuts.toggle_sensitivity": "Medien anzeigen/ausblenden",
   "keyboard_shortcuts.toot": "Neuen Beitrag erstellen",
-  "keyboard_shortcuts.translate": "Beitrag übersetzen",
   "keyboard_shortcuts.unfocus": "Eingabefeld/Suche nicht mehr fokussieren",
   "keyboard_shortcuts.up": "Ansicht nach oben bewegen",
   "lightbox.close": "Schließen",
@@ -490,32 +444,20 @@
   "link_preview.author": "Von {name}",
   "link_preview.more_from_author": "Mehr von {name}",
   "link_preview.shares": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}",
-  "lists.add_member": "Hinzufügen",
-  "lists.add_to_list": "Zur Liste hinzufügen",
-  "lists.add_to_lists": "{name} zu Listen hinzufügen",
-  "lists.create": "Erstellen",
-  "lists.create_a_list_to_organize": "Erstelle eine neue Liste, um deine Startseite zu organisieren",
-  "lists.create_list": "Liste erstellen",
+  "lists.account.add": "Zur Liste hinzufügen",
+  "lists.account.remove": "Von der Liste entfernen",
   "lists.delete": "Liste löschen",
-  "lists.done": "Fertig",
   "lists.edit": "Liste bearbeiten",
-  "lists.exclusive": "Mitglieder auf der Startseite ausblenden",
-  "lists.exclusive_hint": "Profile, die sich auf dieser Liste befinden, werden nicht auf deiner Startseite angezeigt, damit deren Beiträge nicht doppelt erscheinen.",
-  "lists.find_users_to_add": "Suche nach Profilen, um sie hinzuzufügen",
-  "lists.list_members": "Listenmitglieder",
-  "lists.list_members_count": "{count, plural, one {# Mitglied} other {# Mitglieder}}",
-  "lists.list_name": "Titel der Liste",
-  "lists.new_list_name": "Neuer Listentitel",
-  "lists.no_lists_yet": "Noch keine Listen vorhanden.",
-  "lists.no_members_yet": "Keine Mitglieder vorhanden.",
-  "lists.no_results_found": "Keine Suchergebnisse.",
-  "lists.remove_member": "Entfernen",
+  "lists.edit.submit": "Titel ändern",
+  "lists.exclusive": "Diese Beiträge in der Startseite ausblenden",
+  "lists.new.create": "Neue Liste erstellen",
+  "lists.new.title_placeholder": "Titel der neuen Liste",
   "lists.replies_policy.followed": "Alle folgenden Profile",
   "lists.replies_policy.list": "Mitglieder der Liste",
   "lists.replies_policy.none": "Niemanden",
-  "lists.save": "Speichern",
-  "lists.search": "Suchen",
-  "lists.show_replies_to": "Antworten von Listenmitgliedern einbeziehen an …",
+  "lists.replies_policy.title": "Antworten anzeigen für:",
+  "lists.search": "Suche nach Leuten, denen du folgst",
+  "lists.subheading": "Deine Listen",
   "load_pending": "{count, plural, one {# neuer Beitrag} other {# neue Beiträge}}",
   "loading_indicator.label": "Wird geladen …",
   "media_gallery.hide": "Ausblenden",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} meldete {target}",
   "notification.admin.sign_up": "{name} registrierte sich",
   "notification.admin.sign_up.name_and_others": "{name} und {count, plural, one {# weiteres Profil} other {# weitere Profile}} registrierten sich",
-  "notification.annual_report.message": "Dein #Wrapstodon für {year} erwartet dich! Lass deine Highlights und unvergesslichen Momente auf Mastodon erneut aufleben!",
-  "notification.annual_report.view": "#Wrapstodon ansehen",
   "notification.favourite": "{name} favorisierte deinen Beitrag",
   "notification.favourite.name_and_others_with_link": "{name} und <a>{count, plural, one {# weiteres Profil} other {# weitere Profile}}</a> favorisierten deinen Beitrag",
-  "notification.favourite_pm": "{name} favorisierte deine private Erwähnung",
-  "notification.favourite_pm.name_and_others_with_link": "{name} und <a>{count, plural, one {# weiteres Profil} other {# weitere Profile}}</a> favorisierten deine private Erwähnung",
   "notification.follow": "{name} folgt dir",
   "notification.follow.name_and_others": "{name} und <a>{count, plural, one {# weiteres Profil} other {# weitere Profile}}</a> folgen dir",
   "notification.follow_request": "{name} möchte dir folgen",
@@ -598,7 +536,7 @@
   "notification.relationships_severance_event.domain_block": "Ein Admin von {from} hat {target} blockiert – darunter {followersCount} deiner Follower und {followingCount, plural, one {# Konto, dem} other {# Konten, denen}} du folgst.",
   "notification.relationships_severance_event.learn_more": "Mehr erfahren",
   "notification.relationships_severance_event.user_domain_block": "Du hast {target} blockiert – {followersCount} deiner Follower und {followingCount, plural, one {# Konto, dem} other {# Konten, denen}} du folgst, wurden entfernt.",
-  "notification.status": "{name} postete …",
+  "notification.status": "{name} veröffentlichte gerade",
   "notification.update": "{name} bearbeitete einen Beitrag",
   "notification_requests.accept": "Genehmigen",
   "notification_requests.accept_multiple": "{count, plural, one {# Anfrage genehmigen …} other {# Anfragen genehmigen …}}",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Aktiviere Desktop-Benachrichtigungen",
   "notifications_permission_banner.how_to_control": "Um Benachrichtigungen zu erhalten, wenn Mastodon nicht geöffnet ist, aktiviere die Desktop-Benachrichtigungen. Du kannst genau bestimmen, welche Arten von Interaktionen Desktop-Benachrichtigungen über die {icon} -Taste erzeugen, sobald diese aktiviert sind.",
   "notifications_permission_banner.title": "Nichts verpassen",
-  "onboarding.follows.back": "Zurück",
-  "onboarding.follows.done": "Fertig",
+  "onboarding.action.back": "Bring mich zurück",
+  "onboarding.actions.back": "Bring mich zurück",
+  "onboarding.actions.go_to_explore": "Zeig mir die Trends",
+  "onboarding.actions.go_to_home": "Bring mich zu meiner Startseite",
+  "onboarding.compose.template": "Hallo #Mastodon!",
   "onboarding.follows.empty": "Bedauerlicherweise können aktuell keine Ergebnisse angezeigt werden. Du kannst die Suche verwenden oder den Reiter „Entdecken“ auswählen, um neue Leute zum Folgen zu finden – oder du versuchst es später erneut.",
-  "onboarding.follows.search": "Suchen",
-  "onboarding.follows.title": "Folge Profilen, um loszulegen",
+  "onboarding.follows.lead": "Deine Startseite ist der primäre Anlaufpunkt, um Mastodon zu erleben. Je mehr Profilen du folgst, umso aktiver und interessanter wird sie. Damit du direkt loslegen kannst, gibt es hier ein paar Vorschläge:",
+  "onboarding.follows.title": "Personalisiere deine Startseite",
   "onboarding.profile.discoverable": "Mein Profil darf entdeckt werden",
   "onboarding.profile.discoverable_hint": "Wenn du entdeckt werden möchtest, dann können deine Beiträge in Suchergebnissen und Trends erscheinen. Dein Profil kann ebenfalls anderen mit ähnlichen Interessen vorgeschlagen werden.",
   "onboarding.profile.display_name": "Anzeigename",
   "onboarding.profile.display_name_hint": "Dein richtiger Name oder dein Fantasiename …",
+  "onboarding.profile.lead": "Du kannst dein Profil später in den Einstellungen vervollständigen. Dort stehen weitere Anpassungsmöglichkeiten zur Verfügung.",
   "onboarding.profile.note": "Über mich",
   "onboarding.profile.note_hint": "Du kannst andere @Profile erwähnen oder #Hashtags verwenden …",
   "onboarding.profile.save_and_continue": "Speichern und fortfahren",
   "onboarding.profile.title": "Profil einrichten",
   "onboarding.profile.upload_avatar": "Profilbild hochladen",
   "onboarding.profile.upload_header": "Titelbild hochladen",
+  "onboarding.share.lead": "Lass die Leute wissen, wie sie dich auf Mastodon finden können!",
+  "onboarding.share.message": "Ich bin {username} auf #Mastodon! Folge mir auf {url}",
+  "onboarding.share.next_steps": "Mögliche nächste Schritte:",
+  "onboarding.share.title": "Teile dein Profil",
+  "onboarding.start.lead": "Du bist nun ein Teil von Mastodon – eine einzigartige, dezentralisierte Social-Media-Plattform, bei der du und kein Algorithmus deine eigene Erfahrung gestaltest. Fangen wir an, diese neue soziale Dimension zu erkunden:",
+  "onboarding.start.skip": "Du benötigst keine Hilfe für den Einstieg?",
+  "onboarding.start.title": "Du hast es geschafft!",
+  "onboarding.steps.follow_people.body": "Interessanten Profilen zu folgen ist das, was Mastodon ausmacht.",
+  "onboarding.steps.follow_people.title": "Personalisiere deine Startseite",
+  "onboarding.steps.publish_status.body": "Begrüße die Welt mit Text, Fotos, Videos oder Umfragen. {emoji}",
+  "onboarding.steps.publish_status.title": "Erstelle deinen ersten Beitrag",
+  "onboarding.steps.setup_profile.body": "Mit einem vollständigen Profil interagieren andere eher mit dir.",
+  "onboarding.steps.setup_profile.title": "Personalisiere dein Profil",
+  "onboarding.steps.share_profile.body": "Lass deine Freund*innen wissen, wie sie dich auf Mastodon finden können.",
+  "onboarding.steps.share_profile.title": "Teile dein Mastodon-Profil",
+  "onboarding.tips.2fa": "<strong>Wusstest du schon?</strong> Du kannst die Sicherheit deines Kontos erhöhen, indem du die Zwei-Faktor-Authentisierung in deinen Kontoeinstellungen aktivierst. Dafür ist keine Telefonnummer notwendig und es funktioniert jede beliebige TOTP-App!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Wusstest du schon?</strong> Da Mastodon dezentralisiert ist, werden einige Profile, denen du begegnest, auf anderen Servern als deinem bereitgestellt. Und trotzdem kannst du uneingeschränkt mit ihnen interagieren! Der Servername befindet sich in der zweiten Hälfte ihres Profilnamens!",
+  "onboarding.tips.migration": "<strong>Wusstest du schon?</strong> Wenn du das Gefühl hast, dass {domain} in Zukunft nicht die richtige Serverwahl für dich ist, kannst du auf einen anderen Mastodon-Server umziehen, ohne deine Follower zu verlieren. Du kannst sogar deinen eigenen Server betreiben!",
+  "onboarding.tips.verification": "<strong>Wusstest du schon?</strong> Du kannst dein Konto verifizieren, indem du auf deiner Website auf dein Mastodon-Profil verlinkst und den Link deiner Website zu deinem Profil hinzufügst. Völlig kostenlos und ohne Dokumente einsenden zu müssen!",
   "password_confirmation.exceeds_maxlength": "Passwortbestätigung überschreitet die maximal erlaubte Zeichenanzahl",
   "password_confirmation.mismatching": "Passwortbestätigung stimmt nicht überein",
   "picture_in_picture.restore": "Zurücksetzen",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Umfrage entfernen",
   "privacy.change": "Sichtbarkeit anpassen",
   "privacy.direct.long": "Alle in diesem Beitrag erwähnten Profile",
-  "privacy.direct.short": "Private Erwähnung",
+  "privacy.direct.short": "Ausgewählte Profile",
   "privacy.private.long": "Nur deine Follower",
   "privacy.private.short": "Follower",
   "privacy.public.long": "Alle in und außerhalb von Mastodon",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Datenschutzerklärung",
   "recommended": "Empfohlen",
   "refresh": "Aktualisieren",
-  "regeneration_indicator.please_stand_by": "Bitte warten.",
-  "regeneration_indicator.preparing_your_home_feed": "Startseite wird vorbereitet …",
+  "regeneration_indicator.label": "Wird geladen …",
+  "regeneration_indicator.sublabel": "Deine Startseite wird gerade vorbereitet!",
   "relative_time.days": "{number} T.",
   "relative_time.full.days": "vor {number, plural, one {# Tag} other {# Tagen}}",
   "relative_time.full.hours": "vor {number, plural, one {# Stunde} other {# Stunden}}",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Profile",
   "search_results.all": "Alles",
   "search_results.hashtags": "Hashtags",
-  "search_results.no_results": "Keine Ergebnisse.",
-  "search_results.no_search_yet": "Suche nach Beiträgen, Profilen oder Hashtags.",
+  "search_results.nothing_found": "Nichts zu diesen Suchbegriffen gefunden",
   "search_results.see_all": "Alle ansehen",
   "search_results.statuses": "Beiträge",
-  "search_results.title": "Nach „{q}“ suchen",
+  "search_results.title": "Suchergebnisse für {q}",
   "server_banner.about_active_users": "Personen, die diesen Server in den vergangenen 30 Tagen verwendet haben (monatlich aktive Nutzer*innen)",
   "server_banner.active_users": "aktive Profile",
   "server_banner.administered_by": "Verwaltet von:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Diesen Beitrag hat bisher noch niemand geteilt. Sobald es jemand tut, wird das Profil hier erscheinen.",
   "status.redraft": "Löschen und neu erstellen",
   "status.remove_bookmark": "Lesezeichen entfernen",
-  "status.remove_favourite": "Aus Favoriten entfernen",
   "status.replied_in_thread": "Antwortete im Thread",
   "status.replied_to": "Antwortete {name}",
   "status.reply": "Antworten",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Abonnierte Sprachen für {target} ändern",
   "tabs_bar.home": "Startseite",
   "tabs_bar.notifications": "Benachrichtigungen",
-  "terms_of_service.effective_as_of": "Gültig ab {date}",
-  "terms_of_service.title": "Nutzungsbedingungen",
-  "terms_of_service.upcoming_changes_on": "Anstehende Änderungen am {date}",
   "time_remaining.days": "noch {number, plural, one {# Tag} other {# Tage}}",
   "time_remaining.hours": "noch {number, plural, one {# Stunde} other {# Stunden}}",
   "time_remaining.minutes": "noch {number, plural, one {# Minute} other {# Minuten}}",
@@ -897,12 +853,26 @@
   "upload_button.label": "Bilder, Video oder Audio hinzufügen",
   "upload_error.limit": "Dateiupload-Limit überschritten.",
   "upload_error.poll": "Medien-Anhänge sind zusammen mit Umfragen nicht erlaubt.",
+  "upload_form.audio_description": "Beschreibe für Menschen mit Hörbehinderung",
+  "upload_form.description": "Beschreibe für Menschen mit Sehbehinderung",
   "upload_form.drag_and_drop.instructions": "Drücke zum Aufnehmen eines Medienanhangs die Eingabe- oder Leertaste. Verwende beim Ziehen die Pfeiltasten, um den Medienanhang zur gewünschten Position zu bewegen. Drücke erneut die Eingabe- oder Leertaste, um den Medienanhang an der gewünschten Position abzulegen. Mit der Escape-Taste kannst du den Vorgang abbrechen.",
   "upload_form.drag_and_drop.on_drag_cancel": "Das Ziehen wurde abgebrochen und der Medienanhang {item} wurde abgelegt.",
   "upload_form.drag_and_drop.on_drag_end": "Der Medienanhang {item} wurde abgelegt.",
   "upload_form.drag_and_drop.on_drag_over": "Der Medienanhang {item} wurde bewegt.",
   "upload_form.drag_and_drop.on_drag_start": "Der Medienanhang {item} wurde aufgenommen.",
   "upload_form.edit": "Bearbeiten",
+  "upload_form.thumbnail": "Vorschaubild ändern",
+  "upload_form.video_description": "Beschreibe für Menschen mit einer Hör- oder Sehbehinderung",
+  "upload_modal.analyzing_picture": "Bild wird analysiert …",
+  "upload_modal.apply": "Übernehmen",
+  "upload_modal.applying": "Wird übernommen …",
+  "upload_modal.choose_image": "Bild auswählen",
+  "upload_modal.description_placeholder": "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich",
+  "upload_modal.detect_text": "Text aus Bild erkennen",
+  "upload_modal.edit_media": "Medien bearbeiten",
+  "upload_modal.hint": "Ziehe den Kreis auf die Stelle deines Bildes, um den Fokuspunkt festzulegen, der bei Vorschaubildern immer zu sehen sein soll.",
+  "upload_modal.preparing_ocr": "Texterkennung wird vorbereitet …",
+  "upload_modal.preview_label": "Vorschau ({ratio})",
   "upload_progress.label": "Wird hochgeladen …",
   "upload_progress.processing": "Wird verarbeitet…",
   "username.taken": "Dieser Profilname ist vergeben. Versuche einen anderen",
@@ -915,9 +885,5 @@
   "video.mute": "Stummschalten",
   "video.pause": "Pausieren",
   "video.play": "Abspielen",
-  "video.skip_backward": "Zurückspulen",
-  "video.skip_forward": "Vorspulen",
-  "video.unmute": "Stummschaltung aufheben",
-  "video.volume_down": "Leiser",
-  "video.volume_up": "Lauter"
+  "video.unmute": "Stummschaltung aufheben"
 }
diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json
index 0b9e42cbe9..7376fe0543 100644
--- a/app/javascript/mastodon/locales/el.json
+++ b/app/javascript/mastodon/locales/el.json
@@ -29,6 +29,7 @@
   "account.endorse": "Προβολή στο προφίλ",
   "account.featured_tags.last_status_at": "Τελευταία ανάρτηση στις {date}",
   "account.featured_tags.last_status_never": "Καμία ανάρτηση",
+  "account.featured_tags.title": "προβεβλημένες ετικέτες του/της {name}",
   "account.follow": "Ακολούθησε",
   "account.follow_back": "Ακολούθησε και εσύ",
   "account.followers": "Ακόλουθοι",
@@ -85,33 +86,7 @@
   "alert.unexpected.message": "Προέκυψε απροσδόκητο σφάλμα.",
   "alert.unexpected.title": "Ουπς!",
   "alt_text_badge.title": "Εναλλακτικό κείμενο",
-  "alt_text_modal.add_alt_text": "Προσθήκη εναλλακτικού κειμένου",
-  "alt_text_modal.add_text_from_image": "Προσθήκη κειμένου από εικόνα",
-  "alt_text_modal.cancel": "Ακύρωση",
-  "alt_text_modal.change_thumbnail": "Αλλαγή μικρογραφίας",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Περιέγραψε αυτό για άτομα με προβλήματα ακοής…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Περιέγραψε αυτό για άτομα με προβλήματα όρασης…",
-  "alt_text_modal.done": "Ολοκληρώθηκε",
   "announcement.announcement": "Ανακοίνωση",
-  "annual_report.summary.archetype.booster": "Ο κυνηγός των φοβερών",
-  "annual_report.summary.archetype.lurker": "Ο διακριτικός",
-  "annual_report.summary.archetype.oracle": "Η Πυθία",
-  "annual_report.summary.archetype.pollster": "Ο δημοσκόπος",
-  "annual_report.summary.archetype.replier": "Η κοινωνική πεταλούδα",
-  "annual_report.summary.followers.followers": "ακόλουθοι",
-  "annual_report.summary.followers.total": "{count} συνολικά",
-  "annual_report.summary.here_it_is": "Εδώ είναι το {year} σου σε ανασκόπηση:",
-  "annual_report.summary.highlighted_post.by_favourites": "πιο αγαπημένη ανάρτηση",
-  "annual_report.summary.highlighted_post.by_reblogs": "πιο ενισχυμένη ανάρτηση",
-  "annual_report.summary.highlighted_post.by_replies": "ανάρτηση με τις περισσότερες απαντήσεις",
-  "annual_report.summary.highlighted_post.possessive": "του χρήστη {name}",
-  "annual_report.summary.most_used_app.most_used_app": "πιο χρησιμοποιημένη εφαρμογή",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "πιο χρησιμοποιημένη ετικέτα",
-  "annual_report.summary.most_used_hashtag.none": "Κανένα",
-  "annual_report.summary.new_posts.new_posts": "νέες αναρτήσεις",
-  "annual_report.summary.percentile.text": "<topLabel>Αυτό σε βάζει στο </topLabel><percentage></percentage><bottomLabel>των κορυφαίων χρηστών του {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Δεν θα το πούμε στον Bernie.",
-  "annual_report.summary.thanks": "Ευχαριστούμε που συμμετέχεις στο Mastodon!",
   "attachments_list.unprocessed": "(μη επεξεργασμένο)",
   "audio.hide": "Απόκρυψη αρχείου ήχου",
   "block_modal.remote_users_caveat": "Θα ζητήσουμε από τον διακομιστή {domain} να σεβαστεί την απόφασή σου. Ωστόσο, η συμμόρφωση δεν είναι εγγυημένη δεδομένου ότι ορισμένοι διακομιστές ενδέχεται να χειρίζονται τους αποκλεισμούς διαφορετικά. Οι δημόσιες αναρτήσεις ενδέχεται να είναι ορατές σε μη συνδεδεμένους χρήστες.",
@@ -135,7 +110,7 @@
   "bundle_column_error.routing.body": "Η επιθυμητή σελίδα δεν βρέθηκε. Είναι σωστό το URL στο πεδίο διευθύνσεων;",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Κλείσιμο",
-  "bundle_modal_error.message": "Κάτι πήγε στραβά κατά τη φόρτωση αυτής της οθόνης.",
+  "bundle_modal_error.message": "Κάτι πήγε στραβά κατά τη φόρτωση του στοιχείου.",
   "bundle_modal_error.retry": "Δοκίμασε ξανά",
   "closed_registrations.other_server_instructions": "Καθώς το Mastodon είναι αποκεντρωμένο, μπορείς να δημιουργήσεις λογαριασμό σε άλλον διακομιστή αλλά να συνεχίσεις να αλληλεπιδράς με αυτόν.",
   "closed_registrations_modal.description": "Η δημιουργία λογαριασμού στον {domain} προς το παρόν δεν είναι δυνατή, αλλά λάβε υπόψη ότι δεν χρειάζεσαι λογαριασμό ειδικά στον {domain} για να χρησιμοποιήσεις το Mastodon.",
@@ -146,16 +121,13 @@
   "column.blocks": "Αποκλεισμένοι χρήστες",
   "column.bookmarks": "Σελιδοδείκτες",
   "column.community": "Τοπική ροή",
-  "column.create_list": "Δημιουργία λίστας",
   "column.direct": "Ιδιωτικές αναφορές",
   "column.directory": "Περιήγηση στα προφίλ",
   "column.domain_blocks": "Αποκλεισμένοι τομείς",
-  "column.edit_list": "Επεξεργασία λίστας",
   "column.favourites": "Αγαπημένα",
   "column.firehose": "Ζωντανές ροές",
   "column.follow_requests": "Αιτήματα ακολούθησης",
   "column.home": "Αρχική",
-  "column.list_members": "Διαχείριση μελών λίστας",
   "column.lists": "Λίστες",
   "column.mutes": "Αποσιωπημένοι χρήστες",
   "column.notifications": "Ειδοποιήσεις",
@@ -168,7 +140,6 @@
   "column_header.pin": "Καρφίτσωμα",
   "column_header.show_settings": "Εμφάνιση ρυθμίσεων",
   "column_header.unpin": "Ξεκαρφίτσωμα",
-  "column_search.cancel": "Ακύρωση",
   "column_subheading.settings": "Ρυθμίσεις",
   "community.column_settings.local_only": "Τοπικά μόνο",
   "community.column_settings.media_only": "Μόνο πολυμέσα",
@@ -187,7 +158,7 @@
   "compose_form.poll.duration": "Διάρκεια δημοσκόπησης",
   "compose_form.poll.multiple": "Πολλαπλή επιλογή",
   "compose_form.poll.option_placeholder": "Επιλογή {number}",
-  "compose_form.poll.single": "Μονή επιλογή",
+  "compose_form.poll.single": "Διάλεξε ένα",
   "compose_form.poll.switch_to_multiple": "Ενημέρωση δημοσκόπησης με πολλαπλές επιλογές",
   "compose_form.poll.switch_to_single": "Ενημέρωση δημοσκόπησης με μοναδική επιλογή",
   "compose_form.poll.type": "Στυλ",
@@ -211,16 +182,9 @@
   "confirmations.edit.confirm": "Επεξεργασία",
   "confirmations.edit.message": "Αν το επεξεργαστείς τώρα θα αντικατασταθεί το μήνυμα που συνθέτεις. Είσαι σίγουρος ότι θέλεις να συνεχίσεις;",
   "confirmations.edit.title": "Αντικατάσταση ανάρτησης;",
-  "confirmations.follow_to_list.confirm": "Ακολούθησε και πρόσθεσε στη λίστα",
-  "confirmations.follow_to_list.message": "Πρέπει να ακολουθήσεις τον χρήστη {name} για να τον προσθέσεις σε μια λίστα.",
-  "confirmations.follow_to_list.title": "Ακολούθηση χρήστη;",
   "confirmations.logout.confirm": "Αποσύνδεση",
   "confirmations.logout.message": "Σίγουρα θέλεις να αποσυνδεθείς;",
   "confirmations.logout.title": "Αποσύνδεση;",
-  "confirmations.missing_alt_text.confirm": "Προσθήκη εναλ κειμένου",
-  "confirmations.missing_alt_text.message": "Η ανάρτησή σου περιέχει πολυμέσα χωρίς εναλλακτικό κείμενο. Η προσθήκη περιγραφών βοηθά να γίνει το περιεχόμενό σου προσβάσιμο σε περισσότερους ανθρώπους.",
-  "confirmations.missing_alt_text.secondary": "Δημοσίευση όπως και να ΄χει",
-  "confirmations.missing_alt_text.title": "Προσθήκη alt κειμένου;",
   "confirmations.mute.confirm": "Αποσιώπηση",
   "confirmations.redraft.confirm": "Διαγραφή & ξαναγράψιμο",
   "confirmations.redraft.message": "Σίγουρα θέλεις να σβήσεις αυτή την ανάρτηση και να την ξαναγράψεις; Οι προτιμήσεις και προωθήσεις θα χαθούν και οι απαντήσεις στην αρχική ανάρτηση θα μείνουν ορφανές.",
@@ -249,10 +213,10 @@
   "disabled_account_banner.text": "Ο λογαριασμός σου {disabledAccount} είναι προς το παρόν απενεργοποιημένος.",
   "dismissable_banner.community_timeline": "Αυτές είναι οι πιο πρόσφατες δημόσιες αναρτήσεις ατόμων των οποίων οι λογαριασμοί φιλοξενούνται στο {domain}.",
   "dismissable_banner.dismiss": "Παράβλεψη",
-  "dismissable_banner.explore_links": "Αυτές οι ιστορίες ειδήσεων μοιράζονται περισσότερο στο fediverse σήμερα. Νεότερες ιστορίες ειδήσεων που δημοσιεύτηκαν από πιο διαφορετικά άτομα κατατάσσονται υψηλότερα.",
-  "dismissable_banner.explore_statuses": "Αυτές οι αναρτήσεις από όλο το fediverse κερδίζουν την προσοχή σήμερα. Νεότερες αναρτήσεις με περισσότερες ενισχύσεις και αγαπημένα κατατάσσονται υψηλότερα.",
-  "dismissable_banner.explore_tags": "Αυτές οι ετικέτες κερδίζουν την προσοχή στο fediverse σήμερα. Οι ετικέτες που χρησιμοποιούνται από περισσότερα διαφορετικά άτομα είναι υψηλότερα.",
-  "dismissable_banner.public_timeline": "Αυτές είναι οι πιο πρόσφατες δημόσιες αναρτήσεις από άτομα στο fediverse που ακολουθούν άτομα από το {domain}.",
+  "dismissable_banner.explore_links": "Αυτές οι ειδήσεις συζητούνται σε αυτόν και άλλους διακομιστές του αποκεντρωμένου δικτύου αυτή τη στιγμή.",
+  "dismissable_banner.explore_statuses": "Αυτές είναι οι αναρτήσεις που έχουν απήχηση στο κοινωνικό δίκτυο σήμερα. Οι νεώτερες αναρτήσεις με περισσότερες προωθήσεις και προτιμήσεις κατατάσσονται ψηλότερα.",
+  "dismissable_banner.explore_tags": "Αυτές οι ετικέτες αποκτούν απήχηση σε αυτόν και άλλους διακομιστές του αποκεντρωμένου δικτύου αυτή τη στιγμή.",
+  "dismissable_banner.public_timeline": "Αυτές είναι οι πιο πρόσφατες δημόσιες αναρτήσεις από άτομα στον κοινωνικό ιστό που ακολουθούν άτομα από το {domain}.",
   "domain_block_modal.block": "Αποκλεισμός διακομιστή",
   "domain_block_modal.block_account_instead": "Αποκλεισμός @{name} αντ' αυτού",
   "domain_block_modal.they_can_interact_with_old_posts": "Άτομα από αυτόν τον διακομιστή μπορούν να αλληλεπιδράσουν με τις παλιές αναρτήσεις σου.",
@@ -309,6 +273,7 @@
   "empty_column.hashtag": "Δεν υπάρχει ακόμα κάτι για αυτή την ετικέτα.",
   "empty_column.home": "Η τοπική σου ροή είναι κενή! Πήγαινε στο {public} ή κάνε αναζήτηση για να ξεκινήσεις και να γνωρίσεις άλλους χρήστες.",
   "empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμα. Όταν τα μέλη της δημοσιεύσουν νέες καταστάσεις, θα εμφανιστούν εδώ.",
+  "empty_column.lists": "Δεν έχεις καμία λίστα ακόμα. Μόλις φτιάξεις μια, θα εμφανιστεί εδώ.",
   "empty_column.mutes": "Δεν έχεις κανένα χρήστη σε σίγαση ακόμα.",
   "empty_column.notification_requests": "Όλα καθαρά! Δεν υπάρχει τίποτα εδώ. Όταν λαμβάνεις νέες ειδοποιήσεις, αυτές θα εμφανίζονται εδώ σύμφωνα με τις ρυθμίσεις σου.",
   "empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμα. Όταν άλλα άτομα αλληλεπιδράσουν μαζί σου, θα το δεις εδώ.",
@@ -319,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Δοκίμασε να τα απενεργοποιήσεις και ανανέωσε τη σελίδα. Αν αυτό δεν βοηθήσει, ίσως να μπορέσεις να χρησιμοποιήσεις το Mastodon μέσω διαφορετικού φυλλομετρητή ή κάποιας εφαρμογής.",
   "errors.unexpected_crash.copy_stacktrace": "Αντιγραφή μηνυμάτων κώδικα στο πρόχειρο",
   "errors.unexpected_crash.report_issue": "Αναφορά προβλήματος",
+  "explore.search_results": "Αποτελέσματα αναζήτησης",
   "explore.suggested_follows": "Άτομα",
   "explore.title": "Εξερεύνηση",
   "explore.trending_links": "Νέα",
@@ -368,14 +334,13 @@
   "footer.about": "Σχετικά με",
   "footer.directory": "Κατάλογος προφίλ",
   "footer.get_app": "Αποκτήστε την εφαρμογή",
+  "footer.invite": "Προσκάλεσε άτομα",
   "footer.keyboard_shortcuts": "Συντομεύσεις πληκτρολογίου",
   "footer.privacy_policy": "Πολιτική απορρήτου",
   "footer.source_code": "Προβολή πηγαίου κώδικα",
   "footer.status": "Κατάσταση",
-  "footer.terms_of_service": "Όροι υπηρεσίας",
   "generic.saved": "Αποθηκεύτηκε",
   "getting_started.heading": "Ας ξεκινήσουμε",
-  "hashtag.admin_moderation": "Άνοιγμα διεπαφής συντονισμού για το #{name}",
   "hashtag.column_header.tag_mode.all": "και {additional}",
   "hashtag.column_header.tag_mode.any": "ή {additional}",
   "hashtag.column_header.tag_mode.none": "χωρίς {additional}",
@@ -417,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Αγνόηση ειδοποιήσεων από άτομα που δε σας ακολουθούν;",
   "ignore_notifications_modal.not_following_title": "Αγνόηση ειδοποιήσεων από άτομα που δεν ακολουθείς;",
   "ignore_notifications_modal.private_mentions_title": "Αγνόηση ειδοποιήσεων από μη ζητηθείσες ιδιωτικές αναφορές;",
-  "info_button.label": "Βοήθεια",
-  "info_button.what_is_alt_text": "Το εναλλακτικό κείμενο παρέχει περιγραφές εικόνας για άτομα με προβλήματα όρασης, διαδικτυακές συνδέσεις χαμηλής ταχύτητας ή για άτομα που αναζητούν επιπλέον περιεχόμενο.\\n\\nΜπορείς να βελτιώσεις την προσβασιμότητα και την κατανόηση για όλους, γράφοντας σαφές, συνοπτικό και αντικειμενικό εναλλακτικό κείμενο.\\n\\n<ul><li>Κατέγραψε σημαντικά στοιχεία</li>\\n<li>Συνόψισε το κείμενο στις εικόνες</li>\\n<li>Χρησιμοποίησε δομή κανονικής πρότασης</li>\\n<li>Απέφυγε περιττές πληροφορίες</li>\\n<li>Εστίασε στις τάσεις και τα βασικά ευρήματα σε σύνθετα οπτικά στοιχεία (όπως διαγράμματα ή χάρτες)</li></ul>",
-  "interaction_modal.action.favourite": "Για να συνεχίσεις, θα πρέπει να αγαπήσεις από τον λογαριασμό σου.",
-  "interaction_modal.action.follow": "Για να συνεχίσεις, θα πρέπει να ακολουθήσεις από τον λογαριασμό σου.",
-  "interaction_modal.action.reblog": "Για να συνεχίσεις, θα πρέπει να αναδημοσιεύσεις από τον λογαριασμό σου.",
-  "interaction_modal.action.reply": "Για να συνεχίσεις, θα πρέπει να απαντήσεις από τον λογαριασμό σου.",
-  "interaction_modal.action.vote": "Για να συνεχίσεις, θα πρέπει να ψηφίσεις από τον λογαριασμό σου.",
-  "interaction_modal.go": "Πάμε",
-  "interaction_modal.no_account_yet": "Δεν έχεις ακόμη λογαριασμό;",
+  "interaction_modal.description.favourite": "Με ένα συντάκτη στο Mastodon μπορείς να αγαπήσεις αυτή την ανάρτηση, για να ενημερώσεις τον συγγραφέα ότι την εκτιμάς και να την αποθηκεύσεις για αργότερα.",
+  "interaction_modal.description.follow": "Με έναν λογαριασμό Mastodon, μπορείς να ακολουθήσεις τον/την {name} ώστε να λαμβάνεις τις αναρτήσεις του/της στη δική σου ροή.",
+  "interaction_modal.description.reblog": "Με ένα λογαριασμό Mastodon, μπορείς να ενισχύσεις αυτή την ανάρτηση για να τη μοιραστείς με τους δικούς σου ακολούθους.",
+  "interaction_modal.description.reply": "Με ένα λογαριασμό Mastodon, μπορείς να απαντήσεις σε αυτή την ανάρτηση.",
+  "interaction_modal.login.action": "Πήγαινέ με στην αρχική σελίδα",
+  "interaction_modal.login.prompt": "Τομέας του οικιακού σου διακομιστή, πχ. mastodon.social",
+  "interaction_modal.no_account_yet": "Δεν είστε στο Mastodon;",
   "interaction_modal.on_another_server": "Σε διαφορετικό διακομιστή",
   "interaction_modal.on_this_server": "Σε αυτόν τον διακομιστή",
+  "interaction_modal.sign_in": "Δεν είσαι συνδεδεμένος σε αυτόν το διακομιστή. Πού φιλοξενείται ο λογαριασμός σου;",
+  "interaction_modal.sign_in_hint": "Συμβουλή: Αυτή είναι η ιστοσελίδα όπου έχεις εγγραφεί. Αν δεν θυμάσαι, αναζήτησε το καλώς ήρθες e-mail στα εισερχόμενά σου. Μπορείς επίσης να εισάγεις το πλήρες όνομα χρήστη! (πχ. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Favorite {name}'s post\nΠροτίμησε την ανάρτηση της/του {name}",
   "interaction_modal.title.follow": "Ακολούθησε {name}",
   "interaction_modal.title.reblog": "Ενίσχυσε την ανάρτηση του {name}",
   "interaction_modal.title.reply": "Απάντηση στην ανάρτηση του {name}",
-  "interaction_modal.title.vote": "Ψήφισε στη δημοσκόπηση του χρήστη {name}",
-  "interaction_modal.username_prompt": "Πχ. {example}",
   "intervals.full.days": "{number, plural, one {# μέρα} other {# μέρες}}",
   "intervals.full.hours": "{number, plural, one {# ώρα} other {# ώρες}}",
   "intervals.full.minutes": "{number, plural, one {# λεπτό} other {# λεπτά}}",
@@ -469,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Εμφάνιση/απόκρυψη κειμένου πίσω από το CW",
   "keyboard_shortcuts.toggle_sensitivity": "Εμφάνιση/απόκρυψη πολυμέσων",
   "keyboard_shortcuts.toot": "Δημιουργία νέας ανάρτησης",
-  "keyboard_shortcuts.translate": "να μεταφράσει μια δημοσίευση",
   "keyboard_shortcuts.unfocus": "Αποεστίαση του πεδίου σύνθεσης/αναζήτησης",
   "keyboard_shortcuts.up": "Μετακίνηση προς τα πάνω στη λίστα",
   "lightbox.close": "Κλείσιμο",
@@ -482,32 +444,20 @@
   "link_preview.author": "Από {name}",
   "link_preview.more_from_author": "Περισσότερα από {name}",
   "link_preview.shares": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}}",
-  "lists.add_member": "Προσθήκη",
-  "lists.add_to_list": "Προσθήκη στη λίστα",
-  "lists.add_to_lists": "Προσθήκη {name} σε λίστες",
-  "lists.create": "Δημιουργία",
-  "lists.create_a_list_to_organize": "Δημιούργησε μια νέα λίστα για να οργανώσεις την αρχική σου ροή",
-  "lists.create_list": "Δημιουργία λίστας",
+  "lists.account.add": "Πρόσθεσε στη λίστα",
+  "lists.account.remove": "Βγάλε από τη λίστα",
   "lists.delete": "Διαγραφή λίστας",
-  "lists.done": "Έγινε",
   "lists.edit": "Επεξεργασία λίστας",
-  "lists.exclusive": "Απόκρυψη μελών από την Αρχική",
-  "lists.exclusive_hint": "Αν κάποιος είναι σε αυτή τη λίστα, απόκρυψέ τον στην Αρχική σου για να αποφύγεις να βλέπεις τις αναρτήσεις του δύο φορές.",
-  "lists.find_users_to_add": "Εύρεση χρηστών για προσθήκη",
-  "lists.list_members": "Λίστα μελών",
-  "lists.list_members_count": "{count, plural, one {# μέλος} other {# μέλη}}",
-  "lists.list_name": "Όνομα λίστας",
-  "lists.new_list_name": "Νέο όνομα λίστας",
-  "lists.no_lists_yet": "Δεν υπάρχουν λίστες ακόμα.",
-  "lists.no_members_yet": "Κανένα μέλος ακόμα.",
-  "lists.no_results_found": "Δεν βρέθηκαν αποτελέσματα.",
-  "lists.remove_member": "Αφαίρεση",
+  "lists.edit.submit": "Αλλαγή τίτλου",
+  "lists.exclusive": "Απόκρυψη αυτών των αναρτήσεων από την αρχική",
+  "lists.new.create": "Προσθήκη λίστας",
+  "lists.new.title_placeholder": "Τίτλος νέας λίστα",
   "lists.replies_policy.followed": "Οποιοσδήποτε χρήστης που ακολουθείς",
   "lists.replies_policy.list": "Μέλη της λίστας",
   "lists.replies_policy.none": "Κανένας",
-  "lists.save": "Αποθήκευση",
-  "lists.search": "Αναζήτηση",
-  "lists.show_replies_to": "Συμπερίληψη απαντήσεων από τα μέλη της λίστας σε",
+  "lists.replies_policy.title": "Εμφάνιση απαντήσεων σε:",
+  "lists.search": "Αναζήτησε μεταξύ των ανθρώπων που ακουλουθείς",
+  "lists.subheading": "Οι λίστες σου",
   "load_pending": "{count, plural, one {# νέο στοιχείο} other {# νέα στοιχεία}}",
   "loading_indicator.label": "Φόρτωση…",
   "media_gallery.hide": "Απόκρυψη",
@@ -556,12 +506,8 @@
   "notification.admin.report_statuses_other": "Ο χρήστης {name} ανέφερε τον χρήστη {target}",
   "notification.admin.sign_up": "{name} έχει εγγραφεί",
   "notification.admin.sign_up.name_and_others": "{name} και {count, plural, one {# ακόμη} other {# ακόμη}} έχουν εγγραφεί",
-  "notification.annual_report.message": "Το #Wrapstodon {year} σε περιμένει! Αποκάλυψε τα στιγμιότυπα της χρονιάς και αξέχαστες στιγμές σου στο Mastodon!",
-  "notification.annual_report.view": "Προβολή #Wrapstodon",
   "notification.favourite": "{name} favorited your post\n{name} προτίμησε την ανάρτηση σου",
   "notification.favourite.name_and_others_with_link": "{name} και <a>{count, plural, one {# ακόμη} other {# ακόμη}}</a> αγάπησαν την ανάρτησή σου",
-  "notification.favourite_pm": "Ο χρήστης {name} αγάπησε την ιδιωτική σου επισήμανση",
-  "notification.favourite_pm.name_and_others_with_link": "Ο χρήστης {name} και <a>{count, plural, one {# ακόμη} other {# ακόμη}}</a> αγάπησαν την ιδωτική σου επισήμανση",
   "notification.follow": "Ο/Η {name} σε ακολούθησε",
   "notification.follow.name_and_others": "Ο χρήστης {name} και <a>{count, plural, one {# ακόμη} other {# ακόμη}}</a> σε ακολούθησαν",
   "notification.follow_request": "Ο/H {name} ζήτησε να σε ακολουθήσει",
@@ -666,21 +612,44 @@
   "notifications_permission_banner.enable": "Ενεργοποίηση ειδοποιήσεων επιφάνειας εργασίας",
   "notifications_permission_banner.how_to_control": "Για να λαμβάνεις ειδοποιήσεις όταν το Mastodon δεν είναι ανοιχτό, ενεργοποίησε τις ειδοποιήσεις επιφάνειας εργασίας. Μπορείς να ελέγξεις με ακρίβεια ποιοι τύποι αλληλεπιδράσεων δημιουργούν ειδοποιήσεις επιφάνειας εργασίας μέσω του κουμπιού {icon} μόλις ενεργοποιηθούν.",
   "notifications_permission_banner.title": "Μη χάσεις στιγμή",
-  "onboarding.follows.back": "Πίσω",
-  "onboarding.follows.done": "Έγινε",
+  "onboarding.action.back": "Επιστροφή",
+  "onboarding.actions.back": "Επιστροφή",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Πηγαίνετε στην αρχική σας ροή",
+  "onboarding.compose.template": "Γειά σου #Mastodon!",
   "onboarding.follows.empty": "Δυστυχώς, δεν μπορούν να εμφανιστούν αποτελέσματα αυτή τη στιγμή. Μπορείς να προσπαθήσεις να χρησιμοποιήσεις την αναζήτηση ή να περιηγηθείς στη σελίδα εξερεύνησης για να βρεις άτομα να ακολουθήσεις ή να δοκιμάσεις ξανά αργότερα.",
-  "onboarding.follows.search": "Αναζήτηση",
-  "onboarding.follows.title": "Ακολούθησε άτομα για να ξεκινήσεις",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Δημοφιλή στο Mastodon",
   "onboarding.profile.discoverable": "Κάνε το προφίλ μου ανακαλύψιμο",
   "onboarding.profile.discoverable_hint": "Όταν επιλέγεις την δυνατότητα ανακάλυψης στο Mastodon, οι αναρτήσεις σου μπορεί να εμφανιστούν στα αποτελέσματα αναζήτησης και τις τάσεις, και το προφίλ σου μπορεί να προτείνεται σε άτομα με παρόμοια ενδιαφέροντα με εσένα.",
   "onboarding.profile.display_name": "Εμφανιζόμενο όνομα",
   "onboarding.profile.display_name_hint": "Το πλήρες ή το διασκεδαστικό σου όνομα…",
+  "onboarding.profile.lead": "Μπορείς πάντα να το ολοκληρώσεις αργότερα στις ρυθμίσεις, όπου είναι διαθέσιμες ακόμα περισσότερες επιλογές προσαρμογής.",
   "onboarding.profile.note": "Βιογραφικό",
   "onboarding.profile.note_hint": "Μπορείτε να @αναφέρετε άλλα άτομα ή #hashtags…",
   "onboarding.profile.save_and_continue": "Αποθήκευση και συνέχεια",
   "onboarding.profile.title": "Ρύθμιση προφίλ",
   "onboarding.profile.upload_avatar": "Μεταφόρτωση εικόνας προφίλ",
   "onboarding.profile.upload_header": "Μεταφόρτωση κεφαλίδας προφίλ",
+  "onboarding.share.lead": "Let people know how they can find you on Mastodon!\nΕνημερώστε άλλα άτομα πώς μπορούν να σας βρουν στο Mastodon!",
+  "onboarding.share.message": "Με λένε {username} στο #Mastodon! Έλα να με ακολουθήσεις στο {url}",
+  "onboarding.share.next_steps": "Πιθανά επόμενα βήματα:",
+  "onboarding.share.title": "Κοινοποίηση του προφίλ σου",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "You've made it!\nΤα καταφέρατε!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Κάντε την πρώτη σας δημοσίευση",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
+  "onboarding.tips.2fa": "<strong>Το ήξερες;</strong> Μπορείς να ασφαλίσεις το λογαριασμό σου ρυθμίζοντας ταυτότητα δύο παραγόντων στις ρυθμίσεις του λογαριασμού σου. Λειτουργεί με οποιαδήποτε εφαρμογή TOTP της επιλογής σας, δεν απαιτείται αριθμός τηλεφώνου!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Το ήξερες;</strong> Από τη στιγμή που το Mastodon είναι αποκεντρωμένο, κάποια προφίλ που συναντάς θα φιλοξενούνται σε διακομιστές διαφορετικούς από τον δικό σου. Και παρόλα αυτά μπορείς να αλληλεπιδράσεις μαζί τους απρόσκοπτα! Ο διακομιστής τους είναι στο δεύτερο μισό του ονόματος χρήστη!",
+  "onboarding.tips.migration": "<strong>Το ήξερες;</strong> Αν αισθάνεσαι ότι το {domain} δεν είναι η κατάλληλη επιλογή διακομιστή για σένα στο μέλλον, μπορείς να μετακινηθείς σε άλλο διακομιστή Mastodon χωρίς να χάσεις τους ακόλουθούς σου. Μπορείς να κάνεις ακόμα και τον δικό σου διακομιστή!",
+  "onboarding.tips.verification": "<strong>Το ήξερες;</strong> Μπορείς να επαληθεύσεις τον λογαριασμό σου βάζοντας έναν σύνδεσμο του προφίλ σου στο Mastodon στην ιστοσελίδα σου και να προσθέσεις την ιστοσελίδα στο προφίλ σου. Χωρίς έξοδα ή έγγραφα!",
   "password_confirmation.exceeds_maxlength": "Η επιβεβαίωση κωδικού πρόσβασης υπερβαίνει το μέγιστο μήκος κωδικού πρόσβασης",
   "password_confirmation.mismatching": "Η επιβεβαίωση του κωδικού πρόσβασης δε συμπίπτει",
   "picture_in_picture.restore": "Βάλε το πίσω",
@@ -696,7 +665,7 @@
   "poll_button.remove_poll": "Αφαίρεση δημοσκόπησης",
   "privacy.change": "Προσαρμογή ιδιωτικότητας ανάρτησης",
   "privacy.direct.long": "Όλοι όσοι αναφέρθηκαν στη δημοσίευση",
-  "privacy.direct.short": "Ιδιωτική επισήμανση",
+  "privacy.direct.short": "Συγκεκριμένα άτομα",
   "privacy.private.long": "Μόνο οι ακόλουθοί σας",
   "privacy.private.short": "Ακόλουθοι",
   "privacy.public.long": "Όλοι εντός και εκτός του Mastodon",
@@ -708,8 +677,8 @@
   "privacy_policy.title": "Πολιτική Απορρήτου",
   "recommended": "Προτεινόμενα",
   "refresh": "Ανανέωση",
-  "regeneration_indicator.please_stand_by": "Παρακαλούμε περίμενε.",
-  "regeneration_indicator.preparing_your_home_feed": "Ετοιμάζουμε την αρχική σου ροή…",
+  "regeneration_indicator.label": "Φορτώνει…",
+  "regeneration_indicator.sublabel": "Η αρχική σου ροή ετοιμάζεται!",
   "relative_time.days": "{number}η",
   "relative_time.full.days": "πριν από {number, plural, one {# μέρα} other {# μέρες}}",
   "relative_time.full.hours": "πριν από {number, plural, one {# ώρα} other {# ώρες}}",
@@ -793,11 +762,10 @@
   "search_results.accounts": "Προφίλ",
   "search_results.all": "Όλα",
   "search_results.hashtags": "Ετικέτες",
-  "search_results.no_results": "Κανένα αποτέλεσμα.",
-  "search_results.no_search_yet": "Δοκίμασε να ψάξεις για δημοσιεύσεις, προφίλ ή ετικέτες.",
+  "search_results.nothing_found": "Δεν βρέθηκε τίποτα με αυτούς τους όρους αναζήτησης",
   "search_results.see_all": "Δες τα όλα",
   "search_results.statuses": "Αναρτήσεις",
-  "search_results.title": "Αναζήτηση για «{q}»",
+  "search_results.title": "Αναζήτηση για {q}",
   "server_banner.about_active_users": "Άτομα που χρησιμοποιούν αυτόν τον διακομιστή κατά τις τελευταίες 30 ημέρες (Μηνιαία Ενεργοί Χρήστες)",
   "server_banner.active_users": "ενεργοί χρήστες",
   "server_banner.administered_by": "Διαχειριστής:",
@@ -849,7 +817,6 @@
   "status.reblogs.empty": "Κανείς δεν ενίσχυσε αυτή την ανάρτηση ακόμα. Μόλις το κάνει κάποιος, θα εμφανιστεί εδώ.",
   "status.redraft": "Σβήσε & ξαναγράψε",
   "status.remove_bookmark": "Αφαίρεση σελιδοδείκτη",
-  "status.remove_favourite": "Κατάργηση από τα αγαπημένα",
   "status.replied_in_thread": "Απαντήθηκε σε νήμα",
   "status.replied_to": "Απάντησε στον {name}",
   "status.reply": "Απάντησε",
@@ -871,7 +838,6 @@
   "subscribed_languages.target": "Αλλαγή εγγεγραμμένων γλωσσών για {target}",
   "tabs_bar.home": "Αρχική",
   "tabs_bar.notifications": "Ειδοποιήσεις",
-  "terms_of_service.title": "Όροι Παροχής Υπηρεσιών",
   "time_remaining.days": "απομένουν {number, plural, one {# ημέρα} other {# ημέρες}}",
   "time_remaining.hours": "απομένουν {number, plural, one {# ώρα} other {# ώρες}}",
   "time_remaining.minutes": "απομένουν {number, plural, one {# λεπτό} other {# λεπτά}}",
@@ -887,12 +853,26 @@
   "upload_button.label": "Πρόσθεσε εικόνες, ένα βίντεο ή ένα αρχείο ήχου",
   "upload_error.limit": "Υπέρβαση ορίου μεγέθους ανεβασμένων αρχείων.",
   "upload_error.poll": "Στις δημοσκοπήσεις δεν επιτρέπεται η μεταφόρτωση αρχείου.",
+  "upload_form.audio_description": "Περιγραφή για άτομα με προβλήματα ακοής",
+  "upload_form.description": "Περιγραφή για άτομα με προβλήματα όρασης",
   "upload_form.drag_and_drop.instructions": "Για να επιλέξετε ένα συνημμένο αρχείο πολυμέσων, πατήστε το Space ή το Enter. Ενώ το σέρνετε, χρησιμοποιήστε τα πλήκτρα βέλους για να μετακινήσετε το συνημμένο αρχείο πολυμέσων προς οποιαδήποτε κατεύθυνση. Πατήστε ξανά το Space ή το Enter για να αποθέσετε το συνημμένο αρχείο πολυμέσων στη νέα του θέση ή πατήστε το Escape για ακύρωση.",
   "upload_form.drag_and_drop.on_drag_cancel": "Η μετακίνηση ακυρώθηκε. Έγινε απόθεση του συνημμένου αρχείου πολυμέσων «{item}».",
   "upload_form.drag_and_drop.on_drag_end": "Έγινε απόθεση του συνημμένου αρχείου πολυμέσων «{item}».",
   "upload_form.drag_and_drop.on_drag_over": "Έγινε μετακίνηση του συνημμένου αρχείου πολυμέσων «{item}».",
   "upload_form.drag_and_drop.on_drag_start": "Έγινε επιλογή του συνημμένου αρχείου πολυμέσων «{item}».",
   "upload_form.edit": "Επεξεργασία",
+  "upload_form.thumbnail": "Αλλαγή μικρογραφίας",
+  "upload_form.video_description": "Περιγραφή για άτομα με προβλήματα ακοής ή όρασης",
+  "upload_modal.analyzing_picture": "Ανάλυση εικόνας…",
+  "upload_modal.apply": "Εφαρμογή",
+  "upload_modal.applying": "Εφαρμογή…",
+  "upload_modal.choose_image": "Επιλογή εικόνας",
+  "upload_modal.description_placeholder": "Αρνάκι άσπρο και παχύ της μάνας του καμάρι",
+  "upload_modal.detect_text": "Αναγνώριση κειμένου από την εικόνα",
+  "upload_modal.edit_media": "Επεξεργασία Πολυμέσων",
+  "upload_modal.hint": "Κάνε κλικ ή σείρε τον κύκλο στην προεπισκόπηση για να επιλέξεις το σημείο εστίασης που θα είναι πάντα εμφανές σε όλες τις μικρογραφίες.",
+  "upload_modal.preparing_ocr": "Προετοιμασία αναγνώρισης κειμένου…",
+  "upload_modal.preview_label": "Προεπισκόπηση ({ratio})",
   "upload_progress.label": "Μεταφόρτωση...",
   "upload_progress.processing": "Επεξεργασία…",
   "username.taken": "Αυτό το όνομα χρήστη χρησιμοποιείται. Δοκίμασε ένα άλλο",
@@ -902,6 +882,8 @@
   "video.expand": "Επέκταση βίντεο",
   "video.fullscreen": "Πλήρης οθόνη",
   "video.hide": "Απόκρυψη βίντεο",
+  "video.mute": "Σίγαση ήχου",
   "video.pause": "Παύση",
-  "video.play": "Αναπαραγωγή"
+  "video.play": "Αναπαραγωγή",
+  "video.unmute": "Αναπαραγωγή ήχου"
 }
diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json
index b46d02baa9..bbf6fec730 100644
--- a/app/javascript/mastodon/locales/en-GB.json
+++ b/app/javascript/mastodon/locales/en-GB.json
@@ -29,6 +29,7 @@
   "account.endorse": "Feature on profile",
   "account.featured_tags.last_status_at": "Last post on {date}",
   "account.featured_tags.last_status_never": "No posts",
+  "account.featured_tags.title": "{name}'s featured hashtags",
   "account.follow": "Follow",
   "account.follow_back": "Follow back",
   "account.followers": "Followers",
@@ -85,33 +86,7 @@
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
   "alt_text_badge.title": "Alt text",
-  "alt_text_modal.add_alt_text": "Add alt text",
-  "alt_text_modal.add_text_from_image": "Add text from image",
-  "alt_text_modal.cancel": "Cancel",
-  "alt_text_modal.change_thumbnail": "Change thumbnail",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Describe this for people with hearing impairments…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Describe this for people with visual impairments…",
-  "alt_text_modal.done": "Done",
   "announcement.announcement": "Announcement",
-  "annual_report.summary.archetype.booster": "The cool-hunter",
-  "annual_report.summary.archetype.lurker": "The lurker",
-  "annual_report.summary.archetype.oracle": "The oracle",
-  "annual_report.summary.archetype.pollster": "The pollster",
-  "annual_report.summary.archetype.replier": "The social butterfly",
-  "annual_report.summary.followers.followers": "followers",
-  "annual_report.summary.followers.total": "{count} total",
-  "annual_report.summary.here_it_is": "Here is your {year} in review:",
-  "annual_report.summary.highlighted_post.by_favourites": "most favourited post",
-  "annual_report.summary.highlighted_post.by_reblogs": "most boosted post",
-  "annual_report.summary.highlighted_post.by_replies": "post with the most replies",
-  "annual_report.summary.highlighted_post.possessive": "{name}'s",
-  "annual_report.summary.most_used_app.most_used_app": "most used app",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "most used hashtag",
-  "annual_report.summary.most_used_hashtag.none": "None",
-  "annual_report.summary.new_posts.new_posts": "new posts",
-  "annual_report.summary.percentile.text": "<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of {domain} users.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.",
-  "annual_report.summary.thanks": "Thanks for being part of Mastodon!",
   "attachments_list.unprocessed": "(unprocessed)",
   "audio.hide": "Hide audio",
   "block_modal.remote_users_caveat": "We will ask the server {domain} to respect your decision. However, compliance is not guaranteed since some servers may handle blocks differently. Public posts may still be visible to non-logged-in users.",
@@ -135,7 +110,7 @@
   "bundle_column_error.routing.body": "The requested page could not be found. Are you sure the URL in the address bar is correct?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Close",
-  "bundle_modal_error.message": "Something went wrong while loading this screen.",
+  "bundle_modal_error.message": "Something went wrong while loading this component.",
   "bundle_modal_error.retry": "Try again",
   "closed_registrations.other_server_instructions": "Since Mastodon is decentralised, you can create an account on another server and still interact with this one.",
   "closed_registrations_modal.description": "Creating an account on {domain} is currently not possible, but please keep in mind that you do not need an account specifically on {domain} to use Mastodon.",
@@ -146,16 +121,13 @@
   "column.blocks": "Blocked users",
   "column.bookmarks": "Bookmarks",
   "column.community": "Local timeline",
-  "column.create_list": "Create list",
   "column.direct": "Private mentions",
   "column.directory": "Browse profiles",
   "column.domain_blocks": "Blocked domains",
-  "column.edit_list": "Edit list",
   "column.favourites": "Favourites",
   "column.firehose": "Live feeds",
   "column.follow_requests": "Follow requests",
   "column.home": "Home",
-  "column.list_members": "Manage list members",
   "column.lists": "Lists",
   "column.mutes": "Muted users",
   "column.notifications": "Notifications",
@@ -168,7 +140,6 @@
   "column_header.pin": "Pin",
   "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
-  "column_search.cancel": "Cancel",
   "column_subheading.settings": "Settings",
   "community.column_settings.local_only": "Local only",
   "community.column_settings.media_only": "Media Only",
@@ -187,7 +158,7 @@
   "compose_form.poll.duration": "Poll duration",
   "compose_form.poll.multiple": "Multiple choice",
   "compose_form.poll.option_placeholder": "Option {number}",
-  "compose_form.poll.single": "Single choice",
+  "compose_form.poll.single": "Pick one",
   "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
   "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
   "compose_form.poll.type": "Style",
@@ -211,16 +182,9 @@
   "confirmations.edit.confirm": "Edit",
   "confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.edit.title": "Overwrite post?",
-  "confirmations.follow_to_list.confirm": "Follow and add to list",
-  "confirmations.follow_to_list.message": "You need to be following {name} to add them to a list.",
-  "confirmations.follow_to_list.title": "Follow user?",
   "confirmations.logout.confirm": "Log out",
   "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.logout.title": "Log out?",
-  "confirmations.missing_alt_text.confirm": "Add alt text",
-  "confirmations.missing_alt_text.message": "Your post contains media without alt text. Adding descriptions helps make your content accessible to more people.",
-  "confirmations.missing_alt_text.secondary": "Post anyway",
-  "confirmations.missing_alt_text.title": "Add alt text?",
   "confirmations.mute.confirm": "Mute",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
@@ -249,10 +213,10 @@
   "disabled_account_banner.text": "Your account {disabledAccount} is currently disabled.",
   "dismissable_banner.community_timeline": "These are the most recent public posts from people whose accounts are hosted by {domain}.",
   "dismissable_banner.dismiss": "Dismiss",
-  "dismissable_banner.explore_links": "These news stories are being shared the most on the Fediverse today. Newer news stories posted by more different people are ranked higher.",
-  "dismissable_banner.explore_statuses": "These posts from across the Fediverse are gaining traction today. Newer posts with more boosts and favourites are ranked higher.",
-  "dismissable_banner.explore_tags": "These hashtags are gaining traction on the Fediverse today. Hashtags that are used by more different people are ranked higher.",
-  "dismissable_banner.public_timeline": "These are the most recent public posts from people on the Fediverse that people on {domain} follow.",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralised network right now.",
+  "dismissable_banner.explore_statuses": "These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favourites are ranked higher.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralised network right now.",
+  "dismissable_banner.public_timeline": "These are the most recent public posts from people on the social web that people on {domain} follow.",
   "domain_block_modal.block": "Block server",
   "domain_block_modal.block_account_instead": "Block @{name} instead",
   "domain_block_modal.they_can_interact_with_old_posts": "People from this server can interact with your old posts.",
@@ -309,6 +273,7 @@
   "empty_column.hashtag": "There is nothing in this hashtag yet.",
   "empty_column.home": "Your home timeline is empty! Follow more people to fill it up.",
   "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here according to your settings.",
   "empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.",
@@ -319,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Try disabling them and refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
   "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
   "errors.unexpected_crash.report_issue": "Report issue",
+  "explore.search_results": "Search results",
   "explore.suggested_follows": "People",
   "explore.title": "Explore",
   "explore.trending_links": "News",
@@ -368,14 +334,13 @@
   "footer.about": "About",
   "footer.directory": "Profiles directory",
   "footer.get_app": "Get the app",
+  "footer.invite": "Invite people",
   "footer.keyboard_shortcuts": "Keyboard shortcuts",
   "footer.privacy_policy": "Privacy policy",
   "footer.source_code": "View source code",
   "footer.status": "Status",
-  "footer.terms_of_service": "Terms of service",
   "generic.saved": "Saved",
   "getting_started.heading": "Getting started",
-  "hashtag.admin_moderation": "Open moderation interface for #{name}",
   "hashtag.column_header.tag_mode.all": "and {additional}",
   "hashtag.column_header.tag_mode.any": "or {additional}",
   "hashtag.column_header.tag_mode.none": "without {additional}",
@@ -417,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignore notifications from people not following you?",
   "ignore_notifications_modal.not_following_title": "Ignore notifications from people you don't follow?",
   "ignore_notifications_modal.private_mentions_title": "Ignore notifications from unsolicited Private Mentions?",
-  "info_button.label": "Help",
-  "info_button.what_is_alt_text": "<h1>What is alt text?</h1> <p>Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.</p> <p>You can improve accessibility and understanding for everyone by writing clear, concise, and objective alt text.</p> <ul> <li>Capture important elements</li> <li>Summarise text in images</li> <li>Use regular sentence structure</li> <li>Avoid redundant information</li> <li>Focus on trends and key findings in complex visuals (like diagrams or maps)</li> </ul>",
-  "interaction_modal.action.favourite": "To continue, you need to favourite from your account.",
-  "interaction_modal.action.follow": "To continue, you need to follow from your account.",
-  "interaction_modal.action.reblog": "To continue, you need to reblog from your account.",
-  "interaction_modal.action.reply": "To continue, you need to reply from your account.",
-  "interaction_modal.action.vote": "To continue, you need to vote from your account.",
-  "interaction_modal.go": "Go",
-  "interaction_modal.no_account_yet": "Don't have an account yet?",
+  "interaction_modal.description.favourite": "With an account on Mastodon, you can favourite this post to let the author know you appreciate it and save it for later.",
+  "interaction_modal.description.follow": "With an account on Mastodon, you can follow {name} to receive their posts in your home feed.",
+  "interaction_modal.description.reblog": "With an account on Mastodon, you can boost this post to share it with your own followers.",
+  "interaction_modal.description.reply": "With an account on Mastodon, you can respond to this post.",
+  "interaction_modal.login.action": "Take me home",
+  "interaction_modal.login.prompt": "Domain of your home server, e.g. mastodon.social",
+  "interaction_modal.no_account_yet": "Not on Mastodon?",
   "interaction_modal.on_another_server": "On a different server",
   "interaction_modal.on_this_server": "On this server",
+  "interaction_modal.sign_in": "You are not logged in to this server. Where is your account hosted?",
+  "interaction_modal.sign_in_hint": "Tip: That's the website where you signed up. If you don't remember, look for the welcome e-mail in your inbox. You can also enter your full username! (e.g. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Favourite {name}'s post",
   "interaction_modal.title.follow": "Follow {name}",
   "interaction_modal.title.reblog": "Boost {name}'s post",
   "interaction_modal.title.reply": "Reply to {name}'s post",
-  "interaction_modal.title.vote": "Vote in {name}'s poll",
-  "interaction_modal.username_prompt": "E.g. {example}",
   "intervals.full.days": "{number, plural, one {# day} other {# days}}",
   "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
   "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
@@ -469,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toggle_sensitivity": "Show/hide media",
   "keyboard_shortcuts.toot": "to start a brand new post",
-  "keyboard_shortcuts.translate": "to translate a post",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
   "keyboard_shortcuts.up": "Move up in the list",
   "lightbox.close": "Close",
@@ -482,32 +444,20 @@
   "link_preview.author": "By {name}",
   "link_preview.more_from_author": "More from {name}",
   "link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}",
-  "lists.add_member": "Add",
-  "lists.add_to_list": "Add to list",
-  "lists.add_to_lists": "Add {name} to lists",
-  "lists.create": "Create",
-  "lists.create_a_list_to_organize": "Create a new list to organise your Home feed",
-  "lists.create_list": "Create list",
+  "lists.account.add": "Add to list",
+  "lists.account.remove": "Remove from list",
   "lists.delete": "Delete list",
-  "lists.done": "Done",
   "lists.edit": "Edit list",
-  "lists.exclusive": "Hide members in Home",
-  "lists.exclusive_hint": "If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.",
-  "lists.find_users_to_add": "Find users to add",
-  "lists.list_members": "List members",
-  "lists.list_members_count": "{count, plural, one {# member} other {# members}}",
-  "lists.list_name": "List name",
-  "lists.new_list_name": "New list name",
-  "lists.no_lists_yet": "No lists yet.",
-  "lists.no_members_yet": "No members yet.",
-  "lists.no_results_found": "No results found.",
-  "lists.remove_member": "Remove",
+  "lists.edit.submit": "Change title",
+  "lists.exclusive": "Hide these posts from home",
+  "lists.new.create": "Add list",
+  "lists.new.title_placeholder": "New list title",
   "lists.replies_policy.followed": "Any followed user",
   "lists.replies_policy.list": "Members of the list",
   "lists.replies_policy.none": "No one",
-  "lists.save": "Save",
-  "lists.search": "Search",
-  "lists.show_replies_to": "Include replies from list members to",
+  "lists.replies_policy.title": "Show replies to:",
+  "lists.search": "Search among people you follow",
+  "lists.subheading": "Your lists",
   "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Loading…",
   "media_gallery.hide": "Hide",
@@ -556,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} reported {target}",
   "notification.admin.sign_up": "{name} signed up",
   "notification.admin.sign_up.name_and_others": "{name} and {count, plural, one {# other} other {# others}} signed up",
-  "notification.annual_report.message": "Your {year} #Wrapstodon awaits! Unveil your year's highlights and memorable moments on Mastodon!",
-  "notification.annual_report.view": "View #Wrapstodon",
   "notification.favourite": "{name} favourited your post",
   "notification.favourite.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favourited your post",
-  "notification.favourite_pm": "{name} favourited your private mention",
-  "notification.favourite_pm.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favourited your private mention",
   "notification.follow": "{name} followed you",
   "notification.follow.name_and_others": "{name} and <a>{count, plural, one {# other} other {# others}}</a> followed you",
   "notification.follow_request": "{name} has requested to follow you",
@@ -666,21 +612,44 @@
   "notifications_permission_banner.enable": "Enable desktop notifications",
   "notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
   "notifications_permission_banner.title": "Never miss a thing",
-  "onboarding.follows.back": "Back",
-  "onboarding.follows.done": "Done",
+  "onboarding.action.back": "Take me back",
+  "onboarding.actions.back": "Take me back",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Take me to my home feed",
+  "onboarding.compose.template": "Hello #Mastodon!",
   "onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.",
-  "onboarding.follows.search": "Search",
-  "onboarding.follows.title": "Follow people to get started",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Personalize your home feed",
   "onboarding.profile.discoverable": "Make my profile discoverable",
   "onboarding.profile.discoverable_hint": "When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.",
   "onboarding.profile.display_name": "Display name",
   "onboarding.profile.display_name_hint": "Your full name or your fun name…",
+  "onboarding.profile.lead": "You can always complete this later in the settings, where even more customisation options are available.",
   "onboarding.profile.note": "Bio",
   "onboarding.profile.note_hint": "You can @mention other people or #hashtags…",
   "onboarding.profile.save_and_continue": "Save and continue",
   "onboarding.profile.title": "Profile setup",
   "onboarding.profile.upload_avatar": "Upload profile picture",
   "onboarding.profile.upload_header": "Upload profile header",
+  "onboarding.share.lead": "Let people know how they can find you on Mastodon!",
+  "onboarding.share.message": "I'm {username} on #Mastodon! Come follow me at {url}",
+  "onboarding.share.next_steps": "Possible next steps:",
+  "onboarding.share.title": "Share your profile",
+  "onboarding.start.lead": "You're now part of Mastodon, a unique, decentralized social media platform where you—not an algorithm—curate your own experience. Let's get you started on this new social frontier:",
+  "onboarding.start.skip": "Don't need help getting started?",
+  "onboarding.start.title": "You've made it!",
+  "onboarding.steps.follow_people.body": "Following interesting people is what Mastodon is all about.",
+  "onboarding.steps.follow_people.title": "Personalize your home feed",
+  "onboarding.steps.publish_status.body": "Say hello to the world with text, photos, videos, or polls {emoji}",
+  "onboarding.steps.publish_status.title": "Make your first post",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customise your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your Mastodon profile",
+  "onboarding.tips.2fa": "<strong>Did you know?</strong> You can secure your account by setting up two-factor authentication in your account settings. It works with any TOTP app of your choice, no phone number necessary!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Did you know?</strong> Since Mastodon is decentralised, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!",
+  "onboarding.tips.migration": "<strong>Did you know?</strong> If you feel like {domain} is not a great server choice for you in the future, you can move to another Mastodon server without losing your followers. You can even host your own server!",
+  "onboarding.tips.verification": "<strong>Did you know?</strong> You can verify your account by putting a link to your Mastodon profile on your own website and adding the website to your profile. No fees or documents necessary!",
   "password_confirmation.exceeds_maxlength": "Password confirmation exceeds the maximum password length",
   "password_confirmation.mismatching": "Password confirmation does not match",
   "picture_in_picture.restore": "Put it back",
@@ -696,7 +665,7 @@
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Change post privacy",
   "privacy.direct.long": "Everyone mentioned in the post",
-  "privacy.direct.short": "Private mention",
+  "privacy.direct.short": "Specific people",
   "privacy.private.long": "Only your followers",
   "privacy.private.short": "Followers",
   "privacy.public.long": "Anyone on and off Mastodon",
@@ -708,8 +677,8 @@
   "privacy_policy.title": "Privacy Policy",
   "recommended": "Recommended",
   "refresh": "Refresh",
-  "regeneration_indicator.please_stand_by": "Please stand by.",
-  "regeneration_indicator.preparing_your_home_feed": "Preparing your home feed…",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
   "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
@@ -793,11 +762,10 @@
   "search_results.accounts": "Profiles",
   "search_results.all": "All",
   "search_results.hashtags": "Hashtags",
-  "search_results.no_results": "No results.",
-  "search_results.no_search_yet": "Try searching for posts, profiles or hashtags.",
+  "search_results.nothing_found": "Could not find anything for these search terms",
   "search_results.see_all": "See all",
   "search_results.statuses": "Posts",
-  "search_results.title": "Search for \"{q}\"",
+  "search_results.title": "Search for {q}",
   "server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)",
   "server_banner.active_users": "active users",
   "server_banner.administered_by": "Administered by:",
@@ -849,7 +817,6 @@
   "status.reblogs.empty": "No one has boosted this post yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.remove_bookmark": "Remove bookmark",
-  "status.remove_favourite": "Remove from favourites",
   "status.replied_in_thread": "Replied in thread",
   "status.replied_to": "Replied to {name}",
   "status.reply": "Reply",
@@ -871,7 +838,6 @@
   "subscribed_languages.target": "Change subscribed languages for {target}",
   "tabs_bar.home": "Home",
   "tabs_bar.notifications": "Notifications",
-  "terms_of_service.title": "Terms of Service",
   "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
   "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
   "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
@@ -887,12 +853,26 @@
   "upload_button.label": "Add images, a video or an audio file",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
+  "upload_form.audio_description": "Describe for people who are deaf or hard of hearing",
+  "upload_form.description": "Describe for people who are blind or have low vision",
   "upload_form.drag_and_drop.instructions": "To pick up a media attachment, press space or enter. While dragging, use the arrow keys to move the media attachment in any given direction. Press space or enter again to drop the media attachment in its new position, or press escape to cancel.",
   "upload_form.drag_and_drop.on_drag_cancel": "Dragging was cancelled. Media attachment {item} was dropped.",
   "upload_form.drag_and_drop.on_drag_end": "Media attachment {item} was dropped.",
   "upload_form.drag_and_drop.on_drag_over": "Media attachment {item} was moved.",
   "upload_form.drag_and_drop.on_drag_start": "Picked up media attachment {item}.",
   "upload_form.edit": "Edit",
+  "upload_form.thumbnail": "Change thumbnail",
+  "upload_form.video_description": "Describe for people who are deaf, hard of hearing, blind or have low vision",
+  "upload_modal.analyzing_picture": "Analysing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.applying": "Applying…",
+  "upload_modal.choose_image": "Choose image",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preparing_ocr": "Preparing OCR…",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Uploading…",
   "upload_progress.processing": "Processing…",
   "username.taken": "That username is taken. Try another",
@@ -902,6 +882,8 @@
   "video.expand": "Expand video",
   "video.fullscreen": "Full screen",
   "video.hide": "Hide video",
+  "video.mute": "Mute sound",
   "video.pause": "Pause",
-  "video.play": "Play"
+  "video.play": "Play",
+  "video.unmute": "Unmute sound"
 }
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index cccae96dad..87eacaaa28 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -16,7 +16,7 @@
   "about.kmyblue_capabilities": "Features available in this server",
   "about.kmyblue_capability": "This server is using kmyblue, a fork of Mastodon. On this server, kmyblues unique features are configured as follows.",
   "about.not_available": "This information has not been made available on this server.",
-  "about.powered_by": "Decentralized social media powered by {domain}",
+  "about.powered_by": "Decentralized social media powered by {mastodon}",
   "about.public_visibility": "Public visibility",
   "about.rules": "Server rules",
   "account.account_note_header": "Personal note",
@@ -38,11 +38,9 @@
   "account.edit_profile": "Edit profile",
   "account.enable_notifications": "Notify me when @{name} posts",
   "account.endorse": "Feature on profile",
-  "account.featured": "Featured",
-  "account.featured.hashtags": "Hashtags",
-  "account.featured.posts": "Posts",
   "account.featured_tags.last_status_at": "Last post on {date}",
   "account.featured_tags.last_status_never": "No posts",
+  "account.featured_tags.title": "{name}'s featured hashtags",
   "account.follow": "Follow",
   "account.follow_back": "Follow back",
   "account.followers": "Followers",
@@ -79,7 +77,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}",
   "account.unblock": "Unblock @{name}",
   "account.unblock_domain": "Unblock domain {domain}",
-  "account.unblock_domain_short": "Unblock",
   "account.unblock_short": "Unblock",
   "account.unendorse": "Don't feature on profile",
   "account.unfollow": "Unfollow",
@@ -102,94 +99,48 @@
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
   "alt_text_badge.title": "Alt text",
-  "alt_text_modal.add_alt_text": "Add alt text",
-  "alt_text_modal.add_text_from_image": "Add text from image",
-  "alt_text_modal.cancel": "Cancel",
-  "alt_text_modal.change_thumbnail": "Change thumbnail",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Describe this for people with hearing impairments…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Describe this for people with visual impairments…",
-  "alt_text_modal.done": "Done",
   "announcement.announcement": "Announcement",
-  "annual_report.summary.archetype.booster": "The cool-hunter",
-  "annual_report.summary.archetype.lurker": "The lurker",
-  "annual_report.summary.archetype.oracle": "The oracle",
-  "annual_report.summary.archetype.pollster": "The pollster",
-  "annual_report.summary.archetype.replier": "The social butterfly",
-  "annual_report.summary.followers.followers": "followers",
-  "annual_report.summary.followers.total": "{count} total",
-  "annual_report.summary.here_it_is": "Here is your {year} in review:",
-  "annual_report.summary.highlighted_post.by_favourites": "most favourited post",
-  "annual_report.summary.highlighted_post.by_reblogs": "most boosted post",
-  "annual_report.summary.highlighted_post.by_replies": "post with the most replies",
-  "annual_report.summary.highlighted_post.possessive": "{name}'s",
-  "annual_report.summary.most_used_app.most_used_app": "most used app",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "most used hashtag",
-  "annual_report.summary.most_used_hashtag.none": "None",
-  "annual_report.summary.new_posts.new_posts": "new posts",
-  "annual_report.summary.percentile.text": "<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of {domain} users.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.",
-  "annual_report.summary.thanks": "Thanks for being part of Mastodon!",
+  "antennas.account.add": "Add to antenna",
+  "antennas.account.remove": "Remove from antenna",
   "antennas.accounts": "{count} accounts",
   "antennas.add_domain": "Add domain",
   "antennas.add_domain_placeholder": "New domain",
   "antennas.add_keyword": "Add keyword",
   "antennas.add_keyword_placeholder": "New keyword",
-  "antennas.add_member": "Add",
   "antennas.add_tag": "Add tag",
   "antennas.add_tag_placeholder": "New tag",
-  "antennas.add_to_antenna": "Add to antenna",
-  "antennas.add_to_antennas": "Add {name} to antennas",
-  "antennas.antenna_accounts": "Antenna accounts",
-  "antennas.antenna_accounts_count": "{count, plural, one {# member} other {# accounts}}",
-  "antennas.antenna_name": "Antenna name",
-  "antennas.badge_ltl": "LTL",
-  "antennas.badge_stl": "STL",
-  "antennas.create": "Create",
-  "antennas.create_a_antenna_to_organize": "Create a new antenna to organize your Home feed",
-  "antennas.create_antenna": "Create antenna",
   "antennas.delete": "Delete antenna",
-  "antennas.destination": "Destination",
-  "antennas.destination.home": "Insert to home",
-  "antennas.destination.list": "Insert to list",
-  "antennas.destination.timeline": "Antenna timeline only",
   "antennas.domains": "{count} domains",
-  "antennas.done": "Done",
   "antennas.edit": "Edit antenna",
+  "antennas.edit.submit": "Change title",
   "antennas.edit_accounts": "Edit accounts",
+  "antennas.edit_static": "Edit antenna",
   "antennas.exclude_accounts": "Exclude accounts",
   "antennas.exclude_domains": "Exclude domains",
   "antennas.exclude_keywords": "Exclude keywords",
   "antennas.exclude_tags": "Exclude tags",
-  "antennas.favourite": "Favorite",
-  "antennas.favourite_hint": "When opening the Web Client on a PC, this antenna appears in the navigation.",
-  "antennas.filter_items": "Move to antenna filter setting",
+  "antennas.filter": "Filter",
   "antennas.filter_not": "Filter Not",
-  "antennas.find_users_to_add": "Find users to add",
+  "antennas.go_timeline": "Go to antenna timeline",
   "antennas.ignore_reblog": "Exclude boosts",
-  "antennas.ignore_reblog_hint": "Boosts will be excluded from antenna detection.",
   "antennas.in_ltl_mode": "This antenna is in LTL mode.",
   "antennas.in_stl_mode": "This antenna is in STL mode.",
+  "antennas.insert_feeds": "Insert to feeds",
+  "antennas.insert_home": "Home",
+  "antennas.insert_list": "List",
   "antennas.keywords": "{count} keywords",
-  "antennas.list_selection": "List to insert",
+  "antennas.ltl": "LTL mode",
   "antennas.media_only": "Media only",
-  "antennas.media_only_hint": "Only posts with media will be added antenna.",
-  "antennas.memo_insert_home": "Inserts home timeline.",
-  "antennas.memo_insert_list": "List: \"{title}\"",
-  "antennas.mode": "Mode",
-  "antennas.mode.filtering": "Filtering",
-  "antennas.mode.ltl": "Local timeline mode",
-  "antennas.mode.stl": "Social timeline mode",
-  "antennas.new_antenna_name": "New antenna name",
-  "antennas.no_antennas_yet": "No antennas yet.",
-  "antennas.no_members_yet": "No members yet.",
-  "antennas.no_results_found": "No results found.",
-  "antennas.remove_member": "Remove",
-  "antennas.save": "Save",
-  "antennas.save_to_edit_filtering": "You can edit the filtering after saving.",
-  "antennas.search_placeholder": "Search people you follow",
+  "antennas.new.create": "Add antenna",
+  "antennas.new.title_placeholder": "New antenna title",
+  "antennas.not_related_list": "This antenna is not related list. Posts will appear in home timeline. Open edit page to set list.",
+  "antennas.related_list": "This antenna is related to {listTitle}.",
+  "antennas.search": "Search among people you follow",
   "antennas.select.no_options_message": "Empty lists",
   "antennas.select.placeholder": "Select list",
   "antennas.select.set_home": "Set home",
+  "antennas.stl": "STL mode",
+  "antennas.subheading": "Your antennas",
   "antennas.tags": "{count} tags",
   "antennas.warnings.content_radio": "Simultaneous keyword and tag designation is not recommended.",
   "antennas.warnings.range_radio": "Simultaneous account and domain designation is not recommended.",
@@ -203,17 +154,15 @@
   "block_modal.they_will_know": "They can see that they're blocked.",
   "block_modal.title": "Block user?",
   "block_modal.you_wont_see_mentions": "You won't see posts that mention them.",
-  "bookmark_categories.add_to_bookmark_categories": "Add {name} to bookmark_categories",
   "bookmark_categories.all_bookmarks": "All bookmarks",
-  "bookmark_categories.bookmark_category_name": "BookmarkCategory name",
-  "bookmark_categories.create": "Create",
-  "bookmark_categories.create_a_bookmark_category_to_organize": "Create a new bookmark_category to organize your Home feed",
-  "bookmark_categories.create_bookmark_category": "Create bookmark_category",
   "bookmark_categories.delete": "Delete category",
   "bookmark_categories.edit": "Edit category",
-  "bookmark_categories.new_bookmark_category_name": "New bookmark_category name",
-  "bookmark_categories.no_bookmark_categories_yet": "No bookmark_categories yet.",
-  "bookmark_categories.save": "Save",
+  "bookmark_categories.edit.submit": "Change title",
+  "bookmark_categories.new.create": "Add category",
+  "bookmark_categories.new.title_placeholder": "New category title",
+  "bookmark_categories.status.add": "Add to bookmark category",
+  "bookmark_categories.status.remove": "Remove from bookmark category",
+  "bookmark_categories.subheading": "Your categories",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "boost_modal.reblog": "Boost post?",
   "boost_modal.undo_reblog": "Unboost post?",
@@ -227,29 +176,17 @@
   "bundle_column_error.routing.body": "The requested page could not be found. Are you sure the URL in the address bar is correct?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Close",
-  "bundle_modal_error.message": "Something went wrong while loading this screen.",
+  "bundle_modal_error.message": "Something went wrong while loading this component.",
   "bundle_modal_error.retry": "Try again",
-  "circles.add_member": "Add",
-  "circles.add_to_circle": "Add to circle",
-  "circles.add_to_circles": "Add {name} to circles",
-  "circles.circle_members": "Circle members",
-  "circles.circle_members_count": "{count, plural, one {# member} other {# members}}",
-  "circles.circle_name": "Circle name",
-  "circles.create": "Create",
-  "circles.create_a_circle_to_organize": "Create a new circle to organize your Home feed",
-  "circles.create_circle": "Create circle",
+  "circles.account.add": "Add to circle",
+  "circles.account.remove": "Remove from circle",
   "circles.delete": "Delete circle",
-  "circles.done": "Done",
   "circles.edit": "Edit circle",
-  "circles.find_users_to_add": "Find users to add",
-  "circles.new_circle_name": "New circle name",
-  "circles.no_circles_yet": "No circles yet.",
-  "circles.no_members_yet": "No members yet.",
-  "circles.no_results_found": "No results found.",
-  "circles.remove_member": "Remove",
-  "circles.save": "Save",
-  "circles.save_to_edit_member": "You can edit circle members after saving.",
-  "circles.search_placeholder": "Search people you follow",
+  "circles.edit.submit": "Change title",
+  "circles.new.create": "Add circle",
+  "circles.new.title_placeholder": "New circle title",
+  "circles.search": "Search among people follow you",
+  "circles.subheading": "Your circles",
   "closed_registrations.other_server_instructions": "Since Mastodon is decentralized, you can create an account on another server and still interact with this one.",
   "closed_registrations_modal.description": "Creating an account on {domain} is currently not possible, but please keep in mind that you do not need an account specifically on {domain} to use Mastodon.",
   "closed_registrations_modal.description_when_reaching_limit": "New registrations are currently temporarily restricted. Either the maximum number of registrations has been reached for registration. Please contact the administrator for more information or wait until the restriction is lifted.",
@@ -257,32 +194,21 @@
   "closed_registrations_modal.preamble": "Mastodon is decentralized, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!",
   "closed_registrations_modal.title": "Signing up on Mastodon",
   "column.about": "About",
-  "column.antenna_members": "Manage antenna members",
   "column.antennas": "Antennas",
   "column.blocks": "Blocked users",
   "column.bookmark_categories": "Bookmark categories",
   "column.bookmarks": "Bookmarks",
-  "column.circle_members": "Manage circle members",
   "column.circles": "Circles",
   "column.community": "Local timeline",
-  "column.create_antenna": "Create antenna",
-  "column.create_bookmark_category": "Create bookmark_category",
-  "column.create_circle": "Create circle",
-  "column.create_list": "Create list",
   "column.deep_local": "Deep",
   "column.direct": "Private mentions",
   "column.directory": "Browse profiles",
   "column.domain_blocks": "Blocked domains",
-  "column.edit_antenna": "Edit antenna",
-  "column.edit_bookmark_category": "Edit bookmark_category",
-  "column.edit_circle": "Edit circle",
-  "column.edit_list": "Edit list",
-  "column.emoji_reactions": "Emoji Reactions",
+  "column.emoji_reactions": "Stamps",
   "column.favourites": "Favorites",
   "column.firehose": "Live feeds",
   "column.follow_requests": "Follow requests",
   "column.home": "Home",
-  "column.list_members": "Manage list members",
   "column.lists": "Lists",
   "column.local": "Local",
   "column.mutes": "Muted users",
@@ -297,7 +223,6 @@
   "column_header.pin": "Pin",
   "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
-  "column_search.cancel": "Cancel",
   "column_subheading.settings": "Settings",
   "community.column_settings.local_only": "Local only",
   "community.column_settings.media_only": "Media Only",
@@ -320,7 +245,7 @@
   "compose_form.poll.duration": "Poll duration",
   "compose_form.poll.multiple": "Multiple choice",
   "compose_form.poll.option_placeholder": "Option {number}",
-  "compose_form.poll.single": "Single choice",
+  "compose_form.poll.single": "Pick one",
   "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
   "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
   "compose_form.poll.type": "Style",
@@ -339,13 +264,10 @@
   "confirmations.delete.title": "Delete post?",
   "confirmations.delete_antenna.confirm": "Delete",
   "confirmations.delete_antenna.message": "Are you sure you want to permanently delete this antenna?",
-  "confirmations.delete_antenna.title": "Delete antenna?",
   "confirmations.delete_bookmark_category.confirm": "Delete",
   "confirmations.delete_bookmark_category.message": "Are you sure you want to permanently delete this category?",
-  "confirmations.delete_bookmark_category.title": "Delete bookmark_category?",
   "confirmations.delete_circle.confirm": "Delete",
   "confirmations.delete_circle.message": "Are you sure you want to permanently delete this circle?",
-  "confirmations.delete_circle.title": "Delete circle?",
   "confirmations.delete_list.confirm": "Delete",
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.delete_list.title": "Delete list?",
@@ -354,16 +276,9 @@
   "confirmations.edit.confirm": "Edit",
   "confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.edit.title": "Overwrite post?",
-  "confirmations.follow_to_list.confirm": "Follow and add to list",
-  "confirmations.follow_to_list.message": "You need to be following {name} to add them to a list.",
-  "confirmations.follow_to_list.title": "Follow user?",
   "confirmations.logout.confirm": "Log out",
   "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.logout.title": "Log out?",
-  "confirmations.missing_alt_text.confirm": "Add alt text",
-  "confirmations.missing_alt_text.message": "Your post contains media without alt text. Adding descriptions helps make your content accessible to more people.",
-  "confirmations.missing_alt_text.secondary": "Post anyway",
-  "confirmations.missing_alt_text.title": "Add alt text?",
   "confirmations.mute.confirm": "Mute",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.",
@@ -392,10 +307,10 @@
   "disabled_account_banner.text": "Your account {disabledAccount} is currently disabled.",
   "dismissable_banner.community_timeline": "These are the most recent public posts from people whose accounts are hosted by {domain}.",
   "dismissable_banner.dismiss": "Dismiss",
-  "dismissable_banner.explore_links": "These news stories are being shared the most on the fediverse today. Newer news stories posted by more different people are ranked higher.",
-  "dismissable_banner.explore_statuses": "These posts from across the fediverse are gaining traction today. Newer posts with more boosts and favorites are ranked higher.",
-  "dismissable_banner.explore_tags": "These hashtags are gaining traction on the fediverse today. Hashtags that are used by more different people are ranked higher.",
-  "dismissable_banner.public_timeline": "These are the most recent public posts from people on the fediverse that people on {domain} follow.",
+  "dismissable_banner.explore_links": "These are news stories being shared the most on the social web today. Newer news stories posted by more different people are ranked higher.",
+  "dismissable_banner.explore_statuses": "These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.",
+  "dismissable_banner.explore_tags": "These are hashtags that are gaining traction on the social web today. Hashtags that are used by more different people are ranked higher.",
+  "dismissable_banner.public_timeline": "These are the most recent public posts from people on the social web that people on {domain} follow.",
   "domain_block_modal.block": "Block server",
   "domain_block_modal.block_account_instead": "Block @{name} instead",
   "domain_block_modal.they_can_interact_with_old_posts": "People from this server can interact with your old posts.",
@@ -435,15 +350,17 @@
   "emoji_button.search_results": "Search results",
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
-  "empty_column.account_featured": "This list is empty",
   "empty_column.account_hides_collections": "This user has chosen to not make this information available",
   "empty_column.account_suspended": "Account suspended",
   "empty_column.account_timeline": "No posts here!",
   "empty_column.account_unavailable": "Profile unavailable",
   "empty_column.antenna": "There is nothing in this antenna yet. When members of this list post new statuses, they will appear here.",
+  "empty_column.antennas": "You don't have any antennas yet. When you create one, it will show up here.",
   "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.bookmark_categories": "You don't have any categories yet. When you create one, it will show up here.",
   "empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.",
   "empty_column.circle_statuses": "You don't have any circle posts yet. When you post one as circle, it will show up here.",
+  "empty_column.circles": "You don't have any circles yet. When you create one, it will show up here.",
   "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
   "empty_column.direct": "You don't have any private mentions yet. When you send or receive one, it will show up here.",
   "empty_column.domain_blocks": "There are no blocked domains yet.",
@@ -457,6 +374,7 @@
   "empty_column.hashtag": "There is nothing in this hashtag yet.",
   "empty_column.home": "Your home timeline is empty! Follow more people to fill it up.",
   "empty_column.list": "There is nothing in this list yet. When members of this list publish new posts, they will appear here.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
   "empty_column.mentioned_users": "No one has been mentioned by this post.",
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here according to your settings.",
@@ -469,6 +387,7 @@
   "error.unexpected_crash.next_steps_addons": "Try disabling them and refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
   "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
   "errors.unexpected_crash.report_issue": "Report issue",
+  "explore.search_results": "Search results",
   "explore.suggested_follows": "People",
   "explore.title": "Explore",
   "explore.trending_links": "News",
@@ -518,16 +437,13 @@
   "footer.about": "About",
   "footer.directory": "Profiles directory",
   "footer.get_app": "Get the app",
+  "footer.invite": "Invite people",
   "footer.keyboard_shortcuts": "Keyboard shortcuts",
   "footer.privacy_policy": "Privacy policy",
   "footer.source_code": "View source code",
   "footer.status": "Status",
-  "footer.terms_of_service": "Terms of service",
   "generic.saved": "Saved",
   "getting_started.heading": "Getting started",
-  "hashtag.admin_moderation": "Open moderation interface for #{name}",
-  "hashtag.browse": "Browse posts in #{hashtag}",
-  "hashtag.browse_from_account": "Browse posts from @{name} in #{hashtag}",
   "hashtag.column_header.tag_mode.all": "and {additional}",
   "hashtag.column_header.tag_mode.any": "or {additional}",
   "hashtag.column_header.tag_mode.none": "without {additional}",
@@ -541,7 +457,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} post} other {{counter} posts}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} post} other {{counter} posts}} today",
   "hashtag.follow": "Follow hashtag",
-  "hashtag.mute": "Mute #{hashtag}",
   "hashtag.unfollow": "Unfollow hashtag",
   "hashtags.and_other": "…and {count, plural, other {# more}}",
   "hints.profiles.followers_may_be_missing": "Followers for this profile may be missing.",
@@ -570,23 +485,23 @@
   "ignore_notifications_modal.not_followers_title": "Ignore notifications from people not following you?",
   "ignore_notifications_modal.not_following_title": "Ignore notifications from people you don't follow?",
   "ignore_notifications_modal.private_mentions_title": "Ignore notifications from unsolicited Private Mentions?",
-  "info_button.label": "Help",
-  "info_button.what_is_alt_text": "<h1>What is alt text?</h1> <p>Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.</p> <p>You can improve accessibility and understanding for everyone by writing clear, concise, and objective alt text.</p> <ul> <li>Capture important elements</li> <li>Summarize text in images</li> <li>Use regular sentence structure</li> <li>Avoid redundant information</li> <li>Focus on trends and key findings in complex visuals (like diagrams or maps)</li> </ul>",
-  "interaction_modal.action.favourite": "To continue, you need to favorite from your account.",
-  "interaction_modal.action.follow": "To continue, you need to follow from your account.",
-  "interaction_modal.action.reblog": "To continue, you need to reblog from your account.",
-  "interaction_modal.action.reply": "To continue, you need to reply from your account.",
-  "interaction_modal.action.vote": "To continue, you need to vote from your account.",
-  "interaction_modal.go": "Go",
-  "interaction_modal.no_account_yet": "Don't have an account yet?",
+  "interaction_modal.description.emoji_reaection": "With an account on Mastodon, you can emoji react this post to let the author know you appreciate it and save it for later.",
+  "interaction_modal.description.favourite": "With an account on Mastodon, you can favorite this post to let the author know you appreciate it and save it for later.",
+  "interaction_modal.description.follow": "With an account on Mastodon, you can follow {name} to receive their posts in your home feed.",
+  "interaction_modal.description.reblog": "With an account on Mastodon, you can boost this post to share it with your own followers.",
+  "interaction_modal.description.reply": "With an account on Mastodon, you can respond to this post.",
+  "interaction_modal.login.action": "Take me home",
+  "interaction_modal.login.prompt": "Domain of your home server, e.g. mastodon.social",
+  "interaction_modal.no_account_yet": "Not on Mastodon?",
   "interaction_modal.on_another_server": "On a different server",
   "interaction_modal.on_this_server": "On this server",
+  "interaction_modal.sign_in": "You are not logged in to this server. Where is your account hosted?",
+  "interaction_modal.sign_in_hint": "Tip: That's the website where you signed up. If you don't remember, look for the welcome e-mail in your inbox. You can also enter your full username! (e.g. @Mastodon@mastodon.social)",
+  "interaction_modal.title.emoji_reaction": "Emoji react {name}'s post",
   "interaction_modal.title.favourite": "Favorite {name}'s post",
   "interaction_modal.title.follow": "Follow {name}",
   "interaction_modal.title.reblog": "Boost {name}'s post",
   "interaction_modal.title.reply": "Reply to {name}'s post",
-  "interaction_modal.title.vote": "Vote in {name}'s poll",
-  "interaction_modal.username_prompt": "E.g. {example}",
   "intervals.full.days": "{number, plural, one {# day} other {# days}}",
   "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
   "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
@@ -623,7 +538,6 @@
   "keyboard_shortcuts.toggle_hidden": "Show/hide text behind CW",
   "keyboard_shortcuts.toggle_sensitivity": "Show/hide media",
   "keyboard_shortcuts.toot": "Start a new post",
-  "keyboard_shortcuts.translate": "to translate a post",
   "keyboard_shortcuts.unfocus": "Unfocus compose textarea/search",
   "keyboard_shortcuts.up": "Move up in the list",
   "lightbox.close": "Close",
@@ -636,39 +550,23 @@
   "link_preview.author": "By {name}",
   "link_preview.more_from_author": "More from {name}",
   "link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}",
-  "lists.add_member": "Add",
-  "lists.add_to_list": "Add to list",
-  "lists.add_to_lists": "Add {name} to lists",
-  "lists.antennas": "Related antennas:",
-  "lists.create": "Create",
-  "lists.create_a_list_to_organize": "Create a new list to organize your Home feed",
-  "lists.create_list": "Create list",
+  "lists.account.add": "Add to list",
+  "lists.account.remove": "Remove from list",
+  "lists.antennas": "Related antennas",
   "lists.delete": "Delete list",
-  "lists.done": "Done",
   "lists.edit": "Edit list",
-  "lists.exclusive": "Hide members in Home",
-  "lists.exclusive_hint": "If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.",
-  "lists.favourite": "Favorite",
-  "lists.favourite_hint": "When opening the Web Client on a PC, this list appears in the navigation.",
-  "lists.find_users_to_add": "Find users to add",
-  "lists.list_members": "List members",
-  "lists.list_members_count": "{count, plural, one {# member} other {# members}}",
-  "lists.list_name": "List name",
-  "lists.memo_related_antenna": "Antenna: \"{title}\"",
-  "lists.new_list_name": "New list name",
-  "lists.no_lists_yet": "No lists yet.",
-  "lists.no_members_yet": "No members yet.",
-  "lists.no_results_found": "No results found.",
+  "lists.edit.submit": "Change title",
+  "lists.exclusive": "Hide list or antenna account posts from home",
+  "lists.new.create": "Add list",
+  "lists.new.title_placeholder": "New list title",
   "lists.notify": "Notify these posts",
-  "lists.notify_hint": "Notify when new post is added.",
-  "lists.remove_member": "Remove",
   "lists.replies_policy.followed": "Any followed user",
   "lists.replies_policy.list": "Members of the list",
   "lists.replies_policy.none": "No one",
-  "lists.save": "Save",
-  "lists.save_to_edit_member": "You can edit list members after saving.",
-  "lists.search": "Search",
-  "lists.show_replies_to": "Include replies from list members to",
+  "lists.replies_policy.title": "Show replies to:",
+  "lists.search": "Search among people you follow",
+  "lists.subheading": "Your lists",
+  "lists.with_antenna": "Antenna",
   "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Loading…",
   "media_gallery.hide": "Hide",
@@ -723,14 +621,10 @@
   "notification.admin.report_statuses_other": "{name} reported {target}",
   "notification.admin.sign_up": "{name} signed up",
   "notification.admin.sign_up.name_and_others": "{name} and {count, plural, one {# other} other {# others}} signed up",
-  "notification.annual_report.message": "Your {year} #Wrapstodon awaits! Unveil your year's highlights and memorable moments on Mastodon!",
-  "notification.annual_report.view": "View #Wrapstodon",
   "notification.emoji_reaction": "{name} reacted your post with emoji",
   "notification.emoji_reaction.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> reacted your post with emoji",
   "notification.favourite": "{name} favorited your post",
   "notification.favourite.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favorited your post",
-  "notification.favourite_pm": "{name} favorited your private mention",
-  "notification.favourite_pm.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favorited your private mention",
   "notification.follow": "{name} followed you",
   "notification.follow.name_and_others": "{name} and <a>{count, plural, one {# other} other {# others}}</a> followed you",
   "notification.follow_request": "{name} has requested to follow you",
@@ -843,21 +737,45 @@
   "notifications_permission_banner.enable": "Enable desktop notifications",
   "notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
   "notifications_permission_banner.title": "Never miss a thing",
-  "onboarding.follows.back": "Back",
-  "onboarding.follows.done": "Done",
+  "onboarding.action.back": "Take me back",
+  "onboarding.actions.back": "Take me back",
+  "onboarding.actions.go_to_explore": "Take me to trending",
+  "onboarding.actions.go_to_home": "Take me to my home feed",
+  "onboarding.actions.go_to_local_timeline": "See posts from local",
+  "onboarding.compose.template": "Hello #Mastodon!",
   "onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.",
-  "onboarding.follows.search": "Search",
-  "onboarding.follows.title": "Follow people to get started",
+  "onboarding.follows.lead": "Your home feed is the primary way to experience Mastodon. The more people you follow, the more active and interesting it will be. To get you started, here are some suggestions:",
+  "onboarding.follows.title": "Personalize your home feed",
   "onboarding.profile.discoverable": "Make my profile discoverable",
   "onboarding.profile.discoverable_hint": "When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.",
   "onboarding.profile.display_name": "Display name",
   "onboarding.profile.display_name_hint": "Your full name or your fun name…",
+  "onboarding.profile.lead": "You can always complete this later in the settings, where even more customization options are available.",
   "onboarding.profile.note": "Bio",
   "onboarding.profile.note_hint": "You can @mention other people or #hashtags…",
   "onboarding.profile.save_and_continue": "Save and continue",
   "onboarding.profile.title": "Profile setup",
   "onboarding.profile.upload_avatar": "Upload profile picture",
   "onboarding.profile.upload_header": "Upload profile header",
+  "onboarding.share.lead": "Let people know how they can find you on Mastodon!",
+  "onboarding.share.message": "I'm {username} on #Mastodon! Come follow me at {url}",
+  "onboarding.share.next_steps": "Possible next steps:",
+  "onboarding.share.title": "Share your profile",
+  "onboarding.start.lead": "You're now part of Mastodon, a unique, decentralized social media platform where you—not an algorithm—curate your own experience. Let's get you started on this new social frontier:",
+  "onboarding.start.skip": "Don't need help getting started?",
+  "onboarding.start.title": "You've made it!",
+  "onboarding.steps.follow_people.body": "Following interesting people is what Mastodon is all about.",
+  "onboarding.steps.follow_people.title": "Personalize your home feed",
+  "onboarding.steps.publish_status.body": "Say hello to the world with text, photos, videos, or polls {emoji}",
+  "onboarding.steps.publish_status.title": "Make your first post",
+  "onboarding.steps.setup_profile.body": "Boost your interactions by having a comprehensive profile.",
+  "onboarding.steps.setup_profile.title": "Personalize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon",
+  "onboarding.steps.share_profile.title": "Share your Mastodon profile",
+  "onboarding.tips.2fa": "<strong>Did you know?</strong> You can secure your account by setting up two-factor authentication in your account settings. It works with any TOTP app of your choice, no phone number necessary!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Did you know?</strong> Since Mastodon is decentralized, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!",
+  "onboarding.tips.migration": "<strong>Did you know?</strong> If you feel like {domain} is not a great server choice for you in the future, you can move to another Mastodon server without losing your followers. You can even host your own server!",
+  "onboarding.tips.verification": "<strong>Did you know?</strong> You can verify your account by putting a link to your Mastodon profile on your own website and adding the website to your profile. No fees or documents necessary!",
   "password_confirmation.exceeds_maxlength": "Password confirmation exceeds the maximum password length",
   "password_confirmation.mismatching": "Password confirmation does not match",
   "picture_in_picture.restore": "Put it back",
@@ -877,7 +795,7 @@
   "privacy.circle.long": "Circle members only",
   "privacy.circle.short": "Circle",
   "privacy.direct.long": "Everyone mentioned in the post",
-  "privacy.direct.short": "Private mention",
+  "privacy.direct.short": "Specific people",
   "privacy.limited.short": "Limited",
   "privacy.login.long": "Visible for login users only",
   "privacy.login.short": "Login only",
@@ -901,8 +819,8 @@
   "reaction_deck.remove": "Remove",
   "recommended": "Recommended",
   "refresh": "Refresh",
-  "regeneration_indicator.please_stand_by": "Please stand by.",
-  "regeneration_indicator.preparing_your_home_feed": "Preparing your home feed…",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
   "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
@@ -987,11 +905,10 @@
   "search_results.accounts": "Profiles",
   "search_results.all": "All",
   "search_results.hashtags": "Hashtags",
-  "search_results.no_results": "No results.",
-  "search_results.no_search_yet": "Try searching for posts, profiles or hashtags.",
+  "search_results.nothing_found": "Could not find anything for these search terms",
   "search_results.see_all": "See all",
   "search_results.statuses": "Posts",
-  "search_results.title": "Search for \"{q}\"",
+  "search_results.title": "Search for {q}",
   "searchability.change": "Change post searchability",
   "searchability.direct.long": "Nobody can find, but you can",
   "searchability.direct.short": "Self only",
@@ -1073,7 +990,6 @@
   "status.redraft": "Delete & re-draft",
   "status.reference": "Quiet quote",
   "status.remove_bookmark": "Remove bookmark",
-  "status.remove_favourite": "Remove from favorites",
   "status.replied_in_thread": "Replied in thread",
   "status.replied_to": "Replied to {name}",
   "status.reply": "Reply",
@@ -1095,9 +1011,6 @@
   "subscribed_languages.target": "Change subscribed languages for {target}",
   "tabs_bar.home": "Home",
   "tabs_bar.notifications": "Notifications",
-  "terms_of_service.effective_as_of": "Effective as of {date}",
-  "terms_of_service.title": "Terms of Service",
-  "terms_of_service.upcoming_changes_on": "Upcoming changes on {date}",
   "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
   "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
   "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
@@ -1113,12 +1026,26 @@
   "upload_button.label": "Add images, a video or an audio file",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
+  "upload_form.audio_description": "Describe for people who are deaf or hard of hearing",
+  "upload_form.description": "Describe for people who are blind or have low vision",
   "upload_form.drag_and_drop.instructions": "To pick up a media attachment, press space or enter. While dragging, use the arrow keys to move the media attachment in any given direction. Press space or enter again to drop the media attachment in its new position, or press escape to cancel.",
   "upload_form.drag_and_drop.on_drag_cancel": "Dragging was cancelled. Media attachment {item} was dropped.",
   "upload_form.drag_and_drop.on_drag_end": "Media attachment {item} was dropped.",
   "upload_form.drag_and_drop.on_drag_over": "Media attachment {item} was moved.",
   "upload_form.drag_and_drop.on_drag_start": "Picked up media attachment {item}.",
   "upload_form.edit": "Edit",
+  "upload_form.thumbnail": "Change thumbnail",
+  "upload_form.video_description": "Describe for people who are deaf, hard of hearing, blind or have low vision",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.applying": "Applying…",
+  "upload_modal.choose_image": "Choose image",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preparing_ocr": "Preparing OCR…",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Uploading...",
   "upload_progress.processing": "Processing…",
   "username.taken": "That username is taken. Try another",
@@ -1128,12 +1055,8 @@
   "video.expand": "Expand video",
   "video.fullscreen": "Full screen",
   "video.hide": "Hide video",
-  "video.mute": "Mute",
+  "video.mute": "Mute sound",
   "video.pause": "Pause",
   "video.play": "Play",
-  "video.skip_backward": "Skip backward",
-  "video.skip_forward": "Skip forward",
-  "video.unmute": "Unmute",
-  "video.volume_down": "Volume down",
-  "video.volume_up": "Volume up"
+  "video.unmute": "Unmute sound"
 }
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index 1d360e59d7..a512860d6f 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -27,10 +27,9 @@
   "account.edit_profile": "Redakti la profilon",
   "account.enable_notifications": "Sciigu min kiam @{name} afiŝos",
   "account.endorse": "Prezenti ĉe via profilo",
-  "account.featured.hashtags": "Kradvortoj",
-  "account.featured.posts": "Afiŝoj",
   "account.featured_tags.last_status_at": "Lasta afîŝo je {date}",
   "account.featured_tags.last_status_never": "Neniu afiŝo",
+  "account.featured_tags.title": "Rekomendataj kradvortoj de {name}",
   "account.follow": "Sekvi",
   "account.follow_back": "Sekvu reen",
   "account.followers": "Sekvantoj",
@@ -66,7 +65,6 @@
   "account.statuses_counter": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}}",
   "account.unblock": "Malbloki @{name}",
   "account.unblock_domain": "Malbloki la domajnon {domain}",
-  "account.unblock_domain_short": "Malbloki",
   "account.unblock_short": "Malbloki",
   "account.unendorse": "Ne plu rekomendi ĉe la profilo",
   "account.unfollow": "Ĉesi sekvi",
@@ -88,33 +86,7 @@
   "alert.unexpected.message": "Neatendita eraro okazis.",
   "alert.unexpected.title": "Aj!",
   "alt_text_badge.title": "Alt-teksto",
-  "alt_text_modal.add_alt_text": "Aldoni alttekston",
-  "alt_text_modal.add_text_from_image": "Aldoni tekston de bildo",
-  "alt_text_modal.cancel": "Nuligi",
-  "alt_text_modal.change_thumbnail": "Ŝanĝi bildeton",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Priskribi ĉi tion por homoj kun aŭdmalkapabloj…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Priskribi ĉi tion por homoj kun vidaj malkapabloj…",
-  "alt_text_modal.done": "Farita",
   "announcement.announcement": "Anonco",
-  "annual_report.summary.archetype.booster": "La Ĉasanto de Mojoso",
-  "annual_report.summary.archetype.lurker": "La vidanto",
-  "annual_report.summary.archetype.oracle": "La Orakolo",
-  "annual_report.summary.archetype.pollster": "La balotenketisto",
-  "annual_report.summary.archetype.replier": "La plej societema",
-  "annual_report.summary.followers.followers": "sekvantoj",
-  "annual_report.summary.followers.total": "{count} tute",
-  "annual_report.summary.here_it_is": "Jen via resumo de {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "plej ŝatata afiŝo",
-  "annual_report.summary.highlighted_post.by_reblogs": "plej diskonigita afiŝo",
-  "annual_report.summary.highlighted_post.by_replies": "afiŝo kun la plej multaj respondoj",
-  "annual_report.summary.highlighted_post.possessive": "de {name}",
-  "annual_report.summary.most_used_app.most_used_app": "plej uzita apo",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "plej uzata kradvorto",
-  "annual_report.summary.most_used_hashtag.none": "Nenio",
-  "annual_report.summary.new_posts.new_posts": "novaj afiŝoj",
-  "annual_report.summary.percentile.text": "<topLabel>Tio metas vin en la plej</topLabel><percentage></percentage><bottomLabel>de {domain} uzantoj.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Ni ne diros al Zamenhof.",
-  "annual_report.summary.thanks": "Dankon pro esti parto de Mastodon!",
   "attachments_list.unprocessed": "(neprilaborita)",
   "audio.hide": "Kaŝi aŭdion",
   "block_modal.remote_users_caveat": "Ni petos al la servilo {domain} respekti vian elekton. Tamen, plenumo ne estas garantiita ĉar iuj serviloj eble manipulas blokojn malsame. Publikaj afiŝoj eble ankoraŭ estas videbla por ne-ensalutintaj uzantoj.",
@@ -138,7 +110,7 @@
   "bundle_column_error.routing.body": "La celita paĝo ne troveblas. Ĉu vi certas, ke la retadreso (URL) en via retfoliumilo estas ĝusta?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Fermi",
-  "bundle_modal_error.message": "Io misfunkciis dum ŝarĝo de ĉi tiu ekrano.",
+  "bundle_modal_error.message": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
   "bundle_modal_error.retry": "Provu denove",
   "closed_registrations.other_server_instructions": "Ĉar Mastodon estas malcentraliza, vi povas krei konton ĉe alia servilo kaj ankoraŭ komuniki kun ĉi tiu.",
   "closed_registrations_modal.description": "Krei konton ĉe {domain} aktuale ne eblas, tamen bonvole rimarku, ke vi ne bezonas konton specife ĉe {domain} por uzi Mastodon.",
@@ -149,16 +121,13 @@
   "column.blocks": "Blokitaj uzantoj",
   "column.bookmarks": "Legosignoj",
   "column.community": "Loka templinio",
-  "column.create_list": "Krei liston",
   "column.direct": "Privataj mencioj",
   "column.directory": "Foliumi la profilojn",
   "column.domain_blocks": "Blokitaj domajnoj",
-  "column.edit_list": "Redakti liston",
   "column.favourites": "Stelumoj",
   "column.firehose": "Rektaj fluoj",
   "column.follow_requests": "Petoj de sekvado",
   "column.home": "Hejmo",
-  "column.list_members": "Administri listanojn",
   "column.lists": "Listoj",
   "column.mutes": "Silentigitaj uzantoj",
   "column.notifications": "Sciigoj",
@@ -171,7 +140,6 @@
   "column_header.pin": "Fiksi",
   "column_header.show_settings": "Montri la agordojn",
   "column_header.unpin": "Malfiksi",
-  "column_search.cancel": "Nuligi",
   "column_subheading.settings": "Agordoj",
   "community.column_settings.local_only": "Nur loka",
   "community.column_settings.media_only": "Nur vidaŭdaĵoj",
@@ -190,7 +158,7 @@
   "compose_form.poll.duration": "Daŭro de la balotenketo",
   "compose_form.poll.multiple": "Multobla elekto",
   "compose_form.poll.option_placeholder": "Opcio {number}",
-  "compose_form.poll.single": "Ununura elekto",
+  "compose_form.poll.single": "Elektu unu",
   "compose_form.poll.switch_to_multiple": "Ŝanĝi la balotenketon por permesi multajn elektojn",
   "compose_form.poll.switch_to_single": "Ŝanĝi la balotenketon por permesi unu solan elekton",
   "compose_form.poll.type": "Stilo",
@@ -214,16 +182,9 @@
   "confirmations.edit.confirm": "Redakti",
   "confirmations.edit.message": "Redakti nun anstataŭigos la skribatan afiŝon. Ĉu vi certas, ke vi volas daŭrigi?",
   "confirmations.edit.title": "Ĉu superskribi afiŝon?",
-  "confirmations.follow_to_list.confirm": "Sekvi kaj aldoni al listo",
-  "confirmations.follow_to_list.message": "Vi devas sekvi {name} por aldoni ilin al listo.",
-  "confirmations.follow_to_list.title": "Ĉu sekvi uzanton?",
   "confirmations.logout.confirm": "Elsaluti",
   "confirmations.logout.message": "Ĉu vi certas, ke vi volas elsaluti?",
   "confirmations.logout.title": "Ĉu elsaluti?",
-  "confirmations.missing_alt_text.confirm": "Aldoni alttekston",
-  "confirmations.missing_alt_text.message": "Via afiŝo enhavas aŭdvidaĵon sen alternativa teksto. Aldono de priskriboj helpas fari vian enhavon alirebla por pli da homoj.",
-  "confirmations.missing_alt_text.secondary": "Afiŝi ĉiuokaze",
-  "confirmations.missing_alt_text.title": "Ĉu aldoni alttekston?",
   "confirmations.mute.confirm": "Silentigi",
   "confirmations.redraft.confirm": "Forigi kaj reskribi",
   "confirmations.redraft.message": "Ĉu vi certas ke vi volas forigi tiun afiŝon kaj reskribi ĝin? Ĉiuj diskonigoj kaj stelumoj estos perditaj, kaj respondoj al la originala mesaĝo estos senparentaj.",
@@ -252,10 +213,10 @@
   "disabled_account_banner.text": "Via konto {disabledAccount} estas nune malvalidigita.",
   "dismissable_banner.community_timeline": "Jen la plej novaj publikaj afiŝoj de uzantoj, kies kontojn gastigas {domain}.",
   "dismissable_banner.dismiss": "Eksigi",
-  "dismissable_banner.explore_links": "Ĉi tiuj revuaĵoj plejkunhaviĝas en la fediverso hodiaŭ. Pli novaj revuaĵoj afiŝis de pli da homoj metis pli alte.",
-  "dismissable_banner.explore_statuses": "Ĉi tiuj afiŝoj populariĝas sur la fediverso hodiaŭ. Pli novaj afiŝoj kun pli da diskonigoj kaj stemuloj estas rangigitaj pli alte.",
-  "dismissable_banner.explore_tags": "Ĉi tiuj kradvortoj populariĝas sur la fediverso hodiaŭ. Kradvortoj, kiuj estas uzataj de pli malsamaj homoj, estas rangigitaj pli alte.",
-  "dismissable_banner.public_timeline": "Ĉi tiuj estas la plej ĵusaj publikaj afiŝoj de homoj en la fediverso, kiujn la homoj en {domain} sekvas.",
+  "dismissable_banner.explore_links": "Tiuj novaĵoj estas aktuale priparolataj de uzantoj en tiu ĉi kaj aliaj serviloj, sur la malcentrigita reto.",
+  "dismissable_banner.explore_statuses": "Jen afiŝoj en la socia reto kiuj populariĝis hodiaŭ. Novaj afiŝoj kun pli da diskonigoj kaj stelumoj aperas pli alte.",
+  "dismissable_banner.explore_tags": "Ĉi tiuj kradvostoj populariĝas en ĉi tiu kaj aliaj serviloj en la malcentraliza reto nun.",
+  "dismissable_banner.public_timeline": "Ĉi tiuj estas la plej lastatempaj publikaj afiŝoj de homoj en la socia reto, kiujn homoj sur {domain} sekvas.",
   "domain_block_modal.block": "Bloki servilon",
   "domain_block_modal.block_account_instead": "Bloki @{name} anstataŭe",
   "domain_block_modal.they_can_interact_with_old_posts": "Homoj de ĉi tiu servilo povas interagi kun viaj malnovaj afiŝoj.",
@@ -295,7 +256,6 @@
   "emoji_button.search_results": "Serĉaj rezultoj",
   "emoji_button.symbols": "Simboloj",
   "emoji_button.travel": "Vojaĝoj kaj lokoj",
-  "empty_column.account_featured": "Ĉi tiu listo estas malplena",
   "empty_column.account_hides_collections": "Ĉi tiu uzanto elektis ne disponebligi ĉi tiu informon",
   "empty_column.account_suspended": "Konto suspendita",
   "empty_column.account_timeline": "Neniuj afiŝoj ĉi tie!",
@@ -313,6 +273,7 @@
   "empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
   "empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
   "empty_column.list": "Ankoraŭ estas nenio en ĉi tiu listo. Kiam membroj de ĉi tiu listo afiŝos novajn afiŝojn, ili aperos ĉi tie.",
+  "empty_column.lists": "Vi ankoraŭ ne havas liston. Kiam vi kreos iun, ĝi aperos ĉi tie.",
   "empty_column.mutes": "Vi ne ankoraŭ silentigis iun uzanton.",
   "empty_column.notification_requests": "Ĉio klara! Estas nenio tie ĉi. Kiam vi ricevas novajn sciigojn, ili aperos ĉi tie laŭ viaj agordoj.",
   "empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.",
@@ -323,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Provu malaktivigi ilin kaj tiam refreŝigi la paĝon. Se tio ne helpas, vi ankoraŭ povus uzi Mastodon per malsama retumilo aŭ operaciuma aplikajo.",
   "errors.unexpected_crash.copy_stacktrace": "Kopii stakspuron en tondujo",
   "errors.unexpected_crash.report_issue": "Raporti problemon",
+  "explore.search_results": "Serĉaj rezultoj",
   "explore.suggested_follows": "Homoj",
   "explore.title": "Esplori",
   "explore.trending_links": "Novaĵoj",
@@ -372,14 +334,13 @@
   "footer.about": "Pri",
   "footer.directory": "Profilujo",
   "footer.get_app": "Akiri la apon",
+  "footer.invite": "Inviti homojn",
   "footer.keyboard_shortcuts": "Fulmoklavoj",
   "footer.privacy_policy": "Politiko de privateco",
   "footer.source_code": "Montri fontkodon",
   "footer.status": "Stato",
-  "footer.terms_of_service": "Kondiĉoj de uzado",
   "generic.saved": "Konservita",
   "getting_started.heading": "Por komenci",
-  "hashtag.admin_moderation": "Malfermi fasadon de moderigado por #{name}",
   "hashtag.column_header.tag_mode.all": "kaj {additional}",
   "hashtag.column_header.tag_mode.any": "aŭ {additional}",
   "hashtag.column_header.tag_mode.none": "sen {additional}",
@@ -421,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ĉu ignori sciigojn de homoj, kiuj ne sekvas vin?",
   "ignore_notifications_modal.not_following_title": "Ĉu ignori sciigojn de homoj, kiujn vi ne sekvas?",
   "ignore_notifications_modal.private_mentions_title": "Ĉu ignori sciigojn de nepetitaj privataj mencioj?",
-  "info_button.label": "Helpo",
-  "info_button.what_is_alt_text": "<h1>Kio estas la alternativa teksto?</h1> <p>La alternativa teksto ofertas priskribojn de la bildoj por individuoj kun vidaj malfacilaĵoj, konektoj kun malalta larĝa bando aŭ kiuj serĉas plian kuntekston.</p> <p>Vi povas plibonigi alireblecon kaj komprenon por ĉiuj per skribado de klaraj, koncizaj, kaj objektivaj alternativaj tekstoj.</p><ul><li>Kaptu gravajn elementojn.</li><li> Resumu tekston en bildoj.</li> <li>Uzu regulan frazstrukturon.</li> <li>Evitu redundan informon.</li><li>Fokusu sur tendencoj kaj ĉefaj trovoj en kompleksaj visualoj (kiel diagramoj aŭ mapoj).</li></ul>",
-  "interaction_modal.action.favourite": "Por daŭrigi, vi devas stelumi el via konto.",
-  "interaction_modal.action.follow": "Por daŭrigi, vi devas sekvi el via konto.",
-  "interaction_modal.action.reblog": "Por daŭrigi, vi devas diskonigi el via konto.",
-  "interaction_modal.action.reply": "Por daŭrigi, vi devas respondi el via konto.",
-  "interaction_modal.action.vote": "Por daŭrigi, vi devas voĉdoni el via konto.",
-  "interaction_modal.go": "Iru",
-  "interaction_modal.no_account_yet": "Ĉu vi ankoraŭ ne havas konton?",
+  "interaction_modal.description.favourite": "Per konto ĉe Mastodon, vi povas stelumi ĉi tiun afiŝon por sciigi la afiŝanton ke vi sâtas kaj konservas ĝin por poste.",
+  "interaction_modal.description.follow": "Kun konto ĉe Mastodon, vi povas sekvi {name} por ricevi iliajn afiŝojn en via hejma fluo.",
+  "interaction_modal.description.reblog": "Kun konto ĉe Mastodon, vi povas diskonigi ĉi tiun afiŝon, por ke viaj propraj sekvantoj vidu ĝin.",
+  "interaction_modal.description.reply": "Kun konto ĉe Mastodon, vi povos respondi al ĉi tiu afiŝo.",
+  "interaction_modal.login.action": "Prenu min hejmen",
+  "interaction_modal.login.prompt": "Domajno de via hejma servilo, ekz. mastodon.social",
+  "interaction_modal.no_account_yet": "Ĉu ne estas ĉe Mastodon?",
   "interaction_modal.on_another_server": "En alia servilo",
   "interaction_modal.on_this_server": "En ĉi tiu servilo",
+  "interaction_modal.sign_in": "Vi ne estas ensalutita al ĉi tiu servilo.",
+  "interaction_modal.sign_in_hint": "Gvideto: Tio estas la retejo kie vi registris. Vi ankau povas tajpi vian plenan uzantonomon!",
   "interaction_modal.title.favourite": "Stelumi la afiŝon de {name}",
   "interaction_modal.title.follow": "Sekvi {name}",
   "interaction_modal.title.reblog": "Akceli la afiŝon de {name}",
   "interaction_modal.title.reply": "Respondi al la afiŝo de {name}",
-  "interaction_modal.title.vote": "Voĉdonu en la balotenketo de {name}",
-  "interaction_modal.username_prompt": "Ekz. {example}",
   "intervals.full.days": "{number, plural, one {# tago} other {# tagoj}}",
   "intervals.full.hours": "{number, plural, one {# horo} other {# horoj}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutoj}}",
@@ -473,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Montri/kaŝi tekston malantaŭ CW",
   "keyboard_shortcuts.toggle_sensitivity": "Montri/kaŝi vidaŭdaĵojn",
   "keyboard_shortcuts.toot": "Komencu novan afiŝon",
-  "keyboard_shortcuts.translate": "Traduki afiŝon",
   "keyboard_shortcuts.unfocus": "Senfokusigi verki tekstareon/serĉon",
   "keyboard_shortcuts.up": "Movu supren en la listo",
   "lightbox.close": "Fermi",
@@ -486,32 +444,20 @@
   "link_preview.author": "De {name}",
   "link_preview.more_from_author": "Pli de {name}",
   "link_preview.shares": "{count, plural, one {{counter} afiŝo} other {{counter} afiŝoj}}",
-  "lists.add_member": "Aldoni",
-  "lists.add_to_list": "Aldoni al la listo",
-  "lists.add_to_lists": "Aldoni {name} al la listo",
-  "lists.create": "Krei",
-  "lists.create_a_list_to_organize": "Krei novan liston por organizi vian Hejmpaĝon",
-  "lists.create_list": "Krei liston",
+  "lists.account.add": "Aldoni al la listo",
+  "lists.account.remove": "Forigi de la listo",
   "lists.delete": "Forigi la liston",
-  "lists.done": "Farita",
   "lists.edit": "Redakti la liston",
-  "lists.exclusive": "Kaŝi membrojn en Hejmpaĝo",
-  "lists.exclusive_hint": "Se iu estas en ĉi tiuj listo, kaŝu ilin en via hejmpaĝo por eviti vidi iliajn afiŝojn dufoje.",
-  "lists.find_users_to_add": "Trovi uzantojn por aldoni",
-  "lists.list_members": "Listoj de membroj",
-  "lists.list_members_count": "{count, plural,one {# membro} other {# membroj}}",
-  "lists.list_name": "Nomo de la listo",
-  "lists.new_list_name": "Nomo de nova listo",
-  "lists.no_lists_yet": "Ankoraŭ ne estas listoj.",
-  "lists.no_members_yet": "Ankoraŭ neniuj membroj.",
-  "lists.no_results_found": "Neniuj rezultoj trovitaj.",
-  "lists.remove_member": "Forigi",
+  "lists.edit.submit": "Ŝanĝi titolon",
+  "lists.exclusive": "Kaŝi ĉi tiujn afiŝojn de hejmo",
+  "lists.new.create": "Aldoni liston",
+  "lists.new.title_placeholder": "Titolo de la nova listo",
   "lists.replies_policy.followed": "Iu sekvanta uzanto",
   "lists.replies_policy.list": "Membroj de la listo",
   "lists.replies_policy.none": "Neniu",
-  "lists.save": "Konservi",
-  "lists.search": "Ŝerci",
-  "lists.show_replies_to": "Inkludi respondojn de listomembroj al",
+  "lists.replies_policy.title": "Montri respondojn al:",
+  "lists.search": "Serĉi inter la homoj, kiujn vi sekvas",
+  "lists.subheading": "Viaj listoj",
   "load_pending": "{count,plural, one {# nova elemento} other {# novaj elementoj}}",
   "loading_indicator.label": "Ŝargado…",
   "media_gallery.hide": "Kaŝi",
@@ -560,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} raportis {target}",
   "notification.admin.sign_up": "{name} kreis konton",
   "notification.admin.sign_up.name_and_others": "{name} kaj {count, plural, one {# alia} other {# aliaj}} kreis konton",
-  "notification.annual_report.message": "Via {year} #Wrapstodon atendas! Malkovru viajn bonaĵojn kaj memorindajn momentojn en Mastodon!",
-  "notification.annual_report.view": "Vidu #Wrapstodon",
   "notification.favourite": "{name} ŝatis vian afiŝon",
   "notification.favourite.name_and_others_with_link": "{name} kaj <a>{count, plural, one {# alia} other {# aliaj}}</a> ŝatis vian afiŝon",
-  "notification.favourite_pm": "{name} ŝatis vian privatan mencion",
-  "notification.favourite_pm.name_and_others_with_link": "{name} kaj <a>{count, plural, one {# alia} other {# aliaj}}</a> ŝatis vian privatan mencion",
   "notification.follow": "{name} eksekvis vin",
   "notification.follow.name_and_others": "{name} kaj <a>{count, plural, one {# alia} other {# aliaj}}</a> sekvis vin",
   "notification.follow_request": "{name} petis sekvi vin",
@@ -670,21 +612,44 @@
   "notifications_permission_banner.enable": "Ŝalti retumilajn sciigojn",
   "notifications_permission_banner.how_to_control": "Por ricevi sciigojn kiam Mastodon ne estas malfermita, ebligu labortablajn sciigojn. Vi povas regi precize kiuj specoj de interagoj generas labortablajn sciigojn per la supra butono {icon} post kiam ili estas ebligitaj.",
   "notifications_permission_banner.title": "Neniam preterlasas iun ajn",
-  "onboarding.follows.back": "Reen",
-  "onboarding.follows.done": "Farita",
+  "onboarding.action.back": "Prenu min reen",
+  "onboarding.actions.back": "Prenu min reen",
+  "onboarding.actions.go_to_explore": "Konduku min al tendenco",
+  "onboarding.actions.go_to_home": "Konduku min al mia hejma fluo",
+  "onboarding.compose.template": "Saluton #Mastodon!",
   "onboarding.follows.empty": "Bedaŭrinde, neniu rezulto estas montrebla nuntempe. Vi povas provi serĉi aŭ foliumi la esploran paĝon por trovi kontojn por sekvi, aŭ retrovi baldaŭ.",
-  "onboarding.follows.search": "Serĉi",
-  "onboarding.follows.title": "Sekvi homojn por komenci",
+  "onboarding.follows.lead": "Via hejma fluo estas la ĉefa maniero sperti Mastodon. Ju pli da homoj vi sekvas, des pli aktiva kaj interesa ĝi estos. Por komenci, jen kelkaj sugestoj:",
+  "onboarding.follows.title": "Agordi vian hejman fluon",
   "onboarding.profile.discoverable": "Trovebligi mian profilon",
   "onboarding.profile.discoverable_hint": "Kiam vi aliĝi al trovebleco ĉe Mastodon, viaj afiŝoj eble aperos en serĉaj rezultoj kaj populariĝoj, kaj via profilo eble estas sugestota al personoj kun similaj intereseoj al vi.",
   "onboarding.profile.display_name": "Publika nomo",
   "onboarding.profile.display_name_hint": "Via plena nomo aŭ via kromnomo…",
+  "onboarding.profile.lead": "Vi ĉiam povas plenigi ĉi tion poste en la agordoj, kie eĉ pli da personecigagordoj estas disponeblaj.",
   "onboarding.profile.note": "Sinprezento",
   "onboarding.profile.note_hint": "Vi povas @mencii aliajn homojn aŭ #kradvortojn…",
   "onboarding.profile.save_and_continue": "Konservi kaj daŭrigi",
   "onboarding.profile.title": "Profila fikso",
   "onboarding.profile.upload_avatar": "Alŝuti profilbildon",
   "onboarding.profile.upload_header": "Alŝuti profilkapbildon",
+  "onboarding.share.lead": "Sciigi personojn pri kiel ili povas trovi vin ĉe Mastodon!",
+  "onboarding.share.message": "Mi estas {username} en #Mastodon! Sekvu min ĉe {url}",
+  "onboarding.share.next_steps": "Eblaj malantauaj paŝoj:",
+  "onboarding.share.title": "Disvastigi vian profilon",
+  "onboarding.start.lead": "Vi nun estas parto de Mastodon, unika, malcentralizita socia amaskomunikilara platformo, kie vi—ne algoritmo—zorgas vian propran sperton. Ni komencu vin sur ĉi tiu nova socia limo:",
+  "onboarding.start.skip": "Ĉu vi ne bezonas helpon por komenci?",
+  "onboarding.start.title": "Vi atingas ĝin!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Agordu vian hejman fluon",
+  "onboarding.steps.publish_status.body": "Salutu la mondon per teksto, fotoj, filmetoj aŭ balotenketoj {emoji}",
+  "onboarding.steps.publish_status.title": "Fari vian unuan afiŝon",
+  "onboarding.steps.setup_profile.body": "Diskonigu viajn interagojn havante ampleksan profilon.",
+  "onboarding.steps.setup_profile.title": "Agordu vian profilon",
+  "onboarding.steps.share_profile.body": "Sciigu viajn amikojn kiel trovi vin sur Mastodon",
+  "onboarding.steps.share_profile.title": "Kunhavigu vian Mastodon-profilon",
+  "onboarding.tips.2fa": "<strong>Ĉu vi scias?</strong> Vi povas sekurigi vian konton per efektivigi dufaktora autentigo en via kontoagordoj.",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Ĉu vi scias?</strong> Ĉar Mastodon estas sencentra, kelkaj profiloj kiujn vi trovi estas gastigitaj ĉe aliaj serviloj kiuj ne estas via.",
+  "onboarding.tips.migration": "<strong>Ĉu vi scias?</strong> Se vi sentas ke {domain} ne estas bona servilelekto por vi en la estonteco, vi povas translokiĝi al alia servilo de Mastodon sen malgajni viajn sekvantojn.",
+  "onboarding.tips.verification": "<strong>Ĉu vi sciis?</strong> Vi povas kontroli vian konton metante ligilon al via Mastodon-profilo en via propra retejo kaj aldonante la retejon al via profilo. Neniuj kotizoj aŭ dokumentoj necesaj!",
   "password_confirmation.exceeds_maxlength": "Pasvorto-konfirmo superas la maksimuman pasvortan longon",
   "password_confirmation.mismatching": "Pasvorto konfirmo ne kongruas",
   "picture_in_picture.restore": "Remetu ĝin",
@@ -700,7 +665,7 @@
   "poll_button.remove_poll": "Forigi balotenketon",
   "privacy.change": "Ŝanĝu afiŝan privatecon",
   "privacy.direct.long": "Ĉiuj menciitaj en la afiŝo",
-  "privacy.direct.short": "Privata mencio",
+  "privacy.direct.short": "Specifaj homoj",
   "privacy.private.long": "Nur viaj sekvantoj",
   "privacy.private.short": "Sekvantoj",
   "privacy.public.long": "Ĉiujn ajn ĉe kaj ekster Mastodon",
@@ -712,8 +677,8 @@
   "privacy_policy.title": "Politiko de privateco",
   "recommended": "Rekomendita",
   "refresh": "Refreŝigu",
-  "regeneration_indicator.please_stand_by": "Bonvolu atendi.",
-  "regeneration_indicator.preparing_your_home_feed": "Preparante vian hejmpaĝon…",
+  "regeneration_indicator.label": "Ŝargado…",
+  "regeneration_indicator.sublabel": "Via abonfluo estas preparata!",
   "relative_time.days": "{number}t",
   "relative_time.full.days": "antaŭ {number, plural, one {# tago} other {# tagoj}}",
   "relative_time.full.hours": "antaŭ {number, plural, one {# horo} other {# horoj}}",
@@ -797,11 +762,10 @@
   "search_results.accounts": "Profiloj",
   "search_results.all": "Ĉiuj",
   "search_results.hashtags": "Kradvortoj",
-  "search_results.no_results": "Ne estas rezultoj.",
-  "search_results.no_search_yet": "Provu serĉi afiŝojn, profilojn aŭ kradvortojn.",
+  "search_results.nothing_found": "Povis trovi nenion por ĉi tiuj serĉaj terminoj",
   "search_results.see_all": "Vidi ĉiujn",
   "search_results.statuses": "Afiŝoj",
-  "search_results.title": "Serĉu \"{q}\"",
+  "search_results.title": "Serĉ-rezultoj por {q}",
   "server_banner.about_active_users": "Personoj uzantaj ĉi tiun servilon dum la lastaj 30 tagoj (Aktivaj Uzantoj Monate)",
   "server_banner.active_users": "aktivaj uzantoj",
   "server_banner.administered_by": "Administrata de:",
@@ -853,7 +817,6 @@
   "status.reblogs.empty": "Ankoraŭ neniu diskonigis tiun afiŝon. Kiam iu faras tion, ri aperos ĉi tie.",
   "status.redraft": "Forigi kaj reskribi",
   "status.remove_bookmark": "Forigi legosignon",
-  "status.remove_favourite": "Forigi el plej ŝatataj",
   "status.replied_in_thread": "Respondis en fadeno",
   "status.replied_to": "Respondis al {name}",
   "status.reply": "Respondi",
@@ -875,9 +838,6 @@
   "subscribed_languages.target": "Ŝanĝu abonitajn lingvojn por {target}",
   "tabs_bar.home": "Hejmo",
   "tabs_bar.notifications": "Sciigoj",
-  "terms_of_service.effective_as_of": "Ĝi ekvalidas de {date}",
-  "terms_of_service.title": "Kondiĉoj de uzado",
-  "terms_of_service.upcoming_changes_on": "Venontaj ŝanĝoj el {date}",
   "time_remaining.days": "{number, plural, one {# tago} other {# tagoj}} restas",
   "time_remaining.hours": "{number, plural, one {# horo} other {# horoj}} restas",
   "time_remaining.minutes": "{number, plural, one {# minuto} other {# minutoj}} restas",
@@ -893,12 +853,26 @@
   "upload_button.label": "Aldonu bildojn, filmeton aŭ sondosieron",
   "upload_error.limit": "Limo de dosiera alŝutado transpasita.",
   "upload_error.poll": "Alŝuto de dosiero ne permesita kun balotenketo.",
+  "upload_form.audio_description": "Priskribi por homoj kiuj malfacile aŭdi",
+  "upload_form.description": "Priskribi por personoj, kiuj estas blindaj aŭ havas vidmalsufiĉon",
   "upload_form.drag_and_drop.instructions": "Por preni amaskomunikilaron aldonaĵon, premu spacoklavon aŭ enen-klavon. Dum trenado, uzu la sagoklavojn por movi la amaskomunikilaron aldonaĵon en iu ajn direkto. Premu spacoklavon aŭ enen-klavon denove por faligi la amaskomunikilaron aldonaĵon en ĝia nova pozicio, aŭ premu eskapan klavon por nuligi.",
   "upload_form.drag_and_drop.on_drag_cancel": "Trenado estis nuligita. Amaskomunikila aldonaĵo {item} estis forigita.",
   "upload_form.drag_and_drop.on_drag_end": "Amaskomunikila aldonaĵo {item} estis forigita.",
   "upload_form.drag_and_drop.on_drag_over": "Amaskomunikila aldonaĵo {item} estis movita.",
   "upload_form.drag_and_drop.on_drag_start": "Prenis amaskomunikilan aldonaĵon {item}.",
   "upload_form.edit": "Redakti",
+  "upload_form.thumbnail": "Ŝanĝi etigita bildo",
+  "upload_form.video_description": "Priskribi por homoj kiuj malfacile aŭdi aŭ vidi",
+  "upload_modal.analyzing_picture": "Bilda analizado…",
+  "upload_modal.apply": "Apliki",
+  "upload_modal.applying": "Apliki…",
+  "upload_modal.choose_image": "Elekti bildon",
+  "upload_modal.description_placeholder": "Laŭ Ludoviko Zamenhof bongustas freŝa ĉeĥa manĝaĵo kun spicoj",
+  "upload_modal.detect_text": "Detekti tekston de la bildo",
+  "upload_modal.edit_media": "Redakti la vidaŭdaĵojn",
+  "upload_modal.hint": "Klaku aŭ trenu la cirklon en la antaŭvidilo por elekti la fokuspunkton kiu ĉiam videblos en ĉiuj etigitaj bildoj.",
+  "upload_modal.preparing_ocr": "Preparante OSR…",
+  "upload_modal.preview_label": "Antaŭvido ({ratio})",
   "upload_progress.label": "Alŝutante...",
   "upload_progress.processing": "Traktante…",
   "username.taken": "La uzantnomo estas jam posedita. Provu alion",
@@ -911,5 +885,5 @@
   "video.mute": "Silentigi",
   "video.pause": "Paŭzigi",
   "video.play": "Ekigi",
-  "video.unmute": "Ne plu silentigi"
+  "video.unmute": "Malsilentigi"
 }
diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json
index bf6d620474..ad02f27daf 100644
--- a/app/javascript/mastodon/locales/es-AR.json
+++ b/app/javascript/mastodon/locales/es-AR.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Editar perfil",
   "account.enable_notifications": "Notificarme cuando @{name} envíe mensajes",
   "account.endorse": "Destacar en el perfil",
-  "account.featured": "Destacados",
-  "account.featured.hashtags": "Etiquetas",
-  "account.featured.posts": "Mensajes",
   "account.featured_tags.last_status_at": "Último mensaje: {date}",
   "account.featured_tags.last_status_never": "Sin mensajes",
+  "account.featured_tags.title": "Etiquetas destacadas de {name}",
   "account.follow": "Seguir",
   "account.follow_back": "Seguir",
   "account.followers": "Seguidores",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}",
   "account.unblock": "Desbloquear a @{name}",
   "account.unblock_domain": "Desbloquear dominio {domain}",
-  "account.unblock_domain_short": "Desbloquear",
   "account.unblock_short": "Desbloquear",
   "account.unendorse": "No destacar en el perfil",
   "account.unfollow": "Dejar de seguir",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Ocurrió un error.",
   "alert.unexpected.title": "¡Epa!",
   "alt_text_badge.title": "Texto alternativo",
-  "alt_text_modal.add_alt_text": "Agregar texto alternativo",
-  "alt_text_modal.add_text_from_image": "Agregar texto a partir de la imagen",
-  "alt_text_modal.cancel": "Cancelar",
-  "alt_text_modal.change_thumbnail": "Cambiar miniatura",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Describí esto para personas con dificultades auditivas…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Describí esto para personas con dificultades visuales…",
-  "alt_text_modal.done": "Listo",
   "announcement.announcement": "Anuncio",
-  "annual_report.summary.archetype.booster": "Corrió la voz",
-  "annual_report.summary.archetype.lurker": "El acechador",
-  "annual_report.summary.archetype.oracle": "El oráculo",
-  "annual_report.summary.archetype.pollster": "Estuvo consultando",
-  "annual_report.summary.archetype.replier": "Respondió un montón",
-  "annual_report.summary.followers.followers": "seguidores",
-  "annual_report.summary.followers.total": "{count} en total",
-  "annual_report.summary.here_it_is": "Acá está tu resumen de {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "el mensaje más veces marcado como favorito",
-  "annual_report.summary.highlighted_post.by_reblogs": "el mensaje que más adhesiones recibió",
-  "annual_report.summary.highlighted_post.by_replies": "el mensaje que más respuestas recibió",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "la aplicación más usada",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "la etiqueta más usada",
-  "annual_report.summary.most_used_hashtag.none": "Ninguna",
-  "annual_report.summary.new_posts.new_posts": "nuevos mensajes",
-  "annual_report.summary.percentile.text": "<topLabel>Eso te coloca en la cima del</topLabel><percentage></percentage><bottomLabel>de los usuarios de {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Queda entre nos.",
-  "annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!",
   "attachments_list.unprocessed": "[sin procesar]",
   "audio.hide": "Ocultar audio",
   "block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado, ya que algunos servidores pueden manejar los bloqueos de forma diferente. Los mensajes públicos todavía podrían estar visibles para los usuarios no conectados.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "No se pudo encontrar la página solicitada. ¿Estás seguro que la dirección web es correcta?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Cerrar",
-  "bundle_modal_error.message": "Algo salió mal al cargar esta pantalla.",
+  "bundle_modal_error.message": "Algo salió mal al cargar este componente.",
   "bundle_modal_error.retry": "Intentá de nuevo",
   "closed_registrations.other_server_instructions": "Ya que Mastodon es descentralizado, podés crearte una cuenta en otro servidor y todavía interactuar con éste.",
   "closed_registrations_modal.description": "Actualmente no es posible crearte una cuenta en {domain}. pero recordá que no necesitás tener una cuenta puntualmente dentro de {domain} para poder usar Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Usuarios bloqueados",
   "column.bookmarks": "Marcadores",
   "column.community": "Línea temporal local",
-  "column.create_list": "Crear lista",
   "column.direct": "Menciones privadas",
   "column.directory": "Explorar perfiles",
   "column.domain_blocks": "Dominios bloqueados",
-  "column.edit_list": "Editar lista",
   "column.favourites": "Favoritos",
   "column.firehose": "Líneas temporales en vivo",
   "column.follow_requests": "Solicitudes de seguimiento",
   "column.home": "Principal",
-  "column.list_members": "Administrar miembros de la lista",
   "column.lists": "Listas",
   "column.mutes": "Usuarios silenciados",
   "column.notifications": "Notificaciones",
@@ -172,7 +140,6 @@
   "column_header.pin": "Fijar",
   "column_header.show_settings": "Mostrar configuración",
   "column_header.unpin": "Dejar de fijar",
-  "column_search.cancel": "Cancelar",
   "column_subheading.settings": "Configuración",
   "community.column_settings.local_only": "Sólo local",
   "community.column_settings.media_only": "Sólo medios",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Duración de la encuesta",
   "compose_form.poll.multiple": "Múltiples opciones",
   "compose_form.poll.option_placeholder": "Opción {number}",
-  "compose_form.poll.single": "Opción única",
+  "compose_form.poll.single": "Elegí una",
   "compose_form.poll.switch_to_multiple": "Cambiar encuesta para permitir opciones múltiples",
   "compose_form.poll.switch_to_single": "Cambiar encuesta para permitir una sola opción",
   "compose_form.poll.type": "Estilo",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Editar",
   "confirmations.edit.message": "Editar ahora sobreescribirá el mensaje que estás redactando actualmente. ¿Estás seguro que querés seguir?",
   "confirmations.edit.title": "¿Sobrescribir mensaje?",
-  "confirmations.follow_to_list.confirm": "Seguir y agregar a la lista",
-  "confirmations.follow_to_list.message": "Necesitás seguir a {name} para agregarle a una lista.",
-  "confirmations.follow_to_list.title": "¿Querés seguirle?",
   "confirmations.logout.confirm": "Cerrar sesión",
   "confirmations.logout.message": "¿Estás seguro que querés cerrar la sesión?",
   "confirmations.logout.title": "¿Cerrar sesión?",
-  "confirmations.missing_alt_text.confirm": "Agregar texto alternativo",
-  "confirmations.missing_alt_text.message": "Tu mensaje contiene medios sin texto alternativo. Agregar descripciones ayuda a que tu contenido sea accesible para más personas.",
-  "confirmations.missing_alt_text.secondary": "Enviar de todos modos",
-  "confirmations.missing_alt_text.title": "¿Agregar texto alternativo?",
   "confirmations.mute.confirm": "Silenciar",
   "confirmations.redraft.confirm": "Eliminar mensaje original y editarlo",
   "confirmations.redraft.message": "¿Estás seguro que querés eliminar este mensaje y volver a editarlo? Se perderán las veces marcadas como favorito y sus adhesiones, y las respuestas al mensaje original quedarán huérfanas.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada.",
   "dismissable_banner.community_timeline": "Estos son los mensajes públicos más recientes de cuentas alojadas en {domain}.",
   "dismissable_banner.dismiss": "Descartar",
-  "dismissable_banner.explore_links": "Estas noticias son las más compartidas hoy en el Fediverso. Las noticias más recientes compartidas por más cuentas se clasifican mejor.",
-  "dismissable_banner.explore_statuses": "Estos mensajes del Fediverso están ganando popularidad hoy. Los mensajes más recientes, con más adhesiones y marcados como favoritos, se clasifican mejor.",
-  "dismissable_banner.explore_tags": "Estas etiquetas están ganando popularidad hoy en el Fediverso. Las etiquetas que son utilizadas por más cuentas se clasifican mejor.",
-  "dismissable_banner.public_timeline": "Estos son los mensajes más recientes de cuentas del Fediverso a las que sigue la gente de {domain}.",
+  "dismissable_banner.explore_links": "Estas son las noticias más compartidas en la web social, hoy mismo. Las noticias más recientes publicadas por diferentes cuentas obtienen más exposición.",
+  "dismissable_banner.explore_statuses": "Estos son los mensajes que están ganando popularidad en la web social, hoy mismo. Los mensajes más recientes con más adhesiones y marcados como favoritos obtienen más exposición.",
+  "dismissable_banner.explore_tags": "Estas son etiquetas que están ganando popularidad en la web social, hoy mismo. Las etiquetas que son usadas por diferentes cuentas obtienen más exposición.",
+  "dismissable_banner.public_timeline": "Estos son los mensajes públicos más recientes de cuentas en la web social que las personas en {domain} siguen.",
   "domain_block_modal.block": "Bloquear servidor",
   "domain_block_modal.block_account_instead": "Bloquear @{name} en su lugar",
   "domain_block_modal.they_can_interact_with_old_posts": "Las cuentas de este servidor pueden interactuar con tus mensajes antiguos.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Resultados de búsqueda",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viajes y lugares",
-  "empty_column.account_featured": "Esta lista está vacía",
   "empty_column.account_hides_collections": "Este usuario eligió no publicar esta información",
   "empty_column.account_suspended": "Cuenta suspendida",
   "empty_column.account_timeline": "¡No hay mensajes acá!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Todavía no hay nada con esta etiqueta.",
   "empty_column.home": "¡Tu línea temporal principal está vacía! Seguí a más cuentas para llenarla.",
   "empty_column.list": "Todavía no hay nada en esta lista. Cuando miembros de esta lista envíen nuevos mensaje, se mostrarán acá.",
+  "empty_column.lists": "Todavía no tenés ninguna lista. Cuando creés una, se mostrará acá.",
   "empty_column.mutes": "Todavía no silenciaste a ningún usuario.",
   "empty_column.notification_requests": "¡Todo limpio! No hay nada acá. Cuando recibás nuevas notificaciones, aparecerán acá, acorde a tu configuración.",
   "empty_column.notifications": "Todavía no tenés ninguna notificación. Cuando otras cuentas interactúen con vos, vas a ver la notificación acá.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Intentá deshabilitarlos y recargá la página. Si eso no ayuda, podés usar Mastodon a través de un navegador web diferente o aplicación nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar stacktrace al portapapeles",
   "errors.unexpected_crash.report_issue": "Informar problema",
+  "explore.search_results": "Resultados de búsqueda",
   "explore.suggested_follows": "Cuentas",
   "explore.title": "Explorá",
   "explore.trending_links": "Noticias",
@@ -373,16 +334,13 @@
   "footer.about": "Información",
   "footer.directory": "Directorio de perfiles",
   "footer.get_app": "Conseguí la aplicación",
+  "footer.invite": "Invitá a gente",
   "footer.keyboard_shortcuts": "Atajos de teclado",
   "footer.privacy_policy": "Política de privacidad",
   "footer.source_code": "Ver código fuente",
   "footer.status": "Estado",
-  "footer.terms_of_service": "Términos del servicio",
   "generic.saved": "Guardado",
   "getting_started.heading": "Inicio de Mastodon",
-  "hashtag.admin_moderation": "Abrir interface de moderación para #{name}",
-  "hashtag.browse": "Ver publicaciones con #{hashtag}",
-  "hashtag.browse_from_account": "Ver publicaciones de @{name} con #{hashtag}",
   "hashtag.column_header.tag_mode.all": "y {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
   "hashtag.column_header.tag_mode.none": "sin {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}} hoy",
   "hashtag.follow": "Seguir etiqueta",
-  "hashtag.mute": "Silenciar #{hashtag}",
   "hashtag.unfollow": "Dejar de seguir etiqueta",
   "hashtags.and_other": "…y {count, plural, other {# más}}",
   "hints.profiles.followers_may_be_missing": "Es posible que falten seguidores de este perfil.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "¿Ignorar notificaciones de cuentas que no te siguen?",
   "ignore_notifications_modal.not_following_title": "¿Ignorar notificaciones de cuentas a las que no seguís?",
   "ignore_notifications_modal.private_mentions_title": "¿Ignorar notificaciones de menciones privadas no solicitadas?",
-  "info_button.label": "Ayuda",
-  "info_button.what_is_alt_text": "<h1>¿Qué es el texto alternativo?</h1> <p>El texto alternativo proporciona descripciones de las imágenes para personas con dificultades visuales, conexiones con escaso ancho de banda o que buscan un contexto adicional.</p> <p>Podés mejorar la accesibilidad y la comprensión para todos escribiendo un texto alternativo claro, conciso y objetivo.</p> <ul><li>Captura los elementos importantes.</li><li>Resumí el texto en imágenes.</li><li>Usá una estructura de frases normal.</li><li>Evitá la información redundante.</li><li>Focalizate en las tendencias y conclusiones clave de los elementos visuales complejos (como diagramas o mapas).</li></ul>",
-  "interaction_modal.action.favourite": "Para continuar, tenés que marcar como favorito desde tu cuenta.",
-  "interaction_modal.action.follow": "Para continuar, tenés que seguir desde tu cuenta.",
-  "interaction_modal.action.reblog": "Para continuar, tenés que adherir desde tu cuenta.",
-  "interaction_modal.action.reply": "Para continuar, tenés que responder desde tu cuenta.",
-  "interaction_modal.action.vote": "Para continuar, tenés que votar desde tu cuenta.",
-  "interaction_modal.go": "Ir",
-  "interaction_modal.no_account_yet": "¿Todavía no tenés cuenta?",
+  "interaction_modal.description.favourite": "Con una cuenta en Mastodon, podés marcar este mensaje como favorito para que el autor sepa que lo apreciás y lo guardás para más adelante.",
+  "interaction_modal.description.follow": "Con una cuenta en Mastodon, podés seguir a {name} para recibir sus mensajes en tu línea temporal principal.",
+  "interaction_modal.description.reblog": "Con una cuenta en Mastodon, podés adherir a este mensaje para compartirlo con tus propios seguidores.",
+  "interaction_modal.description.reply": "Con una cuenta en Mastodon, podés responder a este mensaje.",
+  "interaction_modal.login.action": "Llevame al comienzo",
+  "interaction_modal.login.prompt": "Dominio de su servidor de inicio, p. ej., mastodon.social",
+  "interaction_modal.no_account_yet": "¿No tenés cuenta en Mastodon?",
   "interaction_modal.on_another_server": "En un servidor diferente",
   "interaction_modal.on_this_server": "En este servidor",
+  "interaction_modal.sign_in": "No iniciaste sesión en este servidor. ¿Dónde se encuentra alojada tu cuenta?",
+  "interaction_modal.sign_in_hint": "Ayuda: es el sitio web en donde te registraste. Si no te acordás, buscá el correo electrónico de bienvenida en tu cuenta de email. También podés usar tu nombre de usuario entero, p. ej., @tunombredeusuario@mastodon.social.",
   "interaction_modal.title.favourite": "Marcar como favorito el mensaje de {name}",
   "interaction_modal.title.follow": "Seguir a {name}",
   "interaction_modal.title.reblog": "Adherir al mensaje de {name}",
   "interaction_modal.title.reply": "Responder al mensaje de {name}",
-  "interaction_modal.title.vote": "Votá en la encuesta de {name}",
-  "interaction_modal.username_prompt": "Por ejemplo: {example}",
   "intervals.full.days": "{number, plural, one {# día} other {# días}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Mostrar/ocultar texto detrás de la advertencia de contenido (\"CW\")",
   "keyboard_shortcuts.toggle_sensitivity": "Mostrar/ocultar medios",
   "keyboard_shortcuts.toot": "Comenzar un mensaje nuevo",
-  "keyboard_shortcuts.translate": "para traducir un mensaje",
   "keyboard_shortcuts.unfocus": "Quitar el foco del área de texto de redacción o de búsqueda",
   "keyboard_shortcuts.up": "Subir en la lista",
   "lightbox.close": "Cerrar",
@@ -490,32 +444,20 @@
   "link_preview.author": "Por {name}",
   "link_preview.more_from_author": "Más de {name}",
   "link_preview.shares": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}",
-  "lists.add_member": "Agregar",
-  "lists.add_to_list": "Agregar a lista",
-  "lists.add_to_lists": "Agregar a {name} a las listas",
-  "lists.create": "Crear",
-  "lists.create_a_list_to_organize": "Creá una nueva lista para organizar tu línea temporal principal",
-  "lists.create_list": "Crear lista",
+  "lists.account.add": "Agregar a lista",
+  "lists.account.remove": "Quitar de lista",
   "lists.delete": "Eliminar lista",
-  "lists.done": "Listo",
   "lists.edit": "Editar lista",
-  "lists.exclusive": "Ocultar miembros en la línea temporal principal",
-  "lists.exclusive_hint": "Si alguien está en esta lista, ocultalo en tu línea temporal principal para evitar que aparezcan sus mensajes dos veces.",
-  "lists.find_users_to_add": "Buscar usuarios para agregar",
-  "lists.list_members": "Miembros de lista",
-  "lists.list_members_count": "{count, plural,one {# miembro} other {# miembros}}",
-  "lists.list_name": "Nombre de la lista",
-  "lists.new_list_name": "Nombre de la nueva lista",
-  "lists.no_lists_yet": "Aún no hay listas.",
-  "lists.no_members_yet": "Aún no hay miembros.",
-  "lists.no_results_found": "No se encontraron resultados.",
-  "lists.remove_member": "Quitar",
+  "lists.edit.submit": "Cambiar título",
+  "lists.exclusive": "Ocultar estos mensajes del inicio",
+  "lists.new.create": "Agregar lista",
+  "lists.new.title_placeholder": "Título de nueva lista",
   "lists.replies_policy.followed": "Cualquier cuenta seguida",
   "lists.replies_policy.list": "Miembros de la lista",
   "lists.replies_policy.none": "Nadie",
-  "lists.save": "Guardar",
-  "lists.search": "Buscar",
-  "lists.show_replies_to": "Incluir las respuestas de los miembros de la lista a",
+  "lists.replies_policy.title": "Mostrar respuestas a:",
+  "lists.search": "Buscar entre la gente que seguís",
+  "lists.subheading": "Tus listas",
   "load_pending": "{count, plural, one {# elemento nuevo} other {# elementos nuevos}}",
   "loading_indicator.label": "Cargando…",
   "media_gallery.hide": "Ocultar",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} denunció a {target}",
   "notification.admin.sign_up": "Se registró {name}",
   "notification.admin.sign_up.name_and_others": "Se registraron {name} y {count, plural, one {# cuenta más} other {# cuentas más}}",
-  "notification.annual_report.message": "¡Tu #Wrapstodon {year} te espera! ¡Desvelá los momentos más destacados y memorables de tu año en Mastodon!",
-  "notification.annual_report.view": "Ver #Wrapstodon",
   "notification.favourite": "{name} marcó tu mensaje como favorito",
   "notification.favourite.name_and_others_with_link": "{name} y <a>{count, plural, one {# cuenta más} other {# cuentas más}}</a> marcaron tu mensaje como favorito",
-  "notification.favourite_pm": "{name} marcó como favorito tu mención privada",
-  "notification.favourite_pm.name_and_others_with_link": "{name} y <a>{count, plural, one {# más} other {# más}}</a> marcaron como favorito tu mención privada",
   "notification.follow": "{name} te empezó a seguir",
   "notification.follow.name_and_others": "{name} y <a>{count, plural, one {# cuenta más} other {# cuentas más}}</a> te están siguiendo",
   "notification.follow_request": "{name} solicitó seguirte",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Habilitar notificaciones de escritorio",
   "notifications_permission_banner.how_to_control": "Para recibir notificaciones cuando Mastodon no está abierto, habilitá las notificaciones de escritorio. Podés controlar con precisión qué tipos de interacciones generan notificaciones de escritorio a través del botón {icon} de arriba, una vez que estén habilitadas.",
   "notifications_permission_banner.title": "No te pierdas nada",
-  "onboarding.follows.back": "Volver",
-  "onboarding.follows.done": "Listo",
+  "onboarding.action.back": "Llevame de regreso",
+  "onboarding.actions.back": "Llevame de regreso",
+  "onboarding.actions.go_to_explore": "Llevame a las tendencias",
+  "onboarding.actions.go_to_home": "Llevame a mi línea temporal principal",
+  "onboarding.compose.template": "¡Hola, #Mastodon!",
   "onboarding.follows.empty": "Desafortunadamente, no se pueden mostrar resultados en este momento. Podés intentar usar la búsqueda o navegar por la página de exploración para encontrar cuentas a las que seguir, o intentarlo de nuevo más tarde.",
-  "onboarding.follows.search": "Buscar",
-  "onboarding.follows.title": "Para comenzar, empezá a seguir cuentas",
+  "onboarding.follows.lead": "Tu línea temporal de inicio es la forma principal de experimentar Mastodon. Cuanta más cuentas sigás, más activa e interesante será. Para empezar, acá tenés algunas sugerencias:",
+  "onboarding.follows.title": "Personalizá tu línea de tiempo principal",
   "onboarding.profile.discoverable": "Hacer que mi perfil sea detectable",
   "onboarding.profile.discoverable_hint": "Cuando optás por ser detectable en Mastodon, tus mensajes pueden aparecer en los resultados de búsqueda y de tendencia, y tu perfil puede ser sugerido a personas con intereses similares a los tuyos.",
   "onboarding.profile.display_name": "Nombre para mostrar",
   "onboarding.profile.display_name_hint": "Tu nombre completo o tu pseudónimo…",
+  "onboarding.profile.lead": "Siempre podés completar esto más tarde en la configuración, donde hay disponibles más opciones de personalización.",
   "onboarding.profile.note": "Biografía",
   "onboarding.profile.note_hint": "Podés @mencionar otras cuentas o usar #etiquetas…",
   "onboarding.profile.save_and_continue": "Guardar y continuar",
   "onboarding.profile.title": "Configuración del perfil",
   "onboarding.profile.upload_avatar": "Subir avatar",
   "onboarding.profile.upload_header": "Subir cabecera",
+  "onboarding.share.lead": "¡Decile a la gente cómo te pueden encontrar en Mastodon!",
+  "onboarding.share.message": "¡En #Mastodon soy «{username}»! Podés seguirme desde {url}",
+  "onboarding.share.next_steps": "Posibles próximos pasos:",
+  "onboarding.share.title": "Compartí tu perfil",
+  "onboarding.start.lead": "Ahora sos parte de Mastodon, una plataforma única y descentralizada de redes sociales donde vos —no un algoritmo— personalizás tu propia experiencia. Vamos a introducirte en esta nueva frontera social:",
+  "onboarding.start.skip": "¿No necesitás ayuda para empezar?",
+  "onboarding.start.title": "¡Listo!",
+  "onboarding.steps.follow_people.body": "Seguir cuentas interesantes es de lo que trata Mastodon.",
+  "onboarding.steps.follow_people.title": "Personalizá tu línea de tiempo principal",
+  "onboarding.steps.publish_status.body": "Decili hola al mundo con textos, fotos, videos o encuestas {emoji}",
+  "onboarding.steps.publish_status.title": "Escribí tu primer mensaje",
+  "onboarding.steps.setup_profile.body": "Aumentá tus interacciones teniendo un perfil completo.",
+  "onboarding.steps.setup_profile.title": "Personalizá tu perfil",
+  "onboarding.steps.share_profile.body": "¡Hacé que tus amistades sepan cómo encontrarte en Mastodon!",
+  "onboarding.steps.share_profile.title": "Compartí tu perfil de Mastodon",
+  "onboarding.tips.2fa": "<strong>¿Lo sabías?</strong> Podés proteger su cuenta configurando la autenticación de dos factores en la configuración de tu cuenta. Funciona con cualquier aplicación TOTP de tu elección, ¡sin necesidad de número de teléfono!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>¿Lo sabías?</strong> Como Mastodon es una red social descentralizada, algunos perfiles que encuentres serán alojados en servidores diferentes del tuyo. ¡Y sin embargo podés interactuar con ellos! ¡El nombre de su servidor está en la segunda mitad de sus nombres de usuario!",
+  "onboarding.tips.migration": "<strong>¿Lo sabías?</strong> Si creés que {domain} no es una gran elección de servidor para vos en el futuro, podés mudarte a otro servidor de Mastodon sin perder a tus seguidores. ¡Incluso podés alojar tu propio servidor!",
+  "onboarding.tips.verification": "<strong>¿Lo sabías?</strong> Podés verificar tu cuenta poniendo el enlace de tu perfil de Mastodon en tu propio sitio web y agregando la dirección web de tu sitio en tu perfil de Mastodon. ¡No hay necesidad de comisiones ni documentación!",
   "password_confirmation.exceeds_maxlength": "La confirmación de contraseña excede la longitud máxima de la contraseña",
   "password_confirmation.mismatching": "La confirmación de contraseña no coincide",
   "picture_in_picture.restore": "Restaurar",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Quitar encuesta",
   "privacy.change": "Configurar privacidad del mensaje",
   "privacy.direct.long": "Todas las cuentas mencionadas en el mensaje",
-  "privacy.direct.short": "Mención privada",
+  "privacy.direct.short": "Cuentas específicas",
   "privacy.private.long": "Solo tus seguidores",
   "privacy.private.short": "Seguidores",
   "privacy.public.long": "Cualquier persona dentro y fuera de Mastodon",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Política de privacidad",
   "recommended": "Opción recomendada",
   "refresh": "Refrescar",
-  "regeneration_indicator.please_stand_by": "Esperá, por favor.",
-  "regeneration_indicator.preparing_your_home_feed": "Preparando tu línea temporal principal…",
+  "regeneration_indicator.label": "Cargando…",
+  "regeneration_indicator.sublabel": "¡Se está preparando tu línea temporal principal!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural,one {hace # día} other {hace # días}}",
   "relative_time.full.hours": "{number, plural,one {hace # hora} other {hace # horas}}",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Perfiles",
   "search_results.all": "Todos",
   "search_results.hashtags": "Etiquetas",
-  "search_results.no_results": "Sin resultados.",
-  "search_results.no_search_yet": "Intentá buscar publicaciones, perfiles o etiquetas.",
+  "search_results.nothing_found": "No se pudo encontrar nada para estos términos de búsqueda",
   "search_results.see_all": "Ver todo",
   "search_results.statuses": "Mensajes",
-  "search_results.title": "Búsqueda de «{q}»",
+  "search_results.title": "Buscar {q}",
   "server_banner.about_active_users": "Personas usando este servidor durante los últimos 30 días (Usuarios Activos Mensuales)",
   "server_banner.active_users": "usuarios activos",
   "server_banner.administered_by": "Administrado por:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Todavía nadie adhirió a este mensaje. Cuando alguien lo haga, se mostrará acá.",
   "status.redraft": "Eliminar mensaje original y editarlo",
   "status.remove_bookmark": "Quitar marcador",
-  "status.remove_favourite": "Quitar de favoritos",
   "status.replied_in_thread": "Respuesta en hilo",
   "status.replied_to": "Respondió a {name}",
   "status.reply": "Responder",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Cambiar idiomas suscritos para {target}",
   "tabs_bar.home": "Principal",
   "tabs_bar.notifications": "Notificaciones",
-  "terms_of_service.effective_as_of": "Efectivo a partir de {date}",
-  "terms_of_service.title": "Términos del servicio",
-  "terms_of_service.upcoming_changes_on": "Próximos cambios el {date}",
   "time_remaining.days": "{number, plural,one {queda # día} other {quedan # días}}",
   "time_remaining.hours": "{number, plural,one {queda # hora} other {quedan # horas}}",
   "time_remaining.minutes": "{number, plural,one {queda # minuto} other {quedan # minutos}}",
@@ -897,12 +853,26 @@
   "upload_button.label": "Agregá imágenes, o un archivo de audio o video",
   "upload_error.limit": "Se excedió el límite de subida de archivos.",
   "upload_error.poll": "No se permite la subida de archivos en encuestas.",
+  "upload_form.audio_description": "Agregá una descripción para personas con dificultades auditivas",
+  "upload_form.description": "Agregá una descripción para personas con dificultades visuales",
   "upload_form.drag_and_drop.instructions": "Para recoger un archivo multimedia, pulsá la barra espaciadora o la tecla Enter. Mientras arrastrás, usá las teclas de flecha para mover el archivo multimedia en cualquier dirección. Volvé a pulsar la barra espaciadora o la tecla Enter para soltar el archivo multimedia en su nueva posición, o pulsá la tecla Escape para cancelar.",
   "upload_form.drag_and_drop.on_drag_cancel": "Se canceló el arrastre. Se eliminó el archivo adjunto {item}.",
   "upload_form.drag_and_drop.on_drag_end": "El archivo adjunto {item} fue soltado.",
   "upload_form.drag_and_drop.on_drag_over": "El archivo adjunto {item} fue movido.",
   "upload_form.drag_and_drop.on_drag_start": "Se ha recogido el archivo adjunto {item}.",
   "upload_form.edit": "Editar",
+  "upload_form.thumbnail": "Cambiar miniatura",
+  "upload_form.video_description": "Agregá una descripción para personas con dificultades auditivas o visuales",
+  "upload_modal.analyzing_picture": "Analizando imagen…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.applying": "Aplicando…",
+  "upload_modal.choose_image": "Elegir imagen",
+  "upload_modal.description_placeholder": "El veloz murciélago hindú comía feliz cardillo y kiwi. La cigüeña tocaba el saxofón detrás del palenque de paja.",
+  "upload_modal.detect_text": "Detectar texto de la imagen",
+  "upload_modal.edit_media": "Editar medio",
+  "upload_modal.hint": "Hacé clic o arrastrá el círculo en la previsualización para elegir el punto focal que siempre estará a la vista en todas las miniaturas.",
+  "upload_modal.preparing_ocr": "Preparando OCR…",
+  "upload_modal.preview_label": "Previsualización ({ratio})",
   "upload_progress.label": "Subiendo...",
   "upload_progress.processing": "Procesando…",
   "username.taken": "Ese nombre de usuario está ocupado. Probá con otro",
@@ -915,9 +885,5 @@
   "video.mute": "Silenciar",
   "video.pause": "Pausar",
   "video.play": "Reproducir",
-  "video.skip_backward": "Saltar atrás",
-  "video.skip_forward": "Adelantar",
-  "video.unmute": "Quitar silenciado",
-  "video.volume_down": "Bajar volumen",
-  "video.volume_up": "Subir volumen"
+  "video.unmute": "Dejar de silenciar"
 }
diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json
index 69e20fc016..88cb88dbd3 100644
--- a/app/javascript/mastodon/locales/es-MX.json
+++ b/app/javascript/mastodon/locales/es-MX.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Editar perfil",
   "account.enable_notifications": "Notificarme cuando @{name} publique algo",
   "account.endorse": "Destacar en mi perfil",
-  "account.featured": "Destacado",
-  "account.featured.hashtags": "Etiquetas",
-  "account.featured.posts": "Publicaciones",
   "account.featured_tags.last_status_at": "Última publicación el {date}",
   "account.featured_tags.last_status_never": "Sin publicaciones",
+  "account.featured_tags.title": "Etiquetas destacadas de {name}",
   "account.follow": "Seguir",
   "account.follow_back": "Seguir también",
   "account.followers": "Seguidores",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
   "account.unblock": "Desbloquear a @{name}",
   "account.unblock_domain": "Mostrar a {domain}",
-  "account.unblock_domain_short": "Desbloquear",
   "account.unblock_short": "Desbloquear",
   "account.unendorse": "No mostrar en el perfil",
   "account.unfollow": "Dejar de seguir",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Hubo un error inesperado.",
   "alert.unexpected.title": "¡Uy!",
   "alt_text_badge.title": "Texto alternativo",
-  "alt_text_modal.add_alt_text": "Añadir texto alternativo",
-  "alt_text_modal.add_text_from_image": "Añadir texto de la imagen",
-  "alt_text_modal.cancel": "Cancelar",
-  "alt_text_modal.change_thumbnail": "Cambiar miniatura",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Describe esto para personas con discapacidad auditiva…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Describe esto para personas con discapacidad visual…",
-  "alt_text_modal.done": "Hecho",
   "announcement.announcement": "Anuncio",
-  "annual_report.summary.archetype.booster": "El cazador de tendencias",
-  "annual_report.summary.archetype.lurker": "El merodeador",
-  "annual_report.summary.archetype.oracle": "El oráculo",
-  "annual_report.summary.archetype.pollster": "El encuestador",
-  "annual_report.summary.archetype.replier": "El más sociable",
-  "annual_report.summary.followers.followers": "seguidores",
-  "annual_report.summary.followers.total": "{count} en total",
-  "annual_report.summary.here_it_is": "Este es el resumen de tu {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "publicación con más favoritos",
-  "annual_report.summary.highlighted_post.by_reblogs": "publicación más impulsada",
-  "annual_report.summary.highlighted_post.by_replies": "publicación con más respuestas",
-  "annual_report.summary.highlighted_post.possessive": "de {name}",
-  "annual_report.summary.most_used_app.most_used_app": "aplicación más utilizada",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta más utilizada",
-  "annual_report.summary.most_used_hashtag.none": "Ninguna",
-  "annual_report.summary.new_posts.new_posts": "nuevas publicaciones",
-  "annual_report.summary.percentile.text": "<topLabel>Eso te sitúa en el top</topLabel><percentage></percentage><bottomLabel>de usuarios de {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "No se lo diremos a Bernie.",
-  "annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!",
   "attachments_list.unprocessed": "(sin procesar)",
   "audio.hide": "Ocultar audio",
   "block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado, ya que algunos servidores pueden manejar bloques de forma diferente. Las publicaciones públicas pueden ser todavía visibles para los usuarios no conectados.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "No se pudo encontrar la página solicitada. ¿Estás seguro de que la URL en la barra de direcciones es correcta?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Cerrar",
-  "bundle_modal_error.message": "Algo ha fallado al cargar esta pantalla.",
+  "bundle_modal_error.message": "Algo salió mal al cargar este componente.",
   "bundle_modal_error.retry": "Inténtalo de nuevo",
   "closed_registrations.other_server_instructions": "Como Mastodon es descentralizado, puedes crear una cuenta en otro servidor y seguir interactuando con este.",
   "closed_registrations_modal.description": "La creación de una cuenta en {domain} no es posible actualmente, pero ten en cuenta que no necesitas una cuenta específicamente en {domain} para usar Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Usuarios bloqueados",
   "column.bookmarks": "Marcadores",
   "column.community": "Cronología local",
-  "column.create_list": "Crear lista",
   "column.direct": "Menciones privadas",
   "column.directory": "Buscar perfiles",
   "column.domain_blocks": "Dominios ocultados",
-  "column.edit_list": "Editar lista",
   "column.favourites": "Favoritos",
   "column.firehose": "Cronologías",
   "column.follow_requests": "Solicitudes de seguimiento",
   "column.home": "Inicio",
-  "column.list_members": "Administrar miembros de la lista",
   "column.lists": "Listas",
   "column.mutes": "Usuarios silenciados",
   "column.notifications": "Notificaciones",
@@ -172,7 +140,6 @@
   "column_header.pin": "Fijar",
   "column_header.show_settings": "Mostrar ajustes",
   "column_header.unpin": "Desfijar",
-  "column_search.cancel": "Cancelar",
   "column_subheading.settings": "Ajustes",
   "community.column_settings.local_only": "Solo local",
   "community.column_settings.media_only": "Solo media",
@@ -191,14 +158,14 @@
   "compose_form.poll.duration": "Duración de la encuesta",
   "compose_form.poll.multiple": "Selección múltiple",
   "compose_form.poll.option_placeholder": "Opción {number}",
-  "compose_form.poll.single": "Selección única",
+  "compose_form.poll.single": "Seleccione uno",
   "compose_form.poll.switch_to_multiple": "Cambiar la encuesta para permitir múltiples opciones",
   "compose_form.poll.switch_to_single": "Cambiar la encuesta para permitir una única opción",
   "compose_form.poll.type": "Estilo",
-  "compose_form.publish": "Publicar",
+  "compose_form.publish": "Publicación",
   "compose_form.publish_form": "Nueva publicación",
   "compose_form.reply": "Respuesta",
-  "compose_form.save_changes": "Actualizar",
+  "compose_form.save_changes": "Actualización",
   "compose_form.spoiler.marked": "Quitar advertencia de contenido",
   "compose_form.spoiler.unmarked": "Añadir advertencia de contenido",
   "compose_form.spoiler_placeholder": "Advertencia de contenido (opcional)",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Editar",
   "confirmations.edit.message": "Editar sobrescribirá el mensaje que estás escribiendo. ¿Estás seguro de que deseas continuar?",
   "confirmations.edit.title": "¿Sobreescribir publicación?",
-  "confirmations.follow_to_list.confirm": "Seguir y agregar a la lista",
-  "confirmations.follow_to_list.message": "Tienes que seguir a {name} para añadirlo a una lista.",
-  "confirmations.follow_to_list.title": "¿Seguir a usuario?",
   "confirmations.logout.confirm": "Cerrar sesión",
   "confirmations.logout.message": "¿Estás seguro de que quieres cerrar la sesión?",
   "confirmations.logout.title": "¿Deseas cerrar sesión?",
-  "confirmations.missing_alt_text.confirm": "Añadir texto alternativo",
-  "confirmations.missing_alt_text.message": "Tu publicación contiene contenido multimedia sin texto alternativo. Agregar descripciones ayuda a que tu contenido sea accesible para más personas.",
-  "confirmations.missing_alt_text.secondary": "Publicar de todas maneras",
-  "confirmations.missing_alt_text.title": "¿Añadir texto alternativo?",
   "confirmations.mute.confirm": "Silenciar",
   "confirmations.redraft.confirm": "Borrar y volver a borrador",
   "confirmations.redraft.message": "¿Estás seguro que quieres borrar esta publicación y editarla? Los favoritos e impulsos se perderán, y las respuestas a la publicación original quedarán separadas.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada.",
   "dismissable_banner.community_timeline": "Estas son las publicaciones públicas más recientes de las personas cuyas cuentas están alojadas en {domain}.",
   "dismissable_banner.dismiss": "Descartar",
-  "dismissable_banner.explore_links": "Estas noticias son las más compartidas hoy en el fediverso. Las noticias más recientes publicadas por más personas diferentes se clasifican mejor.",
-  "dismissable_banner.explore_statuses": "Estas publicaciones del fediverso están ganando popularidad hoy. Las publicaciones más recientes, con más impulsos y favoritos, se clasifican mejor.",
-  "dismissable_banner.explore_tags": "Estas etiquetas están ganando popularidad en el fediverso hoy en día. Las etiquetas que son utilizadas por más personas diferentes se clasifican mejor.",
-  "dismissable_banner.public_timeline": "Estas son las publicaciones más recientes de las personas del fediverso a las que sigue la gente de {domain}.",
+  "dismissable_banner.explore_links": "Estas noticias están siendo discutidas por personas en este y otros servidores de la red descentralizada en este momento.",
+  "dismissable_banner.explore_statuses": "Estas son las publicaciones que están en tendencia en la red ahora. Las publicaciones recientes con más impulsos y favoritos se muestran más arriba.",
+  "dismissable_banner.explore_tags": "Se trata de etiquetas que están ganando adeptos en las redes sociales hoy en día. Las etiquetas que son utilizadas por más personas diferentes se clasifican mejor.",
+  "dismissable_banner.public_timeline": "Estas son las publicaciones públicas más recientes de personas en la web social a las que sigue la gente en {domain}.",
   "domain_block_modal.block": "Bloquear servidor",
   "domain_block_modal.block_account_instead": "Bloquear @{name} en su lugar",
   "domain_block_modal.they_can_interact_with_old_posts": "Las personas de este servidor pueden interactuar con tus publicaciones antiguas.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Resultados de búsqueda",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viajes y lugares",
-  "empty_column.account_featured": "Esta lista está vacía",
   "empty_column.account_hides_collections": "Este usuario ha elegido no hacer disponible esta información",
   "empty_column.account_suspended": "Cuenta suspendida",
   "empty_column.account_timeline": "¡No hay publicaciones aquí!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "No hay nada en esta etiqueta aún.",
   "empty_column.home": "¡Tu cronología está vacía! Sigue a más gente para llenarla.",
   "empty_column.list": "Aún no hay nada en esta lista. Cuando los miembros de esta lista publiquen nuevos contenidos, aparecerán aquí.",
+  "empty_column.lists": "No tienes ninguna lista. cuando crees una, se mostrará aquí.",
   "empty_column.mutes": "Aún no has silenciado a ningún usuario.",
   "empty_column.notification_requests": "¡Todo limpio! No hay nada aquí. Cuando recibas nuevas notificaciones, aparecerán aquí conforme a tu configuración.",
   "empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Intenta deshabilitarlos y recarga la página. Si eso no ayuda, podrías usar Mastodon a través de un navegador web diferente o aplicación nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar el seguimiento de pila en el portapapeles",
   "errors.unexpected_crash.report_issue": "Informar problema",
+  "explore.search_results": "Resultados de búsqueda",
   "explore.suggested_follows": "Personas",
   "explore.title": "Descubrir",
   "explore.trending_links": "Noticias",
@@ -373,16 +334,13 @@
   "footer.about": "Acerca de",
   "footer.directory": "Directorio de perfiles",
   "footer.get_app": "Obtener la aplicación",
+  "footer.invite": "Invitar personas",
   "footer.keyboard_shortcuts": "Atajos de teclado",
   "footer.privacy_policy": "Política de privacidad",
   "footer.source_code": "Ver código fuente",
   "footer.status": "Estado",
-  "footer.terms_of_service": "Condiciones del servicio",
   "generic.saved": "Guardado",
   "getting_started.heading": "Primeros pasos",
-  "hashtag.admin_moderation": "Abrir interfaz de moderación para #{name}",
-  "hashtag.browse": "Explorar publicaciones en #{hashtag}",
-  "hashtag.browse_from_account": "Explorar publicaciones desde @{name} en #{hashtag}",
   "hashtag.column_header.tag_mode.all": "y {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
   "hashtag.column_header.tag_mode.none": "sin {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}} hoy",
   "hashtag.follow": "Seguir etiqueta",
-  "hashtag.mute": "Silenciar #{hashtag}",
   "hashtag.unfollow": "Dejar de seguir etiqueta",
   "hashtags.and_other": "…y {count, plural, other {# más}}",
   "hints.profiles.followers_may_be_missing": "Puede que no se muestren todos los seguidores de este perfil.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "¿Ignorar notificaciones de personas que no te siguen?",
   "ignore_notifications_modal.not_following_title": "¿Ignorar notificaciones de personas a las que no sigues?",
   "ignore_notifications_modal.private_mentions_title": "¿Ignorar notificaciones de menciones privadas no solicitadas?",
-  "info_button.label": "Ayuda",
-  "info_button.what_is_alt_text": "<h1>¿Qué es el texto alternativo?</h1> <p>El texto alternativo ofrece descripciones de las imágenes para individuos con dificultades visuales, conexiones de bajo ancho de banda o que buscan un contexto adicional.</p> <p>Puedes mejorar la accesibilidad y la comprensión para todos redactando un texto alternativo claro, breve y objetivo.</p> <ul><li>Captura los elementos clave.</li><li>Resume el texto en imágenes.</li><li>Utiliza una estructura de oraciones estándar.</li><li>Evita la información repetitiva.</li><li>Enfócate en las tendencias y conclusiones principales de los elementos visuales complejos (como gráficos o mapas).</li></ul>",
-  "interaction_modal.action.favourite": "Para continuar, debes marcar como favorito desde tu cuenta.",
-  "interaction_modal.action.follow": "Para continuar, debes seguir desde tu cuenta.",
-  "interaction_modal.action.reblog": "Para continuar, debes impulsar desde tu cuenta.",
-  "interaction_modal.action.reply": "Para continuar, debes responder desde tu cuenta.",
-  "interaction_modal.action.vote": "Para continuar, debes votar desde tu cuenta.",
-  "interaction_modal.go": "Ir",
-  "interaction_modal.no_account_yet": "¿Aún no tienes una cuenta?",
+  "interaction_modal.description.favourite": "Con una cuenta en Mastodon, puedes marcar como favorita esta publicación para que el autor sepa que te gusta, y guardala para más adelante.",
+  "interaction_modal.description.follow": "Con una cuenta en Mastodon, puedes seguir {name} para recibir sus publicaciones en tu fuente de inicio.",
+  "interaction_modal.description.reblog": "Con una cuenta en Mastodon, puedes impulsar esta publicación para compartirla con tus propios seguidores.",
+  "interaction_modal.description.reply": "Con una cuenta en Mastodon, puedes responder a esta publicación.",
+  "interaction_modal.login.action": "Ir a Inicio",
+  "interaction_modal.login.prompt": "Dominio de tu servidor, por ejemplo: mastodon.social",
+  "interaction_modal.no_account_yet": "¿Aún no tienes cuenta en Mastodon?",
   "interaction_modal.on_another_server": "En un servidor diferente",
   "interaction_modal.on_this_server": "En este servidor",
+  "interaction_modal.sign_in": "No estás registrado en este servidor. ¿Dónde tienes tu cuenta?",
+  "interaction_modal.sign_in_hint": "Pista: Ese es el sitio donde te registraste. Si no lo recuerdas, busca el correo electrónico de bienvenida en tu bandeja de entrada. También puedes introducir tu nombre de usuario completo (por ejemplo: @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Marcar como favorita la publicación de {name}",
   "interaction_modal.title.follow": "Seguir a {name}",
   "interaction_modal.title.reblog": "Impulsar la publicación de {name}",
   "interaction_modal.title.reply": "Responder la publicación de {name}",
-  "interaction_modal.title.vote": "Votar en la encuesta de {name}",
-  "interaction_modal.username_prompt": "Por ejemplo: {example}",
   "intervals.full.days": "{number, plural, one {# día} other {# días}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Mostrar/ocultar texto detrás de AC",
   "keyboard_shortcuts.toggle_sensitivity": "Mostrar/ocultar multimedia",
   "keyboard_shortcuts.toot": "Comenzar una nueva publicación",
-  "keyboard_shortcuts.translate": "para traducir una publicación",
   "keyboard_shortcuts.unfocus": "Desenfocar área de redacción/búsqueda",
   "keyboard_shortcuts.up": "Ascender en la lista",
   "lightbox.close": "Cerrar",
@@ -490,32 +444,20 @@
   "link_preview.author": "Por {name}",
   "link_preview.more_from_author": "Más de {name}",
   "link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
-  "lists.add_member": "Agregar",
-  "lists.add_to_list": "Agregar a lista",
-  "lists.add_to_lists": "Agregar {name} a listas",
-  "lists.create": "Crear",
-  "lists.create_a_list_to_organize": "Crea una nueva lista para organizar tu página de inicio",
-  "lists.create_list": "Crear lista",
+  "lists.account.add": "Añadir a lista",
+  "lists.account.remove": "Quitar de lista",
   "lists.delete": "Borrar lista",
-  "lists.done": "Hecho",
   "lists.edit": "Editar lista",
-  "lists.exclusive": "Ocultar miembros en Inicio",
-  "lists.exclusive_hint": "Si alguien aparece en esta lista, ocúltalo en tu página de inicio para evitar ver sus publicaciones dos veces.",
-  "lists.find_users_to_add": "Buscar usuarios para agregar",
-  "lists.list_members": "Miembros de la lista",
-  "lists.list_members_count": "{count, plural,one {# miembro} other {# miembros}}",
-  "lists.list_name": "Nombre de la lista",
-  "lists.new_list_name": "Nombre de la nueva lista",
-  "lists.no_lists_yet": "No hay listas todavía.",
-  "lists.no_members_yet": "No hay miembros todavía.",
-  "lists.no_results_found": "No se han encontrado resultados.",
-  "lists.remove_member": "Eliminar",
+  "lists.edit.submit": "Cambiar título",
+  "lists.exclusive": "Ocultar estas publicaciones en inicio",
+  "lists.new.create": "Añadir lista",
+  "lists.new.title_placeholder": "Título de la nueva lista",
   "lists.replies_policy.followed": "Cualquier usuario seguido",
   "lists.replies_policy.list": "Miembros de la lista",
   "lists.replies_policy.none": "Nadie",
-  "lists.save": "Guardar",
-  "lists.search": "Buscar",
-  "lists.show_replies_to": "Incluir respuestas de miembros de la lista a",
+  "lists.replies_policy.title": "Mostrar respuestas a:",
+  "lists.search": "Buscar entre la gente a la que sigues",
+  "lists.subheading": "Tus listas",
   "load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
   "loading_indicator.label": "Cargando…",
   "media_gallery.hide": "Ocultar",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} reportó {target}",
   "notification.admin.sign_up": "{name} se unio",
   "notification.admin.sign_up.name_and_others": "{name} y {count, plural, one {# otro} other {# otros}} se registraron",
-  "notification.annual_report.message": "¡Tu #Wrapstodon {year} te espera! ¡Desvela los momentos más destacados y memorables de tu año en Mastodon!",
-  "notification.annual_report.view": "Ver #Wrapstodon",
   "notification.favourite": "{name} marcó como favorita tu publicación",
   "notification.favourite.name_and_others_with_link": "{name} y <a>{count, plural, one {# otro} other {# otros}}</a> marcaron tu publicación como favorita",
-  "notification.favourite_pm": "{name} marcó como favorito tu mención privada",
-  "notification.favourite_pm.name_and_others_with_link": "{name} y <a>{count, plural, one {# otro} other {# otros}}</a> marcaron como favorito tu mención privada",
   "notification.follow": "{name} te empezó a seguir",
   "notification.follow.name_and_others": "{name} y <a>{count, plural, one {# otro} other {# otros}}</a> te han seguido",
   "notification.follow_request": "{name} ha solicitado seguirte",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Habilitar notificaciones de escritorio",
   "notifications_permission_banner.how_to_control": "Para recibir notificaciones cuando Mastodon no esté abierto, habilite las notificaciones de escritorio. Puedes controlar con precisión qué tipos de interacciones generan notificaciones de escritorio a través del botón {icon} de arriba una vez que estén habilitadas.",
   "notifications_permission_banner.title": "Nunca te pierdas nada",
-  "onboarding.follows.back": "Volver",
-  "onboarding.follows.done": "Hecho",
+  "onboarding.action.back": "Llévame atrás",
+  "onboarding.actions.back": "Llévame atrás",
+  "onboarding.actions.go_to_explore": "Ver qué es tendencia",
+  "onboarding.actions.go_to_home": "Ir al inicio",
+  "onboarding.compose.template": "¡Hola #Mastodon!",
   "onboarding.follows.empty": "Desafortunadamente, no se pueden mostrar resultados en este momento. Puedes intentar usar la búsqueda o navegar por la página de exploración para encontrar gente a la que seguir, o inténtalo de nuevo más tarde.",
-  "onboarding.follows.search": "Buscar",
-  "onboarding.follows.title": "Sigue personas para comenzar",
+  "onboarding.follows.lead": "Tienes que personalizar tu inicio. Cuantas más personas sigas, más activo e interesante será. Estos perfiles pueden ser un buen punto de partida, ¡pero siempre puedes dejar de seguirlos más adelante!",
+  "onboarding.follows.title": "Popular en Mastodon",
   "onboarding.profile.discoverable": "Make my profile discoverable",
   "onboarding.profile.discoverable_hint": "Cuando aceptas ser descubierto en Mastodon, tus publicaciones pueden aparecer en resultados de búsqueda y tendencias, y tu perfil puede ser sugerido a personas con intereses similares a los tuyos.",
   "onboarding.profile.display_name": "Nombre a mostrar",
   "onboarding.profile.display_name_hint": "Tu nombre completo o tu apodo…",
+  "onboarding.profile.lead": "Siempre puedes completar esto más tarde en los ajustes, donde hay aún más opciones de personalización disponibles.",
   "onboarding.profile.note": "Biografía",
   "onboarding.profile.note_hint": "Puedes @mencionar a otras personas o #etiquetas…",
   "onboarding.profile.save_and_continue": "Guardar y continuar",
   "onboarding.profile.title": "Configuración del perfil",
   "onboarding.profile.upload_avatar": "Subir foto de perfil",
   "onboarding.profile.upload_header": "Subir foto de cabecera",
+  "onboarding.share.lead": "¡Dile a la gente cómo te pueden encontrar en Mastodon!",
+  "onboarding.share.message": "¡Soy {username} en #Mastodon! Ven a seguirme en {url}",
+  "onboarding.share.next_steps": "Posibles siguientes pasos:",
+  "onboarding.share.title": "Comparte tu perfil",
+  "onboarding.start.lead": "Tu nueva cuenta de Mastodon está lista. Así es como puedes sacarle el máximo provecho:",
+  "onboarding.start.skip": "¿Quieres saltarte todos los pasos?",
+  "onboarding.start.title": "¡Lo has logrado!",
+  "onboarding.steps.follow_people.body": "Tienes que personalizar tu inicio. Vamos a llenarlo de gente interesante.",
+  "onboarding.steps.follow_people.title": "Sigue a {count, plural, one {una persona} other {# personas}}",
+  "onboarding.steps.publish_status.body": "Dile hola al mundo.",
+  "onboarding.steps.publish_status.title": "Escribe tu primera publicación",
+  "onboarding.steps.setup_profile.body": "Si rellenas tu perfil tendrás más posibilidades de que otros interactúen contigo.",
+  "onboarding.steps.setup_profile.title": "Personaliza tu perfil",
+  "onboarding.steps.share_profile.body": "Dile a tus amigos cómo encontrarte en Mastodon",
+  "onboarding.steps.share_profile.title": "Comparte tu perfil",
+  "onboarding.tips.2fa": "<strong>¿Sabías que?</strong> Puedes proteger tu cuenta configurando la autenticación de dos factores en los ajustes de su cuenta. Funciona con cualquier aplicación TOTP que elijas, ¡sin necesidad de número de teléfono!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>¿Sabías que?</strong> Como Mastodon es descentralizado, algunos perfiles que encuentras están alojados en servidores distintos del tuyo. Y sin embargo, ¡puedes interactuar con ellos! ¡Su servidor corresponde a la segunda mitad de su nombre de usuario!",
+  "onboarding.tips.migration": "<strong>¿Sabías que?</strong> Si sientes que {domain} no es una gran elección de servidor para ti en el futuro, puedes moverte a otro servidor de Mastodon sin perder a tus seguidores. ¡Incluso puedes alojar tu propio servidor!",
+  "onboarding.tips.verification": "<strong>¿Sabías que?</strong> Puedes verificar tu cuenta poniendo un enlace a tu perfil de Mastodon en su propio sitio web y añadiendo el sitio web a su perfil. ¡Sin necesidad de comisiones ni documentos!",
   "password_confirmation.exceeds_maxlength": "La contraseña de confirmación excede la longitud máxima de la contraseña",
   "password_confirmation.mismatching": "La contraseña de confirmación no coincide",
   "picture_in_picture.restore": "Restaurar",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Eliminar encuesta",
   "privacy.change": "Ajustar privacidad",
   "privacy.direct.long": "Todos los mencionados en la publicación",
-  "privacy.direct.short": "Mención privada",
+  "privacy.direct.short": "Personas específicas",
   "privacy.private.long": "Sólo tus seguidores",
   "privacy.private.short": "Seguidores",
   "privacy.public.long": "Cualquiera dentro y fuera de Mastodon",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Política de Privacidad",
   "recommended": "Recomendado",
   "refresh": "Actualizar",
-  "regeneration_indicator.please_stand_by": "Espera, por favor.",
-  "regeneration_indicator.preparing_your_home_feed": "Preparando tu página de inicio…",
+  "regeneration_indicator.label": "Cargando…",
+  "regeneration_indicator.sublabel": "¡Tu historia de inicio se está preparando!",
   "relative_time.days": "{number} d",
   "relative_time.full.days": "{number, plural, one {# día} other {# días hace}}",
   "relative_time.full.hours": "{number, plural, one {# hora} other {# horas}} hace",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Perfiles",
   "search_results.all": "Todos",
   "search_results.hashtags": "Etiquetas",
-  "search_results.no_results": "No hay resultados.",
-  "search_results.no_search_yet": "Intenta buscar publicaciones, perfiles o etiquetas.",
+  "search_results.nothing_found": "No se pudo encontrar nada para estos términos de búsqueda",
   "search_results.see_all": "Ver todos",
   "search_results.statuses": "Publicaciones",
-  "search_results.title": "Busqueda de “{q}”",
+  "search_results.title": "Buscar {q}",
   "server_banner.about_active_users": "Personas utilizando este servidor durante los últimos 30 días (Usuarios Activos Mensuales)",
   "server_banner.active_users": "usuarios activos",
   "server_banner.administered_by": "Administrado por:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Nadie impulsó esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.",
   "status.redraft": "Borrar y volver a borrador",
   "status.remove_bookmark": "Eliminar marcador",
-  "status.remove_favourite": "Eliminar de favoritos",
   "status.replied_in_thread": "Respondido en el hilo",
   "status.replied_to": "Respondió a {name}",
   "status.reply": "Responder",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Cambiar idiomas suscritos para {target}",
   "tabs_bar.home": "Inicio",
   "tabs_bar.notifications": "Notificaciones",
-  "terms_of_service.effective_as_of": "En vigor a partir del {date}",
-  "terms_of_service.title": "Condiciones del servicio",
-  "terms_of_service.upcoming_changes_on": "Próximos cambios el {date}",
   "time_remaining.days": "{number, plural, one {# día restante} other {# días restantes}}",
   "time_remaining.hours": "{number, plural, one {# hora restante} other {# horas restantes}}",
   "time_remaining.minutes": "{number, plural, one {# minuto restante} other {# minutos restantes}}",
@@ -897,12 +853,26 @@
   "upload_button.label": "Subir multimedia (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Límite de subida de archivos excedido.",
   "upload_error.poll": "No se permite subir archivos con las encuestas.",
+  "upload_form.audio_description": "Describir para personas con problemas auditivos",
+  "upload_form.description": "Describir para los usuarios con dificultad visual",
   "upload_form.drag_and_drop.instructions": "Para recoger un archivo adjunto, pulsa la barra espaciadora o la tecla Intro. Mientras arrastras, usa las teclas de flecha para mover el archivo adjunto en cualquier dirección. Vuelve a pulsar la barra espaciadora o la tecla Intro para soltar el archivo adjunto en su nueva posición, o pulsa la tecla Escape para cancelar.",
   "upload_form.drag_and_drop.on_drag_cancel": "Arrastre cancelado. El archivo adjunto {item} fue eliminado.",
   "upload_form.drag_and_drop.on_drag_end": "El archivo adjunto {item} fue eliminado.",
   "upload_form.drag_and_drop.on_drag_over": "El archivo adjunto {item} fue movido.",
   "upload_form.drag_and_drop.on_drag_start": "Recogidos los archivos adjuntos {item}.",
   "upload_form.edit": "Editar",
+  "upload_form.thumbnail": "Cambiar miniatura",
+  "upload_form.video_description": "Describir para personas con problemas auditivos o visuales",
+  "upload_modal.analyzing_picture": "Analizando imagen…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.applying": "Aplicando…",
+  "upload_modal.choose_image": "Elegir imagen",
+  "upload_modal.description_placeholder": "Un rápido zorro marrón salta sobre el perro perezoso",
+  "upload_modal.detect_text": "Detectar texto de la imagen",
+  "upload_modal.edit_media": "Editar multimedia",
+  "upload_modal.hint": "Haga clic o arrastre el círculo en la vista previa para elegir el punto focal que siempre estará a la vista en todas las miniaturas.",
+  "upload_modal.preparing_ocr": "Preparando OCR…",
+  "upload_modal.preview_label": "Vista previa ({ratio})",
   "upload_progress.label": "Subiendo...",
   "upload_progress.processing": "Procesando…",
   "username.taken": "Ese nombre de usuario está ocupado. Prueba con otro",
@@ -912,12 +882,8 @@
   "video.expand": "Expandir vídeo",
   "video.fullscreen": "Pantalla completa",
   "video.hide": "Ocultar vídeo",
-  "video.mute": "Silenciar",
+  "video.mute": "Silenciar sonido",
   "video.pause": "Pausar",
   "video.play": "Reproducir",
-  "video.skip_backward": "Saltar atrás",
-  "video.skip_forward": "Saltar adelante",
-  "video.unmute": "Dejar de silenciar",
-  "video.volume_down": "Bajar el volumen",
-  "video.volume_up": "Subir el volumen"
+  "video.unmute": "Dejar de silenciar sonido"
 }
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index 4f9d91ce11..4391660397 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Editar perfil",
   "account.enable_notifications": "Notificarme cuando @{name} publique algo",
   "account.endorse": "Destacar en el perfil",
-  "account.featured": "Destacado",
-  "account.featured.hashtags": "Etiquetas",
-  "account.featured.posts": "Publicaciones",
   "account.featured_tags.last_status_at": "Última publicación el {date}",
   "account.featured_tags.last_status_never": "Sin publicaciones",
+  "account.featured_tags.title": "Etiquetas destacadas de {name}",
   "account.follow": "Seguir",
   "account.follow_back": "Seguir también",
   "account.followers": "Seguidores",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
   "account.unblock": "Desbloquear a @{name}",
   "account.unblock_domain": "Desbloquear dominio {domain}",
-  "account.unblock_domain_short": "Desbloquear",
   "account.unblock_short": "Desbloquear",
   "account.unendorse": "No mostrar en el perfil",
   "account.unfollow": "Dejar de seguir",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Hubo un error inesperado.",
   "alert.unexpected.title": "¡Ups!",
   "alt_text_badge.title": "Texto alternativo",
-  "alt_text_modal.add_alt_text": "Añadir texto alternativo",
-  "alt_text_modal.add_text_from_image": "Añadir texto a partir de la imagen",
-  "alt_text_modal.cancel": "Cancelar",
-  "alt_text_modal.change_thumbnail": "Cambiar miniatura",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Descríbelo para personas con deficiencias auditivas…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Descríbelo para personas con discapacidad visual…",
-  "alt_text_modal.done": "Hecho",
   "announcement.announcement": "Comunicación",
-  "annual_report.summary.archetype.booster": "El cazador de tendencias",
-  "annual_report.summary.archetype.lurker": "El acechador",
-  "annual_report.summary.archetype.oracle": "El oráculo",
-  "annual_report.summary.archetype.pollster": "El encuestador",
-  "annual_report.summary.archetype.replier": "El más sociable",
-  "annual_report.summary.followers.followers": "seguidores",
-  "annual_report.summary.followers.total": "{count} en total",
-  "annual_report.summary.here_it_is": "Aquí está tu resumen de {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "publicación con más favoritos",
-  "annual_report.summary.highlighted_post.by_reblogs": "publicación más impulsada",
-  "annual_report.summary.highlighted_post.by_replies": "publicación con más respuestas",
-  "annual_report.summary.highlighted_post.possessive": "de {name}",
-  "annual_report.summary.most_used_app.most_used_app": "aplicación más usada",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta más usada",
-  "annual_report.summary.most_used_hashtag.none": "Ninguna",
-  "annual_report.summary.new_posts.new_posts": "nuevas publicaciones",
-  "annual_report.summary.percentile.text": "<topLabel>Eso te coloca en el top</topLabel><percentage></percentage><bottomLabel>de usuarios de {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "No se lo diremos a Bernie.",
-  "annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!",
   "attachments_list.unprocessed": "(sin procesar)",
   "audio.hide": "Ocultar audio",
   "block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado, ya que algunos servidores pueden manejar bloqueos de forma distinta. Los mensajes públicos pueden ser todavía visibles para los usuarios que no hayan iniciado sesión.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "No se pudo encontrar la página solicitada. ¿Estás seguro de que la URL en la barra de direcciones es correcta?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Cerrar",
-  "bundle_modal_error.message": "Ha habido algún error mientras cargábamos esta pantalla.",
+  "bundle_modal_error.message": "Algo salió mal al cargar este componente.",
   "bundle_modal_error.retry": "Inténtalo de nuevo",
   "closed_registrations.other_server_instructions": "Como Mastodon es descentralizado, puedes crear una cuenta en otro servidor y seguir interactuando con este.",
   "closed_registrations_modal.description": "La creación de una cuenta en {domain} no es posible actualmente, pero ten en cuenta que no necesitas una cuenta específicamente en {domain} para usar Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Usuarios bloqueados",
   "column.bookmarks": "Marcadores",
   "column.community": "Cronología local",
-  "column.create_list": "Crear lista",
   "column.direct": "Menciones privadas",
   "column.directory": "Buscar perfiles",
   "column.domain_blocks": "Dominios bloqueados",
-  "column.edit_list": "Editar lista",
   "column.favourites": "Favoritos",
   "column.firehose": "Cronologías",
   "column.follow_requests": "Solicitudes de seguimiento",
   "column.home": "Inicio",
-  "column.list_members": "Administrar miembros de la lista",
   "column.lists": "Listas",
   "column.mutes": "Usuarios silenciados",
   "column.notifications": "Notificaciones",
@@ -172,7 +140,6 @@
   "column_header.pin": "Fijar",
   "column_header.show_settings": "Mostrar ajustes",
   "column_header.unpin": "Dejar de fijar",
-  "column_search.cancel": "Cancelar",
   "column_subheading.settings": "Ajustes",
   "community.column_settings.local_only": "Solo local",
   "community.column_settings.media_only": "Solo multimedia",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Duración de la encuesta",
   "compose_form.poll.multiple": "Selección múltiple",
   "compose_form.poll.option_placeholder": "Opción {number}",
-  "compose_form.poll.single": "Opción única",
+  "compose_form.poll.single": "Elige uno",
   "compose_form.poll.switch_to_multiple": "Modificar encuesta para permitir múltiples opciones",
   "compose_form.poll.switch_to_single": "Modificar encuesta para permitir una única opción",
   "compose_form.poll.type": "Estilo",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Editar",
   "confirmations.edit.message": "Editar ahora reemplazará el mensaje que estás escribiendo. ¿Seguro que quieres proceder?",
   "confirmations.edit.title": "¿Sobrescribir publicación?",
-  "confirmations.follow_to_list.confirm": "Seguir y añadir a la lista",
-  "confirmations.follow_to_list.message": "Necesitas seguir a {name} para agregarlo a una lista.",
-  "confirmations.follow_to_list.title": "¿Seguir usuario?",
   "confirmations.logout.confirm": "Cerrar sesión",
   "confirmations.logout.message": "¿Seguro que quieres cerrar la sesión?",
   "confirmations.logout.title": "¿Cerrar sesión?",
-  "confirmations.missing_alt_text.confirm": "Añadir texto alternativo",
-  "confirmations.missing_alt_text.message": "Tu publicación contiene medios sin texto alternativo. Añadir descripciones ayuda a que tu contenido sea accesible para más personas.",
-  "confirmations.missing_alt_text.secondary": "Publicar de todos modos",
-  "confirmations.missing_alt_text.title": "¿Deseas añadir texto alternativo?",
   "confirmations.mute.confirm": "Silenciar",
   "confirmations.redraft.confirm": "Borrar y volver a borrador",
   "confirmations.redraft.message": "¿Estás seguro de querer borrar esta publicación y reescribirla? Los favoritos e impulsos se perderán, y las respuestas a la publicación original quedarán sin contexto.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada.",
   "dismissable_banner.community_timeline": "Estas son las publicaciones públicas más recientes de personas cuyas cuentas están alojadas en {domain}.",
   "dismissable_banner.dismiss": "Descartar",
-  "dismissable_banner.explore_links": "Estas noticias son las más compartidas hoy en el fediverso. Las noticias más recientes publicadas por más personas diferentes se clasifican mejor.",
-  "dismissable_banner.explore_statuses": "Estas publicaciones del fediverso están ganando popularidad hoy. Las publicaciones más recientes, con más impulsos y favoritos, se clasifican mejor.",
-  "dismissable_banner.explore_tags": "Estas etiquetas están ganando popularidad hoy en el fediverso. Las etiquetas que son utilizadas por más personas diferentes se puntúan más alto.",
-  "dismissable_banner.public_timeline": "Estas son las publicaciones más recientes de las personas del fediverso a las que sigue la gente de {domain}.",
+  "dismissable_banner.explore_links": "Estas son las noticias que están siendo más compartidas hoy en la red. Nuevas noticias publicadas por diferentes personas se puntúan más alto.",
+  "dismissable_banner.explore_statuses": "Estas son las publicaciones que están ganando popularidad en la web social hoy. Las publicaciones recientes con más impulsos y favoritos obtienen más exposición.",
+  "dismissable_banner.explore_tags": "Estas son las etiquetas que están ganando popularidad hoy en la red. Etiquetas que se usan por personas diferentes se puntúan más alto.",
+  "dismissable_banner.public_timeline": "Estas son las publicaciones más recientes de personas en el Fediverso que siguen las personas de {domain}.",
   "domain_block_modal.block": "Bloquear servidor",
   "domain_block_modal.block_account_instead": "Bloquear @{name} en su lugar",
   "domain_block_modal.they_can_interact_with_old_posts": "Las personas de este servidor pueden interactuar con tus publicaciones antiguas.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Resultados de búsqueda",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viajes y lugares",
-  "empty_column.account_featured": "Esta lista está vacía",
   "empty_column.account_hides_collections": "Este usuario ha decidido no mostrar esta información",
   "empty_column.account_suspended": "Cuenta suspendida",
   "empty_column.account_timeline": "¡No hay publicaciones aquí!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "No hay nada en esta etiqueta todavía.",
   "empty_column.home": "¡Tu línea temporal está vacía! Sigue a más personas para rellenarla.",
   "empty_column.list": "Aún no hay nada en esta lista. Cuando los miembros de esta lista publiquen nuevos estados, estos aparecerán aquí.",
+  "empty_column.lists": "No tienes ninguna lista. Cuando crees una, se mostrará aquí.",
   "empty_column.mutes": "Aún no has silenciado a ningún usuario.",
   "empty_column.notification_requests": "¡Todo limpio! No hay nada aquí. Cuando recibas nuevas notificaciones, aparecerán aquí conforme a tu configuración.",
   "empty_column.notifications": "Aún no tienes ninguna notificación. Cuando otras personas interactúen contigo, aparecerán aquí.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Intenta deshabilitarlos y recarga la página. Si eso no ayuda, podrías usar Mastodon a través de un navegador web diferente o aplicación nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar el seguimiento de pila en el portapapeles",
   "errors.unexpected_crash.report_issue": "Informar de un problema/error",
+  "explore.search_results": "Resultados de búsqueda",
   "explore.suggested_follows": "Personas",
   "explore.title": "Explorar",
   "explore.trending_links": "Noticias",
@@ -373,16 +334,13 @@
   "footer.about": "Acerca de",
   "footer.directory": "Directorio de perfiles",
   "footer.get_app": "Obtener la aplicación",
+  "footer.invite": "Invitar personas",
   "footer.keyboard_shortcuts": "Atajos de teclado",
   "footer.privacy_policy": "Política de privacidad",
   "footer.source_code": "Ver código fuente",
   "footer.status": "Estado",
-  "footer.terms_of_service": "Términos del servicio",
   "generic.saved": "Guardado",
   "getting_started.heading": "Primeros pasos",
-  "hashtag.admin_moderation": "Abrir interfaz de moderación para #{name}",
-  "hashtag.browse": "Explorar publicaciones en #{hashtag}",
-  "hashtag.browse_from_account": "Explorar publicaciones desde @{name} en #{hashtag}",
   "hashtag.column_header.tag_mode.all": "y {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
   "hashtag.column_header.tag_mode.none": "sin {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}} hoy",
   "hashtag.follow": "Seguir etiqueta",
-  "hashtag.mute": "Silenciar #{hashtag}",
   "hashtag.unfollow": "Dejar de seguir etiqueta",
   "hashtags.and_other": "…y {count, plural, other {# más}}",
   "hints.profiles.followers_may_be_missing": "Puede que no se muestren todos los seguidores de este perfil.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "¿Ignorar notificaciones de personas que no te siguen?",
   "ignore_notifications_modal.not_following_title": "¿Ignorar notificaciones de personas a las que no sigues?",
   "ignore_notifications_modal.private_mentions_title": "¿Ignorar notificaciones de menciones privadas no solicitadas?",
-  "info_button.label": "Ayuda",
-  "info_button.what_is_alt_text": "<h1>¿Qué es el texto alternativo?</h1> <p>El texto alternativo proporciona descripciones de las imágenes para personas con problemas de visión, conexiones con poco ancho de banda o que buscan un contexto adicional.</p> <p>Puedes mejorar la accesibilidad y la comprensión para todos escribiendo un texto alternativo claro, conciso y objetivo.</p> <ul><li>Captura los elementos importantes.</li><li>Resume el texto en imágenes.</li><li>Usa una estructura de frases normal.</li><li>Evita la información redundante.</li><li>Céntrate en las tendencias y conclusiones clave de los elementos visuales complejos (como diagramas o mapas).</li></ul>",
-  "interaction_modal.action.favourite": "Para continuar, tienes que marcar como favorito desde tu cuenta.",
-  "interaction_modal.action.follow": "Para continuar, tienes que seguir desde tu cuenta.",
-  "interaction_modal.action.reblog": "Para continuar, tienes que impulsar desde tu cuenta.",
-  "interaction_modal.action.reply": "Para continuar, tienes que responder desde tu cuenta.",
-  "interaction_modal.action.vote": "Para continuar, tienes que votar desde tu cuenta.",
-  "interaction_modal.go": "Ir",
-  "interaction_modal.no_account_yet": "¿Todavía no tienes cuenta?",
+  "interaction_modal.description.favourite": "Con una cuenta en Mastodon, puedes marcar como favorita esta publicación para que el autor sepa que te gusta, y guardala para más adelante.",
+  "interaction_modal.description.follow": "Con una cuenta en Mastodon, puedes seguir {name} para recibir sus publicaciones en tu página de inicio.",
+  "interaction_modal.description.reblog": "Con una cuenta en Mastodon, puedes impulsar esta publicación para compartirla con tus propios seguidores.",
+  "interaction_modal.description.reply": "Con una cuenta en Mastodon, puedes responder a esta publicación.",
+  "interaction_modal.login.action": "Ir a Inicio",
+  "interaction_modal.login.prompt": "Dominio de tu servidor, por ejemplo mastodon.social",
+  "interaction_modal.no_account_yet": "¿Aún no tienes cuenta en Mastodon?",
   "interaction_modal.on_another_server": "En un servidor diferente",
   "interaction_modal.on_this_server": "En este servidor",
+  "interaction_modal.sign_in": "No estás registrado en este servidor. ¿Dónde tienes tu cuenta?",
+  "interaction_modal.sign_in_hint": "Pista: Ese es el sitio donde te registraste. Si no lo recuerdas, busca el correo electrónico de bienvenida en tu bandeja de entrada. También puedes introducir tu nombre de usuario completo (por ejemplo @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Marcar como favorita la publicación de {name}",
   "interaction_modal.title.follow": "Seguir a {name}",
   "interaction_modal.title.reblog": "Impulsar la publicación de {name}",
   "interaction_modal.title.reply": "Responder a la publicación de {name}",
-  "interaction_modal.title.vote": "Vota en la encuesta de {name}",
-  "interaction_modal.username_prompt": "Por ejemplo: {example}",
   "intervals.full.days": "{number, plural, one {# día} other {# días}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Mostrar/ocultar texto tras aviso de contenido (CW)",
   "keyboard_shortcuts.toggle_sensitivity": "Mostrar/ocultar multimedia",
   "keyboard_shortcuts.toot": "Comenzar una nueva publicación",
-  "keyboard_shortcuts.translate": "para traducir una publicación",
   "keyboard_shortcuts.unfocus": "Quitar el foco de la caja de redacción/búsqueda",
   "keyboard_shortcuts.up": "Moverse hacia arriba en la lista",
   "lightbox.close": "Cerrar",
@@ -490,32 +444,20 @@
   "link_preview.author": "Por {name}",
   "link_preview.more_from_author": "Más de {name}",
   "link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
-  "lists.add_member": "Añadir",
-  "lists.add_to_list": "Añadir a la lista",
-  "lists.add_to_lists": "Añadir {name} a las listas",
-  "lists.create": "Crear",
-  "lists.create_a_list_to_organize": "Crea una nueva lista para organizar tu página de inicio",
-  "lists.create_list": "Crear una lista",
+  "lists.account.add": "Añadir a lista",
+  "lists.account.remove": "Quitar de lista",
   "lists.delete": "Borrar lista",
-  "lists.done": "Hecho",
   "lists.edit": "Editar lista",
-  "lists.exclusive": "Ocultar miembros en Inicio",
-  "lists.exclusive_hint": "Si alguien está en esta lista, escóndelo en tu página de inicio para evitar ver sus publicaciones dos veces.",
-  "lists.find_users_to_add": "Buscar usuarios para añadir",
-  "lists.list_members": "Miembros de la lista",
-  "lists.list_members_count": "{count, plural,one {# miembro} other {# miembros}}",
-  "lists.list_name": "Nombre de la lista",
-  "lists.new_list_name": "Nombre de la nueva lista",
-  "lists.no_lists_yet": "Aún no hay listas.",
-  "lists.no_members_yet": "Aún no hay miembros.",
-  "lists.no_results_found": "No se encontraron resultados.",
-  "lists.remove_member": "Eliminar",
+  "lists.edit.submit": "Cambiar título",
+  "lists.exclusive": "Ocultar estas publicaciones de inicio",
+  "lists.new.create": "Añadir lista",
+  "lists.new.title_placeholder": "Título de la nueva lista",
   "lists.replies_policy.followed": "Cualquier usuario seguido",
   "lists.replies_policy.list": "Miembros de la lista",
   "lists.replies_policy.none": "Nadie",
-  "lists.save": "Guardar",
-  "lists.search": "Buscar",
-  "lists.show_replies_to": "Incluir las respuestas de los miembros de la lista a",
+  "lists.replies_policy.title": "Mostrar respuestas a:",
+  "lists.search": "Buscar entre las personas a las que sigues",
+  "lists.subheading": "Tus listas",
   "load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
   "loading_indicator.label": "Cargando…",
   "media_gallery.hide": "Ocultar",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} informó de {target}",
   "notification.admin.sign_up": "{name} se registró",
   "notification.admin.sign_up.name_and_others": "{name} y {count, plural, one {# más} other {# más}} se registraron",
-  "notification.annual_report.message": "¡Tu #Wrapstodon {year} te espera! ¡Desvela los momentos más destacados y memorables de tu año en Mastodon!",
-  "notification.annual_report.view": "Ver #Wrapstodon",
   "notification.favourite": "{name} marcó como favorita tu publicación",
   "notification.favourite.name_and_others_with_link": "{name} y <a>{count, plural, one {# más} other {# más}}</a> marcaron tu publicación como favorita",
-  "notification.favourite_pm": "{name} ha marcado como favorita tu mención privada",
-  "notification.favourite_pm.name_and_others_with_link": "{name} y <a>{count, plural, one {# más} other {# más}}</a> han marcado como favorita tu mención privada",
   "notification.follow": "{name} te empezó a seguir",
   "notification.follow.name_and_others": "{name} y <a>{count, plural, one {# otro} other {# otros}}</a> te siguieron",
   "notification.follow_request": "{name} ha solicitado seguirte",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Habilitar notificaciones de escritorio",
   "notifications_permission_banner.how_to_control": "Para recibir notificaciones cuando Mastodon no esté abierto, habilite las notificaciones de escritorio. Puedes controlar con precisión qué tipos de interacciones generan notificaciones de escritorio a través del botón {icon} de arriba una vez que estén habilitadas.",
   "notifications_permission_banner.title": "Nunca te pierdas nada",
-  "onboarding.follows.back": "Atrás",
-  "onboarding.follows.done": "Hecho",
+  "onboarding.action.back": "Llévame atrás",
+  "onboarding.actions.back": "Llévame atrás",
+  "onboarding.actions.go_to_explore": "Llévame a tendencias",
+  "onboarding.actions.go_to_home": "Ir a mi página de inicio",
+  "onboarding.compose.template": "¡Hola #Mastodon!",
   "onboarding.follows.empty": "Desafortunadamente, no se pueden mostrar resultados en este momento. Puedes intentar usar la búsqueda o navegar por la página de exploración para encontrar personas a las que seguir, o inténtalo de nuevo más tarde.",
-  "onboarding.follows.search": "Buscar",
-  "onboarding.follows.title": "Sigue personas para comenzar",
+  "onboarding.follows.lead": "Tu página de inicio es la forma principal de experimentar Mastodon. Cuanta más personas sigas, más activa e interesante será. Para empezar, aquí hay algunas sugerencias:",
+  "onboarding.follows.title": "Personaliza tu página de inicio",
   "onboarding.profile.discoverable": "Hacer que mi perfil aparezca en búsquedas",
   "onboarding.profile.discoverable_hint": "Cuando permites que tu perfil aparezca en búsquedas en Mastodon, tus publicaciones podrán aparecer en los resultados de búsqueda y en tendencias, y tu perfil podrá recomendarse a gente con intereses similares a los tuyos.",
   "onboarding.profile.display_name": "Nombre para mostrar",
   "onboarding.profile.display_name_hint": "Tu nombre completo o tu apodo…",
+  "onboarding.profile.lead": "Siempre puedes completar esto más tarde en los ajustes, donde hay aún más opciones de personalización disponibles.",
   "onboarding.profile.note": "Biografía",
   "onboarding.profile.note_hint": "Puedes @mencionar a otras personas o #etiquetas…",
   "onboarding.profile.save_and_continue": "Guardar y continuar",
   "onboarding.profile.title": "Configuración del perfil",
   "onboarding.profile.upload_avatar": "Subir foto de perfil",
   "onboarding.profile.upload_header": "Subir encabezado de perfil",
+  "onboarding.share.lead": "¡Cuéntale a otras personas cómo te pueden encontrar en Mastodon!",
+  "onboarding.share.message": "¡Soy {username} en #Mastodon! Ven a seguirme en {url}",
+  "onboarding.share.next_steps": "Posibles siguientes pasos:",
+  "onboarding.share.title": "Comparte tu perfil",
+  "onboarding.start.lead": "Ahora eres parte de Mastodon, una plataforma única y descentralizada de redes sociales donde tú —no un algoritmo— personalizarás tu propia experiencia. Vamos a introducirte en esta nueva frontera social:",
+  "onboarding.start.skip": "¿No necesitas ayuda para empezar?",
+  "onboarding.start.title": "¡Lo has logrado!",
+  "onboarding.steps.follow_people.body": "Seguir personas interesante es de lo que trata Mastodon.",
+  "onboarding.steps.follow_people.title": "Personaliza tu página de inicio",
+  "onboarding.steps.publish_status.body": "Di hola al mundo con texto, fotos, vídeos o encuestas {emoji}",
+  "onboarding.steps.publish_status.title": "Escribe tu primera publicación",
+  "onboarding.steps.setup_profile.body": "Aumenta tus interacciones con un perfil completo.",
+  "onboarding.steps.setup_profile.title": "Personaliza tu perfil",
+  "onboarding.steps.share_profile.body": "¡Dile a tus amigos cómo encontrarte en Mastodon!",
+  "onboarding.steps.share_profile.title": "Comparte tu perfil de Mastodon",
+  "onboarding.tips.2fa": "<strong>¿Sabías?</strong> Puedes proteger tu cuenta configurando la autenticación de dos factores en la configuración de tu cuenta. Funciona con cualquier aplicación TOTP de tu elección, ¡no necesitas número de teléfono!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>¿Sabías que?</strong> Como Mastodon es descentralizado, algunos perfiles que encuentras están alojados en servidores distintos del tuyo. Y sin embargo, ¡puedes interactuar con ellos! ¡Su servidor corresponde a la segunda mitad de su nombre de usuario!",
+  "onboarding.tips.migration": "<strong>¿Sabías?</strong> Si en el futuro piensas que {domain} no es el servidor adecuado para ti, puedes moverte a otro servidor de Mastodon sin perder a tus seguidores. ¡Incluso puedes alojar tu propio servidor!",
+  "onboarding.tips.verification": "<strong>¿Sabías?</strong> Puedes verificar tu cuenta poniendo un enlace a tu perfil de Mastodon en tu propio sitio web y añadiendo el sitio web a tu perfil. ¡No se necesitan comisiones ni documentos!",
   "password_confirmation.exceeds_maxlength": "La contraseña de confirmación excede la longitud máxima de la contraseña",
   "password_confirmation.mismatching": "La contraseña de confirmación no coincide",
   "picture_in_picture.restore": "Restaurar",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Eliminar encuesta",
   "privacy.change": "Ajustar privacidad",
   "privacy.direct.long": "Visible únicamente por los mencionados en la publicación",
-  "privacy.direct.short": "Mención privada",
+  "privacy.direct.short": "Personas específicas",
   "privacy.private.long": "Visible únicamente por tus seguidores",
   "privacy.private.short": "Seguidores",
   "privacy.public.long": "Visible por todo el mundo, dentro y fuera de Mastodon",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Política de Privacidad",
   "recommended": "Recomendado",
   "refresh": "Actualizar",
-  "regeneration_indicator.please_stand_by": "Espera, por favor.",
-  "regeneration_indicator.preparing_your_home_feed": "Preparando tu página de inicio…",
+  "regeneration_indicator.label": "Cargando…",
+  "regeneration_indicator.sublabel": "¡Tu página de inicio se está preparando!",
   "relative_time.days": "{number} d",
   "relative_time.full.days": "hace {number, plural, one {# día} other {# días}}",
   "relative_time.full.hours": "hace {number, plural, one {# hora} other {# horas}}",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Perfiles",
   "search_results.all": "Todos",
   "search_results.hashtags": "Etiquetas",
-  "search_results.no_results": "Sin resultados.",
-  "search_results.no_search_yet": "Intenta buscar publicaciones, perfiles o etiquetas.",
+  "search_results.nothing_found": "No se pudo encontrar nada para estos términos de búsqueda",
   "search_results.see_all": "Ver todos",
   "search_results.statuses": "Publicaciones",
-  "search_results.title": "Búsqueda de \"{q}\"",
+  "search_results.title": "Buscar {q}",
   "server_banner.about_active_users": "Usuarios activos en el servidor durante los últimos 30 días (Usuarios Activos Mensuales)",
   "server_banner.active_users": "usuarios activos",
   "server_banner.administered_by": "Administrado por:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Nadie ha impulsado esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.",
   "status.redraft": "Borrar y volver a borrador",
   "status.remove_bookmark": "Eliminar marcador",
-  "status.remove_favourite": "Eliminar de favoritos",
   "status.replied_in_thread": "Respondió en el hilo",
   "status.replied_to": "Respondió a {name}",
   "status.reply": "Responder",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Cambiar idiomas suscritos para {target}",
   "tabs_bar.home": "Inicio",
   "tabs_bar.notifications": "Notificaciones",
-  "terms_of_service.effective_as_of": "En vigor a partir del {date}",
-  "terms_of_service.title": "Términos del servicio",
-  "terms_of_service.upcoming_changes_on": "Próximos cambios el {date}",
   "time_remaining.days": "{number, plural, one {# día restante} other {# días restantes}}",
   "time_remaining.hours": "{number, plural, one {# hora restante} other {# horas restantes}}",
   "time_remaining.minutes": "{number, plural, one {# minuto restante} other {# minutos restantes}}",
@@ -897,12 +853,26 @@
   "upload_button.label": "Añadir imágenes, un fichero de vídeo o de audio",
   "upload_error.limit": "Límite de subida de archivos excedido.",
   "upload_error.poll": "No se permite la subida de archivos con encuestas.",
+  "upload_form.audio_description": "Describir para personas con problemas auditivos",
+  "upload_form.description": "Describir para personas con discapacidad visual",
   "upload_form.drag_and_drop.instructions": "Para recoger un archivo multimedia, pulsa la barra espaciadora o la tecla Enter. Mientras arrastras, utiliza las teclas de flecha para mover el archivo multimedia en cualquier dirección. Vuelve a pulsar la barra espaciadora o la tecla Enter para soltar el archivo multimedia en su nueva posición, o pulsa Escape para cancelar.",
   "upload_form.drag_and_drop.on_drag_cancel": "Se canceló el arrastre. Se eliminó el archivo adjunto {item}.",
   "upload_form.drag_and_drop.on_drag_end": "El archivo adjunto {item} ha sido eliminado.",
   "upload_form.drag_and_drop.on_drag_over": "El archivo adjunto {item} se ha movido.",
   "upload_form.drag_and_drop.on_drag_start": "Se ha recogido el archivo adjunto {item}.",
   "upload_form.edit": "Editar",
+  "upload_form.thumbnail": "Cambiar miniatura",
+  "upload_form.video_description": "Describir para personas con problemas auditivos o visuales",
+  "upload_modal.analyzing_picture": "Analizando imagen…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.applying": "Aplicando…",
+  "upload_modal.choose_image": "Elegir imagen",
+  "upload_modal.description_placeholder": "Un rápido zorro marrón salta sobre el perro perezoso",
+  "upload_modal.detect_text": "Detectar texto de la imagen",
+  "upload_modal.edit_media": "Editar multimedia",
+  "upload_modal.hint": "Haga clic o arrastre el círculo en la vista previa para elegir el punto focal que siempre estará a la vista en todas las miniaturas.",
+  "upload_modal.preparing_ocr": "Preparando OCR…",
+  "upload_modal.preview_label": "Vista previa ({ratio})",
   "upload_progress.label": "Subiendo...",
   "upload_progress.processing": "Procesando…",
   "username.taken": "Ese nombre de usuario ya está en uso. Prueba con otro",
@@ -912,12 +882,8 @@
   "video.expand": "Expandir vídeo",
   "video.fullscreen": "Pantalla completa",
   "video.hide": "Ocultar vídeo",
-  "video.mute": "Silenciar",
+  "video.mute": "Silenciar sonido",
   "video.pause": "Pausar",
   "video.play": "Reproducir",
-  "video.skip_backward": "Saltar atrás",
-  "video.skip_forward": "Adelantar",
-  "video.unmute": "Dejar de silenciar",
-  "video.volume_down": "Bajar volumen",
-  "video.volume_up": "Subir volumen"
+  "video.unmute": "Desilenciar sonido"
 }
diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json
index 3e0610126e..20f1e6d79f 100644
--- a/app/javascript/mastodon/locales/et.json
+++ b/app/javascript/mastodon/locales/et.json
@@ -29,6 +29,7 @@
   "account.endorse": "Too profiilil esile",
   "account.featured_tags.last_status_at": "Viimane postitus {date}",
   "account.featured_tags.last_status_never": "Postitusi pole",
+  "account.featured_tags.title": "{name} esiletõstetud sildid",
   "account.follow": "Jälgi",
   "account.follow_back": "Jälgi vastu",
   "account.followers": "Jälgijad",
@@ -85,33 +86,7 @@
   "alert.unexpected.message": "Tekkis ootamatu viga.",
   "alert.unexpected.title": "Oih!",
   "alt_text_badge.title": "Alternatiivtekst",
-  "alt_text_modal.add_alt_text": "Lisa alt-tekst",
-  "alt_text_modal.add_text_from_image": "Lisa tekst pildilt",
-  "alt_text_modal.cancel": "Tühista",
-  "alt_text_modal.change_thumbnail": "Muuda pisipilti",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Kirjelda seda kuulmispuudega inimeste jaoks…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Kirjelda seda nägemispuudega inimeste jaoks…",
-  "alt_text_modal.done": "Valmis",
   "announcement.announcement": "Teadaanne",
-  "annual_report.summary.archetype.booster": "Ägesisu küttija",
-  "annual_report.summary.archetype.lurker": "Hiilija",
-  "annual_report.summary.archetype.oracle": "Oraakel",
-  "annual_report.summary.archetype.pollster": "Küsitleja",
-  "annual_report.summary.archetype.replier": "Sotsiaalne liblikas",
-  "annual_report.summary.followers.followers": "jälgijad",
-  "annual_report.summary.followers.total": "{count} kokku",
-  "annual_report.summary.here_it_is": "Siin on sinu {year} ülevaatlikult:",
-  "annual_report.summary.highlighted_post.by_favourites": "enim lemmikuks märgitud postitus",
-  "annual_report.summary.highlighted_post.by_reblogs": "enim jagatud postitus",
-  "annual_report.summary.highlighted_post.by_replies": "kõige vastatum postitus",
-  "annual_report.summary.highlighted_post.possessive": "omanik {name}",
-  "annual_report.summary.most_used_app.most_used_app": "enim kasutatud äpp",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "enim kasutatud silt",
-  "annual_report.summary.most_used_hashtag.none": "Pole",
-  "annual_report.summary.new_posts.new_posts": "uus postitus",
-  "annual_report.summary.percentile.text": "<topLabel>See paneb su top</topLabel><percentage></percentage><bottomLabel> {domain} kasutajate hulka.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Vägev.",
-  "annual_report.summary.thanks": "Tänud olemast osa Mastodonist!",
   "attachments_list.unprocessed": "(töötlemata)",
   "audio.hide": "Peida audio",
   "block_modal.remote_users_caveat": "Serverile {domain} edastatakse palve otsust järgida. Ometi pole see tagatud, kuna mõned serverid võivad blokeeringuid käsitleda omal moel. Avalikud postitused võivad tuvastamata kasutajatele endiselt näha olla.",
@@ -135,7 +110,7 @@
   "bundle_column_error.routing.body": "Päritud lehte ei leitud. Kas URL on aadressiribal õige?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Sulge",
-  "bundle_modal_error.message": "Selle ekraanitäie laadimisel läks midagi valesti.",
+  "bundle_modal_error.message": "Selle komponendi laadimisel läks midagi viltu.",
   "bundle_modal_error.retry": "Proovi uuesti",
   "closed_registrations.other_server_instructions": "Kuna Mastodon on detsentraliseeritud, võib konto teha teise serverisse ja sellegipoolest siinse kontoga suhelda.",
   "closed_registrations_modal.description": "Praegu ei ole võimalik teha {domain} peale kontot, aga pea meeles, et sul ei pea olema just {domain} konto, et Mastodoni kasutada.",
@@ -146,16 +121,13 @@
   "column.blocks": "Blokeeritud kasutajad",
   "column.bookmarks": "Järjehoidjad",
   "column.community": "Kohalik ajajoon",
-  "column.create_list": "Loo loend",
   "column.direct": "Privaatsed mainimised",
   "column.directory": "Sirvi profiile",
   "column.domain_blocks": "Peidetud domeenid",
-  "column.edit_list": "Muuda loendit",
   "column.favourites": "Lemmikud",
   "column.firehose": "Laiv lõimed",
   "column.follow_requests": "Jälgimistaotlused",
   "column.home": "Kodu",
-  "column.list_members": "Halda loendi liikmeid",
   "column.lists": "Loetelud",
   "column.mutes": "Vaigistatud kasutajad",
   "column.notifications": "Teated",
@@ -168,7 +140,6 @@
   "column_header.pin": "Kinnita",
   "column_header.show_settings": "Näita sätteid",
   "column_header.unpin": "Eemalda kinnitus",
-  "column_search.cancel": "Tühista",
   "column_subheading.settings": "Sätted",
   "community.column_settings.local_only": "Ainult kohalik",
   "community.column_settings.media_only": "Ainult meedia",
@@ -187,7 +158,7 @@
   "compose_form.poll.duration": "Küsitluse kestus",
   "compose_form.poll.multiple": "Mitu vastust",
   "compose_form.poll.option_placeholder": "Valik {number}",
-  "compose_form.poll.single": "Üks valik",
+  "compose_form.poll.single": "Vali üks",
   "compose_form.poll.switch_to_multiple": "Muuda küsitlust mitmikvaliku lubamiseks",
   "compose_form.poll.switch_to_single": "Muuda küsitlust ainult ühe valiku lubamiseks",
   "compose_form.poll.type": "Stiil",
@@ -211,16 +182,9 @@
   "confirmations.edit.confirm": "Muuda",
   "confirmations.edit.message": "Muutes praegu kirjutatakse hetkel loodav sõnum üle. Kas oled kindel, et soovid jätkata?",
   "confirmations.edit.title": "Kirjutada postitus üle?",
-  "confirmations.follow_to_list.confirm": "Jälgi ja lisa loetellu",
-  "confirmations.follow_to_list.message": "Pead jälgima kasutajat {name}, et lisada teda loetellu.",
-  "confirmations.follow_to_list.title": "Jälgida kasutajat?",
   "confirmations.logout.confirm": "Välju",
   "confirmations.logout.message": "Kas oled kindel, et soovid välja logida?",
   "confirmations.logout.title": "Logida välja?",
-  "confirmations.missing_alt_text.confirm": "Lisa alt-tekst",
-  "confirmations.missing_alt_text.message": "Sinu postituses on ilma alt-tekstita meediat. Kirjelduse lisamine aitab su sisu muuta ligipääsetavaks rohkematele inimestele.",
-  "confirmations.missing_alt_text.secondary": "Postita siiski",
-  "confirmations.missing_alt_text.title": "Lisada alt-tekst?",
   "confirmations.mute.confirm": "Vaigista",
   "confirmations.redraft.confirm": "Kustuta & taasalusta",
   "confirmations.redraft.message": "Kindel, et soovid postituse kustutada ja võtta uue aluseks? Lemmikuks märkimised ja jagamised lähevad kaotsi ning vastused jäävad ilma algse postituseta.",
@@ -249,10 +213,10 @@
   "disabled_account_banner.text": "Su konto {disabledAccount} on hetkel keelatud.",
   "dismissable_banner.community_timeline": "Need on kõige viimased avalikud postitused inimestelt, kelle kontosid majutab {domain}.",
   "dismissable_banner.dismiss": "Sulge",
-  "dismissable_banner.explore_links": "Neid uudislugusid jagatakse praegu fediversiumis kõige rohkem. mitme erineva kasutaja postitatud postitused on paigutatud kõrgemale.",
-  "dismissable_banner.explore_statuses": "Need postitused üle kogu fediversiumi koguvad praegu tähelepanu. Uuemad postitused, mida on rohkem jagatud ja lemmikuks märgitud, on paigutatud kõrgemale.",
-  "dismissable_banner.explore_tags": "Need sildid koguvad praegu fediversiumis tähelepanu. Sildid, mida kasutavad rohkemad inimesed, on paigutatud kõrgemale.",
-  "dismissable_banner.public_timeline": "Need on värskeimad avalikud postitused inimestelt fediversiumis, mida domeeni {domain} inimesed jälgivad.",
+  "dismissable_banner.explore_links": "Need on uudised, millest inimesed siin ja teistes serverites üle detsentraliseeritud võrgu praegu räägivad.",
+  "dismissable_banner.explore_statuses": "Need postitused üle sotsiaalse võrgu koguvad praegu tähelepanu. Uued postitused, millel on rohkem jagamisi ja lemmikuks märkimisi, on kõrgemal kohal.",
+  "dismissable_banner.explore_tags": "Need sildid siit ja teistes serveritest detsentraliseeritud võrgus koguvad tähelepanu just praegu selles serveris.",
+  "dismissable_banner.public_timeline": "Need on kõige uuemad avalikud postitused inimestelt sotsiaalvõrgustikus, mida {domain} inimesed jälgivad.",
   "domain_block_modal.block": "Blokeeri server",
   "domain_block_modal.block_account_instead": "Selle asemel blokeeri @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Inimesed sellest serverist saavad interakteeruda sinu vanade postitustega.",
@@ -309,6 +273,7 @@
   "empty_column.hashtag": "Selle sildi all ei ole ühtegi postitust.",
   "empty_column.home": "Su koduajajoon on tühi. Jälgi rohkemaid inimesi, et seda täita {suggestions}",
   "empty_column.list": "Siin loetelus pole veel midagi. Kui loetelu liikmed teevad uusi postitusi, näed neid siin.",
+  "empty_column.lists": "Pole veel ühtegi loetelu. Kui lood mõne, näed neid siin.",
   "empty_column.mutes": "Sa pole veel ühtegi kasutajat vaigistanud.",
   "empty_column.notification_requests": "Kõik tühi! Siin pole mitte midagi. Kui saad uusi teavitusi, ilmuvad need siin vastavalt sinu seadistustele.",
   "empty_column.notifications": "Ei ole veel teateid. Kui keegi suhtleb sinuga, näed seda siin.",
@@ -319,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Proovi need välja lülitada ja leht uuesti laadida. Kui sellest pole abi, võib siiski võimalik olla Mastodoni kasutada mõne teise lehitseja või rakendusega.",
   "errors.unexpected_crash.copy_stacktrace": "Kopeeri stacktrace lõikelauale",
   "errors.unexpected_crash.report_issue": "Teavita veast",
+  "explore.search_results": "Otsitulemused",
   "explore.suggested_follows": "Inimesed",
   "explore.title": "Avasta",
   "explore.trending_links": "Uudised",
@@ -368,14 +334,13 @@
   "footer.about": "Teave",
   "footer.directory": "Profiilikataloog",
   "footer.get_app": "Tõmba äpp",
+  "footer.invite": "Kutsu liituma",
   "footer.keyboard_shortcuts": "Kiirklahvid",
   "footer.privacy_policy": "Isikuandmete kaitse",
   "footer.source_code": "Lähtekood",
   "footer.status": "Olek",
-  "footer.terms_of_service": "Kasutustingimused",
   "generic.saved": "Salvestatud",
   "getting_started.heading": "Alustamine",
-  "hashtag.admin_moderation": "Ava modereerimisliides #{name} jaoks",
   "hashtag.column_header.tag_mode.all": "ja {additional}",
   "hashtag.column_header.tag_mode.any": "või {additional}",
   "hashtag.column_header.tag_mode.none": "ilma {additional}",
@@ -417,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignoreeri inimeste teavitusi, kes sind ei jälgi?",
   "ignore_notifications_modal.not_following_title": "Ignoreeri inimeste teavitusi, keda sa ei jälgi?",
   "ignore_notifications_modal.private_mentions_title": "Ignoreeri soovimatute eraviisiliste mainimiste teateid?",
-  "info_button.label": "Abi",
-  "info_button.what_is_alt_text": "<h1>Mis on alt-tekst?</h1> <p>Alt-tekst pakub pildi kirjeldust nägemispuudega inimeste jaoks või neile, kel on aeglane internet või neile, kes otsivad lisaselgitust</p> <p>Saad parandada ligipääsetavust ja mõistmist kõigi jaoks, kirjutades selge, lühida ja objektiivse alt-teksti.</p> <ul> <li>Lisa tähtsad elemendid</li> <li>Tee pildil olevast tekstist kokkuvõte</li> <li>Kasuta reeglipärast lausestruktuuri</li> <li>Väldi ebaolulist infot</li> <li>Keskendu keerukate vaadete puhul (näiteks diagrammid ja kaardid) puhul trendidele ja põhiseostele</li> </ul>",
-  "interaction_modal.action.favourite": "Jätkamiseks pead oma konto alt lemmikuks märkima.",
-  "interaction_modal.action.follow": "Jätkamiseks pead oma konto alt lemmikuks märkima.",
-  "interaction_modal.action.reblog": "Jätkamiseks pead jagama oma konto alt.",
-  "interaction_modal.action.reply": "Jätkamiseks pead vastama oma konto alt.",
-  "interaction_modal.action.vote": "Jätkamiseks pead hääletama oma konto alt.",
-  "interaction_modal.go": "Mine",
-  "interaction_modal.no_account_yet": "Pole veel kontot?",
+  "interaction_modal.description.favourite": "Mastodoni kontoga saad postituse lemmikuks märkida, et autor teaks, et sa hindad seda, ja jätta see hiljemaks alles.",
+  "interaction_modal.description.follow": "Mastodoni kontoga saad jälgida kasutajat {name}, et tema postitusi oma koduvoos näha.",
+  "interaction_modal.description.reblog": "Mastodoni kontoga saad seda postitust levitada, jagades seda oma jälgijatele.",
+  "interaction_modal.description.reply": "Mastodoni kontoga saad sellele postitusele vastata.",
+  "interaction_modal.login.action": "Vii mind avalehele",
+  "interaction_modal.login.prompt": "Sinu koduserveri domeen, näiteks mastodon.social",
+  "interaction_modal.no_account_yet": "Pole Mastodonis?",
   "interaction_modal.on_another_server": "Teises serveris",
   "interaction_modal.on_this_server": "Selles serveris",
+  "interaction_modal.sign_in": "Sa pole sellesse serverisse sisse logitud. Kus sinu konto asub?",
+  "interaction_modal.sign_in_hint": "Vihje: See on veebileht, millel sa registreerusid. Kui see ei meenu, otsi sisendkaustast tervitus-e-kirja. Võid sisestada ka oma täispika kasutajanime! (Näit. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Lisa konto {name} postitus lemmikuks",
   "interaction_modal.title.follow": "Jälgi kontot {name}",
   "interaction_modal.title.reblog": "Jaga {name} postitust",
   "interaction_modal.title.reply": "Vasta kasutaja {name} postitusele",
-  "interaction_modal.title.vote": "Hääleta {name} küsitluses",
-  "interaction_modal.username_prompt": "Nt {example}",
   "intervals.full.days": "{number, plural, one {# päev} other {# päeva}}",
   "intervals.full.hours": "{number, plural, one {# tund} other {# tundi}}",
   "intervals.full.minutes": "{number, plural, one {# minut} other {# minutit}}",
@@ -469,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Näita/peida teksti hoiatuse taga",
   "keyboard_shortcuts.toggle_sensitivity": "Näita/peida meediat",
   "keyboard_shortcuts.toot": "Alusta uut postitust",
-  "keyboard_shortcuts.translate": "postituse tõlkimiseks",
   "keyboard_shortcuts.unfocus": "Fookus tekstialalt/otsingult ära",
   "keyboard_shortcuts.up": "Liigu loetelus üles",
   "lightbox.close": "Sulge",
@@ -482,32 +444,20 @@
   "link_preview.author": "{name} poolt",
   "link_preview.more_from_author": "Veel kasutajalt {name}",
   "link_preview.shares": "{count, plural, one {{counter} postitus} other {{counter} postitust}}",
-  "lists.add_member": "Lisa",
-  "lists.add_to_list": "Lisa loendisse",
-  "lists.add_to_lists": "Lisa {name} loetellu",
-  "lists.create": "Loo",
-  "lists.create_a_list_to_organize": "Loo uus loetelu, et organiseerida oma avalehe lõime",
-  "lists.create_list": "Loo loetelu",
+  "lists.account.add": "Lisa loetellu",
+  "lists.account.remove": "Eemalda loetelust",
   "lists.delete": "Kustuta loetelu",
-  "lists.done": "Valmis",
   "lists.edit": "Muuda loetelu",
-  "lists.exclusive": "Peida avalehelt liikmed",
-  "lists.exclusive_hint": "Kui keegi on selles loetelus, peida ta avalehe lõimest, vältimaks tema postituste kaks korda nägemist.",
-  "lists.find_users_to_add": "Leia kasutajaid, keda lisada",
-  "lists.list_members": "Loetelu liikmed",
-  "lists.list_members_count": "{count, plural, one {# liige} other {# liiget}}",
-  "lists.list_name": "Loetelu nimi",
-  "lists.new_list_name": "Loetelu uus nimi",
-  "lists.no_lists_yet": "Pole veel loetelusid.",
-  "lists.no_members_yet": "Pole veel liikmeid.",
-  "lists.no_results_found": "Ei leidnud tulemusi.",
-  "lists.remove_member": "Eemalda",
+  "lists.edit.submit": "Pealkirja muutmine",
+  "lists.exclusive": "Peida koduvaatest need postitused",
+  "lists.new.create": "Lisa loetelu",
+  "lists.new.title_placeholder": "Uue loetelu pealkiri",
   "lists.replies_policy.followed": "Igalt jälgitud kasutajalt",
   "lists.replies_policy.list": "Loetelu liikmetelt",
   "lists.replies_policy.none": "Mitte kelleltki",
-  "lists.save": "Salvesta",
-  "lists.search": "Otsi",
-  "lists.show_replies_to": "Kaasa ka loetelu liikmete vastused",
+  "lists.replies_policy.title": "Näita vastuseid nendele:",
+  "lists.search": "Otsi enda jälgitavate inimeste hulgast",
+  "lists.subheading": "Sinu loetelud",
   "load_pending": "{count, plural, one {# uus kirje} other {# uut kirjet}}",
   "loading_indicator.label": "Laadimine…",
   "media_gallery.hide": "Peida",
@@ -556,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} raporteeris kohast {target}",
   "notification.admin.sign_up": "{name} registreerus",
   "notification.admin.sign_up.name_and_others": "{name} ja {count, plural, one {# veel} other {# teist}} liitus",
-  "notification.annual_report.message": "Sinu {year} #Wrapstodon ootab! Avalda oma aasta tipphetked ja meeldejäävad hetked Mastodonis!",
-  "notification.annual_report.view": "Vaata #Wrapstodon",
   "notification.favourite": "{name} märkis su postituse lemmikuks",
   "notification.favourite.name_and_others_with_link": "{name} ja <a>{count, plural, one {# veel} other {# teist}}</a> märkis su postituse lemmikuks",
-  "notification.favourite_pm": "{name} märkis sinu privaatse mainimise lemmikuks",
-  "notification.favourite_pm.name_and_others_with_link": "{name} ja <a>{count, plural, one {# veel} other {# veel}}</a> märkisid su privaatse mainimise lemmikuks",
   "notification.follow": "{name} alustas su jälgimist",
   "notification.follow.name_and_others": "{name} ja veel {count, plural, one {# kasutaja} other {# kasutajat}} hakkas sind jälgima",
   "notification.follow_request": "{name} soovib sind jälgida",
@@ -666,21 +612,44 @@
   "notifications_permission_banner.enable": "Luba töölaua märguanded",
   "notifications_permission_banner.how_to_control": "Et saada teateid, ajal mil Mastodon pole avatud, luba töölauamärguanded. Saad täpselt määrata, mis tüüpi tegevused tekitavad märguandeid, kasutates peale teadaannete sisse lülitamist üleval olevat nuppu {icon}.",
   "notifications_permission_banner.title": "Ära jää millestki ilma",
-  "onboarding.follows.back": "Tagasi",
-  "onboarding.follows.done": "Valmis",
+  "onboarding.action.back": "Võta mind tagasi",
+  "onboarding.actions.back": "Võta mind tagasi",
+  "onboarding.actions.go_to_explore": "Vaata, mis on trendikas",
+  "onboarding.actions.go_to_home": "Mine oma koduvoogu",
+  "onboarding.compose.template": "Tere, #Mastodon!",
   "onboarding.follows.empty": "Kahjuks ei saa hetkel tulemusi näidata. Proovi kasutada otsingut või lehitse uurimise lehte, et leida inimesi, keda jälgida, või proovi hiljem uuesti.",
-  "onboarding.follows.search": "Otsi",
-  "onboarding.follows.title": "Jälgi inimesi, et alustada",
+  "onboarding.follows.lead": "Haldad ise oma koduvoogu. Mida rohkemaid inimesi jälgid, seda aktiivsem ja huvitavam see on. Need profiilid võiksid olla head alustamiskohad — saad nende jälgimise alati lõpetada!",
+  "onboarding.follows.title": "Isikupärasta oma koduvoogu",
   "onboarding.profile.discoverable": "Muuda mu profiil avastatavaks",
   "onboarding.profile.discoverable_hint": "Kui nõustud enda avastamisega Mastodonis, võivad sinu postitused ilmuda otsingutulemustes ja trendides ning sinu profiili võidakse soovitada sinuga sarnaste huvidega inimestele.",
   "onboarding.profile.display_name": "Näidatav nimi",
   "onboarding.profile.display_name_hint": "Su täisnimi või naljanimi…",
+  "onboarding.profile.lead": "Saad selle alati hiljem seadetes lõpuni viia, kus on saadaval veel rohkem kohandamisvalikuid.",
   "onboarding.profile.note": "Elulugu",
   "onboarding.profile.note_hint": "Saad @mainida teisi kasutajaid või #sildistada…",
   "onboarding.profile.save_and_continue": "Salvesta ja jätka",
   "onboarding.profile.title": "Profiili seadistamine",
   "onboarding.profile.upload_avatar": "Laadi üles profiilipilt",
   "onboarding.profile.upload_header": "Laadi üles profiili päis",
+  "onboarding.share.lead": "Anna inimestele teada, kuidas sind Mastodonist üles leida!",
+  "onboarding.share.message": "Ma olen #Mastodon võrgustikus {username}! tule ja jälgi mind aadressil {url}",
+  "onboarding.share.next_steps": "Võimalikud järgmised sammud:",
+  "onboarding.share.title": "Jaga oma profiili",
+  "onboarding.start.lead": "Su uus Mastodoni konto on valmis kasutamiseks. Siin info, kuidas sellest maksimum võtta:",
+  "onboarding.start.skip": "Soovid kohe edasi hüpata?",
+  "onboarding.start.title": "Said valmis!",
+  "onboarding.steps.follow_people.body": "Haldad oma koduvoogu. Täida see huvitavate inimestega.",
+  "onboarding.steps.follow_people.title": "Isikupärasta oma koduvoogu",
+  "onboarding.steps.publish_status.body": "Ütle maailmale tere.",
+  "onboarding.steps.publish_status.title": "Tee oma esimene postitus",
+  "onboarding.steps.setup_profile.body": "Täidetud profiili korral suhtlevad teised sinuga tõenäolisemalt.",
+  "onboarding.steps.setup_profile.title": "Isikupärasta oma profiili",
+  "onboarding.steps.share_profile.body": "Anna sõpradele teada, kuidas sind Mastodonist leida!",
+  "onboarding.steps.share_profile.title": "Jaga oma profiili",
+  "onboarding.tips.2fa": "<strong>Kas sa teadsid?</strong> Saad oma kontot muuta turvalisemaks valides konto seadetes kaheastmelise autoriseerimise. See töötab mistahes sinu valitud TOTP-äpiga, telefoninumbrit pole vaja!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Kas teadsid?</strong> Kuna Mastodon on detsentraliseeritud, kohtad profiile, mis paiknevad teises serveris kui sinu oma. Kuid ikka saad sa nendega ladusalt suhelda! Nende server on nende kasutajanime teises pooles!",
+  "onboarding.tips.migration": "<strong>Kas sa teadsid?</strong> Kui tunned, et {domain} ei ole tuleviku jaoks hea serveri valik, saad kolida teise Mastodoni serverisse, ilma oma jälgijaid kaotamata. Saad isegi käivitada oma serveri!",
+  "onboarding.tips.verification": "<strong>Kas sa teadsid?</strong> Saad verifitseerida oma Mastodoni konto pannes oma veebilehele tagasilingi Mastodoni profiilile ja lisades profiilile lingi oma veebilehele. Dokumente ega raha pole vaja!",
   "password_confirmation.exceeds_maxlength": "Salasõnakinnitus on pikem kui salasõna maksimumpikkus",
   "password_confirmation.mismatching": "Salasõnakinnitus ei sobi kokku",
   "picture_in_picture.restore": "Pane tagasi",
@@ -696,7 +665,7 @@
   "poll_button.remove_poll": "Eemalda küsitlus",
   "privacy.change": "Muuda postituse nähtavust",
   "privacy.direct.long": "Kõik postituses mainitud",
-  "privacy.direct.short": "Privaatne mainimine",
+  "privacy.direct.short": "Määratud kasutajad",
   "privacy.private.long": "Ainult jälgijad",
   "privacy.private.short": "Jälgijad",
   "privacy.public.long": "Nii kasutajad kui mittekasutajad",
@@ -708,6 +677,8 @@
   "privacy_policy.title": "Isikuandmete kaitse",
   "recommended": "Soovitatud",
   "refresh": "Värskenda",
+  "regeneration_indicator.label": "Laeb…",
+  "regeneration_indicator.sublabel": "Su koduvoog on ettevalmistamisel!",
   "relative_time.days": "{number}p",
   "relative_time.full.days": "{number, plural, one {# päev} other {# päeva}} tagasi",
   "relative_time.full.hours": "{number, plural, one {# tund} other {# tundi}} tagasi",
@@ -791,8 +762,10 @@
   "search_results.accounts": "Profiilid",
   "search_results.all": "Kõik",
   "search_results.hashtags": "Sildid",
+  "search_results.nothing_found": "Otsisõnadele vastavat sisu ei leitud",
   "search_results.see_all": "Vaata kõiki",
   "search_results.statuses": "Postitused",
+  "search_results.title": "{q} otsing",
   "server_banner.about_active_users": "Inimesed, kes kasutavad seda serverit viimase 30 päeva jooksul (kuu aktiivsed kasutajad)",
   "server_banner.active_users": "aktiivsed kasutajad",
   "server_banner.administered_by": "Administraator:",
@@ -865,7 +838,6 @@
   "subscribed_languages.target": "Muuda tellitud keeli {target} jaoks",
   "tabs_bar.home": "Kodu",
   "tabs_bar.notifications": "Teated",
-  "terms_of_service.title": "Teenuse tingimused",
   "time_remaining.days": "{number, plural, one {# päev} other {# päeva}} jäänud",
   "time_remaining.hours": "{number, plural, one {# tund} other {# tundi}} jäänud",
   "time_remaining.minutes": "{number, plural, one {# minut} other {# minutit}} jäänud",
@@ -881,12 +853,26 @@
   "upload_button.label": "Lisa meedia (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Faili üleslaadimise limiit ületatud.",
   "upload_error.poll": "Küsitlustes pole faili üleslaadimine lubatud.",
+  "upload_form.audio_description": "Kirjelda kuulmispuudega inimeste jaoks",
+  "upload_form.description": "Kirjelda vaegnägijatele",
   "upload_form.drag_and_drop.instructions": "Vajuta tühikut või enterit, et tõsta manus. Lohistamise ajal kasuta nooleklahve, et manust liigutada teatud suunas. Vajuta tühikut või enterit uuesti, et paigutada manus oma uuele kohale, või escape tühistamiseks.",
   "upload_form.drag_and_drop.on_drag_cancel": "Lohistamine tühistati. Manus {item} on asetatud.",
   "upload_form.drag_and_drop.on_drag_end": "Manus {item} on asetatud.",
   "upload_form.drag_and_drop.on_drag_over": "Manus {item} on liigutatud.",
   "upload_form.drag_and_drop.on_drag_start": "Tõstetud on manus {item}.",
   "upload_form.edit": "Muuda",
+  "upload_form.thumbnail": "Muuda pisipilti",
+  "upload_form.video_description": "Kirjelda kuulmis- või nägemispuudega inimeste jaoks",
+  "upload_modal.analyzing_picture": "Analüüsime pilti…",
+  "upload_modal.apply": "Rakenda",
+  "upload_modal.applying": "Rakendan…",
+  "upload_modal.choose_image": "Vali pilt",
+  "upload_modal.description_placeholder": "Kiire pruun rebane hüppab üle laisa koera",
+  "upload_modal.detect_text": "Tuvasta teksti pildilt",
+  "upload_modal.edit_media": "Muuda meediat",
+  "upload_modal.hint": "Vajuta või tõmba ringi eelvaatel, et valida fookuspunkti, mis on alati nähtaval kõikidel eelvaadetel.",
+  "upload_modal.preparing_ocr": "Valmistan masinlugemist…",
+  "upload_modal.preview_label": "Eelvaade ({ratio})",
   "upload_progress.label": "Laeb üles....",
   "upload_progress.processing": "Töötlen…",
   "username.taken": "See kasutajanimi on juba kasutusel. Proovi teist",
@@ -896,6 +882,8 @@
   "video.expand": "Suurenda video",
   "video.fullscreen": "Täisekraan",
   "video.hide": "Peida video",
+  "video.mute": "Vaigista heli",
   "video.pause": "Paus",
-  "video.play": "Mängi"
+  "video.play": "Mängi",
+  "video.unmute": "Taasta heli"
 }
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index 0c73c9f540..464468d0a6 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -29,6 +29,7 @@
   "account.endorse": "Nabarmendu profilean",
   "account.featured_tags.last_status_at": "Azken bidalketa {date} datan",
   "account.featured_tags.last_status_never": "Bidalketarik ez",
+  "account.featured_tags.title": "{name} erabiltzailearen nabarmendutako traolak",
   "account.follow": "Jarraitu",
   "account.follow_back": "Jarraitu bueltan",
   "account.followers": "Jarraitzaileak",
@@ -85,23 +86,7 @@
   "alert.unexpected.message": "Ustekabeko errore bat gertatu da.",
   "alert.unexpected.title": "Ene!",
   "alt_text_badge.title": "Testu alternatiboa",
-  "alt_text_modal.add_alt_text": "Gehitu ordezko testua",
-  "alt_text_modal.add_text_from_image": "Gehitu testua iruditik",
-  "alt_text_modal.cancel": "Utzi",
-  "alt_text_modal.change_thumbnail": "Aldatu koadro txikia",
-  "alt_text_modal.done": "Egina",
   "announcement.announcement": "Iragarpena",
-  "annual_report.summary.followers.followers": "jarraitzaileak",
-  "annual_report.summary.followers.total": "{count} guztira",
-  "annual_report.summary.highlighted_post.by_favourites": "egindako bidalketa gogokoena",
-  "annual_report.summary.highlighted_post.by_reblogs": "egindako bidalketa zabalduena",
-  "annual_report.summary.highlighted_post.by_replies": "erantzun gehien izan dituen bidalketa",
-  "annual_report.summary.highlighted_post.possessive": "{name}-(r)ena",
-  "annual_report.summary.most_used_app.most_used_app": "app erabiliena",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "traola erabiliena",
-  "annual_report.summary.most_used_hashtag.none": "Bat ere ez",
-  "annual_report.summary.new_posts.new_posts": "bidalketa berriak",
-  "annual_report.summary.thanks": "Eskerrik asko Mastodonen parte izateagatik!",
   "attachments_list.unprocessed": "(prozesatu gabe)",
   "audio.hide": "Ezkutatu audioa",
   "block_modal.remote_users_caveat": "{domain} zerbitzariari zure erabakia errespeta dezan eskatuko diogu. Halere, araua beteko den ezin da bermatu, zerbitzari batzuk modu desberdinean kudeatzen baitituzte blokeoak. Baliteke argitalpen publikoak saioa hasi ez duten erabiltzaileentzat ikusgai egotea.",
@@ -125,7 +110,7 @@
   "bundle_column_error.routing.body": "Eskatutako orria ezin izan da aurkitu. Ziur helbide-barrako URLa zuzena dela?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Itxi",
-  "bundle_modal_error.message": "Zerbait okerra gertatu da pantaila hau kargatzean.",
+  "bundle_modal_error.message": "Zerbait okerra gertatu da osagai hau kargatzean.",
   "bundle_modal_error.retry": "Saiatu berriro",
   "closed_registrations.other_server_instructions": "Mastodon deszentralizatua denez, beste kontu bat sortu dezakezu beste zerbitzari batean eta honekin komunikatu.",
   "closed_registrations_modal.description": "Une honetan ezin da konturik sortu {domain} zerbitzarian, baina kontuan izan Mastodon erabiltzeko ez duzula zertan konturik izan zehazki {domain} zerbitzarian.",
@@ -136,16 +121,13 @@
   "column.blocks": "Blokeatutako erabiltzaileak",
   "column.bookmarks": "Laster-markak",
   "column.community": "Denbora-lerro lokala",
-  "column.create_list": "Sortu zerrenda",
   "column.direct": "Aipamen pribatuak",
   "column.directory": "Arakatu profilak",
   "column.domain_blocks": "Ezkutatutako domeinuak",
-  "column.edit_list": "Editatu zerrenda",
   "column.favourites": "Gogokoak",
   "column.firehose": "Zuzeneko jarioak",
   "column.follow_requests": "Jarraitzeko eskaerak",
   "column.home": "Hasiera",
-  "column.list_members": "Kudeatu zerrrendako partaideak",
   "column.lists": "Zerrendak",
   "column.mutes": "Mutututako erabiltzaileak",
   "column.notifications": "Jakinarazpenak",
@@ -158,7 +140,6 @@
   "column_header.pin": "Finkatu",
   "column_header.show_settings": "Erakutsi ezarpenak",
   "column_header.unpin": "Desfinkatu",
-  "column_search.cancel": "Utzi",
   "column_subheading.settings": "Ezarpenak",
   "community.column_settings.local_only": "Lokala soilik",
   "community.column_settings.media_only": "Edukiak soilik",
@@ -177,7 +158,7 @@
   "compose_form.poll.duration": "Inkestaren iraupena",
   "compose_form.poll.multiple": "Aukera aniza",
   "compose_form.poll.option_placeholder": "{number}. aukera",
-  "compose_form.poll.single": "Aukera bakarra",
+  "compose_form.poll.single": "Hautatu bat",
   "compose_form.poll.switch_to_multiple": "Aldatu inkesta hainbat aukera onartzeko",
   "compose_form.poll.switch_to_single": "Aldatu inkesta aukera bakarra onartzeko",
   "compose_form.poll.type": "Estiloa",
@@ -201,15 +182,9 @@
   "confirmations.edit.confirm": "Editatu",
   "confirmations.edit.message": "Orain editatzen baduzu, une honetan idazten ari zaren mezua gainidatziko da. Ziur jarraitu nahi duzula?",
   "confirmations.edit.title": "Gainidatzi bidalketa?",
-  "confirmations.follow_to_list.confirm": "Jarraitu eta zerrendan sartu",
-  "confirmations.follow_to_list.message": "{name} jarraitu behar duzu zerrenda batean sartzeko.",
-  "confirmations.follow_to_list.title": "Erabiltzailea jarraitu?",
   "confirmations.logout.confirm": "Amaitu saioa",
   "confirmations.logout.message": "Ziur saioa amaitu nahi duzula?",
   "confirmations.logout.title": "Itxi saioa?",
-  "confirmations.missing_alt_text.confirm": "Gehitu testu alternatiboa",
-  "confirmations.missing_alt_text.secondary": "Bidali edonola ere",
-  "confirmations.missing_alt_text.title": "Testu alternatiboa gehitu?",
   "confirmations.mute.confirm": "Mututu",
   "confirmations.redraft.confirm": "Ezabatu eta berridatzi",
   "confirmations.redraft.message": "Ziur argitalpen hau ezabatu eta zirriborroa berriro egitea nahi duzula? Gogokoak eta bultzadak galduko dira, eta jatorrizko argitalpenaren erantzunak zurtz geratuko dira.",
@@ -238,6 +213,10 @@
   "disabled_account_banner.text": "Zure {disabledAccount} kontua desgaituta dago une honetan.",
   "dismissable_banner.community_timeline": "Hauek dira {domain} zerbitzarian ostatatutako kontuen bidalketa publiko berrienak.",
   "dismissable_banner.dismiss": "Baztertu",
+  "dismissable_banner.explore_links": "Albiste hauei buruz hitz egiten ari da jendea orain zerbitzari honetan eta sare deszentralizatuko besteetan.",
+  "dismissable_banner.explore_statuses": "Hauek dira gaur egun lekua hartzen ari diren sare sozial osoaren argitalpenak. Bultzada eta gogoko gehien dituzten argitalpen berrienek sailkapen altuagoa dute.",
+  "dismissable_banner.explore_tags": "Traola hauek daude bogan orain zerbitzari honetan eta sare deszentralizatuko besteetan.",
+  "dismissable_banner.public_timeline": "Hauek dira {domain}-(e)ko jendeak web sozialean jarraitzen dituen jendearen azkeneko argitalpen publikoak.",
   "domain_block_modal.block": "Blokeatu zerbitzaria",
   "domain_block_modal.block_account_instead": "Blokeatu @{name} bestela",
   "domain_block_modal.they_can_interact_with_old_posts": "Zerbitzari honetako jendea zure argitalpen zaharrekin elkarreragin dezake.",
@@ -292,6 +271,7 @@
   "empty_column.hashtag": "Ez dago ezer traola honetan oraindik.",
   "empty_column.home": "Zure hasierako denbora-lerroa hutsik dago! Jarraitu jende gehiago betetzeko.",
   "empty_column.list": "Ez dago ezer zerrenda honetan. Zerrenda honetako kideek bidalketa berriak argitaratzean, hemen agertuko dira.",
+  "empty_column.lists": "Ez duzu zerrendarik oraindik. Baten bat sortzen duzunean hemen agertuko da.",
   "empty_column.mutes": "Ez duzu erabiltzailerik mututu oraindik.",
   "empty_column.notification_requests": "Garbi-garbi! Ezertxo ere ez hemen. Jakinarazpenak jasotzen dituzunean, hemen agertuko dira zure ezarpenen arabera.",
   "empty_column.notifications": "Ez duzu jakinarazpenik oraindik. Jarri besteekin harremanetan elkarrizketa abiatzeko.",
@@ -302,6 +282,7 @@
   "error.unexpected_crash.next_steps_addons": "Saiatu desgaitu eta orria berritzen. Horrek ez badu laguntzen, agian Mastodon erabiltzeko aukera duzu oraindik ere beste nabigatzaile bat edo aplikazio natibo bat erabilita.",
   "errors.unexpected_crash.copy_stacktrace": "Kopiatu irteera arbelera",
   "errors.unexpected_crash.report_issue": "Eman arazoaren berri",
+  "explore.search_results": "Bilaketaren emaitzak",
   "explore.suggested_follows": "Jendea",
   "explore.title": "Arakatu",
   "explore.trending_links": "Berriak",
@@ -351,14 +332,13 @@
   "footer.about": "Honi buruz",
   "footer.directory": "Profil-direktorioa",
   "footer.get_app": "Eskuratu aplikazioa",
+  "footer.invite": "Gonbidatu jendea",
   "footer.keyboard_shortcuts": "Lasterbideak",
   "footer.privacy_policy": "Pribatutasun politika",
   "footer.source_code": "Ikusi iturburu kodea",
   "footer.status": "Egoera",
-  "footer.terms_of_service": "Erabilera baldintzak",
   "generic.saved": "Gordea",
   "getting_started.heading": "Menua",
-  "hashtag.admin_moderation": "#{name}-(r)en moderazio-interfazea ireki",
   "hashtag.column_header.tag_mode.all": "eta {osagarria}",
   "hashtag.column_header.tag_mode.any": "edo {osagarria}",
   "hashtag.column_header.tag_mode.none": "gabe {osagarria}",
@@ -400,15 +380,21 @@
   "ignore_notifications_modal.not_followers_title": "Jarraitzen ez zaituzten pertsonen jakinarazpenei ez ikusiarena egin?",
   "ignore_notifications_modal.not_following_title": "Jarraitzen ez dituzun pertsonen jakinarazpenei ez ikusiarena egin?",
   "ignore_notifications_modal.private_mentions_title": "Eskatu gabeko aipamen pribatuen jakinarazpenei ez ikusiarena egin?",
-  "interaction_modal.go": "Joan",
-  "interaction_modal.no_account_yet": "Ez al duzu konturik oraindik?",
+  "interaction_modal.description.favourite": "Mastodon kontu batekin bidalketa hau gogoko egin dezakezu, egileari eskertzeko eta gerorako gordetzeko.",
+  "interaction_modal.description.follow": "Mastodon kontu batekin {name} jarraitu dezakezu bere bidalketak zure hasierako denbora lerroan jasotzeko.",
+  "interaction_modal.description.reblog": "Mastodon kontu batekin bidalketa hau bultzatu dezakezu, zure jarraitzaileekin partekatzeko.",
+  "interaction_modal.description.reply": "Mastodon kontu batekin bidalketa honi erantzun diezaiokezu.",
+  "interaction_modal.login.action": "Itzuli hasierara",
+  "interaction_modal.login.prompt": "Zure zerbitzariko domeinua, adib. mastodon.eus",
+  "interaction_modal.no_account_yet": "Oraindik ez duzu izena eman Mastodonen?",
   "interaction_modal.on_another_server": "Beste zerbitzari batean",
   "interaction_modal.on_this_server": "Zerbitzari honetan",
+  "interaction_modal.sign_in": "Ez duzu saioa hasita zerbitzari honetan. Non dago zure kontua ostatatua?",
+  "interaction_modal.sign_in_hint": "Aholkua: Izena eman duzun zerbitzaria da. Ez baduzu gogoratzen, begiratu ongietorri-mezua zure sarrera-ontzian. Baita ere, zure erabiltzaile-izen osoa sar dezakezu! (adib. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Egin gogoko {name}(r)en bidalketa",
   "interaction_modal.title.follow": "Jarraitu {name}(r)i",
   "interaction_modal.title.reblog": "Bultzatu {name}(r)en bidalketa",
   "interaction_modal.title.reply": "Erantzun {name}(r)en bidalketari",
-  "interaction_modal.username_prompt": "Adib. {example}",
   "intervals.full.days": "{number, plural, one {egun #} other {# egun}}",
   "intervals.full.hours": "{number, plural, one {ordu #} other {# ordu}}",
   "intervals.full.minutes": "{number, plural, one {minutu #} other {# minutu}}",
@@ -454,18 +440,20 @@
   "link_preview.author": "Egilea: {name}",
   "link_preview.more_from_author": "{name} erabiltzaileaz gehiago jakin",
   "link_preview.shares": "{count, plural, one {{counter} bidalketa} other {{counter} bidalketa}}",
-  "lists.add_member": "Gehitu",
-  "lists.add_to_list": "Gehitu zerrendara",
-  "lists.add_to_lists": "Gehitu {name} zerrendetara",
-  "lists.create": "Sortu",
-  "lists.create_list": "Sortu zerrenda",
+  "lists.account.add": "Gehitu zerrendara",
+  "lists.account.remove": "Kendu zerrendatik",
   "lists.delete": "Ezabatu zerrenda",
-  "lists.done": "Egina",
   "lists.edit": "Editatu zerrenda",
+  "lists.edit.submit": "Aldatu izenburua",
+  "lists.exclusive": "Ezkutatu argitalpen hauek hasieratik",
+  "lists.new.create": "Gehitu zerrenda",
+  "lists.new.title_placeholder": "Zerrenda berriaren izena",
   "lists.replies_policy.followed": "Jarraitutako edozein erabiltzaile",
   "lists.replies_policy.list": "Zerrendako kideak",
   "lists.replies_policy.none": "Bat ere ez",
-  "lists.search": "Bilatu",
+  "lists.replies_policy.title": "Erakutsi erantzunak:",
+  "lists.search": "Bilatu jarraitzen dituzun pertsonen artean",
+  "lists.subheading": "Zure zerrendak",
   "load_pending": "{count, plural, one {elementu berri #} other {# elementu berri}}",
   "loading_indicator.label": "Kargatzen…",
   "media_gallery.hide": "Ezkutatu",
@@ -613,21 +601,44 @@
   "notifications_permission_banner.enable": "Gaitu mahaigaineko jakinarazpenak",
   "notifications_permission_banner.how_to_control": "Mastodon irekita ez dagoenean jakinarazpenak jasotzeko, gaitu mahaigaineko jakinarazpenak. Mahaigaineko jakinarazpenak ze elkarrekintzak eragingo dituzten zehazki kontrolatu dezakezu goiko {icon} botoia erabiliz, gaituta daudenean.",
   "notifications_permission_banner.title": "Ez galdu ezer inoiz",
-  "onboarding.follows.back": "Atzera",
-  "onboarding.follows.done": "Egina",
+  "onboarding.action.back": "Egin atzera",
+  "onboarding.actions.back": "Egin atzera",
+  "onboarding.actions.go_to_explore": "Ikusi zer dagoen pil-pilean",
+  "onboarding.actions.go_to_home": "Joan hasierara",
+  "onboarding.compose.template": "Kaixo #Mastodon!",
   "onboarding.follows.empty": "Zoritxarrez, ezin da emaitzik erakutsi orain. Bilaketa erabil dezakezu edo Arakatu orrian jendea bilatu jarraitzeko, edo saiatu geroago.",
-  "onboarding.follows.search": "Bilatu",
-  "onboarding.follows.title": "Jarraitu jendea hasteko",
+  "onboarding.follows.lead": "Hasierako orria zuk pertsonalizatzen duzu. Gero eta jende gehiagori jarraitu, orduan eta aktibo eta interesgarriago izango da. Profil hauek egokiak izan daitezke hasteko, beti ere, geroago jarraitzeari utz diezazkiekezu!",
+  "onboarding.follows.title": "Mastodonen pil-pilean",
   "onboarding.profile.discoverable": "Profila aurkitzeko moduan jarri",
   "onboarding.profile.discoverable_hint": "Mastodon zure profila aurkitzeko moduan duzunean, zure mezuak bilaketa-emaitzetan eta jarraipenetan ager daitezke, eta zure profila antzeko interesa duen jendeari iradoki ahal zaio.",
   "onboarding.profile.display_name": "Bistaratzeko izena",
   "onboarding.profile.display_name_hint": "Zure izena edo ezizena…",
+  "onboarding.profile.lead": "Geroagoago bete daiteke konfigurazioan, non pertsonalizatzeko aukera gehiago dauden.",
   "onboarding.profile.note": "Biografia",
   "onboarding.profile.note_hint": "Beste pertsona batzuk @aipa ditzakezu edo #traolak erabili…",
   "onboarding.profile.save_and_continue": "Gorde eta jarraitu",
   "onboarding.profile.title": "Profilaren konfigurazioa",
   "onboarding.profile.upload_avatar": "Igo profilaren irudia",
   "onboarding.profile.upload_header": "Igo profilaren goiburua",
+  "onboarding.share.lead": "Esan jendeari nola aurki zaitzaketen Mastodonen!",
+  "onboarding.share.message": "{username} naiz #Mastodon-en! Jarrai nazazu hemen: {url}",
+  "onboarding.share.next_steps": "Hurrengo urrats posibleak:",
+  "onboarding.share.title": "Partekatu zure profila",
+  "onboarding.start.lead": "Mastodon-en parte zara orain, bakarra eta deszentralizatua den sare sozialaren plataforma, non zuk, eta ez algoritmo batek, zeure esperientzia pertsonaliza dezakezun. Igaro ezazu muga soziala:",
+  "onboarding.start.skip": "Urrats guztiak saltatu nahi dituzu?",
+  "onboarding.start.title": "Lortu duzu!",
+  "onboarding.steps.follow_people.body": "Zure jarioa zuk pertsonalizatzen duzu. Bete dezagun jende interesgarriaz.",
+  "onboarding.steps.follow_people.title": "Jarraitu {count, plural, one {pertsona bat} other {# pertsona}}",
+  "onboarding.steps.publish_status.body": "Agurtu munduari.",
+  "onboarding.steps.publish_status.title": "Sortu zure lehen bidalketa",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
+  "onboarding.tips.2fa": "<strong>Bazenekien?</strong> Zure kontua babes dezakezu, bi faktoreko autentifikazioa zure kontuko ezarpenetan ezarriaz. Edozein TOTP aplikaziorekin dabil, ez da telefono-zenbakirik behar!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Badakizu?</strong> Mastodon deszentralizatua denez, beste zerbitzarietako profilak topatuko dituzu. Eta, hala ere, arazorik gabe jardun dezakezu haiekin! Haien zerbitzaria erabiltzaile-izenaren bigarren erdian dago!",
+  "onboarding.tips.migration": "<strong>Bazenekien?</strong> Uste baduzu {domain} ez dela aukera on bat zuretzako etorkizunari begira, beste Mastodon zerbitzari batera alda zaitezke, zure jarraitzaileak galdu gabe. Zure zerbitzaria propioa ere ostata dezakezu!",
+  "onboarding.tips.verification": "<strong>Bazenekien?</strong> Zure kontua egiazta dezakezu zure webgunean zure Mastodon-go profilaren esteka jarriz, eta profilean webgunea gehituz. Ordainketa edo dokumenturik gabe!",
   "password_confirmation.exceeds_maxlength": "Pasahitzaren berrespenak pasahitzaren gehienezko luzera gainditzen du",
   "password_confirmation.mismatching": "Pasahitzaren berrespena ez dator bat",
   "picture_in_picture.restore": "Leheneratu",
@@ -643,6 +654,7 @@
   "poll_button.remove_poll": "Kendu inkesta",
   "privacy.change": "Aldatu bidalketaren pribatutasuna",
   "privacy.direct.long": "Argitalpen honetan aipatutako denak",
+  "privacy.direct.short": "Jende jakina",
   "privacy.private.long": "Soilik jarraitzaileak",
   "privacy.private.short": "Jarraitzaileak",
   "privacy.public.long": "Mastodonen dagoen edo ez dagoen edonor",
@@ -654,8 +666,8 @@
   "privacy_policy.title": "Pribatutasun politika",
   "recommended": "Gomendatua",
   "refresh": "Berritu",
-  "regeneration_indicator.please_stand_by": "Itxaron, mesedez.",
-  "regeneration_indicator.preparing_your_home_feed": "Zure hasierako jarioa prestatzen…",
+  "regeneration_indicator.label": "Kargatzen…",
+  "regeneration_indicator.sublabel": "Zure hasiera-jarioa prestatzen ari da!",
   "relative_time.days": "{number}e",
   "relative_time.full.days": "Duela {number, plural, one {egun #} other {# egun}}",
   "relative_time.full.hours": "Duela {number, plural, one {ordu #} other {# ordu}}",
@@ -739,9 +751,10 @@
   "search_results.accounts": "Profilak",
   "search_results.all": "Guztiak",
   "search_results.hashtags": "Traolak",
-  "search_results.no_results": "Emaitzarik ez.",
+  "search_results.nothing_found": "Ez da emaitzarik aurkitu bilaketa-termino horientzat",
   "search_results.see_all": "Ikusi guztiak",
   "search_results.statuses": "Bidalketak",
+  "search_results.title": "Bilatu {q}",
   "server_banner.about_active_users": "Azken 30 egunetan zerbitzari hau erabili duen jendea (hilabeteko erabiltzaile aktiboak)",
   "server_banner.active_users": "erabiltzaile aktibo",
   "server_banner.administered_by": "Administratzailea(k):",
@@ -829,7 +842,21 @@
   "upload_button.label": "Gehitu multimedia  (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Fitxategi igoera muga gaindituta.",
   "upload_error.poll": "Ez da inkestetan fitxategiak igotzea onartzen.",
+  "upload_form.audio_description": "Deskribatu entzumen galera duten pertsonentzat",
+  "upload_form.description": "Deskribatu ikusmen arazoak dituztenentzat",
   "upload_form.edit": "Editatu",
+  "upload_form.thumbnail": "Aldatu koadro txikia",
+  "upload_form.video_description": "Deskribatu entzumen galera edo ikusmen urritasuna duten pertsonentzat",
+  "upload_modal.analyzing_picture": "Irudia aztertzen…",
+  "upload_modal.apply": "Aplikatu",
+  "upload_modal.applying": "Aplikatzen…",
+  "upload_modal.choose_image": "Aukeratu irudia",
+  "upload_modal.description_placeholder": "Vaudeville itxurako filmean yogi ñaño bat jipoitzen dute Quebec-en whiski truk",
+  "upload_modal.detect_text": "Antzeman testua iruditik",
+  "upload_modal.edit_media": "Editatu media",
+  "upload_modal.hint": "Sakatu eta jaregin aurrebistako zirkulua iruditxoetan beti ikusgai egongo den puntu fokala hautatzeko.",
+  "upload_modal.preparing_ocr": "OCR prestatzen…",
+  "upload_modal.preview_label": "Aurreikusi ({ratio})",
   "upload_progress.label": "Igotzen...",
   "upload_progress.processing": "Prozesatzen…",
   "username.taken": "Erabiltzailea hartuta dago. Saiatu beste batekin",
@@ -839,6 +866,8 @@
   "video.expand": "Hedatu bideoa",
   "video.fullscreen": "Pantaila osoa",
   "video.hide": "Ezkutatu bideoa",
+  "video.mute": "Mututu soinua",
   "video.pause": "Pausatu",
-  "video.play": "Jo"
+  "video.play": "Jo",
+  "video.unmute": "Desmututu soinua"
 }
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 3e31eb8a15..f01db61e73 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -29,6 +29,7 @@
   "account.endorse": "معرّفی در نمایه",
   "account.featured_tags.last_status_at": "آخرین فرسته در {date}",
   "account.featured_tags.last_status_never": "بدون فرسته",
+  "account.featured_tags.title": "برچسب‌های برگزیدهٔ {name}",
   "account.follow": "پی‌گرفتن",
   "account.follow_back": "دنبال کردن متقابل",
   "account.followers": "پی‌گیرندگان",
@@ -64,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",
   "account.unblock": "رفع مسدودیت ‎@{name}",
   "account.unblock_domain": "رفع مسدودیت دامنهٔ {domain}",
-  "account.unblock_domain_short": "آنبلاک",
   "account.unblock_short": "رفع مسدودیت",
   "account.unendorse": "معرّفی نکردن در نمایه",
   "account.unfollow": "پی‌نگرفتن",
@@ -86,33 +86,7 @@
   "alert.unexpected.message": "خطایی غیرمنتظره رخ داد.",
   "alert.unexpected.title": "ای وای!",
   "alt_text_badge.title": "متن جایگزین",
-  "alt_text_modal.add_alt_text": "افزودن متن جایگزین",
-  "alt_text_modal.add_text_from_image": "افزودن متن از عکس",
-  "alt_text_modal.cancel": "لغو",
-  "alt_text_modal.change_thumbnail": "تغییر بندانگشتی",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "شرح برای افرادی با مشکلات شنوایی…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "شرح برای افرادی با مشکلات بینایی…",
-  "alt_text_modal.done": "انجام شد",
   "announcement.announcement": "اعلامیه",
-  "annual_report.summary.archetype.booster": "باحال‌یاب",
-  "annual_report.summary.archetype.lurker": "کم‌پیدا",
-  "annual_report.summary.archetype.oracle": "غیب‌گو",
-  "annual_report.summary.archetype.pollster": "نظرسنج",
-  "annual_report.summary.archetype.replier": "پاسخگو",
-  "annual_report.summary.followers.followers": "دنبال کننده",
-  "annual_report.summary.followers.total": "در مجموع {count}",
-  "annual_report.summary.here_it_is": "بازبینی {year} تان:",
-  "annual_report.summary.highlighted_post.by_favourites": "پرپسندترین فرسته",
-  "annual_report.summary.highlighted_post.by_reblogs": "پرتقویت‌ترین فرسته",
-  "annual_report.summary.highlighted_post.by_replies": "پرپاسخ‌ترین فرسته",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "پراستفاده‌ترین کاره",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "پراستفاده‌ترین برچسب",
-  "annual_report.summary.most_used_hashtag.none": "هیچ‌کدام",
-  "annual_report.summary.new_posts.new_posts": "فرستهٔ جدید",
-  "annual_report.summary.percentile.text": "<topLabel>بین کاربران {domain} جزو</topLabel><percentage></percentage><bottomLabel>برتر هستید.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "به برنی خبر نمی‌دهیم.",
-  "annual_report.summary.thanks": "سپاس که بخشی از ماستودون هستید!",
   "attachments_list.unprocessed": "(پردازش نشده)",
   "audio.hide": "نهفتن صدا",
   "block_modal.remote_users_caveat": "ما از کارساز {domain} خواهیم خواست که به تصمیم شما احترام بگذارد. با این حال، تضمینی برای رعایت آن وجود ندارد زیرا برخی کارسازها ممکن است بلوک‌ها را به‌طور متفاوتی مدیریت کنند. فرسته‌های عمومی ممکن است همچنان برای کاربران که وارد نشده قابل مشاهده باشند.",
@@ -136,7 +110,7 @@
   "bundle_column_error.routing.body": "صفحهٔ درخواستی پیدا نشد. مطمئنید که نشانی را درست وارد کرده‌اید؟",
   "bundle_column_error.routing.title": "۴۰۴",
   "bundle_modal_error.close": "بستن",
-  "bundle_modal_error.message": "هنگام بار کردن این صفحه، اشتباهی رخ داد.",
+  "bundle_modal_error.message": "هنگام بار کردن این مولفه، اشتباهی رخ داد.",
   "bundle_modal_error.retry": "تلاش دوباره",
   "closed_registrations.other_server_instructions": "از آن‌جا که ماستودون نامتمرکز است، می‌توانید حسابی روی کارسازی دیگر ساخته و همچنان با این‌یکی در تعامل باشید.",
   "closed_registrations_modal.description": "هم‌اکنون امکان ساخت حساب روی {domain} وجود ندارد؛ ولی لطفاً به خاطر داشته باشید که برای استفاده از ماستودون، نیازی به داشتن حساب روی {domain} نیست.",
@@ -147,16 +121,13 @@
   "column.blocks": "کاربران مسدود شده",
   "column.bookmarks": "نشانک‌ها",
   "column.community": "خط زمانی محلی",
-  "column.create_list": "ایجاد سیاهه",
   "column.direct": "اشاره‌های خصوصی",
   "column.directory": "مرور نمایه‌ها",
   "column.domain_blocks": "دامنه‌های مسدود شده",
-  "column.edit_list": "ویرایش سیاهه",
   "column.favourites": "برگزیده‌ها",
   "column.firehose": "خوراک‌های زنده",
   "column.follow_requests": "درخواست‌های پی‌گیری",
   "column.home": "خانه",
-  "column.list_members": "مدیریت اعضای سیاهه",
   "column.lists": "سیاهه‌ها",
   "column.mutes": "کاربران خموش",
   "column.notifications": "آگاهی‌ها",
@@ -169,7 +140,6 @@
   "column_header.pin": "سنجاق کردن",
   "column_header.show_settings": "نمایش تنظیمات",
   "column_header.unpin": "برداشتن سنجاق",
-  "column_search.cancel": "لغو",
   "column_subheading.settings": "تنظیمات",
   "community.column_settings.local_only": "فقط محلی",
   "community.column_settings.media_only": "فقط رسانه",
@@ -188,7 +158,7 @@
   "compose_form.poll.duration": "مدت نظرسنجی",
   "compose_form.poll.multiple": "چند گزینه‌ای",
   "compose_form.poll.option_placeholder": "گزینهٔ {number}",
-  "compose_form.poll.single": "تک گزینه‌ای",
+  "compose_form.poll.single": "گزینش یکی",
   "compose_form.poll.switch_to_multiple": "تغییر نظرسنجی برای اجازه به چندین گزینه",
   "compose_form.poll.switch_to_single": "تبدیل به نظرسنجی تک‌گزینه‌ای",
   "compose_form.poll.type": "سبک",
@@ -212,16 +182,9 @@
   "confirmations.edit.confirm": "ویرایش",
   "confirmations.edit.message": "در صورت ویرایش، پیامی که در حال نوشتنش بودید از بین خواهد رفت. می‌خواهید ادامه دهید؟",
   "confirmations.edit.title": "رونویسی فرسته؟",
-  "confirmations.follow_to_list.confirm": "پی‌گیری و افزودن به سیاهه",
-  "confirmations.follow_to_list.message": "برای افزودن {name} به سیاهه باید پیش گرفته باشید.",
-  "confirmations.follow_to_list.title": "پی‌گیری کاربر؟",
   "confirmations.logout.confirm": "خروج از حساب",
   "confirmations.logout.message": "مطمئنید می‌خواهید خارج شوید؟",
   "confirmations.logout.title": "خروج؟",
-  "confirmations.missing_alt_text.confirm": "متن جایگزین را اضافه کنید",
-  "confirmations.missing_alt_text.message": "پست شما حاوی رسانه بدون متن جایگزین است. افزودن توضیحات کمک می کند تا محتوای شما برای افراد بیشتری قابل دسترسی باشد.",
-  "confirmations.missing_alt_text.secondary": "به هر حال پست کن",
-  "confirmations.missing_alt_text.title": "متن جایگزین اضافه شود؟",
   "confirmations.mute.confirm": "خموش",
   "confirmations.redraft.confirm": "حذف و بازنویسی",
   "confirmations.redraft.message": "مطمئنید که می‌خواهید این فرسته را حذف کنید و از نو بنویسید؟ با این کار تقویت‌ها و پسندهایش از دست رفته و پاسخ‌ها به آن بی‌مرجع می‌شود.",
@@ -250,9 +213,9 @@
   "disabled_account_banner.text": "حسابتان {disabledAccount} اکنون از کار افتاده.",
   "dismissable_banner.community_timeline": "این‌ها جدیدترین فرسته‌های عمومی از افرادیند که حساب‌هایشان به دست {domain} میزبانی می‌شود.",
   "dismissable_banner.dismiss": "دور انداختن",
-  "dismissable_banner.explore_links": "امروز این روایت‌های خبری بیش‌تر روی وب اجتماعی هم‌رسانی می‌شوند. روایت‌های خبری جدیدتری که به دست افراد بیش‌تری فرستاده شده‌اند، بالاتر قرار گرفته‌اند.",
-  "dismissable_banner.explore_statuses": "امروز این فرسته‌ها روی وب اجتماعی جذّابند. فرسته‌های جدیدتری که بیش‌تر برگزیده و تقویت شده باشند، بالاتر قرار گرفته‌اند.",
-  "dismissable_banner.explore_tags": "امروز این برچسب‌ها روی وب اجتماعی جذّابند. برچسب‌هایی که به دست افراد بیش‌تری استفاده شده باشند، بالاتر قرار گرفته‌اند.",
+  "dismissable_banner.explore_links": "هم‌اکنون افراد روی این کارساز و دیگر کارسازهای شبکهٔ نامتمرکز در مورد این داستان‌های خبری صحبت می‌کنند.",
+  "dismissable_banner.explore_statuses": "هم‌اکنون این فرسته‌ها از این کارساز و دیگر کارسازهای شبکهٔ نامتمرکز داغ شده‌اند.",
+  "dismissable_banner.explore_tags": "هم‌اکنون این برچسب‌ها بین افراد این کارساز و دیگر کارسازهای شبکهٔ نامتمرکز داغ شده‌اند.",
   "dismissable_banner.public_timeline": "این‌ها جدیدترین فرسته‌های عمومی از افرادی روی وب اجتماعیند که اعضای {domain} پی می‌گیرندشان.",
   "domain_block_modal.block": "انسداد کارساز",
   "domain_block_modal.block_account_instead": "انسداد @{name} به جایش",
@@ -310,6 +273,7 @@
   "empty_column.hashtag": "هنوز هیچ چیزی در این برچسب نیست.",
   "empty_column.home": "خط زمانی خانگیتان خالی است! برای پر کردنش، افراد بیشتری را پی بگیرید.",
   "empty_column.list": "هنوز چیزی در این سیاهه نیست. هنگامی که اعضایش فرسته‌های جدیدی بفرستند، این‌جا ظاهر خواهند شد.",
+  "empty_column.lists": "هنوز هیچ سیاهه‌ای ندارید. هنگامی که یکی بسازید، این‌جا نشان داده خواهد شد.",
   "empty_column.mutes": "هنوز هیچ کاربری را خموش نکرده‌اید.",
   "empty_column.notification_requests": "همه چیز تمیز است! هیچ‌چیزی این‌جا نیست. هنگامی که آگاهی‌های جدیدی دریافت کنید، بسته به تنظیماتتان این‌جا ظاهر خواهند شد.",
   "empty_column.notifications": "هنوز هیچ آگاهی‌آی ندارید. هنگامی که دیگران با شما برهم‌کنش داشته باشند،‌این‌حا خواهید دیدش.",
@@ -320,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "لطفاً از کارشان انداخته و صفحه را نوسازی کنید. اگر کمکی نکرد، شاید همچنان بتوانید با مرورگری دیگر یا با کاره‌ای بومی از ماستودون استفاده کنید.",
   "errors.unexpected_crash.copy_stacktrace": "رونوشت از جزئیات اشکال",
   "errors.unexpected_crash.report_issue": "گزارش مشکل",
+  "explore.search_results": "نتایج جست‌وجو",
   "explore.suggested_follows": "افراد",
   "explore.title": "کاوش",
   "explore.trending_links": "اخبار",
@@ -369,14 +334,13 @@
   "footer.about": "درباره",
   "footer.directory": "فهرست نمایه‌ها",
   "footer.get_app": "گرفتن کاره",
+  "footer.invite": "دعوت دیگران",
   "footer.keyboard_shortcuts": "میان‌برهای صفحه‌کلید",
   "footer.privacy_policy": "سیاست محرمانگی",
   "footer.source_code": "نمایش کد مبدأ",
   "footer.status": "وضعیت",
-  "footer.terms_of_service": "شرایط استفاده از خدمات",
   "generic.saved": "ذخیره شده",
   "getting_started.heading": "آغاز کنید",
-  "hashtag.admin_moderation": "گشودن میانای نظارت برای ‎#{name}",
   "hashtag.column_header.tag_mode.all": "و {additional}",
   "hashtag.column_header.tag_mode.any": "یا {additional}",
   "hashtag.column_header.tag_mode.none": "بدون {additional}",
@@ -418,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "چشم‌پوشی از آگاهی‌های افرادی که پیتان نمی‌گیرند؟",
   "ignore_notifications_modal.not_following_title": "چشم‌پوشی از آگاهی‌های افرادی که پیشان نمی‌گیرید؟",
   "ignore_notifications_modal.private_mentions_title": "چشم‌پوشی از نام‌بری‌های خصوصی ناخواسته؟",
-  "info_button.label": "راهنما",
-  "info_button.what_is_alt_text": "<h1>متن جایگزین چیست؟</h1> <p>متن جایگزین توضیحات تصویری را برای افراد دارای اختلالات بینایی، اتصالات با پهنای باند کم یا کسانی که به دنبال زمینه اضافی هستند ارائه می دهد.</p> <p>با نوشتن متن جایگزین واضح، مختصر و عینی می توانید دسترسی و درک را برای همه بهبود بخشید.</p> <ul> <li>عناصر مهم را ضبط کنید</li> <li>متن را در تصاویر خلاصه کنید</li> <li>از ساختار جمله منظم استفاده کنید</li> <li>از اطلاعات اضافی خودداری کنید</li> <li>روی روندها و یافته های کلیدی در تصاویر پیچیده (مانند نمودارها یا نقشه ها) تمرکز کنید.</li> </ul>",
-  "interaction_modal.action.favourite": "برای ادامه، باید از حساب خود به دلخواه انتخاب کنید.",
-  "interaction_modal.action.follow": "برای ادامه، باید از حساب کاربری خود دنبال کنید.",
-  "interaction_modal.action.reblog": "برای ادامه، باید از حساب خود مجددا بلاگ کنید.",
-  "interaction_modal.action.reply": "برای ادامه، باید از حساب خود پاسخ دهید.",
-  "interaction_modal.action.vote": "برای ادامه، باید از حساب کاربری خود رای دهید.",
-  "interaction_modal.go": "برو",
-  "interaction_modal.no_account_yet": "هنوز حساب کاربری ندارید؟",
+  "interaction_modal.description.favourite": "با حسابی روی ماستودون می‌توانید این فرسته را برگزیده تا نگارنده بداند قدردانش هستید و برای آینده ذخیره‌اش می‌کنید.",
+  "interaction_modal.description.follow": "با حسابی روی ماستودون می‌توانید {name} را برای دریافت فرسته‌هایش در خوراک خانگیتان دنبال کنید.",
+  "interaction_modal.description.reblog": "با حسابی روی ماستودون می‌توانید این فرسته را با پی‌گیران خودتان هم‌رسانی کنید.",
+  "interaction_modal.description.reply": "با حسابی روی ماستودون می‌توانید به این فرسته پاسخ دهید.",
+  "interaction_modal.login.action": "رفتن به خانه",
+  "interaction_modal.login.prompt": "دامنهٔ کارساز شخصیتان چون mastodon.social",
+  "interaction_modal.no_account_yet": "در ماستودون نیست؟",
   "interaction_modal.on_another_server": "روی کارسازی دیگر",
   "interaction_modal.on_this_server": "روی این کارساز",
+  "interaction_modal.sign_in": "شما در این کارساز وارد نشده‌اید. حسابتان کجا میزبانی شده؟",
+  "interaction_modal.sign_in_hint": "نکته: میزبانتان، پایگاه وبیست که رویش ثبت‌نام کرده‌اید. اگر به خاطر نمی‌آورید، به رایانامهٔ خوش‌آمد در صندوق ورودیتان بنگرید. همچنین می‌توانید نام کاربری کاملتان را وارد کنید! (مانند ‪@Mastodon@mastodon.social‬)",
   "interaction_modal.title.favourite": "فرسته‌های برگزیدهٔ {name}",
   "interaction_modal.title.follow": "پیگیری {name}",
   "interaction_modal.title.reblog": "تقویت فرستهٔ {name}",
   "interaction_modal.title.reply": "پاسخ به فرستهٔ {name}",
-  "interaction_modal.title.vote": "رأی دادن در نظرسنجی {name}",
-  "interaction_modal.username_prompt": "به عنوان مثال {example}",
   "intervals.full.days": "{number, plural, one {# روز} other {# روز}}",
   "intervals.full.hours": "{number, plural, one {# ساعت} other {# ساعت}}",
   "intervals.full.minutes": "{number, plural, one {# دقیقه} other {# دقیقه}}",
@@ -470,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "نمایش/نهفتن نوشتهٔ پشت هشدار محتوا",
   "keyboard_shortcuts.toggle_sensitivity": "نمایش/نهفتن رسانه",
   "keyboard_shortcuts.toot": "شروع یک فرستهٔ جدید",
-  "keyboard_shortcuts.translate": "برای ترجمه یک پست",
   "keyboard_shortcuts.unfocus": "برداشتن تمرکز از ناحیهٔ نوشتن یا جست‌وجو",
   "keyboard_shortcuts.up": "بالا بردن در سیاهه",
   "lightbox.close": "بستن",
@@ -483,32 +444,20 @@
   "link_preview.author": "از {name}",
   "link_preview.more_from_author": "بیش‌تر از {name}",
   "link_preview.shares": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",
-  "lists.add_member": "افزودن",
-  "lists.add_to_list": "افزودن به سیاهه",
-  "lists.add_to_lists": "افزودن {name} به سیاهه‌ها",
-  "lists.create": "ایجاد",
-  "lists.create_a_list_to_organize": "ایحاد فهرستی جدید برای سازمان‌دهی خوراک خانگیتان",
-  "lists.create_list": "ایجاد سیاهه",
+  "lists.account.add": "افزودن به سیاهه",
+  "lists.account.remove": "برداشتن از سیاهه",
   "lists.delete": "حذف سیاهه",
-  "lists.done": "انجام شد",
   "lists.edit": "ویرایش سیاهه",
-  "lists.exclusive": "نهفتن اعضا در خانه",
-  "lists.exclusive_hint": "اگر کسی در این سیاهه باشد، در خوراک خانگیتان نهفته تا از نمایش دویارهٔ فرسته‌هایش خودداری شود.",
-  "lists.find_users_to_add": "یافتن کاربرانی برای افزودن",
-  "lists.list_members": "اعضای سیاهه",
-  "lists.list_members_count": "{count, plural,one {# عضو}other {# عضو}}",
-  "lists.list_name": "نام سیاهه",
-  "lists.new_list_name": "نام سیاههٔ جدید",
-  "lists.no_lists_yet": "هنوز هیچ سیاهه‌ای نیست.",
-  "lists.no_members_yet": "هنوز هیچ عضوی نیست.",
-  "lists.no_results_found": "هیچ نتیجه‌ای پیدا نشد.",
-  "lists.remove_member": "حذف",
+  "lists.edit.submit": "تغییر عنوان",
+  "lists.exclusive": "نهفتن این فرسته‌ها از خانه",
+  "lists.new.create": "افزودن سیاهه",
+  "lists.new.title_placeholder": "عنوان سیاههٔ جدید",
   "lists.replies_policy.followed": "هر کاربر پی‌گرفته",
   "lists.replies_policy.list": "اعضای سیاهه",
   "lists.replies_policy.none": "هیچ کدام",
-  "lists.save": "ذخیره",
-  "lists.search": "جست‌وجو",
-  "lists.show_replies_to": "شامل پاسخ از اعضای لیست به",
+  "lists.replies_policy.title": "نمایش پاسخ‌ها به:",
+  "lists.search": "جست‌وجو بین کسانی که پی‌گرفته‌اید",
+  "lists.subheading": "سیاهه‌هایتان",
   "load_pending": "{count, plural, one {# مورد جدید} other {# مورد جدید}}",
   "loading_indicator.label": "در حال بارگذاری…",
   "media_gallery.hide": "نهفتن",
@@ -557,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name}، {target} را گزارش داد",
   "notification.admin.sign_up": "{name} ثبت نام کرد",
   "notification.admin.sign_up.name_and_others": "{name} و {count, plural, one {# نفر دیگر} other {# نفر دیگر}} ثبت‌نام کردند",
-  "notification.annual_report.message": "آمار ‪#Wrapstodon‬ ‏{year} تان منتظر است! لحظه‌های به یاد ماندنی و نقاط پررنگ سال را روی ماستودون رونمایی کنید!",
-  "notification.annual_report.view": "دیدن ‪#Wrapstodon‬",
   "notification.favourite": "{name} فرسته‌تان را برگزید",
   "notification.favourite.name_and_others_with_link": "{name} و <a>{count, plural, one {# نفر دیگر} other {# نفر دیگر}}</a> فرسته‌تان را برگزیدند",
-  "notification.favourite_pm": "{name} ذکر خصوصی شما را مورد علاقه قرار داد",
-  "notification.favourite_pm.name_and_others_with_link": "{name} و <a>{count, plural, one {دیگری} other {دیگران}}</a> ذکر خصوصی شما را مورد علاقه قرار دادند",
   "notification.follow": "‫{name}‬ پی‌گیرتان شد",
   "notification.follow.name_and_others": "{name} و <a>{count, plural, other {#}} نفر دیگر</a> پیتان گرفتند",
   "notification.follow_request": "{name} درخواست پی‌گیریتان را داد",
@@ -667,21 +612,44 @@
   "notifications_permission_banner.enable": "به کار انداختن آگاهی‌های میزکار",
   "notifications_permission_banner.how_to_control": "برای دریافت آگاهی‌ها هنگام باز نبودن ماستودون، آگاهی‌های میزکار را به کار بیندازید. پس از به کار افتادنشان می‌توانید گونه‌های دقیق برهم‌کنش‌هایی که آگاهی‌های میزکار تولید می‌کنند را از {icon} بالا واپایید.",
   "notifications_permission_banner.title": "هرگز چیزی را از دست ندهید",
-  "onboarding.follows.back": "بازگشت",
-  "onboarding.follows.done": "انجام شد",
+  "onboarding.action.back": "برم گردان",
+  "onboarding.actions.back": "برم گردان",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "سلام #ماستودون!",
   "onboarding.follows.empty": "متأسفانه هم‌اکنون نتیجه‌ای قابل نمایش نیست. می‌توانید استفاده از جست‌وجو یا مرور صفحهٔ کاوش را برای یافتن افرادی برای پی‌گیری آزموده یا دوباره تلاش کنید.",
-  "onboarding.follows.search": "جست‌وجو",
-  "onboarding.follows.title": "پی گرفتن افرادی برای آغاز",
+  "onboarding.follows.lead": "فید خانگی شما اولین راه برای تجربه ماستودون است. هرچه افراد بیشتری را دنبال کنید، فعال تر و جالب تر خواهد بود. برای شروع، در اینجا چند پیشنهاد وجود دارد:",
+  "onboarding.follows.title": "فید خانه خود را شخصی کنید",
   "onboarding.profile.discoverable": "نمایه خود را قابل نمایش کنید",
   "onboarding.profile.discoverable_hint": "خواسته‌اید روی ماستودون کشف شوید. ممکن است فرسته‌هایتان در نتیحهٔ جست‌وجوها و فرسته‌های داغ ظاهر شده و نمایه‌تان به افرادی با علایق مشابهتان پیشنهاد شود.",
   "onboarding.profile.display_name": "نام نمایشی",
   "onboarding.profile.display_name_hint": "نام کامل یا نام باحالتان…",
+  "onboarding.profile.lead": "همواره می‌توانید این مورد را در تنظیمات که گزینه‌های شخصی سازی بیش‌تری نیز دارد کامل کنید.",
   "onboarding.profile.note": "درباره شما",
   "onboarding.profile.note_hint": "می‌توانید افراد دیگر را @نام‌بردن یا #برچسب بزنید…",
   "onboarding.profile.save_and_continue": "ذخیره کن و ادامه بده",
   "onboarding.profile.title": "تنظیم نمایه",
   "onboarding.profile.upload_avatar": "بازگذاری تصویر نمایه",
   "onboarding.profile.upload_header": "بارگذاری تصویر سردر نمایه",
+  "onboarding.share.lead": "بگذارید افراد بدانند چگونه می‌توانند در ماستادون بیابندتان!",
+  "onboarding.share.message": "من {username} روی #ماستودون هستم! مرا در {url} پی‌بگیرید",
+  "onboarding.share.next_steps": "گام‌های ممکن بعدی:",
+  "onboarding.share.title": "هم‌رسانی نمایه‌تان",
+  "onboarding.start.lead": "شما اکنون بخشی از ماستودون هستید، یک پلتفرم رسانه اجتماعی منحصر به فرد و غیرمتمرکز که در آن شما - نه یک الگوریتم - تجربه خود را مدیریت می کنید. بیایید شما را در این مرز اجتماعی جدید شروع کنیم:",
+  "onboarding.start.skip": "برای شروع به کمک نیاز ندارید؟",
+  "onboarding.start.title": "انجامش دادید!",
+  "onboarding.steps.follow_people.body": "دنبال کردن افراد جالب هدف ماستودون است.",
+  "onboarding.steps.follow_people.title": "فید خانه خود را شخصی کنید",
+  "onboarding.steps.publish_status.body": "با متن، عکس، ویدیو یا نظرسنجی به دنیا سلام کنید {emoji}",
+  "onboarding.steps.publish_status.title": "نخستین فرسته‌تان را بنویسید",
+  "onboarding.steps.setup_profile.body": "با داشتن یک نمایه جامع، تعاملات خود را تقویت کنید.",
+  "onboarding.steps.setup_profile.title": "پروفایل خود را شخصی سازی کنید",
+  "onboarding.steps.share_profile.body": "به دوستان خود اطلاع دهید که چگونه شما را در ماستودون پیدا کنند",
+  "onboarding.steps.share_profile.title": "نمایه ماستودون خود را به اشتراک بگذارید",
+  "onboarding.tips.2fa": "<strong>آیا می‌دانستید؟</strong> می‌توانید با پریایی هویت‌سنجی دو عاملی در تنظیمات حساب، حسابتان را ایمن کنید؟ این قابلیت با هر نرم‌افزار TOTP دلخواه کار کرده و نیازی به شماره تلفن ندارد!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>آیا می‌دانستید؟</strong> از آن‌جا که ماستودون نامتمرکز است، برخی نمایه‌ها که به آن‌ها برمی‌خورید روی کارسازهایی متفاوت از شما میزبانی می‌شوند و باز هم می‌توانید بدون مشکل با آن‌ها تعامل داشته باشید! کارسازشان در نیمه دوم نام کاربریشان است!",
+  "onboarding.tips.migration": "<strong>آیا می‌دانستید؟</strong> اگر احساس می‌کنید {domain} انتخاب کارساز خوبی برای آینده‌تان نیست، می‌توانید بدون از دست دادن پیگیرهایتان به کارساز ماستودون دیگری مهاجرت کنید. حتا می‌توانید کارساز خودتان را میزبانی کنید!",
+  "onboarding.tips.verification": "<strong>آیا می‌دانستید؟</strong> می‌توانید حسابتان را با گذاشتن پیوندی به نمایهٔ ماستودونتان روی پایگاه وب خود و افزودن پایگاه وبتان به نمایه‌تان تأیید کنید. بدون نیاز به هیچ کارمزد یا سندی!",
   "password_confirmation.exceeds_maxlength": "تأییدیه گذرواژه از حداکثر طول گذرواژه بیشتر است",
   "password_confirmation.mismatching": "تایید گذرواژه با گذرواژه مطابقت ندارد",
   "picture_in_picture.restore": "برگرداندن",
@@ -697,7 +665,7 @@
   "poll_button.remove_poll": "برداشتن نظرسنجی",
   "privacy.change": "تغییر محرمانگی فرسته",
   "privacy.direct.long": "هرکسی که در فرسته نام برده شده",
-  "privacy.direct.short": "ذکر خصوصی",
+  "privacy.direct.short": "افراد مشخّص",
   "privacy.private.long": "تنها پی‌گیرندگانتان",
   "privacy.private.short": "پی‌گیرندگان",
   "privacy.public.long": "هرکسی در و بیرون از ماستودون",
@@ -709,8 +677,8 @@
   "privacy_policy.title": "سیاست محرمانگی",
   "recommended": "پیشنهادشده",
   "refresh": "نوسازی",
-  "regeneration_indicator.please_stand_by": "لطفا منتظر باشید.",
-  "regeneration_indicator.preparing_your_home_feed": "در حال آماده کردن خوراک خانگی شما…",
+  "regeneration_indicator.label": "در حال بار شدن…",
+  "regeneration_indicator.sublabel": "خوراک خانگیتان دارد آماده می‌شود!",
   "relative_time.days": "{number} روز",
   "relative_time.full.days": "{number, plural, one {# روز} other {# روز}} پیش",
   "relative_time.full.hours": "{number, plural, one {# ساعت} other {# ساعت}} پیش",
@@ -794,11 +762,10 @@
   "search_results.accounts": "نمایه‌ها",
   "search_results.all": "همه",
   "search_results.hashtags": "برچسب‌ها",
-  "search_results.no_results": "هیچ نتیجه ای وجود ندارد.",
-  "search_results.no_search_yet": "سعی کنید پست ها، نمایه ها یا هشتگ ها را جستجو کنید.",
+  "search_results.nothing_found": "چیزی برای این عبارت جست‌وجو یافت نشد",
   "search_results.see_all": "دیدن همه",
   "search_results.statuses": "فرسته‌ها",
-  "search_results.title": "جستجو برای \"{q}\"",
+  "search_results.title": "جست‌وجو برای {q}",
   "server_banner.about_active_users": "افرادی که در ۳۰ روز گذشته از این کارساز استفاده کرده‌اند (کاربران فعّال ماهانه)",
   "server_banner.active_users": "کاربر فعّال",
   "server_banner.administered_by": "به مدیریت:",
@@ -850,7 +817,6 @@
   "status.reblogs.empty": "هنوز هیچ کسی این فرسته را تقویت نکرده است. وقتی کسی چنین کاری کند، این‌جا نمایش داده خواهد شد.",
   "status.redraft": "حذف و بازنویسی",
   "status.remove_bookmark": "برداشتن نشانک",
-  "status.remove_favourite": "حذف از موارد دلخواه",
   "status.replied_in_thread": "در رشته پاسخ داده",
   "status.replied_to": "به {name} پاسخ داد",
   "status.reply": "پاسخ",
@@ -872,9 +838,6 @@
   "subscribed_languages.target": "تغییر زبان‌های مشترک شده برای {target}",
   "tabs_bar.home": "خانه",
   "tabs_bar.notifications": "آگاهی‌ها",
-  "terms_of_service.effective_as_of": "اعمال شده از {date}",
-  "terms_of_service.title": "شرایط خدمات",
-  "terms_of_service.upcoming_changes_on": "تغییرات پیش رو در {date}",
   "time_remaining.days": "{number, plural, one {# روز} other {# روز}} باقی مانده",
   "time_remaining.hours": "{number, plural, one {# ساعت} other {# ساعت}} باقی مانده",
   "time_remaining.minutes": "{number, plural, one {# دقیقه} other {# دقیقه}} باقی مانده",
@@ -890,12 +853,26 @@
   "upload_button.label": "افزودن تصاویر، ویدیو یا یک پروندهٔ صوتی",
   "upload_error.limit": "از حد مجاز بارگذاری پرونده فراتر رفتید.",
   "upload_error.poll": "بارگذاری پرونده در نظرسنجی‌ها مجاز نیست.",
+  "upload_form.audio_description": "برای ناشنوایان توصیفش کنید",
+  "upload_form.description": "برای کم‌بینایان توصیفش کنید",
   "upload_form.drag_and_drop.instructions": "برای دریافت پیوست رسانه، space را فشار دهید یا وارد کنید. در حین کشیدن، از کلیدهای جهت دار برای حرکت دادن پیوست رسانه در هر جهت معین استفاده کنید. برای رها کردن ضمیمه رسانه در موقعیت جدید خود، مجدداً space یا enter را فشار دهید، یا برای لغو، escape را فشار دهید.",
   "upload_form.drag_and_drop.on_drag_cancel": "کشیدن لغو شد. پیوست رسانه {item} حذف شد.",
   "upload_form.drag_and_drop.on_drag_end": "پیوست رسانه {item} حذف شد.",
   "upload_form.drag_and_drop.on_drag_over": "پیوست رسانه {item} منتقل شد.",
   "upload_form.drag_and_drop.on_drag_start": "پیوست رسانه {item} برداشته شد.",
   "upload_form.edit": "ویرایش",
+  "upload_form.thumbnail": "تغییر بندانگشتی",
+  "upload_form.video_description": "برای کم‌بینایان یا ناشنوایان توصیفش کنید",
+  "upload_modal.analyzing_picture": "در حال پردازش تصویر…",
+  "upload_modal.apply": "اعمال",
+  "upload_modal.applying": "اعمال کردن…",
+  "upload_modal.choose_image": "گزینش تصویر",
+  "upload_modal.description_placeholder": "الا یا ایّها الساقی، ادر کأساً و ناولها",
+  "upload_modal.detect_text": "تشخیص متن درون عکس",
+  "upload_modal.edit_media": "ویرایش رسانه",
+  "upload_modal.hint": "حتی اگر تصویر بریده یا کوچک شود، نقطهٔ کانونی آن همیشه دیده خواهد شد. نقطهٔ کانونی را با کلیک یا جابه‌جا کردن آن تنظیم کنید.",
+  "upload_modal.preparing_ocr": "در حال آماده سازی OCR…",
+  "upload_modal.preview_label": "پیش‌نمایش ({ratio})",
   "upload_progress.label": "در حال بارگذاری...",
   "upload_progress.processing": "در حال پردازش…",
   "username.taken": "این نام کاربری گرفته شده. نام دیگری امتحان کنید",
@@ -905,6 +882,8 @@
   "video.expand": "گسترش ویدیو",
   "video.fullscreen": "تمام‌صفحه",
   "video.hide": "نهفتن ویدیو",
+  "video.mute": "خموشی صدا",
   "video.pause": "مکث",
-  "video.play": "پخش"
+  "video.play": "پخش",
+  "video.unmute": "لغو خموشی صدا"
 }
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index cc42780f94..f81a548a44 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -27,10 +27,9 @@
   "account.edit_profile": "Muokkaa profiilia",
   "account.enable_notifications": "Ilmoita minulle, kun @{name} julkaisee",
   "account.endorse": "Suosittele profiilissasi",
-  "account.featured.hashtags": "Aihetunnisteet",
-  "account.featured.posts": "Julkaisut",
   "account.featured_tags.last_status_at": "Viimeisin julkaisu {date}",
   "account.featured_tags.last_status_never": "Ei julkaisuja",
+  "account.featured_tags.title": "Käyttäjän {name} suosittelemat aihetunnisteet",
   "account.follow": "Seuraa",
   "account.follow_back": "Seuraa takaisin",
   "account.followers": "Seuraajat",
@@ -66,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}",
   "account.unblock": "Kumoa käyttäjän @{name} esto",
   "account.unblock_domain": "Kumoa verkkotunnuksen {domain} esto",
-  "account.unblock_domain_short": "Kumoa esto",
   "account.unblock_short": "Kumoa esto",
   "account.unendorse": "Kumoa suosittelu profiilissasi",
   "account.unfollow": "Älä seuraa",
@@ -88,33 +86,7 @@
   "alert.unexpected.message": "Tapahtui odottamaton virhe.",
   "alert.unexpected.title": "Hups!",
   "alt_text_badge.title": "Vaihtoehtoinen teksti",
-  "alt_text_modal.add_alt_text": "Lisää vaihtoehtoinen teksti",
-  "alt_text_modal.add_text_from_image": "Lisää teksti kuvasta",
-  "alt_text_modal.cancel": "Peruuta",
-  "alt_text_modal.change_thumbnail": "Vaihda pikkukuva",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Kuvaile tätä kuulovammallisille ihmisille…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Kuvaile tätä näkövammallisille ihmisille…",
-  "alt_text_modal.done": "Valmis",
   "announcement.announcement": "Tiedote",
-  "annual_report.summary.archetype.booster": "Tehostaja",
-  "annual_report.summary.archetype.lurker": "Lymyilijä",
-  "annual_report.summary.archetype.oracle": "Oraakkeli",
-  "annual_report.summary.archetype.pollster": "Mielipidetutkija",
-  "annual_report.summary.archetype.replier": "Sosiaalinen perhonen",
-  "annual_report.summary.followers.followers": "seuraajaa",
-  "annual_report.summary.followers.total": "{count} yhteensä",
-  "annual_report.summary.here_it_is": "Tässä on katsaus vuoteesi {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "suosikkeihin lisätyin julkaisu",
-  "annual_report.summary.highlighted_post.by_reblogs": "tehostetuin julkaisu",
-  "annual_report.summary.highlighted_post.by_replies": "julkaisu, jolla on eniten vastauksia",
-  "annual_report.summary.highlighted_post.possessive": "Käyttäjän {name}",
-  "annual_report.summary.most_used_app.most_used_app": "käytetyin sovellus",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "käytetyin aihetunniste",
-  "annual_report.summary.most_used_hashtag.none": "Ei mitään",
-  "annual_report.summary.new_posts.new_posts": "uutta julkaisua",
-  "annual_report.summary.percentile.text": "<topLabel>Olet osa huippujoukkoa, johon kuuluu</topLabel><percentage></percentage><bottomLabel>{domain}-käyttäjistä.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Emme kerro Bernie Sandersille.",
-  "annual_report.summary.thanks": "Kiitos, että olet osa Mastodonia!",
   "attachments_list.unprocessed": "(käsittelemätön)",
   "audio.hide": "Piilota ääni",
   "block_modal.remote_users_caveat": "Pyydämme palvelinta {domain} kunnioittamaan päätöstäsi. Myötämielisyyttä ei kuitenkaan taata, koska jotkin palvelimet voivat käsitellä estoja eri tavalla. Julkiset julkaisut voivat silti näkyä kirjautumattomille käyttäjille.",
@@ -138,7 +110,7 @@
   "bundle_column_error.routing.body": "Pyydettyä sivua ei löytynyt. Oletko varma, että osoitepalkin URL-osoite on oikein?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Sulje",
-  "bundle_modal_error.message": "Jotain meni pieleen tätä näyttöä ladattaessa.",
+  "bundle_modal_error.message": "Jotain meni pieleen tätä komponenttia ladattaessa.",
   "bundle_modal_error.retry": "Yritä uudelleen",
   "closed_registrations.other_server_instructions": "Koska Mastodon on hajautettu, voit luoda tilin toiselle palvelimelle ja olla silti vuorovaikutuksessa tämän kanssa.",
   "closed_registrations_modal.description": "Tilin luonti palvelimelle {domain} ei tällä hetkellä ole mahdollista, mutta ota huomioon, ettei Mastodonin käyttö edellytä juuri kyseisen palvelimen tiliä.",
@@ -149,16 +121,13 @@
   "column.blocks": "Estetyt käyttäjät",
   "column.bookmarks": "Kirjanmerkit",
   "column.community": "Paikallinen aikajana",
-  "column.create_list": "Luo lista",
   "column.direct": "Yksityismaininnat",
   "column.directory": "Selaa profiileja",
   "column.domain_blocks": "Estetyt verkkotunnukset",
-  "column.edit_list": "Muokkaa listaa",
   "column.favourites": "Suosikit",
   "column.firehose": "Livesyötteet",
   "column.follow_requests": "Seurantapyynnöt",
   "column.home": "Koti",
-  "column.list_members": "Hallitse listan jäseniä",
   "column.lists": "Listat",
   "column.mutes": "Mykistetyt käyttäjät",
   "column.notifications": "Ilmoitukset",
@@ -171,7 +140,6 @@
   "column_header.pin": "Kiinnitä",
   "column_header.show_settings": "Näytä asetukset",
   "column_header.unpin": "Irrota",
-  "column_search.cancel": "Peruuta",
   "column_subheading.settings": "Asetukset",
   "community.column_settings.local_only": "Vain paikalliset",
   "community.column_settings.media_only": "Vain media",
@@ -190,7 +158,7 @@
   "compose_form.poll.duration": "Äänestyksen kesto",
   "compose_form.poll.multiple": "Monivalinta",
   "compose_form.poll.option_placeholder": "Vaihtoehto {number}",
-  "compose_form.poll.single": "Yksittäisvalinta",
+  "compose_form.poll.single": "Valitse yksi",
   "compose_form.poll.switch_to_multiple": "Muuta äänestys monivalinnaksi",
   "compose_form.poll.switch_to_single": "Muuta äänestys yksittäisvalinnaksi",
   "compose_form.poll.type": "Tyyli",
@@ -214,16 +182,9 @@
   "confirmations.edit.confirm": "Muokkaa",
   "confirmations.edit.message": "Jos muokkaat viestiä nyt, se korvaa parhaillaan työstämäsi viestin. Haluatko varmasti jatkaa?",
   "confirmations.edit.title": "Korvataanko julkaisu?",
-  "confirmations.follow_to_list.confirm": "Seuraa ja lisää listaan",
-  "confirmations.follow_to_list.message": "Sinun on seurattava käyttäjää {name}, jotta voit lisätä hänet listaan.",
-  "confirmations.follow_to_list.title": "Seurataanko käyttäjää?",
   "confirmations.logout.confirm": "Kirjaudu ulos",
   "confirmations.logout.message": "Haluatko varmasti kirjautua ulos?",
   "confirmations.logout.title": "Kirjaudutaanko ulos?",
-  "confirmations.missing_alt_text.confirm": "Lisää vaihtoehtoinen teksti",
-  "confirmations.missing_alt_text.message": "Julkaisussasi on mediaa ilman vaihtoehtoista tekstiä. Kuvausten lisääminen auttaa tekemään sisällöstäsi saavutettavamman useammille ihmisille.",
-  "confirmations.missing_alt_text.secondary": "Julkaise silti",
-  "confirmations.missing_alt_text.title": "Lisätäänkö vaihtoehtoinen teksti?",
   "confirmations.mute.confirm": "Mykistä",
   "confirmations.redraft.confirm": "Poista ja palauta muokattavaksi",
   "confirmations.redraft.message": "Haluatko varmasti poistaa julkaisun ja tehdä siitä luonnoksen? Suosikit ja tehostukset menetetään, ja alkuperäisen julkaisun vastaukset jäävät orvoiksi.",
@@ -241,7 +202,7 @@
   "conversation.mark_as_read": "Merkitse luetuksi",
   "conversation.open": "Näytä keskustelu",
   "conversation.with": "{names} kanssa",
-  "copy_icon_button.copied": "Kopioitu leikepöydälle",
+  "copy_icon_button.copied": "Sisältö kopioitiin leikepöydälle",
   "copypaste.copied": "Kopioitu",
   "copypaste.copy_to_clipboard": "Kopioi leikepöydälle",
   "directory.federated": "Tunnetusta fediversumista",
@@ -252,10 +213,10 @@
   "disabled_account_banner.text": "Tilisi {disabledAccount} on tällä hetkellä poissa käytöstä.",
   "dismissable_banner.community_timeline": "Nämä ovat tuoreimpia julkaisuja käyttäjiltä, joiden tili on palvelimella {domain}.",
   "dismissable_banner.dismiss": "Hylkää",
-  "dismissable_banner.explore_links": "Näitä uutisia jaetaan tänään fediversumissa eniten. Uudemmat ja useampien eri käyttäjien lähettämät uutiset sijoittuvat korkeammalle.",
-  "dismissable_banner.explore_statuses": "Nämä julkaisut ympäri fediversumia saavat tänään huomiota. Uudemmat, tehostetummat ja suosikiksi lisätymmät julkaisut sijoittuvat korkeammalle.",
-  "dismissable_banner.explore_tags": "Nämä aihetunnisteet ympäri fediversumia saavat tänään huomiota. Useampien eri käyttäjien käyttämät aihetunnisteet sijoittuvat korkeammalle.",
-  "dismissable_banner.public_timeline": "Nämä ovat tuoreimpia julkaisuja fediversumin käyttäjiltä, joita seurataan palvelimella {domain}.",
+  "dismissable_banner.explore_links": "Näitä uutisia jaetaan tänään sosiaalisessa verkossa eniten. Uusimmat ja eri käyttäjien eniten lähettämät uutiset nousevat korkeammalle sijalle.",
+  "dismissable_banner.explore_statuses": "Nämä sosiaalisen verkon julkaisut keräävät tänään eniten huomiota. Uusimmat, tehostetuimmat ja suosikeiksi lisätyimmät julkaisut nousevat korkeammalle sijalle.",
+  "dismissable_banner.explore_tags": "Nämä sosiaalisen verkon aihetunnisteet keräävät tänään eniten huomiota. Useimman käyttäjän käyttämät aihetunnisteet nousevat korkeammalle sijalle.",
+  "dismissable_banner.public_timeline": "Nämä ovat tuoreimpia julkaisuja sosiaalisen verkon käyttäjiltä, joita seurataan palvelimella {domain}.",
   "domain_block_modal.block": "Estä palvelin",
   "domain_block_modal.block_account_instead": "Estä sen sijaan @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Tämän palvelimen käyttäjät eivät voi olla vuorovaikutuksessa vanhojen julkaisujesi kanssa.",
@@ -295,7 +256,6 @@
   "emoji_button.search_results": "Hakutulokset",
   "emoji_button.symbols": "Symbolit",
   "emoji_button.travel": "Matkailu ja paikat",
-  "empty_column.account_featured": "Tämä lista on tyhjä",
   "empty_column.account_hides_collections": "Käyttäjä on päättänyt pitää nämä tiedot yksityisinä",
   "empty_column.account_suspended": "Tili jäädytetty",
   "empty_column.account_timeline": "Ei viestejä täällä.",
@@ -313,6 +273,7 @@
   "empty_column.hashtag": "Tällä aihetunnisteella ei löydy vielä sisältöä.",
   "empty_column.home": "Kotiaikajanasi on tyhjä! Seuraa useampia käyttäjiä, niin näet enemmän sisältöä.",
   "empty_column.list": "Tässä listassa ei ole vielä mitään. Kun tämän listan jäsenet lähettävät uusia julkaisuja, ne näkyvät tässä.",
+  "empty_column.lists": "Sinulla ei ole vielä yhtään listaa. Kun luot sellaisen, näkyy se tässä.",
   "empty_column.mutes": "Et ole mykistänyt vielä yhtään käyttäjää.",
   "empty_column.notification_requests": "Olet ajan tasalla! Täällä ei ole mitään uutta kerrottavaa. Kun saat uusia ilmoituksia, ne näkyvät täällä asetustesi mukaisesti.",
   "empty_column.notifications": "Sinulla ei ole vielä ilmoituksia. Kun muut ovat vuorovaikutuksessa kanssasi, näet sen täällä.",
@@ -323,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Yritä poistaa ne käytöstä, ja virkistä sitten sivunlataus. Mikäli ongelma jatkuu, voit mahdollisesti käyttää Mastodonia eri selaimella tai natiivilla sovelluksella.",
   "errors.unexpected_crash.copy_stacktrace": "Kopioi pinon jäljitys leikepöydälle",
   "errors.unexpected_crash.report_issue": "Ilmoita ongelmasta",
+  "explore.search_results": "Hakutulokset",
   "explore.suggested_follows": "Käyttäjät",
   "explore.title": "Selaa",
   "explore.trending_links": "Uutiset",
@@ -372,16 +334,13 @@
   "footer.about": "Tietoja",
   "footer.directory": "Profiilihakemisto",
   "footer.get_app": "Hanki sovellus",
+  "footer.invite": "Kutsu käyttäjiä",
   "footer.keyboard_shortcuts": "Pikanäppäimet",
   "footer.privacy_policy": "Tietosuojakäytäntö",
   "footer.source_code": "Näytä lähdekoodi",
   "footer.status": "Tila",
-  "footer.terms_of_service": "Käyttöehdot",
   "generic.saved": "Tallennettu",
   "getting_started.heading": "Näin pääset alkuun",
-  "hashtag.admin_moderation": "Avaa tunnisteen #{name} moderointinäkymä",
-  "hashtag.browse": "Selaa julkaisuja tunnisteella #{hashtag}",
-  "hashtag.browse_from_account": "Selaa julkaisuja käyttäjältä @{name} tunnisteella #{hashtag}",
   "hashtag.column_header.tag_mode.all": "ja {additional}",
   "hashtag.column_header.tag_mode.any": "tai {additional}",
   "hashtag.column_header.tag_mode.none": "ilman {additional}",
@@ -395,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one{{counter} julkaisu} other {{counter} julkaisua}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}} tänään",
   "hashtag.follow": "Seuraa aihetunnistetta",
-  "hashtag.mute": "Mykistä #{hashtag}",
   "hashtag.unfollow": "Lopeta aihetunnisteen seuraaminen",
   "hashtags.and_other": "…ja {count, plural, other {# lisää}}",
   "hints.profiles.followers_may_be_missing": "Tämän profiilin seuraajia saattaa puuttua.",
@@ -424,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Sivuutetaanko ilmoitukset käyttäjiltä, jotka eivät seuraa sinua?",
   "ignore_notifications_modal.not_following_title": "Sivuutetaanko ilmoitukset käyttäjiltä, joita et seuraa?",
   "ignore_notifications_modal.private_mentions_title": "Sivuutetaanko ilmoitukset pyytämättömistä yksityismaininnoista?",
-  "info_button.label": "Ohje",
-  "info_button.what_is_alt_text": "<h1>Mikä vaihtoehtoinen teksti on?</h1> <p>Vaihtoehtoinen teksti tarjoaa kuvauksen kuvista ihmisille, joilla on näkövamma tai matalan kaistanleveyden yhteys tai jotka kaipaavat lisäkontekstia.</p> <p>Voit parantaa saavutettavuutta ja ymmärrettävyyttä kaikkien näkökulmasta kirjoittamalla selkeän, tiiviin ja objektiivisen vaihtoehtoisen tekstin.</p> <ul> <li>Ota mukaan tärkeät elementit</li> <li>Tiivistä kuvissa oleva teksti</li> <li>Käytä tavallisia lauserakenteita</li> <li>Vältä turhaa tietoa</li> <li>Keskity trendeihin ja keskeisiin tuloksiin monimutkaisissa visuaalisissa esityksissä (kuten kaavioissa tai kartoissa)</li> </ul>",
-  "interaction_modal.action.favourite": "Jotta voit jatkaa, sinun tulee lisätä julkaisu suosikiksesi omalta tililtäsi.",
-  "interaction_modal.action.follow": "Jotta voit jatkaa, sinun tulee seurata käyttäjää omalta tililtäsi.",
-  "interaction_modal.action.reblog": "Jotta voit jatkaa, sinun tulee uudelleenjulkaista omalta tililtäsi.",
-  "interaction_modal.action.reply": "Jotta voit jatkaa, sinun tulee vastata omalta tililtäsi.",
-  "interaction_modal.action.vote": "Jotta voit jatkaa, sinun tulee äänestää omalta tililtäsi.",
-  "interaction_modal.go": "Siirry",
-  "interaction_modal.no_account_yet": "Eikö sinulla ole vielä tiliä?",
+  "interaction_modal.description.favourite": "Mastodon-tilillä voit lisätä tämän julkaisun suosikkeihisi osoittaaksesi tekijälle arvostavasi sitä ja tallentaaksesi sen tulevaa käyttöä varten.",
+  "interaction_modal.description.follow": "Mastodon-tilillä voit seurata käyttäjää {name} saadaksesi hänen julkaisunsa kotisyötteeseesi.",
+  "interaction_modal.description.reblog": "Mastodon-tilillä voit tehostaa tätä julkaisua jakaaksesi sen seuraajiesi kanssa.",
+  "interaction_modal.description.reply": "Mastodon-tilillä voit vastata tähän julkaisuun.",
+  "interaction_modal.login.action": "Siirry kotiin",
+  "interaction_modal.login.prompt": "Kotipalvelimesi verkkotunnus, kuten mastodon.social",
+  "interaction_modal.no_account_yet": "Etkö ole vielä Mastodonissa?",
   "interaction_modal.on_another_server": "Toisella palvelimella",
   "interaction_modal.on_this_server": "Tällä palvelimella",
+  "interaction_modal.sign_in": "Et ole kirjautunut tälle palvelimelle. Millä palvelimella tilisi sijaitsee?",
+  "interaction_modal.sign_in_hint": "Vihje: Se on sama verkkosivusto, jolle rekisteröidyit. Jos et muista palvelintasi, etsi tervetulosähköposti saapuneista viesteistäsi. Voit syöttää myös koko käyttäjätunnuksesi! (Esimerkki: @Mastodon@Mastodon.social)",
   "interaction_modal.title.favourite": "Lisää käyttäjän {name} julkaisu suosikkeihin",
   "interaction_modal.title.follow": "Seuraa käyttäjää {name}",
   "interaction_modal.title.reblog": "Tehosta käyttäjän {name} julkaisua",
   "interaction_modal.title.reply": "Vastaa käyttäjän {name} julkaisuun",
-  "interaction_modal.title.vote": "Osallistu käyttäjän {name} äänestykseen",
-  "interaction_modal.username_prompt": "Esim. {example}",
   "intervals.full.days": "{number, plural, one {# päivä} other {# päivää}}",
   "intervals.full.hours": "{number, plural, one {# tunti} other {# tuntia}}",
   "intervals.full.minutes": "{number, plural, one {# minuutti} other {# minuuttia}}",
@@ -476,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Näytä tai piilota sisältövaroituksella merkitty teksti",
   "keyboard_shortcuts.toggle_sensitivity": "Näytä tai piilota media",
   "keyboard_shortcuts.toot": "Luo uusi julkaisu",
-  "keyboard_shortcuts.translate": "Käännä julkaisu",
   "keyboard_shortcuts.unfocus": "Poistu kirjoitus- tai hakukentästä",
   "keyboard_shortcuts.up": "Siirry luettelossa taaksepäin",
   "lightbox.close": "Sulje",
@@ -489,32 +444,20 @@
   "link_preview.author": "Tehnyt {name}",
   "link_preview.more_from_author": "Lisää tekijältä {name}",
   "link_preview.shares": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}",
-  "lists.add_member": "Lisää",
-  "lists.add_to_list": "Lisää listaan",
-  "lists.add_to_lists": "Lisää {name} listaan",
-  "lists.create": "Luo",
-  "lists.create_a_list_to_organize": "Luo uusi lista kotisyötteesi järjestämiseksi",
-  "lists.create_list": "Luo lista",
+  "lists.account.add": "Lisää listaan",
+  "lists.account.remove": "Poista listasta",
   "lists.delete": "Poista lista",
-  "lists.done": "Valmis",
   "lists.edit": "Muokkaa listaa",
-  "lists.exclusive": "Piilota jäsenet kotisyötteestä",
-  "lists.exclusive_hint": "Jos joku on tässä listassa, piilota hänet kotisyötteestäsi, jotta et näe hänen julkaisujaan kahteen kertaan.",
-  "lists.find_users_to_add": "Etsi lisättäviä käyttäjiä",
-  "lists.list_members": "Listan jäsenet",
-  "lists.list_members_count": "{count, plural, one {# jäsen} other {# jäsentä}}",
-  "lists.list_name": "Listan nimi",
-  "lists.new_list_name": "Uuden listan nimi",
-  "lists.no_lists_yet": "Ei vielä listoja.",
-  "lists.no_members_yet": "Ei vielä jäseniä.",
-  "lists.no_results_found": "Tuloksia ei löytynyt.",
-  "lists.remove_member": "Poista",
+  "lists.edit.submit": "Vaihda nimi",
+  "lists.exclusive": "Piilota nämä julkaisut kotisyötteestä",
+  "lists.new.create": "Lisää lista",
+  "lists.new.title_placeholder": "Uuden listan nimi",
   "lists.replies_policy.followed": "Jokaiselle seurattavalle käyttäjälle",
   "lists.replies_policy.list": "Listan jäsenille",
   "lists.replies_policy.none": "Ei kellekään",
-  "lists.save": "Tallenna",
-  "lists.search": "Haku",
-  "lists.show_replies_to": "Sisällytä listan jäsenten vastaukset kohteeseen",
+  "lists.replies_policy.title": "Näytä vastaukset:",
+  "lists.search": "Hae seuraamistasi käyttäjistä",
+  "lists.subheading": "Omat listasi",
   "load_pending": "{count, plural, one {# uusi kohde} other {# uutta kohdetta}}",
   "loading_indicator.label": "Ladataan…",
   "media_gallery.hide": "Piilota",
@@ -563,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} raportoi käyttäjän {target}",
   "notification.admin.sign_up": "{name} rekisteröityi",
   "notification.admin.sign_up.name_and_others": "{name} ja {count, plural, one {# muu} other {# muuta}} rekisteröityivät",
-  "notification.annual_report.message": "Vuoden {year} #Wrapstodon odottaa! Paljasta vuotesi kohokohdat ikimuistoiset hetket Mastodonissa!",
-  "notification.annual_report.view": "Näytä #Wrapstodon",
   "notification.favourite": "{name} lisäsi julkaisusi suosikkeihinsa",
   "notification.favourite.name_and_others_with_link": "{name} ja <a>{count, plural, one {# muu} other {# muuta}}</a> lisäsivät julkaisusi suosikkeihinsa",
-  "notification.favourite_pm": "{name} lisäsi yksityismainintasi suosikkeihinsa",
-  "notification.favourite_pm.name_and_others_with_link": "{name} ja <a>{count, plural, one {# muu} other {# muuta}}</a> lisäsivät yksityismainintasi suosikkeihinsa",
   "notification.follow": "{name} seurasi sinua",
   "notification.follow.name_and_others": "{name} ja <a>{count, plural, one {# muu} other {# muuta}}</a> seurasivat sinua",
   "notification.follow_request": "{name} on pyytänyt lupaa seurata sinua",
@@ -631,7 +570,7 @@
   "notifications.column_settings.follow_request": "Uudet seurantapyynnöt:",
   "notifications.column_settings.group": "Ryhmitä",
   "notifications.column_settings.mention": "Maininnat:",
-  "notifications.column_settings.poll": "Äänestystulokset:",
+  "notifications.column_settings.poll": "Äänestyksen tulokset:",
   "notifications.column_settings.push": "Puskuilmoitukset",
   "notifications.column_settings.reblog": "Tehostukset:",
   "notifications.column_settings.show": "Näytä sarakkeessa",
@@ -645,7 +584,7 @@
   "notifications.filter.favourites": "Suosikit",
   "notifications.filter.follows": "Seuraamiset",
   "notifications.filter.mentions": "Maininnat",
-  "notifications.filter.polls": "Äänestystulokset",
+  "notifications.filter.polls": "Äänestyksen tulokset",
   "notifications.filter.statuses": "Päivitykset seuraamiltasi käyttäjiltä",
   "notifications.grant_permission": "Myönnä käyttöoikeus.",
   "notifications.group": "{count} ilmoitusta",
@@ -673,21 +612,44 @@
   "notifications_permission_banner.enable": "Ota työpöytäilmoitukset käyttöön",
   "notifications_permission_banner.how_to_control": "Saadaksesi ilmoituksia, kun Mastodon ei ole auki, ota työpöytäilmoitukset käyttöön. Voit hallita tarkasti, mistä saat työpöytäilmoituksia kun ilmoitukset on otettu käyttöön yllä olevan {icon}-painikkeen kautta.",
   "notifications_permission_banner.title": "Älä anna minkään mennä ohi",
-  "onboarding.follows.back": "Takaisin",
-  "onboarding.follows.done": "Valmis",
+  "onboarding.action.back": "Palaa takaisin",
+  "onboarding.actions.back": "Palaa takaisin",
+  "onboarding.actions.go_to_explore": "Siirry suosittujen aiheiden syötteeseen",
+  "onboarding.actions.go_to_home": "Siirry kotisyötteeseeni",
+  "onboarding.compose.template": "Tervehdys #Mastodon!",
   "onboarding.follows.empty": "Valitettavasti tuloksia ei voida näyttää juuri nyt. Voit kokeilla hakua tai selata tutustumissivua löytääksesi seurattavaa tai yrittää myöhemmin uudelleen.",
-  "onboarding.follows.search": "Haku",
-  "onboarding.follows.title": "Aloita seuraamalla käyttäjiä",
+  "onboarding.follows.lead": "Kokoat oman kotisyötteesi itse. Mitä enemmän ihmisiä seuraat, sitä aktiivisempi ja kiinnostavampi syöte on. Nämä profiilit voivat olla alkuun hyvä lähtökohta – voit milloin tahansa myös lopettaa niiden seuraamisen:",
+  "onboarding.follows.title": "Mukauta kotisyötettäsi",
   "onboarding.profile.discoverable": "Aseta profiilini löydettäväksi",
   "onboarding.profile.discoverable_hint": "Kun olet määrittänyt itsesi löydettäväksi Mastodonista, julkaisusi voivat näkyä hakutuloksissa ja suosituissa kohteissa. Lisäksi profiiliasi voidaan ehdottaa käyttäjille, jotka ovat kiinnostuneita kanssasi samoista aiheista.",
   "onboarding.profile.display_name": "Näyttönimi",
   "onboarding.profile.display_name_hint": "Koko nimesi tai lempinimesi…",
+  "onboarding.profile.lead": "Voit viimeistellä tämän milloin tahansa asetuksista. Sieltä löydät myös lisää mukautusvaihtoehtoja.",
   "onboarding.profile.note": "Elämäkerta",
   "onboarding.profile.note_hint": "Voit @mainita muita käyttäjiä tai #aihetunnisteita…",
   "onboarding.profile.save_and_continue": "Tallenna ja jatka",
   "onboarding.profile.title": "Profiilin määritys",
   "onboarding.profile.upload_avatar": "Lähetä profiilikuva",
   "onboarding.profile.upload_header": "Lähetä profiilin otsakekuva",
+  "onboarding.share.lead": "Kerro ihmisille, kuinka he voivat löytää sinut Mastodonista!",
+  "onboarding.share.message": "Olen {username} #Mastodon⁠issa! Seuraa minua osoitteessa {url}",
+  "onboarding.share.next_steps": "Mahdolliset seuraavat vaiheet:",
+  "onboarding.share.title": "Jaa profiilisi",
+  "onboarding.start.lead": "Uusi Mastodon-tilisi on nyt valmiina käyttöön. Kyseessä on ainutlaatuinen, hajautettu sosiaalisen median alusta, jolla sinä itse – algoritmin sijaan – määrität käyttökokemuksesi. Näin hyödyt Mastodonista eniten:",
+  "onboarding.start.skip": "Haluatko hypätä suoraan eteenpäin ilman alkuunpääsyohjeistuksia?",
+  "onboarding.start.title": "Olet tehnyt sen!",
+  "onboarding.steps.follow_people.body": "Mastodonissa on kyse kiinnostavien käyttäjien seuraamisesta.",
+  "onboarding.steps.follow_people.title": "Mukauta kotisyötettäsi",
+  "onboarding.steps.publish_status.body": "Tervehdi maailmaa sanoin, kuvin tai äänestyksin {emoji}",
+  "onboarding.steps.publish_status.title": "Laadi ensimmäinen julkaisusi",
+  "onboarding.steps.setup_profile.body": "Täydentämällä profiilisi tietoja tehostat vuorovaikutteisuutta.",
+  "onboarding.steps.setup_profile.title": "Mukauta profiiliasi",
+  "onboarding.steps.share_profile.body": "Kerro kavereillesi, kuinka sinut löytää Mastodonista",
+  "onboarding.steps.share_profile.title": "Jaa Mastodon-profiilisi",
+  "onboarding.tips.2fa": "<strong>Tiesitkö?</strong> Voit suojata tilisi ottamalla kaksivaiheisen todennuksen käyttöön tilisi asetuksista. Se toimii millä tahansa TOTP-sovelluksella, eikä sen käyttö edellytä puhelinnumeron luovuttamista!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Tiesitkö?</strong> Koska Mastodon on hajautettu, osa kohtaamistasi profiileista sijaitsee muilla kuin sinun palvelimellasi. Voit silti viestiä saumattomasti heidän kanssaan! Heidän palvelimensa mainitaan käyttäjätunnuksen jälkiosassa!",
+  "onboarding.tips.migration": "<strong>Tiesitkö?</strong> Jos koet, ettei {domain} ole jatkossa itsellesi hyvä palvelinvalinta, voit siirtyä toiselle Mastodon-palvelimelle menettämättä seuraajiasi. Voit jopa isännöidä omaa palvelintasi!",
+  "onboarding.tips.verification": "<strong>Tiesitkö?</strong> Voit vahvistaa tilisi lisäämällä omalle verkkosivustollesi linkin Mastodon-profiiliisi ja lisäämällä sitten verkkosivustosi osoitteen Mastodon-profiilisi lisäkenttään. Tämä ei maksa mitään, eikä sinun tarvitse lähetellä asiakirjoja!",
   "password_confirmation.exceeds_maxlength": "Salasanan vahvistus ylittää salasanan enimmäispituuden",
   "password_confirmation.mismatching": "Salasanan vahvistus ei täsmää",
   "picture_in_picture.restore": "Laita se takaisin",
@@ -703,7 +665,7 @@
   "poll_button.remove_poll": "Poista äänestys",
   "privacy.change": "Muuta julkaisun näkyvyyttä",
   "privacy.direct.long": "Kaikki tässä julkaisussa mainitut",
-  "privacy.direct.short": "Yksityismaininta",
+  "privacy.direct.short": "Tietyt käyttäjät",
   "privacy.private.long": "Vain seuraajasi",
   "privacy.private.short": "Seuraajat",
   "privacy.public.long": "Kuka tahansa Mastodonissa ja sen ulkopuolella",
@@ -715,8 +677,8 @@
   "privacy_policy.title": "Tietosuojakäytäntö",
   "recommended": "Suositellaan",
   "refresh": "Päivitä",
-  "regeneration_indicator.please_stand_by": "Ole valmiina.",
-  "regeneration_indicator.preparing_your_home_feed": "Kotisyötettäsi valmistellaan…",
+  "regeneration_indicator.label": "Ladataan…",
+  "regeneration_indicator.sublabel": "Kotisyötettäsi valmistellaan!",
   "relative_time.days": "{number} pv",
   "relative_time.full.days": "{number, plural, one {# päivä} other {# päivää}} sitten",
   "relative_time.full.hours": "{number, plural, one {# tunti} other {# tuntia}} sitten",
@@ -800,11 +762,10 @@
   "search_results.accounts": "Profiilit",
   "search_results.all": "Kaikki",
   "search_results.hashtags": "Aihetunnisteet",
-  "search_results.no_results": "Ei tuloksia.",
-  "search_results.no_search_yet": "Kokeile hakea julkaisuja, profiileja tai aihetunnisteita.",
+  "search_results.nothing_found": "Hakusi ei tuottanut tuloksia",
   "search_results.see_all": "Näytä kaikki",
   "search_results.statuses": "Julkaisut",
-  "search_results.title": "Haku ”{q}”",
+  "search_results.title": "Hae {q}",
   "server_banner.about_active_users": "Palvelimen käyttäjät viimeisten 30 päivän ajalta (kuukauden aktiiviset käyttäjät)",
   "server_banner.active_users": "aktiivista käyttäjää",
   "server_banner.administered_by": "Ylläpitäjä:",
@@ -856,7 +817,6 @@
   "status.reblogs.empty": "Kukaan ei ole vielä tehostanut tätä julkaisua. Kun joku tekee niin, tulee hän tähän näkyviin.",
   "status.redraft": "Poista ja palauta muokattavaksi",
   "status.remove_bookmark": "Poista kirjanmerkki",
-  "status.remove_favourite": "Poista suosikeista",
   "status.replied_in_thread": "Vastasi ketjuun",
   "status.replied_to": "Vastaus käyttäjälle {name}",
   "status.reply": "Vastaa",
@@ -878,9 +838,6 @@
   "subscribed_languages.target": "Vaihda tilattuja kieliä käyttäjältä {target}",
   "tabs_bar.home": "Koti",
   "tabs_bar.notifications": "Ilmoitukset",
-  "terms_of_service.effective_as_of": "Tulee voimaan {date}",
-  "terms_of_service.title": "Käyttöehdot",
-  "terms_of_service.upcoming_changes_on": "Tulevia muutoksia {date}",
   "time_remaining.days": "{number, plural, one {# päivä} other {# päivää}} jäljellä",
   "time_remaining.hours": "{number, plural, one {# tunti} other {# tuntia}} jäljellä",
   "time_remaining.minutes": "{number, plural, one {# minuutti} other {# minuuttia}} jäljellä",
@@ -896,12 +853,26 @@
   "upload_button.label": "Lisää kuvia, video tai äänitiedosto",
   "upload_error.limit": "Tiedostolähetysten rajoitus ylitetty.",
   "upload_error.poll": "Tiedostojen lisääminen äänestysten oheen ei ole sallittua.",
+  "upload_form.audio_description": "Kuvaile sisältöä kuuroille ja kuulorajoitteisille",
+  "upload_form.description": "Kuvaile sisältöä sokeille ja näkörajoitteisille",
   "upload_form.drag_and_drop.instructions": "Valitse medialiite painamalla välilyöntiä tai enteriä. Vetäessäsi käytä nuolinäppäimiä siirtääksesi medialiitettä vastaavaan suuntaan. Paina välilyöntiä tai enteriä uudelleen pudottaaksesi medialiitteen uuteen kohtaansa, tai peru siirto painamalla escape-näppäintä.",
   "upload_form.drag_and_drop.on_drag_cancel": "Veto peruttiin. Medialiitettä {item} ei siirretty.",
   "upload_form.drag_and_drop.on_drag_end": "Medialiite {item} pudotettiin.",
   "upload_form.drag_and_drop.on_drag_over": "Medialiitettä {item} siirrettiin.",
   "upload_form.drag_and_drop.on_drag_start": "Valittiin medialiite {item}.",
   "upload_form.edit": "Muokkaa",
+  "upload_form.thumbnail": "Vaihda pienoiskuva",
+  "upload_form.video_description": "Kuvaile sisältöä kuuroille, kuulorajoitteisille, sokeille tai näkörajoitteisille",
+  "upload_modal.analyzing_picture": "Analysoidaan kuvaa…",
+  "upload_modal.apply": "Käytä",
+  "upload_modal.applying": "Otetaan käyttöön…",
+  "upload_modal.choose_image": "Valitse kuva",
+  "upload_modal.description_placeholder": "Nopea ruskea kettu hyppää laiskan koiran yli",
+  "upload_modal.detect_text": "Tunnista teksti kuvasta",
+  "upload_modal.edit_media": "Muokkaa mediaa",
+  "upload_modal.hint": "Napsauta tai vedä ympyrä esikatselussa valitaksesi keskipiste, joka näkyy aina pienoiskuvissa.",
+  "upload_modal.preparing_ocr": "Valmistellaan tekstintunnistusta…",
+  "upload_modal.preview_label": "Esikatselu ({ratio})",
   "upload_progress.label": "Lähetetään…",
   "upload_progress.processing": "Käsitellään…",
   "username.taken": "Tämä käyttäjänimi on jo käytössä. Kokeile toista",
@@ -911,12 +882,8 @@
   "video.expand": "Laajenna video",
   "video.fullscreen": "Koko näyttö",
   "video.hide": "Piilota video",
-  "video.mute": "Mykistä",
+  "video.mute": "Mykistä ääni",
   "video.pause": "Tauko",
   "video.play": "Toista",
-  "video.skip_backward": "Siirry taaksepäin",
-  "video.skip_forward": "Siirry eteenpäin",
-  "video.unmute": "Poista mykistys",
-  "video.volume_down": "Vähennä äänenvoimakkuutta",
-  "video.volume_up": "Lisää äänenvoimakkuutta"
+  "video.unmute": "Palauta ääni"
 }
diff --git a/app/javascript/mastodon/locales/fil.json b/app/javascript/mastodon/locales/fil.json
index c13d0a8afe..7b8a5e8f6b 100644
--- a/app/javascript/mastodon/locales/fil.json
+++ b/app/javascript/mastodon/locales/fil.json
@@ -29,6 +29,7 @@
   "account.endorse": "I-tampok sa profile",
   "account.featured_tags.last_status_at": "Huling post noong {date}",
   "account.featured_tags.last_status_never": "Walang mga post",
+  "account.featured_tags.title": "Nakatampok na hashtag ni {name}",
   "account.follow": "Sundan",
   "account.follow_back": "Sundan pabalik",
   "account.followers": "Mga tagasunod",
@@ -80,6 +81,7 @@
   "bundle_column_error.routing.body": "Hindi mahanap ang hiniling na pahina. Sigurado ka ba na ang URL sa address bar ay tama?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "I-sara",
+  "bundle_modal_error.message": "May nangyaring mali habang kinakarga ang bahaging ito.",
   "bundle_modal_error.retry": "Subukang muli",
   "closed_registrations.other_server_instructions": "Dahil desentralisado ang Mastodon, pwede kang gumawa ng account sa iba pang server at makipag-ugnayan pa rin dito.",
   "closed_registrations_modal.description": "Hindi pa pwedeng gumawa ng account sa {domain}, pero tandaan na hindi mo kailangan ng account partikular sa {domain} para gamitin ang Mastodon.",
@@ -126,6 +128,7 @@
   "compose_form.placeholder": "Anong nangyari?",
   "compose_form.poll.duration": "Tagal ng botohan",
   "compose_form.poll.multiple": "Maraming pagpipilian",
+  "compose_form.poll.single": "Piliin ang isa",
   "compose_form.reply": "Tumugon",
   "compose_form.spoiler.marked": "Tanggalin ang babala sa nilalaman",
   "compose_form.spoiler.unmarked": "Idagdag ang babala sa nilalaman",
@@ -151,6 +154,10 @@
   "disabled_account_banner.text": "Ang iyong account na {disabledAccount} ay hindi pinapagana ngayon.",
   "dismissable_banner.community_timeline": "Ito ang mga pinakamakailang nakapublikong post mula sa mga taong ang mga account hinohost ng {domain}.",
   "dismissable_banner.dismiss": "Alisin",
+  "dismissable_banner.explore_links": "Ito ang mga balitang kwento na pinaka-binabahagi sa social web ngayon. Ang mga mas bagong balitang kwento na pinost ng mas marami pang mga iba't ibang tao ay tinataasan ng antas.",
+  "dismissable_banner.explore_statuses": "Ito ang mga sumisikat na mga post sa iba't ibang bahagi ng social web ngayon. Ang mga mas bagong post na mas marami ang mga pagpapalakas at paborito ay tinataasan ng antas.",
+  "dismissable_banner.explore_tags": "Ito ang mga sumisikat na mga hashtag sa iba't ibang bahagi ng social web ngayon. Ang mga hashtag ginagamit ng mas maraming mga iba't ibang tao ay tinataasan ng antas.",
+  "dismissable_banner.public_timeline": "Ito ang mga pinakamakailang nakapublikong post mula sa mga taong nasa social web na sinusundan ng mga tao sa {domain}.",
   "domain_block_modal.block": "Harangan ang serbiro",
   "domain_block_modal.they_wont_know": "Hindi nila malalaman na hinarang sila.",
   "domain_block_modal.title": "Harangan ang domain?",
@@ -191,8 +198,10 @@
   "empty_column.hashtag": "Wala pang laman ang hashtag na ito.",
   "empty_column.home": "Walang laman ang timeline ng tahanan mo! Sumunod sa marami pang tao para mapunan ito.",
   "empty_column.list": "Wala pang laman ang listahang ito. Kapag naglathala ng mga bagong post ang mga miyembro ng listahang ito, makikita iyon dito.",
+  "empty_column.lists": "Wala ka pang mga listahan. Kapag gumawa ka ng isa, makikita yun dito.",
   "empty_column.notification_requests": "Malinis na lahat! Walang anuman dito. Kapag nakatanggap ka ng mga bagong abiso, makikita sila dito na batay sa iyong mga setting.",
   "errors.unexpected_crash.report_issue": "Iulat ang isyu",
+  "explore.search_results": "Mga resulta ng paghahanap",
   "explore.suggested_follows": "Mga tao",
   "explore.title": "Tuklasin",
   "explore.trending_links": "Mga balita",
@@ -221,10 +230,11 @@
   "hashtags.and_other": "…at {count, plural, one {# iba pa} other {# na iba pa}}",
   "home.column_settings.show_replies": "Ipakita ang mga tugon",
   "home.pending_critical_update.body": "Mangyaring i-update ang iyong serbiro ng Mastodon sa lalong madaling panahon!",
+  "interaction_modal.login.action": "Iuwi mo ako",
+  "interaction_modal.no_account_yet": "Wala sa Mastodon?",
   "interaction_modal.on_another_server": "Sa ibang serbiro",
   "interaction_modal.on_this_server": "Sa serbirong ito",
   "interaction_modal.title.follow": "Sundan si {name}",
-  "interaction_modal.title.vote": "Bumoto sa botohan ni {name}",
   "intervals.full.days": "{number, plural, one {# araw} other {# na araw}}",
   "intervals.full.hours": "{number, plural, one {# oras} other {# na oras}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# na minuto}}",
@@ -238,8 +248,15 @@
   "lightbox.next": "Susunod",
   "lightbox.previous": "Nakaraan",
   "link_preview.author": "Ni/ng {name}",
+  "lists.account.add": "Idagdag sa talaan",
+  "lists.account.remove": "Tanggalin mula sa talaan",
   "lists.delete": "Burahin ang listahan",
+  "lists.edit.submit": "Baguhin ang pamagat",
+  "lists.new.create": "Idagdag sa talaan",
+  "lists.new.title_placeholder": "Bagong pangalan ng talaan",
   "lists.replies_policy.none": "Walang simuman",
+  "lists.replies_policy.title": "Ipakita ang mga tugon sa:",
+  "lists.subheading": "Iyong mga talaan",
   "loading_indicator.label": "Kumakarga…",
   "media_gallery.hide": "Itago",
   "mute_modal.hide_from_notifications": "Itago mula sa mga abiso",
@@ -288,8 +305,11 @@
   "notifications.policy.accept_hint": "Ipakita sa mga abiso",
   "notifications.policy.filter_not_followers_title": "Mga taong hindi ka susundan",
   "notifications.policy.filter_not_following_title": "Mga taong hindi mo sinusundan",
+  "onboarding.action.back": "Ibalik mo ako",
+  "onboarding.actions.back": "Ibalik mo ako",
   "onboarding.profile.note_hint": "Maaari mong @bangitin ang ibang mga tao o mga #hashtag…",
   "onboarding.profile.save_and_continue": "Iimbak at magpatuloy",
+  "onboarding.share.next_steps": "Mga posibleng susunod na hakbang:",
   "picture_in_picture.restore": "Ilagay ito pabalik",
   "poll.closed": "Sarado",
   "poll.reveal": "Ipakita ang mga resulta",
@@ -303,6 +323,7 @@
   "privacy.unlisted.short": "Hindi nakalista",
   "privacy_policy.last_updated": "Huling nabago noong {date}",
   "recommended": "Inirekomenda",
+  "regeneration_indicator.label": "Kumakarga…",
   "relative_time.days": "{number}a",
   "relative_time.full.days": "{number, plural, one {# araw} other {# na araw}} ang nakalipas",
   "relative_time.full.hours": "{number, plural, one {# oras} other {# na oras}} ang nakalipas",
@@ -363,7 +384,6 @@
   "status.more": "Higit pa",
   "status.read_more": "Basahin ang higit pa",
   "status.reblogs.empty": "Wala pang nagpalakas ng post na ito. Kung may sinumang nagpalakas, makikita sila rito.",
-  "status.remove_favourite": "Tanggalin sa mga paborito",
   "status.reply": "Tumugon",
   "status.report": "I-ulat si/ang @{name}",
   "status.sensitive_warning": "Sensitibong nilalaman",
@@ -377,5 +397,8 @@
   "time_remaining.days": "{number, plural, one {# araw} other {# na araw}} ang natitira",
   "time_remaining.hours": "{number, plural, one {# oras} other {# na oras}} ang natitira",
   "time_remaining.minutes": "{number, plural, one {# minuto} other {# na minuto}} ang natitira",
-  "time_remaining.seconds": "{number, plural, one {# segundo} other {# na segundo}} ang natitira"
+  "time_remaining.seconds": "{number, plural, one {# segundo} other {# na segundo}} ang natitira",
+  "upload_modal.apply": "Ilapat",
+  "upload_modal.applying": "Nilalapat…",
+  "upload_modal.choose_image": "Pumili ng larawan"
 }
diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json
index b472fd8bd2..f1170d21ec 100644
--- a/app/javascript/mastodon/locales/fo.json
+++ b/app/javascript/mastodon/locales/fo.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Broyt vanga",
   "account.enable_notifications": "Boða mær frá, tá @{name} skrivar",
   "account.endorse": "Víst á vangamyndini",
-  "account.featured": "Tikin fram",
-  "account.featured.hashtags": "Frámerki",
-  "account.featured.posts": "Postar",
   "account.featured_tags.last_status_at": "Seinasta strongur skrivaður {date}",
   "account.featured_tags.last_status_never": "Einki uppslag",
+  "account.featured_tags.title": "Tvíkrossar hjá {name}",
   "account.follow": "Fylg",
   "account.follow_back": "Fylg aftur",
   "account.followers": "Fylgjarar",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} postur} other {{counter} postar}}",
   "account.unblock": "Banna ikki @{name}",
   "account.unblock_domain": "Banna ikki økisnavnið {domain}",
-  "account.unblock_domain_short": "Banna ikki",
   "account.unblock_short": "Banna ikki",
   "account.unendorse": "Vís ikki á vanga",
   "account.unfollow": "Fylg ikki",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Ein óvæntaður feilur kom fyri.",
   "alert.unexpected.title": "Ups!",
   "alt_text_badge.title": "Annar tekstur",
-  "alt_text_modal.add_alt_text": "Legg alternativan tekst afturat",
-  "alt_text_modal.add_text_from_image": "Legg tekst frá mynd afturat",
-  "alt_text_modal.cancel": "Angra",
-  "alt_text_modal.change_thumbnail": "Broyt smámynd",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Lýs hetta fyri fólk við tey, ið hava niðursetta hoyrn…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Lýs hetta fyri fólk við tey, ið hava avmarkaða sjón…",
-  "alt_text_modal.done": "Liðugt",
   "announcement.announcement": "Kunngerð",
-  "annual_report.summary.archetype.booster": "Kuli jagarin",
-  "annual_report.summary.archetype.lurker": "Lúrarin",
-  "annual_report.summary.archetype.oracle": "Oraklið",
-  "annual_report.summary.archetype.pollster": "Spyrjarin",
-  "annual_report.summary.archetype.replier": "Sosiali firvaldurin",
-  "annual_report.summary.followers.followers": "fylgjarar",
-  "annual_report.summary.followers.total": "{count} íalt",
-  "annual_report.summary.here_it_is": "Her er ein samandráttur av {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "mest dámdi postur",
-  "annual_report.summary.highlighted_post.by_reblogs": "oftast lyfti postur",
-  "annual_report.summary.highlighted_post.by_replies": "postur við flestum svarum",
-  "annual_report.summary.highlighted_post.possessive": "hjá {name}",
-  "annual_report.summary.most_used_app.most_used_app": "mest brúkta app",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "mest brúkta frámerki",
-  "annual_report.summary.most_used_hashtag.none": "Einki",
-  "annual_report.summary.new_posts.new_posts": "nýggir postar",
-  "annual_report.summary.percentile.text": "<topLabel>Tað fær teg í topp</topLabel><percentage></percentage><bottomLabel>av {domain} brúkarum.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Vit fara ikki at fortelja Bernie tað.",
-  "annual_report.summary.thanks": "Takk fyri at tú er partur av Mastodon!",
   "attachments_list.unprocessed": "(óviðgjørt)",
   "audio.hide": "Fjal ljóð",
   "block_modal.remote_users_caveat": "Vit biðja ambætaran {domain} virða tína avgerð. Kortini er eingin vissa um samsvar, av tí at fleiri ambætarar handfara blokkar ymiskt. Almennir postar kunnu framvegis vera sjónligir fyri brúkarar, sum ikki eru innritaðir.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "Tað bar ikki til at finna umbidnu síðuna. Er URL'urin rættur?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Lat aftur",
-  "bundle_modal_error.message": "Okkurt gekk galið, tá hendan síðan bleiv innlisin.",
+  "bundle_modal_error.message": "Okkurt gekk galið, tá hesin komponenturin bleiv innlisin.",
   "bundle_modal_error.retry": "Royn umaftur",
   "closed_registrations.other_server_instructions": "Av tí at Mastodon er desentraliserað, kanst tú stovna eina kontu á einum øðrum ambætara og framvegis virka saman við hesum ambætaranum.",
   "closed_registrations_modal.description": "Tað er ikki møguligt at stovna sær eina kontu á {domain} í løtuni, men vinarliga hav í huga at tær nýtist ikki eina kontu á júst {domain} fyri at brúka Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Bannaðir brúkarar",
   "column.bookmarks": "Bókamerki",
   "column.community": "Lokal tíðarlinja",
-  "column.create_list": "Ger lista",
   "column.direct": "Privatar umrøður",
   "column.directory": "Blaða gjøgnum vangar",
   "column.domain_blocks": "Bannað økisnøvn",
-  "column.edit_list": "Broyt lista",
   "column.favourites": "Dámdir postar",
   "column.firehose": "Beinleiðis rásir",
   "column.follow_requests": "Umbønir at fylgja",
   "column.home": "Heim",
-  "column.list_members": "Rætta limalista",
   "column.lists": "Listar",
   "column.mutes": "Sløktir brúkarar",
   "column.notifications": "Fráboðanir",
@@ -172,7 +140,6 @@
   "column_header.pin": "Fest",
   "column_header.show_settings": "Vís stillingar",
   "column_header.unpin": "Loys",
-  "column_search.cancel": "Angra",
   "column_subheading.settings": "Stillingar",
   "community.column_settings.local_only": "Einans lokalt",
   "community.column_settings.media_only": "Einans miðlar",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Atkvøðugreiðslutíð",
   "compose_form.poll.multiple": "Fleiri valmøguleikar",
   "compose_form.poll.option_placeholder": "Valmøguleiki {number}",
-  "compose_form.poll.single": "Einfalt val",
+  "compose_form.poll.single": "Vel ein",
   "compose_form.poll.switch_to_multiple": "Broyt atkvøðugreiðslu til at loyva fleiri svarum",
   "compose_form.poll.switch_to_single": "Broyt atkvøðugreiðslu til einstakt svar",
   "compose_form.poll.type": "Stílur",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Rætta",
   "confirmations.edit.message": "Rættingar, sum verða gjørdar nú, skriva yvir boðini, sum tú ert í holt við. Ert tú vís/ur í, at tú vilt halda fram?",
   "confirmations.edit.title": "Skriva omaná post?",
-  "confirmations.follow_to_list.confirm": "Fylg og legg afturat lista",
-  "confirmations.follow_to_list.message": "Tú mást fylgja {name} fyri at leggja tey afturat einum lista.",
-  "confirmations.follow_to_list.title": "Fylg brúkara?",
   "confirmations.logout.confirm": "Rita út",
   "confirmations.logout.message": "Ert tú vís/ur í, at tú vilt útrita teg?",
   "confirmations.logout.title": "Rita út?",
-  "confirmations.missing_alt_text.confirm": "Legg alternativan tekst afturat",
-  "confirmations.missing_alt_text.message": "Posturin hjá tær inniheldur miðlar uttan alternativan tekst. Leggur tú lýsingar afturat verður tilfarið hjá tær atkomuligt hjá fleiri.",
-  "confirmations.missing_alt_text.secondary": "Posta allíkavæl",
-  "confirmations.missing_alt_text.title": "Legg alternativan tekst afturat?",
   "confirmations.mute.confirm": "Doyv",
   "confirmations.redraft.confirm": "Sletta og skriva umaftur",
   "confirmations.redraft.message": "Vilt tú veruliga strika hendan postin og í staðin gera hann til eina nýggja kladdu? Yndisfrámerki og framhevjanir blíva burtur, og svar til upprunapostin missa tilknýtið.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Konta tín {disabledAccount} er í løtuni óvirkin.",
   "dismissable_banner.community_timeline": "Hesir er nýggjastu almennu postarnir frá fólki, hvørs kontur eru hýstar av {domain}.",
   "dismissable_banner.dismiss": "Avvís",
-  "dismissable_banner.explore_links": "Hesi eru tíðindini, sum eru mest deilt á fediversinum í dag. Nýggjari tíðindi frá fjølbroyttari fólki eru raðfest hægri.",
-  "dismissable_banner.explore_statuses": "Hesir postar á fediversinum hava framgongd í dag. Nýggjari postar, sum fleiri hava framhevja og dámt, eru raðfestir hægri.",
-  "dismissable_banner.explore_tags": "Hesi frámerki vinna í løtuni fótafesti á fediversinum í dag. Frámerki, sum eru brúkt millum fleiri ymisk fólk, eru raðfest hægri.",
-  "dismissable_banner.public_timeline": "Hetta eru nýggjastu almennu postarnir frá fólki á fediversinum, sum fólk á {domain} fylgja.",
+  "dismissable_banner.explore_links": "Fólk tosa um hesi tíðindi, á hesum og øðrum ambætarum á miðspjadda netverkinum, júst nú.",
+  "dismissable_banner.explore_statuses": "Hesi uppsløg, frá hesum og øðrum ambætarum á miðspjadda netverkinum, hava framgongd á hesum ambætara júst nú. Nýggjari postar, sum fleiri hava framhevja og dáma, verða raðfestir hægri.",
+  "dismissable_banner.explore_tags": "Hesi frámerki vinna í løtuni fótafesti millum fólk á hesum og øðrum ambætarum í desentrala netverkinum beint nú.",
+  "dismissable_banner.public_timeline": "Hetta eru teir nýggjast postarnir frá fólki á sosialu vevinum, sum fólk á {domain} fylgja.",
   "domain_block_modal.block": "Banna ambætara",
   "domain_block_modal.block_account_instead": "Banna @{name} ístaðin",
   "domain_block_modal.they_can_interact_with_old_posts": "Fólk frá hesum ambætara kunnu svara tínum gomlu postum.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Leitiúrslit",
   "emoji_button.symbols": "Ímyndir",
   "emoji_button.travel": "Ferðing og støð",
-  "empty_column.account_featured": "Hesin listin er tómur",
   "empty_column.account_hides_collections": "Hesin brúkarin hevur valt, at hesar upplýsingarnar ikki skulu vera tøkar",
   "empty_column.account_suspended": "Kontan gjørd óvirkin",
   "empty_column.account_timeline": "Einki uppslag her!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Einki er í hesum frámerkinum enn.",
   "empty_column.home": "Heima-tíðarlinjan hjá tær er tóm! Fylg fleiri fyri at fylla hana. {suggestions}",
   "empty_column.list": "Einki er í hesum listanum enn. Tá limir í hesum listanum posta nýggjar postar, so síggjast teir her.",
+  "empty_column.lists": "Tú hevur ongar goymdar listar enn. Tá tú gert ein lista, so sært tú hann her.",
   "empty_column.mutes": "Tú hevur enn ikki doyvt nakran brúkara.",
   "empty_column.notification_requests": "Alt er klárt! Her er einki. Tá tú fært nýggjar fráboðanir, síggjast tær her sambært tínum stillingum.",
   "empty_column.notifications": "Tú hevur ongar fráboðanir enn. Tá onnur samskifta við teg, so sær tú fráboðaninar her.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Royn at gera tey óvirkin og lesa síðuna inn av nýggjum. Hjálpir tað ikki, so kann vera, at tað ber til at brúka Mastodon við einum øðrum kaga ella við eini app.",
   "errors.unexpected_crash.copy_stacktrace": "Avrita stakkaslóðina til setiborðið",
   "errors.unexpected_crash.report_issue": "Fráboða trupulleika",
+  "explore.search_results": "Leitiúrslit",
   "explore.suggested_follows": "Fólk",
   "explore.title": "Rannsaka",
   "explore.trending_links": "Tíðindi",
@@ -373,16 +334,13 @@
   "footer.about": "Um",
   "footer.directory": "Vangaskrá",
   "footer.get_app": "Heinta appina",
+  "footer.invite": "Bjóða fólki",
   "footer.keyboard_shortcuts": "Knappasnarvegir",
   "footer.privacy_policy": "Privatlívspolitikkur",
   "footer.source_code": "Vís keldukotuna",
   "footer.status": "Støða",
-  "footer.terms_of_service": "Tænastutreytir",
   "generic.saved": "Goymt",
   "getting_started.heading": "At byrja",
-  "hashtag.admin_moderation": "Lat umsjónarmarkamót upp fyri #{name}",
-  "hashtag.browse": "Blaða gjøgnum postar í #{hashtag}",
-  "hashtag.browse_from_account": "Blaða gjøgnum postar frá @{name} í #{hashtag}",
   "hashtag.column_header.tag_mode.all": "og {additional}",
   "hashtag.column_header.tag_mode.any": "ella {additional}",
   "hashtag.column_header.tag_mode.none": "uttan {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} postur} other {{counter} postar}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} postur} other {{counter} postar}} í dag",
   "hashtag.follow": "Fylg frámerki",
-  "hashtag.mute": "Doyv @#{hashtag}",
   "hashtag.unfollow": "Gevst at fylgja frámerki",
   "hashtags.and_other": "…og {count, plural, other {# afturat}}",
   "hints.profiles.followers_may_be_missing": "Fylgjarar hjá hesum vanganum kunnu mangla.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Lat sum um tú ikki sær fráboðanir frá fólki, sum ikki fylgja tær?",
   "ignore_notifications_modal.not_following_title": "Lat sum um tú ikki sær fráboðanir frá fólki, sum tú ikki fylgir?",
   "ignore_notifications_modal.private_mentions_title": "Lat sum um tú ikki sær fráboðanir frá óbiðnum privatum umrøðum?",
-  "info_button.label": "Hjálp",
-  "info_button.what_is_alt_text": "<h1>Hvat er alt tekstur?</h1> <p>Alt tekstur lýsir myndir fyri fólki, sum síggja illa, ella sum hava ringt net samband ella tey, sum vilja vita meira um samanhangin.</p> <p>Tú kanst bøta um atkomuna og fatanina hjá øllum við at skriva kláran, stuttan og objektivan alt tekst.</p> <ul> <li>Fanga týdningarmikil element</li> <li>Samanfata tekst í myndum</li> <li>Brúka reglubundnan setningsbygnað</li> <li>Lat vera við at siga ting upp í saman</li> <li>Fokusera á rák og høvuðsúrslit í kompleksum myndum (sosum diagrammir og kort)</li> </ul>",
-  "interaction_modal.action.favourite": "Fyri at halda fram, so mást tú yndismerkja frá tínari kontu.",
-  "interaction_modal.action.follow": "Fyri at halda fram, mást tú fylgja frá tínari kontu.",
-  "interaction_modal.action.reblog": "Fyri at halda fram, mást tú endurblogga frá tínari kontu.",
-  "interaction_modal.action.reply": "Fyri at halda fram, mást tú svara frá tínari kontu.",
-  "interaction_modal.action.vote": "Fyri at halda fram, mást tú atkvøða frá tínari kontu.",
-  "interaction_modal.go": "Far",
-  "interaction_modal.no_account_yet": "Hevur tú onga kontu enn?",
+  "interaction_modal.description.favourite": "Við einari kontu á Mastodon kanst tú dáma hendan postin fyri at vísa rithøvundanum at tú virðismetur hann og goymir hann til seinni.",
+  "interaction_modal.description.follow": "Við eini kontu á Mastodon kanst tú fylgja {name} fyri at síggja teirra postar á tíni heimarás.",
+  "interaction_modal.description.reblog": "Við eini kontu á Mastodon kanst tú stimbra hendan postin og soleiðis deila hann við tínar fylgjarar.",
+  "interaction_modal.description.reply": "Við eini kontu á Mastodon, so kanst tú svara hesum posti.",
+  "interaction_modal.login.action": "Tak meg heim",
+  "interaction_modal.login.prompt": "Navnaøki hjá tínum heimaambætara, t.d. mastodon.social",
+  "interaction_modal.no_account_yet": "Ikki á Mastodon?",
   "interaction_modal.on_another_server": "Á øðrum ambætara",
   "interaction_modal.on_this_server": "Á hesum ambætaranum",
+  "interaction_modal.sign_in": "Tú er ikki ritað/ur inn á hesum ambætaranum. Hvar er kontan hjá tær hýst?",
+  "interaction_modal.sign_in_hint": "Góð ráð: tað er heimasíðan, har tú lat teg skráseta. Minnist tú ikki, so kanst tú leita eftir vælkomin-teldubrævinum í innbakkanum hjá tær. Tú kanst eisini innlesa fulla brúkaranavnið hjá tær! (t.d. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Dáma postin hjá {name}",
   "interaction_modal.title.follow": "Fylg {name}",
   "interaction_modal.title.reblog": "Stimbra postin hjá {name}",
   "interaction_modal.title.reply": "Svara postinum hjá {name}",
-  "interaction_modal.title.vote": "Atkvøði í spurnarkanningini hjá {name}",
-  "interaction_modal.username_prompt": "T.d. {example}",
   "intervals.full.days": "{number, plural, one {# dagur} other {# dagar}}",
   "intervals.full.hours": "{number, plural, one {# tími} other {# tímar}}",
   "intervals.full.minutes": "{number, plural, one {# minuttur} other {# minuttir}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Vís/fjal tekst handan CW",
   "keyboard_shortcuts.toggle_sensitivity": "Vís ella fjal innihald",
   "keyboard_shortcuts.toot": "Byrja nýggjan post",
-  "keyboard_shortcuts.translate": "at umseta ein post",
   "keyboard_shortcuts.unfocus": "Tak skrivi-/leiti-økið úr miðdeplinum",
   "keyboard_shortcuts.up": "Flyt upp á listanum",
   "lightbox.close": "Lat aftur",
@@ -490,32 +444,20 @@
   "link_preview.author": "Av {name}",
   "link_preview.more_from_author": "Meira frá {name}",
   "link_preview.shares": "{count, plural, one {{counter} postur} other {{counter} postar}}",
-  "lists.add_member": "Legg afturat",
-  "lists.add_to_list": "Legg afturat lista",
-  "lists.add_to_lists": "Legg {name} afturat lista",
-  "lists.create": "Ger",
-  "lists.create_a_list_to_organize": "Ger ein nýggjan lista til heimarásina hjá tær",
-  "lists.create_list": "Ger lista",
+  "lists.account.add": "Legg afturat lista",
+  "lists.account.remove": "Tak av lista",
   "lists.delete": "Strika lista",
-  "lists.done": "Liðugt",
   "lists.edit": "Broyt lista",
-  "lists.exclusive": "Fjal limir á heimarás",
-  "lists.exclusive_hint": "Um onkur er á hesum listanum, so skulu tey fjalast á heimarásini, so tú sleppir undan at síggja postar teirra tvær ferðir.",
-  "lists.find_users_to_add": "Finn brúkarar at leggja afturat",
-  "lists.list_members": "Lista limir",
-  "lists.list_members_count": "{count, plural, one {# limur} other {# limir}}",
-  "lists.list_name": "Listanavn",
-  "lists.new_list_name": "Nýtt listanavn",
-  "lists.no_lists_yet": "Ongir listar enn.",
-  "lists.no_members_yet": "Eingir limir enn.",
-  "lists.no_results_found": "Eingi úrslit funnin.",
-  "lists.remove_member": "Burturbein",
+  "lists.edit.submit": "Broyt heiti",
+  "lists.exclusive": "Fjal hesar postarnar frá heima",
+  "lists.new.create": "Ger nýggjan lista",
+  "lists.new.title_placeholder": "Nýtt navn á lista",
   "lists.replies_policy.followed": "Øllum fylgdum brúkarum",
   "lists.replies_policy.list": "Listalimunum",
   "lists.replies_policy.none": "Eingin",
-  "lists.save": "Goym",
-  "lists.search": "Leita",
-  "lists.show_replies_to": "Írokna svar frá limum á listanum til",
+  "lists.replies_policy.title": "Vís svarini fyri:",
+  "lists.search": "Leita millum fólk, sum tú fylgir",
+  "lists.subheading": "Tínir listar",
   "load_pending": "{count, plural, one {# nýtt evni} other {# nýggj evni}}",
   "loading_indicator.label": "Innlesur…",
   "media_gallery.hide": "Fjal",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} meldaði {target}",
   "notification.admin.sign_up": "{name} meldaði seg til",
   "notification.admin.sign_up.name_and_others": "{name} og {count, plural, one {# annar/onnur} other {# onnur}} teknaðu seg",
-  "notification.annual_report.message": "Títt {year} #Wrapstodon bíðar! Avdúka hæddarpunktini og minniligu løturnar á Mastodon!",
-  "notification.annual_report.view": "Sí #Wrapstodon",
   "notification.favourite": "{name} dámdi postin hjá tær",
   "notification.favourite.name_and_others_with_link": "{name} og <a>{count, plural, one {# annar/onnur} other {# onnur}}</a> yndisfrámerktu postin hjá tær",
-  "notification.favourite_pm": "{name} yndismerkti tína privatu umrøðu",
-  "notification.favourite_pm.name_and_others_with_link": "{name} og <a>{count, plural, one {# annar yndismerkti} other {# onnur yndismerktu}}</a> tína privatu umrøðu",
   "notification.follow": "{name} fylgdi tær",
   "notification.follow.name_and_others": "{name} og <a>{count, plural, one {# annar/onnur} other {# onnur}}</a> fylgdu tær",
   "notification.follow_request": "{name} biður um at fylgja tær",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Ger skriviborðsfráboðanir virknar",
   "notifications_permission_banner.how_to_control": "Ger skriviborðsfráboðanir virknar fyri at móttaka fráboðanir, tá Mastodon ikki er opið. Tá tær eru gjørdar virknar, kanst tú stýra, hvørji sløg av samvirkni geva skriviborðsfráboðanir. Hetta umvegis {icon} knøttin omanfyri.",
   "notifications_permission_banner.title": "Miss einki",
-  "onboarding.follows.back": "Aftur",
-  "onboarding.follows.done": "Liðugt",
+  "onboarding.action.back": "Tak meg aftur",
+  "onboarding.actions.back": "Tak meg aftur",
+  "onboarding.actions.go_to_explore": "Sí rákið",
+  "onboarding.actions.go_to_home": "Far til heimarásina",
+  "onboarding.compose.template": "Hey #Mastodon!",
   "onboarding.follows.empty": "Tíverri kunnu eingi úrslit vísast beint nú. Tú kanst royna at brúka leiting ella at kaga gjøgnum Rannsaka síðuna fyri at finna fólk at fylgja - ella royna aftur seinni.",
-  "onboarding.follows.search": "Leita",
-  "onboarding.follows.title": "Fylg fólki fyri at koma í gongd",
+  "onboarding.follows.lead": "Tú snikkar sjálv/ur tína heimarás til. Jú fleiri fólk, tú fylgir, jú virknari og áhugaverdari verður tað. Hesir vangar kunnu vera ein góð byrjan — tú kanst altíð gevast at fylgja teimum seinni!",
+  "onboarding.follows.title": "Vælumtókt á Mastodon",
   "onboarding.profile.discoverable": "Ger tað møguligt hjá øðrum at finna vangan hjá mær",
   "onboarding.profile.discoverable_hint": "Tá tú játtar at onnur skulu kunna finna teg á Mastodon, so kann henda, at postar tínir síggjast í leitiúrslitum og rákum, og vangin hjá tær kann vera skotin upp fyri fólki við áhugamálum sum minna um tíni.",
   "onboarding.profile.display_name": "Navn, sum skal vísast",
   "onboarding.profile.display_name_hint": "Títt fulla navn ella títt stuttliga navn…",
+  "onboarding.profile.lead": "Tú kanst altíð gera hetta liðugt seinni í stillingunum, har enn fleiri tillagingarmøguleikar eru tøkir.",
   "onboarding.profile.note": "Ævilýsing",
   "onboarding.profile.note_hint": "Tú kanst @umrøða onnur fólk ella #frámerki…",
   "onboarding.profile.save_and_continue": "Goym og halt fram",
   "onboarding.profile.title": "Vangauppsetan",
   "onboarding.profile.upload_avatar": "Legg vangamynd upp",
   "onboarding.profile.upload_header": "Legg vangahøvd upp",
+  "onboarding.share.lead": "Lat fólk vita, hvussu tey kunnu finna teg á Mastodon!",
+  "onboarding.share.message": "Eg eri {username} á #Mastodon! Kom og fylg mær á {url}",
+  "onboarding.share.next_steps": "Møgulig næstu stig:",
+  "onboarding.share.title": "Deil tín vanga",
+  "onboarding.start.lead": "Tín nýggja Mastodon konta er klár. Her er ymiskt fyri at fáa sum mest burturúr:",
+  "onboarding.start.skip": "Hevur tú hug at leypa hetta um?",
+  "onboarding.start.title": "Tú kláraði tað!",
+  "onboarding.steps.follow_people.body": "Tú snikkar sjálv/ur tína rás til. Latum okkum fylla hana við áhugaverdum fólki.",
+  "onboarding.steps.follow_people.title": "Fylg {count, plural, one {einum persóni} other {# persónum}}",
+  "onboarding.steps.publish_status.body": "Sig hey við verðina.",
+  "onboarding.steps.publish_status.title": "Ger tín fyrsta post",
+  "onboarding.steps.setup_profile.body": "Líkindini eru størri fyri, at onnur samvirka við tær, um tú hevur ein útfyltan vanga.",
+  "onboarding.steps.setup_profile.title": "Snikka vangan hjá tær til",
+  "onboarding.steps.share_profile.body": "Lat vinir tínar vita, hvussu tey kunnu finna teg á Mastodon!",
+  "onboarding.steps.share_profile.title": "Deil vangan hjá tær",
+  "onboarding.tips.2fa": "<strong>Visti tú?</strong> Tú kanst tryggja kontu tína við at seta upp váttan í tveimum stigum í kontustillingunum hjá tær. Tað riggar við øllum TOTP appum; einki telefonnummar er neyðugt!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Visti tú?</strong> Av tí at Mastodon er miðspjatt, so eru summir vangar, sum tú kemur framat, hýstir á øðrum ambætarum. Og hóast hetta kanst tú samvirka við teir uttan nakað roks! Ambætarin er í seinna parti av brúkaranavninum hjá teimum!",
+  "onboarding.tips.migration": "<strong>Visti tú?</strong> Heldur tú at {domain} ikki er eitt gott ambætaraval fyri teg í framtíðini, so kanst tú flyta til ein annan Mastodon ambætara uttan at missa fylgjararnar hjá tær. Tú kanst enntá hýsa tínum egna ambætara!",
+  "onboarding.tips.verification": "<strong>Visti tú?</strong> Tú kanst vátta tína kontu við at leggja eitt leinki til tín Mastodon vanga á tína heimasíðu og leggja heimasíðuna á vangan hjá tær. Einki gjald og eingi skjøl neyðug!",
   "password_confirmation.exceeds_maxlength": "Loyniorðsváttanin er longri enn mest loyvda loyniorðslongd",
   "password_confirmation.mismatching": "Loyniorðsváttanin passar ikki til",
   "picture_in_picture.restore": "Legg hana aftur",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Strika atkvøðugreiðslu",
   "privacy.change": "Broyt privatverju av posti",
   "privacy.direct.long": "Øll, sum eru nevnd í postinum",
-  "privacy.direct.short": "Privat umrøða",
+  "privacy.direct.short": "Ávís fólk",
   "privacy.private.long": "Einans tey, ið fylgja tær",
   "privacy.private.short": "Fylgjarar",
   "privacy.public.long": "Øll í og uttanfyri Mastodon",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Privatlívspolitikkur",
   "recommended": "Viðmælt",
   "refresh": "Endurles",
-  "regeneration_indicator.please_stand_by": "Bíða við.",
-  "regeneration_indicator.preparing_your_home_feed": "Fyrireiki heimarásina hjá tær…",
+  "regeneration_indicator.label": "Innlesur…",
+  "regeneration_indicator.sublabel": "Tín heimarás verður gjørd klár!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# dagur} other {# dagar}} síðani",
   "relative_time.full.hours": "{number, plural, one {# tími} other {# tímar}} síðani",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Vangar",
   "search_results.all": "Alt",
   "search_results.hashtags": "Frámerki",
-  "search_results.no_results": "Eingi úrslit.",
-  "search_results.no_search_yet": "Royn at leita eftir postum, vangum ella frámerkjum.",
+  "search_results.nothing_found": "Hesi leitiorð góvu ongi úrslit",
   "search_results.see_all": "Sí øll",
   "search_results.statuses": "Postar",
-  "search_results.title": "Leita eftir \"{q}\"",
+  "search_results.title": "Leita eftir {q}",
   "server_banner.about_active_users": "Fólk, sum hava brúkt hendan ambætaran seinastu 30 dagarnar (mánaðarligir virknir brúkarar)",
   "server_banner.active_users": "virknir brúkarar",
   "server_banner.administered_by": "Umsitari:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Eingin hevur stimbrað hendan postin enn. Tá onkur stimbrar postin, verður hann sjónligur her.",
   "status.redraft": "Strika & ger nýggja kladdu",
   "status.remove_bookmark": "Gloym",
-  "status.remove_favourite": "Strika í yndismerkjum",
   "status.replied_in_thread": "Svaraði í tráðnum",
   "status.replied_to": "Svaraði {name}",
   "status.reply": "Svara",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Broyt haldaramál fyri {target}",
   "tabs_bar.home": "Heim",
   "tabs_bar.notifications": "Fráboðanir",
-  "terms_of_service.effective_as_of": "Galdandi frá {date}",
-  "terms_of_service.title": "Tænastutreytir",
-  "terms_of_service.upcoming_changes_on": "Komandi broytingar {date}",
   "time_remaining.days": "{number, plural, one {# dagur} other {# dagar}} eftir",
   "time_remaining.hours": "{number, plural, one {# tími} other {# tímar}} eftir",
   "time_remaining.minutes": "{number, plural, one {# minuttur} other {# minuttir}} eftir",
@@ -897,12 +853,26 @@
   "upload_button.label": "Legg myndir, sjónfílu ella ljóðfílu afturat",
   "upload_error.limit": "Farið er um markið fyri fíluuppsending.",
   "upload_error.poll": "Ikki loyvt at leggja fílur upp í spurnarkanningum.",
+  "upload_form.audio_description": "Lýs fyri teimum, sum eru deyv ella hava ringa hoyrn",
+  "upload_form.description": "Lýs fyri teimum, sum eru blind ella eru sjónveik",
   "upload_form.drag_and_drop.instructions": "Fyri at heinta eitt miðlaviðfesti, trýst á millumrúm ella returknapp. Meðan tú dregur, brúka pílarnar fyri at flyta miðaviðfesti í einhvønn rætning. Trýst á millumrúm ella returknapp aftur fyri at sleppa miðlaviðfestinum í nýggja staðnum ella trýst á esc-knappin fyri at angra.",
   "upload_form.drag_and_drop.on_drag_cancel": "Draging varð steðgað. Miðlaviðfestið {item} varð slept.",
   "upload_form.drag_and_drop.on_drag_end": "Miðlaviðfestið {item} var slept.",
   "upload_form.drag_and_drop.on_drag_over": "Miðlaviðfestið {item} var flutt.",
   "upload_form.drag_and_drop.on_drag_start": "Heintaði miðlaviðfestið {item}.",
   "upload_form.edit": "Rætta",
+  "upload_form.thumbnail": "Broyt smámynd",
+  "upload_form.video_description": "Lýs fyri teimum, sum eru deyv, hava ringa hoyrn, eru blind ella eru sjónveik",
+  "upload_modal.analyzing_picture": "Greini mynd…",
+  "upload_modal.apply": "Ger virkið",
+  "upload_modal.applying": "Geri virkið…",
+  "upload_modal.choose_image": "Vel mynd",
+  "upload_modal.description_placeholder": "Ein skjótur brúnur revur loypur uppum dovna hundin",
+  "upload_modal.detect_text": "Finn text á mynd",
+  "upload_modal.edit_media": "Broyt miðil",
+  "upload_modal.hint": "Klikk ella drag sirkulin á undanvísingini fyri at velja brennidepilspunktið, sum altíð fer at vera sjónligt á øllum smámyndum.",
+  "upload_modal.preparing_ocr": "Fyrireiki OCR…",
+  "upload_modal.preview_label": "Undanvísing ({ratio})",
   "upload_progress.label": "Leggi upp...",
   "upload_progress.processing": "Viðgeri…",
   "username.taken": "Brúkaranavnið er tikið. Royn eitt annað",
@@ -912,12 +882,8 @@
   "video.expand": "Víðka sjónfílu",
   "video.fullscreen": "Fullur skermur",
   "video.hide": "Fjal sjónfílu",
-  "video.mute": "Doyv",
+  "video.mute": "Sløkk ljóðið",
   "video.pause": "Steðga á",
   "video.play": "Spæl",
-  "video.skip_backward": "Leyp um aftureftir",
-  "video.skip_forward": "Leyp um frameftir",
-  "video.unmute": "Doyv ikki",
-  "video.volume_down": "Minka ljóðstyrki",
-  "video.volume_up": "Øk um ljóðstyrki"
+  "video.unmute": "Tendra ljóðið"
 }
diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json
index f63a1d2cba..6bd725e780 100644
--- a/app/javascript/mastodon/locales/fr-CA.json
+++ b/app/javascript/mastodon/locales/fr-CA.json
@@ -27,13 +27,11 @@
   "account.edit_profile": "Modifier le profil",
   "account.enable_notifications": "Me notifier quand @{name} publie",
   "account.endorse": "Inclure sur profil",
-  "account.featured": "En vedette",
-  "account.featured.hashtags": "Hashtags",
-  "account.featured.posts": "Messages",
   "account.featured_tags.last_status_at": "Dernière publication {date}",
   "account.featured_tags.last_status_never": "Aucune publication",
+  "account.featured_tags.title": "Hashtags inclus de {name}",
   "account.follow": "Suivre",
-  "account.follow_back": "Suivre en retour",
+  "account.follow_back": "S'abonner en retour",
   "account.followers": "abonné·e·s",
   "account.followers.empty": "Personne ne suit ce compte pour l'instant.",
   "account.followers_counter": "{count, plural, one {{counter} abonné·e} other {{counter} abonné·e·s}}",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} message} other {{counter} messages}}",
   "account.unblock": "Débloquer @{name}",
   "account.unblock_domain": "Débloquer le domaine {domain}",
-  "account.unblock_domain_short": "Débloquer",
   "account.unblock_short": "Débloquer",
   "account.unendorse": "Ne pas inclure sur profil",
   "account.unfollow": "Ne plus suivre",
@@ -89,40 +86,14 @@
   "alert.unexpected.message": "Une erreur inattendue s’est produite.",
   "alert.unexpected.title": "Oups!",
   "alt_text_badge.title": "Texte alternatif",
-  "alt_text_modal.add_alt_text": "Ajouter un texte alternatif",
-  "alt_text_modal.add_text_from_image": "Ajouter le texte provenant de l'image",
-  "alt_text_modal.cancel": "Annuler",
-  "alt_text_modal.change_thumbnail": "Changer la vignette",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Décrire pour les personnes ayant des difficultés d’audition…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Décrire pour les personnes ayant des problèmes de vue…",
-  "alt_text_modal.done": "Terminé",
   "announcement.announcement": "Annonce",
-  "annual_report.summary.archetype.booster": "Le chasseur de sang-froid",
-  "annual_report.summary.archetype.lurker": "Le faucheur",
-  "annual_report.summary.archetype.oracle": "L’oracle",
-  "annual_report.summary.archetype.pollster": "Le sondeur",
-  "annual_report.summary.archetype.replier": "Le papillon social",
-  "annual_report.summary.followers.followers": "abonné·e·s",
-  "annual_report.summary.followers.total": "{count} au total",
-  "annual_report.summary.here_it_is": "Voici votre récap de {year} :",
-  "annual_report.summary.highlighted_post.by_favourites": "post le plus aimé",
-  "annual_report.summary.highlighted_post.by_reblogs": "post le plus boosté",
-  "annual_report.summary.highlighted_post.by_replies": "post avec le plus de réponses",
-  "annual_report.summary.highlighted_post.possessive": "{name}'s",
-  "annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag le plus utilisé",
-  "annual_report.summary.most_used_hashtag.none": "Aucun",
-  "annual_report.summary.new_posts.new_posts": "nouveaux messages",
-  "annual_report.summary.percentile.text": "<topLabel>Cela vous place dans le top</topLabel><pourcentage></percentage><bottomLabel>des utilisateurs de {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Nous ne le dirons pas à Bernie.",
-  "annual_report.summary.thanks": "Merci de faire partie de Mastodon!",
   "attachments_list.unprocessed": "(non traité)",
   "audio.hide": "Masquer l'audio",
   "block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.",
   "block_modal.show_less": "Afficher moins",
   "block_modal.show_more": "Afficher plus",
   "block_modal.they_cant_mention": "Il ne peut pas vous mentionner ou vous suivre.",
-  "block_modal.they_cant_see_posts": "Il ne peut plus voir vos messages et vous ne verrez plus les siens.",
+  "block_modal.they_cant_see_posts": "Il peut toujours voir vos messages, mais vous ne verrez pas les siens.",
   "block_modal.they_will_know": "Il peut voir qu'il est bloqué.",
   "block_modal.title": "Bloquer le compte ?",
   "block_modal.you_wont_see_mentions": "Vous ne verrez pas les messages qui le mentionne.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "La page demandée est introuvable. Êtes-vous sûr que l’URL dans la barre d’adresse est correcte?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Fermer",
-  "bundle_modal_error.message": "Un problème s'est produit lors du chargement de cet écran.",
+  "bundle_modal_error.message": "Une erreur s’est produite lors du chargement de ce composant.",
   "bundle_modal_error.retry": "Réessayer",
   "closed_registrations.other_server_instructions": "Puisque Mastodon est décentralisé, vous pouvez créer un compte sur un autre serveur et interagir quand même avec celui-ci.",
   "closed_registrations_modal.description": "Créer un compte sur {domain} est présentement impossible, néanmoins souvenez-vous que vous n'avez pas besoin d'un compte spécifiquement sur {domain} pour utiliser Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Comptes bloqués",
   "column.bookmarks": "Signets",
   "column.community": "Fil local",
-  "column.create_list": "Créer une liste",
   "column.direct": "Mention privée",
   "column.directory": "Parcourir les profils",
   "column.domain_blocks": "Domaines bloqués",
-  "column.edit_list": "Modifier la liste",
   "column.favourites": "Favoris",
   "column.firehose": "Flux en direct",
   "column.follow_requests": "Demande d'abonnement",
   "column.home": "Accueil",
-  "column.list_members": "Gérer les membres de la liste",
   "column.lists": "Listes",
   "column.mutes": "Comptes masqués",
   "column.notifications": "Notifications",
@@ -172,7 +140,6 @@
   "column_header.pin": "Épingler",
   "column_header.show_settings": "Afficher les paramètres",
   "column_header.unpin": "Désépingler",
-  "column_search.cancel": "Annuler",
   "column_subheading.settings": "Paramètres",
   "community.column_settings.local_only": "Local seulement",
   "community.column_settings.media_only": "Média seulement",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Éditer",
   "confirmations.edit.message": "Modifier maintenant écrasera votre message en cours de rédaction. Voulez-vous vraiment continuer ?",
   "confirmations.edit.title": "Remplacer le message ?",
-  "confirmations.follow_to_list.confirm": "Suivre et ajouter à la liste",
-  "confirmations.follow_to_list.message": "Vous devez suivre {name} pour l'ajouter à une liste.",
-  "confirmations.follow_to_list.title": "Suivre l'utilisateur ?",
   "confirmations.logout.confirm": "Se déconnecter",
   "confirmations.logout.message": "Voulez-vous vraiment vous déconnecter?",
   "confirmations.logout.title": "Se déconnecter ?",
-  "confirmations.missing_alt_text.confirm": "Ajouter un texte alternatif",
-  "confirmations.missing_alt_text.message": "Votre post contient des médias sans texte alternatif. Ajouter des descriptions rend votre contenu accessible à un plus grand nombre de personnes.",
-  "confirmations.missing_alt_text.secondary": "Publier quand-même",
-  "confirmations.missing_alt_text.title": "Ajouter un texte alternatif?",
   "confirmations.mute.confirm": "Masquer",
   "confirmations.redraft.confirm": "Supprimer et réécrire",
   "confirmations.redraft.message": "Êtes-vous sûr·e de vouloir effacer cette publication pour la réécrire? Ses ses mises en favori et boosts seront perdus et ses réponses seront orphelines.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Votre compte {disabledAccount} est présentement désactivé.",
   "dismissable_banner.community_timeline": "Voici les publications publiques les plus récentes de personnes dont les comptes sont hébergés par {domain}.",
   "dismissable_banner.dismiss": "Rejeter",
-  "dismissable_banner.explore_links": "Ces nouvelles sont les plus partagées sur le fediverse aujourd'hui. Les nouvelles plus récentes postées par un plus grand nombre de personnes sont mieux classées.",
-  "dismissable_banner.explore_statuses": "Ces messages provenant de l'ensemble du fediverse gagnent en popularité aujourd'hui. Les messages les plus récents qui ont reçu le plus d'encouragements et de favoris sont mieux classés.",
-  "dismissable_banner.explore_tags": "Ces hashtags gagnent du terrain sur le fediverse aujourd'hui. Les hashtags qui sont utilisés par un plus grand nombre de personnes différentes sont mieux classés.",
-  "dismissable_banner.public_timeline": "Il s'agit des messages publics les plus récents publiés par des personnes sur le fediverse que les personnes sur {domain} suivent.",
+  "dismissable_banner.explore_links": "Ces nouvelles sont présentement en cours de discussion par des personnes sur d'autres serveurs du réseau décentralisé ainsi que sur celui-ci.",
+  "dismissable_banner.explore_statuses": "Voici des publications venant de tout le web social gagnant en popularité aujourd’hui. Les nouvelles publications avec plus de boosts et de favoris sont classés plus haut.",
+  "dismissable_banner.explore_tags": "Ces hashtags sont présentement en train de gagner de l'ampleur parmi des personnes sur les serveurs du réseau décentralisé dont celui-ci.",
+  "dismissable_banner.public_timeline": "Ce sont les messages publics les plus récents de personnes sur le web social que les gens de {domain} suivent.",
   "domain_block_modal.block": "Bloquer le serveur",
   "domain_block_modal.block_account_instead": "Bloquer @{name} à la place",
   "domain_block_modal.they_can_interact_with_old_posts": "Les personnes de ce serveur peuvent interagir avec vos anciens messages.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Résultats",
   "emoji_button.symbols": "Symboles",
   "emoji_button.travel": "Voyage et lieux",
-  "empty_column.account_featured": "Cette liste est vide",
   "empty_column.account_hides_collections": "Cet utilisateur·ice préfère ne pas rendre publiques ces informations",
   "empty_column.account_suspended": "Compte suspendu",
   "empty_column.account_timeline": "Aucune publication ici!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Il n’y a pas encore de contenu associé à ce hashtag.",
   "empty_column.home": "Votre fil d'accueil est vide! Suivez plus de personnes pour la remplir. {suggestions}",
   "empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Quand des membres de cette liste publieront de nouvelles publications, elles apparaîtront ici.",
+  "empty_column.lists": "Vous n’avez pas encore de liste. Lorsque vous en créerez une, elle apparaîtra ici.",
   "empty_column.mutes": "Vous n’avez masqué aucun compte pour le moment.",
   "empty_column.notification_requests": "C'est fini ! Il n'y a plus rien ici. Lorsque vous recevez de nouvelles notifications, elles apparaitront ici conformément à vos préférences.",
   "empty_column.notifications": "Vous n'avez pas encore de notifications. Quand d'autres personnes interagissent avec vous, vous en verrez ici.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Essayez de les désactiver et de rafraîchir la page. Si cela ne vous aide pas, vous pouvez toujours utiliser Mastodon via un autre navigateur ou une application native.",
   "errors.unexpected_crash.copy_stacktrace": "Copier la trace d'appels dans le presse-papier",
   "errors.unexpected_crash.report_issue": "Signaler un problème",
+  "explore.search_results": "Résultats",
   "explore.suggested_follows": "Personnes",
   "explore.title": "Explorer",
   "explore.trending_links": "Nouvelles",
@@ -373,14 +334,13 @@
   "footer.about": "À propos",
   "footer.directory": "Annuaire des profils",
   "footer.get_app": "Télécharger l’application",
+  "footer.invite": "Inviter des gens",
   "footer.keyboard_shortcuts": "Raccourcis clavier",
   "footer.privacy_policy": "Politique de confidentialité",
   "footer.source_code": "Voir le code source",
   "footer.status": "État",
-  "footer.terms_of_service": "Conditions d’utilisation",
   "generic.saved": "Sauvegardé",
   "getting_started.heading": "Pour commencer",
-  "hashtag.admin_moderation": "Ouvrir l'interface de modération pour #{name}",
   "hashtag.column_header.tag_mode.all": "et {additional}",
   "hashtag.column_header.tag_mode.any": "ou {additional}",
   "hashtag.column_header.tag_mode.none": "sans {additional}",
@@ -422,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignorer les notifications provenant des personnes qui ne vous suivent pas ?",
   "ignore_notifications_modal.not_following_title": "Ignorer les notifications provenant des personnes que vous ne suivez pas ?",
   "ignore_notifications_modal.private_mentions_title": "Ignorer les notifications issues des mentions privées non sollicitées ?",
-  "info_button.label": "Aide",
-  "info_button.what_is_alt_text": "<h1>Qu'est-ce que le texte alternatif ?</h1> <p>Un texte alternatif fournit une description de l'image aux personnes avec un handicap visuel ou une connexion limitée ou qui souhaitent avoir un contexte supplémentaire.</p> <p>Vous pouvez améliorer l'accessibilité et la compression de tout le monde en écrivant un texte alternatif clair, concis et objectif.</p> <ul> <li>Identifiez les éléments importants</li> <li>Résumez le texte présent à l'image</li> <li>Utilisez une structure de phrase normale</li> <li>Évitez les informations redondantes</li> <li>Pour les visuels complexes (tels que les diagrammes ou les cartes), indiquez les tendances ou points-clés</li> </ul>",
-  "interaction_modal.action.favourite": "Pour continuer, vous devez ajouter en favori depuis votre compte.",
-  "interaction_modal.action.follow": "Pour continuer, vous devez suivre depuis votre compte.",
-  "interaction_modal.action.reblog": "Pour continuer, vous devez booster depuis votre compte.",
-  "interaction_modal.action.reply": "Pour continuer, vous devez répondre depuis votre compte.",
-  "interaction_modal.action.vote": "Pour continuer, vous devez voter depuis votre compte.",
-  "interaction_modal.go": "Valider",
-  "interaction_modal.no_account_yet": "Vous n'avez pas encore de compte ?",
+  "interaction_modal.description.favourite": "Avec un compte Mastodon, vous pouvez ajouter cette publication à vos favoris pour informer l'auteur⋅rice que vous l'appréciez et la sauvegarder pour plus tard.",
+  "interaction_modal.description.follow": "Avec un compte Mastodon, vous pouvez suivre {name} et recevoir leurs publications dans votre fil d'accueil.",
+  "interaction_modal.description.reblog": "Avec un compte Mastodon, vous pouvez booster cette publication pour la partager avec vos propres abonné·e·s.",
+  "interaction_modal.description.reply": "Avec un compte sur Mastodon, vous pouvez répondre à cette publication.",
+  "interaction_modal.login.action": "Aller à mon serveur",
+  "interaction_modal.login.prompt": "Domaine de votre serveur, ex. mastodon.social",
+  "interaction_modal.no_account_yet": "Pas sur Mastodon ?",
   "interaction_modal.on_another_server": "Sur un autre serveur",
   "interaction_modal.on_this_server": "Sur ce serveur",
+  "interaction_modal.sign_in": "Vous n’êtes pas connectés sur ce serveur. Où est hébergé votre compte ?",
+  "interaction_modal.sign_in_hint": "Astuce : c'est le site web sur lequel vous vous êtes inscrit. Si vous ne vous en souvenez pas, cherchez le courriel de bienvenue dans votre boîte de réception. Vous pouvez aussi indiquer votre nom d’utilisateur complet ! (par ex. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Ajouter la publication de {name} aux favoris",
   "interaction_modal.title.follow": "Suivre {name}",
   "interaction_modal.title.reblog": "Booster la publication de {name}",
   "interaction_modal.title.reply": "Répondre à la publication de {name}",
-  "interaction_modal.title.vote": "Voter pour le sondage de {name}",
-  "interaction_modal.username_prompt": "Par exemple : {example}",
   "intervals.full.days": "{number, plural, one {# jour} other {# jours}}",
   "intervals.full.hours": "{number, plural, one {# heure} other {# heures}}",
   "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
@@ -474,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Déplier/replier texte derrière avertissement",
   "keyboard_shortcuts.toggle_sensitivity": "Afficher/cacher médias",
   "keyboard_shortcuts.toot": "Commencer un nouveau message",
-  "keyboard_shortcuts.translate": "traduire un message",
   "keyboard_shortcuts.unfocus": "Ne plus se concentrer sur la zone de rédaction/barre de recherche",
   "keyboard_shortcuts.up": "Monter dans la liste",
   "lightbox.close": "Fermer",
@@ -487,32 +444,20 @@
   "link_preview.author": "Par {name}",
   "link_preview.more_from_author": "Plus via {name}",
   "link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
-  "lists.add_member": "Ajouter",
-  "lists.add_to_list": "Ajouter à la liste",
-  "lists.add_to_lists": "Ajouter {name} aux listes",
-  "lists.create": "Créer",
-  "lists.create_a_list_to_organize": "Créer une nouvelle liste pour organiser votre Page d'accueil",
-  "lists.create_list": "Créer une liste",
+  "lists.account.add": "Ajouter à une liste",
+  "lists.account.remove": "Retirer d'une liste",
   "lists.delete": "Supprimer la liste",
-  "lists.done": "Terminé",
   "lists.edit": "Modifier la liste",
-  "lists.exclusive": "Cacher les membres de la page d'accueil",
-  "lists.exclusive_hint": "Si quelqu'un est dans cette liste, les cacher dans votre fil pour éviter de voir leurs messages deux fois.",
-  "lists.find_users_to_add": "Trouver des utilisateurs à ajouter",
-  "lists.list_members": "Lister les membres",
-  "lists.list_members_count": "{count, plural, one {# member} other {# members}}",
-  "lists.list_name": "Nom de la liste",
-  "lists.new_list_name": "Nom de la nouvelle liste",
-  "lists.no_lists_yet": "Aucune liste pour l'instant.",
-  "lists.no_members_yet": "Aucun membre pour l'instant.",
-  "lists.no_results_found": "Aucun résultat.",
-  "lists.remove_member": "Supprimer",
+  "lists.edit.submit": "Modifier le titre",
+  "lists.exclusive": "Cacher ces publications depuis la page d'accueil",
+  "lists.new.create": "Ajouter une liste",
+  "lists.new.title_placeholder": "Titre de la nouvelle liste",
   "lists.replies_policy.followed": "N'importe quel compte suivi",
   "lists.replies_policy.list": "Membres de la liste",
   "lists.replies_policy.none": "Personne",
-  "lists.save": "Enregistrer",
-  "lists.search": "Recherche",
-  "lists.show_replies_to": "Inclure les réponses des membres de la liste à",
+  "lists.replies_policy.title": "Afficher les réponses à:",
+  "lists.search": "Rechercher parmi les gens que vous suivez",
+  "lists.subheading": "Vos listes",
   "load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
   "loading_indicator.label": "Chargement…",
   "media_gallery.hide": "Masquer",
@@ -561,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} a signalé {target}",
   "notification.admin.sign_up": "{name} s'est inscrit·e",
   "notification.admin.sign_up.name_and_others": "{name} et {count, plural, one {# autre} other {# autres}} se sont inscrit",
-  "notification.annual_report.message": "Votre {year} #Wrapstodon attend ! Dévoilez les moments forts et mémorables de votre année sur Mastodon !",
-  "notification.annual_report.view": "Voir #Wrapstodon",
   "notification.favourite": "{name} a ajouté votre publication à ses favoris",
   "notification.favourite.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont mis votre message en favori",
-  "notification.favourite_pm": "{name} a mis votre mention privée en favori",
-  "notification.favourite_pm.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont mis votre mention privée en favori",
   "notification.follow": "{name} vous suit",
   "notification.follow.name_and_others": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> vous suivent",
   "notification.follow_request": "{name} a demandé à vous suivre",
@@ -671,21 +612,44 @@
   "notifications_permission_banner.enable": "Activer les notifications de bureau",
   "notifications_permission_banner.how_to_control": "Pour recevoir des notifications lorsque Mastodon n’est pas ouvert, activez les notifications de bureau. Vous pouvez contrôler précisément quels types d’interactions génèrent des notifications de bureau via le bouton {icon} ci-dessus une fois qu’elles sont activées.",
   "notifications_permission_banner.title": "Ne rien rater",
-  "onboarding.follows.back": "Retour",
-  "onboarding.follows.done": "Terminé",
+  "onboarding.action.back": "Revenir en arrière",
+  "onboarding.actions.back": "Revenir en arrière",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "Bonjour #Mastodon!",
   "onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer de rechercher ou de parcourir la page \"Explorer\" pour trouver des personnes à suivre, ou réessayer plus tard.",
-  "onboarding.follows.search": "Recherche",
-  "onboarding.follows.title": "Suivre des personnes pour commencer",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
   "onboarding.profile.discoverable": "Rendre mon profil découvrable",
   "onboarding.profile.discoverable_hint": "Lorsque vous acceptez d'être découvert sur Mastodon, vos messages peuvent apparaître dans les résultats de recherche et les tendances, et votre profil peut être suggéré à des personnes ayant des intérêts similaires aux vôtres.",
   "onboarding.profile.display_name": "Nom affiché",
   "onboarding.profile.display_name_hint": "Votre nom complet ou votre nom rigolo…",
+  "onboarding.profile.lead": "Vous pouvez toujours compléter cela plus tard dans les paramètres. Vous y trouverez encore plus d'options de personnalisation.",
   "onboarding.profile.note": "Biographie",
   "onboarding.profile.note_hint": "Vous pouvez @mentionner d'autres personnes ou #hashtags…",
   "onboarding.profile.save_and_continue": "Enregistrer et continuer",
   "onboarding.profile.title": "Configuration du profil",
   "onboarding.profile.upload_avatar": "Importer une photo de profil",
   "onboarding.profile.upload_header": "Importer un entête de profil",
+  "onboarding.share.lead": "Faites savoir aux gens comment vous trouver sur Mastodon!",
+  "onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}",
+  "onboarding.share.next_steps": "Étapes suivantes possibles:",
+  "onboarding.share.title": "Partager votre profil",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "Vous avez réussi!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Écrivez votre première publication",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
+  "onboarding.tips.2fa": "<strong>Le saviez-vous ?</strong> Vous pouvez sécuriser votre compte en configurant l'authentification à deux facteurs dans les paramètres de votre compte. Ça marche avec n'importe quelle application TOTP de votre choix, aucun numéro de téléphone nécessaire!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Le saviez-vous ?</strong> Puisque Mastodon est décentralisé, certains profils que vous rencontrez seront hébergés sur des serveurs autres que les vôtres. Et vous pouvez toujours interagir avec ceux-là de façon transparente! Leur serveur est dans la seconde moitié de leur nom d'utilisateur!",
+  "onboarding.tips.migration": "<strong>Le saviez-vous ?</strong> Si vous avez l'impression que {domain} n'est pas un bon choix de serveur pour vous dans le futur, vous pouvez déménager vers un autre serveur Mastodon sans perdre vos abonnés. Vous pouvez même héberger votre propre serveur!",
+  "onboarding.tips.verification": "<strong>Le saviez-vous ?</strong> Vous pouvez vérifier votre compte en mettant un lien vers votre profil Mastodon sur votre propre site web et en ajoutant le site à votre profil. Sans frais ou documents!",
   "password_confirmation.exceeds_maxlength": "La confirmation du mot de passe dépasse la longueur maximale du mot de passe",
   "password_confirmation.mismatching": "Les deux mots de passe ne correspondent pas",
   "picture_in_picture.restore": "Remettre en place",
@@ -701,7 +665,7 @@
   "poll_button.remove_poll": "Supprimer le sondage",
   "privacy.change": "Changer la confidentialité des messages",
   "privacy.direct.long": "Toutes les personnes mentionnées dans le post",
-  "privacy.direct.short": "Mention privée",
+  "privacy.direct.short": "Personnes spécifiques",
   "privacy.private.long": "Seulement vos abonnés",
   "privacy.private.short": "Abonnés",
   "privacy.public.long": "Tout le monde sur et en dehors de Mastodon",
@@ -713,8 +677,8 @@
   "privacy_policy.title": "Politique de confidentialité",
   "recommended": "Recommandé",
   "refresh": "Actualiser",
-  "regeneration_indicator.please_stand_by": "Veuillez patienter.",
-  "regeneration_indicator.preparing_your_home_feed": "Préparation de votre flux principal…",
+  "regeneration_indicator.label": "Chargement…",
+  "regeneration_indicator.sublabel": "Votre fil d'accueil est en cours de préparation!",
   "relative_time.days": "{number} j",
   "relative_time.full.days": "il y a {number, plural, one {# jour} other {# jours}}",
   "relative_time.full.hours": "il y a {number, plural, one {# heure} other {# heures}}",
@@ -798,11 +762,10 @@
   "search_results.accounts": "Profils",
   "search_results.all": "Tout",
   "search_results.hashtags": "Hashtags",
-  "search_results.no_results": "Aucun résultat.",
-  "search_results.no_search_yet": "Essayez de rechercher des messages, des profils ou des hashtags.",
+  "search_results.nothing_found": "Aucun résultat avec ces mots-clés",
   "search_results.see_all": "Afficher tout",
   "search_results.statuses": "Publications",
-  "search_results.title": "Résultat de Recherche pour \"{q}\"",
+  "search_results.title": "Rechercher {q}",
   "server_banner.about_active_users": "Personnes utilisant ce serveur au cours des 30 derniers jours (Comptes actifs mensuellement)",
   "server_banner.active_users": "comptes actifs",
   "server_banner.administered_by": "Administré par:",
@@ -854,7 +817,6 @@
   "status.reblogs.empty": "Personne n’a encore boosté cette publication. Lorsque quelqu’un le fera, elle apparaîtra ici.",
   "status.redraft": "Supprimer et réécrire",
   "status.remove_bookmark": "Retirer des signets",
-  "status.remove_favourite": "Retirer des favoris",
   "status.replied_in_thread": "A répondu dans un fil de discussion",
   "status.replied_to": "A répondu à {name}",
   "status.reply": "Répondre",
@@ -876,9 +838,6 @@
   "subscribed_languages.target": "Changer les langues abonnées pour {target}",
   "tabs_bar.home": "Accueil",
   "tabs_bar.notifications": "Notifications",
-  "terms_of_service.effective_as_of": "En vigueur à compter du {date}",
-  "terms_of_service.title": "Conditions d'utilisation",
-  "terms_of_service.upcoming_changes_on": "Modifications à venir le {date}",
   "time_remaining.days": "{number, plural, one {# jour restant} other {# jours restants}}",
   "time_remaining.hours": "{number, plural, one {# heure restante} other {# heures restantes}}",
   "time_remaining.minutes": "{number, plural, one {# minute restante} other {# minutes restantes}}",
@@ -894,12 +853,26 @@
   "upload_button.label": "Ajouter des images, une vidéo ou un fichier audio",
   "upload_error.limit": "Taille maximale d'envoi de fichier dépassée.",
   "upload_error.poll": "L’envoi de fichiers n’est pas autorisé avec les sondages.",
+  "upload_form.audio_description": "Décrire pour les personnes ayant des difficultés d’audition",
+  "upload_form.description": "Décrire pour les malvoyants",
   "upload_form.drag_and_drop.instructions": "Pour choisir un média joint, appuyez sur la touche espace ou entrée. Tout en faisant glisser, utilisez les touches fléchées pour déplacer le fichier média dans une direction donnée. Appuyez à nouveau sur la touche espace ou entrée pour déposer le fichier média dans sa nouvelle position, ou appuyez sur la touche Echap pour annuler.",
   "upload_form.drag_and_drop.on_drag_cancel": "Le glissement a été annulé. La pièce jointe {item} n'a pas été ajoutée.",
   "upload_form.drag_and_drop.on_drag_end": "La pièce jointe du média {item} a été déplacée.",
   "upload_form.drag_and_drop.on_drag_over": "La pièce jointe du média {item} a été déplacée.",
   "upload_form.drag_and_drop.on_drag_start": "A récupéré la pièce jointe {item}.",
   "upload_form.edit": "Modifier",
+  "upload_form.thumbnail": "Changer la vignette",
+  "upload_form.video_description": "Décrire pour les personnes ayant des problèmes de vue ou d'audition",
+  "upload_modal.analyzing_picture": "Analyse de l’image en cours…",
+  "upload_modal.apply": "Appliquer",
+  "upload_modal.applying": "Application en cours…",
+  "upload_modal.choose_image": "Choisir une image",
+  "upload_modal.description_placeholder": "Buvez de ce whisky que le patron juge fameux",
+  "upload_modal.detect_text": "Détecter le texte de l’image",
+  "upload_modal.edit_media": "Modifier le média",
+  "upload_modal.hint": "Cliquez ou faites glisser le cercle sur l’aperçu pour choisir le point focal qui sera toujours visible sur toutes les miniatures.",
+  "upload_modal.preparing_ocr": "Préparation de la ROC…",
+  "upload_modal.preview_label": "Aperçu ({ratio})",
   "upload_progress.label": "Envoi en cours...",
   "upload_progress.processing": "Traitement en cours…",
   "username.taken": "Ce nom d'utilisateur est déjà pris. Essayez-en en autre",
@@ -912,7 +885,5 @@
   "video.mute": "Couper le son",
   "video.pause": "Pause",
   "video.play": "Lecture",
-  "video.unmute": "Rétablir le son",
-  "video.volume_down": "Baisser le volume",
-  "video.volume_up": "Augmenter le volume"
+  "video.unmute": "Rétablir le son"
 }
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index f9c616627f..375da5daec 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -3,7 +3,7 @@
   "about.contact": "Contact :",
   "about.disclaimer": "Mastodon est un logiciel libre, open-source et une marque déposée de Mastodon gGmbH.",
   "about.domain_blocks.no_reason_available": "Raison non disponible",
-  "about.domain_blocks.preamble": "Mastodon vous permet généralement de visualiser le contenu et d'interagir avec les utilisateur⋅rices de n'importe quel autre serveur dans le fédivers. Voici les exceptions qui ont été faites sur ce serveur-là.",
+  "about.domain_blocks.preamble": "Mastodon vous permet généralement de visualiser le contenu et d'interagir avec les utilisateur⋅rice⋅s de n'importe quel autre serveur dans le fédiverse. Voici les exceptions qui ont été faites sur ce serveur en particulier.",
   "about.domain_blocks.silenced.explanation": "Vous ne verrez généralement pas les profils et le contenu de ce serveur, à moins que vous ne les recherchiez explicitement ou que vous ne choisissiez de les suivre.",
   "about.domain_blocks.silenced.title": "Limité",
   "about.domain_blocks.suspended.explanation": "Aucune donnée de ce serveur ne sera traitée, enregistrée ou échangée, rendant impossible toute interaction ou communication avec les comptes de ce serveur.",
@@ -19,7 +19,7 @@
   "account.block_domain": "Bloquer le domaine {domain}",
   "account.block_short": "Bloquer",
   "account.blocked": "Bloqué·e",
-  "account.cancel_follow_request": "Annuler l'abonnement",
+  "account.cancel_follow_request": "Annuler le suivi",
   "account.copy": "Copier le lien vers le profil",
   "account.direct": "Mention privée @{name}",
   "account.disable_notifications": "Ne plus me notifier quand @{name} publie quelque chose",
@@ -27,24 +27,22 @@
   "account.edit_profile": "Modifier le profil",
   "account.enable_notifications": "Me notifier quand @{name} publie quelque chose",
   "account.endorse": "Recommander sur votre profil",
-  "account.featured": "En vedette",
-  "account.featured.hashtags": "Hashtags",
-  "account.featured.posts": "Messages",
   "account.featured_tags.last_status_at": "Dernier message le {date}",
   "account.featured_tags.last_status_never": "Aucun message",
+  "account.featured_tags.title": "Les hashtags en vedette de {name}",
   "account.follow": "Suivre",
-  "account.follow_back": "Suivre en retour",
+  "account.follow_back": "S'abonner en retour",
   "account.followers": "Abonné·e·s",
   "account.followers.empty": "Personne ne suit cet·te utilisateur·rice pour l’instant.",
   "account.followers_counter": "{count, plural, one {{counter} abonné·e} other {{counter} abonné·e·s}}",
   "account.following": "Abonnements",
   "account.following_counter": "{count, plural, one {{counter} abonnement} other {{counter} abonnements}}",
   "account.follows.empty": "Cet·te utilisateur·rice ne suit personne pour l’instant.",
-  "account.go_to_profile": "Voir le profil",
+  "account.go_to_profile": "Aller au profil",
   "account.hide_reblogs": "Masquer les partages de @{name}",
   "account.in_memoriam": "En mémoire de.",
   "account.joined_short": "Ici depuis",
-  "account.languages": "Modifier les langues d'abonnements",
+  "account.languages": "Changer les langues abonnées",
   "account.link_verified_on": "La propriété de ce lien a été vérifiée le {date}",
   "account.locked_info": "Ce compte est privé. Son ou sa propriétaire approuve manuellement qui peut le suivre.",
   "account.media": "Médias",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} message} other {{counter} messages}}",
   "account.unblock": "Débloquer @{name}",
   "account.unblock_domain": "Débloquer le domaine {domain}",
-  "account.unblock_domain_short": "Débloquer",
   "account.unblock_short": "Débloquer",
   "account.unendorse": "Ne plus recommander sur le profil",
   "account.unfollow": "Ne plus suivre",
@@ -89,40 +86,14 @@
   "alert.unexpected.message": "Une erreur inattendue s’est produite.",
   "alert.unexpected.title": "Oups !",
   "alt_text_badge.title": "Texte alternatif",
-  "alt_text_modal.add_alt_text": "Ajouter un texte alternatif",
-  "alt_text_modal.add_text_from_image": "Ajouter le texte provenant de l'image",
-  "alt_text_modal.cancel": "Annuler",
-  "alt_text_modal.change_thumbnail": "Changer la vignette",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Décrire pour les personnes ayant des difficultés d’audition…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Décrire pour les personnes ayant des problèmes de vue…",
-  "alt_text_modal.done": "Terminé",
   "announcement.announcement": "Annonce",
-  "annual_report.summary.archetype.booster": "Le chasseur de sang-froid",
-  "annual_report.summary.archetype.lurker": "Le faucheur",
-  "annual_report.summary.archetype.oracle": "L’oracle",
-  "annual_report.summary.archetype.pollster": "Le sondeur",
-  "annual_report.summary.archetype.replier": "Le papillon social",
-  "annual_report.summary.followers.followers": "abonné·e·s",
-  "annual_report.summary.followers.total": "{count} au total",
-  "annual_report.summary.here_it_is": "Voici votre récap de {year} :",
-  "annual_report.summary.highlighted_post.by_favourites": "post le plus aimé",
-  "annual_report.summary.highlighted_post.by_reblogs": "post le plus boosté",
-  "annual_report.summary.highlighted_post.by_replies": "post avec le plus de réponses",
-  "annual_report.summary.highlighted_post.possessive": "{name}'s",
-  "annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag le plus utilisé",
-  "annual_report.summary.most_used_hashtag.none": "Aucun",
-  "annual_report.summary.new_posts.new_posts": "nouveaux messages",
-  "annual_report.summary.percentile.text": "<topLabel>Cela vous place dans le top</topLabel><pourcentage></percentage><bottomLabel>des utilisateurs de {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Nous ne le dirons pas à Bernie.",
-  "annual_report.summary.thanks": "Merci de faire partie de Mastodon!",
   "attachments_list.unprocessed": "(non traité)",
   "audio.hide": "Masquer l'audio",
   "block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.",
   "block_modal.show_less": "Afficher moins",
   "block_modal.show_more": "Afficher plus",
   "block_modal.they_cant_mention": "Il ne peut pas vous mentionner ou vous suivre.",
-  "block_modal.they_cant_see_posts": "Il ne peut plus voir vos messages et vous ne verrez plus les siens.",
+  "block_modal.they_cant_see_posts": "Il peut toujours voir vos messages, mais vous ne verrez pas les siens.",
   "block_modal.they_will_know": "Il peut voir qu'il est bloqué.",
   "block_modal.title": "Bloquer le compte ?",
   "block_modal.you_wont_see_mentions": "Vous ne verrez pas les messages qui le mentionne.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "La page demandée est introuvable. Êtes-vous sûr que l’URL dans la barre d’adresse est correcte ?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Fermer",
-  "bundle_modal_error.message": "Un problème s'est produit lors du chargement de cet écran.",
+  "bundle_modal_error.message": "Une erreur s’est produite lors du chargement de ce composant.",
   "bundle_modal_error.retry": "Réessayer",
   "closed_registrations.other_server_instructions": "Puisque Mastodon est décentralisé, vous pouvez créer un compte sur un autre serveur et interagir quand même avec celui-ci.",
   "closed_registrations_modal.description": "Créer un compte sur {domain} est actuellement impossible, néanmoins souvenez-vous que vous n'avez pas besoin d'un compte spécifiquement sur {domain} pour utiliser Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Utilisateurs bloqués",
   "column.bookmarks": "Marque-pages",
   "column.community": "Fil public local",
-  "column.create_list": "Créer une liste",
   "column.direct": "Mentions privées",
   "column.directory": "Parcourir les profils",
   "column.domain_blocks": "Domaines bloqués",
-  "column.edit_list": "Modifier la liste",
   "column.favourites": "Favoris",
   "column.firehose": "Flux en direct",
   "column.follow_requests": "Demandes d'abonnement",
   "column.home": "Accueil",
-  "column.list_members": "Gérer les membres de la liste",
   "column.lists": "Listes",
   "column.mutes": "Comptes masqués",
   "column.notifications": "Notifications",
@@ -172,7 +140,6 @@
   "column_header.pin": "Épingler",
   "column_header.show_settings": "Afficher les paramètres",
   "column_header.unpin": "Désépingler",
-  "column_search.cancel": "Annuler",
   "column_subheading.settings": "Paramètres",
   "community.column_settings.local_only": "Local seulement",
   "community.column_settings.media_only": "Média uniquement",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Modifier",
   "confirmations.edit.message": "Modifier maintenant écrasera votre message en cours de rédaction. Voulez-vous vraiment continuer ?",
   "confirmations.edit.title": "Remplacer le message ?",
-  "confirmations.follow_to_list.confirm": "Suivre et ajouter à la liste",
-  "confirmations.follow_to_list.message": "Vous devez suivre {name} pour l'ajouter à une liste.",
-  "confirmations.follow_to_list.title": "Suivre l'utilisateur ?",
   "confirmations.logout.confirm": "Se déconnecter",
   "confirmations.logout.message": "Voulez-vous vraiment vous déconnecter ?",
   "confirmations.logout.title": "Se déconnecter ?",
-  "confirmations.missing_alt_text.confirm": "Ajouter un texte alternatif",
-  "confirmations.missing_alt_text.message": "Votre post contient des médias sans texte alternatif. Ajouter des descriptions rend votre contenu accessible à un plus grand nombre de personnes.",
-  "confirmations.missing_alt_text.secondary": "Publier quand-même",
-  "confirmations.missing_alt_text.title": "Ajouter un texte alternatif?",
   "confirmations.mute.confirm": "Masquer",
   "confirmations.redraft.confirm": "Supprimer et ré-écrire",
   "confirmations.redraft.message": "Voulez-vous vraiment supprimer le message pour le réécrire ? Ses partages ainsi que ses mises en favori seront perdues, et ses réponses seront orphelines.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Votre compte {disabledAccount} est actuellement désactivé.",
   "dismissable_banner.community_timeline": "Voici les messages publics les plus récents des comptes hébergés par {domain}.",
   "dismissable_banner.dismiss": "Rejeter",
-  "dismissable_banner.explore_links": "Ces nouvelles sont les plus partagées sur le fediverse aujourd'hui. Les nouvelles plus récentes postées par un plus grand nombre de personnes sont mieux classées.",
-  "dismissable_banner.explore_statuses": "Ces messages provenant de l'ensemble du fediverse gagnent en popularité aujourd'hui. Les messages les plus récents qui ont reçu le plus d'encouragements et de favoris sont mieux classés.",
-  "dismissable_banner.explore_tags": "Ces hashtags gagnent du terrain sur le fediverse aujourd'hui. Les hashtags qui sont utilisés par un plus grand nombre de personnes différentes sont mieux classés.",
-  "dismissable_banner.public_timeline": "Il s'agit des messages publics les plus récents publiés par des personnes sur le fediverse que les personnes sur {domain} suivent.",
+  "dismissable_banner.explore_links": "On parle actuellement de ces nouvelles sur ce serveur, ainsi que sur d'autres serveurs du réseau décentralisé.",
+  "dismissable_banner.explore_statuses": "Ces messages venant de tout le web social gagnent en popularité aujourd’hui. Les nouveaux messages avec plus de boosts et de favoris sont classés plus haut.",
+  "dismissable_banner.explore_tags": "Ces hashtags sont actuellement en train de gagner de l'ampleur parmi les personnes sur les serveurs du réseau décentralisé dont celui-ci.",
+  "dismissable_banner.public_timeline": "Il s'agit des messages publics les plus récents publiés par des gens sur le web social et que les utilisateurs de {domain} suivent.",
   "domain_block_modal.block": "Bloquer le serveur",
   "domain_block_modal.block_account_instead": "Bloquer @{name} à la place",
   "domain_block_modal.they_can_interact_with_old_posts": "Les personnes de ce serveur peuvent interagir avec vos anciens messages.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Résultats de la recherche",
   "emoji_button.symbols": "Symboles",
   "emoji_button.travel": "Voyage et lieux",
-  "empty_column.account_featured": "Cette liste est vide",
   "empty_column.account_hides_collections": "Cet utilisateur·ice préfère ne pas rendre publiques ces informations",
   "empty_column.account_suspended": "Compte suspendu",
   "empty_column.account_timeline": "Aucun message ici !",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.",
   "empty_column.home": "Votre fil principal est vide ! Suivez plus de personnes pour le remplir.",
   "empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Quand des membres de cette liste publieront de nouveaux messages, ils apparaîtront ici.",
+  "empty_column.lists": "Vous n’avez pas encore de liste. Lorsque vous en créerez une, elle apparaîtra ici.",
   "empty_column.mutes": "Vous n’avez masqué aucun compte pour le moment.",
   "empty_column.notification_requests": "C'est fini ! Il n'y a plus rien ici. Lorsque vous recevez de nouvelles notifications, elles apparaitront ici conformément à vos préférences.",
   "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Essayez de les désactiver et de rafraîchir la page. Si cela ne vous aide pas, vous pouvez toujours utiliser Mastodon via un autre navigateur ou une application native.",
   "errors.unexpected_crash.copy_stacktrace": "Copier la trace d'appels dans le presse-papier",
   "errors.unexpected_crash.report_issue": "Signaler le problème",
+  "explore.search_results": "Résultats de la recherche",
   "explore.suggested_follows": "Personnes",
   "explore.title": "Explorer",
   "explore.trending_links": "Nouvelles",
@@ -373,14 +334,13 @@
   "footer.about": "À propos",
   "footer.directory": "Annuaire des profils",
   "footer.get_app": "Télécharger l’application",
+  "footer.invite": "Inviter des personnes",
   "footer.keyboard_shortcuts": "Raccourcis clavier",
   "footer.privacy_policy": "Politique de confidentialité",
   "footer.source_code": "Voir le code source",
   "footer.status": "État",
-  "footer.terms_of_service": "Conditions d’utilisation",
   "generic.saved": "Sauvegardé",
   "getting_started.heading": "Pour commencer",
-  "hashtag.admin_moderation": "Ouvrir l'interface de modération pour #{name}",
   "hashtag.column_header.tag_mode.all": "et {additional}",
   "hashtag.column_header.tag_mode.any": "ou {additional}",
   "hashtag.column_header.tag_mode.none": "sans {additional}",
@@ -422,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignorer les notifications provenant des personnes qui ne vous suivent pas ?",
   "ignore_notifications_modal.not_following_title": "Ignorer les notifications provenant des personnes que vous ne suivez pas ?",
   "ignore_notifications_modal.private_mentions_title": "Ignorer les notifications issues des mentions privées non sollicitées ?",
-  "info_button.label": "Aide",
-  "info_button.what_is_alt_text": "<h1>Qu'est-ce que le texte alternatif ?</h1> <p>Un texte alternatif fournit une description de l'image aux personnes avec un handicap visuel ou une connexion limitée ou qui souhaitent avoir un contexte supplémentaire.</p> <p>Vous pouvez améliorer l'accessibilité et la compression de tout le monde en écrivant un texte alternatif clair, concis et objectif.</p> <ul> <li>Identifiez les éléments importants</li> <li>Résumez le texte présent à l'image</li> <li>Utilisez une structure de phrase normale</li> <li>Évitez les informations redondantes</li> <li>Pour les visuels complexes (tels que les diagrammes ou les cartes), indiquez les tendances ou points-clés</li> </ul>",
-  "interaction_modal.action.favourite": "Pour continuer, vous devez ajouter en favori depuis votre compte.",
-  "interaction_modal.action.follow": "Pour continuer, vous devez suivre depuis votre compte.",
-  "interaction_modal.action.reblog": "Pour continuer, vous devez booster depuis votre compte.",
-  "interaction_modal.action.reply": "Pour continuer, vous devez répondre depuis votre compte.",
-  "interaction_modal.action.vote": "Pour continuer, vous devez voter depuis votre compte.",
-  "interaction_modal.go": "Valider",
-  "interaction_modal.no_account_yet": "Vous n'avez pas encore de compte ?",
+  "interaction_modal.description.favourite": "Avec un compte Mastodon, vous pouvez ajouter ce message à vos favoris pour informer l'auteur⋅rice que vous l'appréciez et pour le sauvegarder pour plus tard.",
+  "interaction_modal.description.follow": "Avec un compte Mastodon, vous pouvez suivre {name} et recevoir leurs posts dans votre fil d'actualité.",
+  "interaction_modal.description.reblog": "Avec un compte sur Mastodon, vous pouvez partager ce message pour le faire découvrir à vos propres abonné⋅e⋅s.",
+  "interaction_modal.description.reply": "Avec un compte sur Mastodon, vous pouvez répondre à ce message.",
+  "interaction_modal.login.action": "Aller à mon serveur",
+  "interaction_modal.login.prompt": "Domaine de votre serveur, ex. mastodon.social",
+  "interaction_modal.no_account_yet": "Pas sur Mastodon ?",
   "interaction_modal.on_another_server": "Sur un autre serveur",
   "interaction_modal.on_this_server": "Sur ce serveur",
+  "interaction_modal.sign_in": "Vous n’êtes pas connectés sur ce serveur. Où est hébergé votre compte ?",
+  "interaction_modal.sign_in_hint": "Astuce : c'est le site web sur lequel vous vous êtes inscrit. Si vous ne vous en souvenez pas, cherchez le courriel de bienvenue dans votre boîte de réception. Vous pouvez aussi indiquer votre nom d’utilisateur complet ! (par ex. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Ajouter le message de {name} aux favoris",
   "interaction_modal.title.follow": "Suivre {name}",
   "interaction_modal.title.reblog": "Partager le message de {name}",
   "interaction_modal.title.reply": "Répondre au message de {name}",
-  "interaction_modal.title.vote": "Voter pour le sondage de {name}",
-  "interaction_modal.username_prompt": "Par exemple : {example}",
   "intervals.full.days": "{number, plural, one {# jour} other {# jours}}",
   "intervals.full.hours": "{number, plural, one {# heure} other {# heures}}",
   "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
@@ -474,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Déplier/replier le texte derrière un CW",
   "keyboard_shortcuts.toggle_sensitivity": "Afficher/cacher les médias",
   "keyboard_shortcuts.toot": "Commencer un nouveau message",
-  "keyboard_shortcuts.translate": "traduire un message",
   "keyboard_shortcuts.unfocus": "Quitter la zone de rédaction/barre de recherche",
   "keyboard_shortcuts.up": "Monter dans la liste",
   "lightbox.close": "Fermer",
@@ -487,32 +444,20 @@
   "link_preview.author": "Par {name}",
   "link_preview.more_from_author": "Plus via {name}",
   "link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
-  "lists.add_member": "Ajouter",
-  "lists.add_to_list": "Ajouter à la liste",
-  "lists.add_to_lists": "Ajouter {name} aux listes",
-  "lists.create": "Créer",
-  "lists.create_a_list_to_organize": "Créer une nouvelle liste pour organiser votre Page d'accueil",
-  "lists.create_list": "Créer une liste",
+  "lists.account.add": "Ajouter à la liste",
+  "lists.account.remove": "Supprimer de la liste",
   "lists.delete": "Supprimer la liste",
-  "lists.done": "Terminé",
   "lists.edit": "Modifier la liste",
-  "lists.exclusive": "Cacher les membres de la page d'accueil",
-  "lists.exclusive_hint": "Si quelqu'un est dans cette liste, les cacher dans votre fil pour éviter de voir leurs messages deux fois.",
-  "lists.find_users_to_add": "Trouver des utilisateurs à ajouter",
-  "lists.list_members": "Lister les membres",
-  "lists.list_members_count": "{count, plural, one {# member} other {# members}}",
-  "lists.list_name": "Nom de la liste",
-  "lists.new_list_name": "Nom de la nouvelle liste",
-  "lists.no_lists_yet": "Aucune liste pour l'instant.",
-  "lists.no_members_yet": "Aucun membre pour l'instant.",
-  "lists.no_results_found": "Aucun résultat.",
-  "lists.remove_member": "Supprimer",
+  "lists.edit.submit": "Modifier le titre",
+  "lists.exclusive": "Cacher ces publications sur le fil principal",
+  "lists.new.create": "Ajouter une liste",
+  "lists.new.title_placeholder": "Titre de la nouvelle liste",
   "lists.replies_policy.followed": "N'importe quel compte suivi",
   "lists.replies_policy.list": "Membres de la liste",
   "lists.replies_policy.none": "Personne",
-  "lists.save": "Enregistrer",
-  "lists.search": "Recherche",
-  "lists.show_replies_to": "Inclure les réponses des membres de la liste à",
+  "lists.replies_policy.title": "Afficher les réponses à :",
+  "lists.search": "Rechercher parmi les gens que vous suivez",
+  "lists.subheading": "Vos listes",
   "load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
   "loading_indicator.label": "Chargement…",
   "media_gallery.hide": "Masquer",
@@ -561,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} a signalé {target}",
   "notification.admin.sign_up": "{name} s'est inscrit·e",
   "notification.admin.sign_up.name_and_others": "{name} et {count, plural, one {# autre} other {# autres}} se sont inscrit",
-  "notification.annual_report.message": "Votre {year} #Wrapstodon attend ! Dévoilez les moments forts et mémorables de votre année sur Mastodon !",
-  "notification.annual_report.view": "Voir #Wrapstodon",
   "notification.favourite": "{name} a ajouté votre message à ses favoris",
   "notification.favourite.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont mis votre message en favori",
-  "notification.favourite_pm": "{name} a mis votre mention privée en favori",
-  "notification.favourite_pm.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont mis votre mention privée en favori",
   "notification.follow": "{name} vous suit",
   "notification.follow.name_and_others": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> vous suivent",
   "notification.follow_request": "{name} a demandé à vous suivre",
@@ -671,21 +612,44 @@
   "notifications_permission_banner.enable": "Activer les notifications de bureau",
   "notifications_permission_banner.how_to_control": "Pour recevoir des notifications lorsque Mastodon n’est pas ouvert, activez les notifications du bureau. Vous pouvez contrôler précisément quels types d’interactions génèrent des notifications de bureau via le bouton {icon} ci-dessus une fois qu’elles sont activées.",
   "notifications_permission_banner.title": "Toujours au courant",
-  "onboarding.follows.back": "Retour",
-  "onboarding.follows.done": "Terminé",
+  "onboarding.action.back": "Revenir en arrière",
+  "onboarding.actions.back": "Revenir en arrière",
+  "onboarding.actions.go_to_explore": "Aller aux tendances",
+  "onboarding.actions.go_to_home": "Allers vers mon flux principal",
+  "onboarding.compose.template": "Bonjour #Mastodon !",
   "onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer d'utiliser la recherche ou parcourir la page de découverte pour trouver des personnes à suivre, ou réessayez plus tard.",
-  "onboarding.follows.search": "Recherche",
-  "onboarding.follows.title": "Suivre des personnes pour commencer",
+  "onboarding.follows.lead": "Votre flux principal est le principal moyen de découvrir Mastodon. Plus vous suivez de personnes, plus il sera actif et intéressant. Pour commencer, voici quelques suggestions :",
+  "onboarding.follows.title": "Personnaliser votre flux principal",
   "onboarding.profile.discoverable": "Rendre mon profil découvrable",
   "onboarding.profile.discoverable_hint": "Lorsque vous acceptez d'être découvert sur Mastodon, vos messages peuvent apparaître dans les résultats de recherche et les tendances, et votre profil peut être suggéré à des personnes ayant des intérêts similaires aux vôtres.",
   "onboarding.profile.display_name": "Nom affiché",
   "onboarding.profile.display_name_hint": "Votre nom complet ou votre nom rigolo…",
+  "onboarding.profile.lead": "Vous pouvez toujours compléter cela plus tard dans les paramètres. Vous y trouverez encore plus d'options de personnalisation.",
   "onboarding.profile.note": "Biographie",
   "onboarding.profile.note_hint": "Vous pouvez @mentionner d'autres personnes ou #hashtags…",
   "onboarding.profile.save_and_continue": "Enregistrer et continuer",
   "onboarding.profile.title": "Configuration du profil",
   "onboarding.profile.upload_avatar": "Importer une photo de profil",
   "onboarding.profile.upload_header": "Importer un entête de profil",
+  "onboarding.share.lead": "Faites savoir aux gens comment ils peuvent vous trouver sur Mastodon!",
+  "onboarding.share.message": "Je suis {username} sur #Mastodon ! Suivez-moi sur {url}",
+  "onboarding.share.next_steps": "Étapes suivantes possibles :",
+  "onboarding.share.title": "Partager votre profil",
+  "onboarding.start.lead": "Vous faites désormais partie de Mastodon, une plateforme de médias sociaux unique et décentralisée où c'est vous, et non un algorithme, qui créez votre propre expérience. Nous allons vous aider à vous lancer dans cette nouvelle frontière sociale :",
+  "onboarding.start.skip": "Vous n’avez donc pas besoin d’aide pour commencer ?",
+  "onboarding.start.title": "Vous avez réussi !",
+  "onboarding.steps.follow_people.body": "Suivre des personnes intéressantes, c'est la raison d'être de Mastodon.",
+  "onboarding.steps.follow_people.title": "Personnaliser votre flux principal",
+  "onboarding.steps.publish_status.body": "Dites bonjour au monde avec du texte, des photos, des vidéos ou des sondages {emoji}",
+  "onboarding.steps.publish_status.title": "Rédigez votre premier message",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Personnaliser votre profil",
+  "onboarding.steps.share_profile.body": "Faites savoir à vos ami·e·s comment vous trouver sur Mastodon",
+  "onboarding.steps.share_profile.title": "Partagez votre profil Mastodon",
+  "onboarding.tips.2fa": "<strong>Le saviez-vous ?</strong> Vous pouvez sécuriser votre compte en configurant l'authentification à deux facteurs dans les paramètres de votre compte. Il fonctionne avec n'importe quelle application TOTP de votre choix, sans numéro de téléphone nécessaire !",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Le saviez-vous ?</strong> Puisque Mastodon est décentralisé, certains profils que vous rencontrez seront hébergés sur des serveurs autres que les vôtres. Et pourtant, vous pouvez interagir avec eux ! Leur serveur est dans la seconde moitié de leur nom d'utilisateur !",
+  "onboarding.tips.migration": "<strong>Le saviez-vous ?</strong> Si vous avez l'impression que {domain} n'est pas un bon choix de serveur pour vous dans le futur, vous pouvez vous déplacer sur un autre serveur Mastodon sans perdre vos abonnés. Vous pouvez même héberger votre propre serveur!",
+  "onboarding.tips.verification": "<strong>Le saviez-vous ?</strong> Vous pouvez vérifier votre compte en mettant un lien vers votre profil Mastodon sur votre propre site web et en ajoutant le site à votre profil. Pas de frais ou de documents nécessaires !",
   "password_confirmation.exceeds_maxlength": "La confirmation du mot de passe dépasse la longueur du mot de passe",
   "password_confirmation.mismatching": "Les deux mots de passe ne correspondent pas",
   "picture_in_picture.restore": "Remettre en place",
@@ -701,7 +665,7 @@
   "poll_button.remove_poll": "Supprimer le sondage",
   "privacy.change": "Ajuster la confidentialité du message",
   "privacy.direct.long": "Toutes les personnes mentionnées dans le post",
-  "privacy.direct.short": "Mention privée",
+  "privacy.direct.short": "Personnes spécifiques",
   "privacy.private.long": "Seulement vos abonnés",
   "privacy.private.short": "Abonnés",
   "privacy.public.long": "Tout le monde sur et en dehors de Mastodon",
@@ -713,8 +677,8 @@
   "privacy_policy.title": "Politique de confidentialité",
   "recommended": "Recommandé",
   "refresh": "Actualiser",
-  "regeneration_indicator.please_stand_by": "Veuillez patienter.",
-  "regeneration_indicator.preparing_your_home_feed": "Préparation de votre flux principal…",
+  "regeneration_indicator.label": "Chargement…",
+  "regeneration_indicator.sublabel": "Votre fil principal est en cours de préparation !",
   "relative_time.days": "{number} j",
   "relative_time.full.days": "il y a {number, plural, one {# jour} other {# jours}}",
   "relative_time.full.hours": "il y a {number, plural, one {# heure} other {# heures}}",
@@ -798,11 +762,10 @@
   "search_results.accounts": "Profils",
   "search_results.all": "Tous les résultats",
   "search_results.hashtags": "Hashtags",
-  "search_results.no_results": "Aucun résultat.",
-  "search_results.no_search_yet": "Essayez de rechercher des messages, des profils ou des hashtags.",
+  "search_results.nothing_found": "Aucun résultat avec ces mots-clefs",
   "search_results.see_all": "Afficher tout",
   "search_results.statuses": "Messages",
-  "search_results.title": "Résultat de Recherche pour \"{q}\"",
+  "search_results.title": "Rechercher {q}",
   "server_banner.about_active_users": "Personnes utilisant ce serveur au cours des 30 derniers jours (Comptes actifs mensuellement)",
   "server_banner.active_users": "comptes actifs",
   "server_banner.administered_by": "Administré par :",
@@ -854,7 +817,6 @@
   "status.reblogs.empty": "Personne n’a encore partagé ce message. Lorsque quelqu’un le fera, il apparaîtra ici.",
   "status.redraft": "Supprimer et réécrire",
   "status.remove_bookmark": "Retirer des marque-pages",
-  "status.remove_favourite": "Retirer des favoris",
   "status.replied_in_thread": "A répondu dans un fil de discussion",
   "status.replied_to": "En réponse à {name}",
   "status.reply": "Répondre",
@@ -876,9 +838,6 @@
   "subscribed_languages.target": "Changer les langues abonnées pour {target}",
   "tabs_bar.home": "Accueil",
   "tabs_bar.notifications": "Notifications",
-  "terms_of_service.effective_as_of": "En vigueur à compter du {date}",
-  "terms_of_service.title": "Conditions d'utilisation",
-  "terms_of_service.upcoming_changes_on": "Modifications à venir le {date}",
   "time_remaining.days": "{number, plural, one {# jour restant} other {# jours restants}}",
   "time_remaining.hours": "{number, plural, one {# heure restante} other {# heures restantes}}",
   "time_remaining.minutes": "{number, plural, one {# minute restante} other {# minutes restantes}}",
@@ -894,12 +853,26 @@
   "upload_button.label": "Ajouter des images, une vidéo ou un fichier audio",
   "upload_error.limit": "Taille maximale d'envoi de fichier dépassée.",
   "upload_error.poll": "L’envoi de fichiers n’est pas autorisé avec les sondages.",
+  "upload_form.audio_description": "Décrire pour les personnes ayant des difficultés d’audition",
+  "upload_form.description": "Décrire pour les malvoyant·e·s",
   "upload_form.drag_and_drop.instructions": "Pour choisir un média joint, appuyez sur la touche espace ou entrée. Tout en faisant glisser, utilisez les touches fléchées pour déplacer le fichier média dans une direction donnée. Appuyez à nouveau sur la touche espace ou entrée pour déposer le fichier média dans sa nouvelle position, ou appuyez sur la touche Echap pour annuler.",
   "upload_form.drag_and_drop.on_drag_cancel": "Le glissement a été annulé. La pièce jointe {item} n'a pas été ajoutée.",
   "upload_form.drag_and_drop.on_drag_end": "La pièce jointe du média {item} a été déplacée.",
   "upload_form.drag_and_drop.on_drag_over": "La pièce jointe du média {item} a été déplacée.",
   "upload_form.drag_and_drop.on_drag_start": "A récupéré la pièce jointe {item}.",
   "upload_form.edit": "Modifier",
+  "upload_form.thumbnail": "Changer la vignette",
+  "upload_form.video_description": "Décrire pour les personnes ayant des problèmes de vue ou d'audition",
+  "upload_modal.analyzing_picture": "Analyse de l’image en cours…",
+  "upload_modal.apply": "Appliquer",
+  "upload_modal.applying": "Application en cours…",
+  "upload_modal.choose_image": "Choisir une image",
+  "upload_modal.description_placeholder": "Buvez de ce whisky que le patron juge fameux",
+  "upload_modal.detect_text": "Détecter le texte de l’image",
+  "upload_modal.edit_media": "Modifier le média",
+  "upload_modal.hint": "Cliquez ou faites glisser le cercle sur l’aperçu pour choisir le point focal qui sera toujours visible sur toutes les miniatures.",
+  "upload_modal.preparing_ocr": "Préparation de l’OCR…",
+  "upload_modal.preview_label": "Aperçu ({ratio})",
   "upload_progress.label": "Envoi en cours…",
   "upload_progress.processing": "En cours…",
   "username.taken": "Ce nom d'utilisateur est déjà pris. Essayez d'en prendre un autre",
@@ -912,7 +885,5 @@
   "video.mute": "Couper le son",
   "video.pause": "Pause",
   "video.play": "Lecture",
-  "video.unmute": "Rétablir le son",
-  "video.volume_down": "Baisser le volume",
-  "video.volume_up": "Augmenter le volume"
+  "video.unmute": "Rétablir le son"
 }
diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json
index e3c3222868..a799b65acc 100644
--- a/app/javascript/mastodon/locales/fy.json
+++ b/app/javascript/mastodon/locales/fy.json
@@ -29,6 +29,7 @@
   "account.endorse": "Op profyl werjaan",
   "account.featured_tags.last_status_at": "Lêste berjocht op {date}",
   "account.featured_tags.last_status_never": "Gjin berjochten",
+  "account.featured_tags.title": "Utljochte hashtags fan {name}",
   "account.follow": "Folgje",
   "account.follow_back": "Weromfolgje",
   "account.followers": "Folgers",
@@ -85,33 +86,7 @@
   "alert.unexpected.message": "Der is in ûnferwachte flater bard.",
   "alert.unexpected.title": "Oepsy!",
   "alt_text_badge.title": "Alternative tekst",
-  "alt_text_modal.add_alt_text": "Alt-tekst tafoegje",
-  "alt_text_modal.add_text_from_image": "Tekst fan ôfbylding tafoegje",
-  "alt_text_modal.cancel": "Annulearje",
-  "alt_text_modal.change_thumbnail": "Miniatuerôfbylding wizigje",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Beskriuw dit foar dôven en hurdhearrige…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Beskriuw dit foar blinen en fisueel beheinde…",
-  "alt_text_modal.done": "Klear",
   "announcement.announcement": "Oankundiging",
-  "annual_report.summary.archetype.booster": "De cool-hunter",
-  "annual_report.summary.archetype.lurker": "De lurker",
-  "annual_report.summary.archetype.oracle": "It orakel",
-  "annual_report.summary.archetype.pollster": "De opinypeiler",
-  "annual_report.summary.archetype.replier": "De sosjale flinter",
-  "annual_report.summary.followers.followers": "folgers",
-  "annual_report.summary.followers.total": "totaal {count}",
-  "annual_report.summary.here_it_is": "Jo jieroersjoch foar {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "berjocht mei de measte favoriten",
-  "annual_report.summary.highlighted_post.by_reblogs": "berjocht mei de measte boosts",
-  "annual_report.summary.highlighted_post.by_replies": "berjocht mei de measte reaksjes",
-  "annual_report.summary.highlighted_post.possessive": "{name}’s",
-  "annual_report.summary.most_used_app.most_used_app": "meast brûkte app",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "meast brûkte hashtag",
-  "annual_report.summary.most_used_hashtag.none": "Gjin",
-  "annual_report.summary.new_posts.new_posts": "nije berjochten",
-  "annual_report.summary.percentile.text": "<topLabel>Hjirmei hearre jo ta de top</topLabel><percentage></percentage><bottomLabel> fan {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Wy sille Bernie neat fertelle.",
-  "annual_report.summary.thanks": "Tank dat jo part binne fan Mastodon!",
   "attachments_list.unprocessed": "(net ferwurke)",
   "audio.hide": "Audio ferstopje",
   "block_modal.remote_users_caveat": "Wy freegje de server {domain} om jo beslút te respektearjen. It neilibben hjirfan is echter net garandearre, omdat guon servers blokkaden oars ynterpretearje kinne. Iepenbiere berjochten binne mooglik noch hieltyd sichtber foar net-oanmelde brûkers.",
@@ -135,7 +110,7 @@
   "bundle_column_error.routing.body": "De opfrege side kin net fûn wurde. Binne jo wis dat de URL yn de adresbalke goed is?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Slute",
-  "bundle_modal_error.message": "Der gie wat mis by it laden fan dit skerm.",
+  "bundle_modal_error.message": "Der gie wat mis by it laden fan dizze komponint.",
   "bundle_modal_error.retry": "Opnij probearje",
   "closed_registrations.other_server_instructions": "Omdat Mastodon desintralisearre is, kinne jo in account meitsje op in oare server en noch hieltyd ynteraksje hawwe mei dizze.",
   "closed_registrations_modal.description": "It oanmeitsjen fan in account op {domain} is op dit stuit net mooglik, mar hâld asjebleaft yn gedachten dat jo gjin account spesifyk op {domain} nedich hawwe om Mastodon te brûken.",
@@ -146,16 +121,13 @@
   "column.blocks": "Blokkearre brûkers",
   "column.bookmarks": "Blêdwizers",
   "column.community": "Lokale tiidline",
-  "column.create_list": "List oanmeitsje",
   "column.direct": "Priveefermeldingen",
   "column.directory": "Profilen trochsykje",
   "column.domain_blocks": "Blokkearre domeinen",
-  "column.edit_list": "List bewurkje",
   "column.favourites": "Favoriten",
   "column.firehose": "Live feeds",
   "column.follow_requests": "Folchfersiken",
   "column.home": "Startside",
-  "column.list_members": "Listleden beheare",
   "column.lists": "Listen",
   "column.mutes": "Negearre brûkers",
   "column.notifications": "Meldingen",
@@ -168,7 +140,6 @@
   "column_header.pin": "Fêstsette",
   "column_header.show_settings": "Ynstellingen toane",
   "column_header.unpin": "Losmeitsje",
-  "column_search.cancel": "Annulearje",
   "column_subheading.settings": "Ynstellingen",
   "community.column_settings.local_only": "Allinnich lokaal",
   "community.column_settings.media_only": "Allinnich media",
@@ -187,7 +158,7 @@
   "compose_form.poll.duration": "Doer fan de enkête",
   "compose_form.poll.multiple": "Mearkar",
   "compose_form.poll.option_placeholder": "Opsje {number}",
-  "compose_form.poll.single": "Inkelde kar",
+  "compose_form.poll.single": "Kies ien",
   "compose_form.poll.switch_to_multiple": "Enkête wizigje om meardere karren ta te stean",
   "compose_form.poll.switch_to_single": "Enkête wizigje om in inkelde kar ta te stean",
   "compose_form.poll.type": "Styl",
@@ -211,9 +182,6 @@
   "confirmations.edit.confirm": "Bewurkje",
   "confirmations.edit.message": "Troch no te bewurkjen sil it berjocht dat jo no oan it skriuwen binne oerskreaun wurde. Wolle jo trochgean?",
   "confirmations.edit.title": "Berjocht oerskriuwe?",
-  "confirmations.follow_to_list.confirm": "Folgje en tafoegje oan de list",
-  "confirmations.follow_to_list.message": "Jo moatte {name} folgje om se ta te foegjen oan in list.",
-  "confirmations.follow_to_list.title": "Brûker folgje?",
   "confirmations.logout.confirm": "Ofmelde",
   "confirmations.logout.message": "Binne jo wis dat jo ôfmelde wolle?",
   "confirmations.logout.title": "Ofmelde?",
@@ -245,7 +213,10 @@
   "disabled_account_banner.text": "Jo account {disabledAccount} is op dit stuit útskeakele.",
   "dismissable_banner.community_timeline": "Dit binne de meast resinte iepenbiere berjochten fan accounts op {domain}.",
   "dismissable_banner.dismiss": "Slute",
-  "dismissable_banner.explore_links": "Dizze nijsartikelen wurde hjoed de dei it meast dield op de fediverse. Nijere artikelen dy’t troch mear ferskate minsken pleatst binne, wurde heger rangskikt.",
+  "dismissable_banner.explore_links": "Dizze nijsberjochten winne oan populariteit op dizze en oare servers binnen it desintrale netwurk.",
+  "dismissable_banner.explore_statuses": "Dizze berjochten winne oan populariteit op dizze en oare servers binnen it desintrale netwurk. Nijere berjochten mei mear boosts en favoriten stean heger.",
+  "dismissable_banner.explore_tags": "Dizze hashtags winne oan populariteit op dizze en oare servers binnen it desintrale netwurk.",
+  "dismissable_banner.public_timeline": "Dit binne de meast resinte iepenbiere berjochten fan accounts op it sosjale web dy’t troch minsken op {domain} folge wurde.",
   "domain_block_modal.block": "Server blokkearje",
   "domain_block_modal.block_account_instead": "Yn stee hjirfan {name} blokkearje",
   "domain_block_modal.they_can_interact_with_old_posts": "Minsken op dizze server kinne ynteraksje hawwe mei jo âlde berjochten.",
@@ -302,6 +273,7 @@
   "empty_column.hashtag": "Der is noch neat te finen ûnder dizze hashtag.",
   "empty_column.home": "Dizze tiidline is leech! Folgje mear minsken om it te foljen. {suggestions}",
   "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
+  "empty_column.lists": "Jo hawwe noch gjin inkelde list. Wannear’t jo der ien oanmakke hawwe, falt dat hjir te sjen.",
   "empty_column.mutes": "Jo hawwe noch gjin brûkers negearre.",
   "empty_column.notification_requests": "Hielendal leech! Der is hjir neat. Wannear’t jo nije meldingen ûntfange, ferskine dizze hjir neffens jo ynstellingen.",
   "empty_column.notifications": "Jo hawwe noch gjin meldingen. Ynteraksjes mei oare minsken sjogge jo hjir.",
@@ -312,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Probearje dizze út te skeakeljen en de side te fernijen. Wannear’t dit net helpt is it noch hieltyd mooglik om Mastodon yn in oare browser of mobile app te brûken.",
   "errors.unexpected_crash.copy_stacktrace": "Stacktrace nei klamboerd kopiearje",
   "errors.unexpected_crash.report_issue": "Technysk probleem melde",
+  "explore.search_results": "Sykresultaten",
   "explore.suggested_follows": "Minsken",
   "explore.title": "Ferkenne",
   "explore.trending_links": "Nijs",
@@ -361,6 +334,7 @@
   "footer.about": "Oer",
   "footer.directory": "Profylmap",
   "footer.get_app": "App downloade",
+  "footer.invite": "Minsken útnûgje",
   "footer.keyboard_shortcuts": "Fluchtoetsen",
   "footer.privacy_policy": "Privacybelied",
   "footer.source_code": "Boarnekoade besjen",
@@ -408,17 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Meldingen negearje fan minsken dy’t jo net folgje?",
   "ignore_notifications_modal.not_following_title": "Meldingen negearje fan minsken dy’t josels net folgje?",
   "ignore_notifications_modal.private_mentions_title": "Meldingen negearje fan net frege priveeberjochten?",
-  "info_button.label": "Help",
-  "interaction_modal.go": "Gean",
-  "interaction_modal.no_account_yet": "Hawwe jo noch gjin account?",
+  "interaction_modal.description.favourite": "Jo kinne mei in Mastodon-account dit berjocht as favoryt markearje, om dy brûker witte te litten dat jo it berjocht wurdearje en om it te bewarjen.",
+  "interaction_modal.description.follow": "Jo kinne mei in Mastodon-account {name} folgje, om sa harren berjochten op jo starttiidline te ûntfangen.",
+  "interaction_modal.description.reblog": "Jo kinne mei in Mastodon-account dit berjocht booste, om it sa mei jo folgers te dielen.",
+  "interaction_modal.description.reply": "Jo kinne mei in Mastodon-account op dit berjocht reagearje.",
+  "interaction_modal.login.action": "Gean nei start",
+  "interaction_modal.login.prompt": "Domein fan jo server, byg. mastodon.social",
+  "interaction_modal.no_account_yet": "Net op Mastodon?",
   "interaction_modal.on_another_server": "Op een oare server",
   "interaction_modal.on_this_server": "Op dizze server",
+  "interaction_modal.sign_in": "Jo binne net op dizze server oanmeld. Op hokker server stiet jo account?",
+  "interaction_modal.sign_in_hint": "Tip: Dat is de website wêrop jo jo registrearre hawwe. Wannear’t jo dit ferjitten binne kinne jo nei it wolkomst-emailberjocht sykje yn jo Postfek YN. Jo kinne ek jo folsleine brûkersnamme ynfolje! (byg. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Berjocht fan {name} as favoryt markearje",
   "interaction_modal.title.follow": "{name} folgje",
   "interaction_modal.title.reblog": "Berjocht fan {name} booste",
   "interaction_modal.title.reply": "Op it berjocht fan {name} reagearje",
-  "interaction_modal.title.vote": "Stimme yn {name}’s peiling",
-  "interaction_modal.username_prompt": "Byg. {example}",
   "intervals.full.days": "{number, plural, one {# dei} other {# dagen}} lyn",
   "intervals.full.hours": "{number, plural, one {# oere} other {# oeren}} lyn",
   "intervals.full.minutes": "{number, plural, one {# minút} other {# minuten}} lyn",
@@ -454,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Tekst efter CW-fjild ferstopje/toane",
   "keyboard_shortcuts.toggle_sensitivity": "Media ferstopje/toane",
   "keyboard_shortcuts.toot": "Nij berjocht skriuwe",
-  "keyboard_shortcuts.translate": "om in berjocht oer te setten",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
   "keyboard_shortcuts.up": "Nei boppe yn list ferpleatse",
   "lightbox.close": "Slute",
@@ -467,32 +444,20 @@
   "link_preview.author": "Troch {name}",
   "link_preview.more_from_author": "Mear fan {name}",
   "link_preview.shares": "{count, plural, one {{counter} berjocht} other {{counter} berjochten}}",
-  "lists.add_member": "Tafoegje",
-  "lists.add_to_list": "Oan list tafoegje",
-  "lists.add_to_lists": "{name} oan listen tafoegje",
-  "lists.create": "Oanmeitsje",
-  "lists.create_a_list_to_organize": "Meitsje in nije list oan om jo starttiidline te organisearjen",
-  "lists.create_list": "List oanmeitsje",
+  "lists.account.add": "Oan list tafoegje",
+  "lists.account.remove": "Ut list fuortsmite",
   "lists.delete": "List fuortsmite",
-  "lists.done": "Klear",
   "lists.edit": "List bewurkje",
-  "lists.exclusive": "Leden op jo Startside ferstopje",
-  "lists.exclusive_hint": "As ien op dizze list stiet, ferstopje dizze persoan dan op jo starttiidline om foar te kommen dat harren berjochten twa kear toand wurde.",
-  "lists.find_users_to_add": "Fyn brûkers om ta te foegjen",
-  "lists.list_members": "Listleden",
-  "lists.list_members_count": "{count, plural, one{# lid} other{# leden}}",
-  "lists.list_name": "Listnamme",
-  "lists.new_list_name": "Nije listnamme",
-  "lists.no_lists_yet": "Noch gjin listen.",
-  "lists.no_members_yet": "Noch gjin leden.",
-  "lists.no_results_found": "Gjin resultaten fûn.",
-  "lists.remove_member": "Fuortsmite",
+  "lists.edit.submit": "Titel wizigje",
+  "lists.exclusive": "Ferstopje dizze berjochten op jo startside",
+  "lists.new.create": "List tafoegje",
+  "lists.new.title_placeholder": "Nije listtitel",
   "lists.replies_policy.followed": "Elke folge brûker",
   "lists.replies_policy.list": "Leden fan de list",
   "lists.replies_policy.none": "Net ien",
-  "lists.save": "Bewarje",
-  "lists.search": "Sykje",
-  "lists.show_replies_to": "Foegje antwurden fan listleden ta oan",
+  "lists.replies_policy.title": "Reaksjes toane oan:",
+  "lists.search": "Sykje nei minsken dy’t jo folgje",
+  "lists.subheading": "Jo listen",
   "load_pending": "{count, plural, one {# nij item} other {# nije items}}",
   "loading_indicator.label": "Lade…",
   "media_gallery.hide": "Ferstopje",
@@ -541,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} hat {target} rapportearre",
   "notification.admin.sign_up": "{name} hat harren registrearre",
   "notification.admin.sign_up.name_and_others": "{name} en {count, plural, one {# oar} other {# oaren}} hawwe harren registrearre",
-  "notification.annual_report.message": "Jo {year} #Wrapstodon stiet klear! Lit de hichtepunten en memorabele mominten fan jo jier sjen op Mastodon!",
-  "notification.annual_report.view": "#Wrapstodon besjen",
   "notification.favourite": "{name} hat jo berjocht as favoryt markearre",
   "notification.favourite.name_and_others_with_link": "{name} en <a>{count, plural, one {# oar} other {# oaren}}</a> hawwe jo berjocht as favoryt markearre",
-  "notification.favourite_pm": "{name} hat jo priveeberjocht as favoryt markearre",
-  "notification.favourite_pm.name_and_others_with_link": "{name} en <a>{count, plural, one {# oar} other {# oaren}}</a> hawwe jo priveeberjocht as favoryt markearre",
   "notification.follow": "{name} folget dy",
   "notification.follow.name_and_others": "{name} en <a>{count, plural, one {# oar persoan} other {# oare persoanen}}</a> folgje jo no",
   "notification.follow_request": "{name} hat dy in folchfersyk stjoerd",
@@ -651,21 +612,44 @@
   "notifications_permission_banner.enable": "Desktopmeldingen ynskeakelje",
   "notifications_permission_banner.how_to_control": "Om meldingen te ûntfangen wannear’t Mastodon net iepen stiet. Jo kinne krekt bepale hokker soarte fan ynteraksjes wol of gjin desktopmeldingen jouwe fia de boppesteande {icon} knop.",
   "notifications_permission_banner.title": "Mis neat",
-  "onboarding.follows.back": "Tebek",
-  "onboarding.follows.done": "Klear",
+  "onboarding.action.back": "Bring my tebek",
+  "onboarding.actions.back": "Bring my tebek",
+  "onboarding.actions.go_to_explore": "De aktuele trends besjen",
+  "onboarding.actions.go_to_home": "Gean nei jo startside",
+  "onboarding.compose.template": "Hallo #Mastodon!",
   "onboarding.follows.empty": "Spitigernôch kinne op dit stuit gjin resultaten toand wurde. Jo kinne probearje te sykjen of te blêdzjen troch de ferkenningsside om minsken te finen dy’t jo folgje kinne, of probearje it letter opnij.",
-  "onboarding.follows.search": "Sykje",
-  "onboarding.follows.title": "Folgje minsken om te begjinnen",
+  "onboarding.follows.lead": "Jo beheare jo eigen startside. Hoe mear minsken jo folgje, hoe aktiver en ynteressanter it wêze sil. Dizze profilen kinne in goed startpunt wêze, jo kinne se letter altyd ûntfolgje!",
+  "onboarding.follows.title": "Populêr op Mastodon",
   "onboarding.profile.discoverable": "Meitsje myn profyl te finen",
   "onboarding.profile.discoverable_hint": "Wannear’t jo akkoard gean mei it te finen wêzen op Mastodon, ferskine jo berjochten yn sykresultaten en kinne se trending wurde, en jo profyl kin oan oare minsken oanrekommandearre wurde wannear’t se fergelykbere ynteressen hawwe.",
   "onboarding.profile.display_name": "Werjeftenamme",
   "onboarding.profile.display_name_hint": "Jo folsleine namme of in aardige bynamme…",
+  "onboarding.profile.lead": "Jo kinne dit letter altyd oanfolje yn de ynstellingen, wêr’t noch mear oanpassingsopsjes beskikber binne.",
   "onboarding.profile.note": "Biografy",
   "onboarding.profile.note_hint": "Jo kinne oare minsken @fermelde of #hashtags brûke…",
   "onboarding.profile.save_and_continue": "Bewarje en trochgean",
   "onboarding.profile.title": "Profyl ynstelle",
   "onboarding.profile.upload_avatar": "Profylfoto oplade",
   "onboarding.profile.upload_header": "Omslachfoto foar profyl oplade",
+  "onboarding.share.lead": "Lit minsken witte hoe’t se jo fine kinne op Mastodon!",
+  "onboarding.share.message": "Ik bin {username} op #Mastodon! Folgje my op {url}",
+  "onboarding.share.next_steps": "Mooglike folgjende stappen:",
+  "onboarding.share.title": "Jo profyl diele",
+  "onboarding.start.lead": "Jo nije Mastodon-account stiet klear. Sa helje jo der it beste út:",
+  "onboarding.start.skip": "Wolle jo daliks trochgean?",
+  "onboarding.start.title": "It is jo slagge!",
+  "onboarding.steps.follow_people.body": "Jo beheare jo eigen nijsstream. Litte wy it folje mei ynteressante minsken.",
+  "onboarding.steps.follow_people.title": "Folgje {count, plural, one {ien persoan} other {# minsken}}",
+  "onboarding.steps.publish_status.body": "Sis hallo tsjin de wrâld.",
+  "onboarding.steps.publish_status.title": "Meitsje jo earste berjocht",
+  "onboarding.steps.setup_profile.body": "Oaren sille earder mei jo yn kontakt komme as jo wat oer josels fertelle.",
+  "onboarding.steps.setup_profile.title": "Pas jo profyl oan",
+  "onboarding.steps.share_profile.body": "Lit jo freonen witte hoe’t jo te finen binne op Mastodon!",
+  "onboarding.steps.share_profile.title": "Jo profyl diele",
+  "onboarding.tips.2fa": "<strong>Wisten jo dit al?</strong> Jo kinne de befeiliging fan jo account ferheegje troch twa-staps-autentikaasje yn te stellen yn jo accountynstellingen. Derfoar is gjin telefoannûmer nedich en it funksjonearret mei elke TOTP-app!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Witte jo?</strong> Mastodon desintralisearre is en guon profilen dy’t jo tsjinkomme, stean op oare servers as dy fan jo. En dochs kinne jo sûnder problemen mei harren kommunisearje! Harren server stiet yn de twadde helte fan harren brûkersnamme!",
+  "onboarding.tips.migration": "<strong>Wisten jo dit al?</strong> Wannear’t jo it gefoel hawwe, dat {domain} yn de takomst net de krekte serverkar foar jo is, kinne jo nei elk oare Mastodon-Server wikselje, sûnder jo folgers te ferliezen. Jo kinne sels jo eigen server hoste!",
+  "onboarding.tips.verification": "<strong>Wisten jo dit al?</strong> Jo kinne jo account ferifiearje, troch op jo website in keppeling te pleatsen nei jo Mastodon-profyl en de website oan jo profyl ta te foegjen. Gjin kosten of dokuminten nedich!",
   "password_confirmation.exceeds_maxlength": "Wachtwurdbefêstiging giet oer de maksimale wachtwurdlingte",
   "password_confirmation.mismatching": "Wachtwurdbefêstiging komt net oerien",
   "picture_in_picture.restore": "Tebeksette",
@@ -681,6 +665,7 @@
   "poll_button.remove_poll": "Enkête fuortsmite",
   "privacy.change": "Sichtberheid fan berjocht oanpasse",
   "privacy.direct.long": "Elkenien dy’ yn it berjocht fermeld wurdt",
+  "privacy.direct.short": "Bepaalde minsken",
   "privacy.private.long": "Allinnich jo folgers",
   "privacy.private.short": "Folgers",
   "privacy.public.long": "Elkenien op Mastodon en dêrbûten",
@@ -692,8 +677,8 @@
   "privacy_policy.title": "Privacybelied",
   "recommended": "Oanrekommandearre",
   "refresh": "Ferfarskje",
-  "regeneration_indicator.please_stand_by": "In amerijke.",
-  "regeneration_indicator.preparing_your_home_feed": "Tarieden fan jo starttiidline…",
+  "regeneration_indicator.label": "Lade…",
+  "regeneration_indicator.sublabel": "Jo starttiidline wurdt oanmakke!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# dei} other {# dagen}} lyn",
   "relative_time.full.hours": "{number, plural, one {# oere} other {# oeren}} lyn",
@@ -777,11 +762,10 @@
   "search_results.accounts": "Profilen",
   "search_results.all": "Alles",
   "search_results.hashtags": "Hashtags",
-  "search_results.no_results": "Gjin resultaten.",
-  "search_results.no_search_yet": "Probearje te sykjen nei berjochten, profilen of hashtags.",
+  "search_results.nothing_found": "Dizze syktermen leverje gjin resultaat op",
   "search_results.see_all": "Alles besjen",
   "search_results.statuses": "Berjochten",
-  "search_results.title": "Sykje nei ‘{q}’",
+  "search_results.title": "Nei {q} sykje",
   "server_banner.about_active_users": "Oantal brûkers yn de ôfrûne 30 dagen (MAU)",
   "server_banner.active_users": "warbere brûkers",
   "server_banner.administered_by": "Beheard troch:",
@@ -833,7 +817,6 @@
   "status.reblogs.empty": "Net ien hat dit berjocht noch boost. Wannear’t ien dit docht, falt dat hjir te sjen.",
   "status.redraft": "Fuortsmite en opnij opstelle",
   "status.remove_bookmark": "Blêdwizer fuortsmite",
-  "status.remove_favourite": "Ut favoriten fuortsmite",
   "status.replied_in_thread": "Antwurde yn petear",
   "status.replied_to": "Antwurde op {name}",
   "status.reply": "Beäntwurdzje",
@@ -855,7 +838,6 @@
   "subscribed_languages.target": "Toande talen foar {target} wizigje",
   "tabs_bar.home": "Startside",
   "tabs_bar.notifications": "Meldingen",
-  "terms_of_service.title": "Gebrûksbetingsten",
   "time_remaining.days": "{number, plural, one {# dei} other {# dagen}} te gean",
   "time_remaining.hours": "{number, plural, one {# oere} other {# oeren}} te gean",
   "time_remaining.minutes": "{number, plural, one {# minút} other {# minuten}} te gean",
@@ -871,12 +853,26 @@
   "upload_button.label": "Ofbyldingen, in fideo- of in lûdsbestân tafoegje",
   "upload_error.limit": "Oer de oplaadlimyt fan bestân.",
   "upload_error.poll": "It opladen fan bestannen is yn enkêten net tastien.",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
   "upload_form.drag_and_drop.instructions": "Druk op spaasje of Enter om in mediabylage op te pakken. Bruk de pylktoetsen om de bylage yn in bepaalde rjochting te ferpleatsen. Druk opnij op de spaasjebalke of Enter om de mediabylage op de nije posysje te pleatsen, of druk op Esc om te annulearjen.",
   "upload_form.drag_and_drop.on_drag_cancel": "Slepen is annulearre. Mediabylage {item} is net ferpleatst.",
   "upload_form.drag_and_drop.on_drag_end": "Mediabylage {item} is net ferpleatst.",
   "upload_form.drag_and_drop.on_drag_over": "Mediabylage {item} is ferpleatst.",
   "upload_form.drag_and_drop.on_drag_start": "Mediabylage {item} is oppakt.",
   "upload_form.edit": "Bewurkje",
+  "upload_form.thumbnail": "Miniatuerôfbylding wizigje",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
+  "upload_modal.analyzing_picture": "Ofbylding analysearje…",
+  "upload_modal.apply": "Tapasse",
+  "upload_modal.applying": "Oan it tapassen…",
+  "upload_modal.choose_image": "Kies in ôfbylding",
+  "upload_modal.description_placeholder": "Heit syn wize foks ljept tûk oar de loaie hûn",
+  "upload_modal.detect_text": "Tekst yn in ôfbylding detektearje",
+  "upload_modal.edit_media": "Media bewurkje",
+  "upload_modal.hint": "Klik of sleep de sirkel yn de foarfertoaning nei in sintraal fokuspunt dat op elke thumbnail sichtber bliuwe moat.",
+  "upload_modal.preparing_ocr": "OCR tariede…",
+  "upload_modal.preview_label": "Foarfertoaning ({ratio})",
   "upload_progress.label": "Uploading…",
   "upload_progress.processing": "Dwaande…",
   "username.taken": "Dy brûkersnamme is al yn gebrûk. Probearje in oare",
@@ -886,6 +882,8 @@
   "video.expand": "Fideo grutter meitsje",
   "video.fullscreen": "Folslein skerm",
   "video.hide": "Fideo ferstopje",
+  "video.mute": "Lûd dôvje",
   "video.pause": "Skoft",
-  "video.play": "Ofspylje"
+  "video.play": "Ofspylje",
+  "video.unmute": "Lûd oan"
 }
diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json
index 5935b39e2d..db1ccd3a3c 100644
--- a/app/javascript/mastodon/locales/ga.json
+++ b/app/javascript/mastodon/locales/ga.json
@@ -29,6 +29,7 @@
   "account.endorse": "Cuir ar an phróifíl mar ghné",
   "account.featured_tags.last_status_at": "Postáil is déanaí ar {date}",
   "account.featured_tags.last_status_never": "Gan aon phoist",
+  "account.featured_tags.title": "Haischlib faoi thrácht {name}",
   "account.follow": "Lean",
   "account.follow_back": "Leanúint ar ais",
   "account.followers": "Leantóirí",
@@ -64,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} poist}}",
   "account.unblock": "Bain bac de @{name}",
   "account.unblock_domain": "Bain bac den ainm fearainn {domain}",
-  "account.unblock_domain_short": "Díbhlocáil",
   "account.unblock_short": "Díbhlocáil",
   "account.unendorse": "Ná chuir ar an phróifíl mar ghné",
   "account.unfollow": "Ná lean a thuilleadh",
@@ -86,33 +86,7 @@
   "alert.unexpected.message": "Tharla earráid gan choinne.",
   "alert.unexpected.title": "Hiúps!",
   "alt_text_badge.title": "Téacs alt",
-  "alt_text_modal.add_alt_text": "Cuir téacs alt leis",
-  "alt_text_modal.add_text_from_image": "Cuir téacs ón íomhá leis",
-  "alt_text_modal.cancel": "Cealaigh",
-  "alt_text_modal.change_thumbnail": "Athraigh mionsamhail",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Déan cur síos air seo do dhaoine le lagú éisteachta…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Déan cur síos air seo do dhaoine a bhfuil lagú amhairc orthu…",
-  "alt_text_modal.done": "Déanta",
   "announcement.announcement": "Fógra",
-  "annual_report.summary.archetype.booster": "An sealgair fionnuar",
-  "annual_report.summary.archetype.lurker": "An lurker",
-  "annual_report.summary.archetype.oracle": "An oracal",
-  "annual_report.summary.archetype.pollster": "An pollaire",
-  "annual_report.summary.archetype.replier": "An féileacán sóisialta",
-  "annual_report.summary.followers.followers": "leanúna",
-  "annual_report.summary.followers.total": "{count} san iomlán",
-  "annual_report.summary.here_it_is": "Seo do {year} faoi athbhreithniú:",
-  "annual_report.summary.highlighted_post.by_favourites": "post is fearr leat",
-  "annual_report.summary.highlighted_post.by_reblogs": "post is treisithe",
-  "annual_report.summary.highlighted_post.by_replies": "post leis an líon is mó freagraí",
-  "annual_report.summary.highlighted_post.possessive": "{name}'s",
-  "annual_report.summary.most_used_app.most_used_app": "aip is mó a úsáidtear",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag is mó a úsáidtear",
-  "annual_report.summary.most_used_hashtag.none": "Dada",
-  "annual_report.summary.new_posts.new_posts": "postanna nua",
-  "annual_report.summary.percentile.text": "<topLabel>Cuireann sé sin i mbarr</topLabel><percentage></percentage><bottomLabel> úsáideoirí {domain}.</bottomLabel> thú",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Ní inseoidh muid do Bernie.",
-  "annual_report.summary.thanks": "Go raibh maith agat as a bheith mar chuid de Mastodon!",
   "attachments_list.unprocessed": "(neamhphróiseáilte)",
   "audio.hide": "Cuir fuaim i bhfolach",
   "block_modal.remote_users_caveat": "Iarrfaimid ar an bhfreastalaí {domain} meas a bheith agat ar do chinneadh. Mar sin féin, ní ráthaítear comhlíonadh toisc go bhféadfadh roinnt freastalaithe bloic a láimhseáil ar bhealach difriúil. Seans go mbeidh postálacha poiblí fós le feiceáil ag úsáideoirí nach bhfuil logáilte isteach.",
@@ -136,7 +110,7 @@
   "bundle_column_error.routing.body": "Ní féidir teacht ar an leathanach a iarradh. An bhfuil tú cinnte go bhfuil an URL sa seoladh i gceart?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Dún",
-  "bundle_modal_error.message": "Tharla earráid agus an scáileán seo á lódáil.",
+  "bundle_modal_error.message": "Chuaigh rud éigin mícheart nuair a bhí an chomhpháirt seo ag lódáil.",
   "bundle_modal_error.retry": "Bain triail as arís",
   "closed_registrations.other_server_instructions": "Mar rud díláraithe Mastodon, is féidir leat cuntas a chruthú ar seirbheálaí eile ach fós idirghníomhaigh leis an ceann seo.",
   "closed_registrations_modal.description": "Ní féidir cuntas a chruthú ar {domain} faoi láthair, ach cuimhnigh nach gá go mbeadh cuntas agat go sonrach ar {domain} chun Mastodon a úsáid.",
@@ -147,16 +121,13 @@
   "column.blocks": "Cuntais choiscthe",
   "column.bookmarks": "Leabharmharcanna",
   "column.community": "Amlíne áitiúil",
-  "column.create_list": "Cruthaigh liosta",
   "column.direct": "Luann príobháideach",
   "column.directory": "Brabhsáil próifílí",
   "column.domain_blocks": "Fearainn bhactha",
-  "column.edit_list": "Cuir liosta in eagar",
   "column.favourites": "Ceanáin",
   "column.firehose": "Fothaí beo",
   "column.follow_requests": "Iarratais leanúnaí",
   "column.home": "Baile",
-  "column.list_members": "Bainistigh baill liosta",
   "column.lists": "Liostaí",
   "column.mutes": "Úsáideoirí balbhaithe",
   "column.notifications": "Fógraí",
@@ -169,7 +140,6 @@
   "column_header.pin": "Pionna",
   "column_header.show_settings": "Taispeáin socruithe",
   "column_header.unpin": "Bain pionna",
-  "column_search.cancel": "Cealaigh",
   "column_subheading.settings": "Socruithe",
   "community.column_settings.local_only": "Áitiúil amháin",
   "community.column_settings.media_only": "Meáin Amháin",
@@ -188,7 +158,7 @@
   "compose_form.poll.duration": "Achar suirbhéanna",
   "compose_form.poll.multiple": "Ilrogha",
   "compose_form.poll.option_placeholder": "Rogha {number}",
-  "compose_form.poll.single": "Rogha aonair",
+  "compose_form.poll.single": "Roghnaigh ceann amháin",
   "compose_form.poll.switch_to_multiple": "Athraigh suirbhé chun cead a thabhairt do ilrogha",
   "compose_form.poll.switch_to_single": "Athraigh suirbhé chun cead a thabhairt do rogha amháin",
   "compose_form.poll.type": "Stíl",
@@ -212,16 +182,9 @@
   "confirmations.edit.confirm": "Eagar",
   "confirmations.edit.message": "Má dhéanann tú eagarthóireacht anois, déanfar an teachtaireacht atá á cumadh agat faoi láthair a fhorscríobh. An bhfuil tú cinnte gur mhaith leat leanúint ar aghaidh?",
   "confirmations.edit.title": "Forscríobh postáil?",
-  "confirmations.follow_to_list.confirm": "Lean agus cuir leis an liosta",
-  "confirmations.follow_to_list.message": "Ní mór duit {name} a leanúint chun iad a chur le liosta.",
-  "confirmations.follow_to_list.title": "Lean an t-úsáideoir?",
   "confirmations.logout.confirm": "Logáil amach",
   "confirmations.logout.message": "An bhfuil tú cinnte gur mhaith leat logáil amach?",
   "confirmations.logout.title": "Logáil Amach?",
-  "confirmations.missing_alt_text.confirm": "Cuir téacs alt leis",
-  "confirmations.missing_alt_text.message": "Tá meáin gan alt téacs i do phostáil. Má chuirtear tuairiscí leis, cabhraíonn sé seo leat d’inneachar a rochtain do níos mó daoine.",
-  "confirmations.missing_alt_text.secondary": "Post ar aon nós",
-  "confirmations.missing_alt_text.title": "Cuir téacs alt leis?",
   "confirmations.mute.confirm": "Balbhaigh",
   "confirmations.redraft.confirm": "Scrios ⁊ athdhréachtaigh",
   "confirmations.redraft.message": "An bhfuil tú cinnte gur mhaith leat an postáil seo a scriosadh agus é a athdhréachtú? Caillfear ceanáin agus treisithe, agus dílleachtaí freagraí ar an mbunphostála.",
@@ -250,10 +213,10 @@
   "disabled_account_banner.text": "Tá do chuntas {disabledAccount} díchumasaithe faoi láthair.",
   "dismissable_banner.community_timeline": "Seo iad na postála is déanaí ó dhaoine le cuntais ar {domain}.",
   "dismissable_banner.dismiss": "Diúltaigh",
-  "dismissable_banner.explore_links": "Is iad na scéalta nuachta seo is mó atá á roinnt ar an lá inniu. Rangaítear scéalta nuachta níos nuaí arna bpostáil ag daoine éagsúla níos airde.",
-  "dismissable_banner.explore_statuses": "Tá tarraingt ag teacht ar na poist seo ó gach cearn den fhealsúnacht inniu. Rangaítear poist níos nuaí le níos mó teanntáin agus ceanáin níos airde.",
-  "dismissable_banner.explore_tags": "Tá tarraingt ag na hashtags seo ar an bhfeadóg mhór inniu. Tá na hashtags a úsáideann níos mó daoine difriúla rangaithe níos airde.",
-  "dismissable_banner.public_timeline": "Seo iad na postálacha poiblí is déanaí ó dhaoine ar an bhfealsúnacht a leanann daoine ar {domain}.",
+  "dismissable_banner.explore_links": "Tá na scéalta nuachta seo á phlé anseo agus ar fhreastalaithe eile ar an líonra díláraithe faoi láthair.",
+  "dismissable_banner.explore_statuses": "Is postálacha iad seo ó ar fud an ghréasáin shóisialta atá ag éirí níos tarraingtí inniu. Rangaítear poist níos nuaí le níos mó teanntáin agus ceanáin níos airde.",
+  "dismissable_banner.explore_tags": "Is hashtags iad seo atá ag tarraingt ar an ngréasán sóisialta inniu. Tá na hashtags a úsáideann níos mó daoine difriúla rangaithe níos airde.",
+  "dismissable_banner.public_timeline": "Seo iad na postálacha poiblí is déanaí ó dhaoine ar an ngréasán sóisialta a leanann daoine ar {domain}.",
   "domain_block_modal.block": "Bloc freastalaí",
   "domain_block_modal.block_account_instead": "Cuir bac ar @{name} ina ionad sin",
   "domain_block_modal.they_can_interact_with_old_posts": "Is féidir le daoine ón bhfreastalaí seo idirghníomhú le do sheanphoist.",
@@ -310,6 +273,7 @@
   "empty_column.hashtag": "Níl rud ar bith faoin haischlib seo go fóill.",
   "empty_column.home": "Tá d'amlíne baile folamh! B'fhiú duit cúpla duine eile a leanúint lena líonadh! {suggestions}",
   "empty_column.list": "Níl aon rud ar an liosta seo fós. Nuair a fhoilseoidh baill an liosta seo postálacha nua, beidh siad le feiceáil anseo.",
+  "empty_column.lists": "Níl aon liostaí fós agat. Nuair a chruthaíonn tú ceann, feicfear anseo é.",
   "empty_column.mutes": "Níl aon úsáideoir balbhaithe agat fós.",
   "empty_column.notification_requests": "Gach soiléir! Níl aon rud anseo. Nuair a gheobhaidh tú fógraí nua, beidh siad le feiceáil anseo de réir do shocruithe.",
   "empty_column.notifications": "Níl aon fógraí agat fós. Nuair a dhéanann daoine eile idirghníomhú leat, feicfear anseo é.",
@@ -320,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Déan iarracht iad a dhíchumasú agus an leathanach a athnuachan. Mura gcabhraíonn sé sin, seans go mbeidh tú fós in ann Mastodon a úsáid trí bhrabhsálaí nó aip dhúchais eile.",
   "errors.unexpected_crash.copy_stacktrace": "Cóipeáil rian cruachta go dtí an ghearrthaisce",
   "errors.unexpected_crash.report_issue": "Tuairiscigh deacracht",
+  "explore.search_results": "Torthaí cuardaigh",
   "explore.suggested_follows": "Daoine",
   "explore.title": "Féach thart",
   "explore.trending_links": "Nuacht",
@@ -369,14 +334,13 @@
   "footer.about": "Maidir le",
   "footer.directory": "Eolaire próifílí",
   "footer.get_app": "Faigh an aip",
+  "footer.invite": "Tabhair cuireadh do dhaoine",
   "footer.keyboard_shortcuts": "Aicearraí méarchláir",
   "footer.privacy_policy": "Polasaí príobháideachais",
   "footer.source_code": "Féach ar an gcód foinseach",
   "footer.status": "Stádas",
-  "footer.terms_of_service": "Téarmaí seirbhíse",
   "generic.saved": "Sábháilte",
   "getting_started.heading": "Ag tosú amach",
-  "hashtag.admin_moderation": "Oscail comhéadan modhnóireachta le haghaidh #{name}",
   "hashtag.column_header.tag_mode.all": "agus {additional}",
   "hashtag.column_header.tag_mode.any": "nó {additional}",
   "hashtag.column_header.tag_mode.none": "gan {additional}",
@@ -418,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "An dtugann tú aird ar fhógraí ó dhaoine nach leanann tú?",
   "ignore_notifications_modal.not_following_title": "An ndéanann tú neamhaird de fhógraí ó dhaoine nach leanann tú?",
   "ignore_notifications_modal.private_mentions_title": "An dtugann tú aird ar fhógraí ó Luaintí Príobháideacha gan iarraidh?",
-  "info_button.label": "Cabhrú",
-  "info_button.what_is_alt_text": "<h1>Cad is téacs altach ann?</h1> <p>Soláthraíonn téacs Alt cur síos ar íomhánna do dhaoine le lagú radhairc, naisc íseal-bandaleithead, nó daoine atá ag lorg comhthéacs breise.</p> <p>Is féidir leat inrochtaineacht agus tuiscint a fheabhsú do chách trí théacs alt soiléir, gonta, oibiachtúil a scríobh.</p> <ul> <li>Glac gnéithe tábhachtacha</li> <li>Déan achoimre ar théacs in íomhánna</li> <li>Úsáid struchtúr abairtí rialta</li> li> <li>Seachain faisnéis iomarcach</li> <li>Fócas ar threochtaí agus ar phríomhthorthaí i bhfíseanna casta (amhail léaráidí nó léarscáileanna)</li> </ul>",
-  "interaction_modal.action.favourite": "Chun leanúint ar aghaidh, ní mór duit an ceann is fearr leat ó do chuntas.",
-  "interaction_modal.action.follow": "Chun leanúint ar aghaidh, ní mór duit leanúint ó do chuntas.",
-  "interaction_modal.action.reblog": "Chun leanúint ar aghaidh, ní mór duit athbhlagáil ó do chuntas.",
-  "interaction_modal.action.reply": "Chun leanúint ar aghaidh, ní mór duit freagra a thabhairt ó do chuntas.",
-  "interaction_modal.action.vote": "Chun leanúint ar aghaidh, ní mór duit vótáil ó do chuntas.",
-  "interaction_modal.go": "Téigh",
-  "interaction_modal.no_account_yet": "Níl cuntas agat fós?",
+  "interaction_modal.description.favourite": "Le cuntas ar Mastodon, is fearr leat an postáil seo chun a chur in iúl don údar go bhfuil meas agat air agus é a shábháil ar feadh níos déanaí.",
+  "interaction_modal.description.follow": "Le cuntas ar Mastodon, is féidir leat {name} a leanúint chun a gcuid postálacha a fháil i do fhotha baile.",
+  "interaction_modal.description.reblog": "Le cuntas ar Mastodon, is féidir leat an postáil seo a threisiú chun é a roinnt le do leantóirí féin.",
+  "interaction_modal.description.reply": "Le cuntas ar Mastodon, is féidir leat freagra a thabhairt ar an bpostáil seo.",
+  "interaction_modal.login.action": "Thabhairt dom abhaile",
+  "interaction_modal.login.prompt": "Fearann ​​do fhreastalaí baile, e.g. mastodon.sóisialta",
+  "interaction_modal.no_account_yet": "Ní ar Mastodon?",
   "interaction_modal.on_another_server": "Ar freastalaí eile",
   "interaction_modal.on_this_server": "Ar an freastalaí seo",
+  "interaction_modal.sign_in": "Níl tú logáilte isteach ar an bhfreastalaí seo. Cá bhfuil do chuntas á óstáil?",
+  "interaction_modal.sign_in_hint": "Leid: Sin é an suíomh Gréasáin inar chláraigh tú. Mura cuimhin leat, lorg an ríomhphost fáilte i do bhosca isteach. Is féidir leat d'ainm úsáideora iomlán a chur isteach freisin! (m.sh. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "An postáil {name} is fearr leat",
   "interaction_modal.title.follow": "Lean {name}",
   "interaction_modal.title.reblog": "Mol postáil de chuid {name}",
   "interaction_modal.title.reply": "Freagair postáil {name}",
-  "interaction_modal.title.vote": "Vótáil i vótaíocht {name}",
-  "interaction_modal.username_prompt": "M.sh. {example}",
   "intervals.full.days": "{number, plural, one {# lá} other {# lá}}",
   "intervals.full.hours": "{number, plural, one {# uair} other {# uair}}",
   "intervals.full.minutes": "{number, plural, one {# nóiméad} other {# nóiméad}}",
@@ -470,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Taispeáin/folaigh an téacs taobh thiar de CW",
   "keyboard_shortcuts.toggle_sensitivity": "Taispeáin / cuir i bhfolach meáin",
   "keyboard_shortcuts.toot": "Cuir tús le postáil nua",
-  "keyboard_shortcuts.translate": "post a aistriú",
   "keyboard_shortcuts.unfocus": "Unfocus cum textarea/search",
   "keyboard_shortcuts.up": "Bog suas ar an liosta",
   "lightbox.close": "Dún",
@@ -483,32 +444,20 @@
   "link_preview.author": "Le {name}",
   "link_preview.more_from_author": "Tuilleadh ó {name}",
   "link_preview.shares": "{count, plural, one {{counter} post} other {{counter} poist}}",
-  "lists.add_member": "Cuir",
-  "lists.add_to_list": "Cuir leis an liosta",
-  "lists.add_to_lists": "Cuir {name} le liostaí",
-  "lists.create": "Cruthaigh",
-  "lists.create_a_list_to_organize": "Cruthaigh liosta nua chun d'fhotha Baile a eagrú",
-  "lists.create_list": "Cruthaigh liosta",
+  "lists.account.add": "Cuir leis an liosta",
+  "lists.account.remove": "Scrios as an liosta",
   "lists.delete": "Scrios liosta",
-  "lists.done": "Déanta",
   "lists.edit": "Cuir an liosta in eagar",
-  "lists.exclusive": "Folaigh baill sa Bhaile",
-  "lists.exclusive_hint": "Má tá duine ar an liosta seo, cuir i bhfolach iad i do fhotha Baile ionas nach bhfeicfidh tú a bpoist faoi dhó.",
-  "lists.find_users_to_add": "Aimsigh úsáideoirí le cur leis",
-  "lists.list_members": "Liostaigh baill",
-  "lists.list_members_count": "{count, plural, one {# ball} two {# bhall} few {# baill} many {# baill} other {# baill}}",
-  "lists.list_name": "Ainm an liosta",
-  "lists.new_list_name": "Ainm liosta nua",
-  "lists.no_lists_yet": "Níl aon liostaí fós.",
-  "lists.no_members_yet": "Níl aon bhall fós.",
-  "lists.no_results_found": "Níor aimsíodh aon torthaí.",
-  "lists.remove_member": "Bain",
+  "lists.edit.submit": "Athraigh teideal",
+  "lists.exclusive": "Folaigh na poist seo ón mbaile",
+  "lists.new.create": "Cruthaigh liosta",
+  "lists.new.title_placeholder": "Teideal liosta nua",
   "lists.replies_policy.followed": "Úsáideoir ar bith atá á leanúint",
   "lists.replies_policy.list": "Baill an liosta",
   "lists.replies_policy.none": "Duine ar bith",
-  "lists.save": "Sábháil",
-  "lists.search": "Cuardach",
-  "lists.show_replies_to": "Cuir san áireamh freagraí ó bhaill an liosta go",
+  "lists.replies_policy.title": "Taispeáin freagraí:",
+  "lists.search": "Cuardaigh i measc daoine atá á leanúint agat",
+  "lists.subheading": "Do liostaí",
   "load_pending": "{count, plural, one {# mír nua} two {# mír nua} few {# mír nua} many {# mír nua} other {# mír nua}}",
   "loading_indicator.label": "Á lódáil…",
   "media_gallery.hide": "Folaigh",
@@ -557,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} tuairiscithe {target}",
   "notification.admin.sign_up": "Chláraigh {name}",
   "notification.admin.sign_up.name_and_others": "{name} agus {count, plural, one {# duine eile} two {# daoine eile} few {# daoine eile} many {# daoine eile} other {# daoine eile}} a chláraigh",
-  "notification.annual_report.message": "Tá do {year} #Wrapstodon ag fanacht! Nocht buaicphointí na bliana agus chuimhneacháin i gcuimhne ar Mastodon!",
-  "notification.annual_report.view": "Amharc #Wrapstodon",
   "notification.favourite": "Is fearr le {name} do phostáil",
   "notification.favourite.name_and_others_with_link": "{name} agus <a>{count, plural, one {# duine eile} other {# daoine eile}}</a> thaitin le do phost",
-  "notification.favourite_pm": "B'fhearr le {name} do lua príobháideach",
-  "notification.favourite_pm.name_and_others_with_link": "{name} agus <a>{count, plural, one {# eile} two {# eile} few {# eile} many {# eile} other {# eile}}</a> atá roghnaithe do do luaidh phríobháideach",
   "notification.follow": "Lean {name} thú",
   "notification.follow.name_and_others": "{name} agus <a>{count, plural, one {# other} two {# eile} few {# eile} many {# eile} other {# others}}</a> lean tú",
   "notification.follow_request": "D'iarr {name} ort do chuntas a leanúint",
@@ -667,21 +612,44 @@
   "notifications_permission_banner.enable": "Ceadaigh fógraí ar an deasc",
   "notifications_permission_banner.how_to_control": "Chun fógraí a fháil nuair nach bhfuil Mastodon oscailte, cumasaigh fógraí deisce. Is féidir leat a rialú go beacht cé na cineálacha idirghníomhaíochtaí a ghineann fógraí deisce tríd an gcnaipe {icon} thuas nuair a bhíonn siad cumasaithe.",
   "notifications_permission_banner.title": "Ná caill aon rud go deo",
-  "onboarding.follows.back": "Ar ais",
-  "onboarding.follows.done": "Déanta",
+  "onboarding.action.back": "Tóg ar ais mé",
+  "onboarding.actions.back": "Tóg ar ais mé",
+  "onboarding.actions.go_to_explore": "Tóg mé chun trending",
+  "onboarding.actions.go_to_home": "Tóg go dtí mo bheathú baile mé",
+  "onboarding.compose.template": "Dia duit #Mastodon!",
   "onboarding.follows.empty": "Ar an drochuair, ní féidir aon torthaí a thaispeáint faoi láthair. Is féidir leat triail a bhaint as cuardach nó brabhsáil ar an leathanach taiscéalaíochta chun teacht ar dhaoine le leanúint, nó bain triail eile as níos déanaí.",
-  "onboarding.follows.search": "Cuardach",
-  "onboarding.follows.title": "Lean daoine le tosú",
+  "onboarding.follows.lead": "Is é do bheathú baile an príomhbhealach chun taithí a fháil ar Mastodon. Dá mhéad daoine a leanann tú, is ea is gníomhaí agus is suimiúla a bheidh sé. Chun tú a chur ar bun, seo roinnt moltaí:",
+  "onboarding.follows.title": "Cuir do chuid fotha baile in oiriúint duit féin",
   "onboarding.profile.discoverable": "Déan mo phróifíl a fháil amach",
   "onboarding.profile.discoverable_hint": "Nuair a roghnaíonn tú infhionnachtana ar Mastodon, d’fhéadfadh do phoist a bheith le feiceáil i dtorthaí cuardaigh agus treochtaí, agus d’fhéadfaí do phróifíl a mholadh do dhaoine a bhfuil na leasanna céanna acu leat.",
   "onboarding.profile.display_name": "Ainm taispeána",
   "onboarding.profile.display_name_hint": "D’ainm iomlán nó d’ainm spraíúil…",
+  "onboarding.profile.lead": "Is féidir leat é seo a chomhlánú i gcónaí níos déanaí sna socruithe, áit a bhfuil níos mó roghanna saincheaptha ar fáil.",
   "onboarding.profile.note": "Bith",
   "onboarding.profile.note_hint": "Is féidir leat @ daoine eile a lua nó #hashtags…",
   "onboarding.profile.save_and_continue": "Sábháil agus lean ar aghaidh",
   "onboarding.profile.title": "Socrú próifíle",
   "onboarding.profile.upload_avatar": "Íosluchtaigh pictiúr próifíl",
   "onboarding.profile.upload_header": "Íoslódáil an ceanntásca próifíl",
+  "onboarding.share.lead": "Cuir in iúl do dhaoine conas is féidir leo tú a aimsiú ar Mastodon!",
+  "onboarding.share.message": "Is {username} mé ar #Mastodon! Tar lean mé ag {url}",
+  "onboarding.share.next_steps": "Na chéad chéimeanna eile is féidir:",
+  "onboarding.share.title": "Roinn do phróifíl",
+  "onboarding.start.lead": "Tá tú mar chuid de Mastodon anois, ardán meán sóisialta díláraithe uathúil ina ndéanann tú - ní algartam - do thaithí féin a choimeád. Cuirimis tús leat ar an teorainn shóisialta nua seo:",
+  "onboarding.start.skip": "Nach bhfuil cabhair uait le tosú?",
+  "onboarding.start.title": "Tá sé déanta agat!",
+  "onboarding.steps.follow_people.body": "Is éard atá i gceist le daoine suimiúla a leanúint ná Mastodon.",
+  "onboarding.steps.follow_people.title": "Cuir do chuid fotha baile in oiriúint duit féin",
+  "onboarding.steps.publish_status.body": "Abair heileo leis an domhan le téacs, grianghraif, físeáin nó pobalbhreith {emoji}",
+  "onboarding.steps.publish_status.title": "Déan do chéad phostáil",
+  "onboarding.steps.setup_profile.body": "Cuir le d'idirghníomhaíochtaí trí phróifíl chuimsitheach a bheith agat.",
+  "onboarding.steps.setup_profile.title": "Déan do phróifíl a phearsantú",
+  "onboarding.steps.share_profile.body": "Cuir in iúl do do chairde conas tú a aimsiú ar Mastodon",
+  "onboarding.steps.share_profile.title": "Roinn do phróifíl Mastodon",
+  "onboarding.tips.2fa": "<strong>An raibh a fhios agat?</strong> Is féidir leat do chuntas a dhéanamh slán trí fhíordheimhniú dhá fhachtóir a shocrú i socruithe do chuntais. Oibríonn sé le haon aip TOTP de do rogha féin, níl aon uimhir theileafóin riachtanach!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>An raibh a fhios agat?</strong> Ós rud é go bhfuil Mastodon díláraithe, déanfar roinnt próifílí a dtagann tú trasna orthu a óstáil ar fhreastalaithe seachas do fhreastalaithe. Agus fós is féidir leat idirghníomhú leo gan uaim! Tá an freastalaí acu sa dara leath dá n-ainm úsáideora!",
+  "onboarding.tips.migration": "<strong>An raibh a fhios agat?</strong> Más dóigh leat nach rogha freastalaí iontach é {domain} amach anseo, is féidir leat bogadh go freastalaí Mastodon eile gan do leantóirí a chailliúint. Is féidir leat do fhreastalaí féin a óstáil fiú!",
+  "onboarding.tips.verification": "<strong>An raibh a fhios agat?</strong> Is féidir leat do chuntas a fhíorú trí nasc a chur le do phróifíl Mastodon ar do shuíomh Gréasáin féin agus an suíomh Gréasáin a chur le do phróifíl. Níl aon táillí nó doiciméid riachtanach!",
   "password_confirmation.exceeds_maxlength": "Sáraíonn dearbhú pasfhocail uasfhad an phasfhocail",
   "password_confirmation.mismatching": "Ní hionann dearbhú pasfhocail",
   "picture_in_picture.restore": "Cuir é ar ais",
@@ -697,7 +665,7 @@
   "poll_button.remove_poll": "Bain suirbhé",
   "privacy.change": "Athraigh príobháideacht postála",
   "privacy.direct.long": "Luaigh gach duine sa phost",
-  "privacy.direct.short": "Tagairt phríobháideach",
+  "privacy.direct.short": "Daoine ar leith",
   "privacy.private.long": "Do leanúna amháin",
   "privacy.private.short": "Leantóirí",
   "privacy.public.long": "Duine ar bith ar agus amach Mastodon",
@@ -709,8 +677,8 @@
   "privacy_policy.title": "Polasaí príobháideachais",
   "recommended": "Molta",
   "refresh": "Athnuaigh",
-  "regeneration_indicator.please_stand_by": "Fan i do sheasamh, le do thoil.",
-  "regeneration_indicator.preparing_your_home_feed": "Ag ullmhú do bheatha baile…",
+  "regeneration_indicator.label": "Ag lódáil…",
+  "regeneration_indicator.sublabel": "Tá do bheathú baile á ullmhú!",
   "relative_time.days": "{number}l",
   "relative_time.full.days": "{number, plural, one {# lá} other {# lá}} ó shin",
   "relative_time.full.hours": "{number, plural, one {# uair} other {# uair}} ó shin",
@@ -794,11 +762,10 @@
   "search_results.accounts": "Próifílí",
   "search_results.all": "Gach",
   "search_results.hashtags": "Haischlib",
-  "search_results.no_results": "Gan torthaí.",
-  "search_results.no_search_yet": "Bain triail as postálacha, próifílí nó hashtags a chuardach.",
+  "search_results.nothing_found": "Níorbh fhéidir aon rud a aimsiú do na téarmaí cuardaigh seo",
   "search_results.see_all": "Gach rud a fheicáil",
   "search_results.statuses": "Postálacha",
-  "search_results.title": "Cuardaigh \"{q}\"",
+  "search_results.title": "Cuardaigh ar thóir {q}",
   "server_banner.about_active_users": "Daoine a úsáideann an freastalaí seo le 30 lá anuas (Úsáideoirí Gníomhacha Míosúla)",
   "server_banner.active_users": "úsáideoirí gníomhacha",
   "server_banner.administered_by": "Arna riar ag:",
@@ -850,7 +817,6 @@
   "status.reblogs.empty": "Níor mhol éinne an phostáil seo fós. Nuair a mholfaidh duine éigin í, taispeánfar anseo é sin.",
   "status.redraft": "Scrios ⁊ athdhréachtaigh",
   "status.remove_bookmark": "Bain leabharmharc",
-  "status.remove_favourite": "Bain ó cheanáin",
   "status.replied_in_thread": "D'fhreagair sa snáithe",
   "status.replied_to": "D'fhreagair {name}",
   "status.reply": "Freagair",
@@ -872,9 +838,6 @@
   "subscribed_languages.target": "Athraigh teangacha suibscríofa le haghaidh {target}",
   "tabs_bar.home": "Baile",
   "tabs_bar.notifications": "Fógraí",
-  "terms_of_service.effective_as_of": "I bhfeidhm ó {date}",
-  "terms_of_service.title": "Téarmaí Seirbhíse",
-  "terms_of_service.upcoming_changes_on": "Athruithe atá le teacht ar {date}",
   "time_remaining.days": "{number, plural, one {# lá} other {# lá}} fágtha",
   "time_remaining.hours": "{number, plural, one {# uair} other {# uair}} fágtha",
   "time_remaining.minutes": "{number, plural, one {# nóiméad} other {# nóiméad}} fágtha",
@@ -890,12 +853,26 @@
   "upload_button.label": "Cuir íomhánna, físeán nó comhad fuaime leis",
   "upload_error.limit": "Sáraíodh an teorainn uaslódála comhaid.",
   "upload_error.poll": "Ní cheadaítear uaslódáil comhad le pobalbhreith.",
+  "upload_form.audio_description": "Déan cur síos ar dhaoine bodhra nó lagéisteachta",
+  "upload_form.description": "Describe for the visually impaired",
   "upload_form.drag_and_drop.instructions": "Chun ceangaltán meán a phiocadh suas, brúigh spás nó cuir isteach. Agus tú ag tarraingt, bain úsáid as na heochracha saigheada chun an ceangaltán meán a bhogadh i dtreo ar bith. Brúigh spás nó cuir isteach arís chun an ceangaltán meán a scaoileadh ina phost nua, nó brúigh éalú chun cealú.",
   "upload_form.drag_and_drop.on_drag_cancel": "Cuireadh an tarraingt ar ceal. Scaoileadh ceangaltán meán {item}.",
   "upload_form.drag_and_drop.on_drag_end": "Scaoileadh ceangaltán meán {item}.",
   "upload_form.drag_and_drop.on_drag_over": "Bogadh ceangaltán meán {item}.",
   "upload_form.drag_and_drop.on_drag_start": "Roghnaíodh ceangaltán meán {item}.",
   "upload_form.edit": "Cuir in eagar",
+  "upload_form.thumbnail": "Athraigh mionsamhail",
+  "upload_form.video_description": "Déan cur síos ar dhaoine atá bodhar, lagéisteachta, dall nó lagamhairc",
+  "upload_modal.analyzing_picture": "Ag anailísiú íomhá…",
+  "upload_modal.apply": "Cuir i bhFeidhm",
+  "upload_modal.applying": "Á gcur i bhfeidhm…",
+  "upload_modal.choose_image": "Roghnaigh íomhá",
+  "upload_modal.description_placeholder": "Chuaigh bé mhórsách le dlúthspád fíorfhinn trí hata mo dhea-phorcáin bhig",
+  "upload_modal.detect_text": "Braith téacs ó phictiúr",
+  "upload_modal.edit_media": "Cuir gné in eagar",
+  "upload_modal.hint": "Cliceáil nó tarraing an ciorcal ar an réamhamharc chun an pointe fócasach a roghnú a bheidh le feiceáil i gcónaí ar na mionsamhlacha go léir.",
+  "upload_modal.preparing_ocr": "OCR á ullmhú…",
+  "upload_modal.preview_label": "Réamhamharc ({ratio})",
   "upload_progress.label": "Ag uaslódáil...",
   "upload_progress.processing": "Ag próiseáil…",
   "username.taken": "Glactar leis an ainm úsáideora sin. Bain triail eile as",
@@ -905,12 +882,8 @@
   "video.expand": "Leath físeán",
   "video.fullscreen": "Lánscáileán",
   "video.hide": "Cuir físeán i bhfolach",
-  "video.mute": "Balbhaigh",
+  "video.mute": "Ciúnaigh fuaim",
   "video.pause": "Cuir ar sos",
   "video.play": "Cuir ar siúl",
-  "video.skip_backward": "Scipeáil siar",
-  "video.skip_forward": "Scipeáil ar aghaidh",
-  "video.unmute": "Díbhalbhú",
-  "video.volume_down": "Toirt síos",
-  "video.volume_up": "Toirt suas"
+  "video.unmute": "Díchiúnaigh fuaim"
 }
diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json
index f295db0b43..c8614b2143 100644
--- a/app/javascript/mastodon/locales/gd.json
+++ b/app/javascript/mastodon/locales/gd.json
@@ -29,6 +29,7 @@
   "account.endorse": "Brosnaich air a’ phròifil",
   "account.featured_tags.last_status_at": "Am post mu dheireadh {date}",
   "account.featured_tags.last_status_never": "Gun phost",
+  "account.featured_tags.title": "Na tagaichean hais brosnaichte aig {name}",
   "account.follow": "Lean",
   "account.follow_back": "Lean air ais",
   "account.followers": "Luchd-leantainn",
@@ -86,24 +87,6 @@
   "alert.unexpected.title": "Oich!",
   "alt_text_badge.title": "Roghainn teacsa",
   "announcement.announcement": "Brath-fios",
-  "annual_report.summary.archetype.booster": "Brosnaiche",
-  "annual_report.summary.archetype.lurker": "Eala-bhalbh",
-  "annual_report.summary.archetype.oracle": "Coinneach Odhar",
-  "annual_report.summary.archetype.pollster": "Cunntair nam beachd",
-  "annual_report.summary.archetype.replier": "Ceatharnach nam freagairt",
-  "annual_report.summary.followers.followers": "luchd-leantainn",
-  "annual_report.summary.followers.total": "{count} gu h-iomlan",
-  "annual_report.summary.here_it_is": "Seo mar a chaidh {year} leat:",
-  "annual_report.summary.highlighted_post.by_favourites": "am post as annsa",
-  "annual_report.summary.highlighted_post.by_reblogs": "am post air a bhrosnachadh as trice",
-  "annual_report.summary.highlighted_post.by_replies": "am post dhan deach fhreagairt as trice",
-  "annual_report.summary.highlighted_post.possessive": "Aig {name},",
-  "annual_report.summary.most_used_app.most_used_app": "an aplacaid a chaidh a cleachdadh as trice",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "an taga hais a chaidh a cleachdadh as trice",
-  "annual_report.summary.most_used_hashtag.none": "Chan eil gin",
-  "annual_report.summary.new_posts.new_posts": "postaichean ùra",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Ainmeil ’nad latha ’s ’nad linn.",
-  "annual_report.summary.thanks": "Mòran taing airson conaltradh air Mastodon.",
   "attachments_list.unprocessed": "(gun phròiseasadh)",
   "audio.hide": "Falaich an fhuaim",
   "block_modal.remote_users_caveat": "Iarraidh sinn air an fhrithealaiche {domain} gun gèill iad ri do cho-dhùnadh. Gidheadh, chan eil barantas gun gèill iad on a làimhsicheas cuid a fhrithealaichean bacaidhean air dòigh eadar-dhealaichte. Dh’fhaoidte gum faic daoine gun chlàradh a-steach na postaichean poblach agad fhathast.",
@@ -127,6 +110,7 @@
   "bundle_column_error.routing.body": "Cha do lorg sinn an duilleag a dh’iarr thu. A bheil thu cinnteach gu bheil an t-URL ann am bàr an t-seòlaidh mar bu chòir?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Dùin",
+  "bundle_modal_error.message": "Chaidh rudeigin cearr nuair a dh’fheuch sinn ris a’ cho-phàirt seo a luchdadh.",
   "bundle_modal_error.retry": "Feuch ris a-rithist",
   "closed_registrations.other_server_instructions": "Air sgàth ’s gu bheil Mastodon sgaoilte, ’s urrainn dhut cunntas a chruthachadh air frithealaiche eile agus conaltradh ris an fhrithealaiche seo co-dhiù.",
   "closed_registrations_modal.description": "Cha ghabh cunntas a chruthachadh air {domain} aig an àm seo ach thoir an aire nach fheum thu cunntas air {domain} gu sònraichte airson Mastodon a chleachdadh.",
@@ -174,7 +158,7 @@
   "compose_form.poll.duration": "Faide a’ chunntais",
   "compose_form.poll.multiple": "Iomadh-roghainn",
   "compose_form.poll.option_placeholder": "Roghainn {number}",
-  "compose_form.poll.single": "Aon taghadh",
+  "compose_form.poll.single": "Aonan",
   "compose_form.poll.switch_to_multiple": "Atharraich an cunntas-bheachd ach an gabh iomadh roghainn a thaghadh",
   "compose_form.poll.switch_to_single": "Atharraich an cunntas-bheachd gus nach gabh ach aon roghainn a thaghadh",
   "compose_form.poll.type": "Stoidhle",
@@ -229,6 +213,10 @@
   "disabled_account_banner.text": "Tha an cunntas {disabledAccount} agad à comas aig an àm seo.",
   "dismissable_banner.community_timeline": "Seo na postaichean poblach as ùire o dhaoine aig a bheil cunntas air {domain}.",
   "dismissable_banner.dismiss": "Leig seachad",
+  "dismissable_banner.explore_links": "Seo na cinn-naidheachd a tha ’gan co-roinneadh as trice thar an lìona shòisealta an-diugh. Gheibh naidheachdan nas ùire a tha ’gan co-roinneadh le daoine eadar-dhealaichte rangachadh nas àirde.",
+  "dismissable_banner.explore_statuses": "Tha fèill air na postaichean seo a’ fàs thar an lìona shòisealta an-diugh. Gheibh postaichean nas ùire le barrachd brosnaichean is annsachdan rangachadh nas àirde.",
+  "dismissable_banner.explore_tags": "Tha fèill air na tagaichean hais seo a’ fàs air an fhrithealaiche seo is frithealaichean eile dhen lìonra sgaoilte an-diugh. Gheibh tagaichean hais a tha ’gan cleachdadh le daoine eadar-dhealaichte rangachadh nas àirde.",
+  "dismissable_banner.public_timeline": "Seo na postaichean poblach as ùire o dhaoine air an lìonra sòisealta tha ’gan leantainn le daoine air {domain}.",
   "domain_block_modal.block": "Bac am frithealaiche",
   "domain_block_modal.block_account_instead": "Bac @{name} ’na àite",
   "domain_block_modal.they_can_interact_with_old_posts": "’S urrainn do dhaoine a th’ air an fhrithealaiche seo eadar-ghabhail leis na seann-phostaichean agad.",
@@ -285,6 +273,7 @@
   "empty_column.hashtag": "Chan eil dad san taga hais seo fhathast.",
   "empty_column.home": "Tha loidhne-ama na dachaigh agad falamh! Lean barrachd dhaoine gus a lìonadh.",
   "empty_column.list": "Chan eil dad air an liosta seo fhathast. Nuair a phostaicheas buill a tha air an liosta seo postaichean ùra, nochdaidh iad an-seo.",
+  "empty_column.lists": "Chan eil liosta agad fhathast. Nuair chruthaicheas tu tè, nochdaidh i an-seo.",
   "empty_column.mutes": "Cha do mhùch thu cleachdaiche sam bith fhathast.",
   "empty_column.notification_requests": "Glan! Chan eil dad an-seo. Nuair a gheibh thu brathan ùra, nochdaidh iad an-seo a-rèir nan roghainnean agad.",
   "empty_column.notifications": "Cha d’ fhuair thu brath sam bith fhathast. Nuair a nì càch conaltradh leat, chì thu an-seo e.",
@@ -295,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Feuch an cuir thu à comas iad ’s gun ath-nuadhaich thu an duilleag seo. Mura cuidich sin, dh’fhaoidte gur urrainn dhut Mastodon a chleachdadh fhathast le brabhsair eile no le aplacaid thùsail.",
   "errors.unexpected_crash.copy_stacktrace": "Cuir lethbhreac dhen stacktrace air an stòr-bhòrd",
   "errors.unexpected_crash.report_issue": "Dèan aithris air an duilgheadas",
+  "explore.search_results": "Toraidhean an luirg",
   "explore.suggested_follows": "Daoine",
   "explore.title": "Rùraich",
   "explore.trending_links": "Naidheachdan",
@@ -344,6 +334,7 @@
   "footer.about": "Mu dhèidhinn",
   "footer.directory": "Eòlaire nam pròifil",
   "footer.get_app": "Faigh an aplacaid",
+  "footer.invite": "Thoir cuireadh",
   "footer.keyboard_shortcuts": "Ath-ghoiridean a’ mheur-chlàir",
   "footer.privacy_policy": "Poileasaidh prìobhaideachd",
   "footer.source_code": "Seall am bun-tùs",
@@ -391,13 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "A bheil thu airson na brathan o dhaoine nach eil ’gad leantainn a leigeil seachad?",
   "ignore_notifications_modal.not_following_title": "A bheil thu airson na brathan o dhaoine nach eil thu a’ leantainn a leigeil seachad?",
   "ignore_notifications_modal.private_mentions_title": "A bheil thu airson na brathan o iomraidhean phrìobhaideach gun iarraidh a leigeil seachad?",
+  "interaction_modal.description.favourite": "Le cunntas air Mastodon, ’s urrainn dhut am post seo a chur ris na h-annsachdan airson innse dhan ùghdar gu bheil e a’ còrdadh dhut ’s a shàbhaladh do uaireigin eile.",
+  "interaction_modal.description.follow": "Le cunntas air Mastodon, ’s urrainn dhut {name} a leantainn ach am faigh thu na postaichean aca nad dhachaigh.",
+  "interaction_modal.description.reblog": "Le cunntas air Mastodon, ’s urrainn dhut am post seo a bhrosnachadh gus a cho-roinneadh leis an luchd-leantainn agad fhèin.",
+  "interaction_modal.description.reply": "Le cunntas air Mastodon, ’s urrainn dhut freagairt a chur dhan phost seo.",
+  "interaction_modal.login.action": "Thoir dhachaigh mi",
+  "interaction_modal.login.prompt": "Àrainn-lìn an fhrithealaiche dachaigh agad, can ailbhean.co-shaoghal.net",
+  "interaction_modal.no_account_yet": "Nach eil thu air Mastodon?",
   "interaction_modal.on_another_server": "Air frithealaiche eile",
   "interaction_modal.on_this_server": "Air an frithealaiche seo",
+  "interaction_modal.sign_in": "Cha deach do chlàradh a-steach air an fhrithealaiche seo. Càit a bheil an cunntas agad ’ga òstadh?",
+  "interaction_modal.sign_in_hint": "Gliocas: Seo an làrach-lìn far an do chlàraich thu. Mur eil cuimhne agad dè bh’ ann, thoir sùil air a’ phost-d fàilteachaidh sa bhogsa a-steach agad. ’S urrainn dhut an t-ainm-cleachdaiche slàn agad a chur a-steach cuideachd! (can @mise@ ailbhean.co-shaoghal.net)",
   "interaction_modal.title.favourite": "Cuir am post aig {name} ris na h-annsachdan",
   "interaction_modal.title.follow": "Lean {name}",
   "interaction_modal.title.reblog": "Brosnaich am post aig {name}",
   "interaction_modal.title.reply": "Freagair dhan phost aig {name}",
-  "interaction_modal.title.vote": "Bhòt sa chunntas-bheachd aig {name}",
   "intervals.full.days": "{number, plural, one {# latha} two {# latha} few {# làithean} other {# latha}}",
   "intervals.full.hours": "{number, plural, one {# uair a thìde} two {# uair a thìde} few {# uairean a thìde} other {# uair a thìde}}",
   "intervals.full.minutes": "{number, plural, one {# mhionaid} two {# mhionaid} few {# mionaidean} other {# mionaid}}",
@@ -445,11 +444,20 @@
   "link_preview.author": "Le {name}",
   "link_preview.more_from_author": "Barrachd le {name}",
   "link_preview.shares": "{count, plural, one {{counter} phost} two {{counter} phost} few {{counter} postaichean} other {{counter} post}}",
+  "lists.account.add": "Cuir ris an liosta",
+  "lists.account.remove": "Thoir air falbh on liosta",
   "lists.delete": "Sguab às an liosta",
   "lists.edit": "Deasaich an liosta",
+  "lists.edit.submit": "Atharraich an tiotal",
+  "lists.exclusive": "Falaich na postaichean seo air an dachaigh",
+  "lists.new.create": "Cuir liosta ris",
+  "lists.new.title_placeholder": "Tiotal na liosta ùir",
   "lists.replies_policy.followed": "Cleachdaiche sam bith a leanas mi",
   "lists.replies_policy.list": "Buill na liosta",
   "lists.replies_policy.none": "Na seall idir",
+  "lists.replies_policy.title": "Seall freagairtean do:",
+  "lists.search": "Lorg am measg nan daoine a leanas tu",
+  "lists.subheading": "Na liostaichean agad",
   "load_pending": "{count, plural, one {# nì ùr} two {# nì ùr} few {# nithean ùra} other {# nì ùr}}",
   "loading_indicator.label": "’Ga luchdadh…",
   "media_gallery.hide": "Falaich",
@@ -498,8 +506,6 @@
   "notification.admin.report_statuses_other": "Rinn {name} gearan mu {target}",
   "notification.admin.sign_up": "Chlàraich {name}",
   "notification.admin.sign_up.name_and_others": "Chlàraich {name} ’s {count, plural, one {# eile} two {# eile} few {# eile} other {# eile}}",
-  "notification.annual_report.message": "Tha #Wrapstodon {year} deiseil dhut! Thoir sùil air mar a chaidh leat air Mastodon am bliadhna!",
-  "notification.annual_report.view": "Seall #Wrapstodon",
   "notification.favourite": "Is annsa le {name} am post agad",
   "notification.favourite.name_and_others_with_link": "Is annsa le {name} ’s <a>{count, plural, one {# eile} two {# eile} few {# eile} other {# eile}}</a> am post agad",
   "notification.follow": "Tha {name} ’gad leantainn a-nis",
@@ -606,17 +612,44 @@
   "notifications_permission_banner.enable": "Cuir brathan deasga an comas",
   "notifications_permission_banner.how_to_control": "Airson brathan fhaighinn nuair nach eil Mastodon fosgailte, cuir na brathan deasga an comas. Tha an smachd agad fhèin air dè na seòrsaichean de chonaltradh a ghineas brathan deasga leis a’ phutan {icon} gu h-àrd nuair a bhios iad air an cur an comas.",
   "notifications_permission_banner.title": "Na caill dad gu bràth tuilleadh",
+  "onboarding.action.back": "Air ais leam",
+  "onboarding.actions.back": "Air ais leam",
+  "onboarding.actions.go_to_explore": "Thoir dha na treandaichean mi",
+  "onboarding.actions.go_to_home": "Thoir dhachaigh mi",
+  "onboarding.compose.template": "Shin thu, a #Mhastodon!",
   "onboarding.follows.empty": "Gu mì-fhortanach, chan urrainn dhuinn toradh a shealltainn an-dràsta. Feuch gleus an luirg no duilleag an rùrachaidh airson daoine ri leantainn a lorg no feuch ris a-rithist an ceann tamaill.",
+  "onboarding.follows.lead": "’S e do prìomh-doras do Mhastodon a th’ ann san dachaigh. Mar as motha an t-uiread de dhaoine a leanas tu ’s ann nas beòthaile inntinniche a bhios i. Seo moladh no dhà dhut airson tòiseachadh:",
+  "onboarding.follows.title": "Cuir dreach pearsanta air do dhachaigh",
   "onboarding.profile.discoverable": "Bu mhath leam gun gabh a’ phròifil agam a rùrachadh",
   "onboarding.profile.discoverable_hint": "Ma chuir thu romhad gun gabh a’ phròifil agad a rùrachadh air Mastodon, faodaidh na postaichean agad nochdadh ann an toraidhean luirg agus treandaichean agus dh’fhaoidte gun dèid a’ phròifil agad a mholadh dhan fheadhainn aig a bheil ùidhean coltach ri d’ ùidhean-sa.",
   "onboarding.profile.display_name": "Ainm-taisbeanaidh",
   "onboarding.profile.display_name_hint": "D’ ainm slàn no spòrsail…",
+  "onboarding.profile.lead": "’S urrainn dhut seo a choileanadh uair sam bith eile sna roghainnean far am bi roghainnean gnàthachaidh eile ri làimh dhut cuideachd.",
   "onboarding.profile.note": "Cunntas-beatha",
   "onboarding.profile.note_hint": "’S urrainn dhut @iomradh a thoirt air càch no air #tagaicheanHais…",
   "onboarding.profile.save_and_continue": "Sàbhail ’s lean air adhart",
   "onboarding.profile.title": "Suidheachadh na pròifile",
   "onboarding.profile.upload_avatar": "Luchdaich suas dealbh na pròifil",
   "onboarding.profile.upload_header": "Luchdaich suas bann-cinn na pròifil",
+  "onboarding.share.lead": "Innis do dhaoine mar a gheibh iad grèim ort air Mastodon!",
+  "onboarding.share.message": "Is mise {username} air #Mastodon! Thig ’gam leantainn air {url}",
+  "onboarding.share.next_steps": "Ceuman eile as urrainn dhut gabhail:",
+  "onboarding.share.title": "Co-roinn a’ phròifil agad",
+  "onboarding.start.lead": "Tha thu ’nad bhall de Mhastodon a-nis, seo ùrlar mheadhanan sòisealta sònraichte sgaoilte far am bi na chì thu an urra riut fhèin seach an urra ri algairim. Seo dhut toiseach-tòiseachaidh air an àrainneachd ùr:",
+  "onboarding.start.skip": "Nach eil thu feumach air taic airson tòiseachadh?",
+  "onboarding.start.title": "Rinn thu a’ chùis air!",
+  "onboarding.steps.follow_people.body": "Tha leantainn dhaoine inntinneach air cridhe Mhastodon.",
+  "onboarding.steps.follow_people.title": "Cuir dreach pearsanta air do dhachaigh",
+  "onboarding.steps.publish_status.body": "Cuir an aithne air an t-saoghal le teacsa, dealbhan, videothan no cunntasan-bheachd {emoji}",
+  "onboarding.steps.publish_status.title": "Dèan a’ chiad phost agad",
+  "onboarding.steps.setup_profile.body": "Brosnaich an conaltradh a gheibh thu le pròifil shlàn.",
+  "onboarding.steps.setup_profile.title": "Gnàthaich a’ phròifil agad",
+  "onboarding.steps.share_profile.body": "Leig fios dha do charaidean mar a gheibh iad lorg ort air Mastodon",
+  "onboarding.steps.share_profile.title": "Co-roinn a’ phròifil Mastodon agad",
+  "onboarding.tips.2fa": "<strong>An robh fios agad?</strong> ’S urrainn dhut an cunntas agad a dhìon is tu a’ suidheachadh dearbhadh dà-cheumnach ann an roghainnean a’ chunntais agad. Obraichidh e le aplacaid dearbhaidh dhà-cheumnaich sam bith a thogras tu gun fheum air àireamh fòn!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>An robh fios agad?</strong> On a tha Mastodon sgaoilte, tachraidh tu air pròifilean a tha ’gan òstadh air frithealaichean eile. ’S urrainn dhut bruidhinn riutha gun chnap-starra co-dhiù! ’S e ainm an fhrithealaiche a tha san dàrna leth dhen ainm-chleachdaiche aca!",
+  "onboarding.tips.migration": "<strong>An robh fios agad?</strong> Ma thig an latha nach eil thu toilichte le {domain} mar an fhrithealaiche agad tuilleadh, ’s urrainn dhut imrich gu frithealaiche Mastodon eile gun a bhith a’ call an luchd-leantainn agad. ’S urrainn dhut fiù frithealaiche agad fhèin òstadh!",
+  "onboarding.tips.verification": "<strong>An robh fios agad?</strong> ’S urrainn dhut an cunntas agad a dhearbhadh. Cuir ceangal ris a’ phròifil Mastodon air an làrach-lìn agad fhèin agus cuir an làrach-lìn ris a’ phròifil agad an uairsin. Cha bhi feum air pàigheadh no sgrìobhainnean!",
   "password_confirmation.exceeds_maxlength": "Tha dearbhadh an fhacail-fhaire nas fhaide na tha ceadaichte do dh’faclan-faire",
   "password_confirmation.mismatching": "Chan eil an dearbhadh co-ionnan ris an fhacal-fhaire",
   "picture_in_picture.restore": "Thoir air ais e",
@@ -632,6 +665,7 @@
   "poll_button.remove_poll": "Thoir air falbh an cunntas-bheachd",
   "privacy.change": "Cuir gleus air prìobhaideachd a’ phuist",
   "privacy.direct.long": "A h-uile duine air a bheil iomradh sa phost",
+  "privacy.direct.short": "Daoine àraidh",
   "privacy.private.long": "An luchd-leantainn agad a-mhàin",
   "privacy.private.short": "Luchd-leantainn",
   "privacy.public.long": "Duine sam bith taobh a-staigh no a-muigh Mhastodon",
@@ -643,6 +677,8 @@
   "privacy_policy.title": "Poileasaidh prìobhaideachd",
   "recommended": "Molta",
   "refresh": "Ath-nuadhaich",
+  "regeneration_indicator.label": "’Ga luchdadh…",
+  "regeneration_indicator.sublabel": "Tha do dhachaigh ’ga ullachadh!",
   "relative_time.days": "{number}l",
   "relative_time.full.days": "{number, plural, one {# latha} two {# latha} few {# làithean} other {# latha}} air ais",
   "relative_time.full.hours": "{number, plural, one {# uair a thìde} two {# uair a thìde} few {# uairean a thìde} other {# uair a thìde}} air ais",
@@ -726,8 +762,10 @@
   "search_results.accounts": "Pròifilean",
   "search_results.all": "Na h-uile",
   "search_results.hashtags": "Tagaichean hais",
+  "search_results.nothing_found": "Cha do lorg sinn dad dha na h-abairtean-luirg seo",
   "search_results.see_all": "Seall na h-uile",
   "search_results.statuses": "Postaichean",
+  "search_results.title": "Lorg {q}",
   "server_banner.about_active_users": "Daoine a chleachd am frithealaiche seo rè an 30 latha mu dheireadh (Cleachdaichean gnìomhach gach mìos)",
   "server_banner.active_users": "cleachdaichean gnìomhach",
   "server_banner.administered_by": "Rianachd le:",
@@ -815,12 +853,26 @@
   "upload_button.label": "Cuir ris dealbhan, video no faidhle fuaime",
   "upload_error.limit": "Luchdaich thu suas na tha ceadaichte dhut a dh’fhaidhlichean mu thràth.",
   "upload_error.poll": "Chan fhaod thu faidhle a luchdadh suas an cois cunntais-bheachd.",
+  "upload_form.audio_description": "Mìnich e dhan fheadhainn le èisteachd bheag",
+  "upload_form.description": "Mìnich e dhan fheadhainn le cion-lèirsinne",
   "upload_form.drag_and_drop.instructions": "Airson ceanglachan meadhain a thogail, brùth air space no enter. Fhad ’ a bhios tu ’ga shlaodadh, cleachd na h-iuchraichean-saighde airson an ceanglachan meadhain a ghluasad gu comhair sam bith. Brùth air space no enter a-rithist airson an ceanglachen meadhain a leigeil às air an ionad ùr aige no brùth air escape airson sgur dheth.",
   "upload_form.drag_and_drop.on_drag_cancel": "Chaidh sgur dhen t-slaodadh. Chaidh an ceanglachan meadhain {item} a leigeil às.",
   "upload_form.drag_and_drop.on_drag_end": "Chaidh an ceanglachan meadhain {item} a leigeil às.",
   "upload_form.drag_and_drop.on_drag_over": "Chaidh an ceanglachan meadhain {item} a ghluasad.",
   "upload_form.drag_and_drop.on_drag_start": "Chaidh an ceanglachan meadhain {item} a thogail.",
   "upload_form.edit": "Deasaich",
+  "upload_form.thumbnail": "Atharraich an dealbhag",
+  "upload_form.video_description": "Mìnich e dhan fheadhainn le èisteachd bheag no cion-lèirsinne",
+  "upload_modal.analyzing_picture": "A’ sgrùdadh an deilbh…",
+  "upload_modal.apply": "Cuir an sàs",
+  "upload_modal.applying": "’Ga chur an sàs…",
+  "upload_modal.choose_image": "Tagh dealbh",
+  "upload_modal.description_placeholder": "Lorg Sìm fiù bò, cè ⁊ neup ’ad àth",
+  "upload_modal.detect_text": "Mothaich dhan teacsa on dealbh",
+  "upload_modal.edit_media": "Deasaich am meadhan",
+  "upload_modal.hint": "Briog no slaod an cearcall air an ro-shealladh airson puing an fhòcais a thaghadh a chithear air gach dealbhag dheth.",
+  "upload_modal.preparing_ocr": "Ag ullachadh OCR…",
+  "upload_modal.preview_label": "Ro-shealladh ({ratio})",
   "upload_progress.label": "’Ga luchdadh suas…",
   "upload_progress.processing": "’Ga phròiseasadh…",
   "username.taken": "Tha an t-ainm-cleachdaiche seo aig cuideigin eile. Feuch fear eile",
@@ -830,6 +882,8 @@
   "video.expand": "Leudaich a’ video",
   "video.fullscreen": "Làn-sgrìn",
   "video.hide": "Falaich a’ video",
+  "video.mute": "Mùch an fhuaim",
   "video.pause": "Cuir ’na stad",
-  "video.play": "Cluich"
+  "video.play": "Cluich",
+  "video.unmute": "Dì-mhùch an fhuaim"
 }
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 50dc2437c6..a39af3bc15 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Editar perfil",
   "account.enable_notifications": "Noficarme cando @{name} publique",
   "account.endorse": "Amosar no perfil",
-  "account.featured": "Destacado",
-  "account.featured.hashtags": "Cancelos",
-  "account.featured.posts": "Publicacións",
   "account.featured_tags.last_status_at": "Última publicación o {date}",
   "account.featured_tags.last_status_never": "Sen publicacións",
+  "account.featured_tags.title": "Cancelos destacados de {name}",
   "account.follow": "Seguir",
   "account.follow_back": "Seguir tamén",
   "account.followers": "Seguidoras",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicacións}}",
   "account.unblock": "Desbloquear @{name}",
   "account.unblock_domain": "Amosar {domain}",
-  "account.unblock_domain_short": "Desbloquear",
   "account.unblock_short": "Desbloquear",
   "account.unendorse": "Non amosar no perfil",
   "account.unfollow": "Deixar de seguir",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Aconteceu un fallo non agardado.",
   "alert.unexpected.title": "Vaites!",
   "alt_text_badge.title": "Texto Alt",
-  "alt_text_modal.add_alt_text": "Engadir texto descritivo",
-  "alt_text_modal.add_text_from_image": "Engadir texto desde a imaxe",
-  "alt_text_modal.cancel": "Desbotar",
-  "alt_text_modal.change_thumbnail": "Cambiar a miniatura",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Describe isto para as persoas con dificultades auditivas…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Describe isto para as persoas con dificultades visuais…",
-  "alt_text_modal.done": "Feito",
   "announcement.announcement": "Anuncio",
-  "annual_report.summary.archetype.booster": "O Telexornal",
-  "annual_report.summary.archetype.lurker": "Volleur",
-  "annual_report.summary.archetype.oracle": "Sabichón",
-  "annual_report.summary.archetype.pollster": "I.G.E.",
-  "annual_report.summary.archetype.replier": "Lareteire",
-  "annual_report.summary.followers.followers": "seguidoras",
-  "annual_report.summary.followers.total": "{count} en total",
-  "annual_report.summary.here_it_is": "Este é o resumo do teu {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "a publicación mais favorecida",
-  "annual_report.summary.highlighted_post.by_reblogs": "a publicación con mais promocións",
-  "annual_report.summary.highlighted_post.by_replies": "a publicación con mais respostas",
-  "annual_report.summary.highlighted_post.possessive": "de {name}",
-  "annual_report.summary.most_used_app.most_used_app": "app que mais usaches",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "o cancelo mais utilizado",
-  "annual_report.summary.most_used_hashtag.none": "Nada",
-  "annual_report.summary.new_posts.new_posts": "novas publicacións",
-  "annual_report.summary.percentile.text": "<topLabel>Sitúante no top</topLabel><percentage></percentage><bottomLabel> das usuarias de {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Moito tes que contarnos!",
-  "annual_report.summary.thanks": "Grazas por ser parte de Mastodon!",
   "attachments_list.unprocessed": "(sen procesar)",
   "audio.hide": "Agochar audio",
   "block_modal.remote_users_caveat": "Ímoslle pedir ao servidor {domain} que respecte a túa decisión. Emporiso, non hai garantía de que atenda a petición xa que os servidores xestionan os bloqueos de formas diferentes. As publicacións públicas poderían aínda ser visibles para usuarias que non iniciaron sesión.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "Non atopamos a páxina solicitada. Tes a certeza de que o URL na barra de enderezos é correcto?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Pechar",
-  "bundle_modal_error.message": "Algo fallou mentras cargaba esta páxina.",
+  "bundle_modal_error.message": "Ocorreu un erro ó cargar este compoñente.",
   "bundle_modal_error.retry": "Téntao de novo",
   "closed_registrations.other_server_instructions": "Cómo Mastodon é descentralizado, podes crear unha conta noutro servidor e interactuar igualmente con este.",
   "closed_registrations_modal.description": "Actualmente non é posible crear unha conta en {domain}, pero ten en conta que non precisas unha conta específicamente en {domain} para usar Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Usuarias bloqueadas",
   "column.bookmarks": "Marcadores",
   "column.community": "Cronoloxía local",
-  "column.create_list": "Crear lista",
   "column.direct": "Mencións privadas",
   "column.directory": "Procurar perfís",
   "column.domain_blocks": "Dominios agochados",
-  "column.edit_list": "Editar lista",
   "column.favourites": "Favoritas",
   "column.firehose": "O que acontece",
   "column.follow_requests": "Peticións de seguimento",
   "column.home": "Inicio",
-  "column.list_members": "Xestionar membros da lista",
   "column.lists": "Listaxes",
   "column.mutes": "Usuarias acaladas",
   "column.notifications": "Notificacións",
@@ -172,7 +140,6 @@
   "column_header.pin": "Fixar",
   "column_header.show_settings": "Amosar axustes",
   "column_header.unpin": "Desapegar",
-  "column_search.cancel": "Cancelar",
   "column_subheading.settings": "Axustes",
   "community.column_settings.local_only": "Só local",
   "community.column_settings.media_only": "Só multimedia",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Duración da enquisa",
   "compose_form.poll.multiple": "Escolla múltiple",
   "compose_form.poll.option_placeholder": "Opción {number}",
-  "compose_form.poll.single": "Opción única",
+  "compose_form.poll.single": "Elixe unha",
   "compose_form.poll.switch_to_multiple": "Mudar a enquisa para permitir múltiples escollas",
   "compose_form.poll.switch_to_single": "Mudar a enquisa para permitir unha soa opción",
   "compose_form.poll.type": "Estilo",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Editar",
   "confirmations.edit.message": "Ao editar sobrescribirás a mensaxe que estás a compor. Tes a certeza de que queres continuar?",
   "confirmations.edit.title": "Editar a publicación?",
-  "confirmations.follow_to_list.confirm": "Seguir e engadir á lista",
-  "confirmations.follow_to_list.message": "Tes que seguir a {name} para poder engadila a unha lista.",
-  "confirmations.follow_to_list.title": "Seguir á usuaria?",
   "confirmations.logout.confirm": "Pechar sesión",
   "confirmations.logout.message": "Desexas pechar a sesión?",
   "confirmations.logout.title": "Pechar sesión?",
-  "confirmations.missing_alt_text.confirm": "Engadir texto descritivo",
-  "confirmations.missing_alt_text.message": "A publicación contén multimedia sen un texto que o describa. Ao engadir a descrición fas o contido accesible para máis persoas.",
-  "confirmations.missing_alt_text.secondary": "Publicar igualmente",
-  "confirmations.missing_alt_text.title": "Engadir texto descritivo?",
   "confirmations.mute.confirm": "Acalar",
   "confirmations.redraft.confirm": "Eliminar e reescribir",
   "confirmations.redraft.message": "Tes a certeza de querer eliminar esta publicación e reescribila? Perderás as promocións e favorecementos, e as respostas á publicación orixinal ficarán orfas.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Actualmente a túa conta {disabledAccount} está desactivada.",
   "dismissable_banner.community_timeline": "Estas son as publicacións máis recentes das persoas que teñen a súa conta en {domain}.",
   "dismissable_banner.dismiss": "Desbotar",
-  "dismissable_banner.explore_links": "Estas son as historias de novas que máis se están compartindo hoxe no fediverso. As historias máis recentes compartidas por máis persoas móstranse máis arriba.",
-  "dismissable_banner.explore_statuses": "Estas publicacións do fediverso están hoxe gañando popularidade. As publicacións máis recentes con máis promocións e favorecementos móstranse máis arriba.",
-  "dismissable_banner.explore_tags": "Estes cancelos están gañando popularidade hoxe no fediverso. Os cancelos utilizados por máis persoas móstranse máis arriba.",
-  "dismissable_banner.public_timeline": "Estas son as publicacións públicas más recentes das persoas do fediverso seguidas por persoas de {domain}.",
+  "dismissable_banner.explore_links": "Estas son as novas historias más compartidas hoxe na web social. Aparecen primeiro as novas compartidas por máis persoas diferentes.",
+  "dismissable_banner.explore_statuses": "Estas son as publicacións da web social que hoxe están gañando popularidade. As publicacións con máis promocións e favorecemento teñen puntuación máis alta.",
+  "dismissable_banner.explore_tags": "Estes cancelos están gañando popularidade entre as persoas deste servidor e noutros servidores da rede descentralizada.",
+  "dismissable_banner.public_timeline": "Estas son as publicacións públicas máis recentes das persoas que as usuarias de {domain} están a seguir.",
   "domain_block_modal.block": "Bloquear servidor",
   "domain_block_modal.block_account_instead": "Prefiro bloquear a @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "As persoas deste servidor poden interactuar coas túas publicacións antigas.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Resultados da procura",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viaxes e Lugares",
-  "empty_column.account_featured": "A lista está baleira",
   "empty_column.account_hides_collections": "A usuaria decideu non facer pública esta información",
   "empty_column.account_suspended": "Conta suspendida",
   "empty_column.account_timeline": "Non hai publicacións aquí!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Aínda non hai nada con este cancelo.",
   "empty_column.home": "A túa cronoloxía inicial está baleira! Sigue a outras usuarias para enchela.",
   "empty_column.list": "Aínda non hai nada nesta listaxe. Cando as usuarias incluídas na listaxe publiquen mensaxes, amosaranse aquí.",
+  "empty_column.lists": "Aínda non tes listaxes. Cando crees unha, amosarase aquí.",
   "empty_column.mutes": "Aínda non silenciaches a ningúnha usuaria.",
   "empty_column.notification_requests": "Todo ben! Nada por aquí. Cando recibas novas notificacións aparecerán aquí seguindo o criterio dos teus axustes.",
   "empty_column.notifications": "Aínda non tes notificacións. Aparecerán cando outras persoas interactúen contigo.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Intenta desactivalas e actualiza a páxina. Se isto non funciona, podes seguir usando Mastodon nun navegador diferente ou aplicación nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar trazas (stacktrace) ó portapapeis",
   "errors.unexpected_crash.report_issue": "Informar sobre un problema",
+  "explore.search_results": "Resultados da busca",
   "explore.suggested_follows": "Persoas",
   "explore.title": "Descubrir",
   "explore.trending_links": "Novas",
@@ -373,16 +334,13 @@
   "footer.about": "Sobre",
   "footer.directory": "Directorio de perfís",
   "footer.get_app": "Descarga a app",
+  "footer.invite": "Convidar persoas",
   "footer.keyboard_shortcuts": "Atallos do teclado",
   "footer.privacy_policy": "Política de privacidade",
   "footer.source_code": "Ver código fonte",
   "footer.status": "Estado",
-  "footer.terms_of_service": "Termos do servizo",
   "generic.saved": "Gardado",
   "getting_started.heading": "Primeiros pasos",
-  "hashtag.admin_moderation": "Abrir interface de moderación para ##{name}",
-  "hashtag.browse": "Ver publicacións con #{hashtag}",
-  "hashtag.browse_from_account": "Ver as publicacións de @{name} con #{hashtag}",
   "hashtag.column_header.tag_mode.all": "e {additional}",
   "hashtag.column_header.tag_mode.any": "ou {additional}",
   "hashtag.column_header.tag_mode.none": "sen {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} publicación} other {{counter} publicacións}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} publicación} other {{counter} publicacións}} hoxe",
   "hashtag.follow": "Seguir cancelo",
-  "hashtag.mute": "Acalar a #{hashtag}",
   "hashtag.unfollow": "Deixar de seguir cancelo",
   "hashtags.and_other": "…e {count, plural, one {}other {# máis}}",
   "hints.profiles.followers_may_be_missing": "Poderían faltar seguidoras deste perfil.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignorar notificacións de persoas que non te seguen?",
   "ignore_notifications_modal.not_following_title": "Ignorar notificacións de persoas que non segues?",
   "ignore_notifications_modal.private_mentions_title": "Ignorar notificacións de Mencións Privadas non solicitadas?",
-  "info_button.label": "Axuda",
-  "info_button.what_is_alt_text": "<h1>Que é o Texto Alternativo?</h1> <p>O Text Alt proporciona a descrición das imaxes para as persoas con deficiencias visuais, conexións a internet de baixa calidade ou para engadir contexto ás mesmas.</p> <p>Podes mellorar a accesibilidade e a comprensión da publicación ao escribir un texto alternativo claro, conciso e obxectivo.</p> <ul> <li>Identifica os elementos importantes</li> <li>Inclúe o texto que apareza nas imaxes</li> <li>Utiliza sintaxe estándar nas frases</li> <li>Evita información redundante</li> <li>Céntrate nos elementos principais cando sexan imaxes complexas (como diagramas ou mapas)</li> </ul>",
-  "interaction_modal.action.favourite": "Para continuar, debes favorecer desde a túa conta.",
-  "interaction_modal.action.follow": "Para continuar, debes facer seguimento desde a túa conta.",
-  "interaction_modal.action.reblog": "Para continuar, debes promover desde a túa conta.",
-  "interaction_modal.action.reply": "Para continuar, debes responder desde a túa conta.",
-  "interaction_modal.action.vote": "Para continuar, debes votar desde a túa conta.",
-  "interaction_modal.go": "Ir",
+  "interaction_modal.description.favourite": "Cunha conta Mastodon podes favorecer esta publicación e facerlle saber á autora que che gustou e que a gardas para máis tarde.",
+  "interaction_modal.description.follow": "Cunha conta en Mastodon, poderás seguir a {name} e recibir as súas publicacións na túa cronoloxía de inicio.",
+  "interaction_modal.description.reblog": "Cunha conta en Mastodon, poderás promover esta publicación para compartila con quen te siga.",
+  "interaction_modal.description.reply": "Cunha conta en Mastodon, poderás responder a esta publicación.",
+  "interaction_modal.login.action": "Seguir desde alá",
+  "interaction_modal.login.prompt": "Dominio do teu servidor de inicio, ex. mastodon.social",
   "interaction_modal.no_account_yet": "Aínda non tes unha conta?",
   "interaction_modal.on_another_server": "Nun servidor diferente",
   "interaction_modal.on_this_server": "Neste servidor",
+  "interaction_modal.sign_in": "Non iniciaches sesión neste servidor. Onde creaches a túa conta?",
+  "interaction_modal.sign_in_hint": "Axuda: trátase da web na que te rexistraches. Se non a lembras, busca na caixa de correo a mensaxe de benvida. Tamén podes escribir o teu identificador completo! (ex. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Marcar coma favorita a publicación de {name}",
   "interaction_modal.title.follow": "Seguir a {name}",
   "interaction_modal.title.reblog": "Promover a publicación de {name}",
   "interaction_modal.title.reply": "Responder á publicación de {name}",
-  "interaction_modal.title.vote": "Vota na enquisa de {name}",
-  "interaction_modal.username_prompt": "Ex. {example}",
   "intervals.full.days": "{number, plural,one {# día} other {# días}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Para mostrar o texto tras Aviso de Contido (CW)",
   "keyboard_shortcuts.toggle_sensitivity": "Para amosar/agochar contido multimedia",
   "keyboard_shortcuts.toot": "Para escribir unha nova publicación",
-  "keyboard_shortcuts.translate": "para traducir unha publicación",
   "keyboard_shortcuts.unfocus": "Para deixar de destacar a área de escritura/procura",
   "keyboard_shortcuts.up": "Para mover cara arriba na listaxe",
   "lightbox.close": "Fechar",
@@ -490,32 +444,20 @@
   "link_preview.author": "Por {name}",
   "link_preview.more_from_author": "Máis de {name}",
   "link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicacións}}",
-  "lists.add_member": "Engadir",
-  "lists.add_to_list": "Engadir á lista",
-  "lists.add_to_lists": "Engadir {name} ás listas",
-  "lists.create": "Crear",
-  "lists.create_a_list_to_organize": "Crear unha nova lista para organizar o teu Inicio",
-  "lists.create_list": "Crear lista",
+  "lists.account.add": "Engadir á listaxe",
+  "lists.account.remove": "Eliminar da listaxe",
   "lists.delete": "Eliminar listaxe",
-  "lists.done": "Feito",
   "lists.edit": "Editar listaxe",
-  "lists.exclusive": "Ocultar membros no Inicio",
-  "lists.exclusive_hint": "Se alguén está nesta lista non aparerá na cronoloxía de Inicio para evitar duplicidades das publicacións.",
-  "lists.find_users_to_add": "Buscar persoas que engadir",
-  "lists.list_members": "Membros da lista",
-  "lists.list_members_count": "{count, plural, one {# membro} other {# membros}}",
-  "lists.list_name": "Nome da lista",
-  "lists.new_list_name": "Novo nome da lista",
-  "lists.no_lists_yet": "Aínda non hai listas.",
-  "lists.no_members_yet": "Aínda non hai membros.",
-  "lists.no_results_found": "Non se atoparon resultados.",
-  "lists.remove_member": "Retirar",
+  "lists.edit.submit": "Mudar o título",
+  "lists.exclusive": "Agocha estas publicacións no Inicio",
+  "lists.new.create": "Engadir listaxe",
+  "lists.new.title_placeholder": "Título da nova listaxe",
   "lists.replies_policy.followed": "Calquera usuaria que siga",
   "lists.replies_policy.list": "Membros da lista",
   "lists.replies_policy.none": "Ninguén",
-  "lists.save": "Gardar",
-  "lists.search": "Buscar",
-  "lists.show_replies_to": "Incluír respostas dos membros das listas a",
+  "lists.replies_policy.title": "Mostrar respostas a:",
+  "lists.search": "Procurar entre as persoas que segues",
+  "lists.subheading": "As túas listaxes",
   "load_pending": "{count, plural, one {# novo elemento} other {# novos elementos}}",
   "loading_indicator.label": "Estase a cargar…",
   "media_gallery.hide": "Agochar",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} denunciou a {target}",
   "notification.admin.sign_up": "{name} rexistrouse",
   "notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {# máis} other {# máis}} crearon unha conta",
-  "notification.annual_report.message": "#Wrapstodon de {year} agarda por ti! Desvela os momentos máis destacados e historias reseñables en Mastodon!",
-  "notification.annual_report.view": "Ver #Wrapstodon",
   "notification.favourite": "{name} marcou como favorita a túa publicación",
   "notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {# máis} other {# máis}}</a> favoreceron a túa publicación",
-  "notification.favourite_pm": "{name} favoreceu a túa mención privada",
-  "notification.favourite_pm.name_and_others_with_link": "{name} e <a>{count, plural, one {outra persoa} other {outras # persoas}}</a> favoreceron a túa mención privada",
   "notification.follow": "{name} comezou a seguirte",
   "notification.follow.name_and_others": "{name} e <a>{count, plural, one {# mais} other {# mais}}</a> seguíronte",
   "notification.follow_request": "{name} solicitou seguirte",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Activar notificacións de escritorio",
   "notifications_permission_banner.how_to_control": "Activa as notificacións de escritorio para recibir notificacións mentras Mastodon non está aberto. Podes controlar de xeito preciso o tipo de interaccións que crean as notificacións de escritorio a través da {icon} superior unha vez están activadas.",
   "notifications_permission_banner.title": "Non perder nada",
-  "onboarding.follows.back": "Volver",
-  "onboarding.follows.done": "Feito",
+  "onboarding.action.back": "Lévame de volta",
+  "onboarding.actions.back": "Lévame de volta",
+  "onboarding.actions.go_to_explore": "Mira do que se está a falar",
+  "onboarding.actions.go_to_home": "Vai á cronoloxía de inicio",
+  "onboarding.compose.template": "Ola #Mastodon!",
   "onboarding.follows.empty": "Desgraciadamente agora mesmo non hai nada que mostrar. Podes intentalo coa busca ou na páxina descubrir para atopar persoas ás que seguir, ou intentalo máis tarde.",
-  "onboarding.follows.search": "Buscar",
-  "onboarding.follows.title": "Comeza seguindo algunhas persoas",
+  "onboarding.follows.lead": "A cronoloxía de Inicio é o principal xeito de desfrutar Mastodon. Cantas máis persoas sigas mais interesante e activa será. Para comezar, aquí tes algunhas suxestións:",
+  "onboarding.follows.title": "Personaliza a cronoloxía de inicio",
   "onboarding.profile.discoverable": "Que o meu perfil se poida atopar",
   "onboarding.profile.discoverable_hint": "Cando elixes que poidan atoparte en Mastodon as túas publicacións aparecerán nos resultados das buscas e nos temas en voga, e o teu perfil podería ser suxerido para seguimento a persoas con intereses semellantes aos teus.",
   "onboarding.profile.display_name": "Nome público",
   "onboarding.profile.display_name_hint": "O teu nome completo ou un nome divertido…",
+  "onboarding.profile.lead": "Sempre poderás incluír esta información mais tarde nos axustes, onde terás máis opcións dispoñibles.",
   "onboarding.profile.note": "Acerca de ti",
   "onboarding.profile.note_hint": "Podes @mencionar a outras persoas ou usar #cancelos…",
   "onboarding.profile.save_and_continue": "Gardar e continuar",
   "onboarding.profile.title": "Configuración do perfil",
   "onboarding.profile.upload_avatar": "Subir imaxe do perfil",
   "onboarding.profile.upload_header": "Subir cabeceira para o perfil",
+  "onboarding.share.lead": "Fai que as persoas saiban como atoparte en Mastodon!",
+  "onboarding.share.message": "Son {username} en #Mastodon! Sígueme en {url}",
+  "onboarding.share.next_steps": "Seguintes pasos:",
+  "onboarding.share.title": "Comparte o teu perfil",
+  "onboarding.start.lead": "Xa formas parte de Mastodon, unha plataforma de relacións sociais descentralizada, única, onde ti —e non un algoritmo— elixes o que les. Axudámosche cos primeiros pasos:",
+  "onboarding.start.skip": "Queres omitir todo isto?",
+  "onboarding.start.title": "Pois xa está!",
+  "onboarding.steps.follow_people.body": "Mastodon consiste en seguir a persoas interesantes.",
+  "onboarding.steps.follow_people.title": "Personaliza a túa cronoloxía",
+  "onboarding.steps.publish_status.body": "Exprésate con texto, fotos, vídeos ou enquisas {emoji}",
+  "onboarding.steps.publish_status.title": "Escribe a túa primeira publicación",
+  "onboarding.steps.setup_profile.body": "Ao engadir información ao teu perfil é máis probable que teñas máis interaccións.",
+  "onboarding.steps.setup_profile.title": "Personaliza o perfil",
+  "onboarding.steps.share_profile.body": "Dille ás amizades como poden atoparte en Mastodon.",
+  "onboarding.steps.share_profile.title": "Comparte o teu perfil en Mastodon",
+  "onboarding.tips.2fa": "<strong>Sabes que?</strong> Podes protexer a túa conta configurando un segundo factor de autenticación nos axustes. Funciona con calquera app TOTP, non precisas un número de teléfono!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Sabes que?</strong> Como Mastodon é descentralizado, algúns perfís que atopes estarán en servidores diferentes ao teu. Pero podes interactuar igualmente con eles! O seu servidor é o que ven despois da @ no seu identificador!",
+  "onboarding.tips.migration": "<strong>Sabes que?</strong> Se cres que {domain} non é o servidor axeitado para ti, podes mover a conta a outro servidor Mastodon sen perder as túas seguidoras. Incluso podes hospedar o teu propio servidor!",
+  "onboarding.tips.verification": "<strong>Sabes que?</strong> Podes verificar a túa conta poñendo unha ligazón ao teu perfil en Mastodon no teu sitio web e engadindo esa web ao teu perfil. Non hai que pagar nada nin presentar documentos!",
   "password_confirmation.exceeds_maxlength": "A lonxitude do contrasinal de confirmación excede o máximo permitido",
   "password_confirmation.mismatching": "O contrasinal de confirmación non concorda",
   "picture_in_picture.restore": "Devolver",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Eliminar enquisa",
   "privacy.change": "Axustar privacidade",
   "privacy.direct.long": "Todas as mencionadas na publicación",
-  "privacy.direct.short": "Mención privada",
+  "privacy.direct.short": "Persoas mencionadas",
   "privacy.private.long": "Só para seguidoras",
   "privacy.private.short": "Seguidoras",
   "privacy.public.long": "Para todas dentro e fóra de Mastodon",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Política de Privacidade",
   "recommended": "Aconsellable",
   "refresh": "Actualizar",
-  "regeneration_indicator.please_stand_by": "Por favor, agarda.",
-  "regeneration_indicator.preparing_your_home_feed": "Creando a túa cronoloxía…",
+  "regeneration_indicator.label": "Estase a cargar…",
+  "regeneration_indicator.sublabel": "Estase a preparar a túa cronoloxía de inicio!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "hai {number, plural, one {# día} other {# días}}",
   "relative_time.full.hours": "hai {number, plural, one {# hora} other {# horas}}",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Perfís",
   "search_results.all": "Todo",
   "search_results.hashtags": "Cancelos",
-  "search_results.no_results": "Sen resultados.",
-  "search_results.no_search_yet": "Intenta buscando publicacións, perfís ou cancelos.",
+  "search_results.nothing_found": "Non atopamos nada con estes termos de busca",
   "search_results.see_all": "Ver todo",
   "search_results.statuses": "Publicacións",
-  "search_results.title": "Resultados para «{q}»",
+  "search_results.title": "Resultados para {q}",
   "server_banner.about_active_users": "Persoas que usaron este servidor nos últimos 30 días (Usuarias Activas Mensuais)",
   "server_banner.active_users": "usuarias activas",
   "server_banner.administered_by": "Administrada por:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Aínda ninguén promoveu esta publicación. Cando alguén o faga, amosarase aquí.",
   "status.redraft": "Eliminar e reescribir",
   "status.remove_bookmark": "Eliminar marcador",
-  "status.remove_favourite": "Retirar das favoritas",
   "status.replied_in_thread": "Respondeu nun fío",
   "status.replied_to": "Respondeu a {name}",
   "status.reply": "Responder",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Cambiar a subscrición a idiomas para {target}",
   "tabs_bar.home": "Inicio",
   "tabs_bar.notifications": "Notificacións",
-  "terms_of_service.effective_as_of": "Con efecto desde o {date}",
-  "terms_of_service.title": "Termos do Servizo",
-  "terms_of_service.upcoming_changes_on": "Cambios por vir o {date}",
   "time_remaining.days": "Remata en {number, plural, one {# día} other {# días}}",
   "time_remaining.hours": "Remata en {number, plural, one {# hora} other {# horas}}",
   "time_remaining.minutes": "Remata en {number, plural, one {# minuto} other {# minutos}}",
@@ -897,12 +853,26 @@
   "upload_button.label": "Engadir imaxes, un vídeo ou ficheiro de audio",
   "upload_error.limit": "Límite máximo do ficheiro a subir excedido.",
   "upload_error.poll": "Non se poden subir ficheiros nas enquisas.",
+  "upload_form.audio_description": "Describir para persoas con problemas auditivos",
+  "upload_form.description": "Describir para persoas cegas ou con problemas visuais",
   "upload_form.drag_and_drop.instructions": "Preme en Espazo ou Enter para escoller un anexo multimedia. Ao arrastrar usa as teclas de frecha para mover o anexo en todas direccións.Preme Espazo ou Enter outra vez para soltalo na súa nova posición, ou preme Escape para desbotar.",
   "upload_form.drag_and_drop.on_drag_cancel": "Cancelouse o movemento. O anexo {item} soltouse.",
   "upload_form.drag_and_drop.on_drag_end": "Soltouse o anexo multimedia {item}.",
   "upload_form.drag_and_drop.on_drag_over": "Moveuse o anexo multimedia {item}.",
   "upload_form.drag_and_drop.on_drag_start": "Escolleuse o anexo multimedia {item}.",
   "upload_form.edit": "Editar",
+  "upload_form.thumbnail": "Cambiar a miniatura",
+  "upload_form.video_description": "Describe para persoas con problemas visuais ou auditivos",
+  "upload_modal.analyzing_picture": "Estase a analizar a imaxe…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.applying": "Aplicando…",
+  "upload_modal.choose_image": "Elixir imaxe",
+  "upload_modal.description_placeholder": "Un raposo veloz brinca sobre o can preguiceiro",
+  "upload_modal.detect_text": "Detectar texto na imaxe",
+  "upload_modal.edit_media": "Editar multimedia",
+  "upload_modal.hint": "Preme ou arrastra o círculo na vista previa para escoller o punto focal que sempre estará á vista en todas as miniaturas.",
+  "upload_modal.preparing_ocr": "Preparando OCR…",
+  "upload_modal.preview_label": "Vista previa ({ratio})",
   "upload_progress.label": "Estase a subir...",
   "upload_progress.processing": "Procesando…",
   "username.taken": "O identificador xa está pillado. Inténtao con outro",
@@ -912,12 +882,8 @@
   "video.expand": "Estender o vídeo",
   "video.fullscreen": "Pantalla completa",
   "video.hide": "Agochar vídeo",
-  "video.mute": "Acalar",
+  "video.mute": "Silenciar son",
   "video.pause": "Deter",
   "video.play": "Reproducir",
-  "video.skip_backward": "Retroceder",
-  "video.skip_forward": "Avanzar",
-  "video.unmute": "Non silenciar",
-  "video.volume_down": "Baixar volume",
-  "video.volume_up": "Subir volume"
+  "video.unmute": "Permitir son"
 }
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index d1a2c014a5..527b0a5ac5 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "עריכת פרופיל",
   "account.enable_notifications": "שלח לי התראות כש@{name} מפרסם",
   "account.endorse": "קדם את החשבון בפרופיל",
-  "account.featured": "מומלץ",
-  "account.featured.hashtags": "תגיות",
-  "account.featured.posts": "הודעות",
   "account.featured_tags.last_status_at": "חצרוץ אחרון בתאריך {date}",
   "account.featured_tags.last_status_never": "אין חצרוצים",
+  "account.featured_tags.title": "התגיות המועדפות של {name}",
   "account.follow": "לעקוב",
   "account.follow_back": "לעקוב בחזרה",
   "account.followers": "עוקבים",
@@ -66,9 +64,8 @@
   "account.show_reblogs": "הצג הדהודים מאת @{name}",
   "account.statuses_counter": "{count, plural, one {הודעה אחת} two {הודעותיים} many {{counter} הודעות} other {{counter} הודעות}}",
   "account.unblock": "להסיר חסימה ל- @{name}",
-  "account.unblock_domain": "הסרת החסימה של קהילת {domain}",
-  "account.unblock_domain_short": "הסרת חסימה",
-  "account.unblock_short": "הסרת חסימה",
+  "account.unblock_domain": "הסירי את החסימה של קהילת {domain}",
+  "account.unblock_short": "הסר חסימה",
   "account.unendorse": "אל תקדם בפרופיל",
   "account.unfollow": "הפסקת מעקב",
   "account.unmute": "הפסקת השתקת @{name}",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "אירעה שגיאה בלתי צפויה.",
   "alert.unexpected.title": "אופס!",
   "alt_text_badge.title": "כיתוב חלופי",
-  "alt_text_modal.add_alt_text": "הוספת מלל חלופי",
-  "alt_text_modal.add_text_from_image": "הוספת מלל מתוך התמונה",
-  "alt_text_modal.cancel": "ביטול",
-  "alt_text_modal.change_thumbnail": "החלפת התמונה הממוזערת",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "תיאור התוכן לכבדי שמיעה…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "תיאור התוכן לפגועי ראיה…",
-  "alt_text_modal.done": "סיום",
   "announcement.announcement": "הכרזה",
-  "annual_report.summary.archetype.booster": "ההד-וניסט(ית)",
-  "annual_report.summary.archetype.lurker": "השורץ.ת השקט.ה",
-  "annual_report.summary.archetype.oracle": "כבוד הרב.ה",
-  "annual_report.summary.archetype.pollster": "הסקרן.ית",
-  "annual_report.summary.archetype.replier": "הפרפר.ית החברתי.ת",
-  "annual_report.summary.followers.followers": "עוקבים",
-  "annual_report.summary.followers.total": "{count} בסך הכל",
-  "annual_report.summary.here_it_is": "והנה סיכום {year} שלך:",
-  "annual_report.summary.highlighted_post.by_favourites": "התות הכי מחובב",
-  "annual_report.summary.highlighted_post.by_reblogs": "התות הכי מהודהד",
-  "annual_report.summary.highlighted_post.by_replies": "התות עם מספר התשובות הגבוה ביותר",
-  "annual_report.summary.highlighted_post.possessive": "של {name}",
-  "annual_report.summary.most_used_app.most_used_app": "היישומון שהכי בשימוש",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "התג בשימוש הרב ביותר",
-  "annual_report.summary.most_used_hashtag.none": "אף אחד",
-  "annual_report.summary.new_posts.new_posts": "הודעות חדשות",
-  "annual_report.summary.percentile.text": "<topLabel>ממקם אותך באחוזון </topLabel><percentage></percentage><bottomLabel>של משמשי {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "לא נגלה לברני.",
-  "annual_report.summary.thanks": "תודה על היותך חלק ממסטודון!",
   "attachments_list.unprocessed": "(לא מעובד)",
   "audio.hide": "השתק",
   "block_modal.remote_users_caveat": "אנו נבקש מהשרת {domain} לכבד את החלטתך. עם זאת, ציות למוסכמות איננו מובטח כיוון ששרתים מסויימים עשויים לטפל בחסימות בצורה אחרת. הודעות פומביות עדיין יהיו גלויות לעיני משתמשים שאינם מחוברים.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "העמוד המבוקש לא נמצא. האם ה־URL נכון?",
   "bundle_column_error.routing.title": "שגיאה 404: הדף לא נמצא",
   "bundle_modal_error.close": "לסגור",
-  "bundle_modal_error.message": "משהו השתבש בעת טעינת המסך הזה.",
+  "bundle_modal_error.message": "משהו השתבש בעת טעינת הרכיב הזה.",
   "bundle_modal_error.retry": "לנסות שוב",
   "closed_registrations.other_server_instructions": "מכיוון שמסטודון היא רשת מבוזרת, ניתן ליצור חשבון על שרת נוסף ועדיין לקיים קשר עם משתמשים בשרת זה.",
   "closed_registrations_modal.description": "יצירת חשבון על שרת {domain} איננה אפשרית כרגע, אבל זכרו שאינכן זקוקות לחשבון על {domain} כדי להשתמש במסטודון.",
@@ -150,16 +121,13 @@
   "column.blocks": "משתמשים חסומים",
   "column.bookmarks": "סימניות",
   "column.community": "פיד שרת מקומי",
-  "column.create_list": "יצירת רשימה",
   "column.direct": "הודעות פרטיות",
   "column.directory": "עיין בפרופילים",
   "column.domain_blocks": "קהילות (שמות מתחם) מוסתרות",
-  "column.edit_list": "עריכת רשימה",
   "column.favourites": "חיבובים",
   "column.firehose": "פידים עדכניים",
   "column.follow_requests": "בקשות מעקב",
   "column.home": "פיד הבית",
-  "column.list_members": "ניהול חברי הרשימה",
   "column.lists": "רשימות",
   "column.mutes": "משתמשים בהשתקה",
   "column.notifications": "התראות",
@@ -172,7 +140,6 @@
   "column_header.pin": "הצמדה",
   "column_header.show_settings": "הצגת העדפות",
   "column_header.unpin": "שחרור הצמדה",
-  "column_search.cancel": "ביטול",
   "column_subheading.settings": "הגדרות",
   "community.column_settings.local_only": "מקומי בלבד",
   "community.column_settings.media_only": "מדיה בלבד",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "משך הסקר",
   "compose_form.poll.multiple": "בחירה מרובה",
   "compose_form.poll.option_placeholder": "אפשרות {number}",
-  "compose_form.poll.single": "בחירה יחידה",
+  "compose_form.poll.single": "נא לבחור",
   "compose_form.poll.switch_to_multiple": "אפשרו בחירה מרובה בסקר",
   "compose_form.poll.switch_to_single": "אפשרו בחירה בודדת בסקר",
   "compose_form.poll.type": "סוג משאל",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "עריכה",
   "confirmations.edit.message": "עריכה תדרוס את ההודעה שכבר התחלת לכתוב. האם להמשיך?",
   "confirmations.edit.title": "לבצע החלפת תוכן?",
-  "confirmations.follow_to_list.confirm": "עקיבה והוספה לרשימה",
-  "confirmations.follow_to_list.message": "כדי להכניס את {name} לרשימה, ראשית יש לעקוב אחריהם.",
-  "confirmations.follow_to_list.title": "לעקוב אחר המשתמש.ת?",
   "confirmations.logout.confirm": "התנתקות",
   "confirmations.logout.message": "האם אתם בטוחים שאתם רוצים להתנתק?",
   "confirmations.logout.title": "להתנתק?",
-  "confirmations.missing_alt_text.confirm": "הוספת מלל חלופי",
-  "confirmations.missing_alt_text.message": "ההודעה שלך כוללת קבצים גרפיים ללא תיאור (מלל חלופי). הוספת תיאורים עוזרת להנגיש את התוכן ליותר אנשים.",
-  "confirmations.missing_alt_text.secondary": "לפרסם בכל זאת",
-  "confirmations.missing_alt_text.title": "להוסיף מלל חלופי?",
   "confirmations.mute.confirm": "להשתיק",
   "confirmations.redraft.confirm": "מחיקה ועריכה מחדש",
   "confirmations.redraft.message": "למחוק ולהתחיל טיוטה חדשה? חיבובים והדהודים יאבדו, ותגובות להודעה המקורית ישארו יתומות.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "חשבונך {disabledAccount} אינו פעיל כרגע.",
   "dismissable_banner.community_timeline": "אלו הם החצרוצים הציבוריים האחרונים מהמשתמשים על שרת {domain}.",
   "dismissable_banner.dismiss": "בטל",
-  "dismissable_banner.explore_links": "ידיעות החדשות האלו שותפו במיוחד על ידי משתמשים ששרת זה רואה ברשת המבוזרת כרגע. ידיעות עדכניות יותר ששותפו על ידי יותר אנשים שונים מדורגות גבוה יותר.",
+  "dismissable_banner.explore_links": "אלו הקישורים האחרונים ששותפו על ידי משתמשים ששרת זה רואה ברשת המבוזרת כרגע.",
   "dismissable_banner.explore_statuses": "אלו הודעות משרת זה ואחרים ברשת המבוזרת שצוברות חשיפה היום. הודעות חדשות יותר עם יותר הדהודים וחיבובים מדורגות גבוה יותר.",
-  "dismissable_banner.explore_tags": "התגיות האלו, משרת זה ואחרים ברשת המבוזרת, צוברות חשיפה כעת. תגיות בשימוש נרחב יותר מדורגות גבוה יותר.",
-  "dismissable_banner.public_timeline": "אלו ההודעות האחרונות שהתקבלו מהמשתמשיםות ברשת המבוזרת, אשר משתמשיםות ב־{domain} עוקביםות אחריהםן.",
+  "dismissable_banner.explore_tags": "התגיות האלו, משרת זה ואחרים ברשת המבוזרת, צוברות חשיפה כעת.",
+  "dismissable_banner.public_timeline": "אלו ההודעות האחרונות שהתקבלו מהמשתמשים שנעקבים על ידי משתמשים מ־{domain}.",
   "domain_block_modal.block": "חסימת שרת",
   "domain_block_modal.block_account_instead": "לחסום את @{name} במקום שרת שלם",
   "domain_block_modal.they_can_interact_with_old_posts": "משתמשים משרת זה יכולים להתייחס להודעותיך הישנות.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "תוצאות חיפוש",
   "emoji_button.symbols": "סמלים",
   "emoji_button.travel": "טיולים ואתרים",
-  "empty_column.account_featured": "הרשימה ריקה",
   "empty_column.account_hides_collections": "המשתמש.ת בחר.ה להסתיר מידע זה",
   "empty_column.account_suspended": "חשבון מושעה",
   "empty_column.account_timeline": "אין עדיין אף הודעה!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "אין כלום בתגית הזאת עדיין.",
   "empty_column.home": "פיד הבית ריק ! אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר משתמשים/ות אחרים/ות. {suggestions}",
   "empty_column.list": "אין עדיין פריטים ברשימה. כאשר חברים ברשימה הזאת יפרסמו הודעות חדשות, הן יופיעו פה.",
+  "empty_column.lists": "אין לך שום רשימות עדיין. לכשיהיו, הן תופענה כאן.",
   "empty_column.mutes": "עוד לא השתקת שום משתמש.",
   "empty_column.notification_requests": "בום! אין פה כלום. כשיווצרו עוד התראות, הן יופיעו כאן על בסיס ההעדפות שלך.",
   "empty_column.notifications": "אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "נסה/י להסיר אותם ולרענן את הדף. אם זה לא עוזר, אולי אפשר עדיין להשתמש במסטודון דרך דפדפן אחר או באמצעות אפליקציה ילידית.",
   "errors.unexpected_crash.copy_stacktrace": "להעתיק את הקוד ללוח הכתיבה",
   "errors.unexpected_crash.report_issue": "דווח על בעיה",
+  "explore.search_results": "תוצאות חיפוש",
   "explore.suggested_follows": "אנשים",
   "explore.title": "סיור",
   "explore.trending_links": "חדשות",
@@ -373,16 +334,13 @@
   "footer.about": "אודות",
   "footer.directory": "ספריית פרופילים",
   "footer.get_app": "להתקנת היישומון",
+  "footer.invite": "להזמין אנשים",
   "footer.keyboard_shortcuts": "קיצורי מקלדת",
   "footer.privacy_policy": "מדיניות פרטיות",
   "footer.source_code": "צפיה בקוד המקור",
   "footer.status": "מצב",
-  "footer.terms_of_service": "תנאי השירות",
   "generic.saved": "נשמר",
   "getting_started.heading": "בואו נתחיל",
-  "hashtag.admin_moderation": "פתיחת ממשק פיקוח דיון עבור #{name}",
-  "hashtag.browse": "קריאת הודעות תחת #{hashtag}",
-  "hashtag.browse_from_account": "קריאת הודעות מאת @{name} תחת #{hashtag}",
   "hashtag.column_header.tag_mode.all": "ו- {additional}",
   "hashtag.column_header.tag_mode.any": "או {additional}",
   "hashtag.column_header.tag_mode.none": "ללא {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {הודעה אחת} two {הודעותיים} many {{counter} הודעות} other {{counter} הודעות}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {הודעה אחת} two {הודעותיים} many {{counter} הודעות} other {{counter} הודעות}} היום",
   "hashtag.follow": "לעקוב אחרי תגית",
-  "hashtag.mute": "להשתיק את #{hashtag}",
   "hashtag.unfollow": "להפסיק לעקוב אחרי תגית",
   "hashtags.and_other": "…{count, plural,other {ועוד #}}",
   "hints.profiles.followers_may_be_missing": "יתכן כי עוקבים של פרופיל זה חסרים.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "להתעלם מהתראות מא.נשים שאינם עוקביך?",
   "ignore_notifications_modal.not_following_title": "להתעלם מהתראות מא.נשים שאינם נעקביך?",
   "ignore_notifications_modal.private_mentions_title": "להתעלם מהתראות מאיזכורים פרטיים?",
-  "info_button.label": "עזרה",
-  "info_button.what_is_alt_text": "<h1>מהו כיתוב חלופי?</h1> <p>כיתוב חלופי משמש תיאור מילולי של תמונות לסובלים ממגבלות ראיה, חיבורי רשת איטיים, או אלו הצריכים הקשר יותר מפורט לתוכן המולטימדיה המצורף.</p> <p>ניתן לשפר את הנגישות והבנת התוכן לכולם ע\"י כתיבת תיאור ברור, תמציתי ונטול פניות.</p> <ul> <li>כיסוי היסודות החשובים</li> <li>סיכום המלל שבתמונות</li> <li>שימוש במבנה משפטים רגיל</li> <li>יש להמנע מחזרה על מידע</li> <li>אם העזרים הויזואליים הם דיאגרמות או מפות, התמקדו במגמות וממצאים מרכזיים.</li> </ul>",
-  "interaction_modal.action.favourite": "כדי להמשיך, עליך לחבב מחשבונך.",
-  "interaction_modal.action.follow": "כדי להמשיך, עליך לעקוב מחשבונך.",
-  "interaction_modal.action.reblog": "כדי להמשיך, עליך להדהד מחשבונך.",
-  "interaction_modal.action.reply": "כדי להמשיך, עליך לענות מחשבונך.",
-  "interaction_modal.action.vote": "כדי להמשיך, עליך להצביע מחשבונך.",
-  "interaction_modal.go": "המשך",
-  "interaction_modal.no_account_yet": "אין לך עדיין חשבון?",
+  "interaction_modal.description.favourite": "עם חשבון מסטודון, ניתן לחבב את ההודעה כדי לומר למחבר/ת שהערכת את תוכנו או כדי לשמור אותו לקריאה בעתיד.",
+  "interaction_modal.description.follow": "עם חשבון מסטודון, ניתן לעקוב אחרי {name} כדי לקבל את הפוסטים שלו/ה בפיד הבית.",
+  "interaction_modal.description.reblog": "עם חשבון מסטודון, ניתן להדהד את החצרוץ ולשתף עם עוקבים.",
+  "interaction_modal.description.reply": "עם חשבון מסטודון, ניתן לענות לחצרוץ.",
+  "interaction_modal.login.action": "קח אותי לדף הבית",
+  "interaction_modal.login.prompt": "שם השרת שלך, למשל mastodon.social",
+  "interaction_modal.no_account_yet": "עדיין לא במסטודון?",
   "interaction_modal.on_another_server": "על שרת אחר",
   "interaction_modal.on_this_server": "על שרת זה",
+  "interaction_modal.sign_in": "אינך מחובר.ת לשרת זה. היכן מתאכסן החשבון שלך?",
+  "interaction_modal.sign_in_hint": "רמז: זהו האתר שבו נרשמת. אם שכחת, יש לך הודעת \"ברוכים הבאים\" בתיבת הדוא\"ל. ניתן גם לכתוב את שם המשתמש המלא (למשל @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "חיבוב החצרוץ של {name}",
   "interaction_modal.title.follow": "לעקוב אחרי {name}",
   "interaction_modal.title.reblog": "להדהד את החצרוץ של {name}",
   "interaction_modal.title.reply": "תשובה לחצרוץ של {name}",
-  "interaction_modal.title.vote": "הצביעו בסקר של {name}",
-  "interaction_modal.username_prompt": "למשל {example}",
   "intervals.full.days": "{number, plural, one {# יום} other {# ימים}}",
   "intervals.full.hours": "{number, plural, one {# שעה} other {# שעות}}",
   "intervals.full.minutes": "{number, plural, one {# דקה} other {# דקות}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "הצגת/הסתרת טקסט מוסתר מאחורי אזהרת תוכן",
   "keyboard_shortcuts.toggle_sensitivity": "הצגת/הסתרת מדיה",
   "keyboard_shortcuts.toot": "להתחיל חיצרוץ חדש",
-  "keyboard_shortcuts.translate": "לתרגם הודעה",
   "keyboard_shortcuts.unfocus": "לצאת מתיבת חיבור/חיפוש",
   "keyboard_shortcuts.up": "לנוע במעלה הרשימה",
   "lightbox.close": "סגירה",
@@ -490,32 +444,20 @@
   "link_preview.author": "מאת {name}",
   "link_preview.more_from_author": "עוד מאת {name}",
   "link_preview.shares": "{count, plural, one {הודעה אחת} two {הודעותיים} many {{counter} הודעות} other {{counter} הודעות}}",
-  "lists.add_member": "הוספה",
-  "lists.add_to_list": "הוספה לרשימה",
-  "lists.add_to_lists": "הוספת {name} לרשימות",
-  "lists.create": "יצירה",
-  "lists.create_a_list_to_organize": "יצירת רשימה חדשה לארגון פיד הבית שלך",
-  "lists.create_list": "יצירת רשימה",
+  "lists.account.add": "הוסף לרשימה",
+  "lists.account.remove": "הסר מרשימה",
   "lists.delete": "מחיקת רשימה",
-  "lists.done": "בוצע",
   "lists.edit": "עריכת רשימה",
-  "lists.exclusive": "הסתרת החברים בפיד הבית",
-  "lists.exclusive_hint": "אם שם כלשהו ברשימה זו, נסתיר אותי בפיד הבית כדי למנוע כפילות.",
-  "lists.find_users_to_add": "חיפוש משתמשים להוספה",
-  "lists.list_members": "פירוט חברי הרשימה",
-  "lists.list_members_count": "{count, plural, one {חבר רשימה אחד} other {# חברי רשימה}}",
-  "lists.list_name": "שם הרשימה",
-  "lists.new_list_name": "שם רשימה חדשה",
-  "lists.no_lists_yet": "אין רשימות עדיין.",
-  "lists.no_members_yet": "עוד אין חברים ברשימה.",
-  "lists.no_results_found": "לא נמצאו תוצאות.",
-  "lists.remove_member": "הסרה",
+  "lists.edit.submit": "שנה/י כותרת",
+  "lists.exclusive": "להסתיר את ההודעות האלו מפיד הבית",
+  "lists.new.create": "הוספת רשימה",
+  "lists.new.title_placeholder": "כותרת הרשימה החדשה",
   "lists.replies_policy.followed": "משתמשים שאני עוקב אחריהם",
   "lists.replies_policy.list": "משתמשים שברשימה",
   "lists.replies_policy.none": "אף אחד",
-  "lists.save": "שמירה",
-  "lists.search": "חיפוש",
-  "lists.show_replies_to": "לכלול תשובות מחברי הרשימה אל",
+  "lists.replies_policy.title": "הצג תגובות ל:",
+  "lists.search": "חיפוש בין אנשים שאני עוקב\\ת אחריהם",
+  "lists.subheading": "הרשימות שלך",
   "load_pending": "{count, plural, one {# פריט חדש} other {# פריטים חדשים}}",
   "loading_indicator.label": "בטעינה…",
   "media_gallery.hide": "להסתיר",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} דיווח.ה על {target}",
   "notification.admin.sign_up": "{name} נרשמו",
   "notification.admin.sign_up.name_and_others": "{name} ועוד {count, plural,one {אחד אחר}other {# אחרים}} נרשמו",
-  "notification.annual_report.message": "ה- #סיכומודון שלך לשנת {year} מחכה! גלו את רגעי השיא והזכרונות ממסטודון!",
-  "notification.annual_report.view": "לצפייה ב- #סיכומודון",
   "notification.favourite": "הודעתך חובבה על ידי {name}",
   "notification.favourite.name_and_others_with_link": "{name} ועוד <a>{count, plural,one {אחד נוסף}other {# נוספים}}</a> חיבבו את הודעתך",
-  "notification.favourite_pm": "{name} חיבב.ה איזכור שלך בהודעה פרטית",
-  "notification.favourite_pm.name_and_others_with_link": "{name} ועוד <a>{count, plural,one {אחד נוסף}other {# נוספים}}</a> חיבבו הודעתך הפרטית",
   "notification.follow": "{name} במעקב אחרייך",
   "notification.follow.name_and_others": "{name} ועוד <a>{count, plural,one {מישהו} other {# אחרים}}</a> החלו לעקוב אחריך",
   "notification.follow_request": "{name} ביקשו לעקוב אחריך",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "לאפשר נוטיפיקציות מסך",
   "notifications_permission_banner.how_to_control": "כדי לקבל התראות גם כאשר מסטודון סגור יש לאפשר התראות מסך. ניתן לשלוט בדיוק איזה סוג של אינטראקציות יביא להתראות מסך דרך כפתור ה- {icon} מרגע שהן מאופשרות.",
   "notifications_permission_banner.title": "לעולם אל תחמיץ דבר",
-  "onboarding.follows.back": "בחזרה",
-  "onboarding.follows.done": "בוצע",
+  "onboarding.action.back": "חזרה",
+  "onboarding.actions.back": "חזרה",
+  "onboarding.actions.go_to_explore": "הצגת מגמות",
+  "onboarding.actions.go_to_home": "מעבר לזרם הודעות הנעקבים",
+  "onboarding.compose.template": "שלום #מסטודון!",
   "onboarding.follows.empty": "למצער, תוצאות לחיפושך אינן בנמצא. ניתן להשתמש בחיפוש או בדף החקירות לשם מציאת אנשים ולעקבם. אפשר גם לנסות שוב אחר כך.",
-  "onboarding.follows.search": "חיפוש",
-  "onboarding.follows.title": "כדי להתחיל, יש לעקוב אחרי אנשים",
+  "onboarding.follows.lead": "אתם אוצרים את הזרם הבייתי שלכם. ככל שתעקבו אחרי יותר אנשים, הוא יהיה עשיר ופעיל יותר. הנה כמה פרופילים להתחיל בהם - תמיד ניתן להפסיק מעקב אחריהם בהמשך!",
+  "onboarding.follows.title": "פופולארי על מסטודון",
   "onboarding.profile.discoverable": "כלול את הפרופיל שלי בעמודת התגליות",
   "onboarding.profile.discoverable_hint": "כשתבחרו להכלל ב\"תגליות\" על מסטודון, ההודעות שלכם עשויות להופיע בתוצאות חיפוש ועמודות \"נושאים חמים\", והפרופיל יוצע לאחרים עם תחומי עניין משותפים לכם.",
   "onboarding.profile.display_name": "שם להצגה",
   "onboarding.profile.display_name_hint": "שמך המלא או כינוי הכיף שלך…",
+  "onboarding.profile.lead": "תמיד ניתן להשלים זאת אחר כך בהגדרות, שם יש אפילו עוד אפשרויות להתאמה אישית.",
   "onboarding.profile.note": "אודות",
   "onboarding.profile.note_hint": "ניתן @לאזכר משתמשים אחרים או #תגיות…",
   "onboarding.profile.save_and_continue": "לשמור ולהמשיך",
   "onboarding.profile.title": "הגדרת פרופיל",
   "onboarding.profile.upload_avatar": "העלאת תמונת פרופיל",
   "onboarding.profile.upload_header": "העלאת כותרת פרופיל",
+  "onboarding.share.lead": "כדאי להודיע לחברים היכן למצוא אותך במסטודון!",
+  "onboarding.share.message": "אני {username} ברשת #מסטודון! בואו לעקוב אחרי בכתובת {url}",
+  "onboarding.share.next_steps": "לאיפה להמשיך מכאן:",
+  "onboarding.share.title": "לשתף פרופיל",
+  "onboarding.start.lead": "חשבונך החדש במסטודון מוכן. הנה דרכים להפיק ממנו את המירב:",
+  "onboarding.start.skip": "לדלג הלאה?",
+  "onboarding.start.title": "הצלחת!",
+  "onboarding.steps.follow_people.body": "זרם הבית שלכם יאצר על ידיכם. בואו נמלא אותו באנשים מעניינים.",
+  "onboarding.steps.follow_people.title": "להוסיף למעקב {count, plural,one {חשבון אחד}other {# חשבונות}}",
+  "onboarding.steps.publish_status.body": "ברכו לשלום את העולם.",
+  "onboarding.steps.publish_status.title": "כתבו את הפוסט הראשון שלכם",
+  "onboarding.steps.setup_profile.body": "כדאי להשלים את הפרופיל כדי לעודד אחרים ליצירת קשר.",
+  "onboarding.steps.setup_profile.title": "התאמה אישית של הפרופיל",
+  "onboarding.steps.share_profile.body": "ספרו לחברים איך למצוא אתכם במסטודון!",
+  "onboarding.steps.share_profile.title": "לשתף פרופיל",
+  "onboarding.tips.2fa": "<strong>הידעת?</strong> ניתן לאבטח את החשבון ע\"י הקמת אימות דו-שלבי במסך מאפייני החשבון. השיטה תעבוד עם כל יישומון תואם TOTP על המכשיר שלך, ללא צורך במספר טלפון!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>הידעת?</strong> כיוון שמסטודון פועל ברשת מבוזרת, חלק מהפרופילים שתתקלו בהם פועלים משרתים אחרים משרת הבית שלכם. ניתן להיות איתם בקשר בצורה זהה לכל חשבון אחר! שם השרת שלהם הוא החלק השני של שם המשתמש שלהם!",
+  "onboarding.tips.migration": "<strong>הידעת?</strong> אם תחליטו כי {domain} איננו שרת שמתאים לכם בעתיד, ניתן לעבור לשרת אחר מבלי לאבד עוקבים. תוכלו אפילו להקים שרת משלכן!",
+  "onboarding.tips.verification": "<strong>הידעת?</strong> ניתן לאשרר את החשבון ע\"י קישור הפרופיל אל האתר שלכם ומהאתר חזרה לפרופיל. לא נדרשים תשלומים ומסמכים!",
   "password_confirmation.exceeds_maxlength": "הסיסמה בשדה אימות הסיסמה ארוכה מאורך הסיסמה המירבי",
   "password_confirmation.mismatching": "אימות סיסמה אינו תואם לסיסמה",
   "picture_in_picture.restore": "החזירי למקומו",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "הסרת סקר",
   "privacy.change": "שינוי פרטיות ההודעה",
   "privacy.direct.long": "כל המוזכרים בהודעה",
-  "privacy.direct.short": "אזכור פרטי",
+  "privacy.direct.short": "א.נשים מסוימים",
   "privacy.private.long": "לעוקביך בלבד",
   "privacy.private.short": "עוקבים",
   "privacy.public.long": "כל הגולשים, מחוברים למסטודון או לא",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "מדיניות פרטיות",
   "recommended": "מומלץ",
   "refresh": "רענון",
-  "regeneration_indicator.please_stand_by": "נא להמתין.",
-  "regeneration_indicator.preparing_your_home_feed": "מכין לך את פיד הבית…",
+  "regeneration_indicator.label": "טוען…",
+  "regeneration_indicator.sublabel": "פיד הבית שלך בהכנה!",
   "relative_time.days": "{number} ימים",
   "relative_time.full.days": "לפני {number, plural, one {# יום} other {# ימים}}",
   "relative_time.full.hours": "לפני {number, plural, one {# שעה} other {# שעות}}",
@@ -801,11 +762,10 @@
   "search_results.accounts": "פרופילים",
   "search_results.all": "כל התוצאות",
   "search_results.hashtags": "תגיות",
-  "search_results.no_results": "אין תוצאות.",
-  "search_results.no_search_yet": "נסו לחפש אחר הודעות, פרופילי משתמשים או תגיות.",
+  "search_results.nothing_found": "לא נמצא דבר עבור תנאי חיפוש אלה",
   "search_results.see_all": "הראה הכל",
   "search_results.statuses": "הודעות",
-  "search_results.title": "חיפוש אחר \"{q}\"",
+  "search_results.title": "חפש את: {q}",
   "server_banner.about_active_users": "משתמשים פעילים בשרת ב־30 הימים האחרונים (משתמשים פעילים חודשיים)",
   "server_banner.active_users": "משתמשים פעילים",
   "server_banner.administered_by": "מנוהל ע\"י:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "עוד לא הידהדו את ההודעה הזו. כאשר זה יקרה, ההדהודים יופיעו כאן.",
   "status.redraft": "מחיקה ועריכה מחדש",
   "status.remove_bookmark": "הסרת סימניה",
-  "status.remove_favourite": "להסיר מרשימת המועדפים",
   "status.replied_in_thread": "תגובה לשרשור",
   "status.replied_to": "בתגובה לחשבון {name}",
   "status.reply": "תגובה",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "שינוי רישום שפה עבור {target}",
   "tabs_bar.home": "פיד הבית",
   "tabs_bar.notifications": "התראות",
-  "terms_of_service.effective_as_of": "בתוקף החל מתאריך {date}",
-  "terms_of_service.title": "תנאי השירות",
-  "terms_of_service.upcoming_changes_on": "שינויים עתידיים שיחולו ביום {date}",
   "time_remaining.days": "נותרו {number, plural, one {# יום} other {# ימים}}",
   "time_remaining.hours": "נותרו {number, plural, one {# שעה} other {# שעות}}",
   "time_remaining.minutes": "נותרו {number, plural, one {# דקה} other {# דקות}}",
@@ -897,12 +853,26 @@
   "upload_button.label": "הוספת מדיה",
   "upload_error.limit": "קובץ להעלאה חורג מנפח מותר.",
   "upload_error.poll": "לא ניתן להעלות קובץ עם סקר.",
+  "upload_form.audio_description": "תאר/י עבור לקויי שמיעה",
+  "upload_form.description": "תיאור לכבדי ראיה",
   "upload_form.drag_and_drop.instructions": "כדי לבחור קובץ מוצמד, יש ללחוץ על מקש רווח או אנטר. בעת הגרירה, השתמשו במקשי החיצים כדי להזיז את הקובץ המוצמד בכל כיוון. לחצו רווח או אנטר בשנית כדי לעזוב את הקובץ במקומו החדש, או לחצו אסקייפ לביטול.",
   "upload_form.drag_and_drop.on_drag_cancel": "הגרירה בוטלה. קובץ המדיה {item} נעזב.",
   "upload_form.drag_and_drop.on_drag_end": "קובץ המדיה {item} נעזב.",
   "upload_form.drag_and_drop.on_drag_over": "קובץ המדיה {item} הוזז.",
   "upload_form.drag_and_drop.on_drag_start": "קובץ המדיה {item} נבחר.",
   "upload_form.edit": "עריכה",
+  "upload_form.thumbnail": "שנה/י תמונה ממוזערת",
+  "upload_form.video_description": "תאר/י עבור לקויי שמיעה ולקויי ראייה",
+  "upload_modal.analyzing_picture": "מנתח תמונה…",
+  "upload_modal.apply": "החל",
+  "upload_modal.applying": "מחיל…",
+  "upload_modal.choose_image": "בחר/י תמונה",
+  "upload_modal.description_placeholder": "עטלף אבק נס דרך מזגן שהתפוצץ כי חם",
+  "upload_modal.detect_text": "זהה טקסט מתמונה",
+  "upload_modal.edit_media": "עריכת מדיה",
+  "upload_modal.hint": "הקליקי או גררי את המעגל על גבי התצוגה המקדימה על מנת לבחור בנקודת המוקד שתראה תמיד בכל התמונות הממוזערות.",
+  "upload_modal.preparing_ocr": "מכין OCR…",
+  "upload_modal.preview_label": "תצוגה ({ratio})",
   "upload_progress.label": "עולה...",
   "upload_progress.processing": "מעבד…",
   "username.taken": "שם המשתמש הזה תפוס. נסו אחר",
@@ -912,12 +882,8 @@
   "video.expand": "להרחיב וידאו",
   "video.fullscreen": "מסך מלא",
   "video.hide": "להסתיר וידאו",
-  "video.mute": "השתקה",
+  "video.mute": "השתקת צליל",
   "video.pause": "השהיה",
   "video.play": "ניגון",
-  "video.skip_backward": "דילוג אחורה",
-  "video.skip_forward": "דילוג קדימה",
-  "video.unmute": "ביטול השתקה",
-  "video.volume_down": "הנמכת עוצמת השמע",
-  "video.volume_up": "הגברת עוצמת שמע"
+  "video.unmute": "החזרת צליל"
 }
diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json
index a3eec9544c..4a513c1c07 100644
--- a/app/javascript/mastodon/locales/hi.json
+++ b/app/javascript/mastodon/locales/hi.json
@@ -28,6 +28,7 @@
   "account.endorse": "प्रोफ़ाइल पर दिखाए",
   "account.featured_tags.last_status_at": "{date} का अंतिम पोस्ट",
   "account.featured_tags.last_status_never": "कोई पोस्ट नहीं है",
+  "account.featured_tags.title": "{name} के चुनिंदा हैशटैग",
   "account.follow": "फॉलो करें",
   "account.follow_back": "फॉलो करें",
   "account.followers": "फॉलोवर",
@@ -102,6 +103,7 @@
   "bundle_column_error.routing.body": "अनुरोधित पेज पाया नहीं जा सका। क्या आप सुनिश्चित हैं कि एड्रेस बार में URL सही है?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "बंद",
+  "bundle_modal_error.message": "इस कॉम्पोनेन्ट को लोड करते वक्त कुछ गलत हो गया",
   "bundle_modal_error.retry": "दुबारा कोशिश करें",
   "closed_registrations.other_server_instructions": "जब से मास्टोडन विकेंद्रीकरण हुआ है, आप दुसरे सर्वर पर एक अकाउंट बना सकते हैं और अब भी इसके साथ उपयोग कर सकते हैं",
   "closed_registrations_modal.description": "{domain} पर अकाउंट बनाना अभी संभव नहीं है, किन्तु कृपया ध्यान दें कि आपको मास्टोडन का प्रयोग करने के लिए {domain} पर एक अकाउंट का पूर्ण रूप से नहीं आवश्यकता हैं",
@@ -149,6 +151,7 @@
   "compose_form.poll.duration": "चुनाव की अवधि",
   "compose_form.poll.multiple": "बहुविकल्पी",
   "compose_form.poll.option_placeholder": "विकल्प {number}",
+  "compose_form.poll.single": "कोई एक चुनें",
   "compose_form.poll.switch_to_multiple": "कई विकल्पों की अनुमति देने के लिए पोल बदलें",
   "compose_form.poll.switch_to_single": "एक ही विकल्प के लिए अनुमति देने के लिए पोल बदलें",
   "compose_form.poll.type": "स्टाइल",
@@ -193,6 +196,9 @@
   "disabled_account_banner.text": "आपका अकाउंट {disabledAccount} अभी डिसेबल्ड है",
   "dismissable_banner.community_timeline": "ये उन लोगों की सबसे रीसेंट पब्लिक पोस्ट हैं जिनके अकाउंट इनके {domain} द्वारा होस्ट किए गए हैं",
   "dismissable_banner.dismiss": "डिसमिस",
+  "dismissable_banner.explore_links": "इन समाचारों के बारे में लोगों द्वारा इस पर और डेसेंट्रलीसेड नेटवर्क के अन्य सर्वरों पर अभी बात की जा रही है।",
+  "dismissable_banner.explore_tags": "ये हैशटैग अभी इस पर और डेसेंट्रलीसेड नेटवर्क के अन्य सर्वरों पर लोगों के बीच कर्षण प्राप्त कर रहे हैं।",
+  "dismissable_banner.public_timeline": "यह ताजा सार्वजनिक पोस्ट है जिसका सामाजिक वेब {domain} के लोगो द्वारा अनुसरण हो रहा हैं।",
   "domain_block_modal.block": "सर्वर ब्लॉक करें",
   "domain_block_modal.block_account_instead": "इसकी जगह यह @{name} रखें",
   "domain_block_modal.they_can_interact_with_old_posts": "इस सर्वर की लोग आपकी पूरानी पोस्ट्स का अनुसरण किया जा sakta है।",
@@ -232,6 +238,7 @@
   "empty_column.hashtag": "यह हैशटैग अभी तक खाली है।",
   "empty_column.home": "आपकी मुख्य कालक्रम अभी खली है. अन्य उपयोगकर्ताओं से मिलने के लिए और अपनी गतिविधियां शुरू करने के लिए या तो {public} पर जाएं या खोज का उपयोग करें।",
   "empty_column.list": "यह सूची अभी खाली है. जब इसके सदस्य कोई अभिव्यक्ति देंगे, तो वो यहां दिखाई देंगी.",
+  "empty_column.lists": "आपके पास अभी तक कोई सूची नहीं है। जब आप एक बनाते हैं, तो यह यहां दिखाई देगा।",
   "empty_column.mutes": "आपने अभी तक किसी भी उपयोगकर्ता को म्यूट नहीं किया है।",
   "empty_column.notifications": "आपके पास अभी तक कोई सूचना नहीं है। बातचीत शुरू करने के लिए दूसरों के साथ बातचीत करें।",
   "empty_column.public": "यहां कुछ नहीं है! सार्वजनिक रूप से कुछ लिखें, या इसे भरने के लिए अन्य सर्वर से उपयोगकर्ताओं का मैन्युअल रूप से अनुसरण करें",
@@ -241,6 +248,7 @@
   "error.unexpected_crash.next_steps_addons": "उन्हें अक्षम करने और पृष्ठ को ताज़ा करने का प्रयास करें। यदि वह मदद नहीं करता है, तो आप अभी भी मास्टोडन का उपयोग किसी भिन्न ब्राउज़र या नेटिव ऐप के माध्यम से कर सकते हैं।",
   "errors.unexpected_crash.copy_stacktrace": "स्टैकट्रेस को क्लिपबोर्ड पर कॉपी करें",
   "errors.unexpected_crash.report_issue": "समस्या सूचित करें",
+  "explore.search_results": "सर्च रिजल्ट्स",
   "explore.suggested_follows": "लोग",
   "explore.title": "एक्स्प्लोर",
   "explore.trending_links": "समाचार",
@@ -273,6 +281,7 @@
   "footer.about": "अबाउट",
   "footer.directory": "प्रोफाइल्स डायरेक्टरी",
   "footer.get_app": "अप्प प्राप्त करें",
+  "footer.invite": "लोगों को आमंत्रित करें",
   "footer.keyboard_shortcuts": "कीबोर्ड शॉर्टकट",
   "footer.privacy_policy": "प्राइवेसी पालिसी",
   "footer.source_code": "सोर्स कोड देखें",
@@ -294,8 +303,13 @@
   "home.column_settings.show_replies": "जवाबों को दिखाए",
   "home.hide_announcements": "घोषणाएँ छिपाएँ",
   "home.show_announcements": "घोषणाएं दिखाएं",
+  "interaction_modal.description.follow": "मास्टोडन पर एक अकाउंट के साथ, आप अपने होम फीड में उनकी पोस्ट प्राप्त करने के लिए {name} का अनुसरण कर सकते हैं",
+  "interaction_modal.description.reblog": "मास्टोडन पर एक अकाउंट के साथ, आप इस पोस्ट को अपने फोल्लोवेर्स के साथ साझा करने के लिए बढ़ा सकते हैं।",
+  "interaction_modal.description.reply": "मास्टोडन पर एक अकाउंट के साथ, आप इस पोस्ट का जवाब दे सकते हैं।",
+  "interaction_modal.no_account_yet": "मस्टाडोन पर नहीं है?",
   "interaction_modal.on_another_server": "एक अलग सर्वर पर",
   "interaction_modal.on_this_server": "इस सर्वर पे",
+  "interaction_modal.sign_in": "आप इस सर्वर पर प्रवेशित नहिं है | आपका खाता कहां साजा है?",
   "interaction_modal.title.favourite": "मनपसंद {name} की पोस्ट",
   "interaction_modal.title.follow": "फॉलो {name}",
   "interaction_modal.title.reblog": "बूस्ट {name} की पोस्ट",
@@ -339,11 +353,18 @@
   "lightbox.previous": "पिछला",
   "limited_account_hint.action": "फिर भी प्रोफाइल दिखाओ",
   "limited_account_hint.title": "यह प्रोफ़ाइल {domain} के मॉडरेटर द्वारा छिपाई गई है.",
+  "lists.account.add": "ऐड तो लिस्ट",
+  "lists.account.remove": "सूची से निकालें",
   "lists.delete": "सूची हटाएँ",
   "lists.edit": "सूची संपादित करें",
+  "lists.edit.submit": "शीर्षक बदलें",
+  "lists.new.create": "सूची जोड़ें",
+  "lists.new.title_placeholder": "नये सूची का शीर्षक",
   "lists.replies_policy.followed": "अन्य फोल्लोवेद यूजर",
   "lists.replies_policy.list": "सूची के सदस्य",
   "lists.replies_policy.none": "कोई नहीं",
+  "lists.replies_policy.title": "इसके जवाब दिखाएं:",
+  "lists.subheading": "आपकी सूचियाँ",
   "media_gallery.hide": "छिपाएं",
   "navigation_bar.about": "विवरण",
   "navigation_bar.blocks": "ब्लॉक्ड यूज़र्स",
@@ -391,9 +412,30 @@
   "notifications.filter.polls": "चुनाव परिणाम",
   "notifications.grant_permission": "अनुमति दें",
   "notifications.group": "{count} सूचनाएँ",
+  "onboarding.action.back": "मुझे वापस ले जाओ",
+  "onboarding.actions.back": "मुझे वापस ले जाओ",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "नमस्कार #मस्टोडोन",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
   "onboarding.profile.discoverable": "अपना प्रोफाइल खोजने योग्य बनाएं",
   "onboarding.profile.discoverable_hint": "जब आप मॅस्टोडॉन पर डिस्कवरेबिलिटी चुनते हैं तो आपके पोस्ट ट्रेंडिंग और सर्च में दिख सकते हैं और आपका प्रोफाइल आपके ही जैसे अकाउंट्स को सुझाया जा सकता है।",
   "onboarding.profile.display_name": "प्रदर्शित नाम",
+  "onboarding.share.message": "मैं {username} मॅस्टोडॉन पर हूं! मुझे यहां {url} फॉलो करें",
+  "onboarding.share.next_steps": "आगे कि संभवित विधि",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "आपने कर लिया!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "अपनी पहली पोस्ट बनाएं",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
+  "onboarding.tips.2fa": "<strong>क्या आप जानते है?</strong> आप अपना खाता दो-कदमवाले प्रमाणीकरण से अपने खाते की सेटिंग से सुरक्षित कर सकते हे!",
   "poll.closed": "बंद कर दिया",
   "poll.refresh": "रीफ्रेश करें",
   "poll.vote": "वोट",
@@ -402,6 +444,7 @@
   "privacy.public.short": "सार्वजनिक",
   "recommended": "अनुशंसित",
   "refresh": "रीफ्रेश करें",
+  "regeneration_indicator.label": "लोड हो रहा है...",
   "relative_time.days": "{number}दिन",
   "relative_time.full.days": "{number, plural, one {# दिन} other {# दिन}} पहले",
   "relative_time.full.hours": "{number, plural,one {# घंटा} other {# घंटे}} पहले",
@@ -478,7 +521,12 @@
   "tabs_bar.home": "होम",
   "tabs_bar.notifications": "सूचनाएँ",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
   "upload_form.edit": "संशोधन करें",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
+  "upload_modal.apply": "लागू करें",
+  "upload_modal.edit_media": "मीडिया में संशोधन करें",
   "upload_progress.label": "अपलोडिंग...",
   "video.download": "फाइल डाउनलोड करें"
 }
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index 38807b28b2..26c527f2fe 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -28,6 +28,7 @@
   "account.endorse": "Istakni na profilu",
   "account.featured_tags.last_status_at": "Zadnji post {date}",
   "account.featured_tags.last_status_never": "Nema postova",
+  "account.featured_tags.title": "Istaknuti hashtagovi {name}",
   "account.follow": "Prati",
   "account.follow_back": "Slijedi natrag",
   "account.followers": "Pratitelji",
@@ -93,6 +94,7 @@
   "bundle_column_error.routing.body": "Traženu stranicu nije moguće pronaći. Jeste li sigurni da je URL u adresnoj traci točan?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Zatvori",
+  "bundle_modal_error.message": "Nešto je pošlo po zlu tijekom učitavanja ove komponente.",
   "bundle_modal_error.retry": "Pokušajte ponovno",
   "closed_registrations_modal.find_another_server": "Nađi drugi server",
   "column.about": "O aplikaciji",
@@ -167,6 +169,8 @@
   "directory.recently_active": "Nedavno aktivni",
   "disabled_account_banner.account_settings": "Postavke računa",
   "disabled_account_banner.text": "Tvoj račun {disabledAccount} je trenutno onemogućen.",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "Evo kako će izgledati:",
   "emoji_button.activity": "Aktivnost",
@@ -195,6 +199,7 @@
   "empty_column.hashtag": "Još ne postoji ništa s ovim hashtagom.",
   "empty_column.home": "Vaša početna vremenska crta je prazna! Posjetite {public} ili koristite tražilicu kako biste započeli i upoznali druge korisnike.",
   "empty_column.list": "Na ovoj listi još nema ničega. Kada članovi ove liste objave nove tootove, oni će se pojaviti ovdje.",
+  "empty_column.lists": "Nemaš niti jednu listu. Kada je kreiraš, prikazat će se ovdje.",
   "empty_column.mutes": "Niste utišali nijednog korisnika.",
   "empty_column.notifications": "Još nemate obavijesti. Komunicirajte s drugima kako biste započeli razgovor.",
   "empty_column.public": "Ovdje nema ništa! Napišite nešto javno ili ručno pratite korisnike s drugi poslužitelja da biste ovo popunili",
@@ -204,6 +209,7 @@
   "error.unexpected_crash.next_steps_addons": "Pokušaj ih onemogućiti i osvježiti stranicu. Ako to ne pomogne, i dalje ćeš biti u mogućnosti koristiti Mastodon preko nekog drugog preglednika ili izvornog app-a.",
   "errors.unexpected_crash.copy_stacktrace": "Kopiraj stacktrace u međuspremnik",
   "errors.unexpected_crash.report_issue": "Prijavi problem",
+  "explore.search_results": "Rezultati pretrage",
   "explore.suggested_follows": "Ljudi",
   "explore.title": "Pretraži",
   "explore.trending_links": "Novosti",
@@ -225,6 +231,7 @@
   "footer.about": "O aplikaciji",
   "footer.directory": "Direktorij profila",
   "footer.get_app": "Preuzmi aplikaciju",
+  "footer.invite": "Pozovi ljude",
   "footer.keyboard_shortcuts": "Tipkovni prečaci",
   "footer.privacy_policy": "Pravila o zaštiti privatnosti",
   "footer.source_code": "Prikaz izvornog koda",
@@ -247,6 +254,8 @@
   "home.hide_announcements": "Sakrij najave",
   "home.pending_critical_update.title": "Dostupno je kritično sigurnosno ažuriranje!",
   "home.show_announcements": "Prikaži najave",
+  "interaction_modal.login.action": "Odvedi me kući",
+  "interaction_modal.no_account_yet": "Nisi na Mastodonu?",
   "interaction_modal.on_this_server": "Na ovom serveru",
   "interaction_modal.title.follow": "Prati {name}",
   "intervals.full.days": "{number, plural, one {# dan} other {# dana}}",
@@ -289,11 +298,18 @@
   "lightbox.next": "Sljedeće",
   "lightbox.previous": "Prethodno",
   "limited_account_hint.action": "Svejedno prikaži profil",
+  "lists.account.add": "Dodaj na listu",
+  "lists.account.remove": "Ukloni s liste",
   "lists.delete": "Izbriši listu",
   "lists.edit": "Uredi listu",
+  "lists.edit.submit": "Promijeni naslov",
+  "lists.new.create": "Dodaj listu",
+  "lists.new.title_placeholder": "Naziv nove liste",
   "lists.replies_policy.followed": "Bilo koji praćeni korisnik",
   "lists.replies_policy.list": "Članovi liste",
   "lists.replies_policy.none": "Nitko",
+  "lists.search": "Traži među praćenim ljudima",
+  "lists.subheading": "Vaše liste",
   "navigation_bar.about": "O aplikaciji",
   "navigation_bar.advanced_interface": "Otvori u naprednom web sučelju",
   "navigation_bar.blocks": "Blokirani korisnici",
@@ -341,7 +357,21 @@
   "notifications.grant_permission": "Odobri dopuštenje.",
   "notifications.group": "{count} obavijesti",
   "notifications.mark_as_read": "Označi sve obavijesti kao pročitane",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
   "onboarding.profile.upload_avatar": "Učitaj sliku profila",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Napiši svoj prvi post",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "poll.closed": "Završeno",
   "poll.refresh": "Osvježi",
   "poll.reveal": "Vidi rezultate",
@@ -357,6 +387,8 @@
   "privacy_policy.title": "Pravila o zaštiti privatnosti",
   "recommended": "Preporučeno",
   "refresh": "Osvježi",
+  "regeneration_indicator.label": "Učitavanje…",
+  "regeneration_indicator.sublabel": "Priprema se Vaša početna stranica!",
   "relative_time.days": "{number}d",
   "relative_time.full.just_now": "upravo sad",
   "relative_time.just_now": "sada",
@@ -406,8 +438,10 @@
   "search_popout.user": "korisnik",
   "search_results.accounts": "Profili",
   "search_results.all": "Sve",
+  "search_results.nothing_found": "Nije pronađeno ništa za te ključne riječi",
   "search_results.see_all": "Prikaži sve",
   "search_results.statuses": "Toots",
+  "search_results.title": "Traži {q}",
   "server_banner.about_active_users": "Popis aktivnih korisnika prošli mjesec",
   "server_banner.active_users": "aktivni korisnici",
   "server_banner.administered_by": "Administrator je:",
@@ -473,7 +507,20 @@
   "upload_button.label": "Dodajte slike, video ili audio datoteku",
   "upload_error.limit": "Ograničenje prijenosa datoteka je prekoračeno.",
   "upload_error.poll": "Prijenos datoteka nije dopušten kod anketa.",
+  "upload_form.audio_description": "Opišite za ljude sa slabim sluhom",
+  "upload_form.description": "Opišite za ljude sa slabim vidom",
   "upload_form.edit": "Uredi",
+  "upload_form.thumbnail": "Promijeni pretpregled",
+  "upload_form.video_description": "Opišite za ljude sa slabim sluhom ili vidom",
+  "upload_modal.analyzing_picture": "Analiza slike…",
+  "upload_modal.apply": "Primijeni",
+  "upload_modal.applying": "Primjenjivanje…",
+  "upload_modal.choose_image": "Odaberite sliku",
+  "upload_modal.description_placeholder": "Gojazni đačić s biciklom drži hmelj i finu vatu u džepu nošnje",
+  "upload_modal.detect_text": "Detektiraj tekst sa slike",
+  "upload_modal.edit_media": "Uređivanje medija",
+  "upload_modal.preparing_ocr": "Pripremam OCR…",
+  "upload_modal.preview_label": "Pretpregled ({ratio})",
   "upload_progress.label": "Prenošenje...",
   "upload_progress.processing": "Obrada…",
   "video.close": "Zatvori video",
@@ -482,6 +529,8 @@
   "video.expand": "Proširi video",
   "video.fullscreen": "Cijeli zaslon",
   "video.hide": "Sakrij video",
+  "video.mute": "Utišaj zvuk",
   "video.pause": "Pauziraj",
-  "video.play": "Reproduciraj"
+  "video.play": "Reproduciraj",
+  "video.unmute": "Uključi zvuk"
 }
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index b311dffa72..40fc3b905c 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Profil szerkesztése",
   "account.enable_notifications": "Figyelmeztessen, ha @{name} bejegyzést tesz közzé",
   "account.endorse": "Kiemelés a profilodon",
-  "account.featured": "Kiemelt",
-  "account.featured.hashtags": "Hashtagek",
-  "account.featured.posts": "Bejegyzések",
   "account.featured_tags.last_status_at": "Legutolsó bejegyzés ideje: {date}",
   "account.featured_tags.last_status_never": "Nincs bejegyzés",
+  "account.featured_tags.title": "{name} kiemelt hashtagjei",
   "account.follow": "Követés",
   "account.follow_back": "Viszontkövetés",
   "account.followers": "Követő",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} bejegyzés} other {{counter} bejegyzés}}",
   "account.unblock": "@{name} letiltásának feloldása",
   "account.unblock_domain": "{domain} domain tiltásának feloldása",
-  "account.unblock_domain_short": "Tiltás feloldása",
   "account.unblock_short": "Tiltás feloldása",
   "account.unendorse": "Ne jelenjen meg a profilodon",
   "account.unfollow": "Követés megszüntetése",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Váratlan hiba történt.",
   "alert.unexpected.title": "Hoppá!",
   "alt_text_badge.title": "Helyettesítő szöveg",
-  "alt_text_modal.add_alt_text": "Helyettesítő szöveg hozzáadása",
-  "alt_text_modal.add_text_from_image": "Szöveg hozzáadása a képből",
-  "alt_text_modal.cancel": "Mégse",
-  "alt_text_modal.change_thumbnail": "Bélyegkép megváltoztatása",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Írd le a hallássérültek számára…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Írd le a látássérültek számára…",
-  "alt_text_modal.done": "Kész",
   "announcement.announcement": "Közlemény",
-  "annual_report.summary.archetype.booster": "A cool-vadász",
-  "annual_report.summary.archetype.lurker": "A settenkedő",
-  "annual_report.summary.archetype.oracle": "Az orákulum",
-  "annual_report.summary.archetype.pollster": "A közvélemény-kutató",
-  "annual_report.summary.archetype.replier": "A társasági pillangó",
-  "annual_report.summary.followers.followers": "követő",
-  "annual_report.summary.followers.total": "{count} összesen",
-  "annual_report.summary.here_it_is": "Itt a {year}. év értékelése:",
-  "annual_report.summary.highlighted_post.by_favourites": "legkedvencebb bejegyzés",
-  "annual_report.summary.highlighted_post.by_reblogs": "legtöbbet megtolt bejegyzés",
-  "annual_report.summary.highlighted_post.by_replies": "bejegyzés a legtöbb válasszal",
-  "annual_report.summary.highlighted_post.possessive": "{name} fióktól",
-  "annual_report.summary.most_used_app.most_used_app": "legtöbbet használt app",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "legtöbbet használt hashtag",
-  "annual_report.summary.most_used_hashtag.none": "Nincs",
-  "annual_report.summary.new_posts.new_posts": "új bejegyzés",
-  "annual_report.summary.percentile.text": "<topLabel>Ezzel a csúcs</topLabel><percentage></percentage><bottomLabel>{domain} felhasználó között vagy.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Nem mondjuk el Bernie-nek.",
-  "annual_report.summary.thanks": "Kösz, hogy a Mastodon része vagy!",
   "attachments_list.unprocessed": "(feldolgozatlan)",
   "audio.hide": "Hang elrejtése",
   "block_modal.remote_users_caveat": "Arra kérjük a {domain} kiszolgálót, hogy tartsa tiszteletben a döntésedet. Ugyanakkor az együttműködés nem garantált, mivel néhány kiszolgáló másképp kezelheti a letiltásokat. A nyilvános bejegyzések a be nem jelentkezett felhasználók számára továbbra is látszódhatnak.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "A kért oldal nem található. Biztos, hogy a címsávban lévő webcím helyes?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Bezárás",
-  "bundle_modal_error.message": "Hiba történt a képernyő betöltésekor.",
+  "bundle_modal_error.message": "Hiba történt a komponens betöltésekor.",
   "bundle_modal_error.retry": "Próbáld újra",
   "closed_registrations.other_server_instructions": "Mivel a Mastdon decentralizált, létrehozhatsz egy fiókot egy másik kiszolgálón és mégis kapcsolódhatsz ehhez.",
   "closed_registrations_modal.description": "Fiók létrehozása a {domain} kiszolgálón jelenleg nem lehetséges, de jó, ha tudod, hogy nem szükséges fiókkal rendelkezni pont a {domain} kiszolgálón, hogy használhasd a Mastodont.",
@@ -150,16 +121,13 @@
   "column.blocks": "Letiltott felhasználók",
   "column.bookmarks": "Könyvjelzők",
   "column.community": "Helyi idővonal",
-  "column.create_list": "Lista létrehozása",
   "column.direct": "Személyes említések",
   "column.directory": "Profilok böngészése",
   "column.domain_blocks": "Letiltott domainek",
-  "column.edit_list": "Lista módosítása",
   "column.favourites": "Kedvencek",
   "column.firehose": "Hírfolyamok",
   "column.follow_requests": "Követési kérések",
   "column.home": "Kezdőlap",
-  "column.list_members": "Listatagok kezelése",
   "column.lists": "Listák",
   "column.mutes": "Némított felhasználók",
   "column.notifications": "Értesítések",
@@ -172,7 +140,6 @@
   "column_header.pin": "Kitűzés",
   "column_header.show_settings": "Beállítások megjelenítése",
   "column_header.unpin": "Kitűzés eltávolítása",
-  "column_search.cancel": "Mégse",
   "column_subheading.settings": "Beállítások",
   "community.column_settings.local_only": "Csak helyi",
   "community.column_settings.media_only": "Csak média",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Szavazás időtartama",
   "compose_form.poll.multiple": "Több lehetőség",
   "compose_form.poll.option_placeholder": "Válasz {number}",
-  "compose_form.poll.single": "Feleletválasztós",
+  "compose_form.poll.single": "Egyetlen válasz",
   "compose_form.poll.switch_to_multiple": "Szavazás megváltoztatása több választásosra",
   "compose_form.poll.switch_to_single": "Szavazás megváltoztatása egyetlen választásosra",
   "compose_form.poll.type": "Stílus",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Szerkesztés",
   "confirmations.edit.message": "Ha most szerkeszted, ez felülírja a most szerkesztés alatt álló üzenetet. Mégis ezt szeretnéd?",
   "confirmations.edit.title": "Felülírod a bejegyzést?",
-  "confirmations.follow_to_list.confirm": "Követés, és hozzáadás a listához",
-  "confirmations.follow_to_list.message": "Követned kell {name} felhasználót, hogy hozzáadhasd a listához.",
-  "confirmations.follow_to_list.title": "Felhasználó követése?",
   "confirmations.logout.confirm": "Kijelentkezés",
   "confirmations.logout.message": "Biztos, hogy kijelentkezel?",
   "confirmations.logout.title": "Kijelentkezel?",
-  "confirmations.missing_alt_text.confirm": "Helyettesítő szöveg hozzáadása",
-  "confirmations.missing_alt_text.message": "A bejegyzés helyettesítő szöveg nélküli médiát tartalmaz. A leírások hozzáadása segít a tartalom akadálymentesebbé tételében.",
-  "confirmations.missing_alt_text.secondary": "Közzététel mindenképpen",
-  "confirmations.missing_alt_text.title": "Helyettesítő szöveg hozzáadása?",
   "confirmations.mute.confirm": "Némítás",
   "confirmations.redraft.confirm": "Törlés és újraírás",
   "confirmations.redraft.message": "Biztos, hogy ezt a bejegyzést szeretnéd törölni és újraírni? Minden megtolást és kedvencnek jelölést elvesztesz, az eredetire adott válaszok pedig elárvulnak.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "A(z) {disabledAccount} fiókod jelenleg le van tiltva.",
   "dismissable_banner.community_timeline": "Ezek a legfrissebb nyilvános bejegyzések, amelyeket a(z) {domain} kiszolgáló fiókjait használó emberek tették közzé.",
   "dismissable_banner.dismiss": "Elvetés",
-  "dismissable_banner.explore_links": "Ma ezeket a híreket osztják meg a legtöbbször a födiverzumban. Azok az újabb hírek, melyeket különbözőbb emberek osztanak meg, előrébb vannak sorolva.",
-  "dismissable_banner.explore_statuses": "Ma ezek a bejegyzések hódítanak teret a födiverzumban. Azok az újabb bejegyzések, melyek több megtolással és kedvencnek jelöléssel rendelkeznek, előrébb vannak sorolva.",
-  "dismissable_banner.explore_tags": "Ma ezek a hashtagek hódítanak teret a födiverzumban. Azok a hashtagek, melyeket különbözőbb emberek használnak, előrébb vannak sorolva.",
-  "dismissable_banner.public_timeline": "Ezek a legfrissebb nyilvános bejegyzések a födiverzumban lévő emberektől, akiket a(z) {domain} felhasználói követnek.",
+  "dismissable_banner.explore_links": "Jelenleg ezekről a hírekről beszélgetnek az ezen és a központosítás nélküli hálózat többi kiszolgálóján lévő emberek.",
+  "dismissable_banner.explore_statuses": "Ezek jelenleg népszerűvé váló bejegyzések a háló különböző szegleteiből. Az újabb vagy több megtolással rendelkező bejegyzéseket, illetve a kedvencnek jelöléssel rendelkezőeket rangsoroljuk előrébb.",
+  "dismissable_banner.explore_tags": "Jelenleg ezek a hashtagek hódítanak teret a közösségi weben. Azokat a hashtageket, amelyeket több különböző ember használ, magasabbra rangsorolják.",
+  "dismissable_banner.public_timeline": "Ezek a legfrissebb nyilvános bejegyzések a közösségi weben, amelyeket {domain} domain felhasználói követnek.",
   "domain_block_modal.block": "Kiszolgáló letiltása",
   "domain_block_modal.block_account_instead": "Helyette @{name} letiltása",
   "domain_block_modal.they_can_interact_with_old_posts": "Az ezen a kiszolgálón lévő emberek interaktálhatnak a régi bejegyzéseiddel.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Keresési találatok",
   "emoji_button.symbols": "Szimbólumok",
   "emoji_button.travel": "Utazás és helyek",
-  "empty_column.account_featured": "Ez a lista üres",
   "empty_column.account_hides_collections": "Ez a felhasználó úgy döntött, hogy nem teszi elérhetővé ezt az információt.",
   "empty_column.account_suspended": "Fiók felfüggesztve",
   "empty_column.account_timeline": "Itt nincs bejegyzés!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Jelenleg nem található semmi ezzel a hashtaggel.",
   "empty_column.home": "A saját idővonalad üres! Kövess további embereket ennek megtöltéséhez.",
   "empty_column.list": "A lista jelenleg üres. Ha a listatagok bejegyzést tesznek közzé, itt fog megjelenni.",
+  "empty_column.lists": "Még nincs egyetlen listád sem. Ha létrehozol egyet, itt fog megjelenni.",
   "empty_column.mutes": "Még egy felhasználót sem némítottál le.",
   "empty_column.notification_requests": "Minden tiszta! Itt nincs semmi. Ha új értesítéseket kapsz, azok itt jelennek meg a beállításoknak megfelelően.",
   "empty_column.notifications": "Jelenleg még nincsenek értesítéseid. Ha mások kapcsolatba lépnek veled, ezek itt lesznek láthatóak.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Próbáld letiltani őket és frissíteni az oldalt. Ha ez nem segít, egy másik böngészőn vagy appon keresztül még mindig használhatod a Mastodont.",
   "errors.unexpected_crash.copy_stacktrace": "Veremkiíratás vágólapra másolása",
   "errors.unexpected_crash.report_issue": "Probléma jelentése",
+  "explore.search_results": "Keresési találatok",
   "explore.suggested_follows": "Emberek",
   "explore.title": "Felfedezés",
   "explore.trending_links": "Hírek",
@@ -373,16 +334,13 @@
   "footer.about": "Névjegy",
   "footer.directory": "Profiltár",
   "footer.get_app": "Alkalmazás beszerzése",
+  "footer.invite": "Emberek meghívása",
   "footer.keyboard_shortcuts": "Gyorsbillentyűk",
   "footer.privacy_policy": "Adatvédelmi szabályzat",
   "footer.source_code": "Forráskód megtekintése",
   "footer.status": "Állapot",
-  "footer.terms_of_service": "Felhasználási feltételek",
   "generic.saved": "Elmentve",
   "getting_started.heading": "Első lépések",
-  "hashtag.admin_moderation": "Moderációs felület megnyitása a következőhöz: #{name}",
-  "hashtag.browse": "Bejegyzések ebben: #{hashtag}",
-  "hashtag.browse_from_account": "@{name} bejegyzéseinek tallózása ebben: #{hashtag}",
   "hashtag.column_header.tag_mode.all": "és {additional}",
   "hashtag.column_header.tag_mode.any": "vagy {additional}",
   "hashtag.column_header.tag_mode.none": "{additional} nélkül",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} bejegyzés} other {{counter} bejegyzés}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} bejegyzés} other {{counter} bejegyzés}} ma",
   "hashtag.follow": "Hashtag követése",
-  "hashtag.mute": "#{hashtag} némítása",
   "hashtag.unfollow": "Hashtag követésének megszüntetése",
   "hashtags.and_other": "…és {count, plural, other {# további}}",
   "hints.profiles.followers_may_be_missing": "A profil követői lehet, hogy hiányoznak.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Nem követőktől érkező értesítések figyelmen kívül hagyása?",
   "ignore_notifications_modal.not_following_title": "Nem követettektől érkező értesítések figyelmen kívül hagyása?",
   "ignore_notifications_modal.private_mentions_title": "Figyelmen kívül hagyod a kéretlen privát említéseket?",
-  "info_button.label": "Súgó",
-  "info_button.what_is_alt_text": "<h1>Mi az alternatív szöveg?</h1> <p>Az alternatív szöveg képleírást biztosít a látássérültek, az alacsony sávszélességű kapcsolatokkal rendelkezők, illetve a bővebb kontextust keresők számára.</p> <p>Az egyértelmű, tömör és objektív alternatív szöveg megírásával mindenki számára akadálymentesebb és könnyebben érthető lesz.</p> <ul> <li>Rögzítsd a fontos elemeket.</li> <li>Foglald össze szövegesen a képeket.</li> <li>Használj szabályos mondatszerkezetet.</li> <li>Kerüld a felesleges információkat.</li> <li>Összetett vizuális ábrákon (például diagramokon vagy térképeken) összpontosíts a trendekre és a legfontosabb megállapításokra.</li> </ul>",
-  "interaction_modal.action.favourite": "A folytatáshoz a fiókodból kell kedvencnek jelölnöd.",
-  "interaction_modal.action.follow": "A folytatáshoz a fiókodból kell követned.",
-  "interaction_modal.action.reblog": "A folytatáshoz a fiókodból kell megosztanod.",
-  "interaction_modal.action.reply": "A folytatáshoz a fiókodból kell válaszolnod rá.",
-  "interaction_modal.action.vote": "A folytatáshoz a fiókodból kell szavaznod.",
-  "interaction_modal.go": "Ugrás",
-  "interaction_modal.no_account_yet": "Még nincs fiókod?",
+  "interaction_modal.description.favourite": "Egy Mastodon fiókkal kedvencnek jelölheted ezt a bejegyzést, tudatva a szerzővel, hogy értékeled és elteszed későbbre.",
+  "interaction_modal.description.follow": "Egy Mastodon-fiókkal követheted {name} fiókját, hogy lásd a bejegyzéseit a kezdőlapodon.",
+  "interaction_modal.description.reblog": "Egy Mastodon fiókkal megtolhatod ezt a bejegyzést, hogy megoszd a saját követőiddel.",
+  "interaction_modal.description.reply": "Egy Mastodon fiókkal válaszolhatsz erre a bejegyzésre.",
+  "interaction_modal.login.action": "Vigyen haza",
+  "interaction_modal.login.prompt": "A saját kiszolgálód tartományneve, pl.: mastodon.social",
+  "interaction_modal.no_account_yet": "Nem vagy Mastodonon?",
   "interaction_modal.on_another_server": "Másik kiszolgálón",
   "interaction_modal.on_this_server": "Ezen a kiszolgálón",
+  "interaction_modal.sign_in": "Nem vagy bejelentkezve ezen a kiszolgálón. Hol van a fiókod hosztolva?",
+  "interaction_modal.sign_in_hint": "Tip: Ez az a weboldal, ahol regisztráltál. Ha nem emlékszel, keresd meg az üdvözlőlevelet a bejövő leveleid között. Beírhatod a teljes felhasználónevedet is! (pl.: @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "{name} bejegyzésének megjelölése kedvencként",
   "interaction_modal.title.follow": "{name} követése",
   "interaction_modal.title.reblog": "{name} bejegyzésének megtolása",
   "interaction_modal.title.reply": "Válasz {name} bejegyzésére",
-  "interaction_modal.title.vote": "Szavazz {name} szavazásában",
-  "interaction_modal.username_prompt": "Például {example}",
   "intervals.full.days": "{number, plural, one {# nap} other {# nap}}",
   "intervals.full.hours": "{number, plural, one {# óra} other {# óra}}",
   "intervals.full.minutes": "{number, plural, one {# perc} other {# perc}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Tartalmi figyelmeztetéssel ellátott szöveg megjelenítése/elrejtése",
   "keyboard_shortcuts.toggle_sensitivity": "Média megjelenítése/elrejtése",
   "keyboard_shortcuts.toot": "Új bejegyzés írása",
-  "keyboard_shortcuts.translate": "Bejegyzés lefordítása",
   "keyboard_shortcuts.unfocus": "Szerkesztés/keresés fókuszból való kivétele",
   "keyboard_shortcuts.up": "Mozgás felfelé a listában",
   "lightbox.close": "Bezárás",
@@ -490,32 +444,20 @@
   "link_preview.author": "{name} szerint",
   "link_preview.more_from_author": "Több tőle: {name}",
   "link_preview.shares": "{count, plural, one {{counter} bejegyzés} other {{counter} bejegyzés}}",
-  "lists.add_member": "Hozzáadás",
-  "lists.add_to_list": "Hozzáadás a listához",
-  "lists.add_to_lists": "{name} hozzáadása a listához",
-  "lists.create": "Létrehozás",
-  "lists.create_a_list_to_organize": "Új lista létrehozása a kezdőlapod szervezéséhez",
-  "lists.create_list": "Lista létrehozása",
+  "lists.account.add": "Hozzáadás a listához",
+  "lists.account.remove": "Eltávolítás a listából",
   "lists.delete": "Lista törlése",
-  "lists.done": "Kész",
   "lists.edit": "Lista szerkesztése",
-  "lists.exclusive": "Tagok elrejtése a kezdőlapon",
-  "lists.exclusive_hint": "Ha valaki szerepel ezen a listán, el lesz rejtve a kezdőlapod hírfolyamán, hogy ne lásd kétszer a bejegyzéseit.",
-  "lists.find_users_to_add": "Hozzáadandó felhasználók keresése",
-  "lists.list_members": "Tagok listázása",
-  "lists.list_members_count": "{count, plural, one {# tag} other {# tag}}",
-  "lists.list_name": "Lista neve",
-  "lists.new_list_name": "Új lista neve",
-  "lists.no_lists_yet": "Nincsenek még listák.",
-  "lists.no_members_yet": "Nincsenek még tagok.",
-  "lists.no_results_found": "Nincs találat.",
-  "lists.remove_member": "Eltávolítás",
+  "lists.edit.submit": "Cím megváltoztatása",
+  "lists.exclusive": "Ezen bejegyzések elrejtése a kezdőoldalról",
+  "lists.new.create": "Lista hozzáadása",
+  "lists.new.title_placeholder": "Új lista címe",
   "lists.replies_policy.followed": "Bármely követett felhasználó",
   "lists.replies_policy.list": "A lista tagjai",
   "lists.replies_policy.none": "Senki",
-  "lists.save": "Mentés",
-  "lists.search": "Keresés",
-  "lists.show_replies_to": "Listatagok válaszainak hozzávétele",
+  "lists.replies_policy.title": "Nekik mutassuk a válaszokat:",
+  "lists.search": "Keresés a követett emberek között",
+  "lists.subheading": "Saját listák",
   "load_pending": "{count, plural, one {# új elem} other {# új elem}}",
   "loading_indicator.label": "Betöltés…",
   "media_gallery.hide": "Elrejtés",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} jelentette: {target}",
   "notification.admin.sign_up": "{name} regisztrált",
   "notification.admin.sign_up.name_and_others": "{name} és {count, plural, one {# másik} other {# másik}} regisztrált",
-  "notification.annual_report.message": "Vár a {year}. év #Wrapstodon jelentése! Fedd fel az éved jelentős eseményeit és emlékezetes pillanatait a Mastodonon!",
-  "notification.annual_report.view": "#Wrapstodon Megtekintése",
   "notification.favourite": "{name} kedvencnek jelölte a bejegyzésedet",
   "notification.favourite.name_and_others_with_link": "{name} és <a>{count, plural, one {# másik} other {# másik}}</a> kedvencnek jelölte a bejegyzésedet",
-  "notification.favourite_pm": "{name} kedvelte a privát említésedet",
-  "notification.favourite_pm.name_and_others_with_link": "{name} és <a>{count, plural, one {# másik} other {# másik}}</a> kedvencnek jelölte a privát említésedet",
   "notification.follow": "{name} követ téged",
   "notification.follow.name_and_others": "{name} és <a>{count, plural, one {# másik} other {# másik}}</a> követni kezdett",
   "notification.follow_request": "{name} követni szeretne téged",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Asztali értesítések engedélyezése",
   "notifications_permission_banner.how_to_control": "Ahhoz, hogy értesítéseket kapj akkor, amikor a Mastodon nincs megnyitva, engedélyezd az asztali értesítéseket. Pontosan be tudod állítani, hogy milyen interakciókról értesülj a fenti {icon} gombon keresztül, ha egyszer már engedélyezted őket.",
   "notifications_permission_banner.title": "Soha ne mulassz el semmit",
-  "onboarding.follows.back": "Vissza",
-  "onboarding.follows.done": "Kész",
+  "onboarding.action.back": "Vissza",
+  "onboarding.actions.back": "Vissza",
+  "onboarding.actions.go_to_explore": "Felkapottak megtekintése",
+  "onboarding.actions.go_to_home": "Ugrás a kezdőlapod hírfolyamára",
+  "onboarding.compose.template": "Üdvözlet, #Mastodon!",
   "onboarding.follows.empty": "Sajnos jelenleg nem jeleníthető meg eredmény. Kipróbálhatod a keresést vagy böngészheted a felfedező oldalon a követni kívánt személyeket, vagy próbáld meg később.",
-  "onboarding.follows.search": "Keresés",
-  "onboarding.follows.title": "A kezdéshez kezdj el embereket követni",
+  "onboarding.follows.lead": "A kezdőlapod a Mastodon használatának elsődleges módja. Minél több embert követsz, annál aktívabbak és érdekesebbek lesznek a dolgok. Az induláshoz itt van néhány javaslat:",
+  "onboarding.follows.title": "Szabd személyre a kezdőlapodat",
   "onboarding.profile.discoverable": "Saját profil beállítása felfedezhetőként",
   "onboarding.profile.discoverable_hint": "A Mastodonon a felfedezhetőség választása esetén a saját bejegyzéseid megjelenhetnek a keresési eredmények és a felkapott tartalmak között, valamint a profilod a hozzád hasonló érdeklődési körrel rendelkező embereknél is ajánlásra kerülhet.",
   "onboarding.profile.display_name": "Megjelenített név",
   "onboarding.profile.display_name_hint": "Teljes neved vagy vicces neved…",
+  "onboarding.profile.lead": "Ezt később bármikor befejezheted a beállításokban, ahol még több testreszabási lehetőség áll rendelkezésre.",
   "onboarding.profile.note": "Bemutatkozás",
   "onboarding.profile.note_hint": "Megemlíthetsz @másokat vagy #hashtag-eket…",
   "onboarding.profile.save_and_continue": "Mentés és folytatás",
   "onboarding.profile.title": "Profilbeállítás",
   "onboarding.profile.upload_avatar": "Profilkép feltöltése",
   "onboarding.profile.upload_header": "Profil fejléc feltöltése",
+  "onboarding.share.lead": "Tudassuk az emberekkel, hogyan találhatnak meg a Mastodonon!",
+  "onboarding.share.message": "{username} vagyok a #Mastodon hálózaton! Kövess itt: {url}.",
+  "onboarding.share.next_steps": "Lehetséges következő lépések:",
+  "onboarding.share.title": "Profil megosztása",
+  "onboarding.start.lead": "Az új Mastodon-fiók használatra kész. Így hozhatod ki belőle a legtöbbet:",
+  "onboarding.start.skip": "Nincs szükséged segítségre a kezdéshez?",
+  "onboarding.start.title": "Ez sikerült!",
+  "onboarding.steps.follow_people.body": "A Mastodon az érdekes emberek követéséről szól.",
+  "onboarding.steps.follow_people.title": "Szabd személyre a kezdőlapodat",
+  "onboarding.steps.publish_status.body": "Köszöntsd a világot szöveggel, fotókkal, videókkal vagy szavazásokkal {emoji}",
+  "onboarding.steps.publish_status.title": "Az első bejegyzés létrehozása",
+  "onboarding.steps.setup_profile.body": "Növeld az interakciók számát a profilod részletesebb kitöltésével.",
+  "onboarding.steps.setup_profile.title": "Szabd személyre a profilodat",
+  "onboarding.steps.share_profile.body": "Tudasd az ismerőseiddel, hogyan találhatnak meg a Mastodonon",
+  "onboarding.steps.share_profile.title": "Oszd meg a Mastodon profilodat",
+  "onboarding.tips.2fa": "<strong>Tudtad?</strong> A fiókod biztonságossá teheted, ha a fiók beállításaiban beállítod a kétlépcsős hitelesítést. Bármilyen választott TOTP alkalmazással működik, nincs szükség telefonszámra!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Tudtad?</strong> Mivel a Mastodon decentralizált, egyes profilok, amelyekkel találkozol, más kiszolgálókon lesznek tárolva. És mégis zökkenőmentesen kommunikálhatsz velük! A kiszolgáló a felhasználónevük második felében található!",
+  "onboarding.tips.migration": "<strong>Tudtad?</strong> Ha úgy érzed, hogy a {domain} már nem jó kiszolgáló a számodra, átköltözhetsz egy másik Mastodon kiszolgálóra anélkül, hogy elveszítenéd a követőidet. Akár saját kiszolgálót is üzemeltethetsz!",
+  "onboarding.tips.verification": "<strong>Tudtad?</strong> Fiókodat ellenőrizheted, ha raksz egy hivatkozást a Mastodon-profilodra a saját webhelyeden és hozzáadod ezt a webhelyet a profilodhoz. Nincs szükség díjfizetésre vagy dokumentumra!",
   "password_confirmation.exceeds_maxlength": "A jelszó megerősítése hosszabb a legnagyobb megengedett jelszóhossznál",
   "password_confirmation.mismatching": "A jelszómegerősítés nem egyezik",
   "picture_in_picture.restore": "Visszahelyezés",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Szavazás eltávolítása",
   "privacy.change": "Bejegyzés láthatóságának módosítása",
   "privacy.direct.long": "Mindenki, akit a bejegyzés említ",
-  "privacy.direct.short": "Privát említés",
+  "privacy.direct.short": "Megadott személyek",
   "privacy.private.long": "Csak a követőid",
   "privacy.private.short": "Követők",
   "privacy.public.long": "Bárki a Mastodonon és azon kívül",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Adatvédelmi szabályzat",
   "recommended": "Ajánlott",
   "refresh": "Frissítés",
-  "regeneration_indicator.please_stand_by": "Kis türelmet.",
-  "regeneration_indicator.preparing_your_home_feed": "Kezdőlapi hírfolyam előkészítése…",
+  "regeneration_indicator.label": "Betöltés…",
+  "regeneration_indicator.sublabel": "A kezdőlapod hírfolyama épp készül!",
   "relative_time.days": "{number}n",
   "relative_time.full.days": "{number, plural, one {# napja} other {# napja}}",
   "relative_time.full.hours": "{number, plural, one {# órája} other {# órája}}",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Profilok",
   "search_results.all": "Összes",
   "search_results.hashtags": "Hashtagek",
-  "search_results.no_results": "Nincs találat.",
-  "search_results.no_search_yet": "Próbálj meg bejegyzések, profilok vagy címkék után keresni.",
+  "search_results.nothing_found": "Nincs találat ezekre a keresési kifejezésekre",
   "search_results.see_all": "Összes megtekintése",
   "search_results.statuses": "Bejegyzések",
-  "search_results.title": "Keresés erre: „{q}”",
+  "search_results.title": "{q} keresése",
   "server_banner.about_active_users": "Az elmúlt 30 napban ezt a kiszolgálót használók száma (Havi aktív felhasználók)",
   "server_banner.active_users": "aktív felhasználó",
   "server_banner.administered_by": "Adminisztrátor:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Senki sem tolta még meg ezt a bejegyzést. Ha valaki megteszi, itt fog megjelenni.",
   "status.redraft": "Törlés és újraírás",
   "status.remove_bookmark": "Könyvjelző eltávolítása",
-  "status.remove_favourite": "Eltávolítás a kedvencek közül",
   "status.replied_in_thread": "Válaszolva a szálban",
   "status.replied_to": "Megválaszolva {name} számára",
   "status.reply": "Válasz",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Feliratkozott nyelvek módosítása {target} esetében",
   "tabs_bar.home": "Kezdőlap",
   "tabs_bar.notifications": "Értesítések",
-  "terms_of_service.effective_as_of": "Hatálybalépés dátuma: {date}",
-  "terms_of_service.title": "Felhasználási feltételek",
-  "terms_of_service.upcoming_changes_on": "Érkező változások: {date}",
   "time_remaining.days": "{number, plural, one {# nap} other {# nap}} van hátra",
   "time_remaining.hours": "{number, plural, one {# óra} other {# óra}} van hátra",
   "time_remaining.minutes": "{number, plural, one {# perc} other {# perc}} van hátra",
@@ -897,12 +853,26 @@
   "upload_button.label": "Képek, videó vagy audiófájl hozzáadása",
   "upload_error.limit": "A fájlfeltöltési korlát elérésre került.",
   "upload_error.poll": "Szavazásnál nem lehet fájlt feltölteni.",
+  "upload_form.audio_description": "Leírás siket vagy hallássérült emberek számára",
+  "upload_form.description": "Leírás vak vagy gyengénlátó emberek számára",
   "upload_form.drag_and_drop.instructions": "Egy médiamelléklet kiválasztásához nyomj Szóközt vagy Entert. Húzás közben használd a nyílgombokat a médiamelléklet adott irányba történő mozgatásához. A médiamelléklet új pozícióba helyezéséhez nyomd meg a Szóközt vagy az Entert, vagy a megszakításhoz nyomd meg az Esc gombot.",
   "upload_form.drag_and_drop.on_drag_cancel": "Az áthúzás megszakítva. A(z) {item} médiamelléklet el lett dobva.",
   "upload_form.drag_and_drop.on_drag_end": "A(z) {item} médiamelléklet el lett dobva.",
   "upload_form.drag_and_drop.on_drag_over": "A(z) {item} médiamelléklet át lett helyezve.",
   "upload_form.drag_and_drop.on_drag_start": "A(z) {item} médiamelléklet fel lett véve.",
   "upload_form.edit": "Szerkesztés",
+  "upload_form.thumbnail": "Bélyegkép megváltoztatása",
+  "upload_form.video_description": "Leírás siket, hallássérült, vak vagy gyengénlátó emberek számára",
+  "upload_modal.analyzing_picture": "Kép elemzése…",
+  "upload_modal.apply": "Alkalmaz",
+  "upload_modal.applying": "Alkalmazás…",
+  "upload_modal.choose_image": "Kép kiválasztása",
+  "upload_modal.description_placeholder": "A gyors, barna róka átugrik a lusta kutya fölött",
+  "upload_modal.detect_text": "Szöveg felismerése a képről",
+  "upload_modal.edit_media": "Média szerkesztése",
+  "upload_modal.hint": "Kattints vagy húzd a kört az előnézetben arra a fókuszpontra, mely minden bélyegképen látható kell, hogy legyen.",
+  "upload_modal.preparing_ocr": "OCR előkészítése…",
+  "upload_modal.preview_label": "Előnézet ({ratio})",
   "upload_progress.label": "Feltöltés…",
   "upload_progress.processing": "Feldolgozás…",
   "username.taken": "Ez a felhasználónév foglalt. Válassz másikat.",
@@ -912,12 +882,8 @@
   "video.expand": "Videó nagyítása",
   "video.fullscreen": "Teljes képernyő",
   "video.hide": "Videó elrejtése",
-  "video.mute": "Némítás",
+  "video.mute": "Hang némítása",
   "video.pause": "Szünet",
   "video.play": "Lejátszás",
-  "video.skip_backward": "Visszaugrás",
-  "video.skip_forward": "Előreugrás",
-  "video.unmute": "Némítás feloldása",
-  "video.volume_down": "Hangerő le",
-  "video.volume_up": "Hangerő fel"
+  "video.unmute": "Hang némításának feloldása"
 }
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index 9882d575b2..9e5ae79045 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -72,6 +72,7 @@
   "bundle_column_error.return": "Վերադառնալ տուն",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Փակել",
+  "bundle_modal_error.message": "Այս բաղադրիչը բեռնելու ընթացքում ինչ֊որ բան խափանուեց։",
   "bundle_modal_error.retry": "Կրկին փորձել",
   "closed_registrations_modal.find_another_server": "Գտնել այլ սերուերում",
   "column.about": "Մասին",
@@ -143,6 +144,8 @@
   "directory.recently_active": "Վերջերս ակտիւ",
   "disabled_account_banner.account_settings": "Հաշուի կարգաւորումներ",
   "dismissable_banner.dismiss": "Բաց թողնել",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Այս գրառումը քո կայքում ներդնելու համար կարող ես պատճէնել ներքեւի կոդը։",
   "embed.preview": "Ահա, թէ ինչ տեսք կունենայ այն՝",
   "emoji_button.activity": "Զբաղմունքներ",
@@ -171,6 +174,7 @@
   "empty_column.hashtag": "Այս պիտակով դեռ ոչինչ չկայ։",
   "empty_column.home": "Քո հիմնական հոսքը դատարկ է։ Այցելի՛ր {public}ը կամ օգտուիր որոնումից՝ այլ մարդկանց հանդիպելու համար։",
   "empty_column.list": "Այս ցանկում դեռ ոչինչ չկայ։ Երբ ցանկի անդամներից որեւէ մէկը նոր գրառում անի, այն կը յայտնուի այստեղ։",
+  "empty_column.lists": "Դուք դեռ չունէք ստեղծած ցանկ։ Ցանկ ստեղծելուն պէս այն կը յայտնուի այստեղ։",
   "empty_column.mutes": "Առայժմ ոչ ոքի չէք լռեցրել։",
   "empty_column.notifications": "Ոչ մի ծանուցում դեռ չունես։ Բզիր միւսներին՝ խօսակցութիւնը սկսելու համար։",
   "empty_column.public": "Այստեղ բան չկա՛յ։ Հրապարակային մի բան գրիր կամ հետեւիր այլ հանգոյցներից էակների՝ այն լցնելու համար։",
@@ -180,6 +184,7 @@
   "error.unexpected_crash.next_steps_addons": "Փորձիր անջատել յաւելուածները եւ թարմացնել էջը։ Եթե դա չօգնի, կարող ես օգտուել Մաստադոնից այլ դիտարկիչով կամ յաւելուածով։",
   "errors.unexpected_crash.copy_stacktrace": "Պատճենել սթաքթրեյսը սեղմատախտակին",
   "errors.unexpected_crash.report_issue": "Զեկուցել խնդրի մասին",
+  "explore.search_results": "Որոնման արդիւնքներ",
   "explore.suggested_follows": "Մարդիկ",
   "explore.title": "Բացայայտել",
   "explore.trending_links": "Նորութիւններ",
@@ -200,6 +205,7 @@
   "footer.about": "Մասին",
   "footer.directory": "Հաշիւների մատեան",
   "footer.get_app": "Ներբեռնիր յաւելուած",
+  "footer.invite": "Հրաւիրել մարդկանց",
   "footer.keyboard_shortcuts": "Ստեղնաշարի կարճատներ",
   "footer.privacy_policy": "Գաղտնիութեան քաղաքականութիւն",
   "footer.source_code": "Նայել ելակոդը",
@@ -267,11 +273,19 @@
   "lightbox.close": "Փակել",
   "lightbox.next": "Յաջորդ",
   "lightbox.previous": "Նախորդ",
+  "lists.account.add": "Աւելացնել ցանկին",
+  "lists.account.remove": "Հանել ցանկից",
   "lists.delete": "Ջնջել ցանկը",
   "lists.edit": "Փոփոխել ցանկը",
+  "lists.edit.submit": "Փոխել վերնագիրը",
+  "lists.new.create": "Աւելացնել ցանկ",
+  "lists.new.title_placeholder": "Նոր ցանկի վերնագիր",
   "lists.replies_policy.followed": "Ցանկացած հետեւող օգտատէր",
   "lists.replies_policy.list": "Ցանկի անդամներ",
   "lists.replies_policy.none": "Ոչ ոք",
+  "lists.replies_policy.title": "Ցուցադրել պատասխանները՝",
+  "lists.search": "Փնտրել քո հետեւած մարդկանց մէջ",
+  "lists.subheading": "Քո ցանկերը",
   "load_pending": "{count, plural, one {# նոր նիւթ} other {# նոր նիւթ}}",
   "navigation_bar.about": "Մասին",
   "navigation_bar.blocks": "Արգելափակուած օգտատէրեր",
@@ -337,6 +351,22 @@
   "notifications_permission_banner.enable": "Միացնել դիտարկչից ծանուցումները",
   "notifications_permission_banner.how_to_control": "Ծանուցումներ ստանալու համար, երբ Մաստոդոնը բաց չէ՝ ակտիւացրու աշխատատիրոյթի ծանուցումները։ Դու կարող ես ճշգրտօրէն վերահսկել թէ ինչպիսի փոխգործակցութիւններ առաջանան աշխատատիրոյթի ծանուցումներից՝ {icon}ի կոճակով՝ այն ակտիւացնելուց յետոյ։",
   "notifications_permission_banner.title": "Ոչինչ բաց մի թող",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "Բարեւ #Mastodon!",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "Դու արեցի՜ր դա",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Ստեղծիր առաջին գրառումդ",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "Յետ բերել",
   "poll.closed": "Փակ",
   "poll.refresh": "Թարմացնել",
@@ -352,6 +382,8 @@
   "privacy_policy.last_updated": "Վերջին անգամ թարմացուել է՝ {date}",
   "privacy_policy.title": "Գաղտնիութեան քաղաքականութիւն",
   "refresh": "Թարմացնել",
+  "regeneration_indicator.label": "Բեռնւում է…",
+  "regeneration_indicator.sublabel": "պատրաստւում է հիմնական հոսքդ",
   "relative_time.days": "{number}օր",
   "relative_time.full.days": "{number, plural, one {# օր} other {# օր}} առաջ",
   "relative_time.full.hours": "{number, plural, one {# ժամ} other {# ժամ}} առաջ",
@@ -394,6 +426,7 @@
   "search_results.hashtags": "Պիտակներ",
   "search_results.see_all": "Տեսնել բոլորը",
   "search_results.statuses": "Գրառումներ",
+  "search_results.title": "Որոնել {q}-ն",
   "server_banner.active_users": "ակտիւ մարդիկ",
   "server_banner.administered_by": "Կառաւարող",
   "server_banner.server_stats": "Սերուերի վիճակը",
@@ -464,7 +497,21 @@
   "upload_button.label": "Աւելացնել մեդիա",
   "upload_error.limit": "Նիշքի վերբեռնման սահմանաչափը գերազանցուած է։",
   "upload_error.poll": "Հարցումների հետ նիշք կցել հնարաւոր չէ։",
+  "upload_form.audio_description": "Նկարագրիր ձայնագրութեան բովանդակութիւնը լսողական խնդիրներով անձանց համար",
+  "upload_form.description": "Նկարագիր՝ տեսողական խնդիրներ ունեցողների համար",
   "upload_form.edit": "Խմբագրել",
+  "upload_form.thumbnail": "Փոխել պատկերակը",
+  "upload_form.video_description": "Նկարագրիր տեսանիւթը լսողական կամ տեսողական խնդիրներով անձանց համար",
+  "upload_modal.analyzing_picture": "Լուսանկարի վերլուծում…",
+  "upload_modal.apply": "Կիրառել",
+  "upload_modal.applying": "Կիրառւում է...",
+  "upload_modal.choose_image": "Ընտրել նկար",
+  "upload_modal.description_placeholder": "Բել դղեակի ձախ ժամն օֆ ազգութեանը ցպահանջ չճշտած վնաս էր եւ փառք։",
+  "upload_modal.detect_text": "Յայտնաբերել տեքստը նկարից",
+  "upload_modal.edit_media": "Խմբագրել մեդիան",
+  "upload_modal.hint": "Սեղմէք եւ տեղաշարժէք նախադիտման շրջանակը՝ որ ընտրէք մանրապատկերում միշտ տեսանելի կէտը։",
+  "upload_modal.preparing_ocr": "Գրաճանաչման նախապատրաստում…",
+  "upload_modal.preview_label": "Նախադիտում ({ratio})",
   "upload_progress.label": "Վերբեռնվում է…",
   "upload_progress.processing": "Մշակուում է...",
   "video.close": "Փակել  տեսագրութիւնը",
@@ -473,6 +520,8 @@
   "video.expand": "Ընդարձակել տեսագրութիւնը",
   "video.fullscreen": "Լիաէկրան",
   "video.hide": "Թաքցնել տեսագրութիւնը",
+  "video.mute": "Լռեցնել ձայնը",
   "video.pause": "Դադար տալ",
-  "video.play": "Նուագել"
+  "video.play": "Նուագել",
+  "video.unmute": "Միացնել ձայնը"
 }
diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json
index 7f4f66796e..37a6c3f809 100644
--- a/app/javascript/mastodon/locales/ia.json
+++ b/app/javascript/mastodon/locales/ia.json
@@ -29,6 +29,7 @@
   "account.endorse": "Evidentiar sur le profilo",
   "account.featured_tags.last_status_at": "Ultime message publicate le {date}",
   "account.featured_tags.last_status_never": "Necun message",
+  "account.featured_tags.title": "Hashtags eminente de {name}",
   "account.follow": "Sequer",
   "account.follow_back": "Sequer in retorno",
   "account.followers": "Sequitores",
@@ -85,33 +86,7 @@
   "alert.unexpected.message": "Un error inexpectate ha occurrite.",
   "alert.unexpected.title": "Ups!",
   "alt_text_badge.title": "Texto alternative",
-  "alt_text_modal.add_alt_text": "Adder texto alternative",
-  "alt_text_modal.add_text_from_image": "Adder texto ab imagine",
-  "alt_text_modal.cancel": "Cancellar",
-  "alt_text_modal.change_thumbnail": "Cambiar le miniatura",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Describe isto pro personas con impedimentos auditive…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Describe isto pro personas con impedimentos visual…",
-  "alt_text_modal.done": "Preste",
   "announcement.announcement": "Annuncio",
-  "annual_report.summary.archetype.booster": "Le impulsator",
-  "annual_report.summary.archetype.lurker": "Le lector",
-  "annual_report.summary.archetype.oracle": "Le oraculo",
-  "annual_report.summary.archetype.pollster": "Le sondagista",
-  "annual_report.summary.archetype.replier": "Le responditor",
-  "annual_report.summary.followers.followers": "sequitores",
-  "annual_report.summary.followers.total": "{count} in total",
-  "annual_report.summary.here_it_is": "Ecce tu summario de {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "message le plus favorite",
-  "annual_report.summary.highlighted_post.by_reblogs": "message le plus impulsate",
-  "annual_report.summary.highlighted_post.by_replies": "message le plus respondite",
-  "annual_report.summary.highlighted_post.possessive": "{name}, ecce tu…",
-  "annual_report.summary.most_used_app.most_used_app": "application le plus usate",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag le plus usate",
-  "annual_report.summary.most_used_hashtag.none": "Necun",
-  "annual_report.summary.new_posts.new_posts": "nove messages",
-  "annual_report.summary.percentile.text": "<topLabel>Isto te pone in le prime</topLabel><percentage></percentage><bottomLabel>usatores de {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Tu es un primo inter pares.",
-  "annual_report.summary.thanks": "Gratias pro facer parte de Mastodon!",
   "attachments_list.unprocessed": "(non processate)",
   "audio.hide": "Celar audio",
   "block_modal.remote_users_caveat": "Nos demandera al servitor {domain} de respectar tu decision. Nonobstante, le conformitate non es garantite perque alcun servitores pote tractar le blocadas de maniera differente. Le messages public pote esser totevia visibile pro le usatores non authenticate.",
@@ -135,7 +110,7 @@
   "bundle_column_error.routing.body": "Le pagina requestate non pote esser trovate. Es tu secur que le URL in le barra de adresse es correcte?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Clauder",
-  "bundle_modal_error.message": "Un error ha occurrite durante le cargamento de iste schermo.",
+  "bundle_modal_error.message": "Un error ha occurrite durante le cargamento de iste componente.",
   "bundle_modal_error.retry": "Tentar novemente",
   "closed_registrations.other_server_instructions": "Perque Mastodon es decentralisate, tu pote crear un conto sur un altere servitor e totevia interager con iste servitor.",
   "closed_registrations_modal.description": "Crear un conto sur {domain} non es actualmente possibile, ma considera que non es necessari haber un conto specificamente sur {domain} pro usar Mastodon.",
@@ -146,16 +121,13 @@
   "column.blocks": "Usatores blocate",
   "column.bookmarks": "Marcapaginas",
   "column.community": "Chronologia local",
-  "column.create_list": "Crear lista",
   "column.direct": "Mentiones private",
   "column.directory": "Navigar profilos",
   "column.domain_blocks": "Dominios blocate",
-  "column.edit_list": "Modificar lista",
   "column.favourites": "Favorites",
   "column.firehose": "Fluxos in directo",
   "column.follow_requests": "Requestas de sequimento",
   "column.home": "Initio",
-  "column.list_members": "Gerer le membros del lista",
   "column.lists": "Listas",
   "column.mutes": "Usatores silentiate",
   "column.notifications": "Notificationes",
@@ -168,7 +140,6 @@
   "column_header.pin": "Fixar",
   "column_header.show_settings": "Monstrar le parametros",
   "column_header.unpin": "Disfixar",
-  "column_search.cancel": "Cancellar",
   "column_subheading.settings": "Parametros",
   "community.column_settings.local_only": "Solmente local",
   "community.column_settings.media_only": "Solmente multimedia",
@@ -187,7 +158,7 @@
   "compose_form.poll.duration": "Durata del sondage",
   "compose_form.poll.multiple": "Selection multiple",
   "compose_form.poll.option_placeholder": "Option {number}",
-  "compose_form.poll.single": "Option singule",
+  "compose_form.poll.single": "Seliger un",
   "compose_form.poll.switch_to_multiple": "Cambiar le sondage pro permitter selectiones multiple",
   "compose_form.poll.switch_to_single": "Cambiar le sondage pro permitter selection singule",
   "compose_form.poll.type": "Stilo",
@@ -211,16 +182,9 @@
   "confirmations.edit.confirm": "Modificar",
   "confirmations.edit.message": "Si tu modifica isto ora, le message in curso de composition essera perdite. Es tu secur de voler continuar?",
   "confirmations.edit.title": "Superscriber le message?",
-  "confirmations.follow_to_list.confirm": "Sequer e adder al lista",
-  "confirmations.follow_to_list.message": "Tu debe sequer {name} pro poter adder le/la a un lista.",
-  "confirmations.follow_to_list.title": "Sequer le usator?",
   "confirmations.logout.confirm": "Clauder session",
   "confirmations.logout.message": "Es tu secur que tu vole clauder le session?",
   "confirmations.logout.title": "Clauder session?",
-  "confirmations.missing_alt_text.confirm": "Adder texto alternative",
-  "confirmations.missing_alt_text.message": "Tu message contine multimedia sin texto alternative. Adder descriptiones adjuta a render tu contento accessibile a plus personas.",
-  "confirmations.missing_alt_text.secondary": "Publicar totevia",
-  "confirmations.missing_alt_text.title": "Adder texto alternative?",
   "confirmations.mute.confirm": "Silentiar",
   "confirmations.redraft.confirm": "Deler e rescriber",
   "confirmations.redraft.message": "Es tu secur de voler deler iste message e rescriber lo? Le favorites e le impulsos essera perdite, e le responsas al message original essera orphanate.",
@@ -249,10 +213,10 @@
   "disabled_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate.",
   "dismissable_banner.community_timeline": "Ecce le messages public le plus recente del personas con contos sur {domain}.",
   "dismissable_banner.dismiss": "Clauder",
-  "dismissable_banner.explore_links": "Iste articulos de novas se condivide le plus sur le fediverso hodie. Le articulos de novas le plus recente, publicate per plus personas differente, se classifica plus in alto.",
-  "dismissable_banner.explore_statuses": "Iste messages de tote le fediverso gania popularitate hodie. Le messages plus nove con plus impulsos e favorites se classifica plus in alto.",
-  "dismissable_banner.explore_tags": "Iste hashtags gania popularitate sur le fediverso hodie. Le hashtags usate per plus personas differente se classifica plus in alto.",
-  "dismissable_banner.public_timeline": "Istes es le messages public le plus recente del personas sur le fediverso que le gente sur {domain} seque.",
+  "dismissable_banner.explore_links": "Istes es le articulos de novas que se condivide le plus sur le rete social hodie. Le articulos de novas le plus recente, publicate per plus personas differente, se classifica plus in alto.",
+  "dismissable_banner.explore_statuses": "Ecce le messages de tote le rete social que gania popularitate hodie. Le messages plus nove con plus impulsos e favorites se classifica plus in alto.",
+  "dismissable_banner.explore_tags": "Ecce le hashtags que gania popularitate sur le rete social hodie. Le hashtags usate per plus personas differente se classifica plus in alto.",
+  "dismissable_banner.public_timeline": "Istes es le messages public le plus recente del personas sur le rete social que le gente sur {domain} seque.",
   "domain_block_modal.block": "Blocar le servitor",
   "domain_block_modal.block_account_instead": "Blocar @{name} in su loco",
   "domain_block_modal.they_can_interact_with_old_posts": "Le personas de iste servitor pote interager con tu messages ancian.",
@@ -305,10 +269,11 @@
   "empty_column.favourited_statuses": "Tu non ha alcun message favorite ancora. Quando tu marca un message como favorite, illo apparera hic.",
   "empty_column.favourites": "Necuno ha ancora marcate iste message como favorite. Quando alcuno lo face, ille apparera hic.",
   "empty_column.follow_requests": "Tu non ha ancora requestas de sequimento. Quando tu recipe un, illo apparera hic.",
-  "empty_column.followed_tags": "Tu non seque ancora alcun hashtags. Quando tu lo face, illos apparera hic.",
+  "empty_column.followed_tags": "Tu non ha ancora sequite alcun hashtags. Quando tu lo face, illos apparera hic.",
   "empty_column.hashtag": "Il non ha ancora alcun cosa in iste hashtag.",
   "empty_column.home": "Tu chronologia de initio es vacue! Seque plus personas pro plenar lo.",
   "empty_column.list": "Iste lista es ancora vacue. Quando le membros de iste lista publica nove messages, illos apparera hic.",
+  "empty_column.lists": "Tu non ha ancora listas. Quando tu crea un, illo apparera hic.",
   "empty_column.mutes": "Tu non ha ancora silentiate alcun usator.",
   "empty_column.notification_requests": "Iste lista es toto vacue! Quando tu recipe notificationes, illos apparera hic como configurate in tu parametros.",
   "empty_column.notifications": "Tu non ha ancora notificationes. Quando altere personas interage con te, tu lo videra hic.",
@@ -319,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Tenta disactivar istes e refrescar le pagina. Si isto non remedia le problema, es possibile que tu pote totevia usar Mastodon per medio de un altere navigator o application native.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar le traciamento del pila al area de transferentia",
   "errors.unexpected_crash.report_issue": "Reportar problema",
+  "explore.search_results": "Resultatos de recerca",
   "explore.suggested_follows": "Personas",
   "explore.title": "Explorar",
   "explore.trending_links": "Novas",
@@ -357,25 +323,24 @@
   "follow_suggestions.hints.friends_of_friends": "Iste profilo es popular inter le gente que tu seque.",
   "follow_suggestions.hints.most_followed": "Iste profilo es un del plus sequites sur {domain}.",
   "follow_suggestions.hints.most_interactions": "Iste profilo ha recentemente recipite multe attention sur {domain}.",
-  "follow_suggestions.hints.similar_to_recently_followed": "Iste profilo es similar al profilos que tu ha recentemente comenciate a sequer.",
+  "follow_suggestions.hints.similar_to_recently_followed": "Iste profilo es similar al profilos que tu ha recentemente sequite.",
   "follow_suggestions.personalized_suggestion": "Suggestion personalisate",
   "follow_suggestions.popular_suggestion": "Suggestion personalisate",
   "follow_suggestions.popular_suggestion_longer": "Popular sur {domain}",
-  "follow_suggestions.similar_to_recently_followed_longer": "Similar al profilos que tu ha recentemente comenciate a sequer",
+  "follow_suggestions.similar_to_recently_followed_longer": "Similar al profilos que tu ha sequite recentemente",
   "follow_suggestions.view_all": "Vider toto",
   "follow_suggestions.who_to_follow": "Qui sequer",
   "followed_tags": "Hashtags sequite",
   "footer.about": "A proposito",
   "footer.directory": "Directorio de profilos",
   "footer.get_app": "Obtener le application",
+  "footer.invite": "Invitar personas",
   "footer.keyboard_shortcuts": "Accessos directe de claviero",
   "footer.privacy_policy": "Politica de confidentialitate",
   "footer.source_code": "Vider le codice fonte",
   "footer.status": "Stato",
-  "footer.terms_of_service": "Conditiones de servicio",
   "generic.saved": "Salvate",
   "getting_started.heading": "Prime passos",
-  "hashtag.admin_moderation": "Aperir le interfacie de moderation pro #{name}",
   "hashtag.column_header.tag_mode.all": "e {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
   "hashtag.column_header.tag_mode.none": "sin {additional}",
@@ -417,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignorar notificationes de personas qui non te seque?",
   "ignore_notifications_modal.not_following_title": "Ignorar notificationes de personas que tu non seque?",
   "ignore_notifications_modal.private_mentions_title": "Ignorar notificationes de mentiones private non requestate?",
-  "info_button.label": "Adjuta",
-  "info_button.what_is_alt_text": "<h1>Que es texto alternative?</h1><p>Le texto alternative forni descriptiones de imagines a personas con impedimentos visual, con connexiones lente, o qui cerca contexto additional.</p><p>Tu pote meliorar le accessibilitate e le comprension pro totes scribente un texto alternative clar, concise e objective.</p><ul><li>Captura le elementos importante</li><li>Summarisa texto in imagines</li><li>Usa le structura de phrase normal</li><li>Evita information redundante</li><li>In figuras complexe (como diagrammas o mappas), concentra te sur le tendentias e punctos clave</li></ul>",
-  "interaction_modal.action.favourite": "Per favor reveni a tu conto pro marcar isto como favorite.",
-  "interaction_modal.action.follow": "Per favor reveni a tu conto pro sequer.",
-  "interaction_modal.action.reblog": "Per favor reveni a tu conto pro impulsar.",
-  "interaction_modal.action.reply": "Per favor reveni a tu conto pro responder.",
-  "interaction_modal.action.vote": "Per favor reveni a tu conto pro votar.",
-  "interaction_modal.go": "Revenir",
-  "interaction_modal.no_account_yet": "Tu non ha ancora un conto?",
+  "interaction_modal.description.favourite": "Con un conto sur Mastodon, tu pote marcar iste message como favorite pro informar le autor que tu lo apprecia e lo salva pro plus tarde.",
+  "interaction_modal.description.follow": "Con un conto sur Mastodon, tu pote sequer {name} e reciper su messages in tu fluxo de initio.",
+  "interaction_modal.description.reblog": "Con un conto sur Mastodon, tu pote impulsar iste message pro condivider lo con tu proprie sequitores.",
+  "interaction_modal.description.reply": "Con un conto sur Mastodon, tu pote responder a iste message.",
+  "interaction_modal.login.action": "Porta me a casa",
+  "interaction_modal.login.prompt": "Dominio de tu servitor, p.ex. mastodon.social",
+  "interaction_modal.no_account_yet": "Non sur Mstodon?",
   "interaction_modal.on_another_server": "Sur un altere servitor",
   "interaction_modal.on_this_server": "Sur iste servitor",
+  "interaction_modal.sign_in": "Tu non es in session sur iste servitor. Sur qual servitor se trova tu conto?",
+  "interaction_modal.sign_in_hint": "Consilio: Se tracta del sito web ubi tu te ha inscribite. Si tu non te lo rememora, cerca le e-mail de benvenita in tu cassa de entrata. Tu pote etiam inserer tu pseudonymo complete! (p.ex. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Marcar le message de {name} como favorite",
   "interaction_modal.title.follow": "Sequer {name}",
   "interaction_modal.title.reblog": "Impulsar le message de {name}",
   "interaction_modal.title.reply": "Responder al message de {name}",
-  "interaction_modal.title.vote": "Votar in le sondage de {name}",
-  "interaction_modal.username_prompt": "P.ex. {example}",
   "intervals.full.days": "{number, plural, one {# die} other {# dies}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
   "intervals.full.minutes": "{number, plural, one {# minuta} other {# minutas}}",
@@ -469,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Monstrar/celar texto detra advertimento de contento",
   "keyboard_shortcuts.toggle_sensitivity": "Monstrar/celar multimedia",
   "keyboard_shortcuts.toot": "Initiar un nove message",
-  "keyboard_shortcuts.translate": "a traducer un message",
   "keyboard_shortcuts.unfocus": "Disfocalisar le area de composition de texto/de recerca",
   "keyboard_shortcuts.up": "Displaciar in alto in le lista",
   "lightbox.close": "Clauder",
@@ -482,32 +444,20 @@
   "link_preview.author": "Per {name}",
   "link_preview.more_from_author": "Plus de {name}",
   "link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
-  "lists.add_member": "Adder",
-  "lists.add_to_list": "Adder al lista",
-  "lists.add_to_lists": "Adder {name} al listas",
-  "lists.create": "Crear",
-  "lists.create_a_list_to_organize": "Crear un nove lista pro organisar tu fluxo de initio",
-  "lists.create_list": "Crear lista",
+  "lists.account.add": "Adder al lista",
+  "lists.account.remove": "Remover del lista",
   "lists.delete": "Deler lista",
-  "lists.done": "Facite",
   "lists.edit": "Modificar lista",
-  "lists.exclusive": "Celar memberos in Initio",
-  "lists.exclusive_hint": "Si alcuno es sur iste lista, celar iste persona in tu fluxo de initio pro evitar de vider su messages duo vices.",
-  "lists.find_users_to_add": "Trovar usatores a adder",
-  "lists.list_members": "Membros del lista",
-  "lists.list_members_count": "{count, plural, one {# membro} other {# membros}}",
-  "lists.list_name": "Nomine del lista",
-  "lists.new_list_name": "Nove nomine de lista",
-  "lists.no_lists_yet": "Necun lista ancora.",
-  "lists.no_members_yet": "Necun membro ancora.",
-  "lists.no_results_found": "Necun resultato trovate.",
-  "lists.remove_member": "Remover",
+  "lists.edit.submit": "Cambiar titulo",
+  "lists.exclusive": "Celar iste messages sur le pagina de initio",
+  "lists.new.create": "Adder lista",
+  "lists.new.title_placeholder": "Nove titulo del lista",
   "lists.replies_policy.followed": "Qualcunque usator sequite",
   "lists.replies_policy.list": "Membros del lista",
   "lists.replies_policy.none": "Nemo",
-  "lists.save": "Salvar",
-  "lists.search": "Cercar",
-  "lists.show_replies_to": "Includer responsas de membros del lista a",
+  "lists.replies_policy.title": "Monstrar responsas a:",
+  "lists.search": "Cercar inter le gente que tu seque",
+  "lists.subheading": "Tu listas",
   "load_pending": "{count, plural, one {# nove entrata} other {# nove entratas}}",
   "loading_indicator.label": "Cargante…",
   "media_gallery.hide": "Celar",
@@ -556,14 +506,10 @@
   "notification.admin.report_statuses_other": "{name} ha reportate {target}",
   "notification.admin.sign_up": "{name} se ha inscribite",
   "notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {# altere persona} other {# altere personas}} se ha inscribite",
-  "notification.annual_report.message": "Tu summario #Wrapstodon pro {year} attende! Revela le momentos saliente e memorabile de tu anno sur Mastodon!",
-  "notification.annual_report.view": "Visitar summario #Wrapstodon",
   "notification.favourite": "{name} ha marcate tu message como favorite",
   "notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {# altere} other {# alteres}}</a> ha marcate tu message como favorite",
-  "notification.favourite_pm": "{name} ha marcate tu mention privte como favorite",
-  "notification.favourite_pm.name_and_others_with_link": "{name} e <a>{count, plural, one {# altere} other {# alteres}}</a> ha marcate tu mention private como favorite",
-  "notification.follow": "{name} te seque",
-  "notification.follow.name_and_others": "{name} e <a>{count, plural, one {# other} other {# alteres}}</a> te seque",
+  "notification.follow": "{name} te ha sequite",
+  "notification.follow.name_and_others": "{name} e <a>{count, plural, one {# other} other {# alteres}}</a> te ha sequite",
   "notification.follow_request": "{name} ha requestate de sequer te",
   "notification.follow_request.name_and_others": "{name} e {count, plural, one {# altere} other {# alteres}} ha demandate de sequer te",
   "notification.label.mention": "Mention",
@@ -656,7 +602,7 @@
   "notifications.policy.filter_limited_accounts_title": "Contos moderate",
   "notifications.policy.filter_new_accounts.hint": "Create in le ultime {days, plural, one {die} other {# dies}}",
   "notifications.policy.filter_new_accounts_title": "Nove contos",
-  "notifications.policy.filter_not_followers_hint": "Includente le personas que te seque desde minus de {days, plural, one {un die} other {# dies}}",
+  "notifications.policy.filter_not_followers_hint": "Includente le personas que te ha sequite durante minus de {days, plural, one {un die} other {# dies}}",
   "notifications.policy.filter_not_followers_title": "Personas qui non te seque",
   "notifications.policy.filter_not_following_hint": "Usque tu les approba manualmente",
   "notifications.policy.filter_not_following_title": "Personas que tu non seque",
@@ -666,21 +612,44 @@
   "notifications_permission_banner.enable": "Activar notificationes de scriptorio",
   "notifications_permission_banner.how_to_control": "Pro reciper notificationes quando Mastodon non es aperte, activa le notificationes de scriptorio. Post lor activation, es possibile controlar precisemente qual typos de interaction genera notificationes de scriptorio per medio del button {icon} hic supra.",
   "notifications_permission_banner.title": "Non mancar jammais a un cosa",
-  "onboarding.follows.back": "Retro",
-  "onboarding.follows.done": "Facite",
+  "onboarding.action.back": "Porta me retro",
+  "onboarding.actions.back": "Porta me retro",
+  "onboarding.actions.go_to_explore": "Porta me al tendentias",
+  "onboarding.actions.go_to_home": "Porta me a mi fluxo de initio",
+  "onboarding.compose.template": "Salute #Mastodon!",
   "onboarding.follows.empty": "Regrettabilemente, non es possibile monstrar resultatos al momento. Tu pote tentar usar le recerca o percurrer le pagina de exploration pro cercar personas a sequer, o tentar lo de novo plus tarde.",
-  "onboarding.follows.search": "Cercar",
-  "onboarding.follows.title": "Seque personas pro comenciar",
+  "onboarding.follows.lead": "Le fluxo de initio es le maniera principal de discoperir Mastodon. Quanto plus personas tu seque, tanto plus active e interessante illo essera. Pro comenciar, ecce alcun suggestiones:",
+  "onboarding.follows.title": "Personalisar tu fluxo de initio",
   "onboarding.profile.discoverable": "Render mi profilo discoperibile",
   "onboarding.profile.discoverable_hint": "Quando tu opta pro devenir discoperibile sur Mastodon, tu messages pote apparer in resultatos de recerca e in tendentias, e tu profilo pote esser suggerite al personas con interesses simile al tues.",
   "onboarding.profile.display_name": "Nomine a monstrar",
   "onboarding.profile.display_name_hint": "Tu nomine complete o tu supernomine…",
+  "onboarding.profile.lead": "Tu pote sempre completar isto plus tarde in le parametros, ubi se trova mesmo plus optiones de personalisation.",
   "onboarding.profile.note": "Bio",
   "onboarding.profile.note_hint": "Tu pote @mentionar altere personas o #hashtags…",
   "onboarding.profile.save_and_continue": "Salvar e continuar",
   "onboarding.profile.title": "Configuration del profilo",
   "onboarding.profile.upload_avatar": "Incargar imagine de profilo",
   "onboarding.profile.upload_header": "Actualisar capite de profilo",
+  "onboarding.share.lead": "Face saper al gente como illes pote trovar te sur Mastodon!",
+  "onboarding.share.message": "Io es {username} sur Mastodon! Veni sequer me a {url}",
+  "onboarding.share.next_steps": "Sequente passos possibile:",
+  "onboarding.share.title": "Compartir tu profilo",
+  "onboarding.start.lead": "Tu face ora parte de Mastodon, un platteforma de medios social unic e decentralisate ubi es tu, e non un algorithmo, qui crea tu proprie experientia. Nos va adjutar te a lancear te in iste nove frontiera social:",
+  "onboarding.start.skip": "Non require adjuta a comenciar?",
+  "onboarding.start.title": "Tu ha arrivate!",
+  "onboarding.steps.follow_people.body": "Sequer personas interessante es le ration de esser de Mastodon.",
+  "onboarding.steps.follow_people.title": "Personalisar tu fluxo de initio",
+  "onboarding.steps.publish_status.body": "Saluta le mundo con texto, photos, videos o sondages {emoji}",
+  "onboarding.steps.publish_status.title": "Face tu prime message",
+  "onboarding.steps.setup_profile.body": "Impulsa tu interactiones con un profilo comprehensive.",
+  "onboarding.steps.setup_profile.title": "Personalisa tu profilo",
+  "onboarding.steps.share_profile.body": "Face saper a tu amicos como trovar te sur Mastodon",
+  "onboarding.steps.share_profile.title": "Compartir tu profilo de Mastodon",
+  "onboarding.tips.2fa": "<strong>Lo sapeva tu?</strong> Tu pote securisar tu conto configurante le authentication bifactorial in le parametros de tu conto. Isto functiona con le application TOTP de tu preferentia, sin necessitate de un numero de telephono!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Lo sapeva tu?</strong> Perque Mastodon es decentralisate, le profilos que tu incontra pote esser hospitate sur servitores altere que le tue. Nonobstante, tu pote interager con illos sin problema! Lor servitor se trova in le secunde medietate de lor nomine de usator!",
+  "onboarding.tips.migration": "<strong>Lo sapeva tu?</strong> Si tu pensa que {domain} non es un bon servitor pro te in le futuro, tu pote cambiar a un altere servitor Mastodon sin perder tu sequitores. Tu pote mesmo hospitar tu proprie servitor!",
+  "onboarding.tips.verification": "<strong>Lo sapeva tu?</strong> Pro verificar tu conto, insere un ligamine a tu profilo Mastodon sur tu proprie sito web e adde le sito web a tu profilo. Nulle moneta o documentos necessari!",
   "password_confirmation.exceeds_maxlength": "Le confirmation del contrasigno excede le longitude maxime del contrasigno",
   "password_confirmation.mismatching": "Le confirmation del contrasigno non corresponde",
   "picture_in_picture.restore": "Restaurar",
@@ -696,7 +665,7 @@
   "poll_button.remove_poll": "Remover un sondage",
   "privacy.change": "Cambiar le confidentialitate del message",
   "privacy.direct.long": "Tote le personas mentionate in le message",
-  "privacy.direct.short": "Mention private",
+  "privacy.direct.short": "Personas specific",
   "privacy.private.long": "Solmente tu sequitores",
   "privacy.private.short": "Sequitores",
   "privacy.public.long": "Quicunque, sur Mastodon o non",
@@ -708,8 +677,8 @@
   "privacy_policy.title": "Politica de confidentialitate",
   "recommended": "Recommendate",
   "refresh": "Refrescar",
-  "regeneration_indicator.please_stand_by": "Un momento, per favor.",
-  "regeneration_indicator.preparing_your_home_feed": "Fluxo de initio in preparation…",
+  "regeneration_indicator.label": "Cargamento…",
+  "regeneration_indicator.sublabel": "Tu fluxo de initio es in preparation!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# die} other {# dies}} retro",
   "relative_time.full.hours": "{number, plural, one {# hora} other {# horas}} retro",
@@ -793,11 +762,10 @@
   "search_results.accounts": "Profilos",
   "search_results.all": "Toto",
   "search_results.hashtags": "Hashtags",
-  "search_results.no_results": "Necun resultato.",
-  "search_results.no_search_yet": "Tenta cercar messages, profilos o hashtags.",
+  "search_results.nothing_found": "Nihil trovate pro iste terminos de recerca",
   "search_results.see_all": "Vider toto",
   "search_results.statuses": "Messages",
-  "search_results.title": "Recerca de “{q}”",
+  "search_results.title": "Cercar {q}",
   "server_banner.about_active_users": "Personas que ha usate iste servitor in le ultime 30 dies (usatores active per mense)",
   "server_banner.active_users": "usatores active",
   "server_banner.administered_by": "Administrate per:",
@@ -849,7 +817,6 @@
   "status.reblogs.empty": "Necuno ha ancora impulsate iste message. Quando alcuno lo face, le impulsos apparera hic.",
   "status.redraft": "Deler e reconciper",
   "status.remove_bookmark": "Remover marcapagina",
-  "status.remove_favourite": "Remover del favoritos",
   "status.replied_in_thread": "Respondite in le discussion",
   "status.replied_to": "Respondite a {name}",
   "status.reply": "Responder",
@@ -871,7 +838,6 @@
   "subscribed_languages.target": "Cambiar le linguas subscribite pro {target}",
   "tabs_bar.home": "Initio",
   "tabs_bar.notifications": "Notificationes",
-  "terms_of_service.title": "Conditiones de servicio",
   "time_remaining.days": "{number, plural, one {# die} other {# dies}} restante",
   "time_remaining.hours": "{number, plural, one {# hora} other {# horas}} restante",
   "time_remaining.minutes": "{number, plural, one {# minuta} other {# minutas}} restante",
@@ -887,12 +853,26 @@
   "upload_button.label": "Adde imagines, un video o un file de audio",
   "upload_error.limit": "Limite de incargamento de files excedite.",
   "upload_error.poll": "Incargamento de files non permittite con sondages.",
+  "upload_form.audio_description": "Describe lo pro le gente con difficultates auditive",
+  "upload_form.description": "Describe lo pro le gente con difficultates visual",
   "upload_form.drag_and_drop.instructions": "Pro prender un annexo multimedial, preme sur le barra de spatios o Enter. Trahente lo, usa le claves de flecha pro displaciar le annexo multimedial in un certe direction. Preme le barra de spatios o Enter de novo pro deponer le annexo multimedial in su nove position, o preme sur Escape pro cancellar.",
   "upload_form.drag_and_drop.on_drag_cancel": "Le displaciamento ha essite cancellate. Le annexo multimedial {item} ha essite deponite.",
   "upload_form.drag_and_drop.on_drag_end": "Le annexo multimedial {item} ha essite deponite.",
   "upload_form.drag_and_drop.on_drag_over": "Le annexo multimedial {item} ha essite displaciate.",
   "upload_form.drag_and_drop.on_drag_start": "Le annexo multimedial {item} ha essite prendite.",
   "upload_form.edit": "Modificar",
+  "upload_form.thumbnail": "Cambiar le miniatura",
+  "upload_form.video_description": "Describe lo pro le gente con difficultates auditive o visual",
+  "upload_modal.analyzing_picture": "Analysa imagine…",
+  "upload_modal.apply": "Applicar",
+  "upload_modal.applying": "Applicante…",
+  "upload_modal.choose_image": "Seliger un imagine",
+  "upload_modal.description_placeholder": "Cinque expertos del zoo jam bibeva whisky frigide",
+  "upload_modal.detect_text": "Deteger texto de un imagine",
+  "upload_modal.edit_media": "Modificar multimedia",
+  "upload_modal.hint": "Clicca o trahe le circulo sur le previsualisation pro eliger le puncto focal que essera sempre visibile sur tote le miniaturas.",
+  "upload_modal.preparing_ocr": "Preparation del OCR…",
+  "upload_modal.preview_label": "Previsualisation ({ratio})",
   "upload_progress.label": "Incargante...",
   "upload_progress.processing": "Processante…",
   "username.taken": "Iste nomine de usator es ja in uso. Proba con un altere",
@@ -902,6 +882,8 @@
   "video.expand": "Expander video",
   "video.fullscreen": "Schermo plen",
   "video.hide": "Celar video",
+  "video.mute": "Silentiar le sono",
   "video.pause": "Pausa",
-  "video.play": "Reproducer"
+  "video.play": "Reproducer",
+  "video.unmute": "Non plus silentiar le sono"
 }
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 102e547d40..8707c08dd3 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -29,6 +29,7 @@
   "account.endorse": "Tampilkan di profil",
   "account.featured_tags.last_status_at": "Kiriman terakhir pada {date}",
   "account.featured_tags.last_status_never": "Tidak ada kiriman",
+  "account.featured_tags.title": "Tagar {name} yang difiturkan",
   "account.follow": "Ikuti",
   "account.follow_back": "Ikuti balik",
   "account.followers": "Pengikut",
@@ -109,6 +110,7 @@
   "bundle_column_error.routing.body": "Laman yang diminta tidak ditemukan. Apakah Anda yakin bahwa URL dalam bilah alamat benar?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Tutup",
+  "bundle_modal_error.message": "Kesalahan terjadi saat memuat komponen ini.",
   "bundle_modal_error.retry": "Coba lagi",
   "closed_registrations.other_server_instructions": "Karena Mastodon itu terdesentralisasi, Anda dapat membuat sebuah akun di server lain dan masih dapat berinteraksi dengan satu ini.",
   "closed_registrations_modal.description": "Membuat sebuah akun di {domain} saat ini tidak memungkinkan, tetapi diingat bahwa Anda tidak harus memiliki sebuah akun secara khusus di {domain} untuk menggunakan Mastodon.",
@@ -156,7 +158,7 @@
   "compose_form.poll.duration": "Durasi japat",
   "compose_form.poll.multiple": "Pilihan ganda",
   "compose_form.poll.option_placeholder": "Opsi {number}",
-  "compose_form.poll.single": "Pilihan tunggal",
+  "compose_form.poll.single": "Pilih Satu",
   "compose_form.poll.switch_to_multiple": "Ubah japat menjadi pilihan ganda",
   "compose_form.poll.switch_to_single": "Ubah japat menjadi pilihan tunggal",
   "compose_form.poll.type": "Gaya",
@@ -210,6 +212,10 @@
   "disabled_account_banner.text": "Akun {disabledAccount} Anda kini dinonaktifkan.",
   "dismissable_banner.community_timeline": "Ini adalah kiriman publik terkini dari orang yang akunnya berada di {domain}.",
   "dismissable_banner.dismiss": "Abaikan",
+  "dismissable_banner.explore_links": "Cerita berita ini sekarang sedang dibicarakan oleh orang di server ini dan lainnya dalam jaringan terdesentralisasi.",
+  "dismissable_banner.explore_statuses": "Ini adalah postingan dari seluruh web sosial yang mendapatkan daya tarik saat ini. Postingan baru dengan lebih banyak peningkatan dan favorit memiliki peringkat lebih tinggi.",
+  "dismissable_banner.explore_tags": "Tagar ini sekarang sedang tren di antara orang di server ini dan lainnya dalam jaringan terdesentralisasi.",
+  "dismissable_banner.public_timeline": "Ini adalah postingan publik dari orang-orang di web sosial yang diikuti oleh {domain}.",
   "domain_block_modal.block": "Blokir server",
   "domain_block_modal.block_account_instead": "Blokir @{name} saja",
   "domain_block_modal.they_can_interact_with_old_posts": "Orang-orang dari server ini dapat berinteraksi dengan kiriman lama anda.",
@@ -261,6 +267,7 @@
   "empty_column.hashtag": "Tidak ada apa pun dalam hashtag ini.",
   "empty_column.home": "Linimasa anda kosong! Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.",
   "empty_column.list": "Belum ada apa pun di daftar ini. Ketika anggota dari daftar ini mengirim kiriman baru, mereka akan tampil di sini.",
+  "empty_column.lists": "Anda belum memiliki daftar. Ketika Anda membuatnya, maka akan muncul di sini.",
   "empty_column.mutes": "Anda belum membisukan siapa pun.",
   "empty_column.notifications": "Anda belum memiliki notifikasi. Ketika orang lain berinteraksi dengan Anda, Anda akan melihatnya di sini.",
   "empty_column.public": "Tidak ada apa pun di sini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisi ini",
@@ -270,6 +277,7 @@
   "error.unexpected_crash.next_steps_addons": "Coba nonaktifkan mereka lalu segarkan halaman. Jika itu tidak membantu, Anda masih bisa memakai Mastodon dengan peramban berbeda atau aplikasi asli.",
   "errors.unexpected_crash.copy_stacktrace": "Salin stacktrace ke papan klip",
   "errors.unexpected_crash.report_issue": "Laporkan masalah",
+  "explore.search_results": "Hasil pencarian",
   "explore.suggested_follows": "Orang",
   "explore.title": "Jelajahi",
   "explore.trending_links": "Berita",
@@ -313,6 +321,7 @@
   "footer.about": "Tentang",
   "footer.directory": "Direktori profil",
   "footer.get_app": "Dapatkan aplikasi",
+  "footer.invite": "Undang orang",
   "footer.keyboard_shortcuts": "Pintasan papan ketik",
   "footer.privacy_policy": "Kebijakan privasi",
   "footer.source_code": "Lihat kode sumber",
@@ -335,6 +344,9 @@
   "home.hide_announcements": "Sembunyikan pengumuman",
   "home.pending_critical_update.link": "Lihat pembaruan",
   "home.show_announcements": "Tampilkan pengumuman",
+  "interaction_modal.description.follow": "Dengan sebuah akun di Mastodon, Anda bisa mengikuti {name} untuk menerima kirimannya di beranda Anda.",
+  "interaction_modal.description.reblog": "Dengan sebuah akun di Mastodon, Anda bisa mem-boost kiriman ini untuk membagikannya ke pengikut Anda sendiri.",
+  "interaction_modal.description.reply": "Dengan sebuah akun di Mastodon, Anda bisa menanggapi kiriman ini.",
   "interaction_modal.on_another_server": "Di server lain",
   "interaction_modal.on_this_server": "Di server ini",
   "interaction_modal.title.follow": "Ikuti {name}",
@@ -381,11 +393,19 @@
   "limited_account_hint.action": "Tetap tampilkan profil",
   "limited_account_hint.title": "Profil ini telah disembunyikan oleh moderator {domain}.",
   "link_preview.author": "Oleh {name}",
+  "lists.account.add": "Tambah ke daftar",
+  "lists.account.remove": "Hapus dari daftar",
   "lists.delete": "Hapus daftar",
   "lists.edit": "Sunting daftar",
+  "lists.edit.submit": "Ubah judul",
+  "lists.new.create": "Tambah daftar",
+  "lists.new.title_placeholder": "Judul daftar baru",
   "lists.replies_policy.followed": "Siapa pun pengguna yang diikuti",
   "lists.replies_policy.list": "Anggota di daftar tersebut",
   "lists.replies_policy.none": "Tidak ada satu pun",
+  "lists.replies_policy.title": "Tampilkan balasan ke:",
+  "lists.search": "Cari di antara orang yang Anda ikuti",
+  "lists.subheading": "Daftar Anda",
   "load_pending": "{count, plural, other {# item baru}}",
   "loading_indicator.label": "Memuat…",
   "moved_to_account_banner.text": "Akun {disabledAccount} Anda kini dinonaktifkan karena Anda pindah ke {movedToAccount}.",
@@ -453,6 +473,23 @@
   "notifications_permission_banner.enable": "Aktifkan notifikasi desktop",
   "notifications_permission_banner.how_to_control": "Untuk menerima notifikasi saat Mastodon terbuka, aktifkan notifikasi desktop. Anda dapat mengendalikan tipe interaksi mana yang ditampilkan notifikasi desktop melalui tombol {icon} di atas saat sudah aktif.",
   "notifications_permission_banner.title": "Jangan lewatkan apa pun",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "Halo #Mastodon!",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.share.title": "Berbagi profil Anda",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "Kau berhasil!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Tulis posting pertama anda",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "Taruh kembali",
   "poll.closed": "Ditutup",
   "poll.refresh": "Segarkan",
@@ -469,6 +506,8 @@
   "privacy_policy.last_updated": "Terakhir diperbarui {date}",
   "privacy_policy.title": "Kebijakan Privasi",
   "refresh": "Segarkan",
+  "regeneration_indicator.label": "Memuat…",
+  "regeneration_indicator.sublabel": "Beranda Anda sedang disiapkan!",
   "relative_time.days": "{number}h",
   "relative_time.full.days": "{number, plural, other {# hari}} yang lalu",
   "relative_time.full.hours": "{number, plural, other {# jam}} yang lalu",
@@ -528,7 +567,9 @@
   "search.search_or_paste": "Cari atau ketik URL",
   "search_results.all": "Semua",
   "search_results.hashtags": "Tagar",
+  "search_results.nothing_found": "Tidak dapat menemukan apa pun untuk istilah-istilah pencarian ini",
   "search_results.statuses": "Kiriman",
+  "search_results.title": "Cari {q}",
   "server_banner.about_active_users": "Orang menggunakan server ini selama 30 hari terakhir (Pengguna Aktif Bulanan)",
   "server_banner.active_users": "pengguna aktif",
   "server_banner.administered_by": "Dikelola oleh:",
@@ -600,7 +641,21 @@
   "upload_button.label": "Tambahkan media",
   "upload_error.limit": "Batas unggah berkas terlampaui.",
   "upload_error.poll": "Unggah berkas tak diizinkan di japat ini.",
+  "upload_form.audio_description": "Penjelasan untuk orang dengan gangguan pendengaran",
+  "upload_form.description": "Deskripsikan untuk mereka yang tidak bisa melihat dengan jelas",
   "upload_form.edit": "Sunting",
+  "upload_form.thumbnail": "Ubah gambar kecil",
+  "upload_form.video_description": "Penjelasan untuk orang dengan gangguan pendengaran atau penglihatan",
+  "upload_modal.analyzing_picture": "Analisis gambar…",
+  "upload_modal.apply": "Terapkan",
+  "upload_modal.applying": "Menerapkan…",
+  "upload_modal.choose_image": "Pilih gambar",
+  "upload_modal.description_placeholder": "Muharjo seorang xenofobia universal yang takut pada warga jazirah, contohnya Qatar",
+  "upload_modal.detect_text": "Deteksi teks pada gambar",
+  "upload_modal.edit_media": "Sunting media",
+  "upload_modal.hint": "Klik atau seret lingkaran pada pratinjau untuk memilih titik fokus yang akan ditampilkan pada semua gambar kecil.",
+  "upload_modal.preparing_ocr": "Menyiapkan OCR…",
+  "upload_modal.preview_label": "Pratinjau ({ratio})",
   "upload_progress.label": "Mengunggah...",
   "upload_progress.processing": "Memproses…",
   "video.close": "Tutup video",
@@ -609,6 +664,8 @@
   "video.expand": "Perbesar video",
   "video.fullscreen": "Layar penuh",
   "video.hide": "Sembunyikan video",
+  "video.mute": "Bisukan suara",
   "video.pause": "Jeda",
-  "video.play": "Putar"
+  "video.play": "Putar",
+  "video.unmute": "Bunyikan suara"
 }
diff --git a/app/javascript/mastodon/locales/ie.json b/app/javascript/mastodon/locales/ie.json
index 7cd463727f..d91c49e83f 100644
--- a/app/javascript/mastodon/locales/ie.json
+++ b/app/javascript/mastodon/locales/ie.json
@@ -28,6 +28,7 @@
   "account.endorse": "Recomandar sur profil",
   "account.featured_tags.last_status_at": "Ultim posta ye {date}",
   "account.featured_tags.last_status_never": "Null postas",
+  "account.featured_tags.title": "Recomandat hashtags de {name}",
   "account.follow": "Sequer",
   "account.follow_back": "Sequer reciprocmen",
   "account.followers": "Sequitores",
@@ -102,6 +103,7 @@
   "bundle_column_error.routing.body": "Li demandat págine ne trovat se. Esque tu es cert que li URL in li adresse-barre es corect?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Cluder",
+  "bundle_modal_error.message": "Alquo errat durant li cargation de ti-ci componente.",
   "bundle_modal_error.retry": "Provar denov",
   "closed_registrations.other_server_instructions": "Pro que Mastodon es decentralisat, on posse crear un conto che un altri servitor e ancor interacter con ti-ci.",
   "closed_registrations_modal.description": "Crear un conto che {domain} ne es possibil actualmen, ma ples memorar que on ne besona un conto specificmen che {domain} por usar Mastodon.",
@@ -149,6 +151,7 @@
   "compose_form.poll.duration": "Duration del balotation",
   "compose_form.poll.multiple": "Selection multiplic",
   "compose_form.poll.option_placeholder": "Option {number}",
+  "compose_form.poll.single": "Selecter un",
   "compose_form.poll.switch_to_multiple": "Changea li balotation por permisser multiplic selectiones",
   "compose_form.poll.switch_to_single": "Changea li balotation por permisser un singul selection",
   "compose_form.poll.type": "Stil",
@@ -193,6 +196,10 @@
   "disabled_account_banner.text": "Tui conto {disabledAccount} es actualmen desactivisat.",
   "dismissable_banner.community_timeline": "Tis-ci es li postas max recent de gente con contos che {domain}.",
   "dismissable_banner.dismiss": "Demisser",
+  "dismissable_banner.explore_links": "Tis-ci es li novas max distribuet che li social retage hodie. Novas plu nov, postat de plu diferent persones, es monstrat plu alt.",
+  "dismissable_banner.explore_statuses": "Tis-ci es postas del social retage queles es popular hodie. Nov postas con plu mult boosts e favorites es monstrat plu alt.",
+  "dismissable_banner.explore_tags": "Tis-ci es hashtags queles es popular che li social retage hodie. Hashtags usat de plu mult persones diferent es monstrat plu alt.",
+  "dismissable_banner.public_timeline": "Tis-ci es li max recent public postas de persones che li social retage quem gente che {domain} seque.",
   "domain_block_modal.block": "Bloccar servitor",
   "domain_block_modal.block_account_instead": "Altrimen, bloccar @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Persones de ti servitor posse interacter con tui old postas.",
@@ -247,6 +254,7 @@
   "empty_column.hashtag": "Hay nullcos en ti-ci hashtag ancor.",
   "empty_column.home": "Tui hemal témpor-linea es vacui! Sequer plu gente por plenar it.",
   "empty_column.list": "Ancor ne hay quocunc in ti-ci liste. Quande membres de ti-ci liste publica nov postas, ili va aparir ci.",
+  "empty_column.lists": "Tu ancor have null listes. Quande tu crea un, it va aparir ci.",
   "empty_column.mutes": "Tu ancor ha silentiat null usatores.",
   "empty_column.notification_requests": "Omnicos clar! Hay necos ci. Nov notificationes va venir ci quande tu recive les secun tui parametres.",
   "empty_column.notifications": "Tu have null notificationes. Quande altri persones interacte con te, tu va vider it ci.",
@@ -257,6 +265,7 @@
   "error.unexpected_crash.next_steps_addons": "Prova desactivisar les e recargar li págine. Si to ne auxilia, tu fórsan posse usar Mastodon per un diferent navigator o aplication.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar cumul-tracie a paperiere",
   "errors.unexpected_crash.report_issue": "Raportar un problema",
+  "explore.search_results": "Resultates de sercha",
   "explore.suggested_follows": "Gente",
   "explore.title": "Explorar",
   "explore.trending_links": "Novas",
@@ -304,6 +313,7 @@
   "footer.about": "Information",
   "footer.directory": "Profilarium",
   "footer.get_app": "Obtener li aplication",
+  "footer.invite": "Invitar gente",
   "footer.keyboard_shortcuts": "Rapid-tastes",
   "footer.privacy_policy": "Politica pri privatie",
   "footer.source_code": "Vider li fonte-code",
@@ -332,8 +342,17 @@
   "home.pending_critical_update.link": "Vider actualisationes",
   "home.pending_critical_update.title": "Urgent actualisation de securitá disponibil!",
   "home.show_announcements": "Monstrar proclamationes",
+  "interaction_modal.description.favourite": "Con un conto de Mastodon, tu posse favoritisar ti-ci posta por informar li autor pri quant mult tu aprecia it e conservar it por plu tard.",
+  "interaction_modal.description.follow": "Con un conto de Mastodon, tu posse sequer {name} por reciver su postas in tui hemal témpor-linea.",
+  "interaction_modal.description.reblog": "Con un conto de Mastodon, tu posse boostar ti-ci posta por distribuer it a tui propri sequitores.",
+  "interaction_modal.description.reply": "Con un conto de Mastodon, tu posse responder a ti-ci posta.",
+  "interaction_modal.login.action": "Retorna a hem",
+  "interaction_modal.login.prompt": "Dominia de tui hemal servitor, p.ex. mastodon.social",
+  "interaction_modal.no_account_yet": "Ne sur Mastodon?",
   "interaction_modal.on_another_server": "Sur un servitor diferent",
   "interaction_modal.on_this_server": "Sur ti-ci servitor",
+  "interaction_modal.sign_in": "Tu ne ha initiat session che ti-ci servitor. U logia tui conto?",
+  "interaction_modal.sign_in_hint": "Nota: To es li websitu u tu adheret. Si tu ne rememora, sercha li benevenit-email in tui inbuxe. Tu anc posse introducter tui plen usator-nómine! (p.ex. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Favoritisar li posta de {name}",
   "interaction_modal.title.follow": "Sequer {name}",
   "interaction_modal.title.reblog": "Boostar li posta de {name}",
@@ -381,11 +400,20 @@
   "limited_account_hint.action": "Monstrar profil totvez",
   "limited_account_hint.title": "Ti-ci profil ha esset celat del moderatores de {domain}.",
   "link_preview.author": "De {name}",
+  "lists.account.add": "Adjunter a liste",
+  "lists.account.remove": "Remover de liste",
   "lists.delete": "Deleter liste",
   "lists.edit": "Redacter liste",
+  "lists.edit.submit": "Changear titul",
+  "lists.exclusive": "Celar ti-ci postas del hemal témpor-linea",
+  "lists.new.create": "Adjunter liste",
+  "lists.new.title_placeholder": "Titul del nov liste",
   "lists.replies_policy.followed": "Quelcunc sequet usator",
   "lists.replies_policy.list": "Membres del liste",
   "lists.replies_policy.none": "Nequi",
+  "lists.replies_policy.title": "Monstrar responses a:",
+  "lists.search": "Serchar inter li persones quem tu seque",
+  "lists.subheading": "Tui listes",
   "load_pending": "{count, plural, one {# nov element} other {# nov elementes}}",
   "loading_indicator.label": "Cargant…",
   "moved_to_account_banner.text": "Tui conto {disabledAccount} es actualmen desactivisat pro que tu movet te a {movedToAccount}.",
@@ -495,17 +523,44 @@
   "notifications_permission_banner.enable": "Activisar notificationes sur li computator",
   "notifications_permission_banner.how_to_control": "Por reciver notificationes quande Mastodon ne es apert, activisa notificationes sur li computator. Tu posse decider precisimen quel species de interactiones genera notificationes per li buton {icon} in-supra quande ili es activisat.",
   "notifications_permission_banner.title": "Nequande preterlassa quocunc",
+  "onboarding.action.back": "Retroear",
+  "onboarding.actions.back": "Retroear",
+  "onboarding.actions.go_to_explore": "Ear a vider lu populari",
+  "onboarding.actions.go_to_home": "Ear al hemal témpor-linea",
+  "onboarding.compose.template": "Salute #Mastodon!",
   "onboarding.follows.empty": "Ínfortunatmen, null resultates posse esser monstrat actualmen. Tu posse provar serchar o usar li \"Explorar\" págine por trovar gente por sequer, o prova denov plu tard.",
+  "onboarding.follows.lead": "Tui hemal témpor-linea es li primari maniere de experir Mastodon. Plu persones quem tu seque, plu activ e interessant it va esser. Por auxiliar te comensar, vi quelc suggestiones:",
+  "onboarding.follows.title": "Personalisar tui hemal témpor-linea",
   "onboarding.profile.discoverable": "Fa mi profil decovribil",
   "onboarding.profile.discoverable_hint": "Quande tu opta esser decovribil in Mastodon, tui postas posse aparir in resultates de sercha e tendenties, e tui profil posse esser suggestet a persones con interesses simil a tui.",
   "onboarding.profile.display_name": "Nómine a monstrar",
   "onboarding.profile.display_name_hint": "Tui complet nómine o tui amusant nómine…",
+  "onboarding.profile.lead": "Tu sempre posse completar ti-ci plu tard in li parametres, u mem plu optiones de customisation es disponibil.",
   "onboarding.profile.note": "Biografie",
   "onboarding.profile.note_hint": "Tu posse @mentionar altri persones o #hashtags…",
   "onboarding.profile.save_and_continue": "Conservar e avansar",
   "onboarding.profile.title": "Popular tu profil",
   "onboarding.profile.upload_avatar": "Cargar profil-portrete",
   "onboarding.profile.upload_header": "Cargar cap-image",
+  "onboarding.share.lead": "Di gente qualmen ili posse trovar te che Mastodon!",
+  "onboarding.share.message": "Yo es {username} che #Mastodon! Veni e seque me a {url}",
+  "onboarding.share.next_steps": "Possibil sequent passus:",
+  "onboarding.share.title": "Partir tui profil",
+  "onboarding.start.lead": "Tu es ja un parte de Mastodon, un unic, decentralisat platform de medie social in quel tu—ne un algoritme—selectiona tui propri experientie. Lass nos departer sur un nov frontiera social:",
+  "onboarding.start.skip": "Auxilie por comensar ne besonat?",
+  "onboarding.start.title": "Tu ha successat!",
+  "onboarding.steps.follow_people.body": "Sequer interessant gente es to quo importa in Mastodon.",
+  "onboarding.steps.follow_people.title": "Personalisar tui hemal témpor-linea",
+  "onboarding.steps.publish_status.body": "Saluta li munde con text, images, videos o balotationes {emoji}",
+  "onboarding.steps.publish_status.title": "Crear tui unesim posta",
+  "onboarding.steps.setup_profile.body": "Ascresce tui interactiones per haver un profil detalliat.",
+  "onboarding.steps.setup_profile.title": "Personalisar tui profil",
+  "onboarding.steps.share_profile.body": "Di tui amics qualmen trovar te che Mastodon",
+  "onboarding.steps.share_profile.title": "Partir tui profil Mastodon",
+  "onboarding.tips.2fa": "<strong>Savet tu?</strong> Tu posse securisar tui conto per activisar 2-factor autentication in tui parametres de conto. Ti functiona con quelcunc aplication TOTP quel tu selecte, null númere de telefon besonat!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Savet tu?</strong> Pro que Mastodon es decentralisat, quelc profiles queles tu trova va esser logiat che servitores altri quam tui. Totvez tu posse interacter con les sin grates! Lor servitores es in li duesim demí de lor usator-nómines!",
+  "onboarding.tips.migration": "<strong>Savet tu?</strong> Si tu senti que {domain} ne es un bonissim servitor por te futurimen, tu posse mover te a un altri Mastodon-servitor sin perdir tui sequitores. Tu posse mem etablisser tui propri servitor!",
+  "onboarding.tips.verification": "<strong>Savet tu?</strong> Tu posse verificar tui conto per metter un ligament a tui Mastodon-profil in tui propri websitu e adjunter li websitu a tui profil. Null payament o documentes besonat!",
   "password_confirmation.exceeds_maxlength": "Confirmation de passa-parol transpassa li maxim longore de passa-paroles",
   "password_confirmation.mismatching": "Confirmation de passa-parol ne egala",
   "picture_in_picture.restore": "Restaurar",
@@ -521,6 +576,7 @@
   "poll_button.remove_poll": "Remover balotation",
   "privacy.change": "Changear li privatie del posta",
   "privacy.direct.long": "Omnes mentionat in li posta",
+  "privacy.direct.short": "Specific persones",
   "privacy.private.long": "Solmen tui sequitores",
   "privacy.private.short": "Sequitores",
   "privacy.public.long": "Quicunc in e ex Mastodon",
@@ -532,6 +588,8 @@
   "privacy_policy.title": "Politica pri Privatie",
   "recommended": "Recomandat",
   "refresh": "Recargar",
+  "regeneration_indicator.label": "Cargant…",
+  "regeneration_indicator.sublabel": "On es preparant tui hemal témpor-linea!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "Ante {number, plural, one {# die} other {# dies}}",
   "relative_time.full.hours": "Ante {number, plural, one {# hor} other {# hores}}",
@@ -611,8 +669,10 @@
   "search_results.accounts": "Profiles",
   "search_results.all": "Omni",
   "search_results.hashtags": "Hashtags",
+  "search_results.nothing_found": "Trovat se nullcos por ti término de sercha",
   "search_results.see_all": "Vider omni",
   "search_results.statuses": "Postas",
+  "search_results.title": "Sercha por {q}",
   "server_banner.about_active_users": "Gente usant ti-ci servitor durant li ultim 30 dies (Mensual Activ Usatores)",
   "server_banner.active_users": "activ usatores",
   "server_banner.administered_by": "Administrat de:",
@@ -694,7 +754,21 @@
   "upload_button.label": "Adjunter images, un video o un audio-file",
   "upload_error.limit": "Límite de medie-cargationes transpassat.",
   "upload_error.poll": "On ne es permisset cargar medie con balotationes.",
+  "upload_form.audio_description": "Descrir por persones qui es surd o ne audi bon",
+  "upload_form.description": "Descrir por persones qui es ciec o have mal vision",
   "upload_form.edit": "Redacter",
+  "upload_form.thumbnail": "Changear previsual image",
+  "upload_form.video_description": "Descrir por persones qui es surd, ciec, ne audi bon, o have mal vision",
+  "upload_modal.analyzing_picture": "Analisant image…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.applying": "Aplicant…",
+  "upload_modal.choose_image": "Selecter image",
+  "upload_modal.description_placeholder": "Li Europan lingues es membres del sam familie. Lor separat existentie es un mite",
+  "upload_modal.detect_text": "Detecter text del image",
+  "upload_modal.edit_media": "Redacter medie",
+  "upload_modal.hint": "Clicca o trena li circul por selecter li focal punctu quel va esser sempre visibil in omni previse-images.",
+  "upload_modal.preparing_ocr": "Preparant OCR…",
+  "upload_modal.preview_label": "Previse ({ratio})",
   "upload_progress.label": "Cargant...",
   "upload_progress.processing": "Tractant…",
   "username.taken": "Ti usator-nómine es ja prendet. Trova altri",
@@ -704,6 +778,8 @@
   "video.expand": "Expander video",
   "video.fullscreen": "Plen-ecran",
   "video.hide": "Celar video",
+  "video.mute": "Silentiar li son",
   "video.pause": "Pausar",
-  "video.play": "Reproducter"
+  "video.play": "Reproducter",
+  "video.unmute": "Dessilentiar li son"
 }
diff --git a/app/javascript/mastodon/locales/ig.json b/app/javascript/mastodon/locales/ig.json
index 07110813f9..852fc68b4e 100644
--- a/app/javascript/mastodon/locales/ig.json
+++ b/app/javascript/mastodon/locales/ig.json
@@ -13,7 +13,6 @@
   "account.unfollow": "Kwụsị iso",
   "account_note.placeholder": "Click to add a note",
   "admin.dashboard.retention.cohort_size": "Ojiarụ ọhụrụ",
-  "annual_report.summary.new_posts.new_posts": "edemede ọhụrụ",
   "audio.hide": "Zoo ụda",
   "bundle_column_error.retry": "Nwaa ọzọ",
   "bundle_column_error.routing.title": "404",
@@ -37,6 +36,7 @@
   "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
   "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
   "compose_form.placeholder": "What is on your mind?",
+  "compose_form.poll.single": "Họrọ otu",
   "compose_form.publish_form": "Publish",
   "compose_form.reply": "Zaa",
   "compose_form.spoiler.marked": "Text is hidden behind warning",
@@ -52,6 +52,8 @@
   "conversation.delete": "Hichapụ nkata",
   "conversation.open": "Lelee nkata",
   "disabled_account_banner.account_settings": "Mwube akaụntụ",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "domain_pill.username": "Ahaojiaru",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "emoji_button.activity": "Mmemme",
@@ -108,6 +110,7 @@
   "lightbox.close": "Mechie",
   "lists.delete": "Hichapụ ndepụta",
   "lists.edit": "Dezie ndepụta",
+  "lists.subheading": "Ndepụta gị",
   "navigation_bar.about": "Maka",
   "navigation_bar.bookmarks": "Ebenrụtụakā",
   "navigation_bar.discover": "Chọpụta",
@@ -117,6 +120,19 @@
   "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
   "notification.reblog": "{name} boosted your status",
   "notifications.column_settings.status": "Edemede ọhụrụ:",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Kekọrịta profaịlụ Mastọdọnụ gị",
   "privacy.change": "Adjust status privacy",
   "relative_time.full.just_now": "kịta",
   "relative_time.just_now": "kịta",
@@ -149,5 +165,9 @@
   "tabs_bar.notifications": "Nziọkwà",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
   "trends.trending_now": "Na-ewu ewu kịta",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
+  "upload_modal.choose_image": "Họrọ onyonyo",
   "upload_progress.label": "Uploading…"
 }
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index 596ca4c3fe..e9ec64d954 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -4,7 +4,7 @@
   "about.disclaimer": "Mastodon esas libera, publikfonta e komercmarko di Mastodon gGmbH.",
   "about.domain_blocks.no_reason_available": "Expliko nedisponebla",
   "about.domain_blocks.preamble": "Mastodon generale permisas on vidar kontenajo e interagar kun uzanti de irga altra servilo en fediverso. Existas eceptioni quo facesis che ca partikulara servilo.",
-  "about.domain_blocks.silenced.explanation": "On generale ne vidar profili e enhavajo de ca servilo, se on ne intence serchar o voleskar per sequar.",
+  "about.domain_blocks.silenced.explanation": "On generale ne vidar profili e kontenajo de ca servilo, se on ne reale trovar o voluntale juntar per sequar.",
   "about.domain_blocks.silenced.title": "Limitizita",
   "about.domain_blocks.suspended.explanation": "Nula informi de ca servili procedagesos o retenesos o interchanjesos, do irga interago o komuniko kun uzanti de ca servili esas neposibla.",
   "about.domain_blocks.suspended.title": "Restriktita",
@@ -12,7 +12,7 @@
   "about.powered_by": "Necentraligita sociala ret quo povigesas da {mastodon}",
   "about.rules": "Servilreguli",
   "account.account_note_header": "Personala noto",
-  "account.add_or_remove_from_list": "Adjuntar o forigar de listi",
+  "account.add_or_remove_from_list": "Insertez o removez de listi",
   "account.badges.bot": "Boto",
   "account.badges.group": "Grupo",
   "account.block": "Blokusar @{name}",
@@ -24,11 +24,12 @@
   "account.direct": "Private mencionez @{name}",
   "account.disable_notifications": "Cesez avizar me kande @{name} postas",
   "account.domain_blocked": "Domain hidden",
-  "account.edit_profile": "Redaktar profilo",
+  "account.edit_profile": "Modifikar profilo",
   "account.enable_notifications": "Avizez me kande @{name} postas",
   "account.endorse": "Traito di profilo",
   "account.featured_tags.last_status_at": "Antea posto ye {date}",
   "account.featured_tags.last_status_never": "Nula posti",
+  "account.featured_tags.title": "Estalita hashtagi di {name}",
   "account.follow": "Sequar",
   "account.follow_back": "Anke sequez",
   "account.followers": "Sequanti",
@@ -44,7 +45,7 @@
   "account.languages": "Chanjez abonita lingui",
   "account.link_verified_on": "Proprieteso di ca ligilo kontrolesis ye {date}",
   "account.locked_info": "La privatesostaco di ca konto fixesas quale lokata. Proprietato manue kontrolas personi qui povas sequar.",
-  "account.media": "Audvidaji",
+  "account.media": "Medio",
   "account.mention": "Mencionar @{name}",
   "account.moved_to": "{name} indikis ke lua nova konto es nune:",
   "account.mute": "Celar @{name}",
@@ -55,7 +56,7 @@
   "account.no_bio": "Deskriptajo ne provizesis.",
   "account.open_original_page": "Apertez originala pagino",
   "account.posts": "Mesaji",
-  "account.posts_with_replies": "Afishi e respondi",
+  "account.posts_with_replies": "Posti e respondi",
   "account.report": "Denuncar @{name}",
   "account.requested": "Vartante aprobo",
   "account.requested_follow": "{name} demandis sequar tu",
@@ -85,38 +86,12 @@
   "alert.unexpected.message": "Neexpektita eroro eventis.",
   "alert.unexpected.title": "Problemo!",
   "alt_text_badge.title": "Alternativa texto",
-  "alt_text_modal.add_alt_text": "Adjuntar alternativa texto",
-  "alt_text_modal.add_text_from_image": "Adjuntar texto de imajo",
-  "alt_text_modal.cancel": "Nuligar",
-  "alt_text_modal.change_thumbnail": "Chanjar imajeto",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Priskribar co por personi kun auddeskapableso…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Priskribar co por personi kun viddeskapableso…",
-  "alt_text_modal.done": "Finis",
   "announcement.announcement": "Anunco",
-  "annual_report.summary.archetype.booster": "La plurrepetanto",
-  "annual_report.summary.archetype.lurker": "La plurcelanto",
-  "annual_report.summary.archetype.oracle": "La pluraktivo",
-  "annual_report.summary.archetype.pollster": "La votinquestoiganto",
-  "annual_report.summary.archetype.replier": "La plurrespondanto",
-  "annual_report.summary.followers.followers": "sequanti",
-  "annual_report.summary.followers.total": "{count} sumo",
-  "annual_report.summary.here_it_is": "Caibe es vua rivido ye {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "maxim prizita mesajo",
-  "annual_report.summary.highlighted_post.by_reblogs": "maxim repetita mesajo",
-  "annual_report.summary.highlighted_post.by_replies": "mesajo kun la maxim multa respondi",
-  "annual_report.summary.highlighted_post.possessive": "di {name}",
-  "annual_report.summary.most_used_app.most_used_app": "maxim uzita aplikajo",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "maxim uzita gretvorto",
-  "annual_report.summary.most_used_hashtag.none": "Nulo",
-  "annual_report.summary.new_posts.new_posts": "nova afishi",
-  "annual_report.summary.percentile.text": "<topLabel>To pozas vu sur la supro </topLabel><percentage></percentage><bottomLabel>di uzanti di {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Ni ne dicas ad Bernio.",
-  "annual_report.summary.thanks": "Danki por partoprenar sur Mastodon!",
   "attachments_list.unprocessed": "(neprocedita)",
   "audio.hide": "Celez audio",
   "block_modal.remote_users_caveat": "Ni questionos {domain} di la servilo por respektar vua decido. Publika posti forsan ankore estas videbla a neenirinta uzanti.",
   "block_modal.show_less": "Montrar mine",
-  "block_modal.show_more": "Montrar plu",
+  "block_modal.show_more": "Montrar plue",
   "block_modal.they_cant_mention": "Oli ne povas mencionar o sequar vu.",
   "block_modal.they_cant_see_posts": "Oli ne povas vidar vua mesaji e vu ne vidos vidar olia.",
   "block_modal.they_will_know": "Oli povas vidar ke oli esas blokusita.",
@@ -135,7 +110,7 @@
   "bundle_column_error.routing.body": "Demandita pagino ne povas trovesar. Ka vu certe ke URL en situobuxo esar korekta?",
   "bundle_column_error.routing.title": "Eroro di 404",
   "bundle_modal_error.close": "Klozez",
-  "bundle_modal_error.message": "Ulo nefuncionas dum chargar ca skreno.",
+  "bundle_modal_error.message": "Nulo ne functionis dum chargar ca kompozaj.",
   "bundle_modal_error.retry": "Probez itere",
   "closed_registrations.other_server_instructions": "Nam Mastodon es descentraligita, on povas krear konto che altra servilo e senegarde interagar kun ca servilo.",
   "closed_registrations_modal.description": "Nune on ne povas krear konto che {domain}, ma voluntez savar ke on ne bezonas konto specifike che {domain} por uzar Mastodon.",
@@ -144,22 +119,19 @@
   "closed_registrations_modal.title": "Krear konto che Mastodon",
   "column.about": "Pri co",
   "column.blocks": "Blokusita uzeri",
-  "column.bookmarks": "Lektosigni",
+  "column.bookmarks": "Libromarki",
   "column.community": "Lokala tempolineo",
-  "column.create_list": "Krear listo",
   "column.direct": "Privata mencioni",
   "column.directory": "Videz profili",
-  "column.domain_blocks": "Blokusita domeni",
-  "column.edit_list": "Redaktar listo",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoriziti",
   "column.firehose": "Nuna flui",
   "column.follow_requests": "Demandi di sequado",
   "column.home": "Hemo",
-  "column.list_members": "Administrar listomembri",
   "column.lists": "Listi",
   "column.mutes": "Celita uzeri",
   "column.notifications": "Savigi",
-  "column.pins": "Adpinglita afishi",
+  "column.pins": "Pinned toot",
   "column.public": "Federata tempolineo",
   "column_back_button.label": "Retro",
   "column_header.hide_settings": "Celez ajusti",
@@ -168,28 +140,27 @@
   "column_header.pin": "Pinglagez",
   "column_header.show_settings": "Montrez ajusti",
   "column_header.unpin": "Depinglagez",
-  "column_search.cancel": "Nuligar",
   "column_subheading.settings": "Ajusti",
   "community.column_settings.local_only": "Lokala nur",
-  "community.column_settings.media_only": "Nur audvidaji",
+  "community.column_settings.media_only": "Media only",
   "community.column_settings.remote_only": "Fora nur",
   "compose.language.change": "Chanjez linguo",
   "compose.language.search": "Trovez linguo...",
   "compose.published.body": "Posto publikigita.",
   "compose.published.open": "Apertez",
   "compose.saved.body": "Posto konservita.",
-  "compose_form.direct_message_warning_learn_more": "Lernez plu",
+  "compose_form.direct_message_warning_learn_more": "Lernez pluse",
   "compose_form.encryption_warning": "Posti en Mastodon ne intersequante chifrigesas. Ne partigez irga privata informo che Mastodon.",
-  "compose_form.hashtag_warning": "Ca afisho ne listigesos kun irga gretvorto pro ke ol ne es publika.",
+  "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
   "compose_form.lock_disclaimer": "Vua konto ne esas {locked}. Irgu povas sequar vu por vidar vua sequanto-nura posti.",
   "compose_form.lock_disclaimer.lock": "klefagesas",
-  "compose_form.placeholder": "Quon vu pensas?",
-  "compose_form.poll.duration": "Votinquestoduro",
+  "compose_form.placeholder": "Quo esas en tua spirito?",
+  "compose_form.poll.duration": "Votpostoduro",
   "compose_form.poll.multiple": "Multopla selekteso",
   "compose_form.poll.option_placeholder": "Selektato {number}",
-  "compose_form.poll.single": "Unopla selektato",
-  "compose_form.poll.switch_to_multiple": "Chanjar votinquesto por permisar multopla selektati",
-  "compose_form.poll.switch_to_single": "Chanjez votinquesto por permisar unopla selektato",
+  "compose_form.poll.single": "Selektez un",
+  "compose_form.poll.switch_to_multiple": "Chanjez votposto por permisar multiselektaji",
+  "compose_form.poll.switch_to_single": "Chanjez votposto por permisar una selektajo",
   "compose_form.poll.type": "Stilo",
   "compose_form.publish": "Posto",
   "compose_form.publish_form": "Publish",
@@ -206,21 +177,14 @@
   "confirmations.delete_list.confirm": "Efacez",
   "confirmations.delete_list.message": "Ka vu certe volas netempale efacar ca listo?",
   "confirmations.delete_list.title": "Ka efacar listo?",
-  "confirmations.discard_edit_media.confirm": "Forigar",
-  "confirmations.discard_edit_media.message": "Vu havas nekonservita chanji di audvidajpriskribo o prevido, ka forigas ili irgakaze?",
+  "confirmations.discard_edit_media.confirm": "Efacez",
+  "confirmations.discard_edit_media.message": "Vu havas nesparita chanji di mediodeskript o prevido, vu volas jus efacar?",
   "confirmations.edit.confirm": "Modifikez",
   "confirmations.edit.message": "Modifikar nun remplasos la mesajo quon vu nune skribas. Ka vu certe volas procedar?",
   "confirmations.edit.title": "Ka remplasar posto?",
-  "confirmations.follow_to_list.confirm": "Sequar e adjuntar ad listo",
-  "confirmations.follow_to_list.message": "Vu bezonas sequar {name} por adjuntar lu ad listo.",
-  "confirmations.follow_to_list.title": "Ka sequar uzanto?",
   "confirmations.logout.confirm": "Ekirez",
   "confirmations.logout.message": "Ka tu certe volas ekirar?",
   "confirmations.logout.title": "Ka ekirar?",
-  "confirmations.missing_alt_text.confirm": "Adjuntar alternativa texto",
-  "confirmations.missing_alt_text.message": "Vua afisho enhavas audvidaji sen alternativa texto.",
-  "confirmations.missing_alt_text.secondary": "Mesajar irgamaniere",
-  "confirmations.missing_alt_text.title": "Ka adjuntar alternativa texto?",
   "confirmations.mute.confirm": "Silencigez",
   "confirmations.redraft.confirm": "Efacez e riskisez",
   "confirmations.redraft.message": "Ka vu certe volas efacar ca posto e riskisigar ol? Favoriziti e repeti esos perdita, e respondi al posto originala esos orfanigita.",
@@ -233,7 +197,6 @@
   "confirmations.unfollow.title": "Ka dessequar uzanto?",
   "content_warning.hide": "Celez posto",
   "content_warning.show": "Montrez nur",
-  "content_warning.show_more": "Montrar plu",
   "conversation.delete": "Efacez konverso",
   "conversation.mark_as_read": "Markizez quale lektita",
   "conversation.open": "Videz konverso",
@@ -249,10 +212,10 @@
   "disabled_account_banner.text": "Vua konto {disabledAccount} es nune desaktivigita.",
   "dismissable_banner.community_timeline": "Co esas maxim recenta publika posti de personi quo havas konto quo hostigesas da {domain}.",
   "dismissable_banner.dismiss": "Ignorez",
-  "dismissable_banner.explore_links": "Ca novaji es maxim partigita sur fediverso hodie.",
-  "dismissable_banner.explore_statuses": "Ca afishi di tota fediverso populareskis hodie.",
-  "dismissable_banner.explore_tags": "Ca gretvorti populareskas sur fediverso hodie.",
-  "dismissable_banner.public_timeline": "Co es la maxim lastatempa publika afishi di personi sur la fediverso qua personi di {domain} sequas.",
+  "dismissable_banner.explore_links": "Ca nova rakonti parolesas da personi che ca e altra servili di necentraligita situo nun.",
+  "dismissable_banner.explore_statuses": "Yen posti del tota reto sociala qui esas populara hodie. Posti plu nova kun plu repeti e favoriziti esas rangizita plu alte.",
+  "dismissable_banner.explore_tags": "Ca hashtagi bezonas plu famoza inter personi che ca e altra servili di la necentraligita situo nun.",
+  "dismissable_banner.public_timeline": "Yen la posti maxim recenta da personi che la reto sociala quin personi che {domain} sequas.",
   "domain_block_modal.block": "Blokusez servilo",
   "domain_block_modal.block_account_instead": "Blokusez @{name} vice",
   "domain_block_modal.they_can_interact_with_old_posts": "Personi de ca servilo povas interagar kun vua desnova posti.",
@@ -282,7 +245,7 @@
   "emoji_button.custom": "Kustumizato",
   "emoji_button.flags": "Flagi",
   "emoji_button.food": "Manjajo & Drinkajo",
-  "emoji_button.label": "Enpozar emocimajo",
+  "emoji_button.label": "Insertar emoji",
   "emoji_button.nature": "Naturo",
   "emoji_button.not_found": "Nula tala parigata emojii",
   "emoji_button.objects": "Kozi",
@@ -300,15 +263,16 @@
   "empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
   "empty_column.community": "La lokala tempolineo esas vakua. Skribez ulo publike por iniciar la agiveso!",
   "empty_column.direct": "Vu ankore ne havas irga direta mesaji. Kande vu sendos o recevos un, ol montresos hike.",
-  "empty_column.domain_blocks": "Ne havas blokusita domeni ankore.",
-  "empty_column.explore_statuses": "Nulo populareskas nun.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.explore_statuses": "Nulo esas tendenca nun. Videz itere pose!",
   "empty_column.favourited_statuses": "Vu ankore ne havas irga posti favorizita. Kande vu favorizos un, ol montresos hike.",
   "empty_column.favourites": "Nulu favorizis ca posto. Kande ulu favorizis ol, lu montresos hike.",
   "empty_column.follow_requests": "Vu ne havas irga sequodemandi til nun. Kande vu ganas talo, ol montresos hike.",
   "empty_column.followed_tags": "Vu ankore ne sequis irga hashtago. Kande vu sequos un, ol montresos hike.",
   "empty_column.hashtag": "Esas ankore nulo en ta gretovorto.",
-  "empty_column.home": "Vua hemtempolineo esas desplena!",
+  "empty_column.home": "Vua hemtempolineo esas vakua! Sequez plu multa personi por plenigar lu. {suggestions}",
   "empty_column.list": "There is nothing in this list yet.",
+  "empty_column.lists": "Vu ne havas irga listi til nun. Kande vu kreas talo, ol montresos hike.",
   "empty_column.mutes": "Vu ne silencigis irga uzanti til nun.",
   "empty_column.notification_requests": "Finis. Kande vu recevas nova savigi, oli aparos hike segun vua preferaji.",
   "empty_column.notifications": "Tu havas ankore nula savigo. Komunikez kun altri por debutar la konverso.",
@@ -316,21 +280,22 @@
   "error.unexpected_crash.explanation": "Pro eroro en nia kodexo o vidilkonciliebloproblemo, ca pagino ne povas korekte montresar.",
   "error.unexpected_crash.explanation_addons": "Ca pagino ne povas korekte montresar. Ca eroro posible kauzigesas vidilplusajo o automata tradukutensili.",
   "error.unexpected_crash.next_steps": "Probez rifreshar pagino. Se to ne helpas, vu forsan ankore povas uzar Mastodon per diferenta vidilo o provizita softwaro.",
-  "error.unexpected_crash.next_steps_addons": "Probez desebligar ili e rifreshar la pagino.",
+  "error.unexpected_crash.next_steps_addons": "Probez desaktivigar e rifreshar pagino. Se to ne helpas, vu forsan ankore povas uzar Mastodon per diferenta vidilo o provizita softwaro.",
   "errors.unexpected_crash.copy_stacktrace": "Kopiez amastraso a klipplanko",
   "errors.unexpected_crash.report_issue": "Reportigez problemo",
+  "explore.search_results": "Trovuri",
   "explore.suggested_follows": "Personi",
   "explore.title": "Explorez",
   "explore.trending_links": "Novaji",
   "explore.trending_statuses": "Posti",
   "explore.trending_tags": "Hashtagi",
-  "filter_modal.added.context_mismatch_explanation": "Ca filtrilgrupo ne uzesis ad informo di ca adirita afisho.",
+  "filter_modal.added.context_mismatch_explanation": "Ca filtrilgrupo ne relatesas kun informo de ca acesesita posto. Se vu volas posto filtresar kun ca informo anke, vu bezonas modifikar filtrilo.",
   "filter_modal.added.context_mismatch_title": "Kontenajneparigeso!",
   "filter_modal.added.expired_explanation": "Ca filtrilgrupo expiris, vu bezonas chanjar expirtempo por apliko.",
   "filter_modal.added.expired_title": "Expirinta filtrilo!",
-  "filter_modal.added.review_and_configure": "Por kontrolar e plu ajustar ca filtrilgrupo, irez a {settings_link}.",
-  "filter_modal.added.review_and_configure_title": "Filtrilpreferaji",
-  "filter_modal.added.settings_link": "preferajpagino",
+  "filter_modal.added.review_and_configure": "Por kontrolar e plue ajustar ca filtrilgrupo, irez a {settings_link}.",
+  "filter_modal.added.review_and_configure_title": "Filtrilopcioni",
+  "filter_modal.added.settings_link": "opcionpagino",
   "filter_modal.added.short_explanation": "Ca posto adjuntesas a ca filtrilgrupo: {title}.",
   "filter_modal.added.title": "Filtrilo adjuntesas!",
   "filter_modal.select_filter.context_mismatch": "ne relatesas kun ca informo",
@@ -340,7 +305,6 @@
   "filter_modal.select_filter.subtitle": "Usez disponebla grupo o kreez novajo",
   "filter_modal.select_filter.title": "Filtragez ca posto",
   "filter_modal.title.status": "Filtragez posto",
-  "filter_warning.matches_filter": "Samas kam filtrilo \"<span>{title}</span>\"",
   "filtered_notifications_banner.pending_requests": "De {count, plural,=0 {nulu} one {1 persono} other {# personi}} quan vu forsan konocas",
   "filtered_notifications_banner.title": "Filtrilita savigi",
   "firehose.all": "Omno",
@@ -350,7 +314,7 @@
   "follow_request.reject": "Refuzar",
   "follow_requests.unlocked_explanation": "Quankam vua konto ne klefklozesis, la {domain} laborero pensas ke vu forsan volas kontralar sequodemandi de ca konti manuale.",
   "follow_suggestions.curated_suggestion": "Selektato de jeranto",
-  "follow_suggestions.dismiss": "Ne montrez denove",
+  "follow_suggestions.dismiss": "Ne montrez pluse",
   "follow_suggestions.featured_longer": "Selektesis da la grupo di {domain}",
   "follow_suggestions.friends_of_friends_longer": "Populara inter personi quan vu sequas",
   "follow_suggestions.hints.featured": "Ca profilo selektesis da la grupo di {domain}.",
@@ -368,19 +332,18 @@
   "footer.about": "Pri co",
   "footer.directory": "Profilcheflisto",
   "footer.get_app": "Obtenez la softwaro",
+  "footer.invite": "Invitez personi",
   "footer.keyboard_shortcuts": "Kombini di klavi",
   "footer.privacy_policy": "Guidilo pri privateso",
   "footer.source_code": "Vidar la fontokodexo",
   "footer.status": "Stando",
-  "footer.terms_of_service": "Serveskondicioni",
   "generic.saved": "Sparesis",
   "getting_started.heading": "Debuto",
-  "hashtag.admin_moderation": "Desklozar administrointervizajo por #{name}",
   "hashtag.column_header.tag_mode.all": "e {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
   "hashtag.column_header.tag_mode.none": "sen {additional}",
   "hashtag.column_settings.select.no_options_message": "Nula sugestati",
-  "hashtag.column_settings.select.placeholder": "Insertez gretvorti…",
+  "hashtag.column_settings.select.placeholder": "Insertez hashtagi…",
   "hashtag.column_settings.tag_mode.all": "Omna co",
   "hashtag.column_settings.tag_mode.any": "Irga co",
   "hashtag.column_settings.tag_mode.none": "Nula co",
@@ -388,8 +351,8 @@
   "hashtag.counter_by_accounts": "{count, plural, one {{counter} partoprenanto} other {{counter} partoprenanti}}",
   "hashtag.counter_by_uses": "{count, plural, one {{counter} posto} other {{counter} posti}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} posto} other {{counter} posti}} hodie",
-  "hashtag.follow": "Sequar gretvorto",
-  "hashtag.unfollow": "Dessequar gretvorto",
+  "hashtag.follow": "Sequez hashtago",
+  "hashtag.unfollow": "Desequez hashtago",
   "hashtags.and_other": "…e {count, plural, one {# plusa}other {# plusa}}",
   "hints.profiles.followers_may_be_missing": "Sequanti di ca profilo forsan ne esas hike.",
   "hints.profiles.follows_may_be_missing": "Sequati di ca profilo forsan ne esas hike.",
@@ -417,23 +380,21 @@
   "ignore_notifications_modal.not_followers_title": "Ka ignorar savigi de personi qua ne sequas vu?",
   "ignore_notifications_modal.not_following_title": "Ka ignorar savigi de personi quan vu ne sequas?",
   "ignore_notifications_modal.private_mentions_title": "Ka ignorar savigi de nekonocita privata mencionii?",
-  "info_button.label": "Helpo",
-  "info_button.what_is_alt_text": "<h1>Quo alternativa texto es?</h1> <p>On povas bonigar adireblo e kompreno por omno per skribar klara, deslonga e fakta alternative texto.</p>",
-  "interaction_modal.action.favourite": "Por durar, vu bezonas prizar de vua konto.",
-  "interaction_modal.action.follow": "Por durar, vu bezonas sequar de vua konto.",
-  "interaction_modal.action.reblog": "Por durar, vu bezonas riblogar de vua konto.",
-  "interaction_modal.action.reply": "Por durar, vu bezonas respondar de vua konto.",
-  "interaction_modal.action.vote": "Por durar, vu bezonas vocdonar de vua konto.",
-  "interaction_modal.go": "Irar",
-  "interaction_modal.no_account_yet": "Ka vu ne havas konto ankore?",
+  "interaction_modal.description.favourite": "Kun konto che Mastodon, vu povas favorizar ca posto por savigar la autoro ke vu prizas ol e sparar ol por pose.",
+  "interaction_modal.description.follow": "Per konto che Mastodon, vu povas sequar {name} por ganar ola posti en vua hemniuzeto.",
+  "interaction_modal.description.reblog": "Per konto che Mastodon, vu povas repetar ca posti por dissemar lo a vua propra sequati.",
+  "interaction_modal.description.reply": "Per konto che Mastodon, vu povas respondar ca posto.",
+  "interaction_modal.login.action": "Irar a hemo",
+  "interaction_modal.login.prompt": "Domeno di vua hemala servilo, ex. mastodon.social",
+  "interaction_modal.no_account_yet": "Ka vu ne havas Mastodon-konto?",
   "interaction_modal.on_another_server": "Che diferanta servilo",
   "interaction_modal.on_this_server": "Che ca servilo",
+  "interaction_modal.sign_in": "Vu ne eniris ca servilo. Ube vua konto lokizesas?",
+  "interaction_modal.sign_in_hint": "Averto: To es la retsituo ube vu kreis konto. Se vu ne rimemoras, serchez vua bonvenanta e-posto. Vu anke povas enpozar vua kompleta uzantnomo! (ex. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Favorizez ca posto da {name}",
   "interaction_modal.title.follow": "Sequez {name}",
   "interaction_modal.title.reblog": "Repetez posto di {name}",
   "interaction_modal.title.reply": "Respondez posto di {name}",
-  "interaction_modal.title.vote": "Votar sur votinquesto di {name}",
-  "interaction_modal.username_prompt": "Exemple {example}",
   "intervals.full.days": "{number, plural, one {# dio} other {# dii}}",
   "intervals.full.hours": "{number, plural, one {# horo} other {# hori}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# minuti}}",
@@ -458,8 +419,8 @@
   "keyboard_shortcuts.muted": "to open muted users list",
   "keyboard_shortcuts.my_profile": "to open your profile",
   "keyboard_shortcuts.notifications": "to open notifications column",
-  "keyboard_shortcuts.open_media": "Desklozar audvidaji",
-  "keyboard_shortcuts.pinned": "Desklozar listo di adpinglita afishi",
+  "keyboard_shortcuts.open_media": "to open media",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "to reply",
   "keyboard_shortcuts.requests": "to open follow requests list",
@@ -467,9 +428,8 @@
   "keyboard_shortcuts.spoilers": "to show/hide CW field",
   "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
-  "keyboard_shortcuts.toggle_sensitivity": "Montrar/celar audvidaji",
+  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
   "keyboard_shortcuts.toot": "to start a brand new toot",
-  "keyboard_shortcuts.translate": "por tradukar mesajo",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
   "keyboard_shortcuts.up": "to move up in the list",
   "lightbox.close": "Klozar",
@@ -482,35 +442,23 @@
   "link_preview.author": "Da {name}",
   "link_preview.more_from_author": "Plua de {name}",
   "link_preview.shares": "{count, plural,one {{counter} posto} other {{counter} posti}}",
-  "lists.add_member": "Adjuntar",
-  "lists.add_to_list": "Adjuntar ad listo",
-  "lists.add_to_lists": "Adjuntar {name} ad listi",
-  "lists.create": "Krear",
-  "lists.create_a_list_to_organize": "Krear nova listo por organizar vua hemfluo",
-  "lists.create_list": "Krear listo",
+  "lists.account.add": "Insertez a listo",
+  "lists.account.remove": "Efacez de listo",
   "lists.delete": "Efacez listo",
-  "lists.done": "Finis",
-  "lists.edit": "Redaktar listo",
-  "lists.exclusive": "Celar membri sur Hemo",
-  "lists.exclusive_hint": "Se ulu es en ca listo, celez lu sur vua hemfluo por evitar vidar lua afishi denove.",
-  "lists.find_users_to_add": "Serchi uzanti por adjuntar",
-  "lists.list_members": "Listigar membri",
-  "lists.list_members_count": "{count, plural,one {# membro} other {#membri}}",
-  "lists.list_name": "Listonomo",
-  "lists.new_list_name": "Nova listonomo",
-  "lists.no_lists_yet": "Nula listi ankore.",
-  "lists.no_members_yet": "Nula membri ankore.",
-  "lists.no_results_found": "Nula rezulto trovigesis.",
-  "lists.remove_member": "Forigar",
+  "lists.edit": "Modifikez listo",
+  "lists.edit.submit": "Chanjez titulo",
+  "lists.exclusive": "Celar ca posti del hemo",
+  "lists.new.create": "Insertez listo",
+  "lists.new.title_placeholder": "Nova listotitulo",
   "lists.replies_policy.followed": "Irga sequita uzanto",
   "lists.replies_policy.list": "Membro di listo",
   "lists.replies_policy.none": "Nulu",
-  "lists.save": "Konservar",
-  "lists.search": "Serchar",
-  "lists.show_replies_to": "Inkluzar respondi de listomembri",
+  "lists.replies_policy.title": "Montrez respondi a:",
+  "lists.search": "Trovez inter personi quon vu sequas",
+  "lists.subheading": "Vua listi",
   "load_pending": "{count, plural, one {# nova kozo} other {# nova kozi}}",
   "loading_indicator.label": "Kargante…",
-  "media_gallery.hide": "Celar",
+  "media_gallery.hide": "Celez",
   "moved_to_account_banner.text": "Vua konto {disabledAccount} es nune desaktiva pro ke vu movis a {movedToAccount}.",
   "mute_modal.hide_from_notifications": "Celez de savigi",
   "mute_modal.hide_options": "Celez preferaji",
@@ -525,12 +473,12 @@
   "navigation_bar.administration": "Administro",
   "navigation_bar.advanced_interface": "Apertez per retintervizajo",
   "navigation_bar.blocks": "Blokusita uzeri",
-  "navigation_bar.bookmarks": "Lektosigni",
+  "navigation_bar.bookmarks": "Libromarki",
   "navigation_bar.community_timeline": "Lokala tempolineo",
   "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Privata mencioni",
   "navigation_bar.discover": "Deskovrez",
-  "navigation_bar.domain_blocks": "Blokusita domeni",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.explore": "Explorez",
   "navigation_bar.favourites": "Favoriziti",
   "navigation_bar.filters": "Silencigita vorti",
@@ -543,7 +491,7 @@
   "navigation_bar.mutes": "Celita uzeri",
   "navigation_bar.opened_in_classic_interface": "Posti, konti e altra pagini specifika apertesas en la retovidilo klasika.",
   "navigation_bar.personal": "Personala",
-  "navigation_bar.pins": "Adpinglita afishi",
+  "navigation_bar.pins": "Pinned toots",
   "navigation_bar.preferences": "Preferi",
   "navigation_bar.public_timeline": "Federata tempolineo",
   "navigation_bar.search": "Serchez",
@@ -556,14 +504,9 @@
   "notification.admin.report_statuses_other": "{name} raportis {target}",
   "notification.admin.sign_up": "{name} registresis",
   "notification.admin.sign_up.name_and_others": "{name} e {count, plural,one {# altru} other {#altri}} enrejistris",
-  "notification.annual_report.message": "Yen vua ye {year} #Wrapstodon!",
-  "notification.annual_report.view": "Vidar #Wrapstodon",
   "notification.favourite": "{name} favorizis tua mesajo",
-  "notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural,one {# altru} other {# altri}}</a> stelumis vua afisho",
-  "notification.favourite_pm": "{name} prizis vua privata menciono",
-  "notification.favourite_pm.name_and_others_with_link": "{name} e <a>{count, plural,one {# altro} other {# altri}}</a> prizis vua privata menciono",
+  "notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural,one {# altru} other {# altri}}</a> favorizis vua posto",
   "notification.follow": "{name} sequeskis tu",
-  "notification.follow.name_and_others": "{name} e <a>{count, plural,one {# altro} other {# altri}}</a> sequis vu",
   "notification.follow_request": "{name} demandas sequar vu",
   "notification.follow_request.name_and_others": "{name} e {count, plural,one {# altru} other {# altri}} volas sequar vu",
   "notification.label.mention": "Mencionez",
@@ -571,38 +514,37 @@
   "notification.label.private_reply": "Privata respondo",
   "notification.label.reply": "Respondez",
   "notification.mention": "Mencionez",
-  "notification.mentioned_you": "{name} mencionis vu",
-  "notification.moderation-warning.learn_more": "Lernez plu",
+  "notification.moderation-warning.learn_more": "Lernez pluse",
   "notification.moderation_warning": "Vu recevis jeraverto",
   "notification.moderation_warning.action_delete_statuses": "Kelka vua posti efacesis.",
-  "notification.moderation_warning.action_disable": "Vua konto es desaktivigita.",
+  "notification.moderation_warning.action_disable": "Vua konto estas desaktivigita.",
   "notification.moderation_warning.action_mark_statuses_as_sensitive": "Kelka vua posti markizesis quale sentoza.",
   "notification.moderation_warning.action_none": "Vua konto recevis jeraverto.",
   "notification.moderation_warning.action_sensitive": "Vua posti markizesos quale sentoza pos nun.",
   "notification.moderation_warning.action_silence": "Vua konto limitizesis.",
   "notification.moderation_warning.action_suspend": "Vua konto restriktesis.",
-  "notification.own_poll": "Vua votinquesto fineskis",
-  "notification.poll": "Votinquesto ube vu votis fineskis",
+  "notification.own_poll": "Vua votposto finigis",
+  "notification.poll": "Votposto quan vu partoprenis finis",
   "notification.reblog": "{name} repetis tua mesajo",
   "notification.reblog.name_and_others_with_link": "{name} e <a>{count, plural,one {# altru} other {#altri}}</a> repetis vua posto",
   "notification.relationships_severance_event": "Desganis konekteso kun {name}",
   "notification.relationships_severance_event.account_suspension": "Administranto de {from} restriktis {target}, do vu ne povas plue recevar novaji de lu o interagar kun lu.",
   "notification.relationships_severance_event.domain_block": "Administranto de {from} blokusis {target}, e anke {followersCount} de vua sequanti e {followingCount, plural, one {# konto} other {# konti}} quan vu sequas.",
-  "notification.relationships_severance_event.learn_more": "Lernez plu",
+  "notification.relationships_severance_event.learn_more": "Lernez pluse",
   "notification.relationships_severance_event.user_domain_block": "Vu blokusis {target}, do efacis {followersCount} de vua sequanti e {followingCount, plural, one {# konto} other {#konti}} quan vu sequis.",
   "notification.status": "{name} nove postigis",
-  "notification.update": "{name} redaktis afisho",
+  "notification.update": "{name} modifikis posto",
   "notification_requests.accept": "Aceptez",
   "notification_requests.accept_multiple": "{count, plural, one {Aceptar # demando…} other {Aceptar # demandi…}}",
   "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Aceptar demando} other {Aceptar demandi}}",
   "notification_requests.confirm_accept_multiple.message": "Vu aceptos {count, plural, one {1 savigdemando} other {# savigdemandi}}. Ka vu certe volas durar?",
   "notification_requests.confirm_accept_multiple.title": "Ka aceptar savigdemandi?",
   "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Ignorez demando} other {Ignorez demandi}}",
-  "notification_requests.confirm_dismiss_multiple.message": "Vu ignoros {count, plural, one {1 savigdemando} other {# savigdemandi}}. Vu ne povas facile ganar {count, plural, one {ol} other {oli}} denove. Ka vu esas certe ke vu volas durar?",
+  "notification_requests.confirm_dismiss_multiple.message": "Vu ignoros {count, plural, one {1 savigdemando} other {# savigdemandi}}. Vu ne povas facile ganar {count, plural, one {ol} other {oli}} pluse. Ka vu esas certe ke vu volas durar?",
   "notification_requests.confirm_dismiss_multiple.title": "Ka ignorar savigdemandi?",
   "notification_requests.dismiss": "Ignorez",
   "notification_requests.dismiss_multiple": "{count, plural,one {Ignorez # demando…} other {Ignorez # demandi…}}",
-  "notification_requests.edit_selection": "Redaktar",
+  "notification_requests.edit_selection": "Modifikez",
   "notification_requests.exit_selection": "Finas",
   "notification_requests.explainer_for_limited_account": "Savigi de ca konto filtresis pro ke la konto limitizesis da jeranto.",
   "notification_requests.explainer_for_limited_remote_account": "Savigi de ca konto filtresis pro ke la konto o olua servilo limitizesis da jeranto.",
@@ -622,9 +564,8 @@
   "notifications.column_settings.filter_bar.category": "Rapidfiltrilbaro",
   "notifications.column_settings.follow": "Nova sequanti:",
   "notifications.column_settings.follow_request": "Nova sequodemandi:",
-  "notifications.column_settings.group": "Grupo",
   "notifications.column_settings.mention": "Mencioni:",
-  "notifications.column_settings.poll": "Votinquestorezulti:",
+  "notifications.column_settings.poll": "Votpostorezulti:",
   "notifications.column_settings.push": "Pulsavizi",
   "notifications.column_settings.reblog": "Repeti:",
   "notifications.column_settings.show": "Montrar en kolumno",
@@ -632,19 +573,19 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Nelektita avizi",
   "notifications.column_settings.unread_notifications.highlight": "Briligez nelektita avizi",
-  "notifications.column_settings.update": "Redaktati:",
+  "notifications.column_settings.update": "Modifikati:",
   "notifications.filter.all": "Omna",
   "notifications.filter.boosts": "Repeti",
   "notifications.filter.favourites": "Favoriziti",
   "notifications.filter.follows": "Sequati",
   "notifications.filter.mentions": "Mencioni",
-  "notifications.filter.polls": "Votinquestorezulti",
+  "notifications.filter.polls": "Votpostorezulti",
   "notifications.filter.statuses": "Novaji de personi quon vu sequas",
   "notifications.grant_permission": "Donez permiso.",
   "notifications.group": "{count} avizi",
   "notifications.mark_as_read": "Markizez singla avizi quale lektita",
   "notifications.permission_denied": "Desktopavizi esas nedisplonebla pro antea refuzita vidilpermisdemando",
-  "notifications.permission_denied_alert": "Komputilsavigi ne povas ebligesar, pro ke retumilpermiso desaceptesis antee",
+  "notifications.permission_denied_alert": "Desktopavizi ne povas aktivigesar pro ke vidilpermiso refuzesis",
   "notifications.permission_required": "Desktopavizi esas nedisplonebla pro ke bezonata permiso ne donesis.",
   "notifications.policy.accept": "Aceptez",
   "notifications.policy.accept_hint": "Montrez en savigi",
@@ -663,53 +604,76 @@
   "notifications.policy.filter_private_mentions_hint": "Filtrita se ol ne esas respondo a vua sua menciono o se vu sequas la sendanto",
   "notifications.policy.filter_private_mentions_title": "Nekonocita privata mencioni",
   "notifications.policy.title": "Regular savigi de…",
-  "notifications_permission_banner.enable": "Ebligar komputilsavigi",
-  "notifications_permission_banner.how_to_control": "Por ganar savigi kande Mastodon ne es desklozita, ebligez komputilsavigi.",
+  "notifications_permission_banner.enable": "Aktivigez desktopavizi",
+  "notifications_permission_banner.how_to_control": "Por ganar avizi kande Mastodon ne esas apertita, aktivigez dekstopavizi. Vu povas precize regularar quale interakti facas deskstopavizi tra la supera {icon} butono pos oli aktivigesis.",
   "notifications_permission_banner.title": "Irga kozo ne pasas vu",
-  "onboarding.follows.back": "Retro",
-  "onboarding.follows.done": "Finis",
+  "onboarding.action.back": "Retroirez",
+  "onboarding.actions.back": "Retroirez",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "Saluto #Mastodon!",
   "onboarding.follows.empty": "Regretinde, nula rezultajo povas montresar nune. Vu povas esforcar serchar, o irar al explorala pagino por trovar personi sequinda, o esforcar itere pose.",
-  "onboarding.follows.search": "Serchar",
-  "onboarding.follows.title": "Sequez personi por komencar",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
   "onboarding.profile.discoverable": "Trovebligez mea profilo",
   "onboarding.profile.discoverable_hint": "Se vu selektas deskovrebleso che Mastodon, vua posti povas aparar en sercho-rezultaji e populari, e vua profilo forsan sugestesos a personi kun interesi simila a vua.",
   "onboarding.profile.display_name": "Publika nomo",
   "onboarding.profile.display_name_hint": "Vua tota nomo o vua gaya nomo…",
+  "onboarding.profile.lead": "Vu sempre povas kompletigar co plu tarde en la opcioni, ube mem plua personalizanta opcioni es disponebla.",
   "onboarding.profile.note": "Biografio",
   "onboarding.profile.note_hint": "Vu povas @mencionar altra personi o #hashtagi…",
   "onboarding.profile.save_and_continue": "Preservez e avancez",
   "onboarding.profile.title": "Kompletigez la profilo",
   "onboarding.profile.upload_avatar": "Kargez profiloportreto",
   "onboarding.profile.upload_header": "Kargez profilokapimajo",
+  "onboarding.share.lead": "Savigez personi quale ili povas trovar vu che Mastodon!",
+  "onboarding.share.message": "Me esas {username} che #Mastodon! Venez e sequez me ye {url}",
+  "onboarding.share.next_steps": "Kozi quin vu darfas volar facar sequante:",
+  "onboarding.share.title": "Partigez vua profilo",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "Vu facis lo!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Facar vua unesma posto",
+  "onboarding.steps.setup_profile.body": "Vu interagos plue kun profilo detalizita.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
+  "onboarding.tips.2fa": "<strong>Ka vu savas?</strong> Vu povas sekurigar vua konto per pozar 2-faktora verifiko en preferaji de vua konto. Telefonilnombro ne bezonesis!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Ka vu savas?</strong> Vu povas interagar kun profili sur altra servili senrupte!",
+  "onboarding.tips.migration": "<strong>Ka vu savas?</strong> Se vu sentas ke {domain} ne esas apta por vu en la futuro, vu povas transferar a altra servilo di Mastodon sen malganar vua sequanti!",
+  "onboarding.tips.verification": "<strong>Ka vu savas?</strong> Vu povas verifikar vua konto per pozi ligilo a vua profilo di Mastodon sur vua sua retsituo e adjuntar la retsituo a vua profilo. Senpage!",
   "password_confirmation.exceeds_maxlength": "La konfirmo dil pasvorto superesas la limito pri longeso di pasvorti",
   "password_confirmation.mismatching": "La konfirmo dil pasvorto ne egalesas",
   "picture_in_picture.restore": "Retropozez",
   "poll.closed": "Klozita",
-  "poll.refresh": "Rifreshar",
+  "poll.refresh": "Rifreshez",
   "poll.reveal": "Vidar rezultaji",
   "poll.total_people": "{count, plural, one {# persono} other {# personi}}",
   "poll.total_votes": "{count, plural, one {# voto} other {# voti}}",
-  "poll.vote": "Votar",
+  "poll.vote": "Votez",
   "poll.voted": "Vu ja votis ca respondo",
   "poll.votes": "{votes, plural, one {# voto} other {# voti}}",
-  "poll_button.add_poll": "Adjuntar votinquesto",
-  "poll_button.remove_poll": "Forigar votinquesto",
+  "poll_button.add_poll": "Insertez votposto",
+  "poll_button.remove_poll": "Efacez votposto",
   "privacy.change": "Aranjar privateso di mesaji",
   "privacy.direct.long": "Omnu quan mencionesis en la posto",
-  "privacy.direct.short": "Privata menciono",
+  "privacy.direct.short": "Specifika personi",
   "privacy.private.long": "Nur vua sequanti",
   "privacy.private.short": "Sequanti",
   "privacy.public.long": "Ulu de e ne de Mastodon",
   "privacy.public.short": "Publike",
-  "privacy.unlisted.additional": "Co kondutas exakte kam publika, ecepte la posto ne aparos en nuna flui o gretvorti, exploro, o sercho di Mastodon, mem se vu esas volunta totkonte.",
+  "privacy.unlisted.additional": "Co kondutas exakte kam publika, escepte la posto ne aparos en viva novajari o gretiketi, exploro, o sercho di Mastodon, mem se vu esas volunta totkonte.",
   "privacy.unlisted.long": "Min multa algoritmoridikuli",
   "privacy.unlisted.short": "Deslauta publiko",
   "privacy_policy.last_updated": "Antea novajo ye {date}",
   "privacy_policy.title": "Privatesguidilo",
   "recommended": "Rekomendata",
   "refresh": "Rifreshez",
-  "regeneration_indicator.please_stand_by": "Vartez.",
-  "regeneration_indicator.preparing_your_home_feed": "Preparas vua hemfluo…",
+  "regeneration_indicator.label": "Chargas…",
+  "regeneration_indicator.sublabel": "Vua hemniuzeto preparesas!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# dio} other {# dii}} ante nun",
   "relative_time.full.hours": "{number, plural, one {# horo} other {# hori}} ante nun",
@@ -721,9 +685,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "hodie",
-  "reply_indicator.attachments": "{count, plural, one {# addonajo} other {# addonaji}}",
+  "reply_indicator.attachments": "{count, plural, one {# atachajo} other {# atachaji}}",
   "reply_indicator.cancel": "Nihiligar",
-  "reply_indicator.poll": "Votinquesto",
+  "reply_indicator.poll": "Votposto",
   "report.block": "Restriktez",
   "report.block_explanation": "Vu ne vidos olia posti. Oli ne povas vidar vua posti o sequar vu. Oli savos ke oli restriktesis.",
   "report.categories.legal": "Legala",
@@ -758,13 +722,13 @@
   "report.statuses.title": "Ka existas irga posti quo suportas ca raporto?",
   "report.submit": "Sendar",
   "report.target": "Denuncante",
-  "report.thanks.take_action": "Yen vua preferaji por regularar quon vu vidas sur Mastodon:",
+  "report.thanks.take_action": "Co esas vua opcioni por regularar quo vu vidas che Mastodon:",
   "report.thanks.take_action_actionable": "Dum ke ni kontrolas co, vu povas demarshar kontra @{name}:",
   "report.thanks.title": "Ka vu ne volas vidar co?",
   "report.thanks.title_actionable": "Danko por raportizar, ni kontrolos co.",
   "report.unfollow": "Desequez @{name}",
-  "report.unfollow_explanation": "Vu sequas ca konto.",
-  "report_notification.attached_statuses": "{count, plural,one {{count} posti} other {{count} posti}} addonita",
+  "report.unfollow_explanation": "Vu sequas ca konto. Por ne vidar olia posti en vua hemniuzeto pluse, desequez oli.",
+  "report_notification.attached_statuses": "{count, plural,one {{count} posti} other {{count} posti}} adjuntesas",
   "report_notification.categories.legal": "Legala",
   "report_notification.categories.legal_sentence": "deslegala kontenajo",
   "report_notification.categories.other": "Altra",
@@ -792,12 +756,11 @@
   "search_popout.user": "uzanto",
   "search_results.accounts": "Profili",
   "search_results.all": "Omna",
-  "search_results.hashtags": "Gretvorti",
-  "search_results.no_results": "Nula rezulto.",
-  "search_results.no_search_yet": "Probez serchar afishi, profili o gretvorti.",
+  "search_results.hashtags": "Hashtagi",
+  "search_results.nothing_found": "Ne povas ganar irgo per ca trovvorti",
   "search_results.see_all": "Videz omni",
   "search_results.statuses": "Posti",
-  "search_results.title": "Serchar \"{q}\"",
+  "search_results.title": "Trovez {q}",
   "server_banner.about_active_users": "Personi quo uzas ca servilo dum antea 30 dii (monate aktiva uzanti)",
   "server_banner.active_users": "aktiva uzanti",
   "server_banner.administered_by": "Administresis da:",
@@ -812,7 +775,7 @@
   "status.admin_domain": "Apertez jerintervizajo por {domain}",
   "status.admin_status": "Open this status in the moderation interface",
   "status.block": "Restriktez @{name}",
-  "status.bookmark": "Lektosigno",
+  "status.bookmark": "Libromarko",
   "status.cancel_reblog_private": "Desrepetez",
   "status.cannot_reblog": "Ca posto ne povas repetesar",
   "status.continued_thread": "Durigita postaro",
@@ -821,35 +784,34 @@
   "status.detailed_status": "Detala konversvido",
   "status.direct": "Private mencionez @{name}",
   "status.direct_indicator": "Privata menciono",
-  "status.edit": "Redaktar",
-  "status.edited": "Lastatempe redaktesar ye {date}",
-  "status.edited_x_times": "Redaktesis ye {count, plural, one {{count} foyo} other {{count} foyi}}",
+  "status.edit": "Modifikez",
+  "status.edited": "Recente modifikesis ye {date}",
+  "status.edited_x_times": "Modifikesis {count, plural, one {{count} foyo} other {{count} foyi}}",
   "status.embed": "Ganez adherkodexo",
   "status.favourite": "Favorizar",
-  "status.favourites": "{count, plural, one {stelumo} other {stelumi}}",
+  "status.favourites": "{count, plural, one {favorizo} other {favorizi}}",
   "status.filter": "Filtragez ca posto",
   "status.history.created": "{name} kreis ye {date}",
-  "status.history.edited": "{name} redaktis ye {date}",
-  "status.load_more": "Kargar plu",
+  "status.history.edited": "{name} modifikis ye {date}",
+  "status.load_more": "Kargar pluse",
   "status.media.open": "Klikez por apertar",
   "status.media.show": "Klikez por montrar",
-  "status.media_hidden": "Audvidaji es celita",
+  "status.media_hidden": "Kontenajo celita",
   "status.mention": "Mencionar @{name}",
-  "status.more": "Plu",
+  "status.more": "Pluse",
   "status.mute": "Silencigez @{name}",
   "status.mute_conversation": "Silencigez konverso",
   "status.open": "Detaligar ca mesajo",
   "status.pin": "Pinglagez che profilo",
-  "status.pinned": "Adpinglita afisho",
-  "status.read_more": "Lektez plu",
+  "status.pinned": "Pinned toot",
+  "status.read_more": "Lektez pluse",
   "status.reblog": "Repetez",
   "status.reblog_private": "Repetez kun originala videbleso",
   "status.reblogged_by": "{name} repetis",
   "status.reblogs": "{count, plural, one {repeto} other {repeti}}",
   "status.reblogs.empty": "Nulu ja repetis ca posto. Kande ulu facas lo, lu montresos hike.",
   "status.redraft": "Efacez e riskisigez",
-  "status.remove_bookmark": "Forigar lektosigno",
-  "status.remove_favourite": "Forigar de priziti",
+  "status.remove_bookmark": "Efacez libromarko",
   "status.replied_in_thread": "Respondesis en postaro",
   "status.replied_to": "Respondis a {name}",
   "status.reply": "Respondar",
@@ -858,9 +820,9 @@
   "status.sensitive_warning": "Trubliva kontenajo",
   "status.share": "Partigez",
   "status.show_less_all": "Montrez min por omno",
-  "status.show_more_all": "Montrez plu por omno",
+  "status.show_more_all": "Montrez pluse por omno",
   "status.show_original": "Montrez originalo",
-  "status.title.with_attachments": "{user} afishis {attachmentCount, plural, one {addonajo} other {{attachmentCount} addonaji}}",
+  "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
   "status.translate": "Tradukez",
   "status.translated_from_with": "Tradukita de {lang} per {provider}",
   "status.uncached_media_warning": "Previdajo nedisponebla",
@@ -871,37 +833,52 @@
   "subscribed_languages.target": "Chanjez abonita lingui por {target}",
   "tabs_bar.home": "Hemo",
   "tabs_bar.notifications": "Savigi",
-  "terms_of_service.title": "Servtermini",
   "time_remaining.days": "{number, plural, one {# dio} other {# dii}} restas",
   "time_remaining.hours": "{number, plural, one {# horo} other {# hori}} restas",
   "time_remaining.minutes": "{number, plural, one {# minuto} other {# minuti}} restas",
   "time_remaining.moments": "Poka sekundi restas",
   "time_remaining.seconds": "{number, plural, one {# sekundo} other {# sekundi}} restas",
   "trends.counter_by_accounts": "{count, plural,one {{counter} persono} other {{counter} personi}} en antea {days, plural,one {dio} other {{days} dii}}",
-  "trends.trending_now": "Populareskas nun",
+  "trends.trending_now": "Tendencigas nun",
   "ui.beforeunload": "Vua skisato perdesos se vu ekiras Mastodon.",
   "units.short.billion": "{count}G",
   "units.short.million": "{count}M",
   "units.short.thousand": "{count}K",
-  "upload_area.title": "Tenar e destenar por adkargar",
-  "upload_button.label": "Adjuntar imaji, video o sondosiero",
-  "upload_error.limit": "Dosieradkarglimito ecesesis.",
-  "upload_error.poll": "Dosieradkargo ne permisesas kun votinquesti.",
-  "upload_form.drag_and_drop.instructions": "Por tenar audvidajaddonajo, presez spaco o eniro. Presez spaco o eniro denove por destenar la audvidajatachajo en olua nova loko, o presez eskapo por nuligar.",
-  "upload_form.drag_and_drop.on_drag_cancel": "Tiro nuligesis.",
-  "upload_form.drag_and_drop.on_drag_end": "Audvidajaddonajo {item} destenesis.",
-  "upload_form.drag_and_drop.on_drag_over": "Audvidajaddonajo {item} movigesis.",
-  "upload_form.drag_and_drop.on_drag_start": "Tenis audvidajaddonajo {item}.",
-  "upload_form.edit": "Redaktar",
-  "upload_progress.label": "Adkargas...",
+  "upload_area.title": "Tranar faligar por kargar",
+  "upload_button.label": "Adjuntar kontenajo",
+  "upload_error.limit": "Failadcharglimito ecesesis.",
+  "upload_error.poll": "Failadchargo ne permisesas kun votposti.",
+  "upload_form.audio_description": "Deskriptez por personi kun audnekapableso",
+  "upload_form.description": "Deskriptez por personi kun vidnekapableso",
+  "upload_form.drag_and_drop.instructions": "Por tenar mediatachajo, presez spaco o eniro. Presez spaco o eniro itere por destenar la mediatachajo en olua nova loko, o presez eskapo por anular.",
+  "upload_form.drag_and_drop.on_drag_cancel": "Tiro anulesis. Mediatachajo {item} destenesis.",
+  "upload_form.drag_and_drop.on_drag_end": "Mediatachajo {item} destenesis.",
+  "upload_form.drag_and_drop.on_drag_over": "Mediatachajo {item} movigesis.",
+  "upload_form.drag_and_drop.on_drag_start": "Tenis mediatachajo {item}.",
+  "upload_form.edit": "Modifikez",
+  "upload_form.thumbnail": "Chanjez imajeto",
+  "upload_form.video_description": "Deskriptez por personi kun audnekapableso o vidnekapableso",
+  "upload_modal.analyzing_picture": "Analizas imajo…",
+  "upload_modal.apply": "Aplikez",
+  "upload_modal.applying": "Aplikas…",
+  "upload_modal.choose_image": "Selektez imajo",
+  "upload_modal.description_placeholder": "Rapida bruna foxo saltas super la indolenta hundo",
+  "upload_modal.detect_text": "Deskovrez texto de imajo",
+  "upload_modal.edit_media": "Modifikez medii",
+  "upload_modal.hint": "Kliktez o tirez cirklo che prevido por selektar centrala punto quo sempre montresas kun omna imajeti.",
+  "upload_modal.preparing_ocr": "Preparas OCR…",
+  "upload_modal.preview_label": "Previdez ({ratio})",
+  "upload_progress.label": "Kargante...",
   "upload_progress.processing": "Traktante…",
   "username.taken": "Ta uzantnomo ja es posedita. Provez altro",
   "video.close": "Klozez video",
-  "video.download": "Deschargar dosiero",
+  "video.download": "Deschargez failo",
   "video.exit_fullscreen": "Ekirez plena skreno",
   "video.expand": "Expansez video",
   "video.fullscreen": "Plena skreno",
   "video.hide": "Celez video",
+  "video.mute": "Silencigez sono",
   "video.pause": "Pauzez",
-  "video.play": "Pleez"
+  "video.play": "Pleez",
+  "video.unmute": "Desilencigez sono"
 }
diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json
index 50f31dd623..460bde0082 100644
--- a/app/javascript/mastodon/locales/is.json
+++ b/app/javascript/mastodon/locales/is.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Breyta notandasniði",
   "account.enable_notifications": "Láta mig vita þegar @{name} sendir inn",
   "account.endorse": "Birta á notandasniði",
-  "account.featured": "Með aukið vægi",
-  "account.featured.hashtags": "Myllumerki",
-  "account.featured.posts": "Færslur",
   "account.featured_tags.last_status_at": "Síðasta færsla þann {date}",
   "account.featured_tags.last_status_never": "Engar færslur",
+  "account.featured_tags.title": "Myllumerki hjá {name} með aukið vægi",
   "account.follow": "Fylgjast með",
   "account.follow_back": "Fylgjast með til baka",
   "account.followers": "Fylgjendur",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} færsla} other {{counter} færslur}}",
   "account.unblock": "Aflétta útilokun af @{name}",
   "account.unblock_domain": "Aflétta útilokun lénsins {domain}",
-  "account.unblock_domain_short": "Aflétta útilokun",
   "account.unblock_short": "Hætta að loka á",
   "account.unendorse": "Ekki birta á notandasniði",
   "account.unfollow": "Hætta að fylgja",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Upp kom óvænt villa.",
   "alert.unexpected.title": "Úbbs!",
   "alt_text_badge.title": "Hjálpartexti mynda",
-  "alt_text_modal.add_alt_text": "Bæta við hjálpartexta",
-  "alt_text_modal.add_text_from_image": "Bæta við texta úr mynd",
-  "alt_text_modal.cancel": "Hætta við",
-  "alt_text_modal.change_thumbnail": "Skipta um smámynd",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Lýstu þessu fyrir fólk með skerta heyrn…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Lýstu þessu fyrir fólk með skerta sjón…",
-  "alt_text_modal.done": "Lokið",
   "announcement.announcement": "Auglýsing",
-  "annual_report.summary.archetype.booster": "Svali gaurinn",
-  "annual_report.summary.archetype.lurker": "Lurkurinn",
-  "annual_report.summary.archetype.oracle": "Völvan",
-  "annual_report.summary.archetype.pollster": "Kannanafíkillinn",
-  "annual_report.summary.archetype.replier": "Félagsveran",
-  "annual_report.summary.followers.followers": "fylgjendur",
-  "annual_report.summary.followers.total": "{count} alls",
-  "annual_report.summary.here_it_is": "Hér er yfirlitið þitt fyrir {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "færsla sett oftast í eftirlæti",
-  "annual_report.summary.highlighted_post.by_reblogs": "færsla oftast endurbirt",
-  "annual_report.summary.highlighted_post.by_replies": "færsla með flestum svörum",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "mest notaða forrit",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "mest notaða myllumerki",
-  "annual_report.summary.most_used_hashtag.none": "Ekkert",
-  "annual_report.summary.new_posts.new_posts": "nýjar færslur",
-  "annual_report.summary.percentile.text": "<topLabel>Þetta setur þig á meðal</topLabel><percentage></percentage><bottomLabel>of {domain} virkustu notendanna.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Við förum ekkert að raupa um þetta.",
-  "annual_report.summary.thanks": "Takk fyrir að vera hluti af Mastodon-samfélaginu!",
   "attachments_list.unprocessed": "(óunnið)",
   "audio.hide": "Fela hljóð",
   "block_modal.remote_users_caveat": "Við munum biðja {domain} netþjóninn um að virða ákvörðun þína. Hitt er svo annað mál hvort hann fari eftir þessu, ekki er hægt að tryggja eftirfylgni því sumir netþjónar meðhöndla útilokanir á sinn hátt. Opinberar færslur gætu verið sýnilegar notendum sem ekki eru skráðir inn.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "Umbeðin síða fannst ekki. Ertu viss um að slóðin í vistfangastikunni sé rétt?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Loka",
-  "bundle_modal_error.message": "Eitthvað fór úrskeiðis við að hlaða inn þessum skjá.",
+  "bundle_modal_error.message": "Eitthvað fór úrskeiðis við að hlaða inn þessari einingu.",
   "bundle_modal_error.retry": "Reyndu aftur",
   "closed_registrations.other_server_instructions": "Þar sem Mastodon er ekki miðstýrt, þá getur þú búið til aðgang á öðrum þjóni, en samt haft samskipti við þennan.",
   "closed_registrations_modal.description": "Að búa til aðgang á {domain} er ekki mögulegt eins og er, en vinsamlegast hafðu í huga að þú þarft ekki aðgang sérstaklega á {domain} til að nota Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Útilokaðir notendur",
   "column.bookmarks": "Bókamerki",
   "column.community": "Staðvær tímalína",
-  "column.create_list": "Búa til lista",
   "column.direct": "Einkaspjall",
   "column.directory": "Skoða notendasnið",
   "column.domain_blocks": "Útilokuð lén",
-  "column.edit_list": "Breyta lista",
   "column.favourites": "Eftirlæti",
   "column.firehose": "Bein streymi",
   "column.follow_requests": "Beiðnir um að fylgjast með",
   "column.home": "Heim",
-  "column.list_members": "Sýsla með meðlimi listans",
   "column.lists": "Listar",
   "column.mutes": "Þaggaðir notendur",
   "column.notifications": "Tilkynningar",
@@ -172,7 +140,6 @@
   "column_header.pin": "Festa",
   "column_header.show_settings": "Birta stillingar",
   "column_header.unpin": "Losa",
-  "column_search.cancel": "Hætta við",
   "column_subheading.settings": "Stillingar",
   "community.column_settings.local_only": "Einungis staðvært",
   "community.column_settings.media_only": "Einungis myndskrár",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Tímalengd könnunar",
   "compose_form.poll.multiple": "Margir valkostir",
   "compose_form.poll.option_placeholder": "Valkostur {number}",
-  "compose_form.poll.single": "Eitt val",
+  "compose_form.poll.single": "Veldu eitt",
   "compose_form.poll.switch_to_multiple": "Breyta könnun svo hægt sé að hafa marga valkosti",
   "compose_form.poll.switch_to_single": "Breyta könnun svo hægt sé að hafa einn stakan valkost",
   "compose_form.poll.type": "Stíll",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Breyta",
   "confirmations.edit.message": "Ef þú breytir núna verður skrifað yfir skilaboðin sem þú ert að semja núna. Ertu viss um að þú viljir halda áfram?",
   "confirmations.edit.title": "Skrifa yfir færslu?",
-  "confirmations.follow_to_list.confirm": "Fylgjast með og bæta á lista",
-  "confirmations.follow_to_list.message": "Þú þarft að fylgjast með {name} til að bæta viðkomandi á lista.",
-  "confirmations.follow_to_list.title": "Fylgjast með notanda?",
   "confirmations.logout.confirm": "Skrá út",
   "confirmations.logout.message": "Ertu viss um að þú viljir skrá þig út?",
   "confirmations.logout.title": "Skrá út?",
-  "confirmations.missing_alt_text.confirm": "Bæta við hjálpartexta",
-  "confirmations.missing_alt_text.message": "Færslan þín inniheldur myndefni án ALT-hjálpartexta. Ef þú bætir við lýsingu á myndefninu gerir það efnið þitt aðgengilegt fyrir fleira fólk.",
-  "confirmations.missing_alt_text.secondary": "Birta samt",
-  "confirmations.missing_alt_text.title": "Bæta við hjálpartexta?",
   "confirmations.mute.confirm": "Þagga",
   "confirmations.redraft.confirm": "Eyða og endurvinna drög",
   "confirmations.redraft.message": "Ertu viss um að þú viljir eyða þessari færslu og enduvinna drögin? Eftirlæti og endurbirtingar munu glatast og svör við upprunalegu færslunni munu verða munaðarlaus.",
@@ -253,9 +213,9 @@
   "disabled_account_banner.text": "Aðgangurinn þinn {disabledAccount} er óvirkur í augnablikinu.",
   "dismissable_banner.community_timeline": "Þetta eru nýjustu opinberu færslurnar frá fólki sem er hýst á {domain}.",
   "dismissable_banner.dismiss": "Hunsa",
-  "dismissable_banner.explore_links": "Þessar fréttatengdu færslur hafa verið að fá aukið vægi í samfélaginu í dag. Nýrri fréttafærslur birtar af fjölbreyttara fólki fá meira vægi.",
-  "dismissable_banner.explore_statuses": "Þessar færslur hafa verið að fá aukið vægi í samfélaginu í dag. Nýrri færslur með fleiri endurbirtingar og merkingar sem eftirlæti hjá fólki fá meira vægi.",
-  "dismissable_banner.explore_tags": "Þessi myllumerki hafa verið að fá aukið vægi í samfélaginu í dag. Myllumerki sem notuð eru af fjölbreyttara fólki fá meira vægi.",
+  "dismissable_banner.explore_links": "Þetta eru fréttafærslur sem í augnablikinu er verið að tala um af fólki á þessum og öðrum netþjónum á dreifhýsta netkerfinu.",
+  "dismissable_banner.explore_statuses": "Þessar færslur frá þessum og öðrum netþjónum á dreifhýsta netkerfinu eru að fá aukna athygli í þessu töluðum orðum.",
+  "dismissable_banner.explore_tags": "Þetta eru myllumerki sem í augnablikinu eru að fá aukna athygli hjá fólki á þessum og öðrum netþjónum á dreifhýsta netkerfinu.",
   "dismissable_banner.public_timeline": "Þetta eru nýjustu opinberu færslurnar frá fólki á samfélagsnetinu sem fólk á {domain} fylgjast með.",
   "domain_block_modal.block": "Útiloka netþjón",
   "domain_block_modal.block_account_instead": "Útiloka {name} í staðinn",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Leitarniðurstöður",
   "emoji_button.symbols": "Tákn",
   "emoji_button.travel": "Ferðalög og staðir",
-  "empty_column.account_featured": "Þessi listi er tómur",
   "empty_column.account_hides_collections": "Notandinn hefur valið að gera ekki tiltækar þessar upplýsingar",
   "empty_column.account_suspended": "Notandaaðgangur í frysti",
   "empty_column.account_timeline": "Engar færslur hér!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Það er ekkert ennþá undir þessu myllumerki.",
   "empty_column.home": "Heimatímalínan þín er tóm! Fylgstu með fleira fólki til að fylla hana. {suggestions}",
   "empty_column.list": "Það er ennþá ekki neitt á þessum lista. Þegar meðlimir á listanum senda inn nýjar færslur, munu þær birtast hér.",
+  "empty_column.lists": "Þú ert ennþá ekki með neina lista. Þegar þú býrð til einhvern lista, munu hann birtast hér.",
   "empty_column.mutes": "Þú hefur ekki þaggað niður í neinum notendum ennþá.",
   "empty_column.notification_requests": "Allt hreint! Það er ekkert hér. Þegar þú færð nýjar tilkynningar, munu þær birtast hér í samræmi við stillingarnar þínar.",
   "empty_column.notifications": "Þú ert ekki ennþá með neinar tilkynningar. Vertu í samskiptum við aðra til að umræður fari af stað.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Prófaðu að gera þau óvirk og svo endurlesa síðuna. Ef það hjálpar ekki til, má samt vera að þú getir notað Mastodon í gegnum annan vafra eða forrit.",
   "errors.unexpected_crash.copy_stacktrace": "Afrita rakningarupplýsingar (stacktrace) á klippispjald",
   "errors.unexpected_crash.report_issue": "Tilkynna vandamál",
+  "explore.search_results": "Leitarniðurstöður",
   "explore.suggested_follows": "Fólk",
   "explore.title": "Kanna",
   "explore.trending_links": "Fréttir",
@@ -373,16 +334,13 @@
   "footer.about": "Nánari upplýsingar",
   "footer.directory": "Notandasniðamappa",
   "footer.get_app": "Ná í forritið",
+  "footer.invite": "Bjóða fólki",
   "footer.keyboard_shortcuts": "Flýtileiðir á lyklaborði",
   "footer.privacy_policy": "Meðferð persónuupplýsinga",
   "footer.source_code": "Skoða frumkóða",
   "footer.status": "Staða",
-  "footer.terms_of_service": "Þjónustuskilmálar",
   "generic.saved": "Vistað",
   "getting_started.heading": "Komast í gang",
-  "hashtag.admin_moderation": "Opna umsjónarviðmót fyrir #{name}",
-  "hashtag.browse": "Skoða færslur með #{hashtag}",
-  "hashtag.browse_from_account": "Skoða færslur frá @{name} í #{hashtag}",
   "hashtag.column_header.tag_mode.all": "og {additional}",
   "hashtag.column_header.tag_mode.any": "eða {additional}",
   "hashtag.column_header.tag_mode.none": "án {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} færsla} other {{counter} færslur}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} færsla} other {{counter} færslur}} í dag",
   "hashtag.follow": "Fylgjast með myllumerki",
-  "hashtag.mute": "Þagga #{hashtag}",
   "hashtag.unfollow": "Hætta að fylgjast með myllumerki",
   "hashtags.and_other": "…og {count, plural, other {# til viðbótar}}",
   "hints.profiles.followers_may_be_missing": "Fylgjendur frá þessum notanda gæti vantað.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Hunsa tilkynningar frá fólki sem fylgist ekki með þér?",
   "ignore_notifications_modal.not_following_title": "Hunsa tilkynningar frá fólki sem þú fylgist ekki með?",
   "ignore_notifications_modal.private_mentions_title": "Hunsa tilkynningar frá óumbeðnum tilvísunum í einkaspjalli?",
-  "info_button.label": "Hjálp",
-  "info_button.what_is_alt_text": "<h1>Hvað er alt-texti?</h1> <p>Hjálpartexti eða ALT-myndatexti inniheldur lýsingu á myndefni fyrir fólk með ýmsar gerðir sjónskerðingar, fyrir tengingar með litla bandbreidd, eða til að gefa nánara samhengi fyrir myndefni.</p><p>Þú getur með þessu bætt almennt aðgengi og aukið skilning á efni sem þú birtir með því að skrifa skýran, skorinortan og hlutlægan alt-texta til vara.</p><ul><li>Lýstu mikilvægum atriðum</li>\\n<li>Hafðu yfirlit með þeim texta sem sést í myndum</li><li>Notaðu eðlilega setningaskipan</li><li>Forðastu óþarfar upplýsingar</li><li>Leggðu áherslu á aðalatriði í flóknu myndefni (eins og línuritum eða landakortum)</li></ul>",
-  "interaction_modal.action.favourite": "Til að halda áfram þarftu að setja eitthvað í eftirlæti, verandi inni á aðgangnum þínum.",
-  "interaction_modal.action.follow": "Til að halda áfram þarftu að fylgjast með einhverjum, verandi inni á aðgangnum þínum.",
-  "interaction_modal.action.reblog": "Til að halda áfram þarftu að endurbirta frá einhverjum, verandi inni á aðgangnum þínum.",
-  "interaction_modal.action.reply": "Til að halda áfram þarftu að svara einhverjum, verandi inni á aðgangnum þínum.",
-  "interaction_modal.action.vote": "Til að halda áfram þarftu að greiða atkvæði, verandi inni á aðgangnum þínum.",
-  "interaction_modal.go": "Áfram",
-  "interaction_modal.no_account_yet": "Ertu ekki ennþá með aðgang?",
+  "interaction_modal.description.favourite": "Með notandaaðgangi á Mastodon geturðu sett þessa færslu í eftirlæti og þannig látið höfundinn vita að þú kunnir að meta hana og vistað hana til síðari tíma.",
+  "interaction_modal.description.follow": "Með notandaaðgangi á Mastodon geturðu fylgst með {name} og fengið færslur frá viðkomandi í heimastreymið þitt.",
+  "interaction_modal.description.reblog": "Með notandaaðgangi á Mastodon geturðu endurbirt þessa færslu til að deila henni með þeim sem fylgjast með þér.",
+  "interaction_modal.description.reply": "Með notandaaðgangi á Mastodon geturðu svarað þessari færslu.",
+  "interaction_modal.login.action": "Fara á heimastreymið mitt",
+  "interaction_modal.login.prompt": "Lén heimanetþjónsins þíns, t.d. mastodon.social",
+  "interaction_modal.no_account_yet": "Ekki á Mastodon?",
   "interaction_modal.on_another_server": "Á öðrum netþjóni",
   "interaction_modal.on_this_server": "Á þessum netþjóni",
+  "interaction_modal.sign_in": "Þú ert ekki skráð/ur inn á þennan netþjón. Hvar er aðgangurinn þinn hýstur?",
+  "interaction_modal.sign_in_hint": "Ábending: Það er vefsvæðið þar sem þú skráðir þig. Ef þú manst ekki hvar, geturðu leitað að kynningarpóstinum í pósthólfinu þínu. Þú getur líka sett inn fullt notandanafn þitt (t.d. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Setja færsluna frá {name} í eftirlæti",
   "interaction_modal.title.follow": "Fylgjast með {name}",
   "interaction_modal.title.reblog": "Endurbirta færsluna frá {name}",
   "interaction_modal.title.reply": "Svara færslunni frá {name}",
-  "interaction_modal.title.vote": "Greiða atkvæði í könnun á vegum {name}",
-  "interaction_modal.username_prompt": "Til dæmis {example}",
   "intervals.full.days": "{number, plural, one {# dagur} other {# dagar}}",
   "intervals.full.hours": "{number, plural, one {# klukkustund} other {# klukkustundir}}",
   "intervals.full.minutes": "{number, plural, one {# mínúta} other {# mínútur}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Birta/fela texta á bak við aðvörun vegna efnis",
   "keyboard_shortcuts.toggle_sensitivity": "Birta/fela myndir",
   "keyboard_shortcuts.toot": "Byrja nýja færslu",
-  "keyboard_shortcuts.translate": "að þýða færslu",
   "keyboard_shortcuts.unfocus": "Taka virkni úr textainnsetningarreit eða leit",
   "keyboard_shortcuts.up": "Fara ofar í listanum",
   "lightbox.close": "Loka",
@@ -490,32 +444,20 @@
   "link_preview.author": "Frá {name}",
   "link_preview.more_from_author": "Meira frá {name}",
   "link_preview.shares": "{count, plural, one {{counter} færsla} other {{counter} færslur}}",
-  "lists.add_member": "Bæta við",
-  "lists.add_to_list": "Bæta á lista",
-  "lists.add_to_lists": "Bæta {name} á lista",
-  "lists.create": "Búa til",
-  "lists.create_a_list_to_organize": "Búðu til nýjan lista til að skipuleggja heimastreymið þitt",
-  "lists.create_list": "Búa til lista",
+  "lists.account.add": "Bæta á lista",
+  "lists.account.remove": "Fjarlægja af lista",
   "lists.delete": "Eyða lista",
-  "lists.done": "Lokið",
   "lists.edit": "Breyta lista",
-  "lists.exclusive": "Fela meðlimi í heimastreyminu",
-  "lists.exclusive_hint": "Ef einhver er á þessum lista, geturðu falið viðkomandi í heimastreyminu þínu til að komast hjá því að sjá færslurnar þeirra í tvígang.",
-  "lists.find_users_to_add": "Finndu notendur til að bæta við",
-  "lists.list_members": "Meðlimir lista",
-  "lists.list_members_count": "{count, plural, one {# meðlimur} other {# meðlimir}}",
-  "lists.list_name": "Heiti lista",
-  "lists.new_list_name": "Heiti á nýjum lista",
-  "lists.no_lists_yet": "Ennþá engir listar.",
-  "lists.no_members_yet": "Ennþá engir meðlimir.",
-  "lists.no_results_found": "Engar niðurstöður fundust.",
-  "lists.remove_member": "Fjarlægja",
+  "lists.edit.submit": "Breyta titli",
+  "lists.exclusive": "Hylja þessar færslur í heimastreymi",
+  "lists.new.create": "Bæta við lista",
+  "lists.new.title_placeholder": "Titill á nýjum lista",
   "lists.replies_policy.followed": "Allra notenda sem fylgst er með",
   "lists.replies_policy.list": "Meðlima listans",
   "lists.replies_policy.none": "Engra",
-  "lists.save": "Vista",
-  "lists.search": "Leita",
-  "lists.show_replies_to": "Hafa með svör frá meðlimum lista til",
+  "lists.replies_policy.title": "Sýna svör til:",
+  "lists.search": "Leita meðal þeirra sem þú fylgist með",
+  "lists.subheading": "Listarnir þínir",
   "load_pending": "{count, plural, one {# nýtt atriði} other {# ný atriði}}",
   "loading_indicator.label": "Hleð inn…",
   "media_gallery.hide": "Fela",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} kærði {target}",
   "notification.admin.sign_up": "{name} skráði sig",
   "notification.admin.sign_up.name_and_others": "{name} og {count, plural, one {# í viðbót hefur} other {# í viðbót hafa}} skráð sig",
-  "notification.annual_report.message": "{year} á #Wrapstodon bíður! Afhjúpaðu hvað bar hæst á árinu og minnistæðustu augnablikin á Mastodon!",
-  "notification.annual_report.view": "Skoða #Wrapstodon",
   "notification.favourite": "{name} setti færsluna þína í eftirlæti",
   "notification.favourite.name_and_others_with_link": "{name} og <a>{count, plural, one {# í viðbót hefur} other {# í viðbót hafa}}</a> sett færsluna þína í eftirlæti",
-  "notification.favourite_pm": "{name} setti í eftirlæti færslu í einkaspjalli þar sem þú minntist á viðkomandi",
-  "notification.favourite_pm.name_and_others_with_link": "{name} og <a>{count, plural, one {# í viðbót} other {# í viðbót}}</a> settu í eftirlæti færslu í einkaspjalli þar sem þú minntist á viðkomandi",
   "notification.follow": "{name} fylgist með þér",
   "notification.follow.name_and_others": "{name} og <a>{count, plural, one {# í viðbót fylgdist} other {# í viðbót fylgdust}}</a> með þér",
   "notification.follow_request": "{name} hefur beðið um að fylgjast með þér",
@@ -605,11 +543,11 @@
   "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Samþykkja beiðni} other {Samþykkja beiðnir}}",
   "notification_requests.confirm_accept_multiple.message": "Þú ert að fara að samþykkja {count, plural, one {eina beiðni um tilkynningar} other {# beiðnir um tilkynningar}}. Ertu viss um að þú viljir halda áfram?",
   "notification_requests.confirm_accept_multiple.title": "Samþykkja beiðnir um tilkynningar?",
-  "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Hafna beiðni} other {Hafna beiðnum}}",
+  "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Afgreiða beiðni} other {Afgreiða beiðnir}}",
   "notification_requests.confirm_dismiss_multiple.message": "Þú ert að fara að hunsa {count, plural, one {eina beiðni um tilkynningar} other {# beiðnir um tilkynningar}}. Þú munt ekki eiga auðvelt með að skoða {count, plural, one {hana} other {þær}} aftur síðar. Ertu viss um að þú viljir halda áfram?",
   "notification_requests.confirm_dismiss_multiple.title": "Hunsa beiðnir um tilkynningar?",
-  "notification_requests.dismiss": "Hafna",
-  "notification_requests.dismiss_multiple": "{count, plural, one {Hafna # beiðni…} other {Hafna # beiðnum…}}",
+  "notification_requests.dismiss": "Afgreiða",
+  "notification_requests.dismiss_multiple": "{count, plural, one {Afgreiða # beiðni…} other {Afgreiða # beiðnir…}}",
   "notification_requests.edit_selection": "Breyta",
   "notification_requests.exit_selection": "Lokið",
   "notification_requests.explainer_for_limited_account": "Tilkynningar frá þessum notanda hafa verið síaðar þar sem aðgangur hans hefur verið takmarkaður af umsjónarmanni.",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Virkja tilkynningar á skjáborði",
   "notifications_permission_banner.how_to_control": "Til að taka á móti tilkynningum þegar Mastodon er ekki opið, skaltu virkja tilkynningar á skjáborði. Þegar þær eru orðnar virkar geturðu stýrt nákvæmlega hverskonar atvik framleiða tilkynningar með því að nota {icon}-hnappinn hér fyrir ofan.",
   "notifications_permission_banner.title": "Aldrei missa af neinu",
-  "onboarding.follows.back": "Til baka",
-  "onboarding.follows.done": "Lokið",
+  "onboarding.action.back": "Til baka",
+  "onboarding.actions.back": "Til baka",
+  "onboarding.actions.go_to_explore": "Sjáðu hvað er í umræðunni",
+  "onboarding.actions.go_to_home": "Fara á heimastreymið þitt",
+  "onboarding.compose.template": "Halló #Mastodon!",
   "onboarding.follows.empty": "Því miður er ekki hægt að birta neinar niðurstöður í augnablikinu. Þú getur reynt að nota leitina eða skoðað könnunarsíðuna til að finna fólk til að fylgjast með, nú eða prófað aftur síðar.",
-  "onboarding.follows.search": "Leita",
-  "onboarding.follows.title": "Þú ættir að fylgjast með fólki til að komast í gang",
+  "onboarding.follows.lead": "Þú ræktar heimastreymið þitt. Því fleira fólki sem þú fylgist með, því virkara og áhugaverðara verður það. Að fylgjast með þessum notendum gæti verið ágætt til að byrja með - þú getur alltaf hætt að fylgjast með þeim síðar!",
+  "onboarding.follows.title": "Vinsælt á Mastodon",
   "onboarding.profile.discoverable": "Gera notandasniðið mitt uppgötvanlegt",
   "onboarding.profile.discoverable_hint": "Þegar þú velur að hægt sé að uppgötva þig á Mastodon, munu færslurnar þínar birtast í leitarniðurstöðum og vinsældalistum, auk þess sem stungið verður upp á notandasniðinu þínu við fólk sem er með svipuð áhugamál og þú.",
   "onboarding.profile.display_name": "Birtingarnafn",
   "onboarding.profile.display_name_hint": "Fullt nafn þitt eða eitthvað til gamans…",
+  "onboarding.profile.lead": "Þú getur alltaf klárað þetta seinna í stillingunum, þar sem enn fleiri möguleikar bjóðast á sérsníðingum.",
   "onboarding.profile.note": "Æviágrip",
   "onboarding.profile.note_hint": "Þú getur @minnst á annað fólk eða #myllumerki…",
   "onboarding.profile.save_and_continue": "Vista og halda áfram",
   "onboarding.profile.title": "Uppsetning notandasniðs",
   "onboarding.profile.upload_avatar": "Sendu inn auðkennismynd",
   "onboarding.profile.upload_header": "Sendu inn bakgrunnsmynd í haus notandasniðs",
+  "onboarding.share.lead": "Láttu fólk vita hvernig það getur fundið þig á Mastodon!",
+  "onboarding.share.message": "Ég heiti {username} á #Mastodon! Þú getur fylgst með mér á {url}",
+  "onboarding.share.next_steps": "Möguleg næstu skref:",
+  "onboarding.share.title": "Deildu notandasniðinu þínu",
+  "onboarding.start.lead": "Nýi Mastodon-aðgangurinn þinn er tilbúinn. Hér sérðu hvernig þú nærð mestu út úr honum:",
+  "onboarding.start.skip": "Viltu sleppa þessu og halda beint áfram?",
+  "onboarding.start.title": "Þú hafðir það!",
+  "onboarding.steps.follow_people.body": "Þú ræktar heimastreymið þitt. Fyllum það með áhugaverðu fólki.",
+  "onboarding.steps.follow_people.title": "Fylgjast með {count, plural, one {einum aðila} other {# aðilum}}",
+  "onboarding.steps.publish_status.body": "Heilsaðu heiminum.",
+  "onboarding.steps.publish_status.title": "Gerðu fyrstu færsluna þína",
+  "onboarding.steps.setup_profile.body": "Annað fólk er líklegra til að eiga samskipti við þig ef þý setur einhverjar áhugaverðar upplýsingar í notandasniðið þitt.",
+  "onboarding.steps.setup_profile.title": "Sérsníddu notandasniðið þitt",
+  "onboarding.steps.share_profile.body": "Láttu vini þína vita hvernig þeir geta fundið þig á Mastodon!",
+  "onboarding.steps.share_profile.title": "Deildu notandasniðinu þínu",
+  "onboarding.tips.2fa": "<strong>Vissir þú?</strong> Þú getur gert aðganginn þinn öruggari með því að setja upp tveggja-þátta auðkenningu í stillingum aðgangsins þíns. Þetta virkar með hvaða TOTP-forriti sem er, án þess að nokkuð símanúmer sé nauðsynlegt!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Vissir þú?</strong> Þar sem Mastodon er þreifhýst kerfi, er næstum óhjákvæmilegt að sumt fólk sem þú rekst á sé hýst á öðrum netþjónum en þeim sem þú ert á. Samt geturðu átt hnökralaus samskipti við þetta fólk! Nafnið á netþjóninum þeirra er síðari hluti notandanafnsins!",
+  "onboarding.tips.migration": "<strong>Vissir þú?</strong> Ef þér finns eins og {domain} sé ekki endilega það sem henti þér í framtíðinni, þá geturðu flutt þig á annan Mastodon-netþjón án þess að missa fylgjendurna þína. Þú getur meira að segja hýst þinn eigin netþjón!",
+  "onboarding.tips.verification": "<strong>Vissir þú?</strong> Þú getur sannvottað aðganginn þinn með því að setja tengil á Mastodon-notandasniðið þitt inn á vefsvæðið þitt og síðan setja tengil á vefsvæðið þitt í notandasniðið þitt. Engin gjöld eða pappírsflóð!",
   "password_confirmation.exceeds_maxlength": "Staðfesting lykilorðs; fer fram úr hámarkslengd",
   "password_confirmation.mismatching": "Staðfesting lykilorðs; samsvara ekki",
   "picture_in_picture.restore": "Setja til baka",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Fjarlægja könnun",
   "privacy.change": "Aðlaga gagnaleynd færslu",
   "privacy.direct.long": "Allir sem minnst er á í færslunni",
-  "privacy.direct.short": "Einkaspjall",
+  "privacy.direct.short": "Tilteknir aðilar",
   "privacy.private.long": "Einungis þeir sem fylgjast með þér",
   "privacy.private.short": "Fylgjendur",
   "privacy.public.long": "Hver sem er, á og utan Mastodon",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Persónuverndarstefna",
   "recommended": "Mælt með",
   "refresh": "Endurlesa",
-  "regeneration_indicator.please_stand_by": "Hinkraðu við.",
-  "regeneration_indicator.preparing_your_home_feed": "Undirbý heimastreymið þitt…",
+  "regeneration_indicator.label": "Hleð inn…",
+  "regeneration_indicator.sublabel": "Verið er að útbúa heimastreymið þitt!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "Fyrir {number, plural, one {# degi} other {# dögum}} síðan",
   "relative_time.full.hours": "Fyrir {number, plural, one {# klukkustund} other {# klukkustundum}} síðan",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Notendasnið",
   "search_results.all": "Allt",
   "search_results.hashtags": "Myllumerki",
-  "search_results.no_results": "Engar niðurstöður.",
-  "search_results.no_search_yet": "Prófaðu að leita að færslum, notendum eða myllumerkjum.",
+  "search_results.nothing_found": "Gat ekki fundið neitt sem samsvarar þessum leitarorðum",
   "search_results.see_all": "Sjá allt",
   "search_results.statuses": "Færslur",
-  "search_results.title": "Leita að {q}\"",
+  "search_results.title": "Leita að {q}",
   "server_banner.about_active_users": "Folk sem hefur notað þennan netþjón síðustu 30 daga (virkir notendur í mánuðinum)",
   "server_banner.active_users": "virkir notendur",
   "server_banner.administered_by": "Stýrt af:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Enginn hefur ennþá endurbirt þessa færslu. Þegar einhver gerir það, mun það birtast hér.",
   "status.redraft": "Eyða og endurvinna drög",
   "status.remove_bookmark": "Fjarlægja bókamerki",
-  "status.remove_favourite": "Fjarlægja úr eftirlætum",
   "status.replied_in_thread": "Svaraði í samtali",
   "status.replied_to": "Svaraði til {name}",
   "status.reply": "Svara",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Breyta tungumálum í áskrift fyrir {target}",
   "tabs_bar.home": "Heim",
   "tabs_bar.notifications": "Tilkynningar",
-  "terms_of_service.effective_as_of": "Gildir frá og með {date}",
-  "terms_of_service.title": "Þjónustuskilmálar",
-  "terms_of_service.upcoming_changes_on": "Væntanlegar breytingar þann {date}",
   "time_remaining.days": "{number, plural, one {# dagur} other {# dagar}} eftir",
   "time_remaining.hours": "{number, plural, one {# klukkustund} other {# klukkustundir}} eftir",
   "time_remaining.minutes": "{number, plural, one {# mínúta} other {# mínútur}} eftir",
@@ -897,12 +853,26 @@
   "upload_button.label": "Bæta við myndum, myndskeiði eða hljóðskrá",
   "upload_error.limit": "Fór yfir takmörk á innsendingum skráa.",
   "upload_error.poll": "Innsending skráa er ekki leyfð í könnunum.",
+  "upload_form.audio_description": "Lýstu þessu fyrir heyrnarskerta",
+  "upload_form.description": "Lýstu þessu fyrir sjónskerta",
   "upload_form.drag_and_drop.instructions": "Til að taka í myndefnisviðhengi skaltu ýta á bilslána eða Enter. Til að draga geturðu notað örvalyklana til að færa viðhengið í samsvarandi áttir. Ýttu aftur á bilslána eða Enter til að sleppa viðhenginu á nýja staðinn, eða ýttu á Escape til að hætta við.",
   "upload_form.drag_and_drop.on_drag_cancel": "Hætt var við að draga. Myndefnisviðhenginu {item} var sleppt.",
   "upload_form.drag_and_drop.on_drag_end": "Myndefnisviðhenginu {item} var sleppt.",
   "upload_form.drag_and_drop.on_drag_over": "Myndefnisviðhengið {item} var fært.",
   "upload_form.drag_and_drop.on_drag_start": "Tók í myndefnisviðhengið {item}.",
   "upload_form.edit": "Breyta",
+  "upload_form.thumbnail": "Skipta um smámynd",
+  "upload_form.video_description": "Lýstu þessu fyrir fólk sem heyrir illa eða er með skerta sjón",
+  "upload_modal.analyzing_picture": "Greini mynd…",
+  "upload_modal.apply": "Virkja",
+  "upload_modal.applying": "Beiti…",
+  "upload_modal.choose_image": "Veldu mynd",
+  "upload_modal.description_placeholder": "Öllum dýrunum í skóginum þætti bezt að vera vinir",
+  "upload_modal.detect_text": "Skynja texta úr mynd",
+  "upload_modal.edit_media": "Breyta myndskrá",
+  "upload_modal.hint": "Smelltu eða dragðu til hringinn á forskoðuninni til að velja miðpunktinn sem verður alltaf sýnilegastur á öllum smámyndum.",
+  "upload_modal.preparing_ocr": "Undirbý OCR-ljóslestur…",
+  "upload_modal.preview_label": "Forskoðun ({ratio})",
   "upload_progress.label": "Er að senda inn...",
   "upload_progress.processing": "Meðhöndla…",
   "username.taken": "Þetta notandanafn er frátekið. Prófaðu eitthvað annað",
@@ -912,12 +882,8 @@
   "video.expand": "Stækka myndskeið",
   "video.fullscreen": "Skjáfylli",
   "video.hide": "Fela myndskeið",
-  "video.mute": "Þagga niður",
+  "video.mute": "Þagga hljóð",
   "video.pause": "Gera hlé",
   "video.play": "Spila",
-  "video.skip_backward": "Stökkva til baka",
-  "video.skip_forward": "Stökkva áfram",
-  "video.unmute": "Hætta að þagga",
-  "video.volume_down": "Lækka hljóðstyrk",
-  "video.volume_up": "Hækka hljóðstyrk"
+  "video.unmute": "Kveikja á hljóði"
 }
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index 36770c675f..bb4ea747d7 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -27,18 +27,16 @@
   "account.edit_profile": "Modifica profilo",
   "account.enable_notifications": "Avvisami quando @{name} pubblica un post",
   "account.endorse": "In evidenza sul profilo",
-  "account.featured": "In primo piano",
-  "account.featured.hashtags": "Hashtag",
-  "account.featured.posts": "Post",
   "account.featured_tags.last_status_at": "Ultimo post il {date}",
   "account.featured_tags.last_status_never": "Nessun post",
+  "account.featured_tags.title": "Hashtag in evidenza di {name}",
   "account.follow": "Segui",
   "account.follow_back": "Segui a tua volta",
   "account.followers": "Follower",
   "account.followers.empty": "Ancora nessuno segue questo utente.",
   "account.followers_counter": "{count, plural, one {{counter} seguace} other {{counter} seguaci}}",
   "account.following": "Seguiti",
-  "account.following_counter": "{count, plural, one {{counter} segui} other {{counter} seguiti}}",
+  "account.following_counter": "{count, plural, one {{counter} segui} other {{counter} segui}}",
   "account.follows.empty": "Questo utente non segue ancora nessuno.",
   "account.go_to_profile": "Vai al profilo",
   "account.hide_reblogs": "Nascondi condivisioni da @{name}",
@@ -67,13 +65,12 @@
   "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} post}}",
   "account.unblock": "Sblocca @{name}",
   "account.unblock_domain": "Sblocca il dominio {domain}",
-  "account.unblock_domain_short": "Sblocca",
   "account.unblock_short": "Sblocca",
   "account.unendorse": "Non mostrare sul profilo",
   "account.unfollow": "Smetti di seguire",
   "account.unmute": "Riattiva @{name}",
   "account.unmute_notifications_short": "Riattiva notifiche",
-  "account.unmute_short": "Attiva audio",
+  "account.unmute_short": "Riattiva",
   "account_note.placeholder": "Clicca per aggiungere una nota",
   "admin.dashboard.daily_retention": "Tasso di ritenzione dell'utente per giorno, dopo la registrazione",
   "admin.dashboard.monthly_retention": "Tasso di ritenzione dell'utente per mese, dopo la registrazione",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Si è verificato un errore imprevisto.",
   "alert.unexpected.title": "Oops!",
   "alt_text_badge.title": "Testo alternativo",
-  "alt_text_modal.add_alt_text": "Aggiungi testo alternativo",
-  "alt_text_modal.add_text_from_image": "Aggiungi testo dall'immagine",
-  "alt_text_modal.cancel": "Annulla",
-  "alt_text_modal.change_thumbnail": "Cambia la miniatura",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Descrivi questo per le persone con disturbi dell'udito…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Descrivi questo per le persone con disabilità visive…",
-  "alt_text_modal.done": "Fatto",
   "announcement.announcement": "Annuncio",
-  "annual_report.summary.archetype.booster": "Cacciatore/trice di tendenze",
-  "annual_report.summary.archetype.lurker": "L'osservatore/trice",
-  "annual_report.summary.archetype.oracle": "L'oracolo",
-  "annual_report.summary.archetype.pollster": "Sondaggista",
-  "annual_report.summary.archetype.replier": "Utente socievole",
-  "annual_report.summary.followers.followers": "seguaci",
-  "annual_report.summary.followers.total": "{count} in totale",
-  "annual_report.summary.here_it_is": "Ecco il tuo {year} in sintesi:",
-  "annual_report.summary.highlighted_post.by_favourites": "il post più apprezzato",
-  "annual_report.summary.highlighted_post.by_reblogs": "il post più condiviso",
-  "annual_report.summary.highlighted_post.by_replies": "il post con più risposte",
-  "annual_report.summary.highlighted_post.possessive": "di {name}",
-  "annual_report.summary.most_used_app.most_used_app": "l'app più utilizzata",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "l'hashtag più usato",
-  "annual_report.summary.most_used_hashtag.none": "Nessuno",
-  "annual_report.summary.new_posts.new_posts": "nuovi post",
-  "annual_report.summary.percentile.text": "<topLabel>Ciò ti colloca in cima</topLabel><percentage></percentage><bottomLabel>agli utenti di {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Non lo diremo a Bernie.",
-  "annual_report.summary.thanks": "Grazie per far parte di Mastodon!",
   "attachments_list.unprocessed": "(non elaborato)",
   "audio.hide": "Nascondi audio",
   "block_modal.remote_users_caveat": "Chiederemo al server {domain} di rispettare la tua decisione. Tuttavia, la conformità non è garantita poiché alcuni server potrebbero gestire i blocchi in modo diverso. I post pubblici potrebbero essere ancora visibili agli utenti che non hanno effettuato l'accesso.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "Impossibile trovare la pagina richiesta. Sei sicuro che l'URL nella barra degli indirizzi sia corretto?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Chiudi",
-  "bundle_modal_error.message": "Si è verificato un errore durante il caricamento di questa schermata.",
+  "bundle_modal_error.message": "Qualcosa è andato storto scaricando questo componente.",
   "bundle_modal_error.retry": "Riprova",
   "closed_registrations.other_server_instructions": "Poiché Mastodon è decentralizzato, puoi creare un profilo su un altro server, pur continuando a interagire con questo.",
   "closed_registrations_modal.description": "Correntemente, è impossibile creare un profilo su {domain}, ma sei pregato di tenere presente che non necessiti di un profilo specificamente su {domain} per utilizzare Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Utenti bloccati",
   "column.bookmarks": "Segnalibri",
   "column.community": "Cronologia locale",
-  "column.create_list": "Crea lista",
   "column.direct": "Menzioni private",
   "column.directory": "Sfoglia profili",
   "column.domain_blocks": "Domini bloccati",
-  "column.edit_list": "Modifica lista",
   "column.favourites": "Preferiti",
   "column.firehose": "Feed dal vivo",
   "column.follow_requests": "Richieste di seguirti",
   "column.home": "Home",
-  "column.list_members": "Gestisci i membri della lista",
   "column.lists": "Liste",
   "column.mutes": "Utenti silenziati",
   "column.notifications": "Notifiche",
@@ -172,7 +140,6 @@
   "column_header.pin": "Fissa",
   "column_header.show_settings": "Mostra le impostazioni",
   "column_header.unpin": "Non fissare",
-  "column_search.cancel": "Annulla",
   "column_subheading.settings": "Impostazioni",
   "community.column_settings.local_only": "Solo Locale",
   "community.column_settings.media_only": "Solo Media",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Durata del sondaggio",
   "compose_form.poll.multiple": "Scelta multipla",
   "compose_form.poll.option_placeholder": "Opzione {number}",
-  "compose_form.poll.single": "Scelta singola",
+  "compose_form.poll.single": "Scegli uno",
   "compose_form.poll.switch_to_multiple": "Modifica il sondaggio per consentire scelte multiple",
   "compose_form.poll.switch_to_single": "Modifica il sondaggio per consentire una singola scelta",
   "compose_form.poll.type": "Stile",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Modifica",
   "confirmations.edit.message": "Modificare ora sovrascriverà il messaggio che stai correntemente componendo. Sei sicuro di voler procedere?",
   "confirmations.edit.title": "Sovrascrivere il post?",
-  "confirmations.follow_to_list.confirm": "Segui e aggiungi alla lista",
-  "confirmations.follow_to_list.message": "Devi seguire {name} per aggiungerli a una lista.",
-  "confirmations.follow_to_list.title": "Seguire l'utente?",
   "confirmations.logout.confirm": "Disconnettiti",
   "confirmations.logout.message": "Sei sicuro di volerti disconnettere?",
   "confirmations.logout.title": "Uscire?",
-  "confirmations.missing_alt_text.confirm": "Aggiungi testo alternativo",
-  "confirmations.missing_alt_text.message": "Il tuo post contiene media senza testo alternativo. L'aggiunta di descrizioni aiuta a rendere i tuoi contenuti accessibili a più persone.",
-  "confirmations.missing_alt_text.secondary": "Pubblica comunque",
-  "confirmations.missing_alt_text.title": "Aggiungere testo alternativo?",
   "confirmations.mute.confirm": "Silenzia",
   "confirmations.redraft.confirm": "Elimina e riscrivi",
   "confirmations.redraft.message": "Sei sicuro di voler eliminare questo post e riscriverlo? I preferiti e i boost andranno persi e le risposte al post originale non saranno più collegate.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Il tuo profilo {disabledAccount} è correntemente disabilitato.",
   "dismissable_banner.community_timeline": "Questi sono i post pubblici più recenti da persone i cui profili sono ospitati da {domain}.",
   "dismissable_banner.dismiss": "Ignora",
-  "dismissable_banner.explore_links": "Queste notizie sono le più condivise sul fediverso, oggi. Le notizie più recenti pubblicate da più persone differenti sono classificate più in alto.",
-  "dismissable_banner.explore_statuses": "Questi post provenienti da tutto il fediverso stanno guadagnando terreno oggi. I post più recenti con più condivisioni e gradimenti sono classificati più in alto.",
-  "dismissable_banner.explore_tags": "Questi hashtag stanno guadagnando terreno nel fediverso, oggi. Gli hashtag che vengono usati da più persone differenti sono classificati più in alto.",
-  "dismissable_banner.public_timeline": "Questi sono i post pubblici più recenti pubblicati dalle persone sul fediverso che sono seguite dagli utenti su {domain}.",
+  "dismissable_banner.explore_links": "Queste notizie sono discusse da persone su questo e altri server della rete decentralizzata, al momento.",
+  "dismissable_banner.explore_statuses": "Questi sono post da tutto il social web che stanno guadagnando popolarità oggi. I post più recenti con più condivisioni e preferiti sono classificati più in alto.",
+  "dismissable_banner.explore_tags": "Questi hashtag stanno ottenendo popolarità tra le persone su questo e altri server della rete decentralizzata, al momento.",
+  "dismissable_banner.public_timeline": "Questi sono i post pubblici più recenti di persone sul social che le persone su {domain} seguono.",
   "domain_block_modal.block": "Blocca il server",
   "domain_block_modal.block_account_instead": "Blocca invece @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Le persone da questo server possono interagire con i tuoi vecchi post.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Risultati della ricerca",
   "emoji_button.symbols": "Simboli",
   "emoji_button.travel": "Viaggi & Luoghi",
-  "empty_column.account_featured": "Questa lista è vuota",
   "empty_column.account_hides_collections": "Questo utente ha scelto di non rendere disponibili queste informazioni",
   "empty_column.account_suspended": "Profilo sospeso",
   "empty_column.account_timeline": "Nessun post qui!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Non c'è ancora nulla in questo hashtag.",
   "empty_column.home": "La cronologia della tua home è vuota! Segui altre persone per riempirla. {suggestions}",
   "empty_column.list": "Non c'è ancora nulla in questo elenco. Quando i membri di questo elenco pubblicheranno nuovi post, appariranno qui.",
+  "empty_column.lists": "Non hai ancora nessun elenco. Quando ne creerai uno, apparirà qui.",
   "empty_column.mutes": "Non hai ancora silenziato alcun utente.",
   "empty_column.notification_requests": "Tutto chiaro! Non c'è niente qui. Quando ricevi nuove notifiche, verranno visualizzate qui in base alle tue impostazioni.",
   "empty_column.notifications": "Non hai ancora nessuna notifica. Quando altre persone interagiranno con te, le vedrai qui.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Prova a disabilitarli e a ricaricare la pagina. Se ciò non aiuta, potresti ancora utilizzare Mastodon tramite un browser differente o un'app nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Copia stacktrace negli appunti",
   "errors.unexpected_crash.report_issue": "Segnala un problema",
+  "explore.search_results": "Risultati della ricerca",
   "explore.suggested_follows": "Persone",
   "explore.title": "Esplora",
   "explore.trending_links": "Notizie",
@@ -373,14 +334,13 @@
   "footer.about": "Info",
   "footer.directory": "Cartella dei profili",
   "footer.get_app": "Scarica l'app",
+  "footer.invite": "Invita persone",
   "footer.keyboard_shortcuts": "Scorciatoie da tastiera",
   "footer.privacy_policy": "Politica sulla privacy",
   "footer.source_code": "Visualizza il codice sorgente",
   "footer.status": "Stato",
-  "footer.terms_of_service": "Termini di servizio",
   "generic.saved": "Salvato",
   "getting_started.heading": "Per iniziare",
-  "hashtag.admin_moderation": "Apri l'interfaccia di moderazione per #{name}",
   "hashtag.column_header.tag_mode.all": "e {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
   "hashtag.column_header.tag_mode.none": "senza {additional}",
@@ -422,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignorare le notifiche delle persone che non ti seguono?",
   "ignore_notifications_modal.not_following_title": "Ignorare le notifiche delle persone che non segui?",
   "ignore_notifications_modal.private_mentions_title": "Ignorare le notifiche provenienti da menzioni private indesiderate?",
-  "info_button.label": "Aiuto",
-  "info_button.what_is_alt_text": "<h1>Cos'è il testo alternativo?</h1> <p>Il testo alternativo fornisce descrizioni delle immagini per le persone con disturbi della vista, connessioni a bassa larghezza di banda o per coloro che cercano un contesto aggiuntivo.</p> <p>È possibile migliorare l'accessibilità e la comprensione per tutti scrivendo un testo alt chiaro, conciso e obiettivo.</p> <ul> <li>Cattura elementi importanti</li> <li>Riassume il testo nelle immagini</li> <li>Usa la struttura delle frasi regolari</li> <li>Evita le informazioni ridondanti</li> <li>Concentrati sulle tendenze e i risultati chiave in immagini complesse (come diagrammi o mappe)</li> </ul>",
-  "interaction_modal.action.favourite": "Per continuare, devi aggiungere ai preferiti il ​​tuo account.",
-  "interaction_modal.action.follow": "Per continuare, devi seguire dal tuo account.",
-  "interaction_modal.action.reblog": "Per continuare, devi condividere dal tuo account.",
-  "interaction_modal.action.reply": "Per continuare, devi rispondere dal tuo account.",
-  "interaction_modal.action.vote": "Per continuare, devi votare dal tuo account.",
-  "interaction_modal.go": "Vai",
-  "interaction_modal.no_account_yet": "Non hai ancora un account?",
+  "interaction_modal.description.favourite": "Con un account su Mastodon, puoi aggiungere questo post ai preferiti per far sapere all'autore che lo apprezzi e salvarlo per dopo.",
+  "interaction_modal.description.follow": "Con un profilo di Mastodon, puoi seguire {name} per ricevere i suoi post nel feed della tua home.",
+  "interaction_modal.description.reblog": "Con un profilo di Mastodon, puoi rebloggare questo post per condividerlo con i tuoi seguaci.",
+  "interaction_modal.description.reply": "Con un profilo di Mastodon, puoi rispondere a questo post.",
+  "interaction_modal.login.action": "Portami alla pagina iniziale",
+  "interaction_modal.login.prompt": "Dominio del tuo server principale, ad esempio mastodon.social",
+  "interaction_modal.no_account_yet": "Non su Mastodon?",
   "interaction_modal.on_another_server": "Su un altro server",
   "interaction_modal.on_this_server": "Su questo server",
+  "interaction_modal.sign_in": "Non sei connesso a questo server. Dove è ospitato il tuo account?",
+  "interaction_modal.sign_in_hint": "Suggerimento: questo è il sito in cui ti sei registrato. Se non ti ricordi, cerca l'e-mail di benvenuto nella tua casella di posta. Puoi anche inserire il tuo nome utente completo! (es. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Contrassegna il post di {name} come preferito",
   "interaction_modal.title.follow": "Segui {name}",
   "interaction_modal.title.reblog": "Reblogga il post di {name}",
   "interaction_modal.title.reply": "Rispondi al post di {name}",
-  "interaction_modal.title.vote": "Vota nel sondaggio di {name}",
-  "interaction_modal.username_prompt": "Es. {example}",
   "intervals.full.days": "{number, plural, one {# giorno} other {# giorni}}",
   "intervals.full.hours": "{number, plural, one {# ora} other {# ore}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# minuti}}",
@@ -474,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Mostra/Nasconde il testo dietro CW",
   "keyboard_shortcuts.toggle_sensitivity": "Mostra/Nasconde media",
   "keyboard_shortcuts.toot": "Crea un nuovo post",
-  "keyboard_shortcuts.translate": "Traduce un post",
   "keyboard_shortcuts.unfocus": "Rimuove il focus sull'area di composizione testuale/ricerca",
   "keyboard_shortcuts.up": "Scorre in su nell'elenco",
   "lightbox.close": "Chiudi",
@@ -487,32 +444,20 @@
   "link_preview.author": "Di {name}",
   "link_preview.more_from_author": "Altro da {name}",
   "link_preview.shares": "{count, plural, one {{counter} post} other {{counter} post}}",
-  "lists.add_member": "Aggiungi",
-  "lists.add_to_list": "Aggiungi alla lista",
-  "lists.add_to_lists": "Aggiungi {name} alle liste",
-  "lists.create": "Crea",
-  "lists.create_a_list_to_organize": "Crea una nuova lista per organizzare il tuo feed Home",
-  "lists.create_list": "Crea lista",
+  "lists.account.add": "Aggiungi all'elenco",
+  "lists.account.remove": "Rimuovi dall'elenco",
   "lists.delete": "Elimina elenco",
-  "lists.done": "Fatto",
   "lists.edit": "Modifica elenco",
-  "lists.exclusive": "Nascondi i membri in Home",
-  "lists.exclusive_hint": "Se qualcuno è presente in questa lista, nascondilo nel tuo feed Home per evitare di vedere i suoi post due volte.",
-  "lists.find_users_to_add": "Trova utenti da aggiungere",
-  "lists.list_members": "Membri della lista",
-  "lists.list_members_count": "{count, plural, one {# membro} other {# membri}}",
-  "lists.list_name": "Nome della lista",
-  "lists.new_list_name": "Nuovo nome della lista",
-  "lists.no_lists_yet": "Non ci sono ancora liste.",
-  "lists.no_members_yet": "Non ci sono ancora membri.",
-  "lists.no_results_found": "Nessun risultato trovato.",
-  "lists.remove_member": "Rimuovi",
+  "lists.edit.submit": "Cambia il titolo",
+  "lists.exclusive": "Nascondi questi post dalla home",
+  "lists.new.create": "Aggiungi lista",
+  "lists.new.title_placeholder": "Titolo del nuovo elenco",
   "lists.replies_policy.followed": "Qualsiasi utente seguito",
   "lists.replies_policy.list": "Membri dell'elenco",
   "lists.replies_policy.none": "Nessuno",
-  "lists.save": "Salva",
-  "lists.search": "Cerca",
-  "lists.show_replies_to": "Includi le risposte dei membri della lista a",
+  "lists.replies_policy.title": "Mostra risposte a:",
+  "lists.search": "Cerca tra le persone che segui",
+  "lists.subheading": "Le tue liste",
   "load_pending": "{count, plural, one {# nuovo oggetto} other {# nuovi oggetti}}",
   "loading_indicator.label": "Caricamento…",
   "media_gallery.hide": "Nascondi",
@@ -561,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} ha segnalato {target}",
   "notification.admin.sign_up": "{name} si è iscritto",
   "notification.admin.sign_up.name_and_others": "Si sono iscritti: {name} e {count, plural, one {# altro utente} other {altri # utenti}}",
-  "notification.annual_report.message": "Il tuo #Wrapstodon {year} ti aspetta! Scopri i momenti salienti e memorabili del tuo anno su Mastodon!",
-  "notification.annual_report.view": "Visualizza #Wrapstodon",
   "notification.favourite": "{name} ha aggiunto il tuo post ai preferiti",
   "notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {# altro} other {altri #}}</a> hanno aggiunto il tuo post ai preferiti",
-  "notification.favourite_pm": "{name} ha aggiunto ai preferiti la tua menzione privata",
-  "notification.favourite_pm.name_and_others_with_link": "{name} e <a>{count, plural, one {# altro utente} other {# altri utenti}}</a> hanno aggiunto ai preferiti la tua menzione privata",
   "notification.follow": "{name} ha iniziato a seguirti",
   "notification.follow.name_and_others": "{name} e <a>{count, plural, one {# altro} other {altri #}}</a> hanno iniziato a seguirti",
   "notification.follow_request": "{name} ha richiesto di seguirti",
@@ -671,21 +612,44 @@
   "notifications_permission_banner.enable": "Abilita le notifiche desktop",
   "notifications_permission_banner.how_to_control": "Per ricevere le notifiche quando Mastodon non è aperto, abilita le notifiche desktop. Puoi controllare precisamente quali tipi di interazioni generano le notifiche destkop, tramite il pulsante {icon} sopra, una volta abilitate.",
   "notifications_permission_banner.title": "Non perderti mai nulla",
-  "onboarding.follows.back": "Indietro",
-  "onboarding.follows.done": "Fatto",
+  "onboarding.action.back": "Torna indietro",
+  "onboarding.actions.back": "Torna indietro",
+  "onboarding.actions.go_to_explore": "Guarda le tendenze",
+  "onboarding.actions.go_to_home": "Vai alla cronologia della tua home",
+  "onboarding.compose.template": "Ciao #Mastodon!",
   "onboarding.follows.empty": "Sfortunatamente, nessun risultato può essere mostrato in questo momento. Puoi provare a utilizzare la ricerca o sfogliare la pagina di esplorazione per trovare persone da seguire, oppure riprova più tardi.",
-  "onboarding.follows.search": "Cerca",
-  "onboarding.follows.title": "Segui le persone per iniziare",
+  "onboarding.follows.lead": "La cronologia della tua home è gestita da te. Più persone segui, più attiva e interessante sarà. Questi profili possono essere un buon punto di partenza; puoi sempre smettere di seguirli in seguito!",
+  "onboarding.follows.title": "Popolare su Mastodon",
   "onboarding.profile.discoverable": "Rendi il mio profilo rilevabile",
   "onboarding.profile.discoverable_hint": "Quando attivi la rilevabilità su Mastodon, i tuoi post potrebbero apparire nei risultati di ricerca e nelle tendenze e il tuo profilo potrebbe essere suggerito a persone con interessi simili ai tuoi.",
   "onboarding.profile.display_name": "Nome da visualizzare",
   "onboarding.profile.display_name_hint": "Il tuo nome completo o il tuo nome divertente…",
+  "onboarding.profile.lead": "Puoi sempre completarlo in un secondo momento nelle impostazioni, dove sono disponibili ancora più opzioni di personalizzazione.",
   "onboarding.profile.note": "Biografia",
   "onboarding.profile.note_hint": "Puoi @menzionare altre persone o #hashtags…",
   "onboarding.profile.save_and_continue": "Salva e continua",
   "onboarding.profile.title": "Configurazione del profilo",
   "onboarding.profile.upload_avatar": "Carica l'immagine del profilo",
   "onboarding.profile.upload_header": "Carica l'intestazione del profilo",
+  "onboarding.share.lead": "Fai sapere alle persone come possono trovarti su Mastodon!",
+  "onboarding.share.message": "Sono {username} su #Mastodon! Vieni a seguirmi su {url}",
+  "onboarding.share.next_steps": "Possibili passaggi successivi:",
+  "onboarding.share.title": "Condividi il tuo profilo",
+  "onboarding.start.lead": "Il tuo nuovo account Mastodon è pronto. Ecco come puoi sfruttarlo al meglio:",
+  "onboarding.start.skip": "Vuoi saltare tutto questo?",
+  "onboarding.start.title": "Ce l'hai fatta!",
+  "onboarding.steps.follow_people.body": "Gestisci la tua cronologia. Riempila di persone interessanti.",
+  "onboarding.steps.follow_people.title": "Segui {count, plural, one {una persona} other {# persone}}",
+  "onboarding.steps.publish_status.body": "",
+  "onboarding.steps.publish_status.title": "Scrivi il tuo primo post",
+  "onboarding.steps.setup_profile.body": "Gli altri hanno maggiori probabilità di interagire con te se completi il tuo profilo.",
+  "onboarding.steps.setup_profile.title": "Personalizza il tuo profilo",
+  "onboarding.steps.share_profile.body": "Fai sapere ai tuoi amici come trovarti su Mastodonte",
+  "onboarding.steps.share_profile.title": "Condividi il tuo profilo",
+  "onboarding.tips.2fa": "<strong>Lo sapevi?</strong> Puoi proteggere il tuo account impostando l'autenticazione a due fattori nelle impostazioni del tuo account. Funziona con qualsiasi app TOTP di tua scelta, nessun numero di telefono necessario!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Lo sapevi?</strong> Dal momento che Mastodon è decentralizzato, alcuni profili che incontrerai sono ospitati su server diversi dal tuo. Ma puoi interagire con loro senza problemi! Il loro server è nella seconda metà del loro nome utente!",
+  "onboarding.tips.migration": "<strong>Lo sapevi?</strong> Se ritieni che {domain} non sia un'ottima scelta di server per te in futuro, puoi spostarti su un altro server Mastodon senza perdere i tuoi seguaci. Puoi persino ospitare il tuo server!",
+  "onboarding.tips.verification": "<strong>Lo sapevi?</strong> Puoi verificare il tuo account inserendo un collegamento al tuo profilo Mastodon sul tuo sito web e aggiungendo il sito web al tuo profilo. Nessuna spesa o documento necessario!",
   "password_confirmation.exceeds_maxlength": "La conferma della password supera la lunghezza massima della password",
   "password_confirmation.mismatching": "Le password non corrispondono",
   "picture_in_picture.restore": "Ripristinala",
@@ -701,7 +665,7 @@
   "poll_button.remove_poll": "Rimuovi il sondaggio",
   "privacy.change": "Modifica privacy del post",
   "privacy.direct.long": "Tutti quelli menzionati nel post",
-  "privacy.direct.short": "Menzione privata",
+  "privacy.direct.short": "Persone specifiche",
   "privacy.private.long": "Solo i tuoi follower",
   "privacy.private.short": "Follower",
   "privacy.public.long": "Chiunque dentro e fuori Mastodon",
@@ -713,8 +677,8 @@
   "privacy_policy.title": "Politica sulla Privacy",
   "recommended": "Consigliato",
   "refresh": "Ricarica",
-  "regeneration_indicator.please_stand_by": "Si prega di rimanere in attesa.",
-  "regeneration_indicator.preparing_your_home_feed": "Preparazione della tua home feed in corso…",
+  "regeneration_indicator.label": "Caricamento…",
+  "regeneration_indicator.sublabel": "Il feed della tua home è in preparazione!",
   "relative_time.days": "{number}g",
   "relative_time.full.days": "{number, plural, one {# giorno} other {# giorni}} fa",
   "relative_time.full.hours": "{number, plural, one {# ora} other {# ore}} fa",
@@ -798,11 +762,10 @@
   "search_results.accounts": "Profili",
   "search_results.all": "Tutto",
   "search_results.hashtags": "Hashtag",
-  "search_results.no_results": "Nessun risultato.",
-  "search_results.no_search_yet": "Prova a cercare post, profili o hashtag.",
+  "search_results.nothing_found": "Impossibile trovare qualcosa per questi termini di ricerca",
   "search_results.see_all": "Mostra tutto",
   "search_results.statuses": "Post",
-  "search_results.title": "Cerca \"{q}\"",
+  "search_results.title": "Cerca {q}",
   "server_banner.about_active_users": "Persone che hanno utilizzato questo server negli ultimi 30 giorni (Utenti Attivi Mensilmente)",
   "server_banner.active_users": "utenti attivi",
   "server_banner.administered_by": "Amministrato da:",
@@ -854,7 +817,6 @@
   "status.reblogs.empty": "Ancora nessuno ha rebloggato questo post. Quando qualcuno lo farà, apparirà qui.",
   "status.redraft": "Elimina e riscrivi",
   "status.remove_bookmark": "Rimuovi segnalibro",
-  "status.remove_favourite": "Rimuovi dai preferiti",
   "status.replied_in_thread": "Ha risposto nella discussione",
   "status.replied_to": "Risposta a {name}",
   "status.reply": "Rispondi",
@@ -876,9 +838,6 @@
   "subscribed_languages.target": "Modifica le lingue in cui sei iscritto per {target}",
   "tabs_bar.home": "Home",
   "tabs_bar.notifications": "Notifiche",
-  "terms_of_service.effective_as_of": "In vigore a partire dal giorno {date}",
-  "terms_of_service.title": "Termini di Servizio",
-  "terms_of_service.upcoming_changes_on": "Prossime modifiche nel giorno {date}",
   "time_remaining.days": "{number, plural, one {# giorno} other {# giorni}} left",
   "time_remaining.hours": "{number, plural, one {# ora} other {# ore}} left",
   "time_remaining.minutes": "{number, plural, one {# minuto} other {# minuti}} left",
@@ -894,12 +853,26 @@
   "upload_button.label": "Aggiungi un file immagine, video o audio",
   "upload_error.limit": "Limite di caricamento dei file superato.",
   "upload_error.poll": "Caricamento del file non consentito con i sondaggi.",
+  "upload_form.audio_description": "Descrizione per persone con deficit uditivi",
+  "upload_form.description": "Descrizione per ipovedenti",
   "upload_form.drag_and_drop.instructions": "Per selezionare un allegato multimediale, premi Spazio o Invio. Mentre trascini, usa i tasti con le frecce per spostare l'allegato multimediale in una qualsiasi direzione. Premi di nuovo Spazio o Invio per rilasciare l'allegato multimediale nella sua nuova posizione, oppure premi Esc per annullare.",
   "upload_form.drag_and_drop.on_drag_cancel": "Il trascinamento è stato annullato. L'allegato multimediale {item} è stato eliminato.",
   "upload_form.drag_and_drop.on_drag_end": "L'allegato multimediale {item} è stato eliminato.",
   "upload_form.drag_and_drop.on_drag_over": "L'allegato multimediale {item} è stato spostato.",
   "upload_form.drag_and_drop.on_drag_start": "L'allegato multimediale {item} è stato ricevuto.",
   "upload_form.edit": "Modifica",
+  "upload_form.thumbnail": "Cambia la miniatura",
+  "upload_form.video_description": "Descrizione per persone con deficit uditivi o ipovedenti",
+  "upload_modal.analyzing_picture": "Analizzando l'immagine…",
+  "upload_modal.apply": "Applica",
+  "upload_modal.applying": "Applicazione…",
+  "upload_modal.choose_image": "Scegli l'immagine",
+  "upload_modal.description_placeholder": "Ma la volpe col suo balzo ha raggiunto il fiero Fido",
+  "upload_modal.detect_text": "Rileva il testo dall'immagine",
+  "upload_modal.edit_media": "Modifica il media",
+  "upload_modal.hint": "Clicca o trascina il cerchio sull'anteprima per scegliere il punto focale che sarà sempre visualizzato su tutte le miniature.",
+  "upload_modal.preparing_ocr": "Preparazione OCR…",
+  "upload_modal.preview_label": "Anteprima ({ratio})",
   "upload_progress.label": "Caricamento...",
   "upload_progress.processing": "Elaborazione…",
   "username.taken": "Quel nome utente è già in uso. Prova con un altro",
@@ -909,12 +882,8 @@
   "video.expand": "Espandi il video",
   "video.fullscreen": "Schermo intero",
   "video.hide": "Nascondi il video",
-  "video.mute": "Muta",
+  "video.mute": "Silenzia suono",
   "video.pause": "Pausa",
   "video.play": "Riproduci",
-  "video.skip_backward": "Vai indietro",
-  "video.skip_forward": "Vai avanti",
-  "video.unmute": "Muta",
-  "video.volume_down": "Abbassa volume",
-  "video.volume_up": "Alza volume"
+  "video.unmute": "Riattiva suono"
 }
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index a2328401f4..e05c320d1f 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -40,6 +40,7 @@
   "account.endorse": "プロフィールで紹介する",
   "account.featured_tags.last_status_at": "最終投稿 {date}",
   "account.featured_tags.last_status_never": "投稿がありません",
+  "account.featured_tags.title": "{name}の注目ハッシュタグ",
   "account.follow": "フォロー",
   "account.follow_back": "フォローバック",
   "account.followers": "フォロワー",
@@ -97,85 +98,38 @@
   "alert.unexpected.message": "不明なエラーが発生しました。",
   "alert.unexpected.title": "エラー!",
   "alt_text_badge.title": "代替テキスト",
-  "alt_text_modal.add_alt_text": "代替テキストを追加",
-  "alt_text_modal.add_text_from_image": "画像からテキストを追加",
-  "alt_text_modal.cancel": "キャンセル",
-  "alt_text_modal.change_thumbnail": "サムネイルを変更",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "耳の不自由な方のために説明してください…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "目が不自由な方のために説明してください…",
-  "alt_text_modal.done": "完了",
   "announcement.announcement": "お知らせ",
-  "annual_report.summary.archetype.booster": "トレンドハンター",
-  "annual_report.summary.archetype.lurker": "ROM専",
-  "annual_report.summary.archetype.oracle": "予言者",
-  "annual_report.summary.archetype.pollster": "調査員",
-  "annual_report.summary.archetype.replier": "社交家",
-  "annual_report.summary.followers.followers": "フォロワー",
-  "annual_report.summary.followers.total": "合計{count}",
-  "annual_report.summary.here_it_is": "こちらがあなたの{year}年の振り返りです",
-  "annual_report.summary.highlighted_post.by_favourites": "最もお気に入りされた投稿",
-  "annual_report.summary.highlighted_post.by_reblogs": "最もブーストされた投稿",
-  "annual_report.summary.highlighted_post.by_replies": "最も返信が多かった投稿",
-  "annual_report.summary.highlighted_post.possessive": "{name}の",
-  "annual_report.summary.most_used_app.most_used_app": "最も使用されているアプリ",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "最も使用されたハッシュタグ",
-  "annual_report.summary.most_used_hashtag.none": "なし",
-  "annual_report.summary.new_posts.new_posts": "新しい投稿",
-  "annual_report.summary.percentile.text": "<topLabel>{domain}で 上位</topLabel><percentage></percentage><bottomLabel>に入ります!</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "バー二ーには秘密にしておくよ。",
-  "annual_report.summary.thanks": "Mastodonの一員になってくれてありがとう!",
+  "antennas.account.add": "アンテナに追加",
+  "antennas.account.remove": "アンテナから外す",
   "antennas.accounts": "{count} のアカウント",
   "antennas.add_domain": "新規ドメイン",
   "antennas.add_domain_placeholder": "新しいドメイン名",
   "antennas.add_keyword": "新規キーワード",
   "antennas.add_keyword_placeholder": "新しいキーワード",
-  "antennas.add_member": "追加",
   "antennas.add_tag": "新規タグ",
   "antennas.add_tag_placeholder": "新しいタグ",
-  "antennas.add_to_antenna": "アンテナに追加",
-  "antennas.add_to_antennas": "{name}をアンテナに追加",
-  "antennas.antenna_accounts": "アンテナのアカウント",
-  "antennas.antenna_accounts_count": "{count}のアカウント",
-  "antennas.antenna_name": "アンテナの名前",
-  "antennas.create": "新規作成",
-  "antennas.create_a_antenna_to_organize": "興味ある話題を検出するためにアンテナを作成する",
-  "antennas.create_antenna": "アンテナを作成",
   "antennas.delete": "アンテナを削除",
-  "antennas.destination": "検出された投稿の配置先",
-  "antennas.destination.home": "ホームに追加",
-  "antennas.destination.list": "リストに追加",
-  "antennas.destination.timeline": "アンテナタイムラインのみ",
   "antennas.domains": "{count} のドメイン",
-  "antennas.done": "保存",
   "antennas.edit": "アンテナを編集",
+  "antennas.edit.submit": "タイトルを変更",
+  "antennas.edit_static": "旧編集画面に移動",
   "antennas.edit_accounts": "アカウントを編集",
   "antennas.exclude_accounts": "除外するアカウント",
   "antennas.exclude_domains": "除外するドメイン",
   "antennas.exclude_keywords": "除外するキーワード",
   "antennas.exclude_tags": "除外するタグ",
-  "antennas.favourite": "お気に入りに登録",
-  "antennas.favourite_hint": "お気に入りに登録したアンテナは、PCのWebクライアントでナビゲーションに表示されます",
-  "antennas.filter_items": "絞り込み条件の設定に移動",
+  "antennas.filter": "絞り込み条件",
   "antennas.filter_not": "絞り込み条件の例外",
+  "antennas.go_timeline": "タイムラインを見る",
   "antennas.ignore_reblog": "ブーストを除外",
-  "antennas.ignore_reblog_hint": "ブーストはアンテナの検出対象から外れます",
-  "antennas.in_ltl_mode": "LTLモードが有効になっています",
   "antennas.in_stl_mode": "STLモードが有効になっています",
+  "antennas.insert_feeds": "リストまたはホームに挿入",
   "antennas.keywords": "{count} のキーワード",
-  "antennas.list_selection": "投稿追加先のリスト",
   "antennas.media_only": "メディアのみ",
-  "antennas.media_only_hint": "メディアの添付された投稿のみがアンテナに検出されます",
-  "antennas.memo_insert_home": "ホームタイムラインに挿入",
-  "antennas.memo_insert_list": "リスト: {title}",
-  "antennas.mode": "動作モード",
-  "antennas.mode.filtering": "フィルタリング",
-  "antennas.mode.ltl": "ローカルタイムラインモード",
-  "antennas.mode.stl": "ソーシャルタイムラインモード",
   "antennas.new.create": "アンテナを作成",
   "antennas.new.title_placeholder": "新規アンテナ名",
   "antennas.not_related_list": "このアンテナはどのリストにも関連付けられていません。",
   "antennas.related_list": "このアンテナは {listTitle} に関連付けられています。",
-  "antennas.save_to_edit_filtering": "絞り込み条件は、このアンテナを保存した後に編集できます",
   "antennas.search": "すべてのユーザーから検索",
   "antennas.select.no_options_message": "リストがありません",
   "antennas.select.placeholder": "リストを選択",
@@ -216,7 +170,7 @@
   "bundle_column_error.routing.body": "要求されたページは見つかりませんでした。アドレスバーのURLは正しいですか?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "閉じる",
-  "bundle_modal_error.message": "画面の読み込み中に問題が発生しました。",
+  "bundle_modal_error.message": "コンポーネントの読み込み中に問題が発生しました。",
   "bundle_modal_error.retry": "再試行",
   "circles.account.add": "サークルに追加",
   "circles.account.remove": "サークルから外す",
@@ -225,7 +179,6 @@
   "circles.edit.submit": "タイトルを変更",
   "circles.new.create": "サークルを作成",
   "circles.new.title_placeholder": "新規サークル名",
-  "circles.save_to_edit_member": "メンバーは、このサークルを保存した後に編集できます",
   "circles.search": "フォロワーの中から検索",
   "circles.subheading": "あなたのサークル",
   "closed_registrations.other_server_instructions": "Mastodonは分散型なので他のサーバーにアカウントを作ってもこのサーバーとやり取りできます。",
@@ -241,24 +194,15 @@
   "column.bookmarks": "ブックマーク",
   "column.circles": "サークル",
   "column.community": "ローカルタイムライン",
-  "column.create_antenna": "アンテナを作成",
-  "column.create_bookmark_category": "分類を作成",
-  "column.create_circle": "サークルを作成",
-  "column.create_list": "リストを作成",
   "column.deep_local": "ディープ",
   "column.direct": "非公開の返信",
   "column.directory": "ディレクトリ",
   "column.domain_blocks": "ブロックしたドメイン",
-  "column.edit_antenna": "アンテナを編集",
-  "column.edit_bookmark_category": "分類を編集",
-  "column.edit_circle": "サークルを編集",
-  "column.edit_list": "リストを編集",
   "column.emoji_reactions": "絵文字リアクション",
   "column.favourites": "お気に入り",
   "column.firehose": "リアルタイムフィード",
   "column.follow_requests": "フォローリクエスト",
   "column.home": "ホーム",
-  "column.list_members": "リストのメンバーを管理",
   "column.lists": "リスト",
   "column.local": "ローカル",
   "column.mutes": "ミュートしたユーザー",
@@ -273,7 +217,6 @@
   "column_header.pin": "ピン留めする",
   "column_header.show_settings": "設定を表示",
   "column_header.unpin": "ピン留めを外す",
-  "column_search.cancel": "キャンセル",
   "column_subheading.settings": "設定",
   "community.column_settings.local_only": "ローカルのみ表示",
   "community.column_settings.media_only": "メディアのみ表示",
@@ -313,33 +256,23 @@
   "confirmations.delete.confirm": "削除",
   "confirmations.delete.message": "本当に削除しますか?",
   "confirmations.delete.title": "投稿を削除しようとしています",
-  "confirmations.delete_antenna.confirm": "削除",
-  "confirmations.delete_antenna.message": "本当にこのアンテナを完全に削除しますか?",
-  "confirmations.delete_antenna.title": "アンテナを削除しようとしています",
   "confirmations.delete_bookmark_category.confirm": "削除",
   "confirmations.delete_bookmark_category.message": "本当にこの分類を完全に削除しますか?各投稿からこの分類は削除されますが、ブックマークは解除されません。",
-  "confirmations.delete_bookmark_category.title": "分類を削除しようとしています",
   "confirmations.delete_circle.confirm": "削除",
   "confirmations.delete_circle.message": "本当にこのサークルを完全に削除しますか?",
-  "confirmations.delete_circle.title": "サークルを削除しようとしています",
   "confirmations.delete_list.confirm": "削除",
   "confirmations.delete_list.message": "本当にこのリストを完全に削除しますか?",
   "confirmations.delete_list.title": "リストを削除しようとしています",
+  "confirmations.delete_antenna.confirm": "削除",
+  "confirmations.delete_antenna.message": "本当にこのアンテナを完全に削除しますか?",
   "confirmations.discard_edit_media.confirm": "破棄",
   "confirmations.discard_edit_media.message": "メディアの説明またはプレビューに保存されていない変更があります。それでも破棄しますか?",
   "confirmations.edit.confirm": "編集",
   "confirmations.edit.message": "今編集すると現在作成中のメッセージが上書きされます。本当に実行しますか?",
   "confirmations.edit.title": "作成中の内容を上書きしようとしています",
-  "confirmations.follow_to_list.confirm": "フォローしてリストに追加",
-  "confirmations.follow_to_list.message": "リストに追加するには{name}さんをフォローしている必要があります。",
-  "confirmations.follow_to_list.title": "ユーザーをフォローしますか?",
   "confirmations.logout.confirm": "ログアウト",
   "confirmations.logout.message": "本当にログアウトしますか?",
   "confirmations.logout.title": "ログアウトしようとしています",
-  "confirmations.missing_alt_text.confirm": "代替テキストを追加",
-  "confirmations.missing_alt_text.message": "あなたの投稿には代替テキストのないメディアが含まれています。説明文を追加することで、より多くの人がコンテンツにアクセスできるようになります。",
-  "confirmations.missing_alt_text.secondary": "そのまま投稿する",
-  "confirmations.missing_alt_text.title": "代替テキストを追加しますか?",
   "confirmations.mute.confirm": "ミュート",
   "confirmations.redraft.confirm": "削除して下書きに戻す",
   "confirmations.redraft.message": "投稿を削除して下書きに戻します。この投稿へのお気に入り登録やブーストは失われ、返信は孤立することになります。よろしいですか?",
@@ -368,10 +301,10 @@
   "disabled_account_banner.text": "あなたのアカウント『{disabledAccount}』は現在無効になっています。",
   "dismissable_banner.community_timeline": "これらは{domain}がホストしている人たちの最新の公開投稿です。",
   "dismissable_banner.dismiss": "閉じる",
-  "dismissable_banner.explore_links": "Fediverseで今日話題になっているニュースです。記事が新しく、たくさんのユーザーから投稿があるものほど上位に表示されます。",
-  "dismissable_banner.explore_statuses": "Fediverse全体の投稿で今日特に注目が高まっているものです。投稿日時が新しく、ブーストやお気に入りが多いほど上位に表示されます。",
-  "dismissable_banner.explore_tags": "Fediverseで今日特に注目が高まっているハッシュタグです。たくさんのユーザーに使われたタグほど上位に表示されます。",
-  "dismissable_banner.public_timeline": "{domain}のユーザーがフォローしているFediverseユーザーによる最近の公開投稿です。",
+  "dismissable_banner.explore_links": "ネットワーク上で話題になっているニュースです。たくさんのユーザーにシェアされた記事ほど上位に表示されます。",
+  "dismissable_banner.explore_statuses": "ネットワーク上で注目を集めている投稿です。ブーストやお気に入り登録の多い新しい投稿が上位に表示されます。",
+  "dismissable_banner.explore_tags": "ネットワーク上でトレンドになっているハッシュタグです。たくさんのユーザーに使われたタグほど上位に表示されます。",
+  "dismissable_banner.public_timeline": "{domain}のユーザーがリモートフォローしているアカウントからの公開投稿のタイムラインです。",
   "domain_block_modal.block": "サーバーをブロック",
   "domain_block_modal.block_account_instead": "@{name} さんのみをブロック",
   "domain_block_modal.they_can_interact_with_old_posts": "あなたの今までの投稿は、引き続きこのサーバーのユーザーが閲覧できます。",
@@ -434,6 +367,7 @@
   "empty_column.hashtag": "このハッシュタグはまだ使われていません。",
   "empty_column.home": "ホームタイムラインはまだ空っぽです。だれかをフォローして埋めてみましょう。",
   "empty_column.list": "このリストにはまだなにもありません。このリストのメンバーが新しい投稿をするとここに表示されます。",
+  "empty_column.lists": "まだリストがありません。リストを作るとここに表示されます。",
   "empty_column.mutes": "まだ誰もミュートしていません。",
   "empty_column.notification_requests": "ここに表示するものはありません。新しい通知を受け取ったとき、フィルタリング設定で通知がブロックされたアカウントがある場合はここに表示されます。",
   "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
@@ -445,6 +379,7 @@
   "error.unexpected_crash.next_steps_addons": "それらを無効化してからリロードをお試しください。それでも解決しない場合、他のブラウザやアプリで Mastodon をお試しください。",
   "errors.unexpected_crash.copy_stacktrace": "スタックトレースをクリップボードにコピー",
   "errors.unexpected_crash.report_issue": "問題を報告",
+  "explore.search_results": "検索結果",
   "explore.suggested_follows": "ユーザー",
   "explore.title": "探索する",
   "explore.trending_links": "ニュース",
@@ -494,14 +429,13 @@
   "footer.about": "概要",
   "footer.directory": "ディレクトリ",
   "footer.get_app": "アプリを入手",
+  "footer.invite": "新規ユーザーの招待",
   "footer.keyboard_shortcuts": "キーボードショートカット",
   "footer.privacy_policy": "プライバシーポリシー",
   "footer.source_code": "ソースコードを表示",
   "footer.status": "ステータス",
-  "footer.terms_of_service": "サービス利用規約",
   "generic.saved": "保存しました",
   "getting_started.heading": "スタート",
-  "hashtag.admin_moderation": "#{name}のモデレーション画面を開く",
   "hashtag.column_header.tag_mode.all": "と{additional}",
   "hashtag.column_header.tag_mode.any": "か{additional}",
   "hashtag.column_header.tag_mode.none": "({additional} を除く)",
@@ -543,23 +477,21 @@
   "ignore_notifications_modal.not_followers_title": "本当に「フォローされていないアカウントからの通知」を無視するようにしますか?",
   "ignore_notifications_modal.not_following_title": "本当に「フォローしていないアカウントからの通知」を無視するようにしますか?",
   "ignore_notifications_modal.private_mentions_title": "本当に「外部からの非公開の返信」を無視するようにしますか?",
-  "info_button.label": "ヘルプ",
-  "info_button.what_is_alt_text": "<h1>代替テキストとは何ですか?</h1> <p>代替テキストは、視覚障害、低速ネットワーク接続の人や追加コンテンツを求める人に役立つ画像説明です。</p> <p>明確、簡潔、客観的に記述することでアクセシビリティが向上し、より多くの人に理解されるようになります。</p> <ul> <li>要点をとらえる</li> <li>画像内のテキストを要約する</li> <li>平易な文章で説明する</li> <li>情報の重複を避ける</li> <li>複雑な内容 (図や地図など) では傾向やポイントを見つける</li> </ul>",
-  "interaction_modal.action.favourite": "お気に入り登録はあなたのアカウントがあるサーバーで行う必要があります。",
-  "interaction_modal.action.follow": "ユーザーをフォローするには、あなたのアカウントがあるサーバーからフォローする必要があります。",
-  "interaction_modal.action.reblog": "投稿をブーストするには、あなたのアカウントがあるサーバーでブーストする必要があります。",
-  "interaction_modal.action.reply": "リプライを送るには、あなたのアカウントがあるサーバーから送る必要があります。",
-  "interaction_modal.action.vote": "票を入れるには、あなたのアカウントがあるサーバーから投票する必要があります。",
-  "interaction_modal.go": "サーバーに移動",
-  "interaction_modal.no_account_yet": "アカウントを持っていない場合は:",
+  "interaction_modal.description.favourite": "Mastodonのアカウントがあれば投稿をお気に入り登録して投稿者に気持ちを伝えたり、あとで見返すことができます。",
+  "interaction_modal.description.follow": "Mastodonのアカウントで{name}さんをフォローしてホームフィードで投稿を受け取れます。",
+  "interaction_modal.description.reblog": "Mastodonのアカウントでこの投稿をブーストして自分のフォロワーに共有できます。",
+  "interaction_modal.description.reply": "Mastodonのアカウントでこの投稿に反応できます。",
+  "interaction_modal.login.action": "サーバーに移動",
+  "interaction_modal.login.prompt": "登録したサーバーのドメイン (例: mastodon.social)",
+  "interaction_modal.no_account_yet": "Mastodonにアカウントがない場合は",
   "interaction_modal.on_another_server": "別のサーバー",
   "interaction_modal.on_this_server": "このサーバー",
+  "interaction_modal.sign_in": "このサーバーにアカウントがなくても、ほかのサーバーや互換性のあるプラットフォームのアカウントを使用できます。",
+  "interaction_modal.sign_in_hint": "ワンポイント: ここでは自分のアカウントのドメインを入力します。うまくいかない場合はドメインまで含めた完全なユーザー名を入力してみてください (例: @Mastodon@mastodon.social)。ドメインやユーザー名は登録完了時のメールに記載されています。",
   "interaction_modal.title.favourite": "{name}さんの投稿をお気に入り登録",
   "interaction_modal.title.follow": "{name}さんをフォロー",
   "interaction_modal.title.reblog": "{name}さんの投稿をブースト",
   "interaction_modal.title.reply": "{name}さんの投稿にリプライ",
-  "interaction_modal.title.vote": "{name}さんのアンケートに投票",
-  "interaction_modal.username_prompt": "例: {example}",
   "intervals.full.days": "{number}日",
   "intervals.full.hours": "{number}時間",
   "intervals.full.minutes": "{number}分",
@@ -596,7 +528,6 @@
   "keyboard_shortcuts.toggle_hidden": "CWで隠れた文を見る/隠す",
   "keyboard_shortcuts.toggle_sensitivity": "非表示のメディアを見る/隠す",
   "keyboard_shortcuts.toot": "新規投稿",
-  "keyboard_shortcuts.translate": "投稿を翻訳する",
   "keyboard_shortcuts.unfocus": "投稿の入力欄・検索欄から離れる",
   "keyboard_shortcuts.up": "カラム内一つ上に移動",
   "lightbox.close": "閉じる",
@@ -609,38 +540,22 @@
   "link_preview.author": "{name}",
   "link_preview.more_from_author": "{name}さんの投稿をもっと読む",
   "link_preview.shares": "{count, plural, other {{counter}件の投稿}}",
-  "lists.add_member": "追加",
-  "lists.add_to_list": "リストに追加",
-  "lists.add_to_lists": "{name}をリストに追加",
+  "lists.account.add": "リストに追加",
+  "lists.account.remove": "リストから外す",
   "lists.antennas": "関連付けられたアンテナ",
-  "lists.create": "作成",
-  "lists.create_a_list_to_organize": "リストを作成するとホームタイムラインを見やすく整理できます",
-  "lists.create_list": "リストを作成",
   "lists.delete": "リストを削除",
-  "lists.done": "完了",
   "lists.edit": "リストを編集",
-  "lists.exclusive": "メンバーをホームに表示しない",
-  "lists.exclusive_hint": "リストにあるユーザーをホームタイムラインに表示しません。二重に表示させたくない場合に使用できます。",
-  "lists.favourite": "お気に入りに登録",
-  "lists.favourite_hint": "お気に入りに登録したリストは、PCのWebクライアントでナビゲーションに表示されます",
-  "lists.find_users_to_add": "追加するユーザーを探しましょう",
-  "lists.list_members": "リストのメンバー",
-  "lists.list_members_count": "{count, plural, other{#人のメンバー}}",
-  "lists.list_name": "リスト名",
-  "lists.memo_related_antenna": "アンテナ: {title}",
+  "lists.edit.submit": "タイトルを変更",
+  "lists.exclusive": "ホームからリスト・アンテナに登録されたアカウントの投稿を非表示にする",
+  "lists.new.create": "リストを作成",
+  "lists.new.title_placeholder": "新規リスト名",
   "lists.notify": "これらの投稿を通知する",
-  "lists.new_list_name": "新しいリスト名",
-  "lists.no_lists_yet": "まだリストがありません。",
-  "lists.no_members_yet": "まだメンバーがいません。",
-  "lists.no_results_found": "該当するものが見つかりませんでした。",
-  "lists.remove_member": "削除",
   "lists.replies_policy.followed": "フォロー中のユーザー全員",
   "lists.replies_policy.list": "リストのメンバー",
   "lists.replies_policy.none": "表示しない",
-  "lists.save": "保存",
-  "lists.save_to_edit_member": "メンバーは、このリストを保存した後に編集できます",
-  "lists.search": "検索",
-  "lists.show_replies_to": "メンバーの返信投稿の表示基準",
+  "lists.replies_policy.title": "リプライを表示:",
+  "lists.search": "フォローしている人の中から検索",
+  "lists.subheading": "あなたのリスト",
   "load_pending": "{count}件の新着",
   "loading_indicator.label": "読み込み中…",
   "media_gallery.hide": "隠す",
@@ -695,14 +610,10 @@
   "notification.admin.report_statuses_other": "{name}さんが{target}さんを通報しました",
   "notification.admin.sign_up": "{name}さんがサインアップしました",
   "notification.admin.sign_up.name_and_others": "{name}さんとほか{count, plural, other {#人}}がサインアップしました",
-  "notification.annual_report.message": "「あなたの{year}年の#Wrapstodonが待っています!Mastodonであなたの年のハイライトや思い出の瞬間を公開しましょう!」と訳せます。",
-  "notification.annual_report.view": "#Wrapstodon を表示",
   "notification.emoji_reaction": "{name}さんがあなたの投稿に絵文字をつけました",
   "notification.emoji_reaction.name_and_others_with_link": "{name}さんと<a>{count, plural, other {他#名}}</a>があなたの投稿に絵文字をつけました",
   "notification.favourite": "{name}さんがお気に入りしました",
   "notification.favourite.name_and_others_with_link": "{name}さんと<a>ほか{count, plural, other {#人}}</a>がお気に入りしました",
-  "notification.favourite_pm": "{name} があなたのプライベートメンションをお気に入りにしました",
-  "notification.favourite_pm.name_and_others_with_link": "{name} と <a>{count, plural, other {#人}}</a> があなたのプライベートメンションをお気に入りにしました",
   "notification.follow": "{name}さんにフォローされました",
   "notification.follow.name_and_others": "{name}さんと<a>ほか{count, plural, other {#人}}</a>にフォローされました",
   "notification.follow_request": "{name}さんがあなたにフォローリクエストしました",
@@ -813,21 +724,45 @@
   "notifications_permission_banner.enable": "デスクトップ通知を有効にする",
   "notifications_permission_banner.how_to_control": "Mastodonを閉じている間でも通知を受信するにはデスクトップ通知を有効にしてください。有効にすると上の {icon} ボタンから通知の内容を細かくカスタマイズできます。",
   "notifications_permission_banner.title": "お見逃しなく",
-  "onboarding.follows.back": "戻る",
-  "onboarding.follows.done": "完了",
+  "onboarding.action.back": "チュートリアルに戻る",
+  "onboarding.actions.back": "チュートリアルに戻る",
+  "onboarding.actions.go_to_explore": "話題をさがす",
+  "onboarding.actions.go_to_home": "タイムラインに移動",
+  "onboarding.actions.go_to_local_timeline": "ローカルの投稿を見る",
+  "onboarding.compose.template": "#Mastodon はじめました",
   "onboarding.follows.empty": "表示できる結果はありません。検索やエクスプローラーを使ったり、ほかのアカウントをフォローしたり、後でもう一度試しください。",
-  "onboarding.follows.search": "検索",
-  "onboarding.follows.title": "最初にフォローする人を選ぶ",
+  "onboarding.follows.lead": "ホームタイムラインはMastodonの軸足となる場所です。たくさんのユーザーをフォローすることで、ホームタイムラインはよりにぎやかでおもしろいものになります。手はじめに、おすすめのアカウントから何人かフォローしてみましょう:",
+  "onboarding.follows.title": "ホームタイムラインを埋める",
   "onboarding.profile.discoverable": "自分のプロフィールが見つけられるようにする",
   "onboarding.profile.discoverable_hint": "Mastodonの「見つける」機能にオプトインすると、あなたの投稿が検索結果やトレンドに表示されることがあります。また、あなたに似た関心を持つ人にプロフィールがおすすめされることがあります。",
   "onboarding.profile.display_name": "表示名",
   "onboarding.profile.display_name_hint": "フルネーム、あるいは面白い名前など",
+  "onboarding.profile.lead": "あとでいつでも修正できますし、設定画面にはこれ以外のカスタマイズ項目もあります。",
   "onboarding.profile.note": "自己紹介",
   "onboarding.profile.note_hint": "ほかのユーザーへのメンション (@mention) や、 #ハッシュタグ が使用できます",
   "onboarding.profile.save_and_continue": "保存して続ける",
   "onboarding.profile.title": "プロフィールの設定",
   "onboarding.profile.upload_avatar": "プロフィール画像をアップロード",
   "onboarding.profile.upload_header": "プロフィールのヘッダー画像をアップロード",
+  "onboarding.share.lead": "新しいMastodonのアカウントをみんなに紹介しましょう。",
+  "onboarding.share.message": "「{username}」で #Mastodon はじめました! {url}",
+  "onboarding.share.next_steps": "次のステップに進む:",
+  "onboarding.share.title": "プロフィールをシェアする",
+  "onboarding.start.lead": "Mastodonへようこそ。Mastodonは非中央集権型SNSのひとつで、ユーザーそれぞれの考えかたを尊重するプラットフォームです。ユーザーはどんな「好き」も自由に追いかけることができます。次のステップに進んで、新天地でのつながりをみつけましょう:",
+  "onboarding.start.skip": "下のどれかをクリックしてチュートリアルを終了",
+  "onboarding.start.title": "はじめに",
+  "onboarding.steps.follow_people.body": "ユーザーをフォローしてみましょう。これがMastodonを楽しむ基本です。",
+  "onboarding.steps.follow_people.title": "ホームタイムラインを埋める",
+  "onboarding.steps.publish_status.body": "試しになにか書いてみましょう。写真、ビデオ、アンケートなど、なんでも大丈夫です {emoji}",
+  "onboarding.steps.publish_status.title": "はじめての投稿",
+  "onboarding.steps.setup_profile.body": "ほかのユーザーが親しみやすいように、プロフィールを整えましょう。",
+  "onboarding.steps.setup_profile.title": "プロフィールを完成させる",
+  "onboarding.steps.share_profile.body": "Mastodonのアカウントをほかの人に紹介しましょう。",
+  "onboarding.steps.share_profile.title": "プロフィールをシェアする",
+  "onboarding.tips.2fa": "<strong>ワンポイント</strong> アカウント設定から2要素認証を有効にして、アカウントのセキュリティを強化しておきましょう。認証には任意のワンタイムパスワード(TOTP)アプリを利用でき、電話番号は不要です。",
+  "onboarding.tips.accounts_from_other_servers": "<strong>ワンポイント</strong> Mastodon はたくさんのサーバーがつながりあってできている非中央集権型のSNSです。いくつかのアカウントはこことは別のサーバーに所属していることがありますが、サーバーの違いを意識しなくても同じようにフォローすることができます。サーバーが異なる場合は、ユーザー名の後半にサーバー名が表示されます。",
+  "onboarding.tips.migration": "<strong>ワンポイント</strong> もしも {domain} の雰囲気が合わないと感じたときは、ほかのMastodonサーバーにフォロワーを引き継いだまま引っ越しできます。また、自分で独自のサーバーを開設することも可能です。",
+  "onboarding.tips.verification": "<strong>ワンポイント</strong> webサイトを持っている場合は、webサイトにMastodonアカウントへのリンクを掲載し、さらにアカウントのプロフィール側にもwebサイトへのリンクを追加することで、アカウントが自分のものであることを証明できます。課金や書類の提出は必要ありません。",
   "password_confirmation.exceeds_maxlength": "パスワードの最大文字数を超えています",
   "password_confirmation.mismatching": "入力済みのパスワードと一致しません",
   "picture_in_picture.restore": "元に戻す",
@@ -847,7 +782,7 @@
   "privacy.circle.long": "サークルメンバーのみ閲覧可",
   "privacy.circle.short": "サークル (投稿時点)",
   "privacy.direct.long": "本文で指定した相手のみ",
-  "privacy.direct.short": "非公開の返信",
+  "privacy.direct.short": "特定の人",
   "privacy.limited.short": "限定投稿",
   "privacy.login.long": "ログインユーザーのみ閲覧可、公開",
   "privacy.login.short": "ログインユーザーのみ",
@@ -871,8 +806,8 @@
   "reaction_deck.remove": "削除",
   "recommended": "おすすめ",
   "refresh": "更新",
-  "regeneration_indicator.please_stand_by": "しばらくお待ちください。",
-  "regeneration_indicator.preparing_your_home_feed": "ホームタイムライン準備中…",
+  "regeneration_indicator.label": "読み込み中…",
+  "regeneration_indicator.sublabel": "ホームタイムラインは準備中です!",
   "relative_time.days": "{number}日前",
   "relative_time.full.days": "{number}日前",
   "relative_time.full.hours": "{number}時間前",
@@ -968,11 +903,10 @@
   "search_results.accounts": "ユーザー",
   "search_results.all": "すべて",
   "search_results.hashtags": "ハッシュタグ",
-  "search_results.no_results": "結果なし。",
-  "search_results.no_search_yet": "投稿、プロフィール、またはハッシュタグを検索してみてください。",
+  "search_results.nothing_found": "この検索条件では何も見つかりませんでした",
   "search_results.see_all": "すべて表示",
   "search_results.statuses": "投稿",
-  "search_results.title": "「{q}」を検索",
+  "search_results.title": "『{q}』の検索結果",
   "server_banner.about_active_users": "過去30日間にこのサーバーを使用している人 (月間アクティブユーザー)",
   "server_banner.active_users": "人のアクティブユーザー",
   "server_banner.administered_by": "管理者",
@@ -1043,7 +977,6 @@
   "status.redraft": "削除して下書きに戻す",
   "status.reference": "ひかえめな引用",
   "status.remove_bookmark": "ブックマークを削除",
-  "status.remove_favourite": "お気に入りから削除",
   "status.replied_in_thread": "ほかのユーザーへ",
   "status.replied_to": "{name}さんへの返信",
   "status.reply": "返信",
@@ -1065,7 +998,6 @@
   "subscribed_languages.target": "{target}さんの購読言語を変更します",
   "tabs_bar.home": "ホーム",
   "tabs_bar.notifications": "通知",
-  "terms_of_service.title": "サービス利用規約",
   "time_remaining.days": "残り{number}日",
   "time_remaining.hours": "残り{number}時間",
   "time_remaining.minutes": "残り{number}分",
@@ -1081,12 +1013,26 @@
   "upload_button.label": "メディアを追加 (複数の画像または1つの動画か音声ファイル)",
   "upload_error.limit": "アップロードできる上限を超えています。",
   "upload_error.poll": "アンケートではファイルをアップロードできません。",
+  "upload_form.audio_description": "聴き取りが難しいユーザーへの説明",
+  "upload_form.description": "視覚的に閲覧が難しいユーザーへの説明",
   "upload_form.drag_and_drop.instructions": "メディア添付ファイルを選択するには、スペースキーまたはエンターキーを押してください。ドラッグ中は、矢印キーを使ってメディア添付ファイルを任意の方向に移動できます。再度スペースキーまたはエンターキーを押すと新しい位置にメディア添付ファイルをドロップできます。キャンセルするにはエスケープキーを押してください。",
   "upload_form.drag_and_drop.on_drag_cancel": "ドラッグがキャンセルされました。メディア添付ファイル {item} がドロップされました。",
   "upload_form.drag_and_drop.on_drag_end": "メディア添付ファイル {item} がドロップされました。",
   "upload_form.drag_and_drop.on_drag_over": "メディア添付ファイル {item} が移動されました。",
   "upload_form.drag_and_drop.on_drag_start": "メディア添付ファイル {item} を選択しました。",
   "upload_form.edit": "編集",
+  "upload_form.thumbnail": "サムネイルを変更",
+  "upload_form.video_description": "聴き取りや視覚的に閲覧が難しいユーザーへの説明",
+  "upload_modal.analyzing_picture": "画像を解析中…",
+  "upload_modal.apply": "適用",
+  "upload_modal.applying": "適用中...",
+  "upload_modal.choose_image": "画像を選択",
+  "upload_modal.description_placeholder": "あのイーハトーヴォのすきとおった風",
+  "upload_modal.detect_text": "画像からテキストを検出",
+  "upload_modal.edit_media": "メディアを編集",
+  "upload_modal.hint": "サムネイルの焦点にしたい場所をクリックするか円形の枠をその場所にドラッグしてください。",
+  "upload_modal.preparing_ocr": "OCRの準備中…",
+  "upload_modal.preview_label": "プレビュー ({ratio})",
   "upload_progress.label": "アップロード中...",
   "upload_progress.processing": "処理中…",
   "username.taken": "このユーザー名はすでに使用されています。ほかのユーザー名を入力してください",
@@ -1099,9 +1045,5 @@
   "video.mute": "ミュート",
   "video.pause": "一時停止",
   "video.play": "再生",
-  "video.skip_backward": "後方にスキップ",
-  "video.skip_forward": "前方にスキップ",
-  "video.unmute": "ミュート解除",
-  "video.volume_down": "音量を下げる",
-  "video.volume_up": "音量を上げる"
+  "video.unmute": "ミュートを解除する"
 }
diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json
index d078ef5cab..fc0ed0730d 100644
--- a/app/javascript/mastodon/locales/ka.json
+++ b/app/javascript/mastodon/locales/ka.json
@@ -6,20 +6,20 @@
   "account.badges.group": "ჯგუფი",
   "account.block": "დაბლოკე @{name}",
   "account.block_domain": "დაიმალოს ყველაფერი დომენიდან {domain}",
-  "account.blocked": "დაბლოკილია",
+  "account.blocked": "დაიბლოკა",
   "account.cancel_follow_request": "Withdraw follow request",
   "account.domain_blocked": "დომენი დამალულია",
   "account.edit_profile": "პროფილის ცვლილება",
   "account.endorse": "გამორჩევა პროფილზე",
-  "account.featured_tags.last_status_never": "პოსტების გარეშე",
+  "account.featured_tags.last_status_never": "პოსტები არ არის",
   "account.follow": "გაყოლა",
   "account.followers": "მიმდევრები",
   "account.hide_reblogs": "დაიმალოს ბუსტები @{name}-სგან",
   "account.media": "მედია",
   "account.mention": "ასახელეთ @{name}",
   "account.mute": "გააჩუმე @{name}",
-  "account.muted": "დადუმებულია",
-  "account.posts": "პოსტები",
+  "account.muted": "გაჩუმებული",
+  "account.posts": "ტუტები",
   "account.posts_with_replies": "ტუტები და პასუხები",
   "account.report": "დაარეპორტე @{name}",
   "account.requested": "დამტკიცების მოლოდინში. დააწკაპუნეთ რომ უარყოთ დადევნების მოთხონვა",
@@ -37,12 +37,13 @@
   "boost_modal.combo": "შეგიძლიათ დააჭიროთ {combo}-ს რათა შემდეგ ჯერზე გამოტოვოთ ეს",
   "bundle_column_error.retry": "სცადეთ კიდევ ერთხელ",
   "bundle_modal_error.close": "დახურვა",
+  "bundle_modal_error.message": "ამ კომპონენტის ჩატვირთვისას რაღაც აირია.",
   "bundle_modal_error.retry": "სცადეთ კიდევ ერთხელ",
   "column.blocks": "დაბლოკილი მომხმარებლები",
   "column.community": "ლოკალური თაიმლაინი",
   "column.domain_blocks": "დამალული დომენები",
   "column.follow_requests": "დადევნების მოთხოვნები",
-  "column.home": "საწყისი",
+  "column.home": "სახლი",
   "column.lists": "სიები",
   "column.mutes": "გაჩუმებული მომხმარებლები",
   "column.notifications": "შეტყობინებები",
@@ -52,9 +53,9 @@
   "column_header.hide_settings": "პარამეტრების დამალვა",
   "column_header.moveLeft_settings": "სვეტის მარცხნივ გადატანა",
   "column_header.moveRight_settings": "სვეტის მარჯვნივ გადატანა",
-  "column_header.pin": "მიმაგრება",
+  "column_header.pin": "აპინვა",
   "column_header.show_settings": "პარამეტრების ჩვენება",
-  "column_header.unpin": "მოხსნა",
+  "column_header.unpin": "პინის მოხსნა",
   "column_subheading.settings": "პარამეტრები",
   "community.column_settings.media_only": "მხოლოდ მედია",
   "compose_form.direct_message_warning_learn_more": "გაიგე მეტი",
@@ -66,21 +67,23 @@
   "compose_form.publish_form": "Publish",
   "compose_form.spoiler.marked": "გაფრთხილების უკან ტექსტი დამალულია",
   "compose_form.spoiler.unmarked": "ტექსტი არაა დამალული",
-  "confirmation_modal.cancel": "გაუქმება",
+  "confirmation_modal.cancel": "უარყოფა",
   "confirmations.block.confirm": "ბლოკი",
-  "confirmations.delete.confirm": "წაშლა",
+  "confirmations.delete.confirm": "გაუქმება",
   "confirmations.delete.message": "დარწმუნებული ხართ, გსურთ გააუქმოთ ეს სტატუსი?",
-  "confirmations.delete_list.confirm": "წაშლა",
+  "confirmations.delete_list.confirm": "გაუქმება",
   "confirmations.delete_list.message": "დარწმუნებული ხართ, გსურთ სამუდამოდ გააუქმოთ ეს სია?",
-  "confirmations.mute.confirm": "დადუმება",
+  "confirmations.mute.confirm": "გაჩუმება",
   "confirmations.redraft.confirm": "გაუქმება და გადანაწილება",
   "confirmations.unfollow.confirm": "ნუღარ მიჰყვები",
   "confirmations.unfollow.message": "დარწმუნებული ხართ, აღარ გსურთ მიჰყვებოდეთ {name}-ს?",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "ეს სტატუსი ჩასვით თქვენს ვებ-საიტზე შემდეგი კოდის კოპირებით.",
   "embed.preview": "ესაა თუ როგორც გამოჩნდება:",
   "emoji_button.activity": "აქტივობა",
-  "emoji_button.custom": "მომხმარებლის",
-  "emoji_button.flags": "ალმები",
+  "emoji_button.custom": "პერსონალიზირებული",
+  "emoji_button.flags": "დროშები",
   "emoji_button.food": "საჭმელი და სასლმელი",
   "emoji_button.label": "ემოჯის ჩასმა",
   "emoji_button.nature": "ბუმება",
@@ -119,7 +122,7 @@
   "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "კლავიატურის სწრაფი ბმულები",
   "keyboard_shortcuts.home": "to open home timeline",
-  "keyboard_shortcuts.hotkey": "მალსახმობი ღილაკი",
+  "keyboard_shortcuts.hotkey": "ცხელი კლავიში",
   "keyboard_shortcuts.legend": "ამ ლეგენდის გამოსაჩენად",
   "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "ავტორის დასახელებლად",
@@ -142,8 +145,14 @@
   "lightbox.close": "დახურვა",
   "lightbox.next": "შემდეგი",
   "lightbox.previous": "წინა",
+  "lists.account.add": "სიაში დამატება",
+  "lists.account.remove": "სიიდან ამოშლა",
   "lists.delete": "სიის წაშლა",
   "lists.edit": "სიის შეცვლა",
+  "lists.new.create": "სიის დამატება",
+  "lists.new.title_placeholder": "ახალი სიის სათაური",
+  "lists.search": "ძებნა ადამიანებს შორის რომელთაც მიჰყვებით",
+  "lists.subheading": "თქვენი სიები",
   "navigation_bar.blocks": "დაბლოკილი მომხმარებლები",
   "navigation_bar.community_timeline": "ლოკალური თაიმლაინი",
   "navigation_bar.compose": "Compose new toot",
@@ -173,27 +182,42 @@
   "notifications.column_settings.sound": "ხმის დაკვრა",
   "notifications.column_settings.status": "New toots:",
   "notifications.group": "{count} შეტყობინება",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "privacy.change": "სტატუსის კონფიდენციალურობის მითითება",
   "privacy.public.short": "საჯარო",
+  "regeneration_indicator.label": "იტვირთება…",
+  "regeneration_indicator.sublabel": "თქვენი სახლის ლენტა მზადდება!",
   "relative_time.days": "{number}დღ",
   "relative_time.hours": "{number}სთ",
   "relative_time.just_now": "ახლა",
   "relative_time.minutes": "{number}წთ",
   "relative_time.seconds": "{number}წმ",
-  "reply_indicator.cancel": "გაუქმება",
+  "reply_indicator.cancel": "უარყოფა",
   "report.forward": "ფორვარდი {target}-ს",
   "report.forward_hint": "ანგარიში სხვა სერვერიდანაა. გავაგზავნოთ რეპორტის ანონიმური ასლიც?",
   "report.placeholder": "დამატებითი კომენტარები",
-  "report.submit": "გადაცემა",
+  "report.submit": "დასრულება",
   "report.target": "არეპორტებთ {target}",
   "report_notification.attached_statuses": "{count, plural, one {# post} other {# posts}} attached",
   "search.placeholder": "ძებნა",
   "search_results.hashtags": "ჰეშტეგები",
-  "search_results.statuses": "პოსტები",
-  "sign_in_banner.sign_in": "შესვლა",
+  "search_results.statuses": "ტუტები",
+  "sign_in_banner.sign_in": "Sign in",
   "status.admin_status": "Open this status in the moderation interface",
   "status.block": "დაბლოკე @{name}",
-  "status.cancel_reblog_private": "ბუსტის მოხსნა",
+  "status.cancel_reblog_private": "ბუსტის მოშორება",
   "status.cannot_reblog": "ეს პოსტი ვერ დაიბუსტება",
   "status.copy": "Copy link to status",
   "status.delete": "წაშლა",
@@ -222,18 +246,23 @@
   "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
   "status.unmute_conversation": "საუბარზე გაჩუმების მოშორება",
   "status.unpin": "პროფილიდან პინის მოშორება",
-  "tabs_bar.home": "საწყისი",
+  "tabs_bar.home": "სახლი",
   "tabs_bar.notifications": "შეტყობინებები",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
   "ui.beforeunload": "თქვენი დრაფტი გაუქმდება თუ დატოვებთ მასტოდონს.",
   "upload_area.title": "გადმოწიეთ და ჩააგდეთ ასატვირთათ",
   "upload_button.label": "მედიის დამატება",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "აღწერილობა ვიზუალურად უფასურისთვის",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "იტვირთება...",
   "video.close": "ვიდეოს დახურვა",
   "video.exit_fullscreen": "სრულ ეკრანზე ჩვენების გათიშვა",
   "video.expand": "ვიდეოს გაფართოება",
   "video.fullscreen": "ჩვენება სრულ ეკრანზე",
   "video.hide": "ვიდეოს დამალვა",
+  "video.mute": "ხმის გაჩუმება",
   "video.pause": "პაუზა",
-  "video.play": "დაკვრა"
+  "video.play": "დაკვრა",
+  "video.unmute": "ხმის გაჩუმების მოშორება"
 }
diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json
index bb97a18bd2..74d4be9d0d 100644
--- a/app/javascript/mastodon/locales/kab.json
+++ b/app/javascript/mastodon/locales/kab.json
@@ -20,10 +20,10 @@
   "account.cancel_follow_request": "Sefsex taḍfart",
   "account.copy": "Nɣel assaɣ ɣer umaɣnu",
   "account.direct": "Bder-d @{name} weḥd-s",
-  "account.disable_notifications": "Ḥbes ur iyi-d-ttazen ara ilɣa mi ara d-isuffeɣ @{name}",
+  "account.disable_notifications": "Ḥbes ur iyi-d-ttazen ara alɣuten mi ara d-isuffeɣ @{name}",
   "account.domain_blocked": "Taɣult yeffren",
   "account.edit_profile": "Ẓreg amaɣnu",
-  "account.enable_notifications": "Azen-iyi-d ilɣa mi ara d-isuffeɣ @{name}",
+  "account.enable_notifications": "Azen-iyi-d alɣuten mi ara d-isuffeɣ @{name}",
   "account.endorse": "Welleh fell-as deg umaɣnu-inek",
   "account.featured_tags.last_status_at": "Tasuffeɣt taneggarut ass n {date}",
   "account.featured_tags.last_status_never": "Ulac tisuffaɣ",
@@ -45,7 +45,7 @@
   "account.mention": "Bder-d @{name}",
   "account.moved_to": "{name} yenna-d dakken amiḍan-is amaynut yuɣal :",
   "account.mute": "Sgugem @{name}",
-  "account.mute_notifications_short": "Susem ilɣa",
+  "account.mute_notifications_short": "Susem alɣuten",
   "account.mute_short": "Sgugem",
   "account.muted": "Yettwasgugem",
   "account.mutual": "Temṭafarem",
@@ -61,7 +61,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} n tsuffeɣt} other {{counter} n tsuffaɣ}}",
   "account.unblock": "Serreḥ i @{name}",
   "account.unblock_domain": "Ssken-d {domain}",
-  "account.unblock_domain_short": "Serreḥ",
   "account.unblock_short": "Serreḥ",
   "account.unendorse": "Ur ttwellih ara fell-as deg umaɣnu-inek",
   "account.unfollow": "Ur ṭṭafaṛ ara",
@@ -74,15 +73,8 @@
   "alert.rate_limited.title": "Aktum s talast",
   "alert.unexpected.message": "Yeḍra-d unezri ur netturaǧu ara.",
   "alert.unexpected.title": "Ayhuh!",
-  "alt_text_badge.title": "Aḍris amlellay",
-  "alt_text_modal.add_alt_text": "Rnu aḍris amlellay",
-  "alt_text_modal.add_text_from_image": "Rnu aḍris amlellay seg tugna",
-  "alt_text_modal.cancel": "Semmet",
-  "alt_text_modal.done": "Immed",
+  "alt_text_badge.title": "Aḍris asegzan",
   "announcement.announcement": "Ulɣu",
-  "annual_report.summary.most_used_app.most_used_app": "asnas yettwasqedcen s waṭas",
-  "annual_report.summary.most_used_hashtag.none": "Ula yiwen",
-  "annual_report.summary.new_posts.new_posts": "tisuffaɣ timaynutin",
   "audio.hide": "Ffer amesli",
   "block_modal.show_less": "Ssken-d drus",
   "block_modal.show_more": "Ssken-d ugar",
@@ -100,6 +92,7 @@
   "bundle_column_error.routing.body": "Asebter i d-yettwasutren ur yettwaf ara. Tetḥeqqeḍ belli tansa URL deg ufeggag n tansa tṣeḥḥa?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Mdel",
+  "bundle_modal_error.message": "Tella-d kra n tuccḍa mi d-yettali ugbur-agi.",
   "bundle_modal_error.retry": "Ɛreḍ tikelt-nniḍen",
   "closed_registrations_modal.description": "Asnulfu n umiḍan deg {domain} mačči d ayen izemren ad yili, maca ttxil-k·m, err deg lbal-ik·im belli ur teḥwaǧeḍ ara amiḍan s wudem ibanen ɣef {domain} akken ad tesqedceḍ Mastodon.",
   "closed_registrations_modal.find_another_server": "Aff-d aqeddac nniḍen",
@@ -108,17 +101,15 @@
   "column.blocks": "Imiḍanen yettusḥebsen",
   "column.bookmarks": "Ticraḍ",
   "column.community": "Tasuddemt tadigant",
-  "column.create_list": "Snulfu-d tabdart",
   "column.direct": "Tabdarin tusligin",
   "column.directory": "Inig deg imeɣna",
   "column.domain_blocks": "Taɣulin yeffren",
-  "column.edit_list": "Ẓreg tabdart",
   "column.favourites": "Imenyafen",
   "column.follow_requests": "Isuturen n teḍfeṛt",
   "column.home": "Agejdan",
   "column.lists": "Tibdarin",
   "column.mutes": "Imiḍanen yettwasgugmen",
-  "column.notifications": "Ilɣa",
+  "column.notifications": "Alɣuten",
   "column.pins": "Tisuffaɣ yettwasenṭḍen",
   "column.public": "Tasuddemt tamatut",
   "column_back_button.label": "Tuɣalin",
@@ -128,7 +119,6 @@
   "column_header.pin": "Senteḍ",
   "column_header.show_settings": "Ssken iɣewwaṛen",
   "column_header.unpin": "Kkes asenteḍ",
-  "column_search.cancel": "Semmet",
   "column_subheading.settings": "Iɣewwaṛen",
   "community.column_settings.local_only": "Adigan kan",
   "community.column_settings.media_only": "Imidyaten kan",
@@ -147,7 +137,7 @@
   "compose_form.poll.duration": "Tanzagt n tefrant",
   "compose_form.poll.multiple": "Aṭas n ufran",
   "compose_form.poll.option_placeholder": "Taxtiṛt {number}",
-  "compose_form.poll.single": "Yiwen wefran",
+  "compose_form.poll.single": "Fren yiwen",
   "compose_form.poll.type": "Aɣanib",
   "compose_form.publish": "Suffeɣ",
   "compose_form.publish_form": "Tasuffeɣt tamaynut",
@@ -166,13 +156,9 @@
   "confirmations.discard_edit_media.confirm": "Sefsex",
   "confirmations.edit.confirm": "Ẓreg",
   "confirmations.edit.message": "Abeddel tura ad d-yaru izen-nni i d-tegreḍ akka tura. Tetḥeqqeḍ tebɣiḍ ad tkemmleḍ?",
-  "confirmations.follow_to_list.confirm": "Ḍfeṛ-it sakin rnu-t ɣer tebdart",
   "confirmations.logout.confirm": "Ffeɣ",
   "confirmations.logout.message": "D tidet tebɣiḍ ad teffɣeḍ?",
   "confirmations.logout.title": "Tebɣiḍ ad teffɣeḍ ssya?",
-  "confirmations.missing_alt_text.confirm": "Rnu aḍris amlellay",
-  "confirmations.missing_alt_text.secondary": "Suffeɣ akken yebɣu yili",
-  "confirmations.missing_alt_text.title": "Rnu aḍris amlellay?",
   "confirmations.mute.confirm": "Sgugem",
   "confirmations.redraft.confirm": "Kkes sakin ɛiwed tira",
   "confirmations.reply.confirm": "Err",
@@ -195,6 +181,9 @@
   "directory.recently_active": "Yermed xas melmi kan",
   "disabled_account_banner.account_settings": "Iɣewwaṛen n umiḍan",
   "dismissable_banner.dismiss": "Agi",
+  "dismissable_banner.explore_links": "D tiqsiḍin n yisallen i yettwabḍan ass-a deg web inmetti. Tiqsiḍin n yisallen timaynutin i d-yettwassufɣen s wugar n medden yemgaraden, d tid i d-yufraren ugar.",
+  "dismissable_banner.explore_statuses": "Ti d tisufaɣ seg uzeṭṭa anmetti i d-yettawin tamyigawt ass-a. Tisufaɣ timaynutin yesεan aṭas n lǧehd d tid iḥemmlen s waṭas, ttwaεlayit d timezwura.",
+  "dismissable_banner.explore_tags": "D wiyi i d ihacṭagen i d-yettawin tamyigawt deg web anmetti ass-a. Ihacṭagen i sseqdacen ugar n medden, εlayit d imezwura.",
   "domain_block_modal.block": "Sewḥel aqeddac",
   "domain_block_modal.they_cant_follow": "Yiwen ur yezmir ad k·m-id-yeḍfer seg uqeddac-a.",
   "domain_block_modal.they_wont_know": "Ur-d yettawi ara s lexbaṛ belli yettuseḥbes.",
@@ -232,12 +221,14 @@
   "empty_column.hashtag": "Ar tura ulac kra n ugbur yesɛan assaɣ ɣer uhacṭag-agi.",
   "empty_column.home": "Tasuddemt tagejdant n yisallen d tilemt! Ẓer {public} neɣ nadi ad tafeḍ imseqdacen-nniḍen ad ten-ḍefṛeḍ.",
   "empty_column.list": "Ar tura ur yelli kra deg umuɣ-a. Ad d-yettwasken da ticki iɛeggalen n wumuɣ-a suffɣen-d kra.",
+  "empty_column.lists": "Ulac ɣur-k·m kra n wumuɣ yakan. Ad d-tettwasken da ticki tesluleḍ-d yiwet.",
   "empty_column.mutes": "Ulac ɣur-k·m imseqdacen i yettwasgugmen.",
-  "empty_column.notifications": "Ulac ɣur-k·m ilɣa. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.",
+  "empty_column.notifications": "Ulac ɣur-k·m alɣuten. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.",
   "empty_column.public": "Ulac kra da! Aru kra, neɣ ḍfeṛ imdanen i yellan deg yiqeddacen-nniḍen akken ad d-teččar tsuddemt tazayezt",
   "error.unexpected_crash.next_steps": "Smiren asebter-a, ma ur yekkis ara wugur, ẓer d akken tzemreḍ ad tesqedceḍ Maṣṭudun deg yiminig-nniḍen neɣ deg usnas anaṣli.",
   "errors.unexpected_crash.copy_stacktrace": "Nɣel stacktrace ɣef wafus",
   "errors.unexpected_crash.report_issue": "Mmel ugur",
+  "explore.search_results": "Igemmaḍ n unadi",
   "explore.suggested_follows": "Imdanen",
   "explore.title": "Snirem",
   "explore.trending_links": "Isallen",
@@ -252,7 +243,6 @@
   "filter_modal.select_filter.search": "Nadi neɣ snulfu-d",
   "filter_modal.select_filter.title": "Sizdeg tassufeɣt-a",
   "filter_modal.title.status": "Sizdeg tassufeɣt",
-  "filtered_notifications_banner.title": "Ilɣa yettwasizdgen",
   "firehose.all": "Akk",
   "firehose.local": "Deg uqeddac-ayi",
   "firehose.remote": "Iqeddacen nniḍen",
@@ -260,22 +250,19 @@
   "follow_request.reject": "Agi",
   "follow_suggestions.dismiss": "Dayen ur t-id-skan ara",
   "follow_suggestions.featured_longer": "Yettwafraned s ufus sɣur agraw n {domain}",
-  "follow_suggestions.friends_of_friends_longer": "D aɣeṛfan ar wid i teṭṭafareḍ",
   "follow_suggestions.hints.featured": "Amaɣnu-a ifren-it-id wegraw n {domain} s ufus.",
-  "follow_suggestions.hints.friends_of_friends": "Amaɣnu-a d aɣeṛfan ɣer wid i teṭṭafaṛeḍ.",
-  "follow_suggestions.popular_suggestion": "Asumer aɣeṛfan",
-  "follow_suggestions.popular_suggestion_longer": "D aɣeṛfan deg {domain}",
+  "follow_suggestions.popular_suggestion_longer": "Yettwassen deg {domain}",
   "follow_suggestions.view_all": "Wali-ten akk",
   "follow_suggestions.who_to_follow": "Ad tḍefreḍ?",
   "followed_tags": "Ihacṭagen yettwaḍfaren",
   "footer.about": "Ɣef",
   "footer.directory": "Akaram n imeɣna",
   "footer.get_app": "Awi-d asnas",
+  "footer.invite": "Ɛreḍ-d kra n yimdanen",
   "footer.keyboard_shortcuts": "Inegzumen n unasiw",
   "footer.privacy_policy": "Tasertit tabaḍnit",
   "footer.source_code": "Wali tangalt taɣbalut",
   "footer.status": "Addad",
-  "footer.terms_of_service": "Tiwtilin n useqdec",
   "generic.saved": "Yettwasekles",
   "getting_started.heading": "Bdu",
   "hashtag.column_header.tag_mode.all": "d {additional}",
@@ -300,19 +287,19 @@
   "home.pending_critical_update.body": "Ma ulac aɣilif, leqqem aqeddac-ik Mastodon akken kan tzemreḍ !",
   "home.pending_critical_update.link": "Wali ileqman",
   "home.show_announcements": "Ssken-d ulɣuyen",
-  "info_button.label": "Tallelt",
-  "interaction_modal.action.favourite": "I wakken ad tkemmleḍ, yessefk ad d-tḥemmleḍ seg umiḍan-ik·im.",
-  "interaction_modal.action.follow": "I wakken ad tkemmleḍ, yessefk ad d-tḍefreḍ seg umiḍan-ik·im.",
-  "interaction_modal.action.reblog": "I wakken ad tkemmleḍ, yessefk ad d-snernuḍ seg umiḍan-ik·im.",
-  "interaction_modal.action.reply": "I wakken ad tkemmleḍ, yessefk ad d-terreḍ seg umiḍan-ik·im.",
-  "interaction_modal.action.vote": "I wakken ad tkemmleḍ, yessefk ad d-tferneḍ seg umiḍan-ik·im.",
-  "interaction_modal.go": "Ddu",
-  "interaction_modal.no_account_yet": "Werɛad ur tesɛid amiḍan?",
+  "interaction_modal.description.favourite": "S umiḍan ɣef Mastodon, tzemreḍ ad tesmenyifeḍ tasuffeɣt-a akken ad teǧǧeḍ amaru ad iẓer belli tḥemmleḍ-tt u ad tt-id-tsellkeḍ i ticki.",
+  "interaction_modal.description.follow": "S umiḍan deg Mastodon, tzemreḍ ad tḍefreḍ {name} akken ad d-teṭṭfeḍ iznan-is deg lxiḍ-ik·im agejdan.",
+  "interaction_modal.description.reblog": "S umiḍan deg Mastodon, tzemreḍ ad tesnerniḍ tasuffeɣt-a akken ad tt-tebḍuḍ d yineḍfaren-ik·im.",
+  "interaction_modal.description.reply": "S umiḍan deg Mastodon, tzemreḍ ad d-terreḍ ɣef tsuffeɣt-a.",
+  "interaction_modal.login.action": "Awi-yi ɣer uqeddac-iw",
+  "interaction_modal.login.prompt": "Taɣult n uqeddac-ik·im agejdan, amedya mastodon.social",
+  "interaction_modal.no_account_yet": "Ulac-ik·ikem deg Maṣṭudun?",
   "interaction_modal.on_another_server": "Deg uqeddac nniḍen",
   "interaction_modal.on_this_server": "Deg uqeddac-ayi",
+  "interaction_modal.sign_in": "Ur tekcimeḍ ara ɣer uqeddac-a. Anda yella umiḍan-ik·im ?",
+  "interaction_modal.sign_in_hint": "Ihi : Wa d asmel ideg tjerdeḍ. Ma ur tecfiḍ ara, nadi imayl n ummager deg tenkult-ik·im. Tzemreḍ daɣen ad d-tefkeḍ isem-ik·im n useqdac ummid ! (amedya @Mastodon@mastodon.social)",
   "interaction_modal.title.follow": "Ḍfer {name}",
   "interaction_modal.title.reply": "Tiririt i tsuffeɣt n {name}",
-  "interaction_modal.username_prompt": "Amedya: {example}",
   "intervals.full.days": "{number, plural, one {# wass} other {# wussan}}",
   "intervals.full.hours": "{number, plural, one {# usarag} other {# yisragen}}",
   "intervals.full.minutes": "{number, plural, one {# n tesdat} other {# n tesdatin}}",
@@ -335,7 +322,7 @@
   "keyboard_shortcuts.mention": "akken ad d-bedreḍ ameskar",
   "keyboard_shortcuts.muted": "akken ad teldiḍ tabdart n yimseqdacen yettwasgugmen",
   "keyboard_shortcuts.my_profile": "akken ad d-teldiḍ amaɣnu-ik",
-  "keyboard_shortcuts.notifications": "Ad d-yeldi ajgu n yilɣa",
+  "keyboard_shortcuts.notifications": "akken ad d-teldiḍ ajgu n walɣuten",
   "keyboard_shortcuts.open_media": "i tiɣwalin yeldin",
   "keyboard_shortcuts.pinned": "akken ad teldiḍ tabdart n tjewwiqin yettwasentḍen",
   "keyboard_shortcuts.profile": "akken ad d-teldiḍ amaɣnu n umeskar",
@@ -356,23 +343,20 @@
   "link_preview.author": "S-ɣur {name}",
   "link_preview.more_from_author": "Ugar sɣur {name}",
   "link_preview.shares": "{count, plural, one {{counter} n tsuffeɣt} other {{counter} n tsuffaɣ}}",
-  "lists.add_member": "Rnu",
-  "lists.add_to_list": "Rnu ɣer tebdart",
-  "lists.add_to_lists": "Rnu {name} ɣer tebdarin",
-  "lists.create": "Snulfu-d",
+  "lists.account.add": "Rnu ɣer tebdart",
+  "lists.account.remove": "Kkes seg tebdart",
   "lists.delete": "Kkes tabdart",
-  "lists.done": "Immed",
   "lists.edit": "Ẓreg tabdart",
-  "lists.list_name": "Isem n tebdart",
-  "lists.new_list_name": "Isem n tebdart tamaynut",
-  "lists.no_lists_yet": "Ulac tibdarin akka tura.",
-  "lists.no_results_found": "Ulac igemmad.",
-  "lists.remove_member": "Kkes",
+  "lists.edit.submit": "Beddel azwel",
+  "lists.exclusive": "Ffer tisuffaɣ-a seg ugejdan",
+  "lists.new.create": "Rnu tabdart",
+  "lists.new.title_placeholder": "Azwel amaynut n tebdart",
   "lists.replies_policy.followed": "Kra n useqdac i yettwaḍefren",
   "lists.replies_policy.list": "Iɛeggalen n tebdart",
   "lists.replies_policy.none": "Ula yiwen·t",
-  "lists.save": "Sekles",
-  "lists.search": "Nadi",
+  "lists.replies_policy.title": "Ssken-d tiririyin i:",
+  "lists.search": "Nadi gar yemdanen i teṭṭafaṛeḍ",
+  "lists.subheading": "Tibdarin-ik·im",
   "load_pending": "{count, plural, one {# n uferdis amaynut} other {# n yiferdisen imaynuten}}",
   "loading_indicator.label": "Yessalay-d …",
   "media_gallery.hide": "Seggelmes",
@@ -403,7 +387,6 @@
   "navigation_bar.follows_and_followers": "Imeḍfaṛen akked wid i teṭṭafaṛeḍ",
   "navigation_bar.lists": "Tibdarin",
   "navigation_bar.logout": "Ffeɣ",
-  "navigation_bar.moderation": "Aseɣyed",
   "navigation_bar.mutes": "Iseqdacen yettwasusmen",
   "navigation_bar.opened_in_classic_interface": "Tisuffaɣ, imiḍanen akked isebtar-nniḍen igejdanen ldin-d s wudem amezwer deg ugrudem web aklasiki.",
   "navigation_bar.personal": "Udmawan",
@@ -417,7 +400,6 @@
   "notification.admin.sign_up": "Ijerred {name}",
   "notification.favourite": "{name} yesmenyaf addad-ik·im",
   "notification.follow": "iṭṭafar-ik·em-id {name}",
-  "notification.follow.name_and_others": "{name} akked <a>{count, plural, one {# nniḍen} other {# nniḍen}}</a> iḍfeṛ-k·m-id",
   "notification.follow_request": "{name} yessuter-d ad k·m-yeḍfeṛ",
   "notification.label.mention": "Abdar",
   "notification.label.private_mention": "Abdar uslig",
@@ -434,12 +416,11 @@
   "notification_requests.dismiss": "Agi",
   "notification_requests.edit_selection": "Ẓreg",
   "notification_requests.exit_selection": "Immed",
-  "notification_requests.notifications_from": "Ilɣa sɣur {name}",
-  "notification_requests.title": "Ilɣa yettwasizdgen",
-  "notifications.clear": "Sfeḍ ilɣa",
-  "notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk ilɣa-inek·em i lebda?",
+  "notification_requests.notifications_from": "Alɣuten sɣur {name}",
+  "notifications.clear": "Sfeḍ alɣuten",
+  "notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk alɣuten-inek·em i lebda?",
   "notifications.column_settings.admin.report": "Ineqqisen imaynuten:",
-  "notifications.column_settings.alert": "Ilɣa n tnarit",
+  "notifications.column_settings.alert": "Alɣuten n tnarit",
   "notifications.column_settings.favourite": "Imenyafen:",
   "notifications.column_settings.filter_bar.advanced": "Sken-d akk taggayin",
   "notifications.column_settings.filter_bar.category": "Iri n usizdeg uzrib",
@@ -448,12 +429,12 @@
   "notifications.column_settings.group": "Agraw",
   "notifications.column_settings.mention": "Abdar:",
   "notifications.column_settings.poll": "Igemmaḍ n usenqed:",
-  "notifications.column_settings.push": "Ilɣa yettudemmren",
+  "notifications.column_settings.push": "Alɣuten yettudemmren",
   "notifications.column_settings.reblog": "Seǧhed:",
   "notifications.column_settings.show": "Ssken-d tilɣa deg ujgu",
   "notifications.column_settings.sound": "Rmed imesli",
   "notifications.column_settings.status": "Tisuffaɣ timaynutin :",
-  "notifications.column_settings.unread_notifications.category": "Ilɣa ur nettwaɣra",
+  "notifications.column_settings.unread_notifications.category": "Alɣuten ur nettwaɣra",
   "notifications.column_settings.update": "Iẓreg:",
   "notifications.filter.all": "Akk",
   "notifications.filter.boosts": "Seǧhed",
@@ -463,9 +444,9 @@
   "notifications.filter.polls": "Igemmaḍ n usenqed",
   "notifications.filter.statuses": "Ileqman n yimdanen i teṭṭafareḍ",
   "notifications.grant_permission": "Mudd tasiregt.",
-  "notifications.group": "{count} n yilɣa",
-  "notifications.mark_as_read": "Creḍ akk ilɣa am wakken ttwaɣran",
-  "notifications.permission_denied": "D awezɣi ad yili wermad n yilɣa n tnarit axateṛ turagt tettwagdel",
+  "notifications.group": "{count} n walɣuten",
+  "notifications.mark_as_read": "Creḍ meṛṛa alɣuten am wakken ttwaɣran",
+  "notifications.permission_denied": "D awezɣi ad yili wermad n walɣuten n tnarit axateṛ turagt tettwagdel",
   "notifications.policy.drop": "Anef-as",
   "notifications.policy.filter": "Sizdeg",
   "notifications.policy.filter_new_accounts.hint": "Imiḍanen imaynuten i d-yennulfan deg {days, plural, one {yiwen n wass} other {# n wussan}} yezrin",
@@ -475,12 +456,15 @@
   "notifications.policy.filter_not_following_hint": "Alamma tqebleḍ-ten s ufus",
   "notifications.policy.filter_not_following_title": "Wid akked tid ur tettḍafareḍ ara",
   "notifications.policy.filter_private_mentions_title": "Abdar uslig ur yettwasferken ara",
-  "notifications_permission_banner.enable": "Rmed ilɣa n tnarit",
+  "notifications_permission_banner.enable": "Rmed alɣuten n tnarit",
   "notifications_permission_banner.title": "Ur zeggel acemma",
-  "onboarding.follows.back": "Uɣal",
-  "onboarding.follows.done": "Immed",
-  "onboarding.follows.search": "Nadi",
-  "onboarding.follows.title": "Ḍfeṛ walbɛaḍ i wakken ad ttebdud",
+  "onboarding.action.back": "Tuɣalin ɣer deffir",
+  "onboarding.actions.back": "Tuɣalin ɣer deffir",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "Azul a #Mastodon!",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Ttwassnen deg Mastodon",
   "onboarding.profile.display_name": "Isem ara d-yettwaskanen",
   "onboarding.profile.display_name_hint": "Isem-ik·im ummid neɣ isem-ik·im n uqeṣṣer…",
   "onboarding.profile.note": "Tameddurt",
@@ -489,6 +473,20 @@
   "onboarding.profile.title": "Asbadu n umaɣnu",
   "onboarding.profile.upload_avatar": "Sali tugna n umaɣnu",
   "onboarding.profile.upload_header": "Sali tacacit n umaɣnu",
+  "onboarding.share.lead": "Ini-asen i medden amek ara k·m-id-afen deg Mastodon!",
+  "onboarding.share.message": "Nekk d {username} deg #Mastodon! Ḍfer iyi-d sya {url}",
+  "onboarding.share.title": "Bḍu amaɣnu-inek·inem",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "Tseggmeḍ-tt !",
+  "onboarding.steps.follow_people.body": "Aḍfer n medden yelhan, d tikti n Mastodon.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Ini-as azul i umaḍal s uḍris, s tiwlafin, s tividyutin neɣ s tefranin {emoji}",
+  "onboarding.steps.publish_status.title": "Aru tasuffeɣt-inek·inem tamezwarutt",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "Err-it amkan-is",
   "poll.closed": "Tfukk",
   "poll.refresh": "Smiren",
@@ -502,18 +500,18 @@
   "poll_button.remove_poll": "Kkes asenqed",
   "privacy.change": "Seggem tabaḍnit n yizen",
   "privacy.direct.long": "Wid akk i d-yettwabdaren deg tsuffeɣt",
-  "privacy.direct.short": "Abdar uslig",
+  "privacy.direct.short": "Imdanen yettwafernen",
   "privacy.private.long": "Ala wid i k·m-yeṭṭafaṛen",
   "privacy.private.short": "Imeḍfaren",
   "privacy.public.long": "Kra n win yellan deg Masṭudun neɣ berra-s",
   "privacy.public.short": "Azayez",
   "privacy.unlisted.long": "Kra kan yiwarzimen",
-  "privacy.unlisted.short": "Azayez asusam",
   "privacy_policy.last_updated": "Aleqqem aneggaru {date}",
   "privacy_policy.title": "Tasertit tabaḍnit",
   "recommended": "Yettuwelleh",
   "refresh": "Smiren",
-  "regeneration_indicator.please_stand_by": "Ttxil rǧu.",
+  "regeneration_indicator.label": "Yessalay-d…",
+  "regeneration_indicator.sublabel": "Tasuddemt tagejdant ara d-tettwaheggay!",
   "relative_time.days": "{number}u",
   "relative_time.full.just_now": "tura kan",
   "relative_time.hours": "{number}isr",
@@ -579,10 +577,9 @@
   "search_results.accounts": "Imeɣna",
   "search_results.all": "Akk",
   "search_results.hashtags": "Ihacṭagen",
-  "search_results.no_results": "Ulac igemmaḍ.",
   "search_results.see_all": "Wali-ten akk",
   "search_results.statuses": "Tisuffaɣ",
-  "search_results.title": "Igemmaḍ n unadi ɣef \"{q}\"",
+  "search_results.title": "Anadi ɣef {q}",
   "server_banner.active_users": "iseqdacen urmiden",
   "server_banner.administered_by": "Yettwadbel sɣur :",
   "server_banner.server_stats": "Tidaddanin n uqeddac:",
@@ -643,8 +640,7 @@
   "status.unpin": "Kkes asenteḍ seg umaɣnu",
   "subscribed_languages.save": "Sekles ibeddilen",
   "tabs_bar.home": "Agejdan",
-  "tabs_bar.notifications": "Ilɣa",
-  "terms_of_service.title": "Tiwtilin n useqdec",
+  "tabs_bar.notifications": "Alɣuten",
   "time_remaining.days": "Mazal {number, plural, one {# wass} other {# wussan}}",
   "time_remaining.hours": "Mazal {number, plural, one {# usarag} other {# yisragen}}",
   "time_remaining.minutes": "Mazal {number, plural, one {# n tesdat} other {# n tesdatin}}",
@@ -660,7 +656,20 @@
   "upload_button.label": "Rnu taɣwalt",
   "upload_error.limit": "Asali n ufaylu iεedda talast.",
   "upload_error.poll": "Ur ittusireg ara usali n ufaylu s tefranin.",
+  "upload_form.audio_description": "Glem-d i yemdanen i yesɛan ugur deg tmesliwt",
+  "upload_form.description": "Glem-d i yemdaneni yesɛan ugur deg yiẓri",
   "upload_form.edit": "Ẓreg",
+  "upload_form.thumbnail": "Beddel tugna",
+  "upload_form.video_description": "Glem-d i yemdanen i yesɛan ugur deg tmesliwt neɣ deg yiẓri",
+  "upload_modal.analyzing_picture": "Tasleḍt n tugna tetteddu…",
+  "upload_modal.apply": "Snes",
+  "upload_modal.applying": "Asnas…",
+  "upload_modal.choose_image": "Fren tugna",
+  "upload_modal.description_placeholder": "Aberraɣ arurad ineggez nnig n uqjun amuṭṭis",
+  "upload_modal.detect_text": "Sefru-d aḍris seg tugna",
+  "upload_modal.edit_media": "Ẓreg amidya",
+  "upload_modal.preparing_ocr": "Aheyyi n OCR…",
+  "upload_modal.preview_label": "Taskant ({ratio})",
   "upload_progress.label": "Asali iteddu...",
   "upload_progress.processing": "Asesfer…",
   "username.taken": "Yettwaṭṭef yisem-a n useqdac. Ɛreḍ wayeḍ",
@@ -670,6 +679,8 @@
   "video.expand": "Semɣeṛ tavidyut",
   "video.fullscreen": "Agdil aččuran",
   "video.hide": "Ffer tabidyutt",
+  "video.mute": "Gzem imesli",
   "video.pause": "Sgunfu",
-  "video.play": "Seddu"
+  "video.play": "Seddu",
+  "video.unmute": "Rmed imesli"
 }
diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json
index adc3cdc230..f146fc652d 100644
--- a/app/javascript/mastodon/locales/kk.json
+++ b/app/javascript/mastodon/locales/kk.json
@@ -25,6 +25,7 @@
   "account.enable_notifications": "@{name} постары туралы ескерту",
   "account.endorse": "Профильде ұсыну",
   "account.featured_tags.last_status_never": "Пост жоқ",
+  "account.featured_tags.title": "{name} таңдаулы хэштегтері",
   "account.follow": "Жазылу",
   "account.followers": "Жазылушы",
   "account.followers.empty": "Бұл қолданушыға әлі ешкім жазылмаған.",
@@ -61,6 +62,7 @@
   "boost_modal.combo": "Келесіде өткізіп жіберу үшін басыңыз {combo}",
   "bundle_column_error.retry": "Қайтадан көріңіз",
   "bundle_modal_error.close": "Жабу",
+  "bundle_modal_error.message": "Бұл компонентті жүктеген кезде бір қате пайда болды.",
   "bundle_modal_error.retry": "Қайтадан көріңіз",
   "column.blocks": "Бұғатталғандар",
   "column.bookmarks": "Бетбелгілер",
@@ -119,6 +121,8 @@
   "directory.local": "Тек {domain} доменінен",
   "directory.new_arrivals": "Жаңадан келгендер",
   "directory.recently_active": "Жақында кіргендер",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Төмендегі кодты көшіріп алу арқылы жазбаны басқа сайттарға да орналастыра аласыз.",
   "embed.preview": "Былай көрінетін болады:",
   "emoji_button.activity": "Белсенділік",
@@ -145,6 +149,7 @@
   "empty_column.hashtag": "Бұндай хэштегпен әлі ешкім жазбапты.",
   "empty_column.home": "Әлі ешкімге жазылмапсыз. Бәлкім {public} жазбаларын қарап немесе іздеуді қолданып көрерсіз.",
   "empty_column.list": "Бұл тізімде ештеңе жоқ.",
+  "empty_column.lists": "Әзірше ешқандай тізіміңіз жоқ. Біреуін құрғаннан кейін осы жерде көрінетін болады.",
   "empty_column.mutes": "Әзірше ешқандай үнсізге қойылған қолданушы жоқ.",
   "empty_column.notifications": "Әзірше ешқандай ескертпе жоқ. Басқалармен араласуды бастаңыз және пікірталастарға қатысыңыз.",
   "empty_column.public": "Ештеңе жоқ бұл жерде! Өзіңіз бастап жазып көріңіз немесе басқаларға жазылыңыз",
@@ -207,8 +212,15 @@
   "lightbox.close": "Жабу",
   "lightbox.next": "Келесі",
   "lightbox.previous": "Алдыңғы",
+  "lists.account.add": "Тізімге қосу",
+  "lists.account.remove": "Тізімнен шығару",
   "lists.delete": "Тізімді өшіру",
   "lists.edit": "Тізімді өңдеу",
+  "lists.edit.submit": "Тақырыбын өзгерту",
+  "lists.new.create": "Тізім құру",
+  "lists.new.title_placeholder": "Жаңа тізім аты",
+  "lists.search": "Сіз іздеген адамдар арасында іздеу",
+  "lists.subheading": "Тізімдеріңіз",
   "load_pending": "{count, plural, one {# жаңа нәрсе} other {# жаңа нәрсе}}",
   "navigation_bar.blocks": "Бұғатталғандар",
   "navigation_bar.bookmarks": "Бетбелгілер",
@@ -250,6 +262,19 @@
   "notifications.filter.mentions": "Аталымдар",
   "notifications.filter.polls": "Сауалнама нәтижелері",
   "notifications.group": "{count} ескертпе",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "poll.closed": "Жабық",
   "poll.refresh": "Жаңарту",
   "poll.total_people": "{count, plural, one {# адам} other {# адам}}",
@@ -261,6 +286,8 @@
   "privacy.change": "Құпиялылықты реттеу",
   "privacy.public.short": "Ашық",
   "refresh": "Жаңарту",
+  "regeneration_indicator.label": "Жүктеу…",
+  "regeneration_indicator.sublabel": "Жергілікті желі құрылуда!",
   "relative_time.days": "{number}күн",
   "relative_time.hours": "{number}сағ",
   "relative_time.just_now": "жаңа",
@@ -328,7 +355,19 @@
   "upload_button.label": "Медиа қосу (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Файл жүктеу лимитінен асып кеттіңіз.",
   "upload_error.poll": "Сауалнамамен бірге файл жүктеуге болмайды.",
+  "upload_form.audio_description": "Есту қабілеті нашар адамдарға сипаттама беріңіз",
+  "upload_form.description": "Көру қабілеті нашар адамдар үшін сипаттаңыз",
   "upload_form.edit": "Түзету",
+  "upload_form.thumbnail": "Суретті өзгерту",
+  "upload_form.video_description": "Есту немесе көру қабілеті нашар адамдарға сипаттама беріңіз",
+  "upload_modal.analyzing_picture": "Суретті анализ жасау…",
+  "upload_modal.apply": "Қолдану",
+  "upload_modal.choose_image": "Сурет таңдау",
+  "upload_modal.description_placeholder": "Щучинск съезіндегі өрт пе? Вагон-үй, аэромобиль һәм ұшақ фюзеляжы цехінен ғой",
+  "upload_modal.detect_text": "Суреттен мәтін анықтау",
+  "upload_modal.edit_media": "Медиафайлды өңдеу",
+  "upload_modal.hint": "Алдын-ала қарау шеңберін басыңыз немесе сүйреңіз, барлық нобайларда көрінетін фокусты таңдау үшін.",
+  "upload_modal.preview_label": "Превью ({ratio})",
   "upload_progress.label": "Жүктеп жатыр...",
   "video.close": "Видеоны жабу",
   "video.download": "Файлды түсіру",
@@ -336,6 +375,8 @@
   "video.expand": "Видеоны аш",
   "video.fullscreen": "Толық экран",
   "video.hide": "Видеоны жасыр",
+  "video.mute": "Дыбысын бас",
   "video.pause": "Пауза",
-  "video.play": "Қосу"
+  "video.play": "Қосу",
+  "video.unmute": "Дауысын аш"
 }
diff --git a/app/javascript/mastodon/locales/kn.json b/app/javascript/mastodon/locales/kn.json
index c7ba9e3916..941857e25f 100644
--- a/app/javascript/mastodon/locales/kn.json
+++ b/app/javascript/mastodon/locales/kn.json
@@ -29,6 +29,8 @@
   "compose_form.spoiler.marked": "Text is hidden behind warning",
   "compose_form.spoiler.unmarked": "Text is not hidden",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "empty_column.account_timeline": "No toots here!",
   "empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
@@ -72,6 +74,19 @@
   "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
   "notification.reblog": "{name} boosted your status",
   "notifications.column_settings.status": "New toots:",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "privacy.change": "Adjust status privacy",
   "report.placeholder": "Type or paste additional comments",
   "report.submit": "Submit report",
@@ -87,5 +102,8 @@
   "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "Uploading…"
 }
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 544278e519..48b3e3803c 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "프로필 편집",
   "account.enable_notifications": "@{name} 의 게시물 알림 켜기",
   "account.endorse": "프로필에 추천하기",
-  "account.featured": "추천",
-  "account.featured.hashtags": "해시태그",
-  "account.featured.posts": "게시물",
   "account.featured_tags.last_status_at": "{date}에 마지막으로 게시",
   "account.featured_tags.last_status_never": "게시물 없음",
+  "account.featured_tags.title": "{name} 님의 추천 해시태그",
   "account.follow": "팔로우",
   "account.follow_back": "맞팔로우",
   "account.followers": "팔로워",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, other {게시물 {counter}개}}",
   "account.unblock": "차단 해제",
   "account.unblock_domain": "도메인 {domain} 차단 해제",
-  "account.unblock_domain_short": "차단 해제",
   "account.unblock_short": "차단 해제",
   "account.unendorse": "프로필에 추천하지 않기",
   "account.unfollow": "언팔로우",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "예상하지 못한 에러가 발생했습니다.",
   "alert.unexpected.title": "앗!",
   "alt_text_badge.title": "대체 문구",
-  "alt_text_modal.add_alt_text": "대체 텍스트 추가",
-  "alt_text_modal.add_text_from_image": "이미지에서 텍스트 추가",
-  "alt_text_modal.cancel": "취소",
-  "alt_text_modal.change_thumbnail": "썸네일 변경",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "청력 장애가 있는 사람들을 위한 설명을 작성하세요…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "시각 장애가 있는 사람들을 위한 설명을 작성하세요…",
-  "alt_text_modal.done": "완료",
   "announcement.announcement": "공지사항",
-  "annual_report.summary.archetype.booster": "연쇄부스트마",
-  "annual_report.summary.archetype.lurker": "은둔자",
-  "annual_report.summary.archetype.oracle": "예언자",
-  "annual_report.summary.archetype.pollster": "여론조사원",
-  "annual_report.summary.archetype.replier": "인싸",
-  "annual_report.summary.followers.followers": "팔로워",
-  "annual_report.summary.followers.total": "총 {count}",
-  "annual_report.summary.here_it_is": "{year}년 결산입니다:",
-  "annual_report.summary.highlighted_post.by_favourites": "가장 많은 좋아요를 받은 게시물",
-  "annual_report.summary.highlighted_post.by_reblogs": "가장 많이 부스트된 게시물",
-  "annual_report.summary.highlighted_post.by_replies": "가장 많은 답글을 받은 게시물",
-  "annual_report.summary.highlighted_post.possessive": "{name} 님의",
-  "annual_report.summary.most_used_app.most_used_app": "가장 많이 사용한 앱",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "가장 많이 사용한 해시태그",
-  "annual_report.summary.most_used_hashtag.none": "없음",
-  "annual_report.summary.new_posts.new_posts": "새 게시물",
-  "annual_report.summary.percentile.text": "<topLabel>{domain} 사용자의 상위</topLabel><percentage></percentage><bottomLabel>입니다.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "종부세는 안 걷을게요",
-  "annual_report.summary.thanks": "마스토돈과 함께 해주셔서 감사합니다!",
   "attachments_list.unprocessed": "(처리 안 됨)",
   "audio.hide": "소리 숨기기",
   "block_modal.remote_users_caveat": "우리는 {domain} 서버가 당신의 결정을 존중해 주길 부탁할 것입니다. 하지만 몇몇 서버는 차단을 다르게 취급할 수 있기 때문에 규정이 준수되는 것을 보장할 수는 없습니다. 공개 게시물은 로그인 하지 않은 사용자들에게 여전히 보여질 수 있습니다.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "요청하신 페이지를 찾을 수 없습니다. 주소창에 적힌 URL이 확실히 맞나요?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "닫기",
-  "bundle_modal_error.message": "이 화면을 불러오는 중 뭔가 잘못되었습니다.",
+  "bundle_modal_error.message": "컴포넌트를 불러오는 중 문제가 발생했습니다.",
   "bundle_modal_error.retry": "다시 시도",
   "closed_registrations.other_server_instructions": "마스토돈은 분산화 되어 있기 때문에, 다른 서버에서 계정을 만들더라도 이 서버와 상호작용 할 수 있습니다.",
   "closed_registrations_modal.description": "{domain}은 현재 가입이 불가능합니다. 하지만 마스토돈을 이용하기 위해 꼭 {domain}을 사용할 필요는 없다는 사실을 인지해 두세요.",
@@ -150,16 +121,13 @@
   "column.blocks": "차단한 사용자",
   "column.bookmarks": "북마크",
   "column.community": "로컬 타임라인",
-  "column.create_list": "리스트 만들기",
   "column.direct": "개인적인 멘션",
   "column.directory": "프로필 둘러보기",
   "column.domain_blocks": "차단한 도메인",
-  "column.edit_list": "리스트 편집",
   "column.favourites": "좋아요",
   "column.firehose": "실시간 피드",
   "column.follow_requests": "팔로우 요청",
   "column.home": "홈",
-  "column.list_members": "리스트 구성원 관리",
   "column.lists": "리스트",
   "column.mutes": "뮤트한 사용자",
   "column.notifications": "알림",
@@ -172,7 +140,6 @@
   "column_header.pin": "고정하기",
   "column_header.show_settings": "설정 보이기",
   "column_header.unpin": "고정 해제",
-  "column_search.cancel": "취소",
   "column_subheading.settings": "설정",
   "community.column_settings.local_only": "로컬만",
   "community.column_settings.media_only": "미디어만",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "수정",
   "confirmations.edit.message": "지금 편집하면 작성 중인 메시지를 덮어씁니다. 진행이 확실한가요?",
   "confirmations.edit.title": "게시물을 덮어쓸까요?",
-  "confirmations.follow_to_list.confirm": "팔로우하고 리스트에 추가",
-  "confirmations.follow_to_list.message": "리스트에 추가하려면 {name} 님을 팔로우해야 합니다.",
-  "confirmations.follow_to_list.title": "팔로우할까요?",
   "confirmations.logout.confirm": "로그아웃",
   "confirmations.logout.message": "정말로 로그아웃 하시겠습니까?",
   "confirmations.logout.title": "로그아웃 할까요?",
-  "confirmations.missing_alt_text.confirm": "대체 텍스트 추가",
-  "confirmations.missing_alt_text.message": "대체 텍스트가 없는 미디어를 포함하고 있습니다. 설명을 추가하면 더 많은 사람들이 내 콘텐츠에 접근할 수 있습니다.",
-  "confirmations.missing_alt_text.secondary": "그냥 게시하기",
-  "confirmations.missing_alt_text.title": "대체 텍스트를 추가할까요?",
   "confirmations.mute.confirm": "뮤트",
   "confirmations.redraft.confirm": "삭제하고 다시 쓰기",
   "confirmations.redraft.message": "정말로 이 게시물을 삭제하고 다시 쓰시겠습니까? 해당 게시물에 대한 부스트와 좋아요를 잃게 되고 원본에 대한 답장은 연결 되지 않습니다.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "당신의 계정 {disabledAccount}는 현재 비활성화 상태입니다.",
   "dismissable_banner.community_timeline": "여기 있는 것들은 계정이 {domain}에 있는 사람들의 최근 공개 게시물들입니다.",
   "dismissable_banner.dismiss": "지우기",
-  "dismissable_banner.explore_links": "이 소식들은 오늘 연합우주에서 가장 많이 공유된 것들입니다. 새 소식을 더 많은 사람들이 공유할수록 높은 순위가 됩니다.",
-  "dismissable_banner.explore_statuses": "이 게시물들은 오늘 연합우주에서 호응을 얻고 있는 게시물들입니다. 부스트와 관심을 받는 새로운 글들이 높은 순위가 됩니다.",
-  "dismissable_banner.explore_tags": "이 해시태그들은 연합우주에서 사람들의 인기를 끌고 있는 것들입니다. 다양한 사람들이 사용하는 해시태그일수록 높은 순위가 됩니다.",
-  "dismissable_banner.public_timeline": "이것은 {domain}에서 팔로우한 사람들의 최신 공개 게시물들입니다.",
+  "dismissable_banner.explore_links": "이 소식들은 오늘 소셜 웹에서 가장 많이 공유된 내용들입니다. 새 소식을 더 많은 사람들이 공유할수록 높은 순위가 됩니다.",
+  "dismissable_banner.explore_statuses": "이 게시물들은 오늘 소셜 웹에서 호응을 얻고 있는 게시물들입니다. 부스트와 관심을 받는 새로운 글들이 높은 순위가 됩니다.",
+  "dismissable_banner.explore_tags": "이 해시태그들은 이 서버와 분산화된 네트워크의 다른 서버에서 사람들의 인기를 끌고 있는 것들입니다.",
+  "dismissable_banner.public_timeline": "이것들은 {domain}에 있는 사람들이 팔로우한 사람들의 최신 공개 게시물들입니다.",
   "domain_block_modal.block": "서버 차단",
   "domain_block_modal.block_account_instead": "대신 @{name}를 차단",
   "domain_block_modal.they_can_interact_with_old_posts": "이 서버에 있는 사람들이 내 예전 게시물에 상호작용할 수는 있습니다.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "검색 결과",
   "emoji_button.symbols": "기호",
   "emoji_button.travel": "여행과 장소",
-  "empty_column.account_featured": "목록이 비어있습니다",
   "empty_column.account_hides_collections": "이 사용자는 이 정보를 사용할 수 없도록 설정했습니다",
   "empty_column.account_suspended": "계정 정지됨",
   "empty_column.account_timeline": "이곳에는 게시물이 없습니다!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.",
   "empty_column.home": "당신의 홈 타임라인은 비어있습니다! 더 많은 사람을 팔로우하여 채워보세요.",
   "empty_column.list": "리스트에 아직 아무것도 없습니다. 리스트의 누군가가 게시물을 올리면 여기에 나타납니다.",
+  "empty_column.lists": "아직 리스트가 없습니다. 리스트를 만들면 여기에 나타납니다.",
   "empty_column.mutes": "아직 아무도 뮤트하지 않았습니다.",
   "empty_column.notification_requests": "깔끔합니다! 여기엔 아무 것도 없습니다. 알림을 받게 되면 설정에 따라 여기에 나타나게 됩니다.",
   "empty_column.notifications": "아직 알림이 없습니다. 다른 사람들이 당신에게 반응했을 때, 여기에서 볼 수 있습니다.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "그걸 끄고 페이지를 새로고침 해보세요. 그래도 해결되지 않으면, 다른 브라우저나 네이티브 앱으로 마스토돈을 이용해 보실 수 있습니다.",
   "errors.unexpected_crash.copy_stacktrace": "에러 내용을 클립보드에 복사",
   "errors.unexpected_crash.report_issue": "문제 신고",
+  "explore.search_results": "검색 결과",
   "explore.suggested_follows": "사람들",
   "explore.title": "둘러보기",
   "explore.trending_links": "소식",
@@ -373,16 +334,13 @@
   "footer.about": "정보",
   "footer.directory": "프로필 책자",
   "footer.get_app": "앱 다운로드하기",
+  "footer.invite": "초대하기",
   "footer.keyboard_shortcuts": "키보드 단축키",
   "footer.privacy_policy": "개인정보처리방침",
   "footer.source_code": "소스코드 보기",
   "footer.status": "상태",
-  "footer.terms_of_service": "이용 약관",
   "generic.saved": "저장됨",
   "getting_started.heading": "시작하기",
-  "hashtag.admin_moderation": "#{name}에 대한 중재화면 열기",
-  "hashtag.browse": "#{hashtag}의 게시물 둘러보기",
-  "hashtag.browse_from_account": "@{name}의 #{hashtag} 게시물 둘러보기",
   "hashtag.column_header.tag_mode.all": "및 {additional}",
   "hashtag.column_header.tag_mode.any": "또는 {additional}",
   "hashtag.column_header.tag_mode.none": "{additional}를 제외하고",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, other {게시물 {counter}개}}",
   "hashtag.counter_by_uses_today": "오늘 {count, plural, other {{counter} 개의 게시물}}",
   "hashtag.follow": "해시태그 팔로우",
-  "hashtag.mute": "#{hashtag} 뮤트",
   "hashtag.unfollow": "해시태그 팔로우 해제",
   "hashtags.and_other": "…및 {count, plural,other {#개}}",
   "hints.profiles.followers_may_be_missing": "이 프로필의 팔로워 목록은 일부 누락되었을 수 있습니다.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "나를 팔로우하지 않는 사람들의 알림을 무시할까요?",
   "ignore_notifications_modal.not_following_title": "내가 팔로우하지 않는 사람들의 알림을 무시할까요?",
   "ignore_notifications_modal.private_mentions_title": "요청하지 않은 개인 멘션 알림을 무시할까요?",
-  "info_button.label": "도움말",
-  "info_button.what_is_alt_text": "<h1>대체 텍스트가 무었인가요?</h1> <p>대체 텍스트는 저시력자, 낮은 인터넷 대역폭 사용자, 더 자세한 문맥을 위해 이미지에 대한 설명을 제공하는 것입니다.</p> <p>깔끔하고 간결하고 객관적인 대체 텍스트를 작성해 모두가 이해하기 쉽게 만들고 접근성이 높아질 수 있습니다.</p><ul><li>중요한 요소에 중점을 두세요</li> <li>이미지 안의 글자를 요약하세요</li> <li>정형화된 문장 구조를 사용하세요</li> <li>중복된 정보를 피하세요</li> <li>복잡한 시각자료(도표나 지도 같은)에선 추세와 주요 결과에 중점을 두세요</li> </ul>",
-  "interaction_modal.action.favourite": "계속하려면 내 계정으로 즐겨찾기해야 합니다.",
-  "interaction_modal.action.follow": "계속하려면 내 계정으로 팔로우해야 합니다.",
-  "interaction_modal.action.reblog": "계속하려면 내 계정으로 리블로그해야 합니다.",
-  "interaction_modal.action.reply": "계속하려면 내 계정으로 답장해야 합니다.",
-  "interaction_modal.action.vote": "계속하려면 내 계정으로 투표해야 합니다.",
-  "interaction_modal.go": "이동",
-  "interaction_modal.no_account_yet": "아직 계정이 없나요?",
+  "interaction_modal.description.favourite": "마스토돈 계정을 통해, 게시물을 좋아하는 것으로 작성자에게 호의를 표하고 나중에 보기 위해 저장할 수 있습니다.",
+  "interaction_modal.description.follow": "마스토돈 계정을 통해, {name} 님을 팔로우 하고 그의 게시물을 홈 피드에서 받아 볼 수 있습니다.",
+  "interaction_modal.description.reblog": "마스토돈 계정을 통해, 이 게시물을 부스트 하고 자신의 팔로워들에게 공유할 수 있습니다.",
+  "interaction_modal.description.reply": "마스토돈 계정을 통해, 이 게시물에 응답할 수 있습니다.",
+  "interaction_modal.login.action": "홈 서버로 가기",
+  "interaction_modal.login.prompt": "내 홈 서버의 도메인. 예시: mastodon.social",
+  "interaction_modal.no_account_yet": "Mastodon 계정이 없나요?",
   "interaction_modal.on_another_server": "다른 서버에",
   "interaction_modal.on_this_server": "이 서버에서",
+  "interaction_modal.sign_in": "이 서버에 로그인하지 않았습니다. 계정이 어디에 속해있습니까?",
+  "interaction_modal.sign_in_hint": "팁: 여러분이 가입한 사이트입니다. 만약 기억이 나지 않는다면 가입환영 이메일을 찾아보는 것도 좋습니다. 전체 사용자이름(예: @mastodon@mastodon.social)을 넣어도 됩니다!",
   "interaction_modal.title.favourite": "{name} 님의 게시물을 좋아하기",
   "interaction_modal.title.follow": "{name} 님을 팔로우",
   "interaction_modal.title.reblog": "{name} 님의 게시물을 부스트",
   "interaction_modal.title.reply": "{name} 님의 게시물에 답글",
-  "interaction_modal.title.vote": "{name} 님의 투표에 참여",
-  "interaction_modal.username_prompt": "예시: {example}",
   "intervals.full.days": "{number} 일",
   "intervals.full.hours": "{number} 시간",
   "intervals.full.minutes": "{number} 분",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "CW로 가려진 텍스트를 표시/비표시",
   "keyboard_shortcuts.toggle_sensitivity": "미디어 보이기/숨기기",
   "keyboard_shortcuts.toot": "새 게시물 작성",
-  "keyboard_shortcuts.translate": "게시물 번역",
   "keyboard_shortcuts.unfocus": "작성창에서 포커스 해제",
   "keyboard_shortcuts.up": "리스트에서 위로 이동",
   "lightbox.close": "닫기",
@@ -490,32 +444,20 @@
   "link_preview.author": "{name}",
   "link_preview.more_from_author": "{name} 프로필 보기",
   "link_preview.shares": "{count, plural, other {{counter} 개의 게시물}}",
-  "lists.add_member": "추가",
-  "lists.add_to_list": "리스트에 추가",
-  "lists.add_to_lists": "리스트에 {name} 추가",
-  "lists.create": "생성",
-  "lists.create_a_list_to_organize": "새 리스트를 만들어 홈 피드를 정리하세요",
-  "lists.create_list": "리스트 생성",
+  "lists.account.add": "리스트에 추가",
+  "lists.account.remove": "리스트에서 제거",
   "lists.delete": "리스트 삭제",
-  "lists.done": "완료",
   "lists.edit": "리스트 편집",
-  "lists.exclusive": "구성원을 홈에서 숨기기",
-  "lists.exclusive_hint": "누군가가 이 리스트에 있으면 홈 피드에서는 숨겨 게시물을 두 번 보는 것을 방지합니다.",
-  "lists.find_users_to_add": "추가할 사용자 검색",
-  "lists.list_members": "리스트 구성원",
-  "lists.list_members_count": "{count, plural, other {# 명}}",
-  "lists.list_name": "리스트 이름",
-  "lists.new_list_name": "새 리스트 이름",
-  "lists.no_lists_yet": "아직 아무 리스트도 없습니다.",
-  "lists.no_members_yet": "아직 구성원이 없습니다.",
-  "lists.no_results_found": "결과가 없습니다.",
-  "lists.remove_member": "삭제",
+  "lists.edit.submit": "제목 수정",
+  "lists.exclusive": "홈에서 이 게시물들 숨기기",
+  "lists.new.create": "리스트 추가",
+  "lists.new.title_placeholder": "새 리스트의 이름",
   "lists.replies_policy.followed": "팔로우 한 사용자 누구나에게",
   "lists.replies_policy.list": "리스트의 구성원에게",
   "lists.replies_policy.none": "모두 제외",
-  "lists.save": "저장",
-  "lists.search": "검색",
-  "lists.show_replies_to": "리스트 구성원의 답글또한 포함하기",
+  "lists.replies_policy.title": "답글 표시:",
+  "lists.search": "팔로우 중인 사람들 중에서 찾기",
+  "lists.subheading": "리스트",
   "load_pending": "{count, plural, other {#}} 개의 새 항목",
   "loading_indicator.label": "불러오는 중...",
   "media_gallery.hide": "숨기기",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} 님이 {target}을 신고했습니다",
   "notification.admin.sign_up": "{name} 님이 가입했습니다",
   "notification.admin.sign_up.name_and_others": "{name} 외 {count, plural, other {# 명}}이 가입했습니다",
-  "notification.annual_report.message": "{year} #Wrapstodon 이 기다리고 있습니다! 올 해 마스토돈에서 있었던 최고의 순간과 기억들을 열어보세요!",
-  "notification.annual_report.view": "#Wrapstodon 보기",
   "notification.favourite": "{name} 님이 내 게시물을 좋아합니다",
   "notification.favourite.name_and_others_with_link": "{name} 외 <a>{count, plural, other {# 명}}</a>이 내 게시물을 좋아합니다",
-  "notification.favourite_pm": "{name} 님이 내 개인 멘션을 마음에 들어합니다",
-  "notification.favourite_pm.name_and_others_with_link": "{name} 외 <a>{count, plural, other {# 명}}</a>이 내 개인 멘션을 좋아합니다",
   "notification.follow": "{name} 님이 나를 팔로우했습니다",
   "notification.follow.name_and_others": "{name} 외 {count, plural, other {# 명}}이 날 팔로우했습니다",
   "notification.follow_request": "{name} 님이 팔로우 요청을 보냈습니다",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "데스크탑 알림 활성화",
   "notifications_permission_banner.how_to_control": "마스토돈이 열려 있지 않을 때에도 알림을 받으려면, 데스크탑 알림을 활성화 하세요. 당신은 어떤 종류의 반응이 데스크탑 알림을 발생할 지를 {icon} 버튼을 통해 세세하게 설정할 수 있습니다.",
   "notifications_permission_banner.title": "아무것도 놓치지 마세요",
-  "onboarding.follows.back": "뒤로가기",
-  "onboarding.follows.done": "완료",
+  "onboarding.action.back": "돌아가기",
+  "onboarding.actions.back": "돌아가기",
+  "onboarding.actions.go_to_explore": "무엇이 유행인지 보러 가기",
+  "onboarding.actions.go_to_home": "홈 피드로 가기",
+  "onboarding.compose.template": "안녕 #마스토돈!",
   "onboarding.follows.empty": "안타깝지만 아직은 아무 것도 보여드릴 수 없습니다. 검색을 이용하거나 둘러보기 페이지에서 팔로우 할 사람을 찾을 수 있습니다. 아니면 잠시 후에 다시 시도하세요.",
-  "onboarding.follows.search": "검색",
-  "onboarding.follows.title": "사람들을 팔로우하기",
+  "onboarding.follows.lead": "홈 피드는 마스토돈을 경험하는 주된 경로입니다. 더 많은 사람들을 팔로우 할수록 더 활발하고 흥미로워질 것입니다. 여기 시작을 위한 몇몇 추천을 드립니다:",
+  "onboarding.follows.title": "내게 맞는 홈 피드 꾸미기",
   "onboarding.profile.discoverable": "내 프로필을 발견 가능하도록 설정",
   "onboarding.profile.discoverable_hint": "마스토돈의 발견하기 기능에 참여하면 게시물이 검색 결과와 유행 란에 표시될 수 있고, 비슷한 관심사를 가진 사람들에게 자신의 프로필이 추천될 수 있습니다.",
   "onboarding.profile.display_name": "표시되는 이름",
   "onboarding.profile.display_name_hint": "진짜 이름 또는 재미난 이름…",
+  "onboarding.profile.lead": "언제든지 나중에 설정 메뉴에서 마저 할 수 있고, 그곳에서 더 많은 맞춤 옵션을 고를 수 있습니다.",
   "onboarding.profile.note": "자기소개",
   "onboarding.profile.note_hint": "남을 @mention 하거나 #hashtag 태그를 달 수 있습니다…",
   "onboarding.profile.save_and_continue": "저장 및 계속",
   "onboarding.profile.title": "프로필 설정",
   "onboarding.profile.upload_avatar": "프로필 사진 업로드",
   "onboarding.profile.upload_header": "프로필 헤더 업로드",
+  "onboarding.share.lead": "여러 사람에게 마스토돈에서 나를 찾을 수 있는 방법을 알립니다!",
+  "onboarding.share.message": "#마스토돈 이용하는 {username}입니다! {url} 에서 저를 팔로우 해보세요",
+  "onboarding.share.next_steps": "할만한 다음 단계:",
+  "onboarding.share.title": "프로필 공유하기",
+  "onboarding.start.lead": "특별하고, —알고리즘이 아닌— 내가 내 경험을 만들어 나가는 분산화된 소셜미디어인 마스토돈의 일원이 되셨습니다. 이 새로운 사회에서 새로운 출발을 해 봅시다:",
+  "onboarding.start.skip": "도움이 필요 없으신가요?",
+  "onboarding.start.title": "해내셨군요!",
+  "onboarding.steps.follow_people.body": "흥미로운 사람들을 팔로우하는 것은 마스토돈의 전부입니다.",
+  "onboarding.steps.follow_people.title": "내게 맞는 홈 피드 꾸미기",
+  "onboarding.steps.publish_status.body": "글, 사진, 영상, 설문 또는 {emoji}와 함께 세상에 인사해보세요.",
+  "onboarding.steps.publish_status.title": "첫번째 게시물 쓰기",
+  "onboarding.steps.setup_profile.body": "의미있는 프로필을 작성해 상호작용을 늘려보세요.",
+  "onboarding.steps.setup_profile.title": "프로필 꾸미기",
+  "onboarding.steps.share_profile.body": "친구에게 마스토돈에서 나를 찾을 수 있는 방법을 알려주세요!",
+  "onboarding.steps.share_profile.title": "프로필 공유하기",
+  "onboarding.tips.2fa": "<strong>알고 계신가요?</strong> 계정 설정 페이지에서 2단계 인증을 설정해 계정을 더 안전하게 할 수 있습니다. 마음에 드는 아무 TOTP 앱이나 사용 가능하며 전화번호는 필요 없습니다!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>알고 계신가요?</strong> 마스토돈은 분산형이기 때문에 만날 수 있는 사람들이 내 서버가 아닌 다른 서버에 존재할 수도 있습니다. 그리고 자연스럽게 상호작용 할 수 있습니다! 그들의 서버는 그들의 사용자명의 뒤쪽 절반입니다!",
+  "onboarding.tips.migration": "<strong>알고 계신가요?</strong> 나중에 가서 {domain}이 좋은 선택이 아니었다고 느껴질 때 팔로워를 유지하면서 다른 서버로 옮겨갈 수 있습니다. 심지어 스스로 서버를 세울 수도 있습니다!",
+  "onboarding.tips.verification": "<strong>알고 계신가요?</strong> 자신의 웹사이트에 마스토돈 프로필 링크를 넣은 후 프로필에 그 웹사이트를 넣으면 계정을 검증할 수 있습니다. 수수료나 서류가 필요하지 않습니다!",
   "password_confirmation.exceeds_maxlength": "암호 확인 값이 최대 암호 길이를 초과하였습니다",
   "password_confirmation.mismatching": "암호 확인 값이 일치하지 않습니다",
   "picture_in_picture.restore": "다시 넣기",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "설문 제거",
   "privacy.change": "게시물의 프라이버시 설정을 변경",
   "privacy.direct.long": "이 게시물에서 언급된 모두",
-  "privacy.direct.short": "개인 멘션",
+  "privacy.direct.short": "특정 인물",
   "privacy.private.long": "내 팔로워만",
   "privacy.private.short": "팔로워",
   "privacy.public.long": "마스토돈 내외 모두",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "개인정보처리방침",
   "recommended": "추천함",
   "refresh": "새로고침",
-  "regeneration_indicator.please_stand_by": "잠시 기다려주세요.",
-  "regeneration_indicator.preparing_your_home_feed": "홈 피드를 준비 중입니다…",
+  "regeneration_indicator.label": "불러오는 중…",
+  "regeneration_indicator.sublabel": "홈 피드를 준비하고 있습니다!",
   "relative_time.days": "{number}일 전",
   "relative_time.full.days": "{number} 일 전",
   "relative_time.full.hours": "{number} 시간 전",
@@ -801,11 +762,10 @@
   "search_results.accounts": "프로필",
   "search_results.all": "전부",
   "search_results.hashtags": "해시태그",
-  "search_results.no_results": "결과가 없습니다.",
-  "search_results.no_search_yet": "게시물, 프로필, 해시태그를 검색해보세요.",
+  "search_results.nothing_found": "검색어에 대한 결과를 찾을 수 없습니다",
   "search_results.see_all": "모두 보기",
   "search_results.statuses": "게시물",
-  "search_results.title": "\"{q}\"에 대한 검색",
+  "search_results.title": "{q}에 대한 검색",
   "server_banner.about_active_users": "30일 동안 이 서버를 사용한 사람들 (월간 활성 이용자)",
   "server_banner.active_users": "활성 사용자",
   "server_banner.administered_by": "관리자:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "아직 아무도 이 게시물을 부스트하지 않았습니다. 부스트 한 사람들이 여기에 표시 됩니다.",
   "status.redraft": "지우고 다시 쓰기",
   "status.remove_bookmark": "북마크 삭제",
-  "status.remove_favourite": "즐겨찾기에서 제거",
   "status.replied_in_thread": "글타래에 답장",
   "status.replied_to": "{name} 님에게",
   "status.reply": "답장",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "{target}에 대한 구독 언어 변경",
   "tabs_bar.home": "홈",
   "tabs_bar.notifications": "알림",
-  "terms_of_service.effective_as_of": "{date}부터 적용됨",
-  "terms_of_service.title": "이용 약관",
-  "terms_of_service.upcoming_changes_on": "{date}에 예정된 변경사항",
   "time_remaining.days": "{number} 일 남음",
   "time_remaining.hours": "{number} 시간 남음",
   "time_remaining.minutes": "{number} 분 남음",
@@ -897,12 +853,26 @@
   "upload_button.label": "이미지, 영상, 오디오 파일 추가",
   "upload_error.limit": "파일 업로드 제한에 도달했습니다.",
   "upload_error.poll": "파일 업로드는 설문과 함께 쓸 수 없습니다.",
+  "upload_form.audio_description": "청각장애인이나 저청각자를 위한 설명",
+  "upload_form.description": "시각장애인이나 저시력자를 위한 설명",
   "upload_form.drag_and_drop.instructions": "미디어 첨부파일을 집으려면 스페이스나 엔터를 누르세요. 드래그 하는 동안 방향키를 이용해 원하는 방향으로 이동할 수 있습니다. 스페이스나 엔터를 다시 눌러 새 위치에 놓거나 ESC를 이용해 취소할 수 있습니다.",
   "upload_form.drag_and_drop.on_drag_cancel": "드래그가 취소되었습니다. 미디어 첨부파일 {item}은 이동되지 않았습니다.",
   "upload_form.drag_and_drop.on_drag_end": "미디어 첨부파일 {item}은 이동되지 않았습니다.",
   "upload_form.drag_and_drop.on_drag_over": "미디어 첨부파일 {item}이 이동되었습니다.",
   "upload_form.drag_and_drop.on_drag_start": "미디어 첨부파일 {item}을 집었습니다.",
   "upload_form.edit": "수정",
+  "upload_form.thumbnail": "썸네일 변경",
+  "upload_form.video_description": "청각장애인, 저청각자, 시각장애인, 저시력자를 위한 설명",
+  "upload_modal.analyzing_picture": "사진 분석 중…",
+  "upload_modal.apply": "적용",
+  "upload_modal.applying": "적용 중...",
+  "upload_modal.choose_image": "이미지 선택",
+  "upload_modal.description_placeholder": "다람쥐 헌 쳇바퀴 타고파",
+  "upload_modal.detect_text": "사진에서 문자 탐색",
+  "upload_modal.edit_media": "미디어 수정",
+  "upload_modal.hint": "미리보기를 클릭하거나 드래그 해서 초점을 맞추세요. 이 점은 썸네일에서 항상 보여질 부분을 나타냅니다.",
+  "upload_modal.preparing_ocr": "OCR 준비 중…",
+  "upload_modal.preview_label": "미리보기 ({ratio})",
   "upload_progress.label": "업로드 중...",
   "upload_progress.processing": "처리 중...",
   "username.taken": "이미 쓰인 사용자명입니다. 다른 것으로 시도해보세요",
@@ -915,9 +885,5 @@
   "video.mute": "음소거",
   "video.pause": "일시정지",
   "video.play": "재생",
-  "video.skip_backward": "뒤로 건너뛰기",
-  "video.skip_forward": "앞으로 건너뛰기",
-  "video.unmute": "음소거 해제",
-  "video.volume_down": "음량 감소",
-  "video.volume_up": "음량 증가"
+  "video.unmute": "음소거 해제"
 }
diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json
index 23ee9fc932..33ae908796 100644
--- a/app/javascript/mastodon/locales/ku.json
+++ b/app/javascript/mastodon/locales/ku.json
@@ -29,6 +29,7 @@
   "account.endorse": "Taybetiyên li ser profîl",
   "account.featured_tags.last_status_at": "Şandiya dawî di {date} de",
   "account.featured_tags.last_status_never": "Şandî tune ne",
+  "account.featured_tags.title": "{name}'s hashtagên taybet",
   "account.follow": "Bişopîne",
   "account.follow_back": "Bişopîne",
   "account.followers": "Şopîner",
@@ -80,13 +81,7 @@
   "alert.rate_limited.title": "Rêje sînorkirî ye",
   "alert.unexpected.message": "Çewtiyeke bêhêvî çê bû.",
   "alert.unexpected.title": "Wey li min!",
-  "alt_text_modal.cancel": "Têk bibe",
-  "alt_text_modal.change_thumbnail": "Wêneyê biçûk biguherîne",
-  "alt_text_modal.done": "Qediya",
   "announcement.announcement": "Daxuyanî",
-  "annual_report.summary.followers.followers": "şopîner",
-  "annual_report.summary.followers.total": "{count} tevahî",
-  "annual_report.summary.new_posts.new_posts": "şandiyên nû",
   "attachments_list.unprocessed": "(bêpêvajo)",
   "audio.hide": "Dengê veşêre",
   "block_modal.show_less": "Kêmtir nîşan bide",
@@ -103,6 +98,7 @@
   "bundle_column_error.routing.body": "Rûpela xwestî nehate dîtin. Tu pê bawerî ku girêdana di darika navnîşanê de rast e?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Bigire",
+  "bundle_modal_error.message": "Di dema barkirina vê hêmanê de tiştek çewt çê bû.",
   "bundle_modal_error.retry": "Dîsa bicerbîne",
   "closed_registrations.other_server_instructions": "Ji ber ku Mastodon nenavendî ye, tu dika li ser rajekarek din ajimêrekê biafirînî û hîn jî bi vê yekê re tev bigerî.",
   "closed_registrations_modal.description": "Afirandina ajimêrekê li ser {domain} niha ne pêkan e, lê ji kerema xwe ji bîr neke ku pêdiviya te bi hebûna ajimêreke taybet li ser {domain} tune ye ku tu Mastodon bi kar bînî.",
@@ -180,6 +176,8 @@
   "disabled_account_banner.text": "Ajimêrê te {disabledAccount} niha neçalak e.",
   "dismissable_banner.community_timeline": "Ev şandiyên giştî yên herî dawî ji kesên ku ajimêrê wan ji aliyê {domain} ve têne pêşkêşkirin.",
   "dismissable_banner.dismiss": "Paşguh bike",
+  "dismissable_banner.explore_links": "Ev çîrokên nûçeyan niha li ser vê û rajekarên din ên tora nenavendî ji aliyê mirovan ve têne axaftin.",
+  "dismissable_banner.explore_tags": "Ev hashtagên ji vê û rajekarên din ên di tora nenavendî de niha li ser vê rajekarê balê dikşînin.",
   "domain_block_modal.block": "Rajekar asteng bike",
   "domain_pill.server": "Rajekar",
   "domain_pill.username": "Navê bikarhêner",
@@ -212,6 +210,7 @@
   "empty_column.hashtag": "Di vê hashtagê de hêj tiştekî tune.",
   "empty_column.home": "Rojeva demnameya te vala ye! Ji bona tijîkirinê bêtir mirovan bişopîne. {suggestions}",
   "empty_column.list": "Di vê rêzokê de hîn tiştek tune ye. Gava ku endamên vê rêzokê peyamên nû biweşînin, ew ê li vir xuya bibin.",
+  "empty_column.lists": "Hîn tu rêzokên te tune ne. Dema yekî çê bikî, ew ê li vir xuya bibe.",
   "empty_column.mutes": "Te tu bikarhêner bêdeng nekiriye.",
   "empty_column.notifications": "Hêj hişyariyên te tunene. Dema ku mirovên din bi we re têkilî danîn, hûn ê wê li vir bibînin.",
   "empty_column.public": "Li vir tiştekî tuneye! Ji raya giştî re tiştekî binivîsîne, an ji bo tijîkirinê ji rajekerên din bikarhêneran bi destan bişopînin",
@@ -221,6 +220,7 @@
   "error.unexpected_crash.next_steps_addons": "Neçalakkirin û nûkirina rûpelê biceribîne. Ku ev bi kêr neyê, dibe ku te hîn jî bi riya gerokeke cuda an jî sepana xwecihî ya Mastodon bi kar bînî.",
   "errors.unexpected_crash.copy_stacktrace": "Şopa gemara (stacktrace) tûrikê ra jê bigire",
   "errors.unexpected_crash.report_issue": "Pirsgirêkekê ragihîne",
+  "explore.search_results": "Encamên lêgerînê",
   "explore.title": "Vekole",
   "explore.trending_links": "Nûçe",
   "explore.trending_statuses": "Şandî",
@@ -247,6 +247,7 @@
   "footer.about": "Derbar",
   "footer.directory": "Pelrêça profîlan",
   "footer.get_app": "Bernamokê bistîne",
+  "footer.invite": "Mirovan vexwîne",
   "footer.keyboard_shortcuts": "Kurteriyên klavyeyê",
   "footer.privacy_policy": "Peymana nepeniyê",
   "footer.source_code": "Koda çavkanî nîşan bide",
@@ -268,6 +269,9 @@
   "home.column_settings.show_replies": "Bersivan nîşan bide",
   "home.hide_announcements": "Reklaman veşêre",
   "home.show_announcements": "Reklaman nîşan bide",
+  "interaction_modal.description.follow": "Bi ajimêrekê li ser Mastodon, tu dikarî {name} bişopînî da ku şandiyan li ser rojeva rûpela xwe bi dest bixe.",
+  "interaction_modal.description.reblog": "Bi ajimêrekê li ser Mastodon, tu dikarî vê şandiyê bilind bikî da ku wê bi şopînerên xwe re parve bikî.",
+  "interaction_modal.description.reply": "Bi ajimêrekê li ser Mastodon, tu dikarî bersiva vê şandiyê bidî.",
   "interaction_modal.on_another_server": "Li ser rajekareke cuda",
   "interaction_modal.on_this_server": "Li ser ev rajekar",
   "interaction_modal.title.follow": "{name} bişopîne",
@@ -313,11 +317,19 @@
   "lightbox.previous": "Paş",
   "limited_account_hint.action": "Bi heman awayî profîlê nîşan bide",
   "limited_account_hint.title": "Profîl ji aliyê rêveberên {domain}ê ve hatiye veşartin.",
+  "lists.account.add": "Li lîsteyê zêde bike",
+  "lists.account.remove": "Ji lîsteyê rake",
   "lists.delete": "Lîsteyê jê bibe",
   "lists.edit": "Lîsteyê serrast bike",
+  "lists.edit.submit": "Sernavê biguherîne",
+  "lists.new.create": "Li lîsteyê zêde bike",
+  "lists.new.title_placeholder": "Sernavê lîsteya nû",
   "lists.replies_policy.followed": "Bikarhênereke şopandî",
   "lists.replies_policy.list": "Endamên lîsteyê",
   "lists.replies_policy.none": "Ne yek",
+  "lists.replies_policy.title": "Bersivan nîşan bide:",
+  "lists.search": "Di navbera kesên ku te dişopînin bigere",
+  "lists.subheading": "Lîsteyên te",
   "load_pending": "{count, plural, one {# hêmaneke nû} other {#hêmaneke nû}}",
   "moved_to_account_banner.text": "Ajimêrê te {disabledAccount} niha neçalak e ji ber ku te bar kir bo {movedToAccount}.",
   "navigation_bar.about": "Derbar",
@@ -383,6 +395,20 @@
   "notifications_permission_banner.enable": "Agahdarîyên sermaseyê çalak bike",
   "notifications_permission_banner.how_to_control": "Da ku agahdariyên mastodon bistînî gava ne vekirî be. Agahdariyên sermaseyê çalak bike\n Tu dikarî agahdariyên sermaseyê bi rê ve bibî ku bi hemû cureyên çalakiyên ên ku agahdariyan rû didin ku bi riya tikandînê li ser bişkoka {icon} çalak dibe.",
   "notifications_permission_banner.title": "Tu tiştî bîr neke",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.share.title": "Profîla xwe parve bike",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "Vegerîne paş",
   "poll.closed": "Girtî",
   "poll.refresh": "Nû bike",
@@ -398,6 +424,8 @@
   "privacy_policy.last_updated": "Rojanekirina dawî {date}",
   "privacy_policy.title": "Politîka taybetiyê",
   "refresh": "Nû bike",
+  "regeneration_indicator.label": "Tê barkirin…",
+  "regeneration_indicator.sublabel": "Naveroka rûpela sereke ya te tê amedekirin!",
   "relative_time.days": "{number}r",
   "relative_time.full.days": "{number, plural, one {# roj} other {# roj}} berê",
   "relative_time.full.hours": "{number, plural, one {# demjimêr} other {# demjimêr}} berê",
@@ -465,7 +493,9 @@
   "search_results.accounts": "Profîl",
   "search_results.all": "Hemû",
   "search_results.hashtags": "Hashtag",
+  "search_results.nothing_found": "Ji bo van peyvên lêgerînê tiştek nehate dîtin",
   "search_results.statuses": "Şandî",
+  "search_results.title": "Li {q} bigere",
   "server_banner.about_active_users": "Kesên ku di van 30 rojên dawî de vê rajekarê bi kar tînin (Bikarhênerên Çalak ên Mehane)",
   "server_banner.active_users": "bikarhênerên çalak",
   "server_banner.administered_by": "Tê bi rêvebirin ji aliyê:",
@@ -539,7 +569,21 @@
   "upload_button.label": "Wêne, vîdeoyek an jî pelê dengî tevlî bike",
   "upload_error.limit": "Sînora barkirina pelan derbas bû.",
   "upload_error.poll": "Di rapirsîyan de mafê barkirina pelan nayê dayîn.",
+  "upload_form.audio_description": "Ji bona kesên kêm dibihîsin re pênase bike",
+  "upload_form.description": "Ji bona astengdarên dîtinê re vebêje",
   "upload_form.edit": "Serrast bike",
+  "upload_form.thumbnail": "Wêneyê biçûk biguherîne",
+  "upload_form.video_description": "Ji bo kesên kerr û lalan pênase bike",
+  "upload_modal.analyzing_picture": "Wêne tê analîzkirin…",
+  "upload_modal.apply": "Bisepîne",
+  "upload_modal.applying": "Tê sepandin…",
+  "upload_modal.choose_image": "Wêneyê hilbijêre",
+  "upload_modal.description_placeholder": "Rovîyek qehweyî û bilez li ser kûçikê tîral banz dide",
+  "upload_modal.detect_text": "Ji nivîsa wêneyê re serwext be",
+  "upload_modal.edit_media": "Medyayê sererast bike",
+  "upload_modal.hint": "Ji bo hilbijartina xala navendê her tim dîmenê piçûk de pêşdîtina çerxê bitikîne an jî kaş bike.",
+  "upload_modal.preparing_ocr": "OCR dihê amadekirin…",
+  "upload_modal.preview_label": "Pêşdîtin ({ratio})",
   "upload_progress.label": "Tê barkirin...",
   "upload_progress.processing": "Kar tê kirin…",
   "video.close": "Vîdyoyê bigire",
@@ -548,6 +592,8 @@
   "video.expand": "Vîdyoyê berferh bike",
   "video.fullscreen": "Dimendera tijî",
   "video.hide": "Vîdyo veşêre",
+  "video.mute": "Dengê qut bike",
   "video.pause": "Rawestîne",
-  "video.play": "Vêxe"
+  "video.play": "Vêxe",
+  "video.unmute": "Dengê qut neke"
 }
diff --git a/app/javascript/mastodon/locales/kw.json b/app/javascript/mastodon/locales/kw.json
index 2cd7a3eb0e..cef24aa3b7 100644
--- a/app/javascript/mastodon/locales/kw.json
+++ b/app/javascript/mastodon/locales/kw.json
@@ -43,6 +43,7 @@
   "boost_modal.combo": "Hwi a yll gwaska {combo} dhe woheles hemma an nessa tro",
   "bundle_column_error.retry": "Assayewgh arta",
   "bundle_modal_error.close": "Degea",
+  "bundle_modal_error.message": "Neppyth eth yn kamm ow karga'n elven ma.",
   "bundle_modal_error.retry": "Assayewgh arta",
   "column.blocks": "Devnydhyoryon lettys",
   "column.bookmarks": "Folennosow",
@@ -101,6 +102,8 @@
   "directory.local": "A {domain} hepken",
   "directory.new_arrivals": "Devedhyansow nowydh",
   "directory.recently_active": "Bew a-gynsow",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Stagewgh an post ma a-berth yn agas gwiasva ow tasskrifa'n kod a-wòles.",
   "embed.preview": "Ottomma fatel hevel:",
   "emoji_button.activity": "Gwrians",
@@ -128,6 +131,7 @@
   "empty_column.hashtag": "Nyns eus travyth y'n bòlnos ma hwath.",
   "empty_column.home": "Agas amserlin dre yw gwag! Holyewgh moy a dus dh'y lenwel. {suggestions}",
   "empty_column.list": "Nyns eus travyth y'n rol ma hwath. Pan wra eseli an rol ma dyllo postow nowydh, i a wra omdhiskwedhes omma.",
+  "empty_column.lists": "Nyns eus dhywgh rolyow hwath. Pan wrewgh onan, hi a wra omdhiskwedhes omma.",
   "empty_column.mutes": "Ny wrussowgh tawhe devnydhyoryon vyth hwath.",
   "empty_column.notifications": "Nyns eus dhywgh gwarnyansow hwath. Pan wra tus erel ynterweythresa genowgh, hwi a'n gwel omma.",
   "empty_column.public": "Nyns eus travyth omma! Skrifewgh neppyth yn poblek, po holyewgh tus a leurennow erel dre leuv dh'y lenwel",
@@ -193,11 +197,19 @@
   "lightbox.close": "Degea",
   "lightbox.next": "Nessa",
   "lightbox.previous": "Kynsa",
+  "lists.account.add": "Keworra dhe rol",
+  "lists.account.remove": "Removya a rol",
   "lists.delete": "Dilea rol",
   "lists.edit": "Golegi rol",
+  "lists.edit.submit": "Chanjya titel",
+  "lists.new.create": "Keworra rol",
+  "lists.new.title_placeholder": "Titel rol nowydh",
   "lists.replies_policy.followed": "Py devnydhyer holys pynag",
   "lists.replies_policy.list": "Eseli an rol",
   "lists.replies_policy.none": "Nagonan",
+  "lists.replies_policy.title": "Diskwedhes gorthebow orth:",
+  "lists.search": "Hwilas yn-mysk tus a holyewgh",
+  "lists.subheading": "Agas rolyow",
   "load_pending": "{count, plural, one {# daklennowydh} other {# a daklennow nowydh}}",
   "navigation_bar.blocks": "Devnydhyoryon lettys",
   "navigation_bar.bookmarks": "Folennosow",
@@ -249,6 +261,19 @@
   "notifications_permission_banner.enable": "Gweythresa gwarnyansow pennskrin",
   "notifications_permission_banner.how_to_control": "Dhe dhegemeres gwarnyansow pan na vo Mastodon ygerys, gwrewgh gweythresa gwarnyansow pennskrin. Hwi a yll dyghtya py eghennow a ynterweythresow a wra gwarnyansow pennskrin der an boton {icon} a-wartha, pan vons gweythresys.",
   "notifications_permission_banner.title": "Na wrewgh kelli travyth",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "Daskor e",
   "poll.closed": "Deges",
   "poll.refresh": "Daskarga",
@@ -261,6 +286,8 @@
   "privacy.change": "Chanjya privetter an post",
   "privacy.public.short": "Poblek",
   "refresh": "Daskarga",
+  "regeneration_indicator.label": "Ow karga…",
+  "regeneration_indicator.sublabel": "Yma agas lin dre ow pos pareusys!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}o",
   "relative_time.just_now": "lemmyn",
@@ -331,7 +358,20 @@
   "upload_button.label": "Keworra skeusennow, gwydhyow po sonadow",
   "upload_error.limit": "Finweth ughkarga a veu gorfassys.",
   "upload_error.poll": "Nyns yw gesys ughkarga restrennow gans sondyansow.",
+  "upload_form.audio_description": "Deskrifewgh rag tus vodharek",
+  "upload_form.description": "Deskrifewgh rag tus dhallek",
   "upload_form.edit": "Golegi",
+  "upload_form.thumbnail": "Chanjya avenik",
+  "upload_form.video_description": "Deskrifa rag tus vodharek po dallek",
+  "upload_modal.analyzing_picture": "Ow tytratya skeusen…",
+  "upload_modal.apply": "Gweytha",
+  "upload_modal.choose_image": "Dewis aven",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Kilela tekst a skeusen",
+  "upload_modal.edit_media": "Golegi myski",
+  "upload_modal.hint": "Klykkyewgh po tenna'n kylgh war an gynwel dhe dhewis an poynt fogella neb a vydh pupprys gweladow yn pub avenik.",
+  "upload_modal.preparing_ocr": "Ow pareusi ANG…",
+  "upload_modal.preview_label": "Kynwel ({ratio})",
   "upload_progress.label": "Owth ughkarga...",
   "video.close": "Degea gwydhyow",
   "video.download": "Iskarga restren",
@@ -339,6 +379,8 @@
   "video.expand": "Efani gwydhyow",
   "video.fullscreen": "Skrin leun",
   "video.hide": "Kudha gwydhyow",
+  "video.mute": "Tawhe son",
   "video.pause": "Powes",
-  "video.play": "Seni"
+  "video.play": "Seni",
+  "video.unmute": "Antawhe son"
 }
diff --git a/app/javascript/mastodon/locales/la.json b/app/javascript/mastodon/locales/la.json
index c6e5d85c07..dba92bd5f5 100644
--- a/app/javascript/mastodon/locales/la.json
+++ b/app/javascript/mastodon/locales/la.json
@@ -23,6 +23,7 @@
   "account.domain_blocked": "Dominium impeditum",
   "account.edit_profile": "Recolere notionem",
   "account.featured_tags.last_status_never": "Nulla contributa",
+  "account.featured_tags.title": "Hashtag notātī {name}",
   "account.followers_counter": "{count, plural, one {{counter} sectator} other {{counter} sectatores}}",
   "account.following_counter": "{count, plural, one {{counter} sectans} other {{counter} sectans}}",
   "account.moved_to": "{name} significavit eum suam rationem novam nunc esse:",
@@ -41,6 +42,7 @@
   "bundle_column_error.retry": "Retemptare",
   "bundle_column_error.routing.title": "CCCCIIII",
   "bundle_modal_error.close": "Claudere",
+  "bundle_modal_error.message": "Aliquid pervagātum est dum hunc componentem onerābam.",
   "bundle_modal_error.retry": "Retemptare",
   "column.about": "De",
   "column.bookmarks": "Signa paginales",
@@ -56,6 +58,7 @@
   "compose_form.lock_disclaimer": "Tua ratio non est {locked}. Quisquis te sequi potest ut visum accipiat nuntios tuos tantum pro sectatoribus.",
   "compose_form.lock_disclaimer.lock": "clausum",
   "compose_form.placeholder": "What is on your mind?",
+  "compose_form.poll.single": "Elige unum",
   "compose_form.publish_form": "Barrire",
   "compose_form.spoiler.marked": "Text is hidden behind warning",
   "compose_form.spoiler.unmarked": "Adde praeconium contentūs",
@@ -68,6 +71,10 @@
   "confirmations.reply.confirm": "Respondere",
   "disabled_account_banner.account_settings": "Praeferentiae ratiōnis",
   "disabled_account_banner.text": "Ratio tua {disabledAccount} debilitata est.",
+  "dismissable_banner.explore_links": "Hae sunt nūminae nūtiārum quā potissimum in rēti socialī hodie communicantur. Nūtiārum recentiorum ab pluribus hominibus diversīs positārum gradūs altiorēs sunt.",
+  "dismissable_banner.explore_statuses": "Hae sunt nūtiārum ex rēte socialī quā hodie trahunt favorem. Nūtiārum recentiorum cum pluribus auguriīs et favōribus gradūs altiorēs sunt.",
+  "dismissable_banner.explore_tags": "Hae sunt hashtags quae hodie in rēte socialī favorem trahunt. Hashtags quae ab pluribus diversis hominibus adhibentur gradūs altiorēs sunt.",
+  "dismissable_banner.public_timeline": "Hae sunt recentissimae nuntii publici ab hominibus in rēte socialī qui ab hominibus in {domain} sequuntur.",
   "domain_block_modal.you_wont_see_posts": "Nuntios aut notificātiōnēs ab usoribus in hōc servō nōn vidēbis.",
   "domain_pill.activitypub_like_language": "ActivityPub est velut lingua quam Mastodon cum aliīs sociālibus rētibus loquitur.",
   "domain_pill.your_handle": "Tuus nominulus:",
@@ -89,12 +96,15 @@
   "empty_column.followed_tags": "Nōn adhūc aliquem hastāginem secūtus es. Cum id fēceris, hic ostendētur.",
   "empty_column.home": "Tua linea temporum domesticus vacua est! Sequere plures personas ut eam compleas.",
   "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
+  "empty_column.lists": "\"Nūllae adhuc listae tibi sunt. Cum unam creāveris, hic apparēbit.\"",
   "empty_column.mutes": "Nondum quemquam usorem tacuisti.",
   "empty_column.notification_requests": "Omnia clara sunt! Nihil hic est. Cum novās notificātiōnēs accipīs, hic secundum tua praecepta apparebunt.",
   "empty_column.notifications": "Nōn adhūc habēs ullo notificātiōnēs. Cum aliī tē interagunt, hīc videbis.",
+  "explore.search_results": "Proventus explorationis",
   "explore.trending_statuses": "Contributa",
   "firehose.all": "Omnis",
   "footer.about": "De",
+  "footer.invite": "invitare populum",
   "generic.saved": "Servavit",
   "hashtag.column_header.tag_mode.none": "sine {additional}",
   "hashtag.column_settings.tag_mode.all": "Haec omnia",
@@ -105,6 +115,15 @@
   "hashtags.and_other": "…et {count, plural, other {# plus}}",
   "ignore_notifications_modal.filter_to_act_users": "Adhuc poteris accipere, reicere, vel referre usores",
   "ignore_notifications_modal.filter_to_review_separately": "Percolantur notificatiōnes separātim recensere potes",
+  "interaction_modal.description.favourite": "Cum accūntū in Mastodon, hanc postem praeferre potes ut auctōrī indicēs tē eam aestimāre et ad posterius servēs.",
+  "interaction_modal.description.follow": "Cum accūntū in Mastodon, {name} sequī potes ut eōrum postēs in tēlā domī tuā recipiās.",
+  "interaction_modal.description.reblog": "Cum ratione in Mastodon, hunc nuntium augēre potes ut eum cum tuis sectatoribus communicēs.",
+  "interaction_modal.description.reply": "Mastodon de Ratione, huic nuntio respondere potes.",
+  "interaction_modal.login.action": "Accipe me domum",
+  "interaction_modal.login.prompt": "Domum tuam dominicum servo, exempli causa mastodon.social",
+  "interaction_modal.no_account_yet": "Non in Mastodon?",
+  "interaction_modal.sign_in": "Ad hōc servientem nōn dēlūxī. Ubi accūntum tuum hospitātum est?",
+  "interaction_modal.sign_in_hint": "Consilium: Ille est situs interretialis ubi subscripsisti. Si non meministi, quaere epistulam gratulatoriam in tuis epistolis receptis. Etiam plenam usoris nomen tuum inserere potes! (exempli gratia @Mastodon@mastodon.social)",
   "intervals.full.days": "{number, plural, one {# die} other {# dies}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# horae}}",
   "intervals.full.minutes": "{number, plural, one {# minutum} other {# minuta}}",
@@ -141,6 +160,15 @@
   "keyboard_shortcuts.up": "Sumē sūrsum in īndice",
   "lightbox.close": "Claudere",
   "lightbox.next": "Secundum",
+  "lists.account.add": "Adde ad līstā",
+  "lists.account.remove": "Sūmere ad līstā",
+  "lists.edit.submit": "Mutare titulum",
+  "lists.exclusive": "Abscondere haec scripta ab domo",
+  "lists.new.create": "Addere līstā",
+  "lists.new.title_placeholder": "Novus titulus līstae",
+  "lists.replies_policy.title": "Monstra responsa ad:",
+  "lists.search": "Quaere in hominibus te sequi",
+  "lists.subheading": "Tuae listae",
   "load_pending": "{count, plural, one {# novum item} other {# nova itema}}",
   "moved_to_account_banner.text": "Tua ratione {disabledAccount} interdum reposita est, quod ad {movedToAccount} migrāvisti.",
   "mute_modal.you_wont_see_mentions": "Non videbis nuntios quī eōs commemorant.",
@@ -171,8 +199,35 @@
   "notifications.filter.all": "Omnia",
   "notifications.filter.polls": "Eventus electionis",
   "notifications.group": "{count} Notificātiōnēs",
+  "onboarding.action.back": "Accipe me",
+  "onboarding.actions.back": "Redde me",
+  "onboarding.actions.go_to_explore": "\"Duc me ad trending\"",
+  "onboarding.actions.go_to_home": "Duc me ad fluxum domi mei",
+  "onboarding.compose.template": "Salve #Mastodon!",
+  "onboarding.follows.lead": "Tua domus feed est principalis via Mastodon experīrī. Quō plūrēs persōnas sequeris, eō actīvior et interessantior erit. Ad tē incipiendum, ecce quaedam suāsiones:",
+  "onboarding.follows.title": "Personaliza fluxum domi tui",
   "onboarding.profile.display_name_hint": "Tuum nomen completum aut tuum nomen ludens…",
+  "onboarding.profile.lead": "Hoc semper postea per optiones configuratiónum perficere potes, ubi plura personalizandi optiones praesto sunt.",
   "onboarding.profile.note_hint": "Alios hominēs vel #hashtags @nōmināre potes…",
+  "onboarding.share.lead": "Fac homines scire quomodo te in Mastodon invenire possint!",
+  "onboarding.share.message": "Ego sum {username} in #Mastodon! Veni, sequere me apud {url}.",
+  "onboarding.share.next_steps": "Possibiles gradus sequentes:",
+  "onboarding.share.title": "Communica tuum profilem.",
+  "onboarding.start.lead": "Nunc pars es Mastodonis, singularis, socialis medii platformae decentralis ubi—non algoritmus—tuam ipsius experientiam curas. Incipiāmus in nova hac socialis regione:",
+  "onboarding.start.skip": "Non opus est auxilio ad incipiendum?",
+  "onboarding.start.title": "Perfecisti eam!",
+  "onboarding.steps.follow_people.body": "Sequens homines interessantes est id quod Mastodon agitur.",
+  "onboarding.steps.follow_people.title": "Personaliza fluxum domi tui",
+  "onboarding.steps.publish_status.body": "Dīc 'salvē' mundō per textum, imagines, vīdeōs, aut suffragia {emoji}",
+  "onboarding.steps.publish_status.title": "Fac tuum primum nuntium.",
+  "onboarding.steps.setup_profile.body": "Augere interactiones tuas per habens profilem comprehensivum.",
+  "onboarding.steps.setup_profile.title": "\"Personaliza tuum profilem.\"",
+  "onboarding.steps.share_profile.body": "Amīcīs tuīs nōscere sinē quō modō tē in Mastodon invenīre possint.",
+  "onboarding.steps.share_profile.title": "\"Communica tuum profilem Mastodon.\"",
+  "onboarding.tips.2fa": "<strong>Scisne?</strong>  Tūam ratiōnem sēcūrāre potes duōrum elementōrum authentīcātiōnem in ratiōnis tuī praeferentiīs statuendō. Cum ūllā app TOTP ex tuā ēlēctiōne operātur, numerus tēlephōnicus necessārius nōn est!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Scisne?</strong> Quoniam Mastodon dēcentrālis est, nōnnulla profīlia quae invenīs in servīs aliīs quam tuōrum erunt hospitāta. Tamen cum eīs sine impedīmentō interāgere potes! Servus eōrum in alterā parte nōminis eōrum est!",
+  "onboarding.tips.migration": "<strong>Scisne?</strong> Sī sentīs {domain} tibi in futūrō nōn esse optimam servī ēlēctiōnem, ad alium servum Mastodon sine amittendō sectātōribus tuīs migrāre potes. Etiam tuum servum hospitārī potes!",
+  "onboarding.tips.verification": "<strong>Scisne?</strong>  Tūam ratiōnem verificāre potes iungendō nexum ad prōfīlium Mastodon tuum in propriā pāginā interrētiā et addendō pāginam ad prōfīlium tuum. Nullae pecūniae aut documenta necessāria sunt!",
   "poll.closed": "Clausum",
   "poll.total_people": "{count, plural, one {# persona} other {# personae}}",
   "poll.total_votes": "{count, plural, one {# suffragium} other {# suffragia}}",
@@ -183,6 +238,8 @@
   "poll_button.remove_poll": "Auferre electionem",
   "privacy.change": "Adjust status privacy",
   "privacy.public.short": "Coram publico",
+  "regeneration_indicator.label": "Impendium…",
+  "regeneration_indicator.sublabel": "Tua domus feed praeparātur!",
   "relative_time.full.days": "{number, plural, one {# ante die} other {# ante dies}}",
   "relative_time.full.hours": "{number, plural, one {# ante horam} other {# ante horas}}",
   "relative_time.full.just_now": "nunc",
@@ -210,6 +267,8 @@
   "report_notification.categories.other": "Altera",
   "search.placeholder": "Quaerere",
   "search_results.all": "Omnis",
+  "search_results.nothing_found": "Nihil inveniri potuit pro his quaestionibus.",
+  "search_results.title": "Quaere per {q}",
   "server_banner.active_users": "usūāriī āctīvī",
   "server_banner.administered_by": "Administratur:",
   "server_banner.is_one_of_many": "{domain} est unum ex multis independentibus servientibus Mastodon quos adhibere potes ut participes in fediverso.",
@@ -239,6 +298,9 @@
   "units.short.million": "{count} milionum",
   "units.short.thousand": "{count} millia",
   "upload_button.label": "Imaginēs, vīdeō aut fīle audītūs adde",
+  "upload_form.audio_description": "Describe for people who are hard of hearing",
   "upload_form.edit": "Recolere",
-  "upload_progress.label": "Uploading…"
+  "upload_modal.description_placeholder": "A velox brunneis vulpes salit super piger canis",
+  "upload_progress.label": "Uploading…",
+  "video.mute": "Confutare soni"
 }
diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json
index f67ef676ad..cb78b7772f 100644
--- a/app/javascript/mastodon/locales/lad.json
+++ b/app/javascript/mastodon/locales/lad.json
@@ -29,6 +29,7 @@
   "account.endorse": "Avalia en profil",
   "account.featured_tags.last_status_at": "Ultima publikasyon de {date}",
   "account.featured_tags.last_status_never": "No ay publikasyones",
+  "account.featured_tags.title": "Etiketas avaliadas de {name}",
   "account.follow": "Sige",
   "account.follow_back": "Sige tamyen",
   "account.followers": "Suivantes",
@@ -64,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}}",
   "account.unblock": "Dezbloka a @{name}",
   "account.unblock_domain": "Dezbloka domeno {domain}",
-  "account.unblock_domain_short": "Dezbloka",
   "account.unblock_short": "Dezbloka",
   "account.unendorse": "No avalia en profil",
   "account.unfollow": "Desige",
@@ -86,22 +86,7 @@
   "alert.unexpected.message": "Afito un yerro no asperado.",
   "alert.unexpected.title": "Atyo!",
   "alt_text_badge.title": "Teksto alternativo",
-  "alt_text_modal.add_alt_text": "Adjusta teksto alternativo",
-  "alt_text_modal.cancel": "Anula",
-  "alt_text_modal.change_thumbnail": "Troka minyatura",
-  "alt_text_modal.done": "Fecho",
   "announcement.announcement": "Pregon",
-  "annual_report.summary.archetype.pollster": "El anketero",
-  "annual_report.summary.followers.followers": "suivantes",
-  "annual_report.summary.highlighted_post.by_favourites": "la puvlikasyon mas favoritada",
-  "annual_report.summary.highlighted_post.by_reblogs": "la puvlikasyon mas repartajada",
-  "annual_report.summary.highlighted_post.by_replies": "la puvlikasyon kon mas repuestas",
-  "annual_report.summary.highlighted_post.possessive": "de {name}",
-  "annual_report.summary.most_used_app.most_used_app": "la aplikasyon mas uzada",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "la etiketa mas uzada",
-  "annual_report.summary.most_used_hashtag.none": "Dinguno",
-  "annual_report.summary.new_posts.new_posts": "puvlikasyones muevas",
-  "annual_report.summary.thanks": "Mersi por ser parte de Mastodon!",
   "attachments_list.unprocessed": "(no prosesado)",
   "audio.hide": "Eskonde audio",
   "block_modal.show_less": "Amostra manko",
@@ -123,6 +108,7 @@
   "bundle_column_error.routing.body": "No se pudo trokar la pajina solisitada. Estas siguro ke el adreso URL en la vara de adreso es djusto?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Serra",
+  "bundle_modal_error.message": "Algo negro afito al eskargar este komponente.",
   "bundle_modal_error.retry": "Aprova de muevo",
   "closed_registrations.other_server_instructions": "Deke Mastodon es desentralizado, puedes kriyar un kuento en otro sirvidor i ainda enteraktuar kon este.",
   "closed_registrations_modal.description": "Aktualmente no es posivle kriyar un kuento en {domain}, ama por favor akodrate de ke no ay menester de tener un kuento espesifikamente en {domain} para kulanear Mastodon.",
@@ -133,11 +119,9 @@
   "column.blocks": "Utilizadores blokados",
   "column.bookmarks": "Markadores",
   "column.community": "Linya lokala",
-  "column.create_list": "Kriya lista",
   "column.direct": "Enmentaduras privadas",
   "column.directory": "Eksplora profiles",
   "column.domain_blocks": "Domenos blokados",
-  "column.edit_list": "Edita lista",
   "column.favourites": "Te plazen",
   "column.firehose": "Linyas en bivo",
   "column.follow_requests": "Solisitudes de segimiento",
@@ -154,7 +138,6 @@
   "column_header.pin": "Fiksa",
   "column_header.show_settings": "Amostra opsyones",
   "column_header.unpin": "Defiksar",
-  "column_search.cancel": "Anula",
   "column_subheading.settings": "Opsyones",
   "community.column_settings.local_only": "Solo lokalas",
   "community.column_settings.media_only": "Solo multimedia",
@@ -173,7 +156,7 @@
   "compose_form.poll.duration": "Durasion de anketa",
   "compose_form.poll.multiple": "Multiples opsyones",
   "compose_form.poll.option_placeholder": "Opsyon {number}",
-  "compose_form.poll.single": "Opsyon unika",
+  "compose_form.poll.single": "Eskoje uno",
   "compose_form.poll.switch_to_multiple": "Troka anketa para permeter a eskojer mas ke una opsyon",
   "compose_form.poll.switch_to_single": "Troka anketa para permeter a eskojer solo una opsyon",
   "compose_form.poll.type": "Estilo",
@@ -196,12 +179,9 @@
   "confirmations.discard_edit_media.message": "Tienes trokamientos no guadrados en la deskripsion o vista previa. Keres efasarlos entanto?",
   "confirmations.edit.confirm": "Edita",
   "confirmations.edit.message": "Si edites agora, kitaras el mesaj kualo estas eskriviendo aktualmente. Estas siguro ke keres fazerlo?",
-  "confirmations.follow_to_list.title": "Segir utilizador?",
   "confirmations.logout.confirm": "Sal",
   "confirmations.logout.message": "Estas siguro ke keres salir de tu kuento?",
   "confirmations.logout.title": "Salir?",
-  "confirmations.missing_alt_text.confirm": "Adjusta teksto alternativo",
-  "confirmations.missing_alt_text.title": "Adjustar teksto alternativo?",
   "confirmations.mute.confirm": "Silensia",
   "confirmations.redraft.confirm": "Efasa i reeskrive",
   "confirmations.redraft.message": "Estas siguro ke keres efasar esta publikasyon i reeskrivirla? Pedreras todos los favoritos i repartajasyones asosiados kon esta publikasyon i repuestas a eya seran guerfanadas.",
@@ -229,8 +209,10 @@
   "disabled_account_banner.text": "Tu kuento {disabledAccount} esta aktualmente inkapasitado.",
   "dismissable_banner.community_timeline": "Estas son las publikasyones publikas mas resientes de las personas kualos kuentos estan balabayados en {domain}.",
   "dismissable_banner.dismiss": "Kita",
-  "dismissable_banner.explore_statuses": "Estas publikasyones del fediverso estan agora popularas. Publikasyones mas muevas, kon mas repartajasiones i favoritadas por mas djente aparesen primero.",
-  "dismissable_banner.public_timeline": "Estas son las publikasyones publikas mas resientes de personas en el fediverso a las kualas la djente de {domain} sige.",
+  "dismissable_banner.explore_links": "Estos haberes estan diskutidos agora por djente en este sirvidor i otros de la red desentralizada.",
+  "dismissable_banner.explore_statuses": "Estas publikasyones de este sirvidor i otros de la red desentralizada estan agora popularas. Publikasyones mas muevas, kon mas repartajasiones i favoritadas por mas djente aparesen primero.",
+  "dismissable_banner.explore_tags": "Estas etiketas estan agora popularas en la red sosyala. Etiketas uzadas por mas djente aparesen primero.",
+  "dismissable_banner.public_timeline": "Estas son las publikasyones publikas mas resientes de personas en la red sosyala a las kualas la djente de {domain} sige.",
   "domain_block_modal.block": "Bloka sirvidor",
   "domain_block_modal.block_account_instead": "Bloka @{name} en su lugar",
   "domain_block_modal.they_can_interact_with_old_posts": "Las personas de este sirvidor pueden enteraktuar kon tus puvlikasyones viejas.",
@@ -277,6 +259,7 @@
   "empty_column.hashtag": "Ainda no ay niente en esta etiketa.",
   "empty_column.home": "Tu linya de tiempo esta vaziya! Sige a mas personas para inchirla.",
   "empty_column.list": "Ainda no ay niente en esta lista. Kuando miembros de esta lista publiken muevas publikasyones, se amostraran aki.",
+  "empty_column.lists": "Ainda no tienes dinguna lista. Kuando kriyes una, aperesera aki.",
   "empty_column.mutes": "Ainda no tienes silensiado a dingun utilizador.",
   "empty_column.notifications": "Ainda no tienes dingun avizo. Kuando otras personas enteraktuen kontigo, se amostraran aki.",
   "empty_column.public": "No ay niente aki! Eskrive algo publikamente o manualmente sige utilizadores de otros sirvidores para inchirlo",
@@ -286,6 +269,7 @@
   "error.unexpected_crash.next_steps_addons": "Aprova inkapasitarlos i arefreskar la pajina. Si esto no te ayuda, es posivle ke ainda puedas kulanear Mastodon kon otro navigador u otra aplikasyon nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Kopia stacktrace al portapapeles",
   "errors.unexpected_crash.report_issue": "Raporta problema",
+  "explore.search_results": "Rizultados de bushkeda",
   "explore.suggested_follows": "Djente",
   "explore.title": "Eksplora",
   "explore.trending_links": "Haberes",
@@ -333,13 +317,13 @@
   "footer.about": "Sovre mozotros",
   "footer.directory": "Katalogo de profiles",
   "footer.get_app": "Abasha aplikasyon",
+  "footer.invite": "Envita a djente",
   "footer.keyboard_shortcuts": "Akortamientos de klaviatura",
   "footer.privacy_policy": "Politika de privasita",
   "footer.source_code": "Ve kodiche fuente",
   "footer.status": "Estado",
   "generic.saved": "Guadrado",
   "getting_started.heading": "Primos pasos",
-  "hashtag.admin_moderation": "Avre la enterfaz de moderasyon para #{name}",
   "hashtag.column_header.tag_mode.all": "i {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
   "hashtag.column_header.tag_mode.none": "sin {additional}",
@@ -376,14 +360,21 @@
   "ignore_notifications_modal.not_followers_title": "Inyorar avizos de personas a las kualas no te sigen?",
   "ignore_notifications_modal.not_following_title": "Inyorar avizos de personas a las kualas no siges?",
   "ignore_notifications_modal.private_mentions_title": "Ignorar avizos de mensyones privadas no solisitadas?",
-  "info_button.label": "Ayuda",
+  "interaction_modal.description.favourite": "Kon un kuento en Mastodon, puedes markar esta publikasyon komo favorita para ke el autor sepa ke te plaze i para guadrarla para dempues.",
+  "interaction_modal.description.follow": "Kon un kuento en Mastodon, puedes segir a {name} para risivir sus publikasyones en tu linya temporal prinsipala.",
+  "interaction_modal.description.reblog": "Kon un kuento en Mastodon, puedes repartajar esta publikasyon para amostrarla a tus suivantes.",
+  "interaction_modal.description.reply": "Kon un kuento en Mastodon, puedes arispondir a esta publikasyon.",
+  "interaction_modal.login.action": "Va a tu sirvidor",
+  "interaction_modal.login.prompt": "Domeno del sirvidor de tu kuento, por enshemplo mastodon.social",
+  "interaction_modal.no_account_yet": "No tyenes kuento de Mastodon?",
   "interaction_modal.on_another_server": "En otro sirvidor",
   "interaction_modal.on_this_server": "En este sirvidor",
+  "interaction_modal.sign_in": "No estas konektado kon este sirvidor. Ande tyenes tu kuento?",
+  "interaction_modal.sign_in_hint": "Konsejo: Akel es el sitio adonde te enrejistrates. Si no lo akodras, bushka el mesaj de posta elektronika de bienvenida en tu kuti de arivo. Tambien puedes eskrivir tu nombre de utilizador kompleto (por enshemplo @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Endika ke te plaze publikasyon de {name}",
   "interaction_modal.title.follow": "Sige a {name}",
   "interaction_modal.title.reblog": "Repartaja publikasyon de {name}",
   "interaction_modal.title.reply": "Arisponde a publikasyon de {name}",
-  "interaction_modal.title.vote": "Vota en la anketa de {name}",
   "intervals.full.days": "{number, plural, one {# diya} other {# diyas}}",
   "intervals.full.hours": "{number, plural, one {# ora} other {# oras}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
@@ -429,18 +420,20 @@
   "link_preview.author": "Publikasyon de {name}",
   "link_preview.more_from_author": "Mas de {name}",
   "link_preview.shares": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}}",
-  "lists.add_member": "Adjusta",
-  "lists.add_to_list": "Adjusta a lista",
-  "lists.create": "Kriya",
-  "lists.create_list": "Kriya lista",
+  "lists.account.add": "Adjusta a lista",
+  "lists.account.remove": "Kita de lista",
   "lists.delete": "Efasa lista",
-  "lists.done": "Fecho",
   "lists.edit": "Edita lista",
+  "lists.edit.submit": "Troka titolo",
+  "lists.exclusive": "Eskonder estas publikasyones de linya prinsipala",
+  "lists.new.create": "Adjusta lista",
+  "lists.new.title_placeholder": "Titolo de mueva lista",
   "lists.replies_policy.followed": "Kualseker utilizador segido",
   "lists.replies_policy.list": "Miembros de la lista",
   "lists.replies_policy.none": "Dinguno",
-  "lists.save": "Guadra",
-  "lists.search": "Bushka",
+  "lists.replies_policy.title": "Amostra repuestas a:",
+  "lists.search": "Bushka entre personas a las kualas siges",
+  "lists.subheading": "Tus listas",
   "load_pending": "{count, plural, one {# muevo elemento} other {# muevos elementos}}",
   "loading_indicator.label": "Eskargando…",
   "media_gallery.hide": "Eskonde",
@@ -484,7 +477,6 @@
   "notification.admin.report_statuses": "{name} raporto {target} por {category}",
   "notification.admin.report_statuses_other": "{name} raporto {target}",
   "notification.admin.sign_up": "{name} kriyo un konto",
-  "notification.annual_report.view": "Ve #Wrapstodon",
   "notification.favourite": "A {name} le plaze tu publikasyon",
   "notification.follow": "{name} te ampeso a segir",
   "notification.follow_request": "{name} tiene solisitado segirte",
@@ -565,20 +557,44 @@
   "notifications_permission_banner.enable": "Kapasita avizos de ensimameza",
   "notifications_permission_banner.how_to_control": "Para risivir avizos kuando Mastodon no esta avierto, kapasita avizos de ensimameza. Puedes kontrolar presizamente kualos tipos de enteraksiones djeneren avizos de ensimameza kon el boton {icon} arriva kuando esten kapasitadas.",
   "notifications_permission_banner.title": "Nunkua te piedres niente",
-  "onboarding.follows.back": "Atras",
-  "onboarding.follows.done": "Fecho",
+  "onboarding.action.back": "Va atras",
+  "onboarding.actions.back": "Va atras",
+  "onboarding.actions.go_to_explore": "Va a los trendes",
+  "onboarding.actions.go_to_home": "Va a tu linya prinsipala",
+  "onboarding.compose.template": "Ke haber, #Mastodon?",
   "onboarding.follows.empty": "Malorozamente, no se pueden amostrar rezultados en este momento. Puedes aprovar uzar la bushkeda o navigar por la pajina de eksplorasyon para topar personas a las que segir, o aprovarlo de muevo mas tadre.",
-  "onboarding.follows.search": "Bushka",
+  "onboarding.follows.lead": "Tu linya prinsipala es la forma prinsipala de eksperiensa de Mastodon. Kuantas mas personas sigas, sera mas aktiva o interesante. Para ampesar, aki ay algunas sujestyones:",
+  "onboarding.follows.title": "Personaliza tu linya prinsipala",
   "onboarding.profile.discoverable": "Faz ke mi profil apareska en bushkedas",
   "onboarding.profile.discoverable_hint": "Kuando permites ke tu profil sea diskuvriravle en Mastodon, tus publikasyones podran apareser en rezultados de bushkedas i trendes i tu profil podra ser sujerido a personas kon intereses similares a los tuyos.",
   "onboarding.profile.display_name": "Nombre amostrado",
   "onboarding.profile.display_name_hint": "Tu nombre para amostrar.",
+  "onboarding.profile.lead": "Siempre puedes kompletar esto mas tadre en las preferensyas, ande tambien ay mas opsyones de personalizasyon.",
   "onboarding.profile.note": "Tu deskripsyon",
   "onboarding.profile.note_hint": "Puedes @enmentar a otra djente o #etiketas…",
   "onboarding.profile.save_and_continue": "Guadra i kontinua",
   "onboarding.profile.title": "Konfigurasyon de profil",
   "onboarding.profile.upload_avatar": "Karga imaje de profil",
   "onboarding.profile.upload_header": "Karga kavesera de profil",
+  "onboarding.share.lead": "Informe a otros komo toparte en Mastodon!",
+  "onboarding.share.message": "Soy {username} en #Mastodon! Segidme en {url}",
+  "onboarding.share.next_steps": "Posivles sigientes pasos:",
+  "onboarding.share.title": "Partaja tu profil",
+  "onboarding.start.lead": "Agora eres parte de Mastodon, una red sosyala unika y desentralizada ande tu, no un algoritmo, puedes personalizar tu propya eksperyensya. Te entrodiziramos a esta mueva frontera sosyala:",
+  "onboarding.start.skip": "No nesesitas ayudo para ampesar?",
+  "onboarding.start.title": "Lo logrates!",
+  "onboarding.steps.follow_people.body": "El buto de Mastodon es segir a djente interesante.",
+  "onboarding.steps.follow_people.title": "Personaliza tu linya prinsipala",
+  "onboarding.steps.publish_status.body": "Puedes introdusirte al mundo kon teksto, fotos, videos o anketas {emoji}",
+  "onboarding.steps.publish_status.title": "Eskrive tu primera publikasyon",
+  "onboarding.steps.setup_profile.body": "Kompleta tu profil para aumentar tus enteraksyones.",
+  "onboarding.steps.setup_profile.title": "Personaliza tu profil",
+  "onboarding.steps.share_profile.body": "Informa a tus amigos komo toparte en Mastodon",
+  "onboarding.steps.share_profile.title": "Partaja tu profil de Mastodon",
+  "onboarding.tips.2fa": "<strong>Saviyas?</strong> Puedes protejar tu kuento konfigurando la autentifikasyon de dos pasos en la konfigurasyon de tu kuento. Funksyona kon kualsekera aplikasyon de TOTP ke eskojas. No ay menester de uzar tu numero de telefon!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Saviyas?</strong> komo Mastodon es desentralizado, algunos profiles que topas estan lokalizados en sirvidores distinktos del tuyo. I malgrado esto, puedes enteraktuar kon eyos! Sus sirvidor forma la sigunda mitad de sus nombres de utilizador!",
+  "onboarding.tips.migration": "<strong>Savias?</strong> Si en el avenir pensas ke {domain} no es el sirvidor adekuado para ti, puedes moverte a otruno sirvidor de Mastodon sir pedrer a tus suivantes. Inkluzo puedes ser el balabay de tu propyo sirvidor!",
+  "onboarding.tips.verification": "<strong>Savias?</strong> Puedes verifikar tu kuento ponyendo un atadijo a tu profil de Mastodon en tu propio sitio web i adjustando el sitio a tu profil. No ay menester de pagamyentos o dokumentos!",
   "password_confirmation.exceeds_maxlength": "La konfirmasyon de kod es demaziado lunga",
   "password_confirmation.mismatching": "Los dos kodes son desferentes",
   "picture_in_picture.restore": "Restora",
@@ -594,7 +610,7 @@
   "poll_button.remove_poll": "Kita anketa",
   "privacy.change": "Troka privasita de publikasyon",
   "privacy.direct.long": "Todos enmentados en la publikasyon",
-  "privacy.direct.short": "Enmentadura privada",
+  "privacy.direct.short": "Djente espesifika",
   "privacy.private.long": "Solo para tus suivantes",
   "privacy.private.short": "Suivantes",
   "privacy.public.long": "Todos en i afuera de Mastodon",
@@ -606,6 +622,8 @@
   "privacy_policy.title": "Politika de privasita",
   "recommended": "Rekomendado",
   "refresh": "Arefreska",
+  "regeneration_indicator.label": "Eskargando…",
+  "regeneration_indicator.sublabel": "Tu linya de tiempo prinsipala esta preparando!",
   "relative_time.days": "{number} d",
   "relative_time.full.days": "antes {number, plural, one {# diya} other {# diyas}}",
   "relative_time.full.hours": "antes {number, plural, one {# ora} other {# oras}}",
@@ -689,10 +707,10 @@
   "search_results.accounts": "Profiles",
   "search_results.all": "Todos",
   "search_results.hashtags": "Etiketas",
-  "search_results.no_results": "No ay rezultados.",
+  "search_results.nothing_found": "No se pudo topar niente para estos terminos de bushkeda",
   "search_results.see_all": "Ve todo",
   "search_results.statuses": "Publikasyones",
-  "search_results.title": "Bushka por \"{q}\"",
+  "search_results.title": "Bushka por {q}",
   "server_banner.about_active_users": "Utilizadores aktivos en este sirvidor durante los ultimos 30 diyas (utilizadores aktivos mensuales)",
   "server_banner.active_users": "utilizadores aktivos",
   "server_banner.administered_by": "Administrado por:",
@@ -773,7 +791,21 @@
   "upload_button.label": "Adjusta imajes, un video or una dosya audio",
   "upload_error.limit": "Limito de dosyas kargadas eksedido.",
   "upload_error.poll": "No se permite kargar dosyas kon anketas.",
+  "upload_form.audio_description": "Deskrive para personas sodras o kon problemes auditivos",
+  "upload_form.description": "Deskrive para personas siegas o kon problemes vizuales",
   "upload_form.edit": "Edita",
+  "upload_form.thumbnail": "Troka minyatura",
+  "upload_form.video_description": "Deskrive para personas sodras, kon problemes auditivos, siegas o kon problemes vizuales",
+  "upload_modal.analyzing_picture": "Analizando imaje…",
+  "upload_modal.apply": "Aplika",
+  "upload_modal.applying": "Aplikando…",
+  "upload_modal.choose_image": "Eskoje imaje",
+  "upload_modal.description_placeholder": "Lorem ipsum dolor sit amet",
+  "upload_modal.detect_text": "Detektar teksto de la imaje",
+  "upload_modal.edit_media": "Edita multimedia",
+  "upload_modal.hint": "Klika o arrastra el sirkolo en la vista previa para eskojer el punto fokal ke siempre estara en vista en todas las minyaturas.",
+  "upload_modal.preparing_ocr": "Preparando OCR…",
+  "upload_modal.preview_label": "Vista previa ({ratio})",
   "upload_progress.label": "Kargando...",
   "upload_progress.processing": "Prosesando…",
   "username.taken": "Akel nombre de utilizador ya esta en uzo. Aprova otruno",
@@ -783,8 +815,8 @@
   "video.expand": "Espande video",
   "video.fullscreen": "Ekran kompleto",
   "video.hide": "Eskonde video",
-  "video.mute": "Silensia",
+  "video.mute": "Silensia son",
   "video.pause": "Pauza",
   "video.play": "Reproduze",
-  "video.unmute": "Desilensia"
+  "video.unmute": "Desilensia son"
 }
diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json
index 7110e809c1..ae181c3494 100644
--- a/app/javascript/mastodon/locales/lt.json
+++ b/app/javascript/mastodon/locales/lt.json
@@ -29,6 +29,7 @@
   "account.endorse": "Rodyti profilyje",
   "account.featured_tags.last_status_at": "Paskutinis įrašas {date}",
   "account.featured_tags.last_status_never": "Nėra įrašų",
+  "account.featured_tags.title": "{name} rodomi saitažodžiai",
   "account.follow": "Sekti",
   "account.follow_back": "Sekti atgal",
   "account.followers": "Sekėjai",
@@ -85,33 +86,7 @@
   "alert.unexpected.message": "Įvyko netikėta klaida.",
   "alert.unexpected.title": "Ups!",
   "alt_text_badge.title": "Alternatyvus tekstas",
-  "alt_text_modal.add_alt_text": "Pridėti alternatyvųjį tekstą",
-  "alt_text_modal.add_text_from_image": "Pridėti tekstą iš vaizdo",
-  "alt_text_modal.cancel": "Atšaukti",
-  "alt_text_modal.change_thumbnail": "Keisti miniatiūrą",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Aprašykite tai klausos negalią turintiems asmenims…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Aprašykite tai regos sutrikimų turintiems asmenims…",
-  "alt_text_modal.done": "Atlikta",
   "announcement.announcement": "Skelbimas",
-  "annual_report.summary.archetype.booster": "Šaunus medžiotojas",
-  "annual_report.summary.archetype.lurker": "Stebėtojas",
-  "annual_report.summary.archetype.oracle": "Vydūnas",
-  "annual_report.summary.archetype.pollster": "Apklausos rengėjas",
-  "annual_report.summary.archetype.replier": "Socialinis drugelis",
-  "annual_report.summary.followers.followers": "sekėjai (-ų)",
-  "annual_report.summary.followers.total": "iš viso {count}",
-  "annual_report.summary.here_it_is": "Štai jūsų {year} apžvalga:",
-  "annual_report.summary.highlighted_post.by_favourites": "labiausiai pamėgtas įrašas",
-  "annual_report.summary.highlighted_post.by_reblogs": "labiausiai pasidalintas įrašas",
-  "annual_report.summary.highlighted_post.by_replies": "įrašas su daugiausiai atsakymų",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "labiausiai naudota programa",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "labiausiai naudotas saitažodis",
-  "annual_report.summary.most_used_hashtag.none": "Nieko",
-  "annual_report.summary.new_posts.new_posts": "nauji įrašai",
-  "annual_report.summary.percentile.text": "<topLabel>Tai reiškia, kad esate tarp</topLabel><percentage></percentage><bottomLabel>populiariausių {domain} naudotojų.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Mes nesakysime Bernie.",
-  "annual_report.summary.thanks": "Dėkojame, kad esate „Mastodon“ dalis!",
   "attachments_list.unprocessed": "(neapdorotas)",
   "audio.hide": "Slėpti garsą",
   "block_modal.remote_users_caveat": "Paprašysime serverio {domain} gerbti tavo sprendimą. Tačiau atitiktis negarantuojama, nes kai kurie serveriai gali skirtingai tvarkyti blokavimus. Vieši įrašai vis tiek gali būti matomi neprisijungusiems naudotojams.",
@@ -135,7 +110,7 @@
   "bundle_column_error.routing.body": "Paprašyto puslapio nepavyko rasti. Ar esi tikras (-a), kad adreso juostoje nurodytas URL adresas yra teisingas?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Uždaryti",
-  "bundle_modal_error.message": "Įkeliant šį ekraną kažkas nutiko ne taip.",
+  "bundle_modal_error.message": "Įkeliant šį komponentą kažkas nutiko ne taip.",
   "bundle_modal_error.retry": "Bandyti dar kartą",
   "closed_registrations.other_server_instructions": "Kadangi „Mastodon“ yra decentralizuotas, gali susikurti paskyrą kitame serveryje ir vis tiek bendrauti su šiuo serveriu.",
   "closed_registrations_modal.description": "Sukurti paskyrą serveryje {domain} šiuo metu neįmanoma, bet nepamiršk, kad norint naudotis „Mastodon“ nebūtina turėti paskyrą serveryje {domain}.",
@@ -146,16 +121,13 @@
   "column.blocks": "Užblokuoti naudotojai",
   "column.bookmarks": "Žymės",
   "column.community": "Vietinė laiko skalė",
-  "column.create_list": "Kurti sąrašą",
   "column.direct": "Privatūs paminėjimai",
   "column.directory": "Naršyti profilius",
   "column.domain_blocks": "Užblokuoti serveriai",
-  "column.edit_list": "Redaguoti sąrašą",
   "column.favourites": "Mėgstami",
   "column.firehose": "Tiesioginiai srautai",
   "column.follow_requests": "Sekimo prašymai",
   "column.home": "Pagrindinis",
-  "column.list_members": "Tvarkyti sąrašo narius",
   "column.lists": "Sąrašai",
   "column.mutes": "Nutildyti naudotojai",
   "column.notifications": "Pranešimai",
@@ -168,7 +140,6 @@
   "column_header.pin": "Prisegti",
   "column_header.show_settings": "Rodyti nustatymus",
   "column_header.unpin": "Atsegti",
-  "column_search.cancel": "Atšaukti",
   "column_subheading.settings": "Nustatymai",
   "community.column_settings.local_only": "Tik vietinis",
   "community.column_settings.media_only": "Tik medija",
@@ -187,7 +158,7 @@
   "compose_form.poll.duration": "Apklausos trukmė",
   "compose_form.poll.multiple": "Keli pasirinkimai",
   "compose_form.poll.option_placeholder": "{number} parinktis",
-  "compose_form.poll.single": "Vienas pasirinkimas",
+  "compose_form.poll.single": "Pasirinkti vieną",
   "compose_form.poll.switch_to_multiple": "Keisti apklausą, kad būtų leidžiama pasirinkti kelis pasirinkimus",
   "compose_form.poll.switch_to_single": "Keisti apklausą, kad būtų leidžiama pasirinkti vieną pasirinkimą",
   "compose_form.poll.type": "Stilius",
@@ -211,16 +182,9 @@
   "confirmations.edit.confirm": "Redaguoti",
   "confirmations.edit.message": "Redaguojant dabar, bus perrašyta šiuo metu kuriama žinutė. Ar tikrai nori tęsti?",
   "confirmations.edit.title": "Perrašyti įrašą?",
-  "confirmations.follow_to_list.confirm": "Sekti ir pridėti prie sąrašo",
-  "confirmations.follow_to_list.message": "Kad pridėtumėte juos į sąrašą, turite sekti {name}.",
-  "confirmations.follow_to_list.title": "Sekti naudotoją?",
   "confirmations.logout.confirm": "Atsijungti",
   "confirmations.logout.message": "Ar tikrai nori atsijungti?",
   "confirmations.logout.title": "Atsijungti?",
-  "confirmations.missing_alt_text.confirm": "Pridėti alternatyvųjį tekstą",
-  "confirmations.missing_alt_text.message": "Jūsų įrašas turi mediją be alternatyvaus teksto. Pridėjus aprašymus, jūsų turinys taps pasiekiamas daugeliui asmenų.",
-  "confirmations.missing_alt_text.secondary": "Siųsti vis tiek",
-  "confirmations.missing_alt_text.title": "Pridėti alternatyvųjį tekstą?",
   "confirmations.mute.confirm": "Nutildyti",
   "confirmations.redraft.confirm": "Ištrinti ir iš naujo parengti",
   "confirmations.redraft.message": "Ar tikrai nori ištrinti šį įrašą ir parengti jį iš naujo? Bus prarasti mėgstami ir pasidalinimai, o atsakymai į originalų įrašą bus panaikinti.",
@@ -249,6 +213,10 @@
   "disabled_account_banner.text": "Tavo paskyra {disabledAccount} šiuo metu išjungta.",
   "dismissable_banner.community_timeline": "Tai – naujausi vieši įrašai iš žmonių, kurių paskyros talpinamos {domain}.",
   "dismissable_banner.dismiss": "Atmesti",
+  "dismissable_banner.explore_links": "Tai – naujienos, kuriomis šiandien daugiausiai bendrinamasi socialiniame žiniatinklyje. Naujesnės naujienų istorijos, kurias paskelbė daugiau skirtingų žmonių, vertinamos aukščiau.",
+  "dismissable_banner.explore_statuses": "Tai – įrašai iš viso socialinio žiniatinklio, kurie šiandien sulaukia daug dėmesio. Naujesni įrašai, turintys daugiau pasidalinimų ir mėgstamų, vertinami aukščiau.",
+  "dismissable_banner.explore_tags": "Tai – saitažodžiai, kurie šiandien sulaukia daug dėmesio socialiniame žiniatinklyje. Saitažodžiai, kuriuos naudoja daugiau skirtingų žmonių, vertinami aukščiau.",
+  "dismissable_banner.public_timeline": "Tai – naujausi vieši įrašai iš žmonių socialiniame žiniatinklyje, kuriuos seka {domain} žmonės.",
   "domain_block_modal.block": "Blokuoti serverį",
   "domain_block_modal.block_account_instead": "Blokuoti @{name} vietoj to",
   "domain_block_modal.they_can_interact_with_old_posts": "Žmonės iš šio serverio gali bendrauti su tavo senomis įrašomis.",
@@ -305,6 +273,7 @@
   "empty_column.hashtag": "Nėra nieko šiame saitažodyje kol kas.",
   "empty_column.home": "Tavo pagrindinio laiko skalė tuščia. Sek daugiau žmonių, kad ją užpildytum.",
   "empty_column.list": "Nėra nieko šiame sąraše kol kas. Kai šio sąrašo nariai paskelbs naujų įrašų, jie bus rodomi čia.",
+  "empty_column.lists": "Dar neturi jokių sąrašų. Kai jį sukursi, jis bus rodomas čia.",
   "empty_column.mutes": "Dar nesi nutildęs (-usi) nė vieno naudotojo.",
   "empty_column.notification_requests": "Viskas švaru! Čia nieko nėra. Kai gausi naujų pranešimų, jie bus rodomi čia pagal tavo nustatymus.",
   "empty_column.notifications": "Dar neturi jokių pranešimų. Kai kiti žmonės su tavimi sąveikaus, matysi tai čia.",
@@ -315,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Pabandyk juos išjungti ir atnaujinti puslapį. Jei tai nepadeda, galbūt vis dar galėsi naudotis Mastodon per kitą naršyklę arba savąją programėlę.",
   "errors.unexpected_crash.copy_stacktrace": "Kopijuoti dėklo eigą į iškarpinę",
   "errors.unexpected_crash.report_issue": "Pranešti apie problemą",
+  "explore.search_results": "Paieškos rezultatai",
   "explore.suggested_follows": "Žmonės",
   "explore.title": "Naršyti",
   "explore.trending_links": "Naujienos",
@@ -364,14 +334,13 @@
   "footer.about": "Apie",
   "footer.directory": "Profilių katalogas",
   "footer.get_app": "Gauti programėlę",
+  "footer.invite": "Kviesti žmones",
   "footer.keyboard_shortcuts": "Spartieji klavišai",
   "footer.privacy_policy": "Privatumo politika",
   "footer.source_code": "Peržiūrėti šaltinio kodą",
   "footer.status": "Statusas",
-  "footer.terms_of_service": "Paslaugų sąlygos",
   "generic.saved": "Išsaugota",
   "getting_started.heading": "Kaip pradėti",
-  "hashtag.admin_moderation": "Atverti prižiūrėjimo sąsają saitažodžiui #{name}",
   "hashtag.column_header.tag_mode.all": "ir {additional}",
   "hashtag.column_header.tag_mode.any": "ar {additional}",
   "hashtag.column_header.tag_mode.none": "be {additional}",
@@ -413,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignoruoti pranešimus iš žmonių, kurie tave neseka?",
   "ignore_notifications_modal.not_following_title": "Ignoruoti pranešimus iš žmonių, kuriuos neseki?",
   "ignore_notifications_modal.private_mentions_title": "Ignoruoti pranešimus iš neprašytų privačių paminėjimų?",
-  "info_button.label": "Žinynas",
-  "info_button.what_is_alt_text": "<h1>Kas yra alternatyvusis tekstas?</h1> <p>Alternatyvusis tekstas pateikia vaizdų aprašymus asmenims su regos sutrikimais, turintiems mažo pralaidumo ryšį arba ieškantiems papildomo konteksto.</p> <p>Galite pagerinti prieinamumą ir suprantamumą visiems, jei parašysite aiškų, glaustą ir objektyvų alternatyvųjį tekstą.</p> <ul> <li>Užfiksuokite svarbiausius elementus.</li> <li>Apibendrinkite tekstą vaizduose.</li> <li>Naudokite įprasta sakinio struktūrą.</li> <li>Venkite nereikalingos informacijos.</li> <li>Sutelkite dėmesį į tendencijas ir pagrindines išvadas sudėtinguose vaizdiniuose (tokiuose kaip diagramos ar žemėlapiai).</li> </ul>",
-  "interaction_modal.action.favourite": "Kad tęstumėte, turite pamėgti iš savo paskyros.",
-  "interaction_modal.action.follow": "Kad tęstumėte, turite sekti iš savo paskyros.",
-  "interaction_modal.action.reblog": "Kad tęstumėte, turite pasidalinti iš savo paskyros.",
-  "interaction_modal.action.reply": "Kad tęstumėte, turite atsakyti iš savo paskyros.",
-  "interaction_modal.action.vote": "Kad tęstumėte, turite balsuoti iš savo paskyros.",
-  "interaction_modal.go": "Eiti",
-  "interaction_modal.no_account_yet": "Dar neturite paskyros?",
+  "interaction_modal.description.favourite": "Su „Mastodon“ paskyra galite pamėgti šį įrašą, kad autorius žinotų, jog vertinti tai ir išsaugoti jį vėliau.",
+  "interaction_modal.description.follow": "Su „Mastodon“ paskyra galite sekti {name}, kad gautumėte jų įrašus pagrindiniame sraute.",
+  "interaction_modal.description.reblog": "Su Mastodon paskyra gali pakelti šią įrašą ir pasidalyti juo su savo sekėjais.",
+  "interaction_modal.description.reply": "Su Mastodon paskyra gali atsakyti į šį įrašą.",
+  "interaction_modal.login.action": "Į pagrindinį puslapį",
+  "interaction_modal.login.prompt": "Tavo pagrindinio serverio domenas, pvz., mastodon.social.",
+  "interaction_modal.no_account_yet": "Nesi Mastodon?",
   "interaction_modal.on_another_server": "Kitame serveryje",
   "interaction_modal.on_this_server": "Šiame serveryje",
+  "interaction_modal.sign_in": "Nesi prisijungęs (-usi) prie šio serverio. Kur yra talpinama tavo paskyra?",
+  "interaction_modal.sign_in_hint": "Patarimas: tai svetainė, kurioje užsiregistravai. Jei neprisimeni, ieškok sveikinimo el. laiško savo pašto dėžutėje. Taip pat gali įvesti visą savo naudotojo vardą (pvz., @Mastodon@mastodon.social).",
   "interaction_modal.title.favourite": "Pamėgti {name} įrašą",
   "interaction_modal.title.follow": "Sekti {name}",
   "interaction_modal.title.reblog": "Pakelti {name} įrašą",
   "interaction_modal.title.reply": "Atsakyti į {name} įrašą",
-  "interaction_modal.title.vote": "Balsuoti {name} apklausoje",
-  "interaction_modal.username_prompt": "Pvz., {example}",
   "intervals.full.days": "{number, plural, one {# diena} few {# dienos} many {# dienos} other {# dienų}}",
   "intervals.full.hours": "{number, plural, one {# valanda} few {# valandos} many {# valandos} other {# valandų}}",
   "intervals.full.minutes": "{number, plural, one {# minutė} few {# minutes} many {# minutės} other {# minučių}}",
@@ -465,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Rodyti / slėpti tekstą po TĮ",
   "keyboard_shortcuts.toggle_sensitivity": "Rodyti / slėpti mediją",
   "keyboard_shortcuts.toot": "Pradėti naują įrašą",
-  "keyboard_shortcuts.translate": "išversti įrašą",
   "keyboard_shortcuts.unfocus": "Nebefokusuoti rengykles teksto sritį / paiešką",
   "keyboard_shortcuts.up": "Perkelti į viršų sąraše",
   "lightbox.close": "Uždaryti",
@@ -478,32 +444,20 @@
   "link_preview.author": "Sukūrė {name}",
   "link_preview.more_from_author": "Daugiau iš {name}",
   "link_preview.shares": "{count, plural, one {{counter} įrašas} few {{counter} įrašai} many {{counter} įrašo} other {{counter} įrašų}}",
-  "lists.add_member": "Pridėti",
-  "lists.add_to_list": "Pridėti į sąrašą",
-  "lists.add_to_lists": "Pridėti {name} į sąrašą",
-  "lists.create": "Kurti",
-  "lists.create_a_list_to_organize": "Sukurkite naują sąrašą, kad sutvarkytumėte pagrindinį srautą",
-  "lists.create_list": "Kurti sąrašą",
+  "lists.account.add": "Pridėti į sąrašą",
+  "lists.account.remove": "Pašalinti iš sąrašo",
   "lists.delete": "Ištrinti sąrašą",
-  "lists.done": "Atlikta",
   "lists.edit": "Redaguoti sąrašą",
-  "lists.exclusive": "Slėpti narius pagrindiniame",
-  "lists.exclusive_hint": "Jei kas nors yra šiame sąraše, paslėpkite juos pagrindinio srauto laiko skalėje, kad nematytumėte jų įrašus dukart.",
-  "lists.find_users_to_add": "Raskite naudotojų, kurių pridėti",
-  "lists.list_members": "Sąrašo nariai",
-  "lists.list_members_count": "{count, plural, one {# narys} few {# nariai} many {# nario} other {# narių}}",
-  "lists.list_name": "Sąrašo pavadinimas",
-  "lists.new_list_name": "Naujas sąrašo pavadinimas",
-  "lists.no_lists_yet": "Kol kas nėra sąrašų.",
-  "lists.no_members_yet": "Kol kas nėra narių.",
-  "lists.no_results_found": "Rezultatų nerasta.",
-  "lists.remove_member": "Šalinti",
+  "lists.edit.submit": "Keisti pavadinimą",
+  "lists.exclusive": "Slėpti šiuos įrašus iš pagrindinio",
+  "lists.new.create": "Pridėti sąrašą",
+  "lists.new.title_placeholder": "Naujas sąrašo pavadinimas",
   "lists.replies_policy.followed": "Bet kuriam sekamam naudotojui",
   "lists.replies_policy.list": "Sąrašo nariams",
   "lists.replies_policy.none": "Nei vienam",
-  "lists.save": "Išsaugoti",
-  "lists.search": "Ieškoti",
-  "lists.show_replies_to": "Įtraukti atsakymus iš sąrašo narių į",
+  "lists.replies_policy.title": "Rodyti atsakymus:",
+  "lists.search": "Ieškoti tarp sekamų žmonių",
+  "lists.subheading": "Tavo sąrašai",
   "load_pending": "{count, plural, one {# naujas elementas} few {# nauji elementai} many {# naujo elemento} other {# naujų elementų}}",
   "loading_indicator.label": "Kraunama…",
   "media_gallery.hide": "Slėpti",
@@ -552,10 +506,7 @@
   "notification.admin.report_statuses_other": "{name} pranešė {target}",
   "notification.admin.sign_up": "{name} užsiregistravo",
   "notification.admin.sign_up.name_and_others": "{name} ir {count, plural, one {# kitas} few {# kiti} many {# kito} other {# kitų}} užsiregistravo",
-  "notification.annual_report.message": "Jūsų laukia {year} #Wrapstodon! Atskleiskite savo metų svarbiausius įvykius ir įsimintinas akimirkas platformoje „Mastodon“.",
-  "notification.annual_report.view": "Peržiūrėti #Wrapstodon",
   "notification.favourite": "{name} pamėgo tavo įrašą",
-  "notification.favourite_pm": "{name} pamėgo jūsų privatų paminėjimą",
   "notification.follow": "{name} seka tave",
   "notification.follow.name_and_others": "{name} ir <a>{count, plural, one {# kitas} few {# kiti} many {# kito} other {# kitų}}</a> seka tave",
   "notification.follow_request": "{name} paprašė tave sekti",
@@ -653,21 +604,44 @@
   "notifications_permission_banner.enable": "Įjungti darbalaukio pranešimus",
   "notifications_permission_banner.how_to_control": "Jei nori gauti pranešimus, kai Mastodon nėra atidarytas, įjunk darbalaukio pranešimus. Įjungęs (-usi) darbalaukio pranešimus, gali tiksliai valdyti, kokių tipų sąveikos generuoja darbalaukio pranešimus, naudojant pirmiau esančiu mygtuku {icon}.",
   "notifications_permission_banner.title": "Niekada nieko nepraleisk",
-  "onboarding.follows.back": "Atgal",
-  "onboarding.follows.done": "Atlikta",
+  "onboarding.action.back": "Grąžinti mane atgal",
+  "onboarding.actions.back": "Grąžinti mane atgal",
+  "onboarding.actions.go_to_explore": "Į tendencijų puslapį",
+  "onboarding.actions.go_to_home": "Į mano pagrindinį srautų puslapį",
+  "onboarding.compose.template": "Sveiki #Mastodon!",
   "onboarding.follows.empty": "Deja, šiuo metu jokių rezultatų parodyti negalima. Gali pabandyti naudoti paiešką arba naršyti atradimo puslapį, kad surastum žmonių, kuriuos nori sekti, arba bandyti vėliau.",
-  "onboarding.follows.search": "Ieškoti",
-  "onboarding.follows.title": "Sekite asmenis, kad pradėtumėte",
+  "onboarding.follows.lead": "Tavo pagrindinis srautas – pagrindinis būdas patirti Mastodon. Kuo daugiau žmonių seksi, tuo jis bus aktyvesnis ir įdomesnis. Norint pradėti, pateikiame keletą pasiūlymų:",
+  "onboarding.follows.title": "Suasmenink savo pagrindinį srautą",
   "onboarding.profile.discoverable": "Padaryti mano profilį atrandamą",
   "onboarding.profile.discoverable_hint": "Kai sutinki su Mastodon atrandamumu, tavo įrašai gali būti rodomi paieškos rezultatuose ir tendencijose, o profilis gali būti siūlomas panašių pomėgių turintiems žmonėms.",
   "onboarding.profile.display_name": "Rodomas vardas",
   "onboarding.profile.display_name_hint": "Tavo pilnas vardas arba linksmas vardas…",
+  "onboarding.profile.lead": "Gali visada tai užbaigti vėliau nustatymuose, kur yra dar daugiau pritaikymo parinkčių.",
   "onboarding.profile.note": "Biografija",
   "onboarding.profile.note_hint": "Gali @paminėti kitus žmones arba #saitažodžius…",
   "onboarding.profile.save_and_continue": "Išsaugoti ir tęsti",
   "onboarding.profile.title": "Profilio sąranka",
   "onboarding.profile.upload_avatar": "Įkelti profilio nuotrauką",
   "onboarding.profile.upload_header": "Įkelti profilio antraštę",
+  "onboarding.share.lead": "Leisk žmonėms sužinoti, kaip tave rasti Mastodon!",
+  "onboarding.share.message": "Aš {username}, esant #Mastodon! Ateik sekti manęs adresu {url}.",
+  "onboarding.share.next_steps": "Galimi kiti žingsniai:",
+  "onboarding.share.title": "Bendrink savo profilį",
+  "onboarding.start.lead": "Dabar esi Mastodon dalis – unikalios decentralizuotos socialinės medijos platformos, kurioje tu, o ne algoritmas, pats nustatai savo patirtį. Pradėkime tavo kelionę šioje naujoje socialinėje erdvėje:",
+  "onboarding.start.skip": "Nereikia pagalbos pradėti?",
+  "onboarding.start.title": "Tau pavyko!",
+  "onboarding.steps.follow_people.body": "Sekti įdomius žmones – tai, kas yra Mastodon.",
+  "onboarding.steps.follow_people.title": "Suasmenink savo pagrindinį srautą",
+  "onboarding.steps.publish_status.body": "Sakyk labas pasauliui tekstu, nuotraukomis, vaizdo įrašais arba apklausomis {emoji}.",
+  "onboarding.steps.publish_status.title": "Sukūrk savo pirmąjį įrašą",
+  "onboarding.steps.setup_profile.body": "Padidink savo sąveiką turint išsamų profilį.",
+  "onboarding.steps.setup_profile.title": "Suasmenink savo profilį",
+  "onboarding.steps.share_profile.body": "Leisk draugams sužinoti, kaip tave rasti Mastodon.",
+  "onboarding.steps.share_profile.title": "Bendrink savo Mastodon profilį",
+  "onboarding.tips.2fa": "<strong>Ar žinojai?</strong> Savo paskyrą gali apsaugoti nustatant dviejų veiksnių tapatybės nustatymą paskyros nustatymuose. Jis veikia su bet kuria pasirinkta TOTP programėle, telefono numeris nebūtinas.",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Ar žinojai?</strong> Kadangi Mastodon decentralizuotas, kai kurie profiliai, su kuriais susidursi, bus talpinami ne tavo, o kituose serveriuose. Ir vis tiek galėsi su jais sklandžiai bendrauti! Jų serveris yra antroje naudotojo vardo pusėje.",
+  "onboarding.tips.migration": "<strong>Ar žinojai?</strong> Jei manai, kad {domain} serveris ateityje tau netiks, gali persikelti į kitą „Mastodon“ serverį neprarandant savo sekėjų. Gali net talpinti savo paties serverį!",
+  "onboarding.tips.verification": "<strong>Ar žinojai?</strong> Savo paskyrą gali patvirtinti pateikęs (-usi) nuorodą į Mastodon profilį savo interneto svetainėje ir pridėjęs (-usi) svetainę prie savo profilio. Nereikia jokių mokesčių ar dokumentų.",
   "password_confirmation.exceeds_maxlength": "Slaptažodžio patvirtinimas viršija maksimalų slaptažodžio ilgį.",
   "password_confirmation.mismatching": "Slaptažodžio patvirtinimas nesutampa.",
   "picture_in_picture.restore": "Padėti jį atgal",
@@ -683,7 +657,7 @@
   "poll_button.remove_poll": "Pašalinti apklausą",
   "privacy.change": "Keisti įrašo privatumą",
   "privacy.direct.long": "Visus, paminėtus įraše",
-  "privacy.direct.short": "Privatus paminėjimas",
+  "privacy.direct.short": "Konkretūs žmonės",
   "privacy.private.long": "Tik sekėjams",
   "privacy.private.short": "Sekėjai",
   "privacy.public.long": "Bet kas iš Mastodon ir ne Mastodon",
@@ -695,8 +669,8 @@
   "privacy_policy.title": "Privatumo politika",
   "recommended": "Rekomenduojama",
   "refresh": "Atnaujinti",
-  "regeneration_indicator.please_stand_by": "Laukite.",
-  "regeneration_indicator.preparing_your_home_feed": "Ruošimas jūsų pagrindinis srautas…",
+  "regeneration_indicator.label": "Įkeliama…",
+  "regeneration_indicator.sublabel": "Ruošiamas jūsų pagrindinis srautas!",
   "relative_time.days": "{number} d.",
   "relative_time.full.days": "prieš {number, plural, one {# dieną} few {# dienas} many {# dienos} other {# dienų}}",
   "relative_time.full.hours": "prieš {number, plural, one {# valandą} few {# valandas} many {# valandos} other {# valandų}}",
@@ -780,8 +754,7 @@
   "search_results.accounts": "Profiliai",
   "search_results.all": "Visi",
   "search_results.hashtags": "Saitažodžiai",
-  "search_results.no_results": "Nėra rezultatų.",
-  "search_results.no_search_yet": "Pabandykite ieškoti įrašų, profilių arba saitažodžių.",
+  "search_results.nothing_found": "Nepavyko rasti nieko pagal šiuos paieškos terminus.",
   "search_results.see_all": "Žiūrėti viską",
   "search_results.statuses": "Įrašai",
   "search_results.title": "Paieška užklausai „{q}“",
@@ -834,7 +807,6 @@
   "status.reblogs.empty": "Šio įrašo dar niekas nepakėlė. Kai kas nors tai padarys, jie bus rodomi čia.",
   "status.redraft": "Ištrinti ir parengti iš naujo",
   "status.remove_bookmark": "Pašalinti žymę",
-  "status.remove_favourite": "Šalinti iš mėgstamų",
   "status.replied_in_thread": "Atsakyta gijoje",
   "status.replied_to": "Atsakyta į {name}",
   "status.reply": "Atsakyti",
@@ -856,9 +828,6 @@
   "subscribed_languages.target": "Keisti prenumeruojamas kalbas {target}",
   "tabs_bar.home": "Pagrindinis",
   "tabs_bar.notifications": "Pranešimai",
-  "terms_of_service.effective_as_of": "Įsigaliojo nuo {date}.",
-  "terms_of_service.title": "Paslaugų sąlygos",
-  "terms_of_service.upcoming_changes_on": "Numatomi pakeitimai {date}.",
   "time_remaining.days": "liko {number, plural, one {# diena} few {# dienos} many {# dienos} other {# dienų}}",
   "time_remaining.hours": "liko {number, plural, one {# valanda} few {# valandos} many {# valandos} other {# valandų}}",
   "time_remaining.minutes": "liko {number, plural, one {# minutė} few {# minutės} many {# minutės} other {# minučių}}",
@@ -874,12 +843,26 @@
   "upload_button.label": "Pridėti vaizdų, vaizdo įrašą arba garso failą",
   "upload_error.limit": "Viršyta failo įkėlimo riba.",
   "upload_error.poll": "Failų įkėlimas neleidžiamas su apklausomis.",
+  "upload_form.audio_description": "Aprašyk žmonėms, kurie yra kurtieji ar neprigirdintys.",
+  "upload_form.description": "Aprašyk žmonėms, kurie yra aklieji arba silpnaregiai.",
   "upload_form.drag_and_drop.instructions": "Kad paimtum medijos priedą, paspausk tarpo arba įvedimo klavišą. Tempant naudok rodyklių klavišus, kad perkeltum medijos priedą bet kuria kryptimi. Dar kartą paspausk tarpo arba įvedimo klavišą, kad nuleistum medijos priedą naujoje vietoje, arba paspausk grįžimo klavišą, kad atšauktum.",
   "upload_form.drag_and_drop.on_drag_cancel": "Nutempimas buvo atšauktas. Medijos priedas {item} buvo nuleistas.",
   "upload_form.drag_and_drop.on_drag_end": "Medijos priedas {item} buvo nuleistas.",
   "upload_form.drag_and_drop.on_drag_over": "Medijos priedas {item} buvo perkeltas.",
   "upload_form.drag_and_drop.on_drag_start": "Paimtas medijos priedas {item}.",
   "upload_form.edit": "Redaguoti",
+  "upload_form.thumbnail": "Keisti miniatiūrą",
+  "upload_form.video_description": "Aprašyk žmonėms, kurie yra kurtieji, neprigirdintys, aklieji ar silpnaregiai.",
+  "upload_modal.analyzing_picture": "Analizuojamas vaizdas…",
+  "upload_modal.apply": "Taikyti",
+  "upload_modal.applying": "Pritaikoma…",
+  "upload_modal.choose_image": "Pasirinkti vaizdą",
+  "upload_modal.description_placeholder": "Greita rudoji lapė peršoka tinginį šunį",
+  "upload_modal.detect_text": "Aptikti tekstą iš nuotraukos",
+  "upload_modal.edit_media": "Redaguoti mediją",
+  "upload_modal.hint": "Spustelėk arba nuvilk apskritimą peržiūroje, kad pasirinktum centrinį tašką, kuris visada bus matomas visose miniatiūrose.",
+  "upload_modal.preparing_ocr": "Rengimas OCR…",
+  "upload_modal.preview_label": "Peržiūra ({ratio})",
   "upload_progress.label": "Įkeliama...",
   "upload_progress.processing": "Apdorojama…",
   "username.taken": "Šis naudotojo vardas užimtas. Pabandyk kitą.",
@@ -889,6 +872,8 @@
   "video.expand": "Išplėsti vaizdo įrašą",
   "video.fullscreen": "Visas ekranas",
   "video.hide": "Slėpti vaizdo įrašą",
+  "video.mute": "Išjungti garsą",
   "video.pause": "Pristabdyti",
-  "video.play": "Leisti"
+  "video.play": "Leisti",
+  "video.unmute": "Įjungti garsą"
 }
diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json
index 27760c59ff..cbc0cbe2d4 100644
--- a/app/javascript/mastodon/locales/lv.json
+++ b/app/javascript/mastodon/locales/lv.json
@@ -3,7 +3,7 @@
   "about.contact": "Kontakts:",
   "about.disclaimer": "Mastodon ir bezmaksas atklātā pirmkoda programmatūra un Mastodon gGmbH preču zīme.",
   "about.domain_blocks.no_reason_available": "Iemesls nav norādīts",
-  "about.domain_blocks.preamble": "Mastodon parasti ļauj apskatīt saturu un mijiedarboties ar lietotājiem no jebkura cita fediversa servera. Šie ir izņēmumi, kas veikti tieši šajā serverī.",
+  "about.domain_blocks.preamble": "Mastodon parasti ļauj apskatīt saturu un mijiedarboties ar lietotājiem no jebkura cita federācijas servera. Šie ir izņēmumi, kas veikti šajā konkrētajā serverī.",
   "about.domain_blocks.silenced.explanation": "Parasti tu neredzēsi profilus un saturu no šī servera, ja vien tu nepārprotami izvēlēsies to pārskatīt vai sekot.",
   "about.domain_blocks.silenced.title": "Ierobežotie",
   "about.domain_blocks.suspended.explanation": "Nekādi dati no šī servera netiks apstrādāti, uzglabāti vai apmainīti, padarot neiespējamu mijiedarbību vai saziņu ar lietotājiem no šī servera.",
@@ -29,6 +29,7 @@
   "account.endorse": "Izcelts profilā",
   "account.featured_tags.last_status_at": "Beidzamā ziņa {date}",
   "account.featured_tags.last_status_never": "Ierakstu nav",
+  "account.featured_tags.title": "{name} izceltie tēmturi",
   "account.follow": "Sekot",
   "account.follow_back": "Sekot atpakaļ",
   "account.followers": "Sekotāji",
@@ -53,7 +54,7 @@
   "account.muted": "Apklusināts",
   "account.mutual": "Abpusēji",
   "account.no_bio": "Apraksts nav sniegts.",
-  "account.open_original_page": "Atvērt pirmavota lapu",
+  "account.open_original_page": "Atvērt oriģinālo lapu",
   "account.posts": "Ieraksti",
   "account.posts_with_replies": "Ieraksti un atbildes",
   "account.report": "Sūdzēties par @{name}",
@@ -85,29 +86,7 @@
   "alert.unexpected.message": "Radās negaidīta kļūda.",
   "alert.unexpected.title": "Ups!",
   "alt_text_badge.title": "Alt teksts",
-  "alt_text_modal.add_text_from_image": "Pievienot tekstu no attēla",
-  "alt_text_modal.cancel": "Atcelt",
-  "alt_text_modal.change_thumbnail": "Nomainīt sīktēlu",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Aprakstīt šo cilvēkiem ar dzirdes traucējumiem…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Aprakstīt šo cilvēkiem ar redzes traucējumiem…",
-  "alt_text_modal.done": "Gatavs",
   "announcement.announcement": "Paziņojums",
-  "annual_report.summary.archetype.oracle": "Orākuls",
-  "annual_report.summary.archetype.replier": "Sabiedriskais tauriņš",
-  "annual_report.summary.followers.followers": "sekotāji",
-  "annual_report.summary.followers.total": "pavisam {count}",
-  "annual_report.summary.here_it_is": "Šeit ir {year}. gada pārskats:",
-  "annual_report.summary.highlighted_post.by_favourites": "izlasēs visvairāk ievietotais ieraksts",
-  "annual_report.summary.highlighted_post.by_reblogs": "vispastiprinātākais ieraksts",
-  "annual_report.summary.highlighted_post.by_replies": "ieraksts ar vislielāko atbilžu skaitu",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "visizmantotākā lietotne",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "visizmantotākais tēmturis",
-  "annual_report.summary.most_used_hashtag.none": "Nav",
-  "annual_report.summary.new_posts.new_posts": "jauni ieraksti",
-  "annual_report.summary.percentile.text": "<topLabel>Tas ievieto Tevi virsējos</topLabel><percentage></percentage><bottomLabel>no {domain} lietotājiem.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Mēs neteiksim Bērnijam.",
-  "annual_report.summary.thanks": "Paldies, ka esi daļa no Mastodon!",
   "attachments_list.unprocessed": "(neapstrādāti)",
   "audio.hide": "Slēpt audio",
   "block_modal.remote_users_caveat": "Mēs vaicāsim serverim {domain} ņemt vērā Tavu lēmumu. Tomēr atbilstība nav nodrošināta, jo atsevišķi serveri var apstrādāt bloķēšanu citādi. Publiski ieraksti joprojām var būt redzami lietotājiem, kuri nav pieteikušies.",
@@ -129,6 +108,7 @@
   "bundle_column_error.routing.body": "Pieprasīto lapu nevarēja atrast. Vai esi pārliecināts, ka URL adreses joslā ir pareizs?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Aizvērt",
+  "bundle_modal_error.message": "Kaut kas nogāja greizi šīs sastāvdaļas ielādēšanas laikā.",
   "bundle_modal_error.retry": "Mēģināt vēlreiz",
   "closed_registrations.other_server_instructions": "Tā kā Mastodon ir decentralizēts, tu vari izveidot kontu citā serverī un joprojām mijiedarboties ar šo.",
   "closed_registrations_modal.description": "Pašlaik nav iespējams izveidot kontu {domain}, bet, lūdzu, ņem vērā, ka Tev nav nepieciešams tieši {domain} konts, lai lietotu Mastodon!",
@@ -139,16 +119,13 @@
   "column.blocks": "Bloķētie lietotāji",
   "column.bookmarks": "Grāmatzīmes",
   "column.community": "Vietējā laika līnija",
-  "column.create_list": "Izveidot sarakstu",
   "column.direct": "Privātas pieminēšanas",
   "column.directory": "Pārlūkot profilus",
   "column.domain_blocks": "Bloķētie domēni",
-  "column.edit_list": "Labot sarakstu",
   "column.favourites": "Iecienītie",
   "column.firehose": "Tiešraides plūsmas",
   "column.follow_requests": "Sekošanas pieprasījumi",
   "column.home": "Sākums",
-  "column.list_members": "Pārvaldīt saraksta dalībniekus",
   "column.lists": "Saraksti",
   "column.mutes": "Apklusinātie lietotāji",
   "column.notifications": "Paziņojumi",
@@ -161,7 +138,6 @@
   "column_header.pin": "Piespraust",
   "column_header.show_settings": "Rādīt iestatījumus",
   "column_header.unpin": "Atspraust",
-  "column_search.cancel": "Atcelt",
   "column_subheading.settings": "Iestatījumi",
   "community.column_settings.local_only": "Tikai vietējie",
   "community.column_settings.media_only": "Tikai multivide",
@@ -172,7 +148,7 @@
   "compose.published.open": "Atvērt",
   "compose.saved.body": "Ziņa saglabāta.",
   "compose_form.direct_message_warning_learn_more": "Uzzināt vairāk",
-  "compose_form.encryption_warning": "Mastodon ieraksti nav pilnībā šifrēti. Nedalies ar jebkādu jūtīgu informāciju caur Mastodon!",
+  "compose_form.encryption_warning": "Mastodon ieraksti nav pilnībā šifrēti. Nedalies ar jebkādu jutīgu informāciju caur Mastodon!",
   "compose_form.hashtag_warning": "Šis ieraksts netiks uzrādīts nevienā tēmturī, jo tas nav redzams visiem. Tikai visiem redzamos ierakstus var meklēt pēc tēmtura.",
   "compose_form.lock_disclaimer": "Tavs konts nav {locked}. Ikviens var Tev sekot, lai redzētu tikai sekotājiem paredzētos ierakstus.",
   "compose_form.lock_disclaimer.lock": "slēgts",
@@ -180,7 +156,7 @@
   "compose_form.poll.duration": "Aptaujas ilgums",
   "compose_form.poll.multiple": "Vairākas izvēles iespējas",
   "compose_form.poll.option_placeholder": "Izvēle {number}",
-  "compose_form.poll.single": "Viena izvēle",
+  "compose_form.poll.single": "Jāizvēlas viens",
   "compose_form.poll.switch_to_multiple": "Mainīt aptaujas veidu, lai atļautu vairākas izvēles",
   "compose_form.poll.switch_to_single": "Mainīt aptaujas veidu, lai atļautu vienu izvēli",
   "compose_form.poll.type": "Stils",
@@ -204,16 +180,12 @@
   "confirmations.edit.confirm": "Labot",
   "confirmations.edit.message": "Labošana pārrakstīs ziņojumu, kas šobrīd tiek sastādīts. Vai tiešām turpināt?",
   "confirmations.edit.title": "Pārrakstīt ierakstu?",
-  "confirmations.follow_to_list.confirm": "Sekot un pievienot sarakstam",
-  "confirmations.follow_to_list.message": "Ir jāseko {name}, lai pievienotu sarakstam.",
-  "confirmations.follow_to_list.title": "Sekot lietotājam?",
   "confirmations.logout.confirm": "Iziet",
   "confirmations.logout.message": "Vai tiešām vēlies izrakstīties?",
   "confirmations.logout.title": "Atteikties?",
-  "confirmations.missing_alt_text.secondary": "Vienalga iesūtīt",
   "confirmations.mute.confirm": "Apklusināt",
   "confirmations.redraft.confirm": "Dzēst un pārrakstīt",
-  "confirmations.redraft.message": "Vai tiešām vēlies izdzēst šo ierakstu un veidot jaunu tā uzmetumu? Pievienošana izlasēs un pastiprinājumi tiks zaudēti, un sākotnējā ieraksta atbildes paliks bez saiknes ar to.",
+  "confirmations.redraft.message": "Vai tiešām vēlies dzēst šo ziņu un no jauna noformēt to? Izlase un pastiprinājumi tiks zaudēti, un atbildes uz sākotnējo ziņu tiks atstātas bez autoratlīdzības.",
   "confirmations.redraft.title": "Dzēst un rakstīt vēlreiz?",
   "confirmations.reply.confirm": "Atbildēt",
   "confirmations.reply.message": "Tūlītēja atbildēšana pārrakstīs pašlaik sastādīto ziņu. Vai tiešām turpināt?",
@@ -231,7 +203,7 @@
   "copy_icon_button.copied": "Ievietots starpliktuvē",
   "copypaste.copied": "Nokopēts",
   "copypaste.copy_to_clipboard": "Kopēt uz starpliktuvi",
-  "directory.federated": "No zināma fediversa",
+  "directory.federated": "No pazīstamas federācijas",
   "directory.local": "Tikai no {domain}",
   "directory.new_arrivals": "Jaunpienācēji",
   "directory.recently_active": "Nesen aktīvi",
@@ -239,6 +211,10 @@
   "disabled_account_banner.text": "Tavs konts {disabledAccount} pašlaik ir atspējots.",
   "dismissable_banner.community_timeline": "Šie ir jaunākie publiskie ieraksti no cilvēkiem, kuru konti ir mitināti {domain}.",
   "dismissable_banner.dismiss": "Atcelt",
+  "dismissable_banner.explore_links": "Par šiem jaunumiem šobrīd runā cilvēki šajā un citos decentralizētā tīkla serveros.",
+  "dismissable_banner.explore_statuses": "Šie ir ieraksti, kas šodien gūst arvien lielāku ievērību visā sociālajā tīklā. Augstāk tiek kārtoti jaunāki ieraksti, kuri tiek vairāk pastiprināti un ievietoti izlasēs.",
+  "dismissable_banner.explore_tags": "Šie ir tēmturi, kas šodien gūst uzmanību sabiedriskajā tīmeklī. Tēmturi, kurus izmanto vairāk dažādu cilvēku, tiek vērtēti augstāk.",
+  "dismissable_banner.public_timeline": "Šie ir jaunākie publiskie ieraksti no lietotājiem sociālajā tīmeklī, kuriem {domain} seko cilvēki.",
   "domain_block_modal.block": "Bloķēt serveri",
   "domain_block_modal.block_account_instead": "Tā vietā liegt @{name}",
   "domain_block_modal.they_cant_follow": "Neviens šajā serverī nevar Tev sekot.",
@@ -280,6 +256,7 @@
   "empty_column.hashtag": "Ar šo tēmturi nekas nav atrodams.",
   "empty_column.home": "Tava mājas laikjosla ir tukša. Seko vairāk cilvēkiem, lai to piepildītu!",
   "empty_column.list": "Pagaidām šajā sarakstā nekā nav. Kad šī saraksta dalībnieki ievietos jaunus ierakstus, tie parādīsies šeit.",
+  "empty_column.lists": "Pašlaik Tev nav neviena saraksta. Kad tādu izveidosi, tas parādīsies šeit.",
   "empty_column.mutes": "Neviens lietotājs vēl nav apklusināts.",
   "empty_column.notifications": "Tev vēl nav paziņojumu. Kad citi cilvēki ar Tevi mijiedarbosies, Tu to redzēsi šeit.",
   "empty_column.public": "Šeit nekā nav. Ieraksti kaut ko publiski vai seko lietotājiem no citiem serveriem, lai iegūtu saturu",
@@ -289,6 +266,7 @@
   "error.unexpected_crash.next_steps_addons": "Mēģini tos atspējot un atsvaidzināt lapu. Ja tas nepalīdz, iespējams, varēsi lietot Mastodon, izmantojot citu pārlūkprogrammu vai lietotni.",
   "errors.unexpected_crash.copy_stacktrace": "Kopēt stacktrace uz starpliktuvi",
   "errors.unexpected_crash.report_issue": "Ziņot par problēmu",
+  "explore.search_results": "Meklēšanas rezultāti",
   "explore.suggested_follows": "Cilvēki",
   "explore.title": "Izpētīt",
   "explore.trending_links": "Jaunumi",
@@ -310,7 +288,6 @@
   "filter_modal.select_filter.subtitle": "Izmanto esošu kategoriju vai izveido jaunu",
   "filter_modal.select_filter.title": "Filtrēt šo ziņu",
   "filter_modal.title.status": "Filtrēt ziņu",
-  "filtered_notifications_banner.title": "Filtrētie paziņojumi",
   "firehose.all": "Visi",
   "firehose.local": "Šis serveris",
   "firehose.remote": "Citi serveri",
@@ -321,8 +298,6 @@
   "follow_suggestions.dismiss": "Vairs nerādīt",
   "follow_suggestions.friends_of_friends_longer": "Populārs to cilvēku vidū, kuriem tu seko",
   "follow_suggestions.personalized_suggestion": "Pielāgots ieteikums",
-  "follow_suggestions.popular_suggestion": "Populārs ieteikums",
-  "follow_suggestions.popular_suggestion_longer": "Populārs {domain}",
   "follow_suggestions.similar_to_recently_followed_longer": "Līdzīgi profieliem, kuriem nesen sāki sekot",
   "follow_suggestions.view_all": "Skatīt visu",
   "follow_suggestions.who_to_follow": "Kam sekot",
@@ -330,14 +305,13 @@
   "footer.about": "Par",
   "footer.directory": "Profilu direktorija",
   "footer.get_app": "Iegūt lietotni",
+  "footer.invite": "Uzaicināt cilvēkus",
   "footer.keyboard_shortcuts": "Īsinājumtaustiņi",
   "footer.privacy_policy": "Privātuma politika",
   "footer.source_code": "Skatīt pirmkodu",
   "footer.status": "Statuss",
-  "footer.terms_of_service": "Pakalpojuma noteikumi",
   "generic.saved": "Saglabāts",
   "getting_started.heading": "Darba sākšana",
-  "hashtag.admin_moderation": "Atvērt #{name} satura pārraudzības saskarni",
   "hashtag.column_header.tag_mode.all": "un {additional}",
   "hashtag.column_header.tag_mode.any": "vai {additional}",
   "hashtag.column_header.tag_mode.none": "bez {additional}",
@@ -366,8 +340,17 @@
   "home.pending_critical_update.title": "Ir pieejams būtisks drošības atjauninājums.",
   "home.show_announcements": "Rādīt paziņojumus",
   "ignore_notifications_modal.ignore": "Neņemt vērā paziņojumus",
+  "interaction_modal.description.favourite": "Ar Mastodon kontu tu vari pievienot šo ziņu izlasei, lai informētu autoru, ka to novērtē, un saglabātu to vēlākai lasīšanai.",
+  "interaction_modal.description.follow": "Ar Mastodon kontu Tu vari sekot {name}, lai saņemtu lietotāja ierakstus savā mājas plūsmā.",
+  "interaction_modal.description.reblog": "Ar Mastodon kontu Tu vari izvirzīt šo ierakstu, lai kopīgotu to ar saviem sekotājiem.",
+  "interaction_modal.description.reply": "Ar Mastodon kontu tu vari atbildēt uz šo ziņu.",
+  "interaction_modal.login.action": "Nogādāt mani mājās",
+  "interaction_modal.login.prompt": "Tavas mājvietas servera domēns, piem., mastodon.social",
+  "interaction_modal.no_account_yet": "Neesi Mastodon?",
   "interaction_modal.on_another_server": "Citā serverī",
   "interaction_modal.on_this_server": "Šajā serverī",
+  "interaction_modal.sign_in": "Tu neesi pieteicies šajā serverī. Kur tiek mitināts Tavs konts?",
+  "interaction_modal.sign_in_hint": "Padoms: tā ir tīmekļvietne, kurā Tu reģistrējies. Ja neatceries, jāmeklē sveiciena e-pasts savā iesūtnē. Vari arī ievadīt pilnu lietotājvārdu (piem., @Mastodon@mastodon.social).",
   "interaction_modal.title.favourite": "Pievienot {name} ziņu izlasei",
   "interaction_modal.title.follow": "Sekot {name}",
   "interaction_modal.title.reblog": "Pastiprināt {name} ierakstu",
@@ -416,18 +399,20 @@
   "limited_account_hint.title": "{domain} moderatori ir paslēpuši šo profilu.",
   "link_preview.author": "No {name}",
   "link_preview.more_from_author": "Vairāk no {name}",
-  "lists.add_member": "Pievienot",
-  "lists.add_to_list": "Pievienot sarakstam",
-  "lists.create": "Izveidot",
-  "lists.create_list": "Izveidot sarakstu",
+  "lists.account.add": "Pievienot sarakstam",
+  "lists.account.remove": "Noņemt no saraksta",
   "lists.delete": "Izdzēst sarakstu",
-  "lists.done": "Gatavs",
   "lists.edit": "Labot sarakstu",
-  "lists.remove_member": "Noņemt",
+  "lists.edit.submit": "Mainīt virsrakstu",
+  "lists.exclusive": "Nerādīt šos ierakstus sākumā",
+  "lists.new.create": "Pievienot sarakstu",
+  "lists.new.title_placeholder": "Jaunā saraksta nosaukums",
   "lists.replies_policy.followed": "Jebkuram sekotajam lietotājam",
   "lists.replies_policy.list": "Saraksta dalībniekiem",
   "lists.replies_policy.none": "Nevienam",
-  "lists.save": "Saglabāt",
+  "lists.replies_policy.title": "Rādīt atbildes:",
+  "lists.search": "Meklēt starp cilvēkiem, kuriem tu seko",
+  "lists.subheading": "Tavi saraksti",
   "load_pending": "{count, plural, zero{# jaunu vienumu} one {# jauns vienums} other {# jauni vienumi}}",
   "loading_indicator.label": "Ielādē…",
   "media_gallery.hide": "Paslēpt",
@@ -473,9 +458,9 @@
   "notification.moderation_warning": "Ir saņemts satura pārraudzības brīdinājums",
   "notification.moderation_warning.action_delete_statuses": "Daži no Taviem ierakstiem tika noņemti.",
   "notification.moderation_warning.action_disable": "Tavs konts tika atspējots.",
-  "notification.moderation_warning.action_mark_statuses_as_sensitive": "Daži no Taviem ierakstiem tika atzīmēti kā jūtīgi.",
+  "notification.moderation_warning.action_mark_statuses_as_sensitive": "Daži no Taviem ierakstiem tika atzīmēti kā jutīgi.",
   "notification.moderation_warning.action_none": "Konts ir saņēmis satura pārraudzības brīdinājumu.",
-  "notification.moderation_warning.action_sensitive": "Tavi ieraksti turpmāk tiks atzīmēti kā jūtīgi.",
+  "notification.moderation_warning.action_sensitive": "Tavi ieraksti turpmāk tiks atzīmēti kā jutīgi.",
   "notification.moderation_warning.action_silence": "Tavs konts tika ierobežots.",
   "notification.moderation_warning.action_suspend": "Tava konta darbība tika apturēta.",
   "notification.own_poll": "Tava aptauja ir noslēgusies",
@@ -500,7 +485,6 @@
   "notifications.column_settings.filter_bar.category": "Atrās atlasīšanas josla",
   "notifications.column_settings.follow": "Jauni sekotāji:",
   "notifications.column_settings.follow_request": "Jauni sekošanas pieprasījumi:",
-  "notifications.column_settings.group": "Grupēt",
   "notifications.column_settings.mention": "Pieminēšanas:",
   "notifications.column_settings.poll": "Aptaujas rezultāti:",
   "notifications.column_settings.push": "Uznirstošie paziņojumi",
@@ -525,25 +509,50 @@
   "notifications.permission_denied_alert": "Darbvirsmas paziņojumus nevar iespējot, jo pārlūkprogrammai atļauja tika iepriekš atteikta",
   "notifications.permission_required": "Darbvirsmas paziņojumi nav pieejami, jo nav piešķirta nepieciešamā atļauja.",
   "notifications.policy.accept": "Pieņemt",
-  "notifications.policy.drop": "Ignorēt",
   "notifications.policy.filter_new_accounts_title": "Jauni konti",
   "notifications.policy.filter_not_followers_title": "Cilvēki, kuri Tev neseko",
   "notifications.policy.filter_not_following_hint": "Līdz tos pašrocīgi apstiprināsi",
   "notifications.policy.filter_not_following_title": "Cilvēki, kuriem Tu neseko",
   "notifications_permission_banner.enable": "Iespējot darbvirsmas paziņojumus",
-  "notifications_permission_banner.how_to_control": "Lai saņemtu paziņojumus, kad Mastodon nav atvērts, jāiespējo darbvirsmas paziņojumi. Var pārvaldīt, tieši kāda veida mijiedarbības rada darbvirsmas paziņojumus, izmantojot augstāk redzamo pogu {icon}, tiklīdz tie būs iespējoti.",
+  "notifications_permission_banner.how_to_control": "Lai saņemtu paziņojumus, kad Mastodon nav atvērts, iespējo darbvirsmas paziņojumus. Vari precīzi kontrolēt, kāda veida mijiedarbības rada darbvirsmas paziņojumus, izmantojot augstāk redzamo pogu {icon}, kad tie būs iespējoti.",
   "notifications_permission_banner.title": "Nekad nepalaid neko garām",
-  "onboarding.follows.back": "Atpakaļ",
+  "onboarding.action.back": "Aizved mani atpakaļ",
+  "onboarding.actions.back": "Aizved mani atpakaļ",
+  "onboarding.actions.go_to_explore": "Skatīt tendences",
+  "onboarding.actions.go_to_home": "Doties uz manu sākuma plūsmu",
+  "onboarding.compose.template": "Sveiki, #Mastodon!",
   "onboarding.follows.empty": "Diemžēl pašlaik nevar parādīt rezultātus. Vari mēģināt izmantot meklēšanu vai pārlūkot izpētes lapu, lai atrastu cilvēkus, kuriem sekot, vai vēlāk mēģināt vēlreiz.",
+  "onboarding.follows.lead": "Tava sākuma plūsma ir galvenais veids, kā pieredzēt Mastodon. Jo vairāk cilvēkiem sekosi, jo dzīvīgāka un aizraujošāka tā būs. Lai sāktu, šeit ir daži ieteikumi:",
+  "onboarding.follows.title": "Pielāgo savu mājas barotni",
   "onboarding.profile.discoverable": "Padarīt manu profilu atklājamu",
   "onboarding.profile.display_name": "Attēlojamais vārds",
   "onboarding.profile.display_name_hint": "Tavs pilnais vārds vai Tavs joku vārds…",
+  "onboarding.profile.lead": "Šo vienmēr var pabeigt vēlāk iestatījumos, kur ir pieejamas vēl vairāk pielāgošanas iespēju.",
   "onboarding.profile.note": "Apraksts",
   "onboarding.profile.note_hint": "Tu vari @pieminēt citus cilvēkus vai #tēmturus…",
   "onboarding.profile.save_and_continue": "Saglabāt un turpināt",
   "onboarding.profile.title": "Profila iestatīšana",
   "onboarding.profile.upload_avatar": "Augšupielādēt profila attēlu",
   "onboarding.profile.upload_header": "Augšupielādēt profila galveni",
+  "onboarding.share.lead": "Dari cilvēkiem zināmu, ka viņi var Tevi atrast Mastodon!",
+  "onboarding.share.message": "Es esmu {username} #Mastodon! Nāc sekot man uz {url}",
+  "onboarding.share.next_steps": "Iespējamie nākamie soļi:",
+  "onboarding.share.title": "Kopīgo savu profilu",
+  "onboarding.start.lead": "Tagad Tu esi daļa no Mastodon — vienreizējas, decentralizētas sociālās mediju platformas, kurā Tu, nevis algoritms, veido Tavu pieredzi. Sāksim darbu šajā jaunajā sociālajā jomā:",
+  "onboarding.start.skip": "Nav nepieciešama palīdzība darba sākšanai?",
+  "onboarding.start.title": "Tev tas izdevās!",
+  "onboarding.steps.follow_people.body": "Sekošana aizraujošiem cilvēkiem ir tas, par ko ir Mastodon.",
+  "onboarding.steps.follow_people.title": "Pielāgo savu mājas barotni",
+  "onboarding.steps.publish_status.body": "Pasveicini pasauli ar tekstu, attēliem, video vai aptaujām {emoji}",
+  "onboarding.steps.publish_status.title": "Izveido savu pirmo ziņu",
+  "onboarding.steps.setup_profile.body": "Palielini mijiedarbību ar aptverošu profilu!",
+  "onboarding.steps.setup_profile.title": "Pielāgo savu profilu",
+  "onboarding.steps.share_profile.body": "Dari saviem draugiem zināmu, kā Tevi atrast Mastodon!",
+  "onboarding.steps.share_profile.title": "Kopīgo savu Mastodon profilu",
+  "onboarding.tips.2fa": "<strong>Vai zināji?</strong> Tu vari aizsargāt savu kontu, konta iestatījumos iestatot divpakāpju autentifikāciju. Tas darbojas ar jebkuru Tevis izvēlētu TOTP lietotni, nav nepieciešams tālruņa numurs!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Vai zināji?</strong> Tā kā Mastodon ir decentralizēts, daži profili, ar kuriem saskaraties, tiks mitināti citos, nevis tavos serveros. Un tomēr tu varat sazināties ar viņiem nevainojami! Viņu serveris atrodas viņu lietotājvārda otrajā pusē!",
+  "onboarding.tips.migration": "<strong>Vai zināji?</strong> Ja uzskati, ka {domain} nākotnē nav lieliska servera izvēle, vari pāriet uz citu Mastodon serveri, nezaudējot savus sekotājus. Tu pat vari mitināt savu serveri!",
+  "onboarding.tips.verification": "<strong>Vai zināji?</strong> Tu vari apliecināt savu kontu, ievietojot savā tīmekļvietnē saiti uz savu Mastodon profilu un pievienojot tīmekļvietni savam profilam. Nav nepieciešami nekādi maksājumi vai dokumenti.",
   "password_confirmation.exceeds_maxlength": "Paroles apstiprināšana pārsniedz maksimālo paroles garumu",
   "password_confirmation.mismatching": "Paroles apstiprinājums neatbilst",
   "picture_in_picture.restore": "Novietot atpakaļ",
@@ -559,6 +568,7 @@
   "poll_button.remove_poll": "Noņemt aptauju",
   "privacy.change": "Mainīt ieraksta privātumu",
   "privacy.direct.long": "Visi ierakstā pieminētie",
+  "privacy.direct.short": "Noteikti cilvēki",
   "privacy.private.long": "Tikai Tavi sekotāji",
   "privacy.private.short": "Sekotāji",
   "privacy.public.long": "Jebkurš Mastodon un ārpus tā",
@@ -569,6 +579,8 @@
   "privacy_policy.title": "Privātuma politika",
   "recommended": "Ieteicams",
   "refresh": "Atsvaidzināt",
+  "regeneration_indicator.label": "Ielādē…",
+  "regeneration_indicator.sublabel": "Tiek gatavota tava plūsma!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "Pirms {number, plural, one {# dienas} other {# dienām}}",
   "relative_time.full.hours": "Pirms {number, plural, one {# stundas} other {# stundām}}",
@@ -596,7 +608,7 @@
   "report.close": "Darīts",
   "report.comment.title": "Vai, tavuprāt, mums vēl būtu kas jāzina?",
   "report.forward": "Pārsūtīt {target}",
-  "report.forward_hint": "Konts ir no cita servera. Vai nosūtīt anonimizētu ziņojuma kopiju arī tur?",
+  "report.forward_hint": "Konts ir no cita servera. Vai nosūtīt anonimizētu sūdzības kopiju arī tam?",
   "report.mute": "Apklusināt",
   "report.mute_explanation": "Tu neredzēsi viņu ierakstus. Viņi joprojām var Tev sekot un redzēt Tavus ierakstus un nezinās, ka viņi ir apklusināti.",
   "report.next": "Tālāk",
@@ -648,8 +660,10 @@
   "search_results.accounts": "Profili",
   "search_results.all": "Visi",
   "search_results.hashtags": "Tēmturi",
+  "search_results.nothing_found": "Nevarēja atrast neko, kas atbilstu šim meklēšanas vaicājumam",
   "search_results.see_all": "Skatīt visus",
   "search_results.statuses": "Ieraksti",
+  "search_results.title": "Meklēt {q}",
   "server_banner.about_active_users": "Cilvēki, kas izmantojuši šo serveri pēdējo 30 dienu laikā (aktīvie lietotāji mēnesī)",
   "server_banner.active_users": "aktīvi lietotāji",
   "server_banner.administered_by": "Pārvalda:",
@@ -688,10 +702,10 @@
   "status.mute_conversation": "Apklusināt sarunu",
   "status.open": "Paplašināt šo ziņu",
   "status.pin": "Piespraust profilam",
-  "status.pinned": "Piesprausts ieraksts",
+  "status.pinned": "Piespraustais ieraksts",
   "status.read_more": "Lasīt vairāk",
   "status.reblog": "Pastiprināt",
-  "status.reblog_private": "Pastiprināt ar sākotnējo redzamību",
+  "status.reblog_private": "Pastiprināt, nemainot redzamību",
   "status.reblogged_by": "{name} pastiprināja",
   "status.reblogs": "{count, plural, zero {pastiprinājumu} one {pastiprinājums} other {pastiprinājumi}}",
   "status.reblogs.empty": "Neviens šo ierakstu vēl nav pastiprinājis. Kad būs, tie parādīsies šeit.",
@@ -701,11 +715,11 @@
   "status.reply": "Atbildēt",
   "status.replyAll": "Atbildēt uz tematu",
   "status.report": "Ziņot par @{name}",
-  "status.sensitive_warning": "Jūtīgs saturs",
+  "status.sensitive_warning": "Sensitīvs saturs",
   "status.share": "Kopīgot",
   "status.show_less_all": "Rādīt mazāk visiem",
   "status.show_more_all": "Rādīt vairāk visiem",
-  "status.show_original": "Rādīt pirmavotu",
+  "status.show_original": "Rādīt oriģinālu",
   "status.title.with_attachments": "{user} publicējis {attachmentCount, plural, one {pielikumu} other {{attachmentCount} pielikumus}}",
   "status.translate": "Tulkot",
   "status.translated_from_with": "Tulkots no {lang} izmantojot {provider}",
@@ -732,7 +746,21 @@
   "upload_button.label": "Pievienot bildi, video vai audio datni",
   "upload_error.limit": "Sasniegts datņu augšupielādes ierobežojums.",
   "upload_error.poll": "Datņu augšupielādes aptaujās nav atļautas.",
+  "upload_form.audio_description": "Pievieno aprakstu cilvēkiem ar dzirdes zudumu",
+  "upload_form.description": "Pievieno aprakstu vājredzīgajiem",
   "upload_form.edit": "Labot",
+  "upload_form.thumbnail": "Nomainīt sīktēlu",
+  "upload_form.video_description": "Pievieno aprakstu cilvēkiem ar dzirdes vai redzes traucējumiem",
+  "upload_modal.analyzing_picture": "Analizē attēlu…",
+  "upload_modal.apply": "Pielietot",
+  "upload_modal.applying": "Pielieto…",
+  "upload_modal.choose_image": "Izvēlēties attēlu",
+  "upload_modal.description_placeholder": "Raibais runcis rīgā ratu rumbā rūc",
+  "upload_modal.detect_text": "Noteikt tekstu no attēla",
+  "upload_modal.edit_media": "Labot informācijas nesēju",
+  "upload_modal.hint": "Noklikšķini vai velc apli priekšskatījumā, lai izvēlētos fokusa punktu, kas vienmēr būs redzams visos sīktēlos.",
+  "upload_modal.preparing_ocr": "Sagatavo OCR…",
+  "upload_modal.preview_label": "Priekšskatīt ({ratio})",
   "upload_progress.label": "Augšupielādē...",
   "upload_progress.processing": "Apstrādā…",
   "username.taken": "Šis lietotājvārds ir jau paņemts. Izmēģini citu",
@@ -742,6 +770,8 @@
   "video.expand": "Paplašināt video",
   "video.fullscreen": "Pilnekrāns",
   "video.hide": "Slēpt video",
+  "video.mute": "Izslēgt skaņu",
   "video.pause": "Pauze",
-  "video.play": "Atskaņot"
+  "video.play": "Atskaņot",
+  "video.unmute": "Ieslēgt skaņu"
 }
diff --git a/app/javascript/mastodon/locales/mk.json b/app/javascript/mastodon/locales/mk.json
index 63851de700..084a94cc14 100644
--- a/app/javascript/mastodon/locales/mk.json
+++ b/app/javascript/mastodon/locales/mk.json
@@ -49,6 +49,7 @@
   "boost_modal.combo": "Кликни {combo} за да го прескокниш ова нареден пат",
   "bundle_column_error.retry": "Обидете се повторно",
   "bundle_modal_error.close": "Затвори",
+  "bundle_modal_error.message": "Настана грешка при прикажувањето на оваа веб-страница.",
   "bundle_modal_error.retry": "Обидете се повторно",
   "column.blocks": "Блокирани корисници",
   "column.community": "Локална временска зона",
@@ -94,6 +95,8 @@
   "conversation.with": "Со {names}",
   "directory.federated": "Од познати fediverse",
   "directory.local": "Само од {domain}",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "emoji_button.activity": "Активност",
   "emoji_button.food": "Храна &amp; Пијалаци",
@@ -187,6 +190,19 @@
   "notifications.filter.mentions": "Спомнувања",
   "notifications.filter.polls": "Резултати од анкета",
   "notifications.group": "{count} нотификации",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "poll.closed": "Затворени",
   "poll.refresh": "Освежи",
   "poll.total_people": "{count, plural, one {# човек} other {# луѓе}}",
@@ -198,6 +214,8 @@
   "privacy.change": "Штеловај статус на приватност",
   "privacy.public.short": "Јавно",
   "refresh": "Освежи",
+  "regeneration_indicator.label": "Вчитување…",
+  "regeneration_indicator.sublabel": "Вашиот новости се подготвуваат!",
   "relative_time.days": "{number}д",
   "relative_time.hours": "{number}ч",
   "relative_time.just_now": "сега",
@@ -228,5 +246,8 @@
   "time_remaining.moments": "Уште некои мига",
   "time_remaining.seconds": "{number, plural, one {# секунда} other {# секунди}} {number, plural, one {остана} other {останаа}}",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "Uploading…"
 }
diff --git a/app/javascript/mastodon/locales/ml.json b/app/javascript/mastodon/locales/ml.json
index b865f1aa6e..96dd723b82 100644
--- a/app/javascript/mastodon/locales/ml.json
+++ b/app/javascript/mastodon/locales/ml.json
@@ -77,6 +77,7 @@
   "bundle_column_error.return": "ഹോം പേജിലേക്ക് മടങ്ങാം",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "അടയ്ക്കുക",
+  "bundle_modal_error.message": "ഈ വെബ്പേജ് പ്രദർശിപ്പിക്കുമ്പോൾ എന്തോ കുഴപ്പം സംഭവിച്ചു.",
   "bundle_modal_error.retry": "വീണ്ടും ശ്രമിക്കുക",
   "closed_registrations.other_server_instructions": "Mastodon വികേന്ദ്രീകൃത സംവിധാനം ആയതിനാൽ, നിങ്ങൾക്ക് മറ്റൊരു സെർവറിൽ ഒരു അക്കൗണ്ട് ഉണ്ടാക്കിയും ഇതുമായി ആശയവിനിമയം നടത്താൻ സാധിക്കുന്നതാണ്.",
   "closed_registrations_modal.description": "{domain} ഇൽ ഇപ്പോൾ അക്കൗണ്ട് ഉണ്ടാക്കാൻ സാധിക്കുന്നതല്ല, Mastodon ഉപയോഗിക്കുന്നതിനായി നിങ്ങൾക്ക് {domain}-ൽ പ്രത്യേകമായി ഒരു അക്കൗണ്ട് ആവശ്യമില്ല എന്നത് ദയവായി ഓർക്കുക.",
@@ -118,7 +119,6 @@
   "compose_form.poll.duration": "തിരഞ്ഞെടുപ്പിന്റെ സമയദൈർഖ്യം",
   "compose_form.poll.multiple": "ഒരുപാടു് സാധ്യതകൾ",
   "compose_form.poll.option_placeholder": "സാധ്യത {number}",
-  "compose_form.poll.single": "ഒറ്റ സാധ്യത",
   "compose_form.poll.switch_to_multiple": "വോട്ടെടുപ്പിൽ ഒന്നിലധികം ചോയ്‌സുകൾ ഉൾപ്പെടുതുക",
   "compose_form.poll.switch_to_single": "വോട്ടെടുപ്പിൽ ഒരൊറ്റ ചോയ്‌സ്‌ മാത്രം ആക്കുക",
   "compose_form.poll.type": "രീതി",
@@ -158,6 +158,8 @@
   "directory.recently_active": "അടുത്തിടെയായി സജീവമായ",
   "disabled_account_banner.account_settings": "ഇടപാടു് ക്രമീകരങ്ങൾ",
   "disabled_account_banner.text": "നിങ്ങളുടെ {disabledAccount} എന്ന അക്കൗണ്ട് ഇപ്പോൾ പ്രവർത്തനരഹിതമാണ്.",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "domain_block_modal.title": "മേഖല തടസ്സപെടുത്തുക?",
   "domain_pill.username": "ഉപയോക്തൃപേരു്",
   "embed.instructions": "ചുവടെയുള്ള കോഡ് പകർത്തിക്കൊണ്ട് നിങ്ങളുടെ വെബ്‌സൈറ്റിൽ ഈ ടൂട്ട് ഉൾച്ചേർക്കുക.",
@@ -190,6 +192,7 @@
   "empty_column.notifications": "നിങ്ങൾക്ക് ഇതുവരെ ഒരു അറിയിപ്പുകളും ഇല്ല. മറ്റുള്ളവരുമായി ഇടപെട്ട് സംഭാഷണത്തിന് തുടക്കം കുറിക്കു.",
   "empty_column.public": "ഇവിടെ ഒന്നുമില്ലല്ലോ! ഇവിടെ നിറയ്ക്കാൻ എന്തെങ്കിലും പരസ്യമായി എഴുതുകയോ മറ്റ് ഉപഭോക്താക്കളെ പിന്തുടരുകയോ ചെയ്യുക",
   "errors.unexpected_crash.report_issue": "പ്രശ്നം അറിയിക്കുക",
+  "explore.search_results": "തിരയൽ ഫലങ്ങൾ",
   "explore.suggested_follows": "ആൾക്കാർ",
   "explore.title": "പര്യവേക്ഷണം നടത്തുക",
   "explore.trending_links": "വാര്‍ത്ത",
@@ -207,6 +210,7 @@
   "followed_tags": "പിന്തുടരിയതു് ചർച്ചാവിഷയങ്ങൾ",
   "footer.directory": "രൂപരേഖ നാമഗൃഹസൂചി",
   "footer.get_app": "ഉപകരണം ലഭിക്കൂ",
+  "footer.invite": "ആളുകളെ ക്ഷണിക്കുക",
   "footer.privacy_policy": "സ്വകാര്യത്തനയം",
   "footer.source_code": "ഉറവിടസങ്കേതം കാണുക",
   "footer.status": "അവസ്ഥ",
@@ -228,6 +232,9 @@
   "home.hide_announcements": "പ്രഖ്യാപനങ്ങൾ മറയ്‌ക്കുക",
   "home.pending_critical_update.link": "പുതുകൾ കാണുക",
   "home.show_announcements": "പ്രഖ്യാപനങ്ങൾ കാണിക്കുക",
+  "interaction_modal.login.action": "ആമുഖം വരെ എടുത്തോണ്ടു് പോവുക",
+  "interaction_modal.login.prompt": "ആമുഖപ്രദാനിയുടെ മേഖലപേരു്. ഉദ: mastodon.social",
+  "interaction_modal.no_account_yet": "മാസ്റ്റഡോണിൽ ഇല്ലേ?",
   "interaction_modal.on_this_server": "ഈ സെർവറീൽ",
   "interaction_modal.title.favourite": "പ്രിയപ്പെട്ട {name}-ന്റെ എഴുതു്",
   "interaction_modal.title.follow": "{name}-െ പിന്തുടരുക",
@@ -267,9 +274,17 @@
   "lightbox.previous": "പുറകോട്ട്",
   "limited_account_hint.action": "എന്നാലും രൂപരേഖ കാണിക്കുക",
   "link_preview.author": "{name}-നിന്നു്",
+  "lists.account.add": "പട്ടികയിലേക്ക് ചേർക്കുക",
+  "lists.account.remove": "പട്ടികയിൽ നിന്ന് ഒഴിവാക്കുക",
   "lists.delete": "പട്ടിക ഒഴിവാക്കുക",
   "lists.edit": "പട്ടിക തിരുത്തുക",
+  "lists.edit.submit": "തലക്കെട്ട് മാറ്റുക",
+  "lists.exclusive": "ഈ എഴുത്തുകൾ ആമുഖം നിന്നു് മറയ്ക്കുക",
+  "lists.new.create": "പുതിയ പട്ടിക ചേർക്കുക",
+  "lists.new.title_placeholder": "പുതിയ പട്ടിക തലക്കെട്ടു്",
   "lists.replies_policy.none": "ആരുമില്ല",
+  "lists.replies_policy.title": "ഇതിനുള്ള മറുപടികൾ കാണിക്കുക:",
+  "lists.subheading": "എന്റെ പട്ടികകൾ",
   "media_gallery.hide": "മറയ്ക്കുക",
   "navigation_bar.blocks": "തടയപ്പെട്ട ഉപയോക്താക്കൾ",
   "navigation_bar.bookmarks": "ബുക്ക്മാർക്കുകൾ",
@@ -326,6 +341,20 @@
   "notifications.policy.filter_new_accounts_title": "പുതിയ ഇടപാടുകൾ",
   "notifications.policy.filter_not_followers_title": "താങ്ങളെ പിന്തുടരാത്തതു് ആൾക്കാർ",
   "notifications_permission_banner.enable": "ഡെസ്ക്ടോപ്പ് അറിയിപ്പുകൾ പ്രാപ്തമാക്കുക",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "ആമുഖത്താൾ വരെ പോവ്വുക",
+  "onboarding.follows.lead": "",
+  "onboarding.follows.title": "താങ്ങളുടെ ആമുഖത്താളിന് വ്യക്തിപരമാക്കുക",
+  "onboarding.share.title": "താങ്ങളുടെ രൂപരേഖ പങ്കിടുക",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "താങ്ങളുടെ ആമുഖത്താളിന് വ്യക്തിപരമാക്കുക",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "തിരികെ വയ്ക്കുക",
   "poll.closed": "അടച്ചു",
   "poll.refresh": "പുതുക്കുക",
@@ -340,6 +369,8 @@
   "privacy.public.short": "എല്ലാവര്‍ക്കും",
   "privacy_policy.title": "സ്വകാര്യത്തനയം",
   "refresh": "പുതുക്കുക",
+  "regeneration_indicator.label": "ലഭ്യമാക്കുന്നു…",
+  "regeneration_indicator.sublabel": "നിങ്ങളുടെ താങ്ങളുടെ ആമുഖത്താളിന് തയാറാക്കുന്നു!",
   "relative_time.days": "{number}ദിവസം",
   "relative_time.full.just_now": "ഇപ്പോൾതന്നെ",
   "relative_time.hours": "{number}മണി",
@@ -397,7 +428,18 @@
   "upload_area.title": "അപ്‌ലോഡുചെയ്യാൻ വലിച്ചിടുക",
   "upload_button.label": "ഇമേജുകൾ, ഒരു വീഡിയോ അല്ലെങ്കിൽ ഓഡിയോ ഫയൽ ചേർക്കുക",
   "upload_error.limit": "ഫയൽ അപ്‌ലോഡ് പരിധി കവിഞ്ഞു.",
+  "upload_form.audio_description": "കേൾവിശക്തി ഇല്ലാത്തവർക്ക് വേണ്ടി വിവരണം നൽകൂ",
+  "upload_form.description": "കാഴ്ചശക്തി ഇല്ലാത്തവർക്ക് വേണ്ടി വിവരണം നൽകൂ",
   "upload_form.edit": "തിരുത്തുക",
+  "upload_form.thumbnail": "ലഘുചിത്രം മാറ്റുക",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
+  "upload_modal.analyzing_picture": "ചിത്രം വിശകലനം ചെയ്യുന്നു…",
+  "upload_modal.apply": "പ്രയോഗിക്കുക",
+  "upload_modal.choose_image": "ചിത്രം തിരഞ്ഞെടുക്കുക",
+  "upload_modal.detect_text": "ചിത്രത്തിൽ നിന്ന് വാചകം കണ്ടെത്തുക",
+  "upload_modal.edit_media": "മീഡിയ തിരുത്തുക",
+  "upload_modal.preparing_ocr": "OCR തയ്യാറാക്കുന്നു…",
+  "upload_modal.preview_label": "പൂര്‍വ്വദൃശ്യം({ratio})",
   "upload_progress.label": "Uploading…",
   "video.close": "വീഡിയോ അടയ്ക്കുക",
   "video.download": "ഫയൽ ഇറക്കുവയ്ക്കുക",
@@ -405,6 +447,7 @@
   "video.expand": "വീഡിയോ വികസപ്പിക്കൂ",
   "video.fullscreen": "പൂർണ്ണ സ്ക്രീൻ",
   "video.hide": "വീഡിയോ മറയ്ക്കുക",
+  "video.mute": "ശബ്ദം നിശബ്‌ദമാക്കൂ",
   "video.pause": "താൽക്കാലികമായി നിർത്തുക",
   "video.play": "പ്ലേ"
 }
diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json
index 919a34532f..aa8169616e 100644
--- a/app/javascript/mastodon/locales/mr.json
+++ b/app/javascript/mastodon/locales/mr.json
@@ -28,6 +28,7 @@
   "account.endorse": "प्रोफाइलवरील वैशिष्ट्य",
   "account.featured_tags.last_status_at": "शेवटचे पोस्ट {date} रोजी",
   "account.featured_tags.last_status_never": "पोस्ट नाहीत",
+  "account.featured_tags.title": "{name} चे वैशिष्ट्यीकृत हॅशटॅग",
   "account.follow": "अनुयायी व्हा",
   "account.follow_back": "आपणही अनुसरण करा",
   "account.followers": "अनुयायी",
@@ -74,6 +75,7 @@
   "announcement.announcement": "घोषणा",
   "bundle_column_error.retry": "पुन्हा प्रयत्न करा",
   "bundle_modal_error.close": "बंद करा",
+  "bundle_modal_error.message": "हा घटक लोड करतांना काहीतरी चुकले आहे.",
   "bundle_modal_error.retry": "पुन्हा प्रयत्न करा",
   "column.blocks": "ब्लॉक केलेले खातेधारक",
   "column.domain_blocks": "गुप्त डोमेन्स",
@@ -105,6 +107,8 @@
   "confirmations.delete_list.message": "ही यादी तुम्हाला नक्की कायमची हटवायचीय?",
   "confirmations.logout.message": "तुमची खात्री आहे की तुम्ही लॉग आउट करू इच्छिता?",
   "confirmations.mute.confirm": "आवाज बंद करा",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "empty_column.account_timeline": "No toots here!",
   "empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
@@ -121,6 +125,9 @@
   "home.column_settings.show_replies": "उत्तरे दाखवा",
   "home.hide_announcements": "घोषणा लपवा",
   "home.show_announcements": "घोषणा दाखवा",
+  "interaction_modal.description.follow": "मॅस्टोडॉन वरील खात्यासह, तुम्ही त्यांच्या पोस्ट तुमच्या होम फीडमध्ये प्राप्त करण्यासाठी {name} चे अनुसरण करू शकता.",
+  "interaction_modal.description.reblog": "मॅस्टोडॉन वरील खात्यासह, तुम्ही ही पोस्ट तुमच्या स्वतःच्या अनुयायांसह शेअर करण्यासाठी बूस्ट करू शकता.",
+  "interaction_modal.description.reply": "मॅस्टोडॉनवरील खात्यासह, तुम्ही या पोस्टला प्रतिसाद देऊ शकता.",
   "interaction_modal.on_another_server": "वेगळ्या सर्व्हरवर",
   "interaction_modal.on_this_server": "या सर्व्हरवर",
   "interaction_modal.title.follow": "{name} चे अनुसरण करा",
@@ -166,11 +173,19 @@
   "lightbox.previous": "मागील",
   "limited_account_hint.action": "तरीही प्रोफाइल दाखवा",
   "limited_account_hint.title": "हे प्रोफाइल {domain} च्या नियंत्रकांनी लपवले आहे.",
+  "lists.account.add": "यादीमध्ये जोडा",
+  "lists.account.remove": "यादीमधून काढा",
   "lists.delete": "सूची हटवा",
   "lists.edit": "सूची संपादित करा",
+  "lists.edit.submit": "शीर्षक बदला",
+  "lists.new.create": "यादी जोडा",
+  "lists.new.title_placeholder": "नवीन सूची शीर्षक",
   "lists.replies_policy.followed": "कोणताही फॉलो केलेला वापरकर्ता",
   "lists.replies_policy.list": "यादीतील सदस्य",
   "lists.replies_policy.none": "कोणीच नाही",
+  "lists.replies_policy.title": "यांना उत्तरे दाखवा:",
+  "lists.search": "तुम्ही फॉलो करत असलेल्या लोकांमध्ये शोधा",
+  "lists.subheading": "तुमच्या याद्या",
   "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "navigation_bar.compose": "Compose new toot",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -191,6 +206,19 @@
   "notifications.filter.mentions": "उल्लेख केलेले",
   "notifications.filter.polls": "मतदान परिणाम",
   "notifications.filter.statuses": "तुम्ही फॉलो करत असलेल्या लोकांकडून अपडेट",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "privacy.change": "Adjust status privacy",
   "report.placeholder": "Type or paste additional comments",
   "report.submit": "Submit report",
@@ -206,5 +234,8 @@
   "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "Uploading…"
 }
diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json
index 483261da6b..684096e26d 100644
--- a/app/javascript/mastodon/locales/ms.json
+++ b/app/javascript/mastodon/locales/ms.json
@@ -1,5 +1,5 @@
 {
-  "about.blocks": "Pelayan yang diselaraskan",
+  "about.blocks": "Pelayan yang disederhanakan",
   "about.contact": "Hubungi:",
   "about.disclaimer": "Mastodon ialah perisian sumber terbuka percuma, dan merupakan tanda dagangan Mastodon gGmbH.",
   "about.domain_blocks.no_reason_available": "Sebab tidak tersedia",
@@ -11,13 +11,13 @@
   "about.not_available": "Maklumat ini belum tersedia pada pelayan ini.",
   "about.powered_by": "Media sosial terpencar yang dikuasakan oleh {mastodon}",
   "about.rules": "Peraturan pelayan",
-  "account.account_note_header": "Catatan peribadi",
+  "account.account_note_header": "Personal note",
   "account.add_or_remove_from_list": "Tambah atau Buang dari senarai",
-  "account.badges.bot": "Automatik",
+  "account.badges.bot": "Bot",
   "account.badges.group": "Kumpulan",
   "account.block": "Sekat @{name}",
   "account.block_domain": "Sekat domain {domain}",
-  "account.block_short": "Sekat",
+  "account.block_short": "Malay",
   "account.blocked": "Disekat",
   "account.cancel_follow_request": "Batalkan permintaan ikut",
   "account.copy": "Salin pautan ke profil",
@@ -29,28 +29,29 @@
   "account.endorse": "Tampilkan di profil",
   "account.featured_tags.last_status_at": "Hantaran terakhir pada {date}",
   "account.featured_tags.last_status_never": "Tiada hantaran",
+  "account.featured_tags.title": "Tanda pagar pilihan {name}",
   "account.follow": "Ikuti",
   "account.follow_back": "Ikut balik",
   "account.followers": "Pengikut",
   "account.followers.empty": "Belum ada yang mengikuti pengguna ini.",
-  "account.followers_counter": "{count, plural, one {{counter} pengikut} other {{counter} pengikut}}",
-  "account.following": "Ikutan",
-  "account.following_counter": "{count, plural, other {{counter} ikutan}}",
+  "account.followers_counter": "{count, plural, one {{counter} Diikuti} other {{counter} Diikuti}}",
+  "account.following": "Mengikuti",
+  "account.following_counter": "{count, plural, other {{counter} following}}",
   "account.follows.empty": "Pengguna ini belum mengikuti sesiapa.",
   "account.go_to_profile": "Pergi ke profil",
   "account.hide_reblogs": "Sembunyikan galakan daripada @{name}",
-  "account.in_memoriam": "Dalam kenangan.",
-  "account.joined_short": "Tarikh penyertaan",
-  "account.languages": "Tukar bahasa langganan",
+  "account.in_memoriam": "Dalam Memoriam.",
+  "account.joined_short": "Menyertai",
+  "account.languages": "Tukar bahasa yang dilanggan",
   "account.link_verified_on": "Pemilikan pautan ini telah disemak pada {date}",
-  "account.locked_info": "Taraf privasi akaun ini dikunci. Pemiliknya menyaring sendiri siapa yang boleh mengikutinya.",
+  "account.locked_info": "Status privasi akaun ini dikunci. Pemiliknya menyaring sendiri siapa yang boleh mengikutinya.",
   "account.media": "Media",
   "account.mention": "Sebut @{name}",
   "account.moved_to": "{name} telah menandakan bahawa akaun baru mereka sekarang ialah:",
-  "account.mute": "Redamkan @{name}",
-  "account.mute_notifications_short": "Redamkan pemberitahuan",
+  "account.mute": "Bisukan @{name}",
+  "account.mute_notifications_short": "Redam pemberitahuan",
   "account.mute_short": "Redam",
-  "account.muted": "Diredamkan",
+  "account.muted": "Dibisukan",
   "account.mutual": "Rakan kongsi",
   "account.no_bio": "Tiada penerangan diberikan.",
   "account.open_original_page": "Buka halaman asal",
@@ -64,13 +65,12 @@
   "account.statuses_counter": "{count, plural, other {{counter} siaran}}",
   "account.unblock": "Nyahsekat @{name}",
   "account.unblock_domain": "Nyahsekat domain {domain}",
-  "account.unblock_domain_short": "Nyahsekat",
   "account.unblock_short": "Nyahsekat",
   "account.unendorse": "Jangan tampilkan di profil",
   "account.unfollow": "Nyahikut",
   "account.unmute": "Nyahbisukan @{name}",
-  "account.unmute_notifications_short": "Nyahredamkan pemberitahuan",
-  "account.unmute_short": "Nyahredam",
+  "account.unmute_notifications_short": "Nyahredam notifikasi",
+  "account.unmute_short": "Nyahbisu",
   "account_note.placeholder": "Klik untuk menambah catatan",
   "admin.dashboard.daily_retention": "Kadar pengekalan pengguna mengikut hari selepas mendaftar",
   "admin.dashboard.monthly_retention": "Kadar pengekalan pengguna mengikut bulan selepas mendaftar",
@@ -86,38 +86,10 @@
   "alert.unexpected.message": "Berlaku ralat di luar jangkaan.",
   "alert.unexpected.title": "Alamak!",
   "alt_text_badge.title": "Teks alternatif",
-  "alt_text_modal.add_alt_text": "Tambah teks alternatif",
-  "alt_text_modal.add_text_from_image": "Tambah teks dari imej",
-  "alt_text_modal.cancel": "Batal",
-  "alt_text_modal.change_thumbnail": "Ubah imej kecil",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Terangkan untuk OKU pendengaran…vorite\n",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Terangkan untuk OKU penglihatan…",
-  "alt_text_modal.done": "Selesai",
   "announcement.announcement": "Pengumuman",
-  "annual_report.summary.archetype.booster": "Si pencapap",
-  "annual_report.summary.archetype.lurker": "Si penghendap",
-  "annual_report.summary.archetype.oracle": "Si penilik",
-  "annual_report.summary.archetype.pollster": "Si peninjau",
-  "annual_report.summary.archetype.replier": "Si peramah",
-  "annual_report.summary.followers.followers": "pengikut",
-  "annual_report.summary.followers.total": "sebanyak {count}",
-  "annual_report.summary.here_it_is": "Ini ulasan {year} anda:",
-  "annual_report.summary.highlighted_post.by_favourites": "hantaran paling disukai ramai",
-  "annual_report.summary.highlighted_post.by_reblogs": "hantaran paling digalak ramai",
-  "annual_report.summary.highlighted_post.by_replies": "hantaran paling dibalas ramai",
-  "annual_report.summary.highlighted_post.possessive": "oleh",
-  "annual_report.summary.most_used_app.most_used_app": "aplikasi paling banyak digunakan",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "tanda pagar paling banyak digunakan",
-  "annual_report.summary.most_used_hashtag.none": "Tiada",
-  "annual_report.summary.new_posts.new_posts": "hantaran baharu",
-  "annual_report.summary.percentile.text": "<topLabel>Anda berkedudukan</topLabel><percentage></percentage><bottomLabel> pengguna {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Rahsia anda selamat bersama kami. ;)",
-  "annual_report.summary.thanks": "Terima kasih kerana setia bersama Mastodon!",
   "attachments_list.unprocessed": "(belum diproses)",
   "audio.hide": "Sembunyikan audio",
   "block_modal.remote_users_caveat": "Kami akan meminta pelayan {domain} untuk menghormati keputusan anda. Bagaimanapun, pematuhan tidak dijamin kerana ada pelayan yang mungkin menangani sekatan dengan cara berbeza. Hantaran awam mungkin masih tampak kepada pengguna yang tidak log masuk.",
-  "block_modal.show_less": "Tunjuk kurang",
-  "block_modal.show_more": "Tunjuk lebih",
   "block_modal.they_cant_mention": "Dia tidak boleh menyebut tentang anda atau mengikut anda.",
   "block_modal.they_cant_see_posts": "Dia tidak boleh melihat hantaran anda dan sebaliknya.",
   "block_modal.they_will_know": "Dia boleh lihat bahawa dia disekat.",
@@ -136,7 +108,7 @@
   "bundle_column_error.routing.body": "Halaman tersebut tidak dapat ditemui. Adakah anda pasti URL dalam bar alamat adalah betul?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Tutup",
-  "bundle_modal_error.message": "Terdapat masalah yang dihadapi ketika memuat layar ini.",
+  "bundle_modal_error.message": "Ada yang tidak kena semasa memuatkan komponen ini.",
   "bundle_modal_error.retry": "Cuba lagi",
   "closed_registrations.other_server_instructions": "Oleh sebab Mastodon terpencar, anda boleh mencipta akaun pada pelayan lain dan masih berinteraksi dengan pelayan ini.",
   "closed_registrations_modal.description": "Mencipta akaun pada {domain} tidak dapat dibuat sekarang, tetapi sila ingat bahawa anda tidak memerlukan akaun khususnya pada {domain} untuk menggunakan Mastodon.",
@@ -144,21 +116,18 @@
   "closed_registrations_modal.preamble": "Mastodon adalah terpencar, oleh itu di mana-mana anda mencipta akaun anda, anda boleh mengikut dan berinteraksi dengan sesiapa pada pelayan ini. Anda juga boleh hos sendiri!",
   "closed_registrations_modal.title": "Mendaftar pada Mastodon",
   "column.about": "Perihal",
-  "column.blocks": "Pengguna tersekat",
+  "column.blocks": "Pengguna yang disekat",
   "column.bookmarks": "Tanda buku",
   "column.community": "Garis masa tempatan",
-  "column.create_list": "Cipta senarai",
   "column.direct": "Sebutan peribadi",
   "column.directory": "Layari profil",
-  "column.domain_blocks": "Domain tersekat",
-  "column.edit_list": "Sunting senarai",
+  "column.domain_blocks": "Domain disekat",
   "column.favourites": "Sukaan",
   "column.firehose": "Suapan langsung",
   "column.follow_requests": "Permintaan ikutan",
   "column.home": "Laman Utama",
-  "column.list_members": "Urus ahli senarai",
   "column.lists": "Senarai",
-  "column.mutes": "Pengguna teredam",
+  "column.mutes": "Pengguna yang dibisukan",
   "column.notifications": "Pemberitahuan",
   "column.pins": "Hantaran disemat",
   "column.public": "Garis masa bersekutu",
@@ -169,7 +138,6 @@
   "column_header.pin": "Sematkan",
   "column_header.show_settings": "Tunjukkan tetapan",
   "column_header.unpin": "Nyahsemat",
-  "column_search.cancel": "Batal",
   "column_subheading.settings": "Tetapan",
   "community.column_settings.local_only": "Tempatan sahaja",
   "community.column_settings.media_only": "Media sahaja",
@@ -188,7 +156,7 @@
   "compose_form.poll.duration": "Tempoh undian",
   "compose_form.poll.multiple": "Pelbagai pilihan",
   "compose_form.poll.option_placeholder": "Pilihan {number}",
-  "compose_form.poll.single": "Satu pilihan",
+  "compose_form.poll.single": "Pilih satu",
   "compose_form.poll.switch_to_multiple": "Ubah kepada membenarkan aneka undian",
   "compose_form.poll.switch_to_single": "Ubah kepada undian pilihan tunggal",
   "compose_form.poll.type": "Gaya",
@@ -212,29 +180,16 @@
   "confirmations.edit.confirm": "Sunting",
   "confirmations.edit.message": "Mengedit sekarang akan menimpa mesej yang sedang anda karang. Adakah anda pasti mahu meneruskan?",
   "confirmations.edit.title": "Tulis ganti hantaran?",
-  "confirmations.follow_to_list.confirm": "Ikut dan tambah ke senarai",
-  "confirmations.follow_to_list.message": "Anda mesti mengikuti {name} untuk tambahkannya ke senarai.",
-  "confirmations.follow_to_list.title": "Ikut pengguna?",
   "confirmations.logout.confirm": "Log keluar",
   "confirmations.logout.message": "Adakah anda pasti anda ingin log keluar?",
   "confirmations.logout.title": "Log keluar?",
-  "confirmations.missing_alt_text.confirm": "Tambah teks alternatif",
-  "confirmations.missing_alt_text.message": "Hantaran anda mempunyai media tanpa teks alternatif. Kandungan anda akan lebih mudah tercapai jika anda menambah keterangan.",
-  "confirmations.missing_alt_text.secondary": "Hantar saja",
-  "confirmations.missing_alt_text.title": "Tambah teks alternatif?",
-  "confirmations.mute.confirm": "Redamkan",
+  "confirmations.mute.confirm": "Bisukan",
   "confirmations.redraft.confirm": "Padam & rangka semula",
   "confirmations.redraft.message": "Adakah anda pasti anda ingin memadam hantaran ini dan gubal semula? Sukaan dan galakan akan hilang, dan balasan ke hantaran asal akan menjadi yatim.",
-  "confirmations.redraft.title": "Padam & gubah semula hantaran?",
   "confirmations.reply.confirm": "Balas",
   "confirmations.reply.message": "Membalas sekarang akan menulis ganti mesej yang anda sedang karang. Adakah anda pasti anda ingin teruskan?",
-  "confirmations.reply.title": "Tulis ganti hantaran?",
   "confirmations.unfollow.confirm": "Nyahikut",
   "confirmations.unfollow.message": "Adakah anda pasti anda ingin nyahikuti {name}?",
-  "confirmations.unfollow.title": "Berhenti mengikut pengguna?",
-  "content_warning.hide": "Sorok hantaran",
-  "content_warning.show": "Tunjuk saja",
-  "content_warning.show_more": "Tunjuk lebih",
   "conversation.delete": "Padam perbualan",
   "conversation.mark_as_read": "Tanda sudah dibaca",
   "conversation.open": "Lihat perbualan",
@@ -250,23 +205,10 @@
   "disabled_account_banner.text": "Akaun anda {disabledAccount} telah dinyahaktif.",
   "dismissable_banner.community_timeline": "Inilah hantaran awam terkini daripada orang yang akaun dihos oleh {domain}.",
   "dismissable_banner.dismiss": "Ketepikan",
+  "dismissable_banner.explore_links": "Berita-berita ini sedang dibualkan oleh orang di pelayar ini dan pelayar lain dalam rangkaian terpencar sekarang.",
   "dismissable_banner.explore_statuses": "Hantaran-hantaran dari seluruh alam bersekutu ini sedang sohor. Hantaran terbaharu dengan lebih banyak galakan dan sukaan diberi kedudukan lebih tinggi.",
-  "dismissable_banner.public_timeline": "Hantaran-hantaran awam terkini ini dari pengguna alam bersekutu yang diikuti oleh pengguna dari {domain}.",
-  "domain_block_modal.block": "Sekat pelayan",
-  "domain_block_modal.block_account_instead": "Sekat @{name} sahaja",
-  "domain_block_modal.they_can_interact_with_old_posts": "Pengguna dari pelayan ini boleh berinteraksi dengan hantaran lama anda.",
-  "domain_block_modal.they_cant_follow": "Pengguna dari pelayan ini tidak boleh mengikuti anda.",
-  "domain_block_modal.they_wont_know": "Dia tidak akan tahu bahawa dia telah disekat.",
-  "domain_block_modal.title": "Sekat domain?",
-  "domain_block_modal.you_will_lose_num_followers": "Anda akan kehilangan {followersCount, plural, other {{followersCountDisplay} pengikut}} dan {followingCount, plural, other {{followingCountDisplay} ikutan}}.",
-  "domain_block_modal.you_will_lose_relationships": "Anda akan kehilangan semua pengikut dan ikutan anda dari pelayan ini.",
-  "domain_block_modal.you_wont_see_posts": "Anda tidak akan melihat hantaran atau pemberitahuan dari pengguna pada pelayan ini.",
-  "domain_pill.activitypub_lets_connect": "Hal ini membolehkan anda berhubung dan berinteraksi bukan sahaja dengan pengguna Mastodon tetapi melintasi pelbagai aplikasi sosial juga.",
-  "domain_pill.activitypub_like_language": "ActivityPub adalah seperti bahasa yang digunakan oleh Mastodon untuk berhubung dengan jaringan sosial lain.",
-  "domain_pill.server": "Pelayan",
-  "domain_pill.your_handle": "Pemegang anda:",
-  "domain_pill.your_server": "Rumah maya anda, tempatnya hantaran anda disimpan. Tidak berkenan dengan yang ini? Pindah antara pelayan pada bila-bila masa dan bawa pengikut anda sekali.",
-  "domain_pill.your_username": "Pengenal unik anda pada pelayan ini. Anda mungkin akan berkongsi nama pengguna dengan pengguna daripada pelayan lain.",
+  "dismissable_banner.explore_tags": "Tanda-tanda pagar ini daripada pelayar ini dan pelayar lain dalam rangkaian terpencar sedang hangat pada pelayar ini sekarang.",
+  "dismissable_banner.public_timeline": "Ini ialah pos awam terbaharu daripada orang di web sosial yang diikuti oleh orang di {domain}.",
   "embed.instructions": "Benam hantaran ini di laman sesawang anda dengan menyalin kod berikut.",
   "embed.preview": "Begini rupanya nanti:",
   "emoji_button.activity": "Aktiviti",
@@ -284,7 +226,6 @@
   "emoji_button.search_results": "Hasil carian",
   "emoji_button.symbols": "Simbol",
   "emoji_button.travel": "Kembara & Tempat",
-  "empty_column.account_featured": "Senarai ini kosong",
   "empty_column.account_hides_collections": "Pengguna ini telah memilih untuk tidak menyediakan informasi tersebut",
   "empty_column.account_suspended": "Akaun digantung",
   "empty_column.account_timeline": "Tiada hantaran di sini!",
@@ -302,7 +243,8 @@
   "empty_column.hashtag": "Belum ada apa-apa dengan tanda pagar ini.",
   "empty_column.home": "Garis masa laman utama anda kosong! Ikuti lebih ramai orang untuk mengisinya. {suggestions}",
   "empty_column.list": "Tiada apa-apa di senarai ini lagi. Apabila ahli senarai ini menerbitkan hantaran baharu, ia akan dipaparkan di sini.",
-  "empty_column.mutes": "Anda belum meredamkan sesiapa.",
+  "empty_column.lists": "Anda belum ada sebarang senarai. Apabila anda menciptanya, ia akan muncul di sini.",
+  "empty_column.mutes": "Anda belum membisukan sesiapa.",
   "empty_column.notifications": "Anda belum ada sebarang pemberitahuan. Apabila orang lain berinteraksi dengan anda, ia akan muncul di sini.",
   "empty_column.public": "Tiada apa-apa di sini! Tulis sesuatu secara awam, atau ikuti pengguna daripada pelayan lain secara manual untuk mengisinya",
   "error.unexpected_crash.explanation": "Disebabkan pepijat dalam kod kami atau masalah keserasian pelayar, halaman ini tidak dapat dipaparkan dengan betulnya.",
@@ -311,6 +253,7 @@
   "error.unexpected_crash.next_steps_addons": "Cuba nyahdaya pemalam dan segarkan semula halaman. Jika itu tidak membantu, anda masih boleh menggunakan Mastodon dengan pelayar yang berlainan atau aplikasi natif.",
   "errors.unexpected_crash.copy_stacktrace": "Salin surih tindanan ke papan keratan",
   "errors.unexpected_crash.report_issue": "Laporkan masalah",
+  "explore.search_results": "Hasil carian",
   "explore.suggested_follows": "Orang",
   "explore.title": "Terokai",
   "explore.trending_links": "Baru",
@@ -350,6 +293,7 @@
   "footer.about": "Perihal",
   "footer.directory": "Direktori profil",
   "footer.get_app": "Dapatkan app",
+  "footer.invite": "Jemput kenalan",
   "footer.keyboard_shortcuts": "Pintasan papan kekunci",
   "footer.privacy_policy": "Dasar privasi",
   "footer.source_code": "Lihat kod sumber",
@@ -369,7 +313,6 @@
   "hashtag.counter_by_uses": "{count, plural, other {{counter} siaran}}",
   "hashtag.counter_by_uses_today": "{count, plural, other {{counter} siaran}} hari ini",
   "hashtag.follow": "Ikuti hashtag",
-  "hashtag.mute": "Redamkan #{hashtag}",
   "hashtag.unfollow": "Nyahikut tanda pagar",
   "hashtags.and_other": "…dan {count, plural, other {# more}}",
   "home.column_settings.show_reblogs": "Tunjukkan galakan",
@@ -379,9 +322,17 @@
   "home.pending_critical_update.link": "Lihat pengemaskinian",
   "home.pending_critical_update.title": "Kemas kini keselamatan kritikal tersedia!",
   "home.show_announcements": "Tunjukkan pengumuman",
-  "interaction_modal.action.favourite": "Untuk sambung, anda perlu suka dari akaun anda.",
+  "interaction_modal.description.favourite": "Dengan akaun di Mastodon, anda boleh menyukai hantaran ini sebagai tanda penghargaan kepada pencipta dan menyimpannya untuk kemudian.",
+  "interaction_modal.description.follow": "Dengan akaun pada Mastodon, anda boleh mengikut {name} untuk menerima hantaran mereka di suapan rumah anda.",
+  "interaction_modal.description.reblog": "Dengan akaun pada Mastodon, anda boleh menggalakkan hantaran ini untuk dikongsi dengan pengikut anda.",
+  "interaction_modal.description.reply": "Dengan akaun pada Mastodon, anda boleh membalas kepada hantaran ini.",
+  "interaction_modal.login.action": "Bawa saya pulang rumah",
+  "interaction_modal.login.prompt": "Domain server utama anda, mis. mastodon.social",
+  "interaction_modal.no_account_yet": "Tak di Mastadon?",
   "interaction_modal.on_another_server": "Di pelayan lain",
   "interaction_modal.on_this_server": "Pada pelayan ini",
+  "interaction_modal.sign_in": "Anda tidak log masuk ke server ini. Di manakah akaun anda dihoskan?",
+  "interaction_modal.sign_in_hint": "Petua: Itulah tapak web tempat anda mendaftar. Jika anda tidak ingat, cari e-mel alu-aluan dalam peti masuk anda. Anda juga boleh memasukkan nama pengguna penuh anda! (cth. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Suka hantaran {name}",
   "interaction_modal.title.follow": "Ikuti {name}",
   "interaction_modal.title.reblog": "Galak hantaran {name}",
@@ -390,7 +341,7 @@
   "intervals.full.hours": "{number, plural, other {# jam}}",
   "intervals.full.minutes": "{number, plural, other {# minit}}",
   "keyboard_shortcuts.back": "to navigate back",
-  "keyboard_shortcuts.blocked": "Buka senarai pengguna tersekat",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "to boost",
   "keyboard_shortcuts.column": "Tumpu pada lajur",
   "keyboard_shortcuts.compose": "to focus the compose textarea",
@@ -407,7 +358,7 @@
   "keyboard_shortcuts.legend": "to display this legend",
   "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "to mention author",
-  "keyboard_shortcuts.muted": "Buka senarai pengguna teredam",
+  "keyboard_shortcuts.muted": "to open muted users list",
   "keyboard_shortcuts.my_profile": "to open your profile",
   "keyboard_shortcuts.notifications": "to open notifications column",
   "keyboard_shortcuts.open_media": "to open media",
@@ -429,35 +380,41 @@
   "limited_account_hint.action": "Paparkan profil",
   "limited_account_hint.title": "Profil ini telah disembunyikan oleh moderator {domain}.",
   "link_preview.author": "Dengan {name}",
+  "lists.account.add": "Tambah ke senarai",
+  "lists.account.remove": "Buang daripada senarai",
   "lists.delete": "Padam senarai",
   "lists.edit": "Sunting senarai",
+  "lists.edit.submit": "Ubah tajuk",
+  "lists.exclusive": "Sembunyikan pos ini dari rumah",
+  "lists.new.create": "Tambah senarai",
+  "lists.new.title_placeholder": "Tajuk senarai baharu",
   "lists.replies_policy.followed": "Sesiapa yang diikuti",
   "lists.replies_policy.list": "Ahli-ahli dalam senarai",
   "lists.replies_policy.none": "Tiada sesiapa",
+  "lists.replies_policy.title": "Tunjukkan balasan kepada:",
+  "lists.search": "Cari dalam kalangan orang yang anda ikuti",
+  "lists.subheading": "Senarai anda",
   "load_pending": "{count, plural, one {# item baharu} other {# item baharu}}",
   "loading_indicator.label": "Memuatkan…",
   "moved_to_account_banner.text": "Akaun anda {disabledAccount} kini dinyahdayakan kerana anda berpindah ke {movedToAccount}.",
-  "mute_modal.indefinite": "Sehingga dinyahredamkan",
-  "mute_modal.they_wont_know": "Dia tidak akan tahu bahawa dia telah diredam.",
-  "mute_modal.title": "Redamkan pengguna?",
   "navigation_bar.about": "Perihal",
   "navigation_bar.advanced_interface": "Buka dalam antara muka web lanjutan",
-  "navigation_bar.blocks": "Pengguna tersekat",
+  "navigation_bar.blocks": "Pengguna yang disekat",
   "navigation_bar.bookmarks": "Tanda buku",
   "navigation_bar.community_timeline": "Garis masa tempatan",
   "navigation_bar.compose": "Karang hantaran baharu",
   "navigation_bar.direct": "Sebutan peribadi",
   "navigation_bar.discover": "Teroka",
-  "navigation_bar.domain_blocks": "Domain tersekat",
+  "navigation_bar.domain_blocks": "Domain disekat",
   "navigation_bar.explore": "Teroka",
   "navigation_bar.favourites": "Sukaan",
-  "navigation_bar.filters": "Perkataan teredam",
+  "navigation_bar.filters": "Perkataan yang dibisukan",
   "navigation_bar.follow_requests": "Permintaan ikutan",
   "navigation_bar.followed_tags": "Ikuti hashtag",
   "navigation_bar.follows_and_followers": "Ikutan dan pengikut",
   "navigation_bar.lists": "Senarai",
   "navigation_bar.logout": "Log keluar",
-  "navigation_bar.mutes": "Pengguna teredam",
+  "navigation_bar.mutes": "Pengguna yang dibisukan",
   "navigation_bar.opened_in_classic_interface": "Kiriman, akaun dan halaman tertentu yang lain dibuka secara lalai di antara muka web klasik.",
   "navigation_bar.personal": "Peribadi",
   "navigation_bar.pins": "Hantaran disemat",
@@ -470,15 +427,11 @@
   "notification.admin.sign_up": "{name} mendaftar",
   "notification.favourite": "{name} menyukai hantaran anda",
   "notification.favourite.name_and_others_with_link": "{name} dan <a>{count, plural, other {# orang lain}}</a> telah suka hantaran anda",
-  "notification.favourite_pm": "{name} menyukai sebutan persendirian anda",
-  "notification.favourite_pm.name_and_others_with_link": "{name} dan <a>{count, plural, other {# orang lain}}</a> telah suka sebutan persendirian anda",
   "notification.follow": "{name} mengikuti anda",
   "notification.follow_request": "{name} meminta untuk mengikuti anda",
   "notification.own_poll": "Undian anda telah tamat",
   "notification.reblog": "{name} menggalak hantaran anda",
   "notification.reblog.name_and_others_with_link": "{name} dan <a>{count, plural, other {# orang lain}}</a> telah galakkan hantaran anda",
-  "notification.relationships_severance_event.domain_block": "Pentadbir dari {from} telah menyekat {target} termasuk {followersCount} pengikut anda dan {followingCount, plural, other {# akaun}} ikutan anda.",
-  "notification.relationships_severance_event.user_domain_block": "Anda telah menyekat {target} termasuk {followersCount} pengikut anda dan {followingCount, plural, other {# akaun}} ikutan anda.",
   "notification.status": "{name} baru sahaja mengirim hantaran",
   "notification.update": "{name} menyunting hantaran",
   "notifications.clear": "Buang pemberitahuan",
@@ -515,12 +468,38 @@
   "notifications_permission_banner.enable": "Dayakan pemberitahuan atas meja",
   "notifications_permission_banner.how_to_control": "Untuk mendapat pemberitahuan ketika Mastodon tidak dibuka, dayakan pemberitahuan atas meja. Anda boleh mengawal jenis interaksi mana yang menjana pemberitahuan atas meja melalui butang {icon} di atas setelah ia didayakan.",
   "notifications_permission_banner.title": "Jangan terlepas apa-apa",
+  "onboarding.action.back": "Bawa saya kembali",
+  "onboarding.actions.back": "Bawa saya kembali",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "Hello #Mastodon!",
   "onboarding.follows.empty": "Malangnya, tiada hasil dapat ditunjukkan sekarang. Anda boleh cuba menggunakan carian atau menyemak imbas halaman teroka untuk mencari orang untuk diikuti atau cuba lagi kemudian.",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
   "onboarding.profile.display_name": "Nama paparan",
   "onboarding.profile.display_name_hint": "Nama penuh anda atau nama anda yang menyeronokkan…",
   "onboarding.profile.note_hint": "Anda boleh @menyebut orang lain atau #hashtags…",
   "onboarding.profile.save_and_continue": "Simpan dan teruskan",
   "onboarding.profile.upload_avatar": "Muat naik gambar profil",
+  "onboarding.share.lead": "Beritahu orang ramai bagaimana mereka boleh menemui anda di Mastodon!",
+  "onboarding.share.message": "Saya {username} di #Mastodon! Jom ikut saya di {url}",
+  "onboarding.share.next_steps": "Langkah seterusnya yang mungkin:",
+  "onboarding.share.title": "Berkongsi profil anda",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "Anda telah berjaya!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Buat pos pertama anda",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
+  "onboarding.tips.2fa": "<strong>Tahukah anda?</strong> Anda boleh melindungi akaun anda dengan menyediakan pengesahan dua faktor dalam tetapan akaun anda. Ia berfungsi dengan mana-mana aplikasi TOTP pilihan anda, tiada nombor telefon diperlukan!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Tahukah anda?</strong> Memandangkan Mastodon tidak berpusat, beberapa profil yang anda temui akan dihoskan pada server selain anda. Namun anda boleh berinteraksi dengan mereka dengan lancar! Server mereka berada di separuh kedua nama pengguna mereka!",
+  "onboarding.tips.migration": "<strong>Tahukah anda?</strong> Jika anda rasa {domain} bukan pilihan server yang bagus untuk anda pada masa hadapan, anda boleh beralih ke server Mastodon yang lain tanpa kehilangan pengikut anda. Anda juga boleh mengehoskan server anda sendiri!",
+  "onboarding.tips.verification": "<strong>Tahukah anda?</strong> Anda boleh mengesahkan akaun anda dengan meletakkan pautan ke profil Mastodon anda pada tapak web anda sendiri dan menambah tapak web pada profil anda. Tiada bayaran atau dokumen diperlukan!",
   "password_confirmation.exceeds_maxlength": "Pengesahan kata laluan melebihi panjang kata laluan maksimum",
   "password_confirmation.mismatching": "Pengesahan kata laluan tidak sepadan",
   "picture_in_picture.restore": "Letak semula",
@@ -536,6 +515,7 @@
   "poll_button.remove_poll": "Buang undian",
   "privacy.change": "Ubah privasi hantaran",
   "privacy.direct.long": "Semua orang yang disebutkan dalam siaran",
+  "privacy.direct.short": "Orang tertentu",
   "privacy.private.long": "Pengikut anda sahaja",
   "privacy.private.short": "Pengikut",
   "privacy.public.short": "Awam",
@@ -543,6 +523,8 @@
   "privacy_policy.title": "Dasar Privasi",
   "recommended": "Disyorkan",
   "refresh": "Muat semula",
+  "regeneration_indicator.label": "Memuatkan…",
+  "regeneration_indicator.sublabel": "Suapan rumah anda sedang disediakan!",
   "relative_time.days": "{number}h",
   "relative_time.full.days": "{number, plural, other {# hari}} yang lalu",
   "relative_time.full.hours": "{number, plural, other {# jam}} yang lalu",
@@ -558,7 +540,7 @@
   "reply_indicator.cancel": "Batal",
   "reply_indicator.poll": "Undian",
   "report.block": "Sekat",
-  "report.block_explanation": "Anda tidak akan melihat hantarannya. Dia tidak akan dapat melihat hantaran anda atau mengikuti anda. Dia akan sedar bahawa dia disekat.",
+  "report.block_explanation": "Anda tidak akan melihat hantaran mereka. Mereka tidak dapat melihat hantaran anda atau mengikuti anda. Mereka akan sedar bahawa mereka disekat.",
   "report.categories.legal": "Sah",
   "report.categories.other": "Lain-lain",
   "report.categories.spam": "Spam",
@@ -571,7 +553,7 @@
   "report.comment.title": "Adakah ada hal-hal lain yang perlu kita ketahui?",
   "report.forward": "Panjangkan ke {target}",
   "report.forward_hint": "Akaun ini daripada pelayan lain. Hantar salinan laporan yang ditanpanamakan ke sana juga?",
-  "report.mute": "Redam",
+  "report.mute": "Bisukan",
   "report.mute_explanation": "Anda tidak akan melihat siaran mereka. Mereka masih boleh mengikuti dan melihat siaran anda dan tidak akan mengetahui bahawa mereka telah dibisukan.",
   "report.next": "Seterusnya",
   "report.placeholder": "Ulasan tambahan",
@@ -621,8 +603,10 @@
   "search_results.accounts": "Profil",
   "search_results.all": "Semua",
   "search_results.hashtags": "Tanda pagar",
+  "search_results.nothing_found": "Tidak dapat menemui apa-apa untuk istilah carian tersebut",
   "search_results.see_all": "Lihat semua",
   "search_results.statuses": "Hantaran",
+  "search_results.title": "Mencari {q}",
   "server_banner.about_active_users": "Pengguna pelayan ini sepanjang 30 hari yang lalu (Pengguna Aktif Bulanan)",
   "server_banner.active_users": "pengguna aktif",
   "server_banner.administered_by": "Ditadbir oleh:",
@@ -655,8 +639,8 @@
   "status.media_hidden": "Media disembunyikan",
   "status.mention": "Sebut @{name}",
   "status.more": "Lagi",
-  "status.mute": "Redamkan @{name}",
-  "status.mute_conversation": "Redamkan perbualan",
+  "status.mute": "Bisukan @{name}",
+  "status.mute_conversation": "Bisukan perbualan",
   "status.open": "Kembangkan hantaran ini",
   "status.pin": "Semat di profil",
   "status.pinned": "Hantaran disemat",
@@ -668,7 +652,6 @@
   "status.reblogs.empty": "Tiada sesiapa yang galakkan hantaran ini. Apabila ada yang galakkan, hantaran akan muncul di sini.",
   "status.redraft": "Padam & rangka semula",
   "status.remove_bookmark": "Buang tanda buku",
-  "status.remove_favourite": "Padam dari sukaan",
   "status.replied_to": "Menjawab kepada {name}",
   "status.reply": "Balas",
   "status.replyAll": "Balas ke bebenang",
@@ -682,7 +665,7 @@
   "status.translate": "Menterjemah",
   "status.translated_from_with": "Diterjemah daripada {lang} dengan {provider}",
   "status.uncached_media_warning": "Pratonton tidak tersedia",
-  "status.unmute_conversation": "Nyahredamkan perbualan",
+  "status.unmute_conversation": "Nyahbisukan perbualan",
   "status.unpin": "Nyahsemat daripada profil",
   "subscribed_languages.lead": "Hanya hantaran dalam bahasa-bahasa terpilih akan dipaparkan pada garis masa rumah dan senarai selepas perubahan. Pilih tiada untuk menerima hantaran dalam semua bahasa.",
   "subscribed_languages.save": "Simpan perubahan",
@@ -704,9 +687,23 @@
   "upload_button.label": "Tambah fail imej, video atau audio",
   "upload_error.limit": "Sudah melebihi had muat naik.",
   "upload_error.poll": "Tidak boleh memuat naik fail bersama undian.",
+  "upload_form.audio_description": "Jelaskan untuk orang yang ada masalah pendengaran",
+  "upload_form.description": "Jelaskan untuk orang yang ada masalah penglihatan",
   "upload_form.drag_and_drop.instructions": "Untuk mengangkat lampiran media, tekan jarak atau enter. Ketika menarik, gunakan kekunci anak panah untuk menggerakkan lampiran media pada mana-mana arah. Tekan jarak atau enter untuk melepaskan lampiran media pada kedudukan baharunya, atau tekan keluar untuk batalkan.",
   "upload_form.drag_and_drop.on_drag_cancel": "Seretan dibatalkan. Lampiran media {item} dilepaskan.",
   "upload_form.edit": "Sunting",
+  "upload_form.thumbnail": "Ubah gambar kecil",
+  "upload_form.video_description": "Jelaskan untuk orang yang ada masalah pendengaran atau penglihatan",
+  "upload_modal.analyzing_picture": "Menganalisis gambar…",
+  "upload_modal.apply": "Guna",
+  "upload_modal.applying": "Terapkan…",
+  "upload_modal.choose_image": "Pilih imej",
+  "upload_modal.description_placeholder": "Seekor rubah perang pantas melompat merentasi anjing yang pemalas",
+  "upload_modal.detect_text": "Kesan teks daripada gambar",
+  "upload_modal.edit_media": "Sunting media",
+  "upload_modal.hint": "Klik atau seret bulatan di pratonton untuk memilih titik tumpu yang akan kelihatan pada semua gambar kecil.",
+  "upload_modal.preparing_ocr": "Mempersiapkan OCR…",
+  "upload_modal.preview_label": "Pratonton ({ratio})",
   "upload_progress.label": "Memuat naik...",
   "upload_progress.processing": "Memproses…",
   "username.taken": "Nama pengguna tersebut sudah digunakan. Sila cuba lagi",
@@ -716,8 +713,8 @@
   "video.expand": "Besarkan video",
   "video.fullscreen": "Skrin penuh",
   "video.hide": "Sembunyikan video",
-  "video.mute": "Redam",
+  "video.mute": "Bisukan bunyi",
   "video.pause": "Jeda",
   "video.play": "Main",
-  "video.unmute": "Nyahredam"
+  "video.unmute": "Nyahbisukan bunyi"
 }
diff --git a/app/javascript/mastodon/locales/my.json b/app/javascript/mastodon/locales/my.json
index 362537edeb..c97de73335 100644
--- a/app/javascript/mastodon/locales/my.json
+++ b/app/javascript/mastodon/locales/my.json
@@ -28,6 +28,7 @@
   "account.endorse": "အကောင့်ပရိုဖိုင်တွင်ဖော်ပြပါ",
   "account.featured_tags.last_status_at": "နောက်ဆုံးပို့စ်ကို {date} တွင် တင်ခဲ့သည်။",
   "account.featured_tags.last_status_never": "ပို့စ်တင်ထားခြင်းမရှိပါ",
+  "account.featured_tags.title": "ဖော်ပြထားသောဟက်ရှ်တက်ခ်များ",
   "account.follow": "စောင့်ကြည့်",
   "account.followers": "စောင့်ကြည့်သူများ",
   "account.followers.empty": "ဤသူကို စောင့်ကြည့်သူ မရှိသေးပါ။",
@@ -92,6 +93,7 @@
   "bundle_column_error.routing.body": "ရှာနေသောအရာမှာမရှိပါ။ URL မှန်မမှန်ပြန်စစ်ပေးပါ",
   "bundle_column_error.routing.title": "လေးသုံညလေး",
   "bundle_modal_error.close": "ပိတ်ပါ",
+  "bundle_modal_error.message": "ဤဝက်ဘ်စာမျက်နှာအား ဖွင့်နေစဥ် အမှားတစ်ခု ဖြစ်ပေါ်ခဲ့သည်။",
   "bundle_modal_error.retry": "ထပ်မံကြိုးစားပါ",
   "closed_registrations.other_server_instructions": "Mastodon ကို ဗဟိုချုပ်ကိုင်မှု လျှော့ချထားသောကြောင့် သင်သည် အခြားဆာဗာတစ်ခုပေါ်တွင် အကောင့်တစ်ခု ဖန်တီးနိုင်ပြီး ဤတစ်ခုနှင့် အပြန်အလှန် တုံ့ပြန်ဆဲဖြစ်သည်။",
   "closed_registrations_modal.description": "{domain} တွင် အကောင့်တစ်ခုဖန်တီးခြင်းသည် လောလောဆယ်မဖြစ်နိုင်ပါ၊ သို့သော် Mastodon ကိုအသုံးပြုရန်အတွက် သင်သည် {domain} တွင် အထူးအကောင့်တစ်ခုမလိုအပ်ကြောင်း ကျေးဇူးပြု၍ သတိရပါ။",
@@ -176,6 +178,10 @@
   "disabled_account_banner.text": "{disabledAccount} သည်လတ်တလောပိတ်ခံထားရသည်",
   "dismissable_banner.community_timeline": "အကောင့်များမှ လတ်တလောတင်ထားသည့်အများမြင်ပို့စ်များမှာ {domain} တွင် တင်ထားသောပို့စ်များဖြစ်သည်။",
   "dismissable_banner.dismiss": "ပယ်ရန်",
+  "dismissable_banner.explore_links": "ဤသတင်းများကို ယခုအချိန်တွင် ဗဟိုချုပ်ကိုင်မှုလျှော့ချထားသော ကွန်ရက်၏ အခြားဆာဗာများမှ လူများက ပြောဆိုနေကြပါသည်။",
+  "dismissable_banner.explore_statuses": "ဒီ​နေ့အတွက် လူမှုကွန်ရက်​ပေါ်မှာ စိတ်ဝင်စားမှူများ​နေတဲ့ ပို့စ်​တွေရှိပါတယ်။ပြန်မျှ​ဝေမှုနဲ့ကြယ်ပွင့်များ တဲ့ပိုစ့်အသစ်​တွေကို ပိုမြင်ရမှာပါ။.",
+  "dismissable_banner.explore_tags": "ဤ hashtag များသည် ယခုအချိန်တွင် ဗဟိုချုပ်ကိုင်မှုလျှော့ချထားသော ကွန်ရက်၏ အခြားဆာဗာများပေါ်ရှိ လူများကြားတွင် ဆွဲဆောင်မှုရှိလာပါသည်",
+  "dismissable_banner.public_timeline": "ဤအရာများသည် {domain} ရှိလူများ လိုက်နာသော လူမှုဝဘ်ပေါ်ရှိ လူများထံမှ လတ်တလော အများမြင်ပို့စ်များဖြစ်သည်။",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "ဒါမျိုးမြင်ရမှာပါ။",
   "emoji_button.activity": "လုပ်ဆောင်ချက်",
@@ -209,6 +215,7 @@
   "empty_column.hashtag": "ဤ hashtag တွင် မည်သည့်အရာမျှ မရှိသေးပါ။",
   "empty_column.home": "သင့်ပင်မစာမျက်နှာမှာ အလွတ်ဖြစ်နေပါသည်။ ဖြည့်ရန်အတွက် လူများကို စောင့်ကြည့်ပါ {suggestions}",
   "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
+  "empty_column.lists": "သင့်တွင် List မရှိသေးပါ။ List အသစ်ဖွင့်လျှင် ဤနေရာတွင်ကြည့်ရှုနိုင်မည်",
   "empty_column.mutes": "ပိတ်ထားသောအကောင့်များမရှိသေးပါ",
   "empty_column.notifications": "သတိပေးချက်မရှိသေးပါ။ သတိပေးချက်အသစ်ရှိလျှင် ဤနေရာတွင်ကြည့်ရှုနိုင်သည်",
   "empty_column.public": "ဤနေရာတွင် မည်သည့်အရာမျှမရှိပါ။ တစ်ခုခုရေးပါ သို့မဟုတ် ဖြည့်စွက်ရန်အတွက် အခြားဆာဗာ အသုံးပြုသူများကို စောင့်ကြည့်ပါ။",
@@ -218,6 +225,7 @@
   "error.unexpected_crash.next_steps_addons": "၎င်းတို့ကို ပိတ်ပြီး စာမျက်နှာကို ပြန်လည်စတင်ကြည့်ပါ။ အဆင်မပြေပါက အခြားဘရောက်ဆာ သို့မဟုတ် မူရင်းအက်ပ်မှတစ်ဆင့် Mastodon ကို ဆက်ပြီးအသုံးပြုနိုင်ပါမည်။",
   "errors.unexpected_crash.copy_stacktrace": "stacktrace ကို ကလစ်ဘုတ်သို့ ကူးယူပါ",
   "errors.unexpected_crash.report_issue": "အဆင်မပြေမှုကို တိုင်ကြားရန်",
+  "explore.search_results": "ရှာဖွေမှုရလဒ်များ",
   "explore.suggested_follows": "လူများ",
   "explore.title": "စူးစမ်းရန်",
   "explore.trending_links": "သတင်းများ",
@@ -249,6 +257,7 @@
   "footer.about": "အကြောင်း",
   "footer.directory": "ပရိုဖိုင်များလမ်းညွှန်",
   "footer.get_app": "အက်ပ်ကို ရယူပါ",
+  "footer.invite": "လူများကို ဖိတ်ပါ",
   "footer.keyboard_shortcuts": "ကီးဘုတ်အမြန်ခလုတ်များ",
   "footer.privacy_policy": "ကိုယ်ရေးအချက်အလက်မူဝါဒ",
   "footer.source_code": "မူရင်းကုဒ်အားကြည့်ရှုမည်",
@@ -277,8 +286,17 @@
   "home.pending_critical_update.link": "အပ်ဒိတ်များကြည့်ရန်",
   "home.pending_critical_update.title": "အရေးကြီးသည့် လုံခြုံရေးအပ်ဒိတ် ရနိုင်ပါမည်။",
   "home.show_announcements": "ကြေညာချက်များကို ပြပါ",
+  "interaction_modal.description.favourite": "Mastodon အကောင့်ဖြင့် ဤပို့စ်ကို သင် favorite ပြုလုပ်ကြောင်း စာရေးသူအား အသိပေးပြီး နောက်ပိုင်းတွင် သိမ်းဆည်းနိုင်သည်။",
+  "interaction_modal.description.follow": "Mastodon အကောင့်ဖြင့် သင်၏ ပင်မစာမျက်နှာတွင် ၎င်းတို့၏ ပို့စ်များကို ရရှိရန်အတွက် {name} ကို စောင့်ကြည့်နိုင်ပါသည်။",
+  "interaction_modal.description.reblog": "Mastodon အကောင့်တစ်ခုဖြင့် သင်၏စောင့်ကြည့်သူများကို မျှဝေရန်အတွက် ဤပို့စ်ကို Boost လုပ်ပါ။",
+  "interaction_modal.description.reply": "Mastodon အကောင့်တစ်ခုဖြင့် သင် ဤပို့စ်ကို တုံ့ပြန်နိုင်ပါသည်။",
+  "interaction_modal.login.action": "ပင်မစာမျက်နှာသို့",
+  "interaction_modal.login.prompt": "သင့်ပင်မဆာဗာ၏ ဒိုမိန်း၊ ဥပမာ၊ mastodon.social",
+  "interaction_modal.no_account_yet": "Mastodon မှာ မဟုတ်ဘူးလား။",
   "interaction_modal.on_another_server": "တခြားဆာဗာပေါ်တွင်",
   "interaction_modal.on_this_server": "ဤဆာဗာတွင်",
+  "interaction_modal.sign_in": "သင်သည် ဤဆာဗာတွင် လော့ဂ်အင်မဝင်ပါ။ သင့်အကောင့်ကို မည်သည့်ဆာဗာတွင် ဖွင့်ထားပါသလဲ။",
+  "interaction_modal.sign_in_hint": "အကြံပြုချက် - ၎င်းသည် သင် အကောင့်ဖွင့်ထားသည့် ဝဘ်ဆိုက်ဖြစ်သည်။ မမှတ်မိပါက သင့် ဝင်စာပုံးရှိ welcome e-mail ကို ရှာပါ။ သင့်အသုံးပြုသူအမည်အပြည့်အစုံကိုလည်း ထည့်သွင်းနိုင်သည်။ (ဥပမာ @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Favorite {name} ၏ ပို့စ်",
   "interaction_modal.title.follow": "{name} ကို စောင့်ကြည့်မယ်",
   "interaction_modal.title.reblog": "{name} ၏ ပို့စ်ကို Boost လုပ်ပါ",
@@ -326,11 +344,20 @@
   "limited_account_hint.action": "ဘာပဲဖြစ်ဖြစ် ပရိုဖိုင်ကို ပြပါ",
   "limited_account_hint.title": "ဤပရိုဖိုင်ကို {domain} ၏ စိစစ်သူများမှ ဖျောက်ထားသည်။",
   "link_preview.author": "{name} ဖြင့်",
+  "lists.account.add": "စာရင်းထဲသို့ထည့်ပါ",
+  "lists.account.remove": "စာရင်းမှ ဖယ်ရှားလိုက်ပါ။",
   "lists.delete": "စာရင်းကိုဖျက်ပါ",
   "lists.edit": "စာရင်းကိုပြင်ဆင်ပါ",
+  "lists.edit.submit": "ခေါင်းစဥ် ပြောင်းလဲရန်",
+  "lists.exclusive": "ဤပို့စ်များကို ပင်မစာမျက်နှာတွင် မပြပါနှင့်",
+  "lists.new.create": "စာရင်းသွင်းပါ",
+  "lists.new.title_placeholder": "စာရင်းသစ်ခေါင်းစဥ်",
   "lists.replies_policy.followed": "မည်သည့်စောင့်ကြည့်သူမဆို",
   "lists.replies_policy.list": "စာရင်းထဲမှ အဖွဲ့ဝင်များ",
   "lists.replies_policy.none": "တစ်ယောက်မှမရှိပါ",
+  "lists.replies_policy.title": "ပြန်စာများကို ပြရန် -",
+  "lists.search": "မိမိဖောလိုးထားသူများမှရှာဖွေမည်",
+  "lists.subheading": "သင့်၏စာရင်းများ",
   "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "လုပ်ဆောင်နေသည်…",
   "moved_to_account_banner.text": "{movedToAccount} အကောင့်သို့ပြောင်းလဲထားသဖြင့် {disabledAccount} အကောင့်မှာပိတ်ထားသည်",
@@ -403,7 +430,14 @@
   "notifications_permission_banner.enable": "ဒက်စ်တော့ အသိပေးချက်များကို ဖွင့်ပါ",
   "notifications_permission_banner.how_to_control": "Mastodon မဖွင့်သည့်အခါ အကြောင်းကြားချက်များကို လက်ခံရယူရန်၊ ဒက်စ်တော့ အသိပေးချက်များကို ဖွင့်ပါ။ ၎င်းတို့ကို ဖွင့်ပြီးသည်နှင့် ၎င်းတို့ကို ဖွင့်ပြီးသည်နှင့် အထက် {icon} ခလုတ်မှ ဒက်စ်တော့ အသိပေးချက်များကို ထုတ်ပေးသည့် အပြန်အလှန်တုံ့ပြန်မှု အမျိုးအစားများကို သင် အတိအကျ ထိန်းချုပ်နိုင်သည်။",
   "notifications_permission_banner.title": "လက်လွတ်မခံပါနှင့်",
+  "onboarding.action.back": "ပြန်ယူရန်",
+  "onboarding.actions.back": "ပြန်ယူရန်",
+  "onboarding.actions.go_to_explore": "ခေတ်စားနေသည်များကို ကြည့်ပါ",
+  "onboarding.actions.go_to_home": "ပင်မစာမျက်နှာသို့ သွားပါ",
+  "onboarding.compose.template": "မင်္ဂလာပါ #Mastodon",
   "onboarding.follows.empty": "ယခုအချိန် မည်သည့်ရလဒ်ကိုမျှ မပြသနိုင်ပါ။ လူများကိုစောင့်ကြည့်ရန်အတွက် Explore စာမျက်နှာကို အသုံးပြု၍ စမ်းကြည့်နိုင်သည် သို့မဟုတ် နောက်မှ ထပ်စမ်းကြည့်ပါ။",
+  "onboarding.follows.lead": "သင့်ကိုယ်ပိုင်ပို့စ်များ တင်နိုင်သည်။ သင်စောင့်ကြည့်သူ များလေလေ၊ စိတ်ဝင်စားစရာကောင်းသောပို့စ်များ တွေ့ရလေဖြစ်သည်။ ဤပရိုဖိုင်များမှာ ကောင်းမွန်သောအစပြုမှုတစ်ခုဖြစ်ပြီး ၎င်းတို့ကိုစောင့်ကြည့်ခြင်းမှလည်း အချိန်မရွေး ပယ်ဖျက်နိုင်ပါသည်။",
+  "onboarding.follows.title": "Mastodon တွင် ရေပန်းစားခြင်း",
   "onboarding.profile.discoverable": "ပရိုဖိုင် ရှာဖွေနိုင်ပါမည်",
   "onboarding.profile.display_name": "ဖော်ပြမည့်အမည်",
   "onboarding.profile.display_name_hint": "သင့်အမည်အပြည့်အစုံ သို့မဟုတ် သင့်အမည်ပြောင်။",
@@ -411,6 +445,25 @@
   "onboarding.profile.save_and_continue": "သိမ်းပြီး ဆက်လုပ်ပါ",
   "onboarding.profile.title": "ပရိုဖိုင်စနစ် ထည့်သွင်းခြင်း",
   "onboarding.profile.upload_avatar": "ပရိုဖိုင်ပုံ အပ်လုဒ်လုပ်ပါ",
+  "onboarding.share.lead": "Mastodon တွင် သင့်အား မည်သို့ရှာတွေ့နိုင်သည်ကို အသိပေးပါ။",
+  "onboarding.share.message": "Mastodon ရှိ ကျွန်ုပ်၏အမည်မှာ {username} ဖြစ်သည်။ ကျွန်ုပ်ကို {url} တွင် စောင့်ကြည့်နိုင်ပါသည်",
+  "onboarding.share.next_steps": "ဖြစ်နိုင်ချေရှိသော နောက်အဆင့်များ -",
+  "onboarding.share.title": "သင့်ပရိုဖိုင်ကို မျှဝေပါ",
+  "onboarding.start.lead": "သင့် Mastodon အကောင့်အသစ်မှာ အသုံးပြုနိုင်ပါပြီ။ အကောင်းဆုံးဖြစ်အောင် သင်ပြုလုပ်နိုင်သည် -",
+  "onboarding.start.skip": "ရှေ့ကို ကျော်သွားချင်ပါသလား။",
+  "onboarding.start.title": "ပြုလုပ်ပြီးပြီ။",
+  "onboarding.steps.follow_people.body": "သင်သည် ကိုယ်ပိုင်ပို့စ်များ တင်နိုင်သည်။ စိတ်ဝင်စားသူများနဲ့ အပြန်အလှန်စောင့်ကြည့်နိုင်ပါပြီ။",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "ကမ္ဘာကြီးကို နှုတ်ဆက်လိုက်ပါ။",
+  "onboarding.steps.publish_status.title": "ပထမဆုံးပို့စ်တင်လိုက်ပါ။",
+  "onboarding.steps.setup_profile.body": "အခြားသူများမှာလည်း သင်နှင့် အပြန်အလှန် တုံ့ပြန်နိုင်ပါသည်။",
+  "onboarding.steps.setup_profile.title": "သင့်ပရိုဖိုင်ကို စိတ်ကြိုက်ပြင်ဆင်လိုက်ပါ",
+  "onboarding.steps.share_profile.body": "Mastodon တွင် သင့်ကို သူငယ်ချင်းများ မည်သို့ရှာတွေ့နိုင်သည်ကို အသိပေးပါ။",
+  "onboarding.steps.share_profile.title": "သင့်ပရိုဖိုင်ကို မျှဝေပါ",
+  "onboarding.tips.2fa": "<strong>သင်သိပါသလား။</strong> သင့်အကောင့်သတ်မှတ်ချက်များတွင် နှစ်ဆင့်ခံလုံခြုံရေးစနစ် ထည့်သွင်းခြင်းဖြင့် သင့်အကောင့်ကို လုံခြုံစေနိုင်ပါသည်။ ထိုစနစ်မှာ သင်နှစ်သက်ရာ TOTP အက်ပ်နှင့် အလုပ်လုပ်ပြီး ဖုန်းနံပါတ်ထည့်ရန်မလိုပါ။",
+  "onboarding.tips.accounts_from_other_servers": "<strong>သင်သိပါသလား?</strong> Mastodon ကို ဗဟိုချုပ်ကိုင်မှု လျှော့ချထားခြင်းကြောင့် သင်တွေ့မြင်ရသော ပရိုဖိုင်အချို့မှာ အခြားဆာဗာများမှ အသုံးပြုနေခြင်းဖြစ်သည်။ သို့သော် သင့်အနေဖြင့် ၎င်းတို့နှင့် အပြန်အလှန် တုံ့ပြန်နိုင်သည်။ အသုံးပြုသူအမည်၏အနောက်တွင် ရေးထားသောအရာမှာ ၎င်းတို့၏ဆာဗာအမည်ဖြစ်သည်။",
+  "onboarding.tips.migration": "<strong>သင်သိပါသလား?</strong> အကယ်၍ {domain} သည် နောင်တွင် သင့်အတွက် အဆင်မပြေပါက သင့်စောင့်ကြည့်သူများကို မဆုံးရှုံးစေဘဲ အခြား Mastodon ဆာဗာသို့ ပြောင်းရွှေ့နိုင်ပါသည်။ သင့် ကိုယ်ပိုင်ဆာဗာတစ်လုံး ပြုလုပ်၍ပင် ဆောင်ရွက်နိုင်ပါသည်။",
+  "onboarding.tips.verification": "<strong>သင်သိပါသလား။</strong> သင့်ဝဘ်ဆိုက်တွင် Mastodon ပရိုဖိုင်အတွက် လင့်ခ်ထည့်ထားပြီး သင့်ပရိုဖိုင်တွင် ဝဘ်ဆိုက်ထည့်ထားခြင်းဖြင့် သင့်အကောင့်ကို အတည်ပြုနိုင်သည်။ အခကြေးငွေ သို့မဟုတ် စာရွက်စာတမ်းများ မလိုအပ်ပါ။",
   "password_confirmation.exceeds_maxlength": "စကားဝှက်အတည်ပြုခြင်းတွင် အများဆုံးစကားဝှက်အရှည်ထက် ကျော်လွန်နေပါသည်",
   "password_confirmation.mismatching": "စကားဝှက်အတည်ပြုချက်မှာ မကိုက်ညီပါ",
   "picture_in_picture.restore": "ပြန်ထားပါ",
@@ -430,6 +483,8 @@
   "privacy_policy.title": "ကိုယ်ရေးအချက်အလက်မူဝါဒ",
   "recommended": "အကြံပြုသည်",
   "refresh": "ပြန်လည်စတင်ပါ",
+  "regeneration_indicator.label": "လုပ်ဆောင်နေသည်…",
+  "regeneration_indicator.sublabel": "သင့်ပင်မစာမျက်နှာကို ပြင်ဆင်နေပါသည်။",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
   "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
@@ -507,8 +562,10 @@
   "search_results.accounts": "စာမျက်နှာ",
   "search_results.all": "အားလုံး",
   "search_results.hashtags": "ဟက်ရှ်တက်များ",
+  "search_results.nothing_found": "ရှာဖွေလိုသောအရာမရှိပါ",
   "search_results.see_all": "အားလုံးကို ကြည့်ရန်",
   "search_results.statuses": "ပို့စ်တင်မယ်",
+  "search_results.title": "{q} ကို ရှာပါ",
   "server_banner.about_active_users": "ပြီးခဲ့သည့် ရက်ပေါင်း ၃၀ အတွင်း ဤဆာဗာကို အသုံးပြုသူများ (လအလိုက် လက်ရှိအသုံးပြုသူများ)",
   "server_banner.active_users": "လက်ရှိအသုံးပြုသူများ",
   "server_banner.administered_by": "မှ စီမံခန့်ခွဲသည် -",
@@ -587,7 +644,21 @@
   "upload_button.label": "ပုံများ၊ ဗီဒီယို သို့မဟုတ် အသံဖိုင်တစ်ခု ထည့်ပါ",
   "upload_error.limit": "ဖိုင်အများဆုံးတင်နိုင်မည့်ကန့်သတ်ချက်ကို ကျော်သွားပါပြီ။",
   "upload_error.poll": "စစ်တမ်းနှင့်အတူဖိုင်များတင်ခွင့်မပြုပါ",
+  "upload_form.audio_description": "အကြားအာရုံချို့ယွင်းသော ခက်ခဲသောသူများအတွက် ဖော်ပြထားသည်",
+  "upload_form.description": "အမြင်အာရုံချို့ယွင်းသော ခက်ခဲသောသူများအတွက် ဖော်ပြထားသည်",
   "upload_form.edit": "ပြင်ရန်",
+  "upload_form.thumbnail": "ပုံသေးကို ပြောင်းပါ",
+  "upload_form.video_description": "အမြင်အာရုံနှင့်အကြားအာရုံ ချို့ယွင်းသော ခက်ခဲသောသူများအတွက် ဖော်ပြထားသည်",
+  "upload_modal.analyzing_picture": "ပုံအား ပိုင်းခြားစိတ်ဖြာနေသည်...",
+  "upload_modal.apply": "သုံးပါ",
+  "upload_modal.applying": "အသုံးချနေသည်...",
+  "upload_modal.choose_image": "ပုံရွေးပါ",
+  "upload_modal.description_placeholder": "သီဟိုဠ်မှ ဉာဏ်ကြီးရှင်သည် အာယုဝဍ္ဎနဆေးညွှန်းစာကို ဇလွန်ဈေးဘေး ဗာဒံပင်ထက် အဓိဋ္ဌာန်လျက် ဂဃနဏဖတ်ခဲ့သည်",
+  "upload_modal.detect_text": "ပုံမှစာသားကို ရှာဖွေပါ",
+  "upload_modal.edit_media": "မီဒီယာကို ပြင်ဆင်ရန်",
+  "upload_modal.hint": "ပုံသေးအားလုံးတွင် အမြဲတမ်းကြည့်ရှုနိုင်သည့် focal point ကို ရွေးချယ်ရန် Preview ပေါ်ရှိ စက်ဝိုင်းကို နှိပ်ပါ သို့မဟုတ် ဖိဆွဲပါ။",
+  "upload_modal.preparing_ocr": "OCR ပြင်ဆင်နေသည်…",
+  "upload_modal.preview_label": "({ratio}) အစမ်းကြည့်ရှုရန်",
   "upload_progress.label": "တင်နေသည်...",
   "upload_progress.processing": "လုပ်ဆောင်နေသည်…",
   "username.taken": "ထိုအသုံးပြုသူအမည်မှာ အသုံးပြုထားပြီးဖြစ်ပါသည်။ နောက်တစ်ခု စမ်းကြည့်ပါ",
@@ -597,6 +668,8 @@
   "video.expand": "ဗီဒီယိုကို ချဲ့ပါ",
   "video.fullscreen": "မျက်နှာပြင်အပြည့်",
   "video.hide": "ဗီဒီယိုကို ဖျောက်ပါ",
+  "video.mute": "အသံပိတ်ထားပါ",
   "video.pause": "ခဏရပ်ပါ",
-  "video.play": "ဖွင့်ပါ"
+  "video.play": "ဖွင့်ပါ",
+  "video.unmute": "အသံပြန်ဖွင့်ပါ"
 }
diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json
index 2c5af1d406..75d6a0489b 100644
--- a/app/javascript/mastodon/locales/nan.json
+++ b/app/javascript/mastodon/locales/nan.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "編輯個人資料",
   "account.enable_notifications": "佇 {name} PO文ê時通知我",
   "account.endorse": "用個人資料推薦對方",
-  "account.featured": "精選ê",
-  "account.featured.hashtags": "Hashtag",
-  "account.featured.posts": "PO文",
   "account.featured_tags.last_status_at": "頂kái tī {date} Po文",
   "account.featured_tags.last_status_never": "無PO文",
+  "account.featured_tags.title": "{name} ê推薦hashtag",
   "account.follow": "跟tuè",
   "account.follow_back": "Tuè tńg去",
   "account.followers": "跟tuè lí ê",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, other {{count} ê PO文}}",
   "account.unblock": "取消封鎖 @{name}",
   "account.unblock_domain": "Kā域名 {domain} 取消封鎖",
-  "account.unblock_domain_short": "取消封鎖",
   "account.unblock_short": "取消封鎖",
   "account.unendorse": "Mài tī個人資料推薦伊",
   "account.unfollow": "取消跟tuè",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "發生意外ê錯誤。.",
   "alert.unexpected.title": "Ai-ioh!",
   "alt_text_badge.title": "替代文字",
-  "alt_text_modal.add_alt_text": "加添說明文字",
-  "alt_text_modal.add_text_from_image": "Tuì圖加說明文字",
-  "alt_text_modal.cancel": "取消",
-  "alt_text_modal.change_thumbnail": "改縮小圖",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "請替聽有困難ê敘述tsit ê內容…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "請替看有困難ê敘述tsit ê內容…",
-  "alt_text_modal.done": "做好ah",
   "announcement.announcement": "公告",
-  "annual_report.summary.archetype.booster": "追求趣味ê",
-  "annual_report.summary.archetype.lurker": "有讀無PO ê",
-  "annual_report.summary.archetype.oracle": "先知",
-  "annual_report.summary.archetype.pollster": "愛發動投票ê",
-  "annual_report.summary.archetype.replier": "社交ê蝴蝶",
-  "annual_report.summary.followers.followers": "跟tuè lí ê",
-  "annual_report.summary.followers.total": "Lóng總有 {count} ê",
-  "annual_report.summary.here_it_is": "下kha是lí {year} 年ê回顧:",
-  "annual_report.summary.highlighted_post.by_favourites": "Hōo足tsē lâng收藏ê PO文",
-  "annual_report.summary.highlighted_post.by_reblogs": "Hōo足tsē lâng轉ê PO文",
-  "annual_report.summary.highlighted_post.by_replies": "有上tsē回應ê PO文",
-  "annual_report.summary.highlighted_post.possessive": "{name} ê",
-  "annual_report.summary.most_used_app.most_used_app": "上tsē lâng用ê app",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "上tsia̍p用ê hashtag",
-  "annual_report.summary.most_used_hashtag.none": "無",
-  "annual_report.summary.new_posts.new_posts": "新ê PO文",
-  "annual_report.summary.percentile.text": "<topLabel>Tse 予lí變做 {domain} ê用戶ê </topLabel><percentage></percentage><bottomLabel></bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Gún bē kā Bernie講。",
-  "annual_report.summary.thanks": "多謝成做Mastodon ê成員!",
   "attachments_list.unprocessed": "(Iáu bē處理)",
   "audio.hide": "Tshàng聲音",
   "block_modal.remote_users_caveat": "Guán ē要求服侍器 {domain} 尊重lí ê決定。但是bô法度保證ta̍k ê服侍器lóng遵守,因為tsi̍t-kuá服侍器huân-sè用別款方法處理封鎖。公開ê PO文可能iáu是ē hōo bô登入ê用者看著。",
@@ -139,7 +110,6 @@
   "bundle_column_error.routing.body": "Tshuē bô所要求ê頁面。Lí kám確定地址liâu-á ê URL正確?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "關",
-  "bundle_modal_error.message": "Tī載入tsit ê畫面ê時起錯誤。",
   "bundle_modal_error.retry": "Koh試",
   "closed_registrations.other_server_instructions": "因為Mastodon非中心化,所以lí ē當tī別ê服侍器建立口座,iáu ē當kap tsit ê服侍器來往。",
   "closed_registrations_modal.description": "Tann bē當tī {domain} 建立新ê口座,m̄-koh著記得,lí bô需要 {domain} 服侍器ê帳號,mā ē當用 Mastodon。",
@@ -150,16 +120,13 @@
   "column.blocks": "封鎖ê用者",
   "column.bookmarks": "冊籤",
   "column.community": "本地ê時間線",
-  "column.create_list": "建立列單",
   "column.direct": "私人ê提起",
   "column.directory": "瀏覽個人資料",
   "column.domain_blocks": "封鎖ê域名",
-  "column.edit_list": "編輯列單",
   "column.favourites": "Siōng kah意",
   "column.firehose": "Tsit-má ê動態",
   "column.follow_requests": "跟tuè請求",
   "column.home": "頭頁",
-  "column.list_members": "管理列單ê成員",
   "column.lists": "列單",
   "column.mutes": "消音ê用者",
   "column.notifications": "通知",
@@ -172,7 +139,6 @@
   "column_header.pin": "釘",
   "column_header.show_settings": "顯示設定",
   "column_header.unpin": "Pak掉",
-  "column_search.cancel": "取消",
   "column_subheading.settings": "設定",
   "community.column_settings.local_only": "Kan-ta展示本地ê",
   "community.column_settings.media_only": "Kan-ta展示媒體",
@@ -191,7 +157,7 @@
   "compose_form.poll.duration": "投票期間",
   "compose_form.poll.multiple": "Tsē選擇",
   "compose_form.poll.option_placeholder": "選項 {number}",
-  "compose_form.poll.single": "單選擇",
+  "compose_form.poll.single": "揀tsi̍t ê",
   "compose_form.poll.switch_to_multiple": "Kā投票改做ē當選tsē-tsē ê。",
   "compose_form.poll.switch_to_single": "Kā投票改做kan-ta通選tsi̍t-ê",
   "compose_form.poll.type": "投票ê方法",
@@ -215,16 +181,9 @@
   "confirmations.edit.confirm": "編輯",
   "confirmations.edit.message": "Tsit-má編輯ē khàm掉lí tng-leh編寫ê訊息,lí kám beh繼續án-ne做?",
   "confirmations.edit.title": "Kám beh khàm掉PO文?",
-  "confirmations.follow_to_list.confirm": "跟tuè,加入kàu列單",
-  "confirmations.follow_to_list.message": "Beh kā {name} 加添kàu列單,lí tio̍h先跟tuè伊。",
-  "confirmations.follow_to_list.title": "Kám beh跟tuè tsit ê用者?",
   "confirmations.logout.confirm": "登出",
   "confirmations.logout.message": "Lí kám確定beh登出?",
   "confirmations.logout.title": "Lí kám beh登出?",
-  "confirmations.missing_alt_text.confirm": "加添說明文字",
-  "confirmations.missing_alt_text.message": "Lí ê PO文包含無說明文字ê媒體。加添敘述,ē幫tsān lí ê內容hōo khah tsē lâng接近使用。",
-  "confirmations.missing_alt_text.secondary": "就按呢PO出去",
-  "confirmations.missing_alt_text.title": "Kám beh加添說明文字?",
   "confirmations.mute.confirm": "消音",
   "confirmations.redraft.confirm": "Thâi掉了後重寫",
   "confirmations.redraft.message": "Lí kám確定behthâi掉tsit篇PO文了後koh重寫?收藏kap轉PO ē無去,而且原底ê PO文ê回應ē變孤立。",
@@ -253,10 +212,6 @@
   "disabled_account_banner.text": "Lí ê口座 {disabledAccount} tsit-má hōo lâng停止使用。",
   "dismissable_banner.community_timeline": "Tsia sī uì 口座hē tī {domain} ê lâng,最近所公開PO ê。",
   "dismissable_banner.dismiss": "Mài kā tshah",
-  "dismissable_banner.explore_links": "Tsiah ê新聞故事是kín-á日tī Fediverse hōo lâng分享上tsē pái ê。Hōo koh khah tsē lâng 分享ê khah新ê新聞故事,名次koh khah kuân。",
-  "dismissable_banner.explore_statuses": "Tsiah ê tuì Fediverse來ê PO文kín-á日有真tsē關注。Khah新ê,有khah tsē轉送kap收藏ê,名次koh khah kuân。",
-  "dismissable_banner.explore_tags": "Tsiah ê hashtag kín-á日tī Fediverse 有真tsē關注。Khah tsē無kâng ê lâng leh用ê hashtag,名次koh khah kuân。",
-  "dismissable_banner.public_timeline": "Tsiah ê是 {domain} 內底ê lâng 佇 Fediverse所跟tuè ê ê,上新ê公開PO文。.",
   "domain_block_modal.block": "封鎖服侍器",
   "domain_block_modal.block_account_instead": "改做封鎖 @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Uì tsit ê服侍器來ê,通kap lí khah早ê PO交流。",
@@ -271,15 +226,12 @@
   "domain_pill.server": "服侍器",
   "domain_pill.their_handle": "In ê口座:",
   "domain_pill.their_server": "In數位ê tau,in所有ê PO文lóng tī tsia。",
-  "domain_pill.their_username": "In佇in ê服侍器獨一ê稱呼。佇無kâng ê服侍器有可能tshuē著kāng名ê用者。",
+  "domain_pill.their_username": "In佇tsit ê服侍器獨一ê稱呼。佇無kâng ê服侍器有可能tshuē著kāng名ê用者。",
   "domain_pill.username": "用者ê名",
   "domain_pill.whats_in_a_handle": "口座是siánn-mih?",
   "domain_pill.who_they_are": "因為口座(handle)表示tsit ê lâng是siáng kap tī toh,lí ē當佇<button>支援ActivityPub ê平臺</button>. ê社交網路kap lâng交流。",
   "domain_pill.who_you_are": "因為口座(handle)表示lí是siáng kap tī toh,lâng ē當佇<button>支援ActivityPub ê平臺</button>. ê社交網路kap lí交流。",
   "domain_pill.your_handle": "Lí ê口座:",
-  "domain_pill.your_server": "Lí數位ê厝,內底有lí所有ê PO文。無kah意?Ē當轉kàu別ê服侍器,koh保有跟tuè lí êl âng。.",
-  "domain_pill.your_username": "Lí 佇tsit ê服侍器獨一ê稱呼。佇無kâng ê服侍器有可能tshuē著kāng名ê用者。",
-  "embed.instructions": "Khóo-pih 下kha ê程式碼,來kā tsit篇PO文tàu佇lí ê網站。",
   "embed.preview": "伊e án-ne顯示:\n",
   "emoji_button.activity": "活動",
   "emoji_button.clear": "清掉",
@@ -296,42 +248,15 @@
   "emoji_button.search_results": "Tshiau-tshuē ê結果",
   "emoji_button.symbols": "符號",
   "emoji_button.travel": "旅行kap地點",
-  "empty_column.account_featured": "Tsit ê列單是空ê",
   "empty_column.account_hides_collections": "Tsit位用者選擇無愛公開tsit ê資訊",
   "empty_column.account_suspended": "口座已經受停止",
   "empty_column.account_timeline": "Tsia無PO文!",
   "empty_column.account_unavailable": "個人資料bē當看",
-  "empty_column.blocks": "Lí iáu無封鎖任何用者。",
-  "empty_column.bookmarked_statuses": "Lí iáu無加添任何冊籤。Nā是lí加添冊籤,伊ē佇tsia顯示。",
-  "empty_column.community": "本站時間線是空ê。緊來公開PO文oh!",
-  "empty_column.direct": "Lí iáu無任何ê私人訊息。Nā是lí送á是收著私人訊息,ē佇tsia顯示。.",
-  "empty_column.domain_blocks": "Iáu無封鎖任何網域。",
-  "empty_column.explore_statuses": "目前iáu無有流行ê趨勢,請sió等tsi̍t-ē,koh確認。",
-  "empty_column.favourited_statuses": "Lí iáu無加添任何收藏 ê PO文。Nā是lí加收藏,伊ē佇tsia顯示。",
-  "empty_column.favourites": "Iáu無lâng收藏tsit篇PO文。Nā是有lâng收藏,ē佇tsia顯示。",
-  "empty_column.follow_requests": "Lí iáu buē收著任何ê跟tuè請求。Nā是lí收著,伊ē佇tsia顯示。",
-  "empty_column.followed_tags": "Lí iáu buē收著任何ê hashtag。Nā是lí收著,ē佇tsia顯示。",
-  "empty_column.hashtag": "Tsit ê hashtag內底無物件。",
-  "empty_column.home": "Lí tshù ê時間線是空ê!跟tuè別lâng來kā充滿。",
-  "empty_column.list": "Tsit張列單內底iáu bô物件。若是列單內底ê成員貼新ê PO文,in ē tī tsia顯示。",
-  "empty_column.mutes": "Lí iáu無消音任何用者。",
-  "empty_column.notification_requests": "清hōo空ah!內底無物件。若是lí收著新ê通知,ē根據lí ê設定,佇tsia出現。",
-  "empty_column.notifications": "Lí iáu無收著任何通知。Nā別lâng kap lí互動,lí ē佇tsia看著。",
-  "empty_column.public": "內底無物件!寫beh公開ê PO文,á是主動跟tuè別ê服侍器ê用者,來加添內容。",
-  "error.unexpected_crash.explanation": "因為原始碼內底有錯誤,á是瀏覽器相容出tshê,tsit頁bē當正確顯示。",
-  "error.unexpected_crash.explanation_addons": "Tsit頁bē當正確顯示,可能是瀏覽器附ê功能,á是自動翻譯工具所致。",
-  "error.unexpected_crash.next_steps": "請試更新tsit頁。若是bē當改善,lí iáu是ē當改使用無kâng ê瀏覽器,á是app,來用Mastodon。",
-  "error.unexpected_crash.next_steps_addons": "請試kā in停止使用,suà落來更新tsit頁。若是bē當改善,lí iáu是ē當改使用無kâng ê瀏覽器,á是app,來用Mastodon。",
   "errors.unexpected_crash.copy_stacktrace": "Khóo-pih stacktrace kàu剪貼pang-á",
   "errors.unexpected_crash.report_issue": "報告問題",
   "explore.suggested_follows": "用者",
   "explore.title": "探索",
   "explore.trending_links": "新聞",
-  "explore.trending_statuses": "PO文",
-  "explore.trending_tags": "Hashtag",
-  "filter_modal.added.context_mismatch_explanation": "Tsit ê過濾器類別bē當適用佇lí所接近使用ê PO文ê情境。若是lí mā beh佇tsit ê情境過濾tsit ê PO文,lí著編輯過濾器。.",
-  "filter_modal.added.context_mismatch_title": "本文無sio合!",
-  "filter_modal.added.expired_explanation": "Tsit ê過濾器類別過期ah,lí需要改到期ê日期來繼續用。",
   "filter_modal.added.expired_title": "過期ê過濾器",
   "filter_modal.added.review_and_configure": "Beh審視kap進前設定tsit ê過濾器ê類別,請kàu {settings_link}。",
   "filter_modal.added.review_and_configure_title": "過濾器ê設定",
@@ -358,11 +283,6 @@
   "follow_suggestions.dismiss": "Mài koh顯示。",
   "follow_suggestions.featured_longer": "{domain} 團隊所揀ê",
   "follow_suggestions.friends_of_friends_longer": "時行佇lí所tuè ê lâng",
-  "follow_suggestions.hints.featured": "Tsit ê個人資料是 {domain} 團隊特別揀ê。",
-  "follow_suggestions.hints.friends_of_friends": "Tsit ê個人資料tī lí跟tuè ê lâng之間真流行。",
-  "follow_suggestions.hints.most_followed": "Tsit ê個人資料是 {domain} 內,有足tsē跟tuè者ê其中tsit ê。",
-  "follow_suggestions.hints.most_interactions": "Tsit ê個人資料tsi̍t-tsām-á佇 {domain} 有得著真tsē關注。",
-  "follow_suggestions.hints.similar_to_recently_followed": "Tsit ê個人資料kap lí最近跟tuè ê口座相siâng。",
   "follow_suggestions.personalized_suggestion": "個人化ê推薦",
   "follow_suggestions.popular_suggestion": "流行ê推薦",
   "follow_suggestions.popular_suggestion_longer": "佇{domain} 足有lâng緣",
@@ -373,16 +293,13 @@
   "footer.about": "概要",
   "footer.directory": "個人資料ê目錄",
   "footer.get_app": "The̍h著app",
+  "footer.invite": "邀請lâng",
   "footer.keyboard_shortcuts": "鍵盤kiu-té khí (shortcut)",
   "footer.privacy_policy": "隱私權政策",
   "footer.source_code": "看原始碼",
   "footer.status": "狀態",
-  "footer.terms_of_service": "服務規定",
   "generic.saved": "儲存ah",
   "getting_started.heading": "開始用",
-  "hashtag.admin_moderation": "Phah開 #{name} ê管理界面",
-  "hashtag.browse": "瀏覽佇 #{hashtag} ê PO文",
-  "hashtag.browse_from_account": "瀏覽 @{name} 佇 #{hashtag} 所寫ê PO文",
   "hashtag.column_header.tag_mode.all": "kap {additional}",
   "hashtag.column_header.tag_mode.any": "á是 {additional}",
   "hashtag.column_header.tag_mode.none": "無需要 {additional}",
@@ -393,201 +310,13 @@
   "hashtag.column_settings.tag_mode.none": "Lóng mài",
   "hashtag.column_settings.tag_toggle": "Kā追加ê標籤加添kàu tsit ê欄",
   "hashtag.counter_by_accounts": "{count, plural, one {{counter} ê} other {{counter} ê}}參與ê",
-  "hashtag.counter_by_uses": "{count, plural, one {{counter} 篇} other {{counter} 篇}} PO文",
-  "hashtag.counter_by_uses_today": "Kin-á日有 {count, plural, one {{counter} 篇} other {{counter} 篇}} PO文",
+  "hashtag.counter_by_uses": "{count, plural, one {{counter} ê} other {{counter} ê}} PO文",
+  "hashtag.counter_by_uses_today": "Kin-á日有 {count, plural, one {{counter} ê} other {{counter} ê}} PO文",
   "hashtag.follow": "跟tuè hashtag",
-  "hashtag.mute": "消音 #{hashtag}",
   "hashtag.unfollow": "取消跟tuè hashtag",
   "hashtags.and_other": "……kap 其他 {count, plural, other {# ê}}",
-  "hints.profiles.followers_may_be_missing": "Tsit ê個人資料ê跟tuè者資訊可能有落勾ê。",
-  "hints.profiles.follows_may_be_missing": "Tsit ê口座所跟tuè ê ê資訊可能有落勾ê。",
-  "hints.profiles.posts_may_be_missing": "Tsit ê口座ê tsi̍t kuá PO文可能有落勾ê。",
-  "hints.profiles.see_more_followers": "佇 {domain} 看koh khah tsē跟tuè lí ê",
-  "hints.profiles.see_more_follows": "佇 {domain} 看koh khah tsē lí跟tuè ê",
-  "hints.profiles.see_more_posts": "佇 {domain} 看koh khah tsē ê PO文",
-  "hints.threads.replies_may_be_missing": "Tuì其他ê服侍器來ê回應可能有phah m̄見。",
-  "hints.threads.see_more": "佇 {domain} 看koh khah tsē ê回應",
-  "home.column_settings.show_reblogs": "顯示轉PO",
-  "home.column_settings.show_replies": "顯示回應",
-  "home.hide_announcements": "Khàm掉公告",
-  "home.pending_critical_update.body": "請liōng早更新lí ê Mastodon ê服侍器!",
-  "home.pending_critical_update.link": "看更新內容",
-  "home.pending_critical_update.title": "有重要ê安全更新!",
-  "home.show_announcements": "顯示公告",
-  "ignore_notifications_modal.disclaimer": "Lí所忽略in ê通知ê用者,Mastodonbē當kā lí通知。忽略通知bē當阻擋訊息ê寄送。",
-  "ignore_notifications_modal.filter_instead": "改做過濾",
-  "ignore_notifications_modal.filter_to_act_users": "Lí猶原ē當接受、拒絕猶是檢舉用者",
-  "ignore_notifications_modal.filter_to_avoid_confusion": "過濾ē當避免可能ê bē分明。",
-  "ignore_notifications_modal.filter_to_review_separately": "Lí ē當個別檢視所過濾ê通知",
-  "ignore_notifications_modal.ignore": "Kā通知忽略",
-  "ignore_notifications_modal.limited_accounts_title": "Kám beh忽略受限制ê口座送來ê通知?",
-  "ignore_notifications_modal.new_accounts_title": "Kám beh忽略新口座送來ê通知?",
-  "ignore_notifications_modal.not_followers_title": "Kám beh忽略無跟tuè lí ê口座送來ê通知?",
-  "ignore_notifications_modal.not_following_title": "Kám beh忽略lí 無跟tuè ê口座送來ê通知?",
-  "ignore_notifications_modal.private_mentions_title": "忽略ka-kī主動送ê私人提起ê通知?",
-  "info_button.label": "幫tsān",
-  "info_button.what_is_alt_text": "<h1>Siánn物是替代文字?</h1> <p>替代文字kā視覺有障礙、網路速度khah慢,á是beh tshuē頂下文ê lâng,提供圖ê敘述。</p> <p>Lí ē當通過寫明白、簡單kap客觀ê替代文字,替逐家改善容易使用性kap幫tsān理解。</p> <ul> <li>掌握重要ê因素</li> <li>替圖寫摘要ê文字</li> <li>用規則ê語句結構</li> <li>避免重複ê資訊</li> <li>專注佇趨勢kap佇複雜視覺(比如圖表á是地圖)內底tshuē關鍵</li> </ul>",
-  "interaction_modal.action.favourite": "Nā beh繼續,lí tio̍h用你ê口座收藏。",
-  "interaction_modal.action.follow": "Nā beh繼續,lí tio̍h用你ê口座跟tuè。",
-  "interaction_modal.action.reblog": "Nā beh繼續,lí tio̍h用你ê口座轉送。",
-  "interaction_modal.action.reply": "Nā beh繼續,lí tio̍h用你ê口座回應。",
-  "interaction_modal.action.vote": "Nā beh繼續,lí tio̍h用你ê口座投票。",
-  "interaction_modal.go": "行",
-  "interaction_modal.no_account_yet": "Tsit-má iáu bô口座?",
-  "interaction_modal.on_another_server": "佇無kâng ê服侍器",
-  "interaction_modal.on_this_server": "Tī tsit ê服侍器",
-  "interaction_modal.title.favourite": "收藏 {name} ê PO文",
-  "interaction_modal.title.follow": "跟tuè {name}",
-  "interaction_modal.title.reblog": "轉送 {name} ê PO文",
-  "interaction_modal.title.reply": "回應 {name} ê PO文",
-  "interaction_modal.title.vote": "參加 {name} ê投票",
-  "interaction_modal.username_prompt": "比如:{example}",
-  "intervals.full.days": "{number, plural, other {# kang}}",
-  "intervals.full.hours": "{number, plural, other {# 點鐘}}",
-  "intervals.full.minutes": "{number, plural, other {# 分鐘}}",
-  "keyboard_shortcuts.back": "Tńg去",
-  "keyboard_shortcuts.blocked": "開封鎖ê用者ê列單",
-  "keyboard_shortcuts.boost": "轉送PO文",
-  "keyboard_shortcuts.column": "揀tsit ê欄",
-  "keyboard_shortcuts.compose": "揀寫文字ê框仔",
-  "keyboard_shortcuts.description": "說明",
-  "keyboard_shortcuts.direct": "phah開私人提起ê欄",
-  "keyboard_shortcuts.down": "佇列單內kā suá khah 下kha",
-  "keyboard_shortcuts.enter": "Phah開PO文",
-  "keyboard_shortcuts.favourite": "收藏PO文",
-  "keyboard_shortcuts.favourites": "Phah開收藏ê列單",
-  "keyboard_shortcuts.federated": "Phah開聯邦ê時間線",
-  "keyboard_shortcuts.heading": "鍵盤ê快速key",
-  "keyboard_shortcuts.home": "Phah開tshù ê時間線",
-  "keyboard_shortcuts.hotkey": "快速key",
-  "keyboard_shortcuts.legend": "顯示tsit篇說明",
-  "keyboard_shortcuts.local": "Phah開本站ê時間線",
-  "keyboard_shortcuts.mention": "提起作者",
-  "keyboard_shortcuts.muted": "Phah開消音ê用者列單",
-  "keyboard_shortcuts.my_profile": "Phah開lí ê個人資料",
-  "keyboard_shortcuts.notifications": "Phah開通知欄",
-  "keyboard_shortcuts.open_media": "Phah開媒體",
-  "keyboard_shortcuts.pinned": "Phah開釘起來ê PO文列單",
-  "keyboard_shortcuts.profile": "Phah開作者ê個人資料",
-  "keyboard_shortcuts.reply": "回應PO文",
-  "keyboard_shortcuts.requests": "Phah開跟tuè請求ê列單",
-  "keyboard_shortcuts.search": "揀tshiau-tshuē條á",
-  "keyboard_shortcuts.spoilers": "顯示/隱藏內容警告",
-  "keyboard_shortcuts.start": "Phah開「開始用」欄",
-  "keyboard_shortcuts.toggle_hidden": "顯示/隱藏內容警告後壁ê PO文",
-  "keyboard_shortcuts.toggle_sensitivity": "顯示/tshàng媒體",
-  "keyboard_shortcuts.toot": "PO新PO文",
-  "keyboard_shortcuts.translate": "kā PO文翻譯",
-  "keyboard_shortcuts.unfocus": "離開輸入框仔/tshiau-tshuē格仔",
-  "keyboard_shortcuts.up": "佇列單內kā suá khah面頂",
-  "lightbox.close": "關",
-  "lightbox.next": "下tsi̍t ê",
-  "lightbox.previous": "頂tsi̍t ê",
-  "lightbox.zoom_in": "Tshūn-kiu kàu實際ê sài-suh",
-  "lightbox.zoom_out": "Tshūn-kiu kàu適當ê sài-suh",
-  "limited_account_hint.action": "一直顯示個人資料",
-  "limited_account_hint.title": "Tsit ê 個人資料予 {domain} ê管理員tshàng起來ah。",
-  "link_preview.author": "Tuì {name}",
-  "link_preview.more_from_author": "看 {name} ê其他內容",
-  "link_preview.shares": "{count, plural, one {{counter} 篇} other {{counter} 篇}} PO文",
-  "lists.add_member": "加",
-  "lists.add_to_list": "加添kàu列單",
-  "lists.add_to_lists": "Kā {name} 加添kàu列單",
-  "lists.create": "建立",
-  "lists.create_a_list_to_organize": "開新ê列單,組織lí tshù ê時間線",
-  "lists.create_list": "建立列單",
-  "lists.delete": "Thâi掉列單",
-  "lists.done": "做好ah",
-  "lists.edit": "編輯列單",
-  "lists.exclusive": "佇tshù ê時間線kā成員tshàng起來。",
-  "lists.exclusive_hint": "Nā bóo-mi̍h口座佇tsit ê列單,ē tuì lí tshù ê時間線kā tsit ê口座tshàng起來,避免koh看見in ê PO文。",
-  "lists.find_users_to_add": "Tshuē beh加添ê用者",
-  "lists.list_members": "列單ê成員",
-  "lists.list_members_count": "{count, plural, other {# 位成員}}",
-  "lists.list_name": "列單ê名",
-  "lists.new_list_name": "新ê列單ê名",
-  "lists.no_lists_yet": "Iáu無列單。",
-  "lists.no_members_yet": "Iáu無成員。",
-  "lists.no_results_found": "Tshuē無結果。",
-  "lists.remove_member": "Suá掉",
-  "lists.replies_policy.followed": "所跟tuè ê任何用者",
-  "lists.replies_policy.list": "列單ê成員",
-  "lists.replies_policy.none": "無半位",
-  "lists.save": "儲存",
-  "lists.search": "Tshiau-tshuē",
-  "lists.show_replies_to": "列單成員回應ê顯示範圍",
-  "load_pending": "{count, plural, other {# ê 項目}}",
-  "loading_indicator.label": "Leh載入……",
-  "media_gallery.hide": "Khàm掉",
-  "moved_to_account_banner.text": "Lí ê口座 {disabledAccount} 已經停止使用ah,因為suá kàu {movedToAccount}。",
-  "mute_modal.hide_from_notifications": "Tuì通知內底khàm掉",
-  "mute_modal.hide_options": "Khàm掉選項",
-  "mute_modal.indefinite": "直到我取消消音",
-  "mute_modal.show_options": "顯示選項",
-  "mute_modal.they_can_mention_and_follow": "In iáu ē當提起á是跟tuè lí,毋過lí看buē著in。",
-  "mute_modal.they_wont_know": "In buē知影in受消音。",
-  "mute_modal.title": "Kā用者消音?",
-  "mute_modal.you_wont_see_mentions": "Lí buē看見提起in ê PO文。",
-  "mute_modal.you_wont_see_posts": "In iáu ē當看著lí ê PO文,毋過lí看bē tio̍h in ê。",
-  "navigation_bar.about": "概要",
-  "navigation_bar.administration": "管理",
-  "navigation_bar.advanced_interface": "用進階ê網頁界面開",
-  "navigation_bar.blocks": "封鎖ê用者",
-  "navigation_bar.bookmarks": "冊籤",
-  "navigation_bar.community_timeline": "本地ê時間線",
-  "navigation_bar.compose": "寫新ê PO文",
-  "navigation_bar.direct": "私人ê提起",
-  "navigation_bar.discover": "發現",
-  "navigation_bar.domain_blocks": "封鎖ê域名",
-  "navigation_bar.explore": "探查",
-  "navigation_bar.favourites": "Siōng kah意",
-  "navigation_bar.filters": "消音ê詞",
-  "navigation_bar.follow_requests": "跟tuè請求",
-  "navigation_bar.followed_tags": "跟tuè ê hashtag",
-  "navigation_bar.follows_and_followers": "Leh跟tuè ê kap跟tuè lí ê",
-  "navigation_bar.lists": "列單",
-  "navigation_bar.logout": "登出",
-  "navigation_bar.moderation": "審核",
-  "navigation_bar.mutes": "消音ê用者",
-  "navigation_bar.opened_in_classic_interface": "PO文、口座kap其他指定ê頁面,預設ē佇經典ê網頁界面內phah開。",
-  "navigation_bar.personal": "個人",
-  "navigation_bar.pins": "釘起來ê PO文",
-  "navigation_bar.preferences": "偏愛ê設定",
-  "navigation_bar.public_timeline": "聯邦ê時間線",
-  "navigation_bar.search": "Tshiau-tshuē",
-  "navigation_bar.security": "安全",
-  "not_signed_in_indicator.not_signed_in": "Lí著登入來接近使用tsit ê資源。",
-  "notification.admin.report": "{name} kā {target} 檢舉ah",
-  "notification.admin.report_account": "{name} kā {target} 所寫ê {count, plural, other {# 篇PO文}}檢舉ah,原因是:{category}",
-  "notification.admin.report_account_other": "{name} kā {target} 所寫ê {count, plural, other {# 篇PO文}}檢舉ah",
-  "notification.admin.report_statuses": "{name} kā {target} 檢舉ah,原因是:{category}",
-  "notification.admin.report_statuses_other": "{name} kā {target} 檢舉ah",
-  "notification.admin.sign_up": "口座 {name} 有開ah。",
-  "notification.admin.sign_up.name_and_others": "{name} kap {count, plural, other {其他 # ê lâng}} ê口座有開ah",
-  "notification.annual_report.message": "Lí ê {year} #Wrapstodon teh等lí!緊來看tsit年lí佇Mastodon頂ê上精彩ê內容,kap難忘ê時刻!",
-  "notification.annual_report.view": "Kā #Wrapstodon 看māi。",
-  "notification.favourite": "{name} kah意lí ê PO文",
-  "notification.favourite.name_and_others_with_link": "{name} kap<a>{count, plural, other {另外 # ê lâng}}</a>kah意lí ê PO文",
-  "notification.favourite_pm": "{name} kah意lí ê私人提起",
-  "notification.favourite_pm.name_and_others_with_link": "{name} kap<a>{count, plural, other {另外 # ê lâng}}</a>kah意lí ê私人提起",
-  "notification.follow": "{name}跟tuè lí",
-  "notification.follow.name_and_others": "{name} kap<a>{count, plural, other {另外 # ê lâng}}</a>跟tuè lí",
-  "notification.follow_request": "{name} 請求跟tuè lí",
-  "notification.follow_request.name_and_others": "{name} kap{count, plural, other {另外 # ê lâng}}請求跟tuè lí",
-  "notification.label.mention": "提起",
-  "notification.label.private_mention": "私人ê提起",
-  "notification.label.private_reply": "私人ê回應",
-  "notification.label.reply": "回應",
-  "notification.mention": "提起",
-  "notification.mentioned_you": "{name}kā lí提起",
-  "notification.moderation-warning.learn_more": "看詳細",
-  "notification.moderation_warning": "Lí有收著審核ê警告",
-  "notification.moderation_warning.action_delete_statuses": "Lí ê一寡PO文hōo lâng thâi掉ah。",
-  "notification.moderation_warning.action_disable": "Lí ê口座hōo lâng停止使用ah。",
-  "notification.moderation_warning.action_mark_statuses_as_sensitive": "Lí ê一寡PO文,hōo lâng標做敏感ê內容。",
-  "notification.moderation_warning.action_none": "Lí ê口座有收著審核ê警告。",
-  "notification_requests.edit_selection": "編輯",
-  "notification_requests.exit_selection": "做好ah",
+  "onboarding.action.back": "Tńg去",
+  "onboarding.actions.back": "Tńg去",
   "search_popout.language_code": "ISO語言代碼",
   "status.translated_from_with": "用 {provider} 翻譯 {lang}"
 }
diff --git a/app/javascript/mastodon/locales/ne.json b/app/javascript/mastodon/locales/ne.json
index af2c922cbd..a4b5b0cb4f 100644
--- a/app/javascript/mastodon/locales/ne.json
+++ b/app/javascript/mastodon/locales/ne.json
@@ -25,6 +25,7 @@
   "account.enable_notifications": "@{name} ले पोस्ट गर्दा मलाई सूचित गर्नुहोस्",
   "account.endorse": "प्रोफाइलमा फिचर गर्नुहोस्",
   "account.featured_tags.last_status_never": "कुनै पोस्ट छैन",
+  "account.featured_tags.title": "{name}का विशेष ह्यासट्यागहरू",
   "account.follow": "फलो गर्नुहोस",
   "account.follow_back": "फलो ब्याक गर्नुहोस्",
   "account.followers": "फलोअरहरु",
@@ -67,9 +68,6 @@
   "alert.rate_limited.message": "कृपया {retry_time, time, medium} पछि पुन: प्रयास गर्नुहोस्।",
   "alert.unexpected.message": "एउटा अनपेक्षित त्रुटि भयो।",
   "announcement.announcement": "घोषणा",
-  "annual_report.summary.followers.followers": "फलोअरहरु",
-  "annual_report.summary.highlighted_post.by_reblogs": "सबैभन्दा बढि बूस्ट गरिएको पोस्ट",
-  "annual_report.summary.new_posts.new_posts": "नयाँ पोस्टहरू",
   "block_modal.remote_users_caveat": "हामी सर्भर {domain} लाई तपाईंको निर्णयको सम्मान गर्न सोध्नेछौं। तर, हामी अनुपालनको ग्यारेन्टी दिन सक्दैनौं किनभने केही सर्भरहरूले ब्लकहरू फरक रूपमा ह्यान्डल गर्न सक्छन्। सार्वजनिक पोस्टहरू लग इन नभएका प्रयोगकर्ताहरूले देख्न सक्छन्।",
   "block_modal.show_less": "कम देखाउनुहोस्",
   "block_modal.show_more": "थप देखाउनुहोस्",
@@ -81,6 +79,7 @@
   "bundle_column_error.retry": "पुन: प्रयास गर्नुहोस्",
   "bundle_column_error.routing.title": "४०४",
   "bundle_modal_error.close": "बन्द गर्नुहोस्",
+  "bundle_modal_error.message": "यो कम्पोनेन्ट लोड गर्दा केही गडबड भयो।",
   "bundle_modal_error.retry": "Try again",
   "closed_registrations.other_server_instructions": "Mastodon विकेन्द्रीकृत भएकोले, तपाइँ अर्को सर्भरमा खाता खोल्न सक्नुहुन्छ र पनि यो सर्भरसँग अन्तरक्रिया गर्न सक्नुहुन्छ।",
   "closed_registrations_modal.description": "हाल {domain} मा खाता सिर्जना गर्न सम्भव छैन, तर कृपया ध्यान राख्नुहोस् कि तपाईंले Mastodon प्रयोग गर्नको लागि {domain} मा नै खाता खोल्न आवश्यक छैन।",
@@ -88,11 +87,9 @@
   "closed_registrations_modal.title": "Mastodon मा साइन अप गर्दै",
   "column.blocks": "ब्लक गरिएको प्रयोगकर्ताहरु",
   "column.bookmarks": "बुकमार्कहरू",
-  "column.create_list": "सूची बनाउनुहोस्",
   "column.direct": "निजी उल्लेखहरू",
   "column.directory": "प्रोफाइल ब्राउज गर्नुहोस्",
   "column.domain_blocks": "ब्लक गरिएको डोमेन",
-  "column.edit_list": "सूची सम्पादन गर्नुहोस्",
   "column.follow_requests": "फलो अनुरोधहरू",
   "column.home": "गृहपृष्ठ",
   "column.lists": "सूचीहरू",
@@ -102,7 +99,6 @@
   "column_header.hide_settings": "सेटिङ्हरू लुकाउनुहोस्",
   "column_header.pin": "पिन गर्नुहोस्",
   "column_header.unpin": "अनपिन गर्नुहोस्",
-  "column_search.cancel": "रद्द गर्नुहोस्",
   "column_subheading.settings": "सेटिङहरू",
   "community.column_settings.media_only": "मिडिया मात्र",
   "compose.language.change": "भाषा परिवर्तन गर्नुहोस्",
@@ -125,9 +121,6 @@
   "confirmations.edit.confirm": "सम्पादन गर्नुहोस्",
   "confirmations.edit.message": "अहिले सम्पादन गर्नाले तपाईंले हाल लेखिरहनुभएको सन्देश अधिलेखन हुनेछ। के तपाईं अगाडि बढ्न चाहनुहुन्छ?",
   "confirmations.edit.title": "पोस्ट अधिलेखन गर्ने?",
-  "confirmations.follow_to_list.confirm": "फलो गर्नुहोस र सूचीमा थप्नुहोस्",
-  "confirmations.follow_to_list.message": "सूचीमा {name}लाई थप्नको लागि तपाईंले तिनीहरूलाई फलो गरेको हुनुपर्छ।",
-  "confirmations.follow_to_list.title": "प्रयोगकर्तालाई फलो गर्ने?",
   "confirmations.logout.message": "के तपाइँ पक्का हुनुहुन्छ कि तपाइँ लाई लग आउट गर्न चाहनुहुन्छ?",
   "confirmations.logout.title": "लग आउट गर्ने?",
   "confirmations.mute.confirm": "म्यूट गर्नुहोस्",
@@ -153,7 +146,6 @@
   "hashtag.follow": "ह्यासट्याग फलो गर्नुहोस्",
   "hashtag.unfollow": "ह्यासट्याग अनफलो गर्नुहोस्",
   "home.column_settings.show_reblogs": "बूस्टहरू देखाउनुहोस्",
-  "interaction_modal.no_account_yet": "अहिलेसम्म खाता छैन?",
   "interaction_modal.title.follow": "{name} लाई फलो गर्नुहोस्",
   "interaction_modal.title.reblog": "{name} को पोस्ट बुस्ट गर्नुहोस्",
   "keyboard_shortcuts.boost": "पोस्ट बुस्ट गर्नुहोस्",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index 30f4777f61..59c3782d42 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Profiel bewerken",
   "account.enable_notifications": "Geef een melding wanneer @{name} een bericht plaatst",
   "account.endorse": "Op profiel weergeven",
-  "account.featured": "Uitgelicht",
-  "account.featured.hashtags": "Hashtags",
-  "account.featured.posts": "Berichten",
   "account.featured_tags.last_status_at": "Laatste bericht op {date}",
   "account.featured_tags.last_status_never": "Geen berichten",
+  "account.featured_tags.title": "Uitgelichte hashtags van {name}",
   "account.follow": "Volgen",
   "account.follow_back": "Terugvolgen",
   "account.followers": "Volgers",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} bericht} other {{counter} berichten}}",
   "account.unblock": "@{name} deblokkeren",
   "account.unblock_domain": "{domain} niet langer blokkeren",
-  "account.unblock_domain_short": "Deblokkeren",
   "account.unblock_short": "Deblokkeren",
   "account.unendorse": "Niet op profiel weergeven",
   "account.unfollow": "Ontvolgen",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Er deed zich een onverwachte fout voor",
   "alert.unexpected.title": "Oeps!",
   "alt_text_badge.title": "Alt-tekst",
-  "alt_text_modal.add_alt_text": "Alt-tekst toevoegen",
-  "alt_text_modal.add_text_from_image": "Tekst van afbeelding toevoegen",
-  "alt_text_modal.cancel": "Annuleren",
-  "alt_text_modal.change_thumbnail": "Miniatuur wijzigen",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Beschrijf dit voor doven en slechthorenden…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Beschrijf dit voor blinden en slechtzienden…",
-  "alt_text_modal.done": "Klaar",
   "announcement.announcement": "Mededeling",
-  "annual_report.summary.archetype.booster": "De cool-hunter",
-  "annual_report.summary.archetype.lurker": "De lurker",
-  "annual_report.summary.archetype.oracle": "Het orakel",
-  "annual_report.summary.archetype.pollster": "De opiniepeiler",
-  "annual_report.summary.archetype.replier": "De sociale vlinder",
-  "annual_report.summary.followers.followers": "volgers",
-  "annual_report.summary.followers.total": "totaal {count}",
-  "annual_report.summary.here_it_is": "Hier is jouw terugblik op {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "bericht met de meeste favorieten",
-  "annual_report.summary.highlighted_post.by_reblogs": "bericht met de meeste boosts",
-  "annual_report.summary.highlighted_post.by_replies": "bericht met de meeste reacties",
-  "annual_report.summary.highlighted_post.possessive": "{name}'s",
-  "annual_report.summary.most_used_app.most_used_app": "meest gebruikte app",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "meest gebruikte hashtag",
-  "annual_report.summary.most_used_hashtag.none": "Geen",
-  "annual_report.summary.new_posts.new_posts": "nieuwe berichten",
-  "annual_report.summary.percentile.text": "<topLabel>Hiermee behoor je tot de top</topLabel><percentage></percentage><bottomLabel> van {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "We zullen Bernie niets vertellen.",
-  "annual_report.summary.thanks": "Bedankt dat je deel uitmaakt van Mastodon!",
   "attachments_list.unprocessed": "(niet verwerkt)",
   "audio.hide": "Audio verbergen",
   "block_modal.remote_users_caveat": "We vragen de server {domain} om je besluit te respecteren. Het naleven hiervan is echter niet gegarandeerd, omdat sommige servers blokkades anders kunnen interpreteren. Openbare berichten zijn mogelijk nog steeds zichtbaar voor niet-ingelogde gebruikers.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "De opgevraagde pagina kon niet worden gevonden. Weet je zeker dat de URL in de adresbalk de juiste is?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Sluiten",
-  "bundle_modal_error.message": "Er ging iets mis tijdens het laden van dit scherm.",
+  "bundle_modal_error.message": "Er ging iets mis tijdens het laden van dit component.",
   "bundle_modal_error.retry": "Opnieuw proberen",
   "closed_registrations.other_server_instructions": "Omdat Mastodon gedecentraliseerd is, kun je op een andere server een account registreren en vanaf daar nog steeds met deze server communiceren.",
   "closed_registrations_modal.description": "Momenteel is het niet mogelijk om op {domain} een account aan te maken. Hou echter in gedachte dat om Mastodon te kunnen gebruiken het niet een vereiste is om op {domain} een account te hebben.",
@@ -150,16 +121,13 @@
   "column.blocks": "Geblokkeerde gebruikers",
   "column.bookmarks": "Bladwijzers",
   "column.community": "Lokale tijdlijn",
-  "column.create_list": "Lijst aanmaken",
   "column.direct": "Privéberichten",
   "column.directory": "Gebruikersgids",
   "column.domain_blocks": "Geblokkeerde servers",
-  "column.edit_list": "Lijst bewerken",
   "column.favourites": "Favorieten",
   "column.firehose": "Openbare tijdlijnen",
   "column.follow_requests": "Volgverzoeken",
   "column.home": "Start",
-  "column.list_members": "Lijstleden beheren",
   "column.lists": "Lijsten",
   "column.mutes": "Genegeerde gebruikers",
   "column.notifications": "Meldingen",
@@ -172,7 +140,6 @@
   "column_header.pin": "Vastmaken",
   "column_header.show_settings": "Instellingen tonen",
   "column_header.unpin": "Losmaken",
-  "column_search.cancel": "Annuleren",
   "column_subheading.settings": "Instellingen",
   "community.column_settings.local_only": "Alleen lokaal",
   "community.column_settings.media_only": "Alleen media",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Bewerken",
   "confirmations.edit.message": "Door nu te reageren overschrijf je het bericht dat je op dit moment aan het schrijven bent. Weet je zeker dat je verder wil gaan?",
   "confirmations.edit.title": "Bericht overschrijven?",
-  "confirmations.follow_to_list.confirm": "Volgen en toevoegen aan de lijst",
-  "confirmations.follow_to_list.message": "Je moet {name} volgen om ze toe te voegen aan een lijst.",
-  "confirmations.follow_to_list.title": "Gebruiker volgen?",
   "confirmations.logout.confirm": "Uitloggen",
   "confirmations.logout.message": "Weet je zeker dat je wilt uitloggen?",
   "confirmations.logout.title": "Uitloggen?",
-  "confirmations.missing_alt_text.confirm": "Alt-tekst toevoegen",
-  "confirmations.missing_alt_text.message": "Je bericht bevat media zonder alt-tekst. Het toevoegen van beschrijvingen helpt je om je inhoud toegankelijk te maken voor meer mensen.",
-  "confirmations.missing_alt_text.secondary": "Toch plaatsen",
-  "confirmations.missing_alt_text.title": "Alt-tekst toevoegen?",
   "confirmations.mute.confirm": "Negeren",
   "confirmations.redraft.confirm": "Verwijderen en herschrijven",
   "confirmations.redraft.message": "Weet je zeker dat je dit bericht wilt verwijderen en herschrijven? Je verliest wel de boosts en favorieten, en de reacties op het originele bericht raak je kwijt.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Jouw account {disabledAccount} is momenteel uitgeschakeld.",
   "dismissable_banner.community_timeline": "Dit zijn de meest recente openbare berichten van accounts op {domain}. Je kunt onder 'instellingen > voorkeuren > overig' kiezen welke talen je wilt zien.",
   "dismissable_banner.dismiss": "Sluiten",
-  "dismissable_banner.explore_links": "Deze nieuwsartikelen worden vandaag de dag het meest gedeeld op de fediverse. Nieuwere artikelen die door meer verschillende mensen zijn geplaatst, worden hoger gerangschikt.",
-  "dismissable_banner.explore_statuses": "Deze berichten uit de hele fediverse winnen vandaag aan populariteit. Nieuwere berichten met meer boosts en favorieten worden hoger gerangschikt.",
-  "dismissable_banner.explore_tags": "Deze hashtags winnen tegenwoordig aan populariteit op de fediverse. Hashtags die door meer verschillende mensen worden gebruikt, worden hoger gerangschikt.",
-  "dismissable_banner.public_timeline": "Dit zijn de meest recente openbare berichten van mensen op de fediverse die mensen op {domain} volgen.",
+  "dismissable_banner.explore_links": "Dit zijn nieuwsberichten die vandaag het meest op het sociale web (fediverse) worden gedeeld. Nieuwere nieuwsberichten die door meer verschillende mensen zijn geplaatst staan hoger op de lijst.",
+  "dismissable_banner.explore_statuses": "Dit zijn berichten op het sociale web (fediverse) die vandaag aan populariteit winnen. Nieuwere berichten met meer boosts en favorieten staan hoger.",
+  "dismissable_banner.explore_tags": "Deze hashtags winnen aan populariteit op het sociale web (fediverse). Hashtags die door meer verschillende mensen worden gebruikt staan hoger.",
+  "dismissable_banner.public_timeline": "Dit zijn de meest recente openbare berichten van accounts op het sociale web (fediverse) die door mensen op {domain} worden gevolgd.",
   "domain_block_modal.block": "Server blokkeren",
   "domain_block_modal.block_account_instead": "Alleen {name} blokkeren",
   "domain_block_modal.they_can_interact_with_old_posts": "Mensen op deze server kunnen interactie hebben met jouw oude berichten.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Zoekresultaten",
   "emoji_button.symbols": "Symbolen",
   "emoji_button.travel": "Reizen en locaties",
-  "empty_column.account_featured": "Deze lijst is leeg",
   "empty_column.account_hides_collections": "Deze gebruiker heeft ervoor gekozen deze informatie niet beschikbaar te maken",
   "empty_column.account_suspended": "Account opgeschort",
   "empty_column.account_timeline": "Hier zijn geen berichten!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.",
   "empty_column.home": "Deze tijdlijn is leeg! Volg meer mensen om het te vullen.",
   "empty_column.list": "Er is nog niks te zien in deze lijst. Wanneer lijstleden nieuwe berichten plaatsen, zijn deze hier te zien.",
+  "empty_column.lists": "Je hebt nog geen lijsten. Wanneer je er een aanmaakt, valt dat hier te zien.",
   "empty_column.mutes": "Jij hebt nog geen gebruikers genegeerd.",
   "empty_column.notification_requests": "Helemaal leeg! Er is hier niets. Wanneer je nieuwe meldingen ontvangt, verschijnen deze hier volgens jouw instellingen.",
   "empty_column.notifications": "Je hebt nog geen meldingen. Begin met iemand een gesprek.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Probeer deze uit te schakelen en de pagina te verversen. Wanneer dat niet helpt, kun je Mastodon nog altijd met een andere webbrowser of mobiele app gebruiken.",
   "errors.unexpected_crash.copy_stacktrace": "Stacktrace naar klembord kopiëren",
   "errors.unexpected_crash.report_issue": "Technisch probleem melden",
+  "explore.search_results": "Zoekresultaten",
   "explore.suggested_follows": "Mensen",
   "explore.title": "Verkennen",
   "explore.trending_links": "Nieuws",
@@ -373,16 +334,13 @@
   "footer.about": "Over",
   "footer.directory": "Gebruikersgids",
   "footer.get_app": "App downloaden",
+  "footer.invite": "Mensen uitnodigen",
   "footer.keyboard_shortcuts": "Sneltoetsen",
   "footer.privacy_policy": "Privacybeleid",
   "footer.source_code": "Broncode bekijken",
   "footer.status": "Status",
-  "footer.terms_of_service": "Gebruiksvoorwaarden",
   "generic.saved": "Opgeslagen",
   "getting_started.heading": "Aan de slag",
-  "hashtag.admin_moderation": "Moderatie-omgeving van #{name} openen",
-  "hashtag.browse": "Berichten met #{hashtag} bekijken",
-  "hashtag.browse_from_account": "Berichten van @{name} met #{hashtag} bekijken",
   "hashtag.column_header.tag_mode.all": "en {additional}",
   "hashtag.column_header.tag_mode.any": "of {additional}",
   "hashtag.column_header.tag_mode.none": "zonder {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} bericht} other {{counter} berichten}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} bericht} other {{counter} berichten}} vandaag",
   "hashtag.follow": "Hashtag volgen",
-  "hashtag.mute": "#{hashtag} negeren",
   "hashtag.unfollow": "Hashtag ontvolgen",
   "hashtags.and_other": "…en {count, plural, one {}other {# meer}}",
   "hints.profiles.followers_may_be_missing": "Volgers voor dit profiel kunnen ontbreken.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Meldingen negeren van mensen die jou niet volgen?",
   "ignore_notifications_modal.not_following_title": "Meldingen negeren van mensen die je niet volgt?",
   "ignore_notifications_modal.private_mentions_title": "Meldingen negeren van ongevraagde privéberichten?",
-  "info_button.label": "Help",
-  "info_button.what_is_alt_text": "<h1>Wat is alt-tekst?</h1> <p>Alt-tekst biedt beschrijvingen van afbeeldingen voor mensen met een visuele beperking, voor verbindingen met lage internetsnelheid of mensen die op zoek zijn naar extra context.</p> <p>Je kunt de toegankelijkheid en de begrijpelijkheid voor iedereen verbeteren door heldere, beknopte en objectieve alt-teksten te schrijven.</p> <ul> <li>Leg belangrijke elementen vast</li> <li>Tekst in afbeeldingen samenvatten</li> <li>Een eenvoudige zinsbouw gebruiken</li> <li>Overbodige informatie vermijden</li> <li>Voor complexe diagrammen of kaarten alleen op trends en belangrijke bevindingen focussen</li> </ul>",
-  "interaction_modal.action.favourite": "Om verder te gaan, moet je vanaf je eigen account als favoriet markeren.",
-  "interaction_modal.action.follow": "Om verder te gaan, moet je vanaf je eigen account volgen.",
-  "interaction_modal.action.reblog": "Om verder te gaan, moet je vanaf je eigen account boosten.",
-  "interaction_modal.action.reply": "Om verder te gaan, moet je vanaf je eigen account reageren.",
-  "interaction_modal.action.vote": "Om verder te gaan, moet je vanaf je eigen account stemmen.",
-  "interaction_modal.go": "Ga",
-  "interaction_modal.no_account_yet": "Heb je nog geen account?",
+  "interaction_modal.description.favourite": "Je kunt met een Mastodon-account dit bericht als favoriet markeren, om die gebruiker te laten weten dat je het bericht waardeert en om het op te slaan.",
+  "interaction_modal.description.follow": "Je kunt met een Mastodon-account {name} volgen, om zo diens berichten op jouw starttijdlijn te ontvangen.",
+  "interaction_modal.description.reblog": "Je kunt met een Mastodon-account dit bericht boosten, om het zo met jouw volgers te delen.",
+  "interaction_modal.description.reply": "Je kunt met een Mastodon-account op dit bericht reageren.",
+  "interaction_modal.login.action": "Ga naar start",
+  "interaction_modal.login.prompt": "Domein van jouw server, bv. mastodon.social",
+  "interaction_modal.no_account_yet": "Niet op Mastodon?",
   "interaction_modal.on_another_server": "Op een andere server",
   "interaction_modal.on_this_server": "Op deze server",
+  "interaction_modal.sign_in": "Je bent niet op deze server ingelogd. Op welke server bevindt zich jouw account?",
+  "interaction_modal.sign_in_hint": "Tip: Dat is de website waarop je je hebt geregistreerd. Wanneer je dit bent vergeten kun je naar de welkomstmail zoeken in je inbox. Je kunt ook je volledige gebruikersnaam invullen! (bv. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Bericht van {name} als favoriet markeren",
   "interaction_modal.title.follow": "{name} volgen",
   "interaction_modal.title.reblog": "Bericht van {name} boosten",
   "interaction_modal.title.reply": "Op het bericht van {name} reageren",
-  "interaction_modal.title.vote": "Stemmen in {name}'s peiling",
-  "interaction_modal.username_prompt": "Bijv. {example}",
   "intervals.full.days": "{number, plural, one {# dag} other {# dagen}}",
   "intervals.full.hours": "{number, plural, one {# uur} other {# uur}}",
   "intervals.full.minutes": "{number, plural, one {# minuut} other {# minuten}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Inhoudswaarschuwing tonen/verbergen",
   "keyboard_shortcuts.toggle_sensitivity": "Media tonen/verbergen",
   "keyboard_shortcuts.toot": "Nieuw bericht schrijven",
-  "keyboard_shortcuts.translate": "om een bericht te vertalen",
   "keyboard_shortcuts.unfocus": "Tekst- en zoekveld ontfocussen",
   "keyboard_shortcuts.up": "Naar boven in de lijst bewegen",
   "lightbox.close": "Sluiten",
@@ -490,32 +444,20 @@
   "link_preview.author": "Door {name}",
   "link_preview.more_from_author": "Meer van {name}",
   "link_preview.shares": "{count, plural, one {{counter} bericht} other {{counter} berichten}}",
-  "lists.add_member": "Toevoegen",
-  "lists.add_to_list": "Aan lijst toevoegen",
-  "lists.add_to_lists": "{name} aan lijsten toevoegen",
-  "lists.create": "Aanmaken",
-  "lists.create_a_list_to_organize": "Maak een nieuwe lijst aan om je starttijdlijn te organiseren",
-  "lists.create_list": "Lijst aanmaken",
+  "lists.account.add": "Aan lijst toevoegen",
+  "lists.account.remove": "Uit lijst verwijderen",
   "lists.delete": "Lijst verwijderen",
-  "lists.done": "Klaar",
   "lists.edit": "Lijst bewerken",
-  "lists.exclusive": "Leden op je Startpagina verbergen",
-  "lists.exclusive_hint": "Als iemand op deze lijst staat, verberg deze persoon dan op je starttijdlijn om te voorkomen dat zijn berichten twee keer worden getoond.",
-  "lists.find_users_to_add": "Vind gebruikers om toe te voegen",
-  "lists.list_members": "Lijstleden",
-  "lists.list_members_count": "{count, plural, one{# lid} other{# leden}}",
-  "lists.list_name": "Lijstnaam",
-  "lists.new_list_name": "Nieuwe lijstnaam",
-  "lists.no_lists_yet": "Nog geen lijsten.",
-  "lists.no_members_yet": "Nog geen leden.",
-  "lists.no_results_found": "Geen resultaten gevonden.",
-  "lists.remove_member": "Verwijderen",
+  "lists.edit.submit": "Titel veranderen",
+  "lists.exclusive": "Verberg lijstleden op je starttijdlijn",
+  "lists.new.create": "Lijst toevoegen",
+  "lists.new.title_placeholder": "Naam nieuwe lijst",
   "lists.replies_policy.followed": "Elke gevolgde gebruiker",
   "lists.replies_policy.list": "Leden van de lijst",
   "lists.replies_policy.none": "Niemand",
-  "lists.save": "Opslaan",
-  "lists.search": "Zoeken",
-  "lists.show_replies_to": "Voeg antwoorden van lijstleden toe aan",
+  "lists.replies_policy.title": "Toon reacties aan:",
+  "lists.search": "Zoek naar mensen die je volgt",
+  "lists.subheading": "Jouw lijsten",
   "load_pending": "{count, plural, one {# nieuw item} other {# nieuwe items}}",
   "loading_indicator.label": "Laden…",
   "media_gallery.hide": "Verberg",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} rapporteerde {target}",
   "notification.admin.sign_up": "{name} heeft zich geregistreerd",
   "notification.admin.sign_up.name_and_others": "{name} en {count, plural, one {# ander persoon} other {# andere personen}} hebben zich geregistreerd",
-  "notification.annual_report.message": "Jouw {year} #Wrapstodon staat klaar! Laat de hoogtepunten en memorabele momenten van jouw jaar zien op Mastodon!",
-  "notification.annual_report.view": "#Wrapstodon bekijken",
   "notification.favourite": "{name} markeerde jouw bericht als favoriet",
   "notification.favourite.name_and_others_with_link": "{name} en <a>{count, plural, one {# ander persoon} other {# andere personen}}</a> hebben jouw bericht als favoriet gemarkeerd",
-  "notification.favourite_pm": "{name} heeft je privébericht als favoriet gemarkeerd",
-  "notification.favourite_pm.name_and_others_with_link": "{name} en <a>{count, plural, one {# ander} other {# anderen}}</a> hebben je privébericht als favoriet gemarkeerd",
   "notification.follow": "{name} volgt jou nu",
   "notification.follow.name_and_others": "{name} en <a>{count, plural, one {# ander persoon} other {# andere personen}}</a> volgen jou nou",
   "notification.follow_request": "{name} wil jou graag volgen",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Desktopmeldingen inschakelen",
   "notifications_permission_banner.how_to_control": "Om meldingen te ontvangen wanneer Mastodon niet open staat. Je kunt precies bepalen welke soort interacties wel of geen desktopmeldingen geven via de bovenstaande {icon} knop.",
   "notifications_permission_banner.title": "Mis nooit meer iets",
-  "onboarding.follows.back": "Terug",
-  "onboarding.follows.done": "Klaar",
+  "onboarding.action.back": "Breng me terug",
+  "onboarding.actions.back": "Breng me terug",
+  "onboarding.actions.go_to_explore": "Laat mij de huidige trends zien",
+  "onboarding.actions.go_to_home": "Ga naar je starttijdlijn",
+  "onboarding.compose.template": "Hallo #Mastodon!",
   "onboarding.follows.empty": "Helaas kunnen op dit moment geen resultaten worden getoond. Je kunt proberen te zoeken of op de verkenningspagina te bladeren om mensen te vinden die je kunt volgen, of probeer het later opnieuw.",
-  "onboarding.follows.search": "Zoeken",
-  "onboarding.follows.title": "Volg mensen om te beginnen",
+  "onboarding.follows.lead": "Jouw starttijdlijn is de belangrijkste manier om Mastodon te ervaren. Hoe meer mensen je volgt, hoe actiever en interessanter het wordt. Om te beginnen zijn hier enkele suggesties:",
+  "onboarding.follows.title": "Je starttijdlijn aan jouw wensen aanpassen",
   "onboarding.profile.discoverable": "Maak mijn profiel vindbaar",
   "onboarding.profile.discoverable_hint": "Wanneer je akkoord gaat met het vindbaar zijn op Mastodon, verschijnen je berichten in zoekresultaten en kunnen ze trending worden, en je profiel kan aan andere mensen worden aanbevolen wanneer ze vergelijkbare interesses hebben.",
   "onboarding.profile.display_name": "Weergavenaam",
   "onboarding.profile.display_name_hint": "Jouw volledige naam of een leuke bijnaam…",
+  "onboarding.profile.lead": "Je kunt dit later altijd aanvullen in de instellingen, waar nog meer aanpassingsopties beschikbaar zijn.",
   "onboarding.profile.note": "Biografie",
   "onboarding.profile.note_hint": "Je kunt andere mensen @vermelden of #hashtags gebruiken…",
   "onboarding.profile.save_and_continue": "Opslaan en doorgaan",
   "onboarding.profile.title": "Profiel instellen",
   "onboarding.profile.upload_avatar": "Profielfoto uploaden",
   "onboarding.profile.upload_header": "Omslagfoto voor het profiel uploaden",
+  "onboarding.share.lead": "Laat mensen weten hoe ze je kunnen vinden op Mastodon!",
+  "onboarding.share.message": "Ik ben {username} op #Mastodon! Volg mij op {url}",
+  "onboarding.share.next_steps": "Mogelijke volgende stappen:",
+  "onboarding.share.title": "Jouw profiel delen",
+  "onboarding.start.lead": "Je maakt nu deel uit van Mastodon, een uniek, gedecentraliseerd sociaal mediaplatform waar jij - en dus geen algoritme - jouw eigen ervaring beheert. Laten we beginnen aan deze nieuwe sociale uitdaging:",
+  "onboarding.start.skip": "Wil je meteen verdergaan?",
+  "onboarding.start.title": "Het is je gelukt!",
+  "onboarding.steps.follow_people.body": "Op Mastodon draait het helemaal om het volgen van interessante mensen.",
+  "onboarding.steps.follow_people.title": "Je starttijdlijn aan jouw wensen aanpassen",
+  "onboarding.steps.publish_status.body": "Zeg hallo tegen de wereld met tekst, foto's, video's of peilingen {emoji}",
+  "onboarding.steps.publish_status.title": "Maak je eerste bericht",
+  "onboarding.steps.setup_profile.body": "Wanneer je meer over jezelf vertelt, krijg je meer interactie met andere mensen.",
+  "onboarding.steps.setup_profile.title": "Je profiel aanpassen",
+  "onboarding.steps.share_profile.body": "Laat je vrienden weten waar je te vinden bent op Mastodon",
+  "onboarding.steps.share_profile.title": "Deel je Mastodonprofiel",
+  "onboarding.tips.2fa": "<strong>Wist je dat?</strong> Je kunt je account beveiligen door tweestapsverificatie in te stellen in je accountinstellingen. Het werkt met elke TOTP-app naar keuze, geen telefoonnummer nodig!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Wist je dat?</strong> Mastodon is gedecentraliseerd en daarom kom je regelmatig mensen tegen die van een andere server gebruik maken dan jij. En toch kun je probleemloos met deze mensen communiceren! Hun server vind je in de tweede helft van hun gebruikersnaam!",
+  "onboarding.tips.migration": "<strong>Wist je dat?</strong> Als je het gevoel hebt dat {domain} in de toekomst voor jou geen goede serverkeuze is, dan kan je naar een andere Mastodonserver overstappen zonder je volgers te verliezen. Je kunt zelfs je eigen server opzetten!",
+  "onboarding.tips.verification": "<strong>Wist je dat?</strong> Je kunt je account verifiëren door een link naar je Mastodon-profiel op je eigen website te plaatsen en de website aan je profiel toe te voegen. Geen kosten of documenten nodig!",
   "password_confirmation.exceeds_maxlength": "Wachtwoordbevestiging overschrijdt de maximale wachtwoordlengte",
   "password_confirmation.mismatching": "Wachtwoordbevestiging komt niet overeen",
   "picture_in_picture.restore": "Terugzetten",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Privacybeleid",
   "recommended": "Aanbevolen",
   "refresh": "Vernieuwen",
-  "regeneration_indicator.please_stand_by": "Even geduld alsjeblieft.",
-  "regeneration_indicator.preparing_your_home_feed": "Voorbereiden van jouw starttijdlijn…",
+  "regeneration_indicator.label": "Aan het laden…",
+  "regeneration_indicator.sublabel": "Jouw starttijdlijn wordt aangemaakt!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# dag} other {# dagen}} geleden",
   "relative_time.full.hours": "{number, plural, one {# uur} other {# uur}} geleden",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Accounts",
   "search_results.all": "Alles",
   "search_results.hashtags": "Hashtags",
-  "search_results.no_results": "Geen resultaten.",
-  "search_results.no_search_yet": "Probeer te zoeken naar berichten, profielen of hashtags.",
+  "search_results.nothing_found": "Deze zoektermen leveren geen resultaat op",
   "search_results.see_all": "Alles bekijken",
   "search_results.statuses": "Berichten",
-  "search_results.title": "Zoeken naar \"{q}\"",
+  "search_results.title": "Naar {q} zoeken",
   "server_banner.about_active_users": "Aantal gebruikers tijdens de afgelopen 30 dagen (MAU)",
   "server_banner.active_users": "actieve gebruikers",
   "server_banner.administered_by": "Beheerd door:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Niemand heeft dit bericht nog geboost. Wanneer iemand dit doet, valt dat hier te zien.",
   "status.redraft": "Verwijderen en herschrijven",
   "status.remove_bookmark": "Bladwijzer verwijderen",
-  "status.remove_favourite": "Verwijderen uit favorieten",
   "status.replied_in_thread": "Reageerde in gesprek",
   "status.replied_to": "Reageerde op {name}",
   "status.reply": "Reageren",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Getoonde talen voor {target} wijzigen",
   "tabs_bar.home": "Start",
   "tabs_bar.notifications": "Meldingen",
-  "terms_of_service.effective_as_of": "Van kracht met ingang van {date}",
-  "terms_of_service.title": "Gebruiksvoorwaarden",
-  "terms_of_service.upcoming_changes_on": "Aankomende wijzigingen op {date}",
   "time_remaining.days": "{number, plural, one {# dag} other {# dagen}} te gaan",
   "time_remaining.hours": "{number, plural, one {# uur} other {# uur}} te gaan",
   "time_remaining.minutes": "{number, plural, one {# minuut} other {# minuten}} te gaan",
@@ -897,12 +853,26 @@
   "upload_button.label": "Afbeeldingen, een video- of een geluidsbestand toevoegen",
   "upload_error.limit": "Uploadlimiet van bestand overschreden.",
   "upload_error.poll": "Het uploaden van bestanden is bij peilingen niet toegestaan.",
+  "upload_form.audio_description": "Omschrijf dit voor dove of slechthorende mensen",
+  "upload_form.description": "Omschrijf dit voor blinde of slechtziende mensen",
   "upload_form.drag_and_drop.instructions": "Druk op spatie of enter om een mediabijlage op te pakken. Gebruik de pijltjestoetsen om de bijlage in een bepaalde richting te verplaatsen. Druk opnieuw op de spatiebalk of enter om de mediabijlage op de nieuwe positie te plaatsen, of druk op escape om te annuleren.",
   "upload_form.drag_and_drop.on_drag_cancel": "Slepen is geannuleerd. Mediabijlage {item} is niet verplaatst.",
   "upload_form.drag_and_drop.on_drag_end": "Mediabijlage {item} is niet verplaatst.",
   "upload_form.drag_and_drop.on_drag_over": "Mediabijlage {item} is verplaatst.",
   "upload_form.drag_and_drop.on_drag_start": "Mediabijlage {item} is opgepakt.",
   "upload_form.edit": "Bewerken",
+  "upload_form.thumbnail": "Miniatuur wijzigen",
+  "upload_form.video_description": "Omschrijf dit voor dove, slechthorende, blinde of slechtziende mensen",
+  "upload_modal.analyzing_picture": "Afbeelding analyseren…",
+  "upload_modal.apply": "Toepassen",
+  "upload_modal.applying": "Aan het toepassen…",
+  "upload_modal.choose_image": "Kies een afbeelding",
+  "upload_modal.description_placeholder": "Pa's wijze lynx bezag vroom het fikse aquaduct",
+  "upload_modal.detect_text": "Tekst in een afbeelding detecteren",
+  "upload_modal.edit_media": "Media bewerken",
+  "upload_modal.hint": "Klik of sleep de cirkel in de voorvertoning naar een centraal focuspunt in de afbeelding dat altijd zichtbaar moet blijven.",
+  "upload_modal.preparing_ocr": "OCR voorbereiden…",
+  "upload_modal.preview_label": "Voorvertoning ({ratio})",
   "upload_progress.label": "Uploaden...",
   "upload_progress.processing": "Bezig…",
   "username.taken": "Die gebruikersnaam is al in gebruik. Probeer een andere",
@@ -912,12 +882,8 @@
   "video.expand": "Video groter maken",
   "video.fullscreen": "Volledig scherm",
   "video.hide": "Video verbergen",
-  "video.mute": "Dempen",
+  "video.mute": "Geluid uitschakelen",
   "video.pause": "Pauze",
   "video.play": "Afspelen",
-  "video.skip_backward": "Terugspoelen",
-  "video.skip_forward": "Vooruitspoelen",
-  "video.unmute": "Dempen opheffen",
-  "video.volume_down": "Volume omlaag",
-  "video.volume_up": "Volume omhoog"
+  "video.unmute": "Geluid inschakelen"
 }
diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json
index ab867017a9..d49c36e652 100644
--- a/app/javascript/mastodon/locales/nn.json
+++ b/app/javascript/mastodon/locales/nn.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Rediger profil",
   "account.enable_notifications": "Varsle meg når @{name} skriv innlegg",
   "account.endorse": "Vis på profilen",
-  "account.featured": "Utvald",
-  "account.featured.hashtags": "Emneknaggar",
-  "account.featured.posts": "Innlegg",
   "account.featured_tags.last_status_at": "Sist nytta {date}",
   "account.featured_tags.last_status_never": "Ingen innlegg",
+  "account.featured_tags.title": "{name} sine framheva emneknaggar",
   "account.follow": "Fylg",
   "account.follow_back": "Fylg tilbake",
   "account.followers": "Fylgjarar",
@@ -88,33 +86,7 @@
   "alert.unexpected.message": "Det oppstod eit uventa problem.",
   "alert.unexpected.title": "Oi sann!",
   "alt_text_badge.title": "Alternativ tekst",
-  "alt_text_modal.add_alt_text": "Legg til alternativ tekst",
-  "alt_text_modal.add_text_from_image": "Legg til tekst frå biletet",
-  "alt_text_modal.cancel": "Avbryt",
-  "alt_text_modal.change_thumbnail": "Endre miniatyrbilete",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Skriv ei skildring for menneske med høyrselsnedsetjingar…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Skriv ei skildring for menneske med synsnedsetjingar…",
-  "alt_text_modal.done": "Ferdig",
   "announcement.announcement": "Kunngjering",
-  "annual_report.summary.archetype.booster": "Den som jaktar på noko kult",
-  "annual_report.summary.archetype.lurker": "Den som heng på hjørnet",
-  "annual_report.summary.archetype.oracle": "Orakelet",
-  "annual_report.summary.archetype.pollster": "Meiningsmålaren",
-  "annual_report.summary.archetype.replier": "Den sosiale sumarfuglen",
-  "annual_report.summary.followers.followers": "fylgjarar",
-  "annual_report.summary.followers.total": "{count} i alt",
-  "annual_report.summary.here_it_is": "Her er eit gjensyn med {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "det mest omtykte innlegget",
-  "annual_report.summary.highlighted_post.by_reblogs": "det mest framheva innlegget",
-  "annual_report.summary.highlighted_post.by_replies": "innlegget med flest svar",
-  "annual_report.summary.highlighted_post.possessive": "som {name} laga",
-  "annual_report.summary.most_used_app.most_used_app": "mest brukte app",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "mest brukte emneknagg",
-  "annual_report.summary.most_used_hashtag.none": "Ingen",
-  "annual_report.summary.new_posts.new_posts": "nye innlegg",
-  "annual_report.summary.percentile.text": "<topLabel>Du er av dei</topLabel><percentage></percentage><bottomLabel>ivrigaste brukarane på {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Ikkje eit ord til pressa.",
-  "annual_report.summary.thanks": "Takk for at du er med i Mastodon!",
   "attachments_list.unprocessed": "(ubehandla)",
   "audio.hide": "Gøym lyd",
   "block_modal.remote_users_caveat": "Me vil be tenaren {domain} om å respektere di avgjerd. Me kan ikkje garantera at det vert gjort, sidan nokre tenarar kan handtera blokkering ulikt. Offentlege innlegg kan framleis vera synlege for ikkje-innlogga brukarar.",
@@ -138,7 +110,7 @@
   "bundle_column_error.routing.body": "Den etterspurde sida vart ikkje funnen. Er du sikker på at URL-adressa er rett?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Lat att",
-  "bundle_modal_error.message": "Noko gjekk gale då denne sida vart lasta.",
+  "bundle_modal_error.message": "Noko gjekk gale under lastinga av denne komponenten.",
   "bundle_modal_error.retry": "Prøv igjen",
   "closed_registrations.other_server_instructions": "Sidan Mastodon er desentralisert kan du lage ein brukar på ein anna tenar og framleis interagere med denne.",
   "closed_registrations_modal.description": "Det er ikkje mogleg å opprette ein konto på {domain} nett no, men hugs at du ikkje treng ein konto på akkurat {domain} for å nytte Mastodon.",
@@ -149,16 +121,13 @@
   "column.blocks": "Blokkerte brukarar",
   "column.bookmarks": "Bokmerke",
   "column.community": "Lokal tidsline",
-  "column.create_list": "Lag liste",
   "column.direct": "Private omtaler",
   "column.directory": "Sjå gjennom profilar",
   "column.domain_blocks": "Blokkerte domene",
-  "column.edit_list": "Rediger liste",
   "column.favourites": "Favorittar",
   "column.firehose": "Tidslinjer",
   "column.follow_requests": "Fylgjeførespurnadar",
   "column.home": "Heim",
-  "column.list_members": "Administrer medlemer på lista",
   "column.lists": "Lister",
   "column.mutes": "Målbundne brukarar",
   "column.notifications": "Varsel",
@@ -171,7 +140,6 @@
   "column_header.pin": "Fest",
   "column_header.show_settings": "Vis innstillingar",
   "column_header.unpin": "Løys",
-  "column_search.cancel": "Avbryt",
   "column_subheading.settings": "Innstillingar",
   "community.column_settings.local_only": "Berre lokalt",
   "community.column_settings.media_only": "Berre media",
@@ -190,7 +158,7 @@
   "compose_form.poll.duration": "Varigheit for rundspørjing",
   "compose_form.poll.multiple": "Fleirval",
   "compose_form.poll.option_placeholder": "Alternativ {number}",
-  "compose_form.poll.single": "Eitt val",
+  "compose_form.poll.single": "Vel ein",
   "compose_form.poll.switch_to_multiple": "Endre rundspørjinga til å tillate fleire val",
   "compose_form.poll.switch_to_single": "Endre rundspørjinga til å tillate berre eitt val",
   "compose_form.poll.type": "Stil",
@@ -214,16 +182,9 @@
   "confirmations.edit.confirm": "Rediger",
   "confirmations.edit.message": "Å redigera no vil overskriva den meldinga du er i ferd med å skriva. Er du sikker på at du vil halda fram?",
   "confirmations.edit.title": "Overskriv innlegget?",
-  "confirmations.follow_to_list.confirm": "Fylg og legg til lista",
-  "confirmations.follow_to_list.message": "Du må fylgja {name} for å leggja dei til ei liste.",
-  "confirmations.follow_to_list.title": "Vil du fylgja brukaren?",
   "confirmations.logout.confirm": "Logg ut",
   "confirmations.logout.message": "Er du sikker på at du vil logga ut?",
   "confirmations.logout.title": "Logg ut?",
-  "confirmations.missing_alt_text.confirm": "Legg til alternativ tekst",
-  "confirmations.missing_alt_text.message": "Posten din inneheld media utan alternativ-tekst. Det å leggje til skildringar hjelper med gjere innhaldet ditt tilgjengeleg for fleire personar.",
-  "confirmations.missing_alt_text.secondary": "Publiser likevel",
-  "confirmations.missing_alt_text.title": "Legg til alternativ tekst?",
   "confirmations.mute.confirm": "Demp",
   "confirmations.redraft.confirm": "Slett & skriv på nytt",
   "confirmations.redraft.message": "Er du sikker på at du vil sletta denne statusen og skriva han på nytt? Då misser du favorittar og framhevingar, og svar til det opprinnelege innlegget vert foreldrelause.",
@@ -252,10 +213,10 @@
   "disabled_account_banner.text": "Kontoen din, {disabledAccount} er for tida deaktivert.",
   "dismissable_banner.community_timeline": "Dette er dei nylegaste offentlege innlegga frå personar med kontoar frå {domain}.",
   "dismissable_banner.dismiss": "Avvis",
-  "dismissable_banner.explore_links": "Desse nyhendesakene er mest delte på allheimen i dag. Sakene som er nyast og mest delte er rangert høgast.",
-  "dismissable_banner.explore_statuses": "Her er populære innlegg på allheimen nett no. Dei nyaste inlegga med flest favorittar og framhevingar er rangert høgast.",
-  "dismissable_banner.explore_tags": "Desse merkelappane er populære på allheimen i dag. Merkelappane som er brukte av flest folk er rangert høgast.",
-  "dismissable_banner.public_timeline": "Dette er dei nyaste offentlege innlegga frå menneske på allheimen som folk på {domain} fylgjer.",
+  "dismissable_banner.explore_links": "Desse nyhendesakene snakkast om av folk på denne og andre tenarar på det desentraliserte nettverket no.",
+  "dismissable_banner.explore_statuses": "Dette er innlegg frå det sosiale nettet som er populære i dag. Nye innlegg med mange favorittmerkingar og framhevingar er rangert høgare.",
+  "dismissable_banner.explore_tags": "Desse emneknaggane er populære blant folk på denne tenaren og andre tenarar i det desentraliserte nettverket nett no.",
+  "dismissable_banner.public_timeline": "Dette er dei nyaste offentlege innlegga frå menneske på det sosiale nettet som folk på {domain} fylgjer.",
   "domain_block_modal.block": "Blokker tenaren",
   "domain_block_modal.block_account_instead": "Blokker @{name} i staden",
   "domain_block_modal.they_can_interact_with_old_posts": "Folk på denne tenaren kan samhandla med dei gamle innlegga dine.",
@@ -295,7 +256,6 @@
   "emoji_button.search_results": "Søkeresultat",
   "emoji_button.symbols": "Symbol",
   "emoji_button.travel": "Reise & stader",
-  "empty_column.account_featured": "Denne lista er tom",
   "empty_column.account_hides_collections": "Denne brukaren har valt å ikkje gjere denne informasjonen tilgjengeleg",
   "empty_column.account_suspended": "Kontoen er utestengd",
   "empty_column.account_timeline": "Ingen tut her!",
@@ -313,6 +273,7 @@
   "empty_column.hashtag": "Det er ingenting i denne emneknaggen enno.",
   "empty_column.home": "Heime-tidslina di er tom! Fylg fleire folk for å fylla ho med innhald. {suggestions}.",
   "empty_column.list": "Det er ingenting i denne lista enno. Når medlemer av denne lista legg ut nye statusar, så dukkar dei opp her.",
+  "empty_column.lists": "Du har ingen lister enno. Når du lagar ei, så dukkar ho opp her.",
   "empty_column.mutes": "Du har ikkje målbunde nokon enno.",
   "empty_column.notification_requests": "Ferdig! Her er det ingenting. Når du får nye varsel, kjem dei opp her slik du har valt.",
   "empty_column.notifications": "Du har ingen varsel enno. Kommuniser med andre for å starte samtalen.",
@@ -323,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Prøv å skru dei av og last inn sida på nytt. Hjelper ikkje det kan du framleis bruka Mastodon i ein annan nettlesar eller app.",
   "errors.unexpected_crash.copy_stacktrace": "Kopier stacktrace til utklippstavla",
   "errors.unexpected_crash.report_issue": "Rapporter problem",
+  "explore.search_results": "Søkeresultat",
   "explore.suggested_follows": "Folk",
   "explore.title": "Utforsk",
   "explore.trending_links": "Nytt",
@@ -372,14 +334,13 @@
   "footer.about": "Om",
   "footer.directory": "Profilmappe",
   "footer.get_app": "Få appen",
+  "footer.invite": "Inviter folk",
   "footer.keyboard_shortcuts": "Snøggtastar",
   "footer.privacy_policy": "Personvernsreglar",
   "footer.source_code": "Vis kjeldekode",
   "footer.status": "Status",
-  "footer.terms_of_service": "Brukarvilkår",
   "generic.saved": "Lagra",
   "getting_started.heading": "Kom i gang",
-  "hashtag.admin_moderation": "Opne moderasjonsgrensesnitt for #{name}",
   "hashtag.column_header.tag_mode.all": "og {additional}",
   "hashtag.column_header.tag_mode.any": "eller {additional}",
   "hashtag.column_header.tag_mode.none": "utan {additional}",
@@ -421,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Oversjå varsel frå folk som ikkje fylgjer deg?",
   "ignore_notifications_modal.not_following_title": "Oversjå varsel frå folk du ikkje fylgjer?",
   "ignore_notifications_modal.private_mentions_title": "Oversjå varsel frå masseutsende private omtaler?",
-  "info_button.label": "Hjelp",
-  "info_button.what_is_alt_text": "<h1>Kva er alternativ tekst?</h1> <p>Alternativ eller skildrande tekst gjev ei skildring av biletet for menneske som har synsvanskar, sein nettilkopling eller dei som ser etter ekstra innhald.</p> <p>Du gjer innhaldet ditt meir tilgjengeleg og forståeleg for alle ved å skriva klåre, presise og nøytrale alt-tekstar.</p> <ul> <li>Skriv om viktige element</li> <li>Oppsummer tekst i bilete</li> <li>Skriv vanlege setningar</li> <li>Unngå unyttige opplysingar</li> <li>Legg vekt på hovudpunkta i innhaldsrike visuelle element, som grafar eller kart</li> </ul>",
-  "interaction_modal.action.favourite": "Du må favorittmerka frå kontoen din for å halda fram.",
-  "interaction_modal.action.follow": "Du må fylgja frå kontoen din for å halda fram.",
-  "interaction_modal.action.reblog": "Du må framheva frå kontoen din for å halda fram.",
-  "interaction_modal.action.reply": "Du må svara frå kontoen din for å halda fram.",
-  "interaction_modal.action.vote": "Du må røysta frå kontoen din for å halda fram.",
-  "interaction_modal.go": "Gå",
-  "interaction_modal.no_account_yet": "Har du ikkje ein konto enno?",
+  "interaction_modal.description.favourite": "Med ein konto på Mastodon kan du favorittmerka dette innlegget for å visa forfattaren at du set pris på det, og for å lagra det til seinare.",
+  "interaction_modal.description.follow": "Med ein konto på Mastodon kan du fylgja {name} for å sjå innlegga deira i din heimestraum.",
+  "interaction_modal.description.reblog": "Med ein konto på Mastodon kan du framheva dette innlegget for å dela det med dine eigne fylgjarar.",
+  "interaction_modal.description.reply": "Med ein konto på Mastodon kan du svara på dette innlegget.",
+  "interaction_modal.login.action": "Ta meg heim",
+  "interaction_modal.login.prompt": "Domenenamnet til din heime-tenar. t.d. mastodon.social",
+  "interaction_modal.no_account_yet": "Ikkje på Mastodon?",
   "interaction_modal.on_another_server": "På ein annan tenar",
   "interaction_modal.on_this_server": "På denne tenaren",
+  "interaction_modal.sign_in": "Du er ikkje logga inn på denne tenaren. På kva for ein tenar høyrer kontoen din heime?",
+  "interaction_modal.sign_in_hint": "Tips: Det er nettstaden der du registrerte deg. Om du har gløymt kvar det var, sjå om du finn velkomst-eposten i innboksen din. Du kan òg skriva inn ditt fulle brukarnamn! (t.d. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Marker innlegget til {name} som favoritt",
   "interaction_modal.title.follow": "Fylg {name}",
   "interaction_modal.title.reblog": "Framhev {name} sitt innlegg",
   "interaction_modal.title.reply": "Svar på innlegge til {name}",
-  "interaction_modal.title.vote": "Røyst i {name} si avrøysting",
-  "interaction_modal.username_prompt": "T.d. {example}",
   "intervals.full.days": "{number, plural, one {# dag} other {# dagar}}",
   "intervals.full.hours": "{number, plural, one {# time} other {# timar}}",
   "intervals.full.minutes": "{number, plural, one {# minutt} other {# minutt}}",
@@ -473,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Vis/gøym tekst bak innhaldsvarsel",
   "keyboard_shortcuts.toggle_sensitivity": "Vis/gøym media",
   "keyboard_shortcuts.toot": "Lag nytt tut",
-  "keyboard_shortcuts.translate": "å omsetje eit innlegg",
   "keyboard_shortcuts.unfocus": "for å fokusere vekk skrive-/søkefeltet",
   "keyboard_shortcuts.up": "Flytt opp på lista",
   "lightbox.close": "Lukk",
@@ -486,32 +444,20 @@
   "link_preview.author": "Av {name}",
   "link_preview.more_from_author": "Meir frå {name}",
   "link_preview.shares": "{count, plural,one {{counter} innlegg} other {{counter} innlegg}}",
-  "lists.add_member": "Legg til",
-  "lists.add_to_list": "Legg til i liste",
-  "lists.add_to_lists": "Legg til {name} i lister",
-  "lists.create": "Lag",
-  "lists.create_a_list_to_organize": "Lag ei ny liste for å organisera startskjermen din",
-  "lists.create_list": "Lag liste",
+  "lists.account.add": "Legg til i liste",
+  "lists.account.remove": "Fjern frå liste",
   "lists.delete": "Slett liste",
-  "lists.done": "Ferdig",
   "lists.edit": "Rediger liste",
-  "lists.exclusive": "Gøym medlemer frå startskjermen",
-  "lists.exclusive_hint": "Viss nokon er på denne lista, blir dei gøymde frå startskjermen slik at du slepp sjå innlegga deira to gonger.",
-  "lists.find_users_to_add": "Finn brukarar å leggja til",
-  "lists.list_members": "Syn medlemer",
-  "lists.list_members_count": "{count, plural, one {# medlem} other {# medlemer}}",
-  "lists.list_name": "Namn på lista",
-  "lists.new_list_name": "Namn på den nye lista",
-  "lists.no_lists_yet": "Ingen lister enno.",
-  "lists.no_members_yet": "Ingen medlemer enno.",
-  "lists.no_results_found": "Fann ingenting.",
-  "lists.remove_member": "Fjern",
+  "lists.edit.submit": "Endre tittel",
+  "lists.exclusive": "Skjul desse innlegga frå heimestraumen din",
+  "lists.new.create": "Legg til liste",
+  "lists.new.title_placeholder": "Ny listetittel",
   "lists.replies_policy.followed": "Alle fylgde brukarar",
   "lists.replies_policy.list": "Medlemar i lista",
   "lists.replies_policy.none": "Ingen",
-  "lists.save": "Lagre",
-  "lists.search": "Søk",
-  "lists.show_replies_to": "Inkluder svar frå listemedlemer til",
+  "lists.replies_policy.title": "Vis svar på:",
+  "lists.search": "Søk blant folk du fylgjer",
+  "lists.subheading": "Listene dine",
   "load_pending": "{count, plural, one {# nytt element} other {# nye element}}",
   "loading_indicator.label": "Lastar…",
   "media_gallery.hide": "Gøym",
@@ -560,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} rapporterte {target}",
   "notification.admin.sign_up": "{name} er registrert",
   "notification.admin.sign_up.name_and_others": "{name} og {count, plural, one {# annan} other {# andre}} vart med",
-  "notification.annual_report.message": "#Året ditt for {year} ventar! Sjå kva som skjedde i løpet av Mastodon-året ditt!",
-  "notification.annual_report.view": "Sjå #Året ditt",
   "notification.favourite": "{name} markerte innlegget ditt som favoritt",
   "notification.favourite.name_and_others_with_link": "{name} og <a>{count, plural, one {# annan} other {# andre}}</a> favorittmerka innlegget ditt",
-  "notification.favourite_pm": "{name} favorittmerka den private nemninga di",
-  "notification.favourite_pm.name_and_others_with_link": "{name} og <a>{count, plural, one {# annan} other {# andre}}</a> favorittmerka den private nemninga di",
   "notification.follow": "{name} fylgde deg",
   "notification.follow.name_and_others": "{name} og <a>{count, plural, one {# annan} other {# andre}}</a> fylgde deg",
   "notification.follow_request": "{name} har bedt om å fylgja deg",
@@ -670,21 +612,44 @@
   "notifications_permission_banner.enable": "Skru på skrivebordsvarsel",
   "notifications_permission_banner.how_to_control": "Aktiver skrivebordsvarsel for å få varsel når Mastodon ikkje er open. Du kan nøye bestemme kva samhandlingar som skal føre til skrivebordsvarsel gjennom {icon}-knappen ovanfor etter at varsel er aktivert.",
   "notifications_permission_banner.title": "Gå aldri glipp av noko",
-  "onboarding.follows.back": "Tilbake",
-  "onboarding.follows.done": "Ferdig",
+  "onboarding.action.back": "Ta meg tilbake",
+  "onboarding.actions.back": "Ta meg tilbake",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "Hei #Mastodon!",
   "onboarding.follows.empty": "Me kan ikkje visa deg nokon resultat no. Du kan prøva å søkja eller bla gjennom utforsk-sida for å finna folk å fylgja, eller du kan prøva att seinare.",
-  "onboarding.follows.search": "Søk",
-  "onboarding.follows.title": "Fylg folk for å koma i gang",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
   "onboarding.profile.discoverable": "Gjer profilen min synleg",
   "onboarding.profile.discoverable_hint": "Når du vel å gjera profilen din synleg på Mastodon, vil innlegga dine syna i søkjeresultat og populære innlegg, og profilen din kan bli føreslegen for folk med liknande interesser som deg.",
   "onboarding.profile.display_name": "Synleg namn",
   "onboarding.profile.display_name_hint": "Det fulle namnet eller kallenamnet ditt…",
+  "onboarding.profile.lead": "Du kan alltid fullføra dette seinare i innstillingane, og der er det endå fleire tilpassingsalternativ.",
   "onboarding.profile.note": "Om meg",
   "onboarding.profile.note_hint": "Du kan @nemna folk eller #emneknaggar…",
   "onboarding.profile.save_and_continue": "Lagre og hald fram",
   "onboarding.profile.title": "Profiloppsett",
   "onboarding.profile.upload_avatar": "Last opp profilbilete",
   "onboarding.profile.upload_header": "Last opp profiltoppbilete",
+  "onboarding.share.lead": "La folk vita korleis dei kan finna deg på Mastodon!",
+  "onboarding.share.message": "Eg er {username} på #Mastodon! Du kan fylgja meg på {url}",
+  "onboarding.share.next_steps": "Dette kan du gjera no:",
+  "onboarding.share.title": "Del profilen din",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "No er du klar!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Skriv ditt fyrste innlegg",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
+  "onboarding.tips.2fa": "<strong>Visste du det?</strong> Du kan sikra kontoen din ved å setja opp 2-stegsinnlogging i kontoinnstillingane dine. Det fungerer med alle appar for tostegsinnloogging, så du treng ikkje noko telefonnummer.",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Visste du det?</strong> Sidan Mastodon er desentralisert, vil mange av profilane du kjem over vera på andre tenarar enn din. Likevel kan du samhandla med dei som elles. Kva tenar dei er på, står i andre halvdelen av brukarnamnet deira.",
+  "onboarding.tips.migration": "<strong>Visste du det?</strong> Viss du ikkje synes {domain} er staden for deg, kan du flytta til ein annan Mastodon-tenar utan å mista fylgjarane dine. Du kan òg driva din eigen tenar!",
+  "onboarding.tips.verification": "<strong>Visste du det?</strong> Du kan stadfesta brukarkontoen din ved å leggja til ei lenke til Mastodon-profilen din på di eiga nettside og leggja nettsida til i profilen din. Det kostar ingenting, og krev ingen dokument!",
   "password_confirmation.exceeds_maxlength": "Passordbekreftelsen overskrider den maksimale passordlengden",
   "password_confirmation.mismatching": "Passordene er ulike",
   "picture_in_picture.restore": "Legg den tilbake",
@@ -700,7 +665,7 @@
   "poll_button.remove_poll": "Fjern rundspørjing",
   "privacy.change": "Endre personvernet på innlegg",
   "privacy.direct.long": "Alle nemnde i innlegget",
-  "privacy.direct.short": "Privat omtale",
+  "privacy.direct.short": "Spesifikke folk",
   "privacy.private.long": "Berre dei som fylgjer deg",
   "privacy.private.short": "Fylgjarar",
   "privacy.public.long": "Kven som helst på og av Mastodon",
@@ -712,8 +677,8 @@
   "privacy_policy.title": "Personvernsreglar",
   "recommended": "Tilrådd",
   "refresh": "Oppdater",
-  "regeneration_indicator.please_stand_by": "Vent litt.",
-  "regeneration_indicator.preparing_your_home_feed": "Gjer klar startskjermen din…",
+  "regeneration_indicator.label": "Lastar…",
+  "regeneration_indicator.sublabel": "Heimetidslina di vert førebudd!",
   "relative_time.days": "{number}dg",
   "relative_time.full.days": "{number, plural, one {# dag} other {# dagar}} sidan",
   "relative_time.full.hours": "{number, plural, one {# time} other {# timar}} sidan",
@@ -797,11 +762,10 @@
   "search_results.accounts": "Profiler",
   "search_results.all": "Alt",
   "search_results.hashtags": "Emneknaggar",
-  "search_results.no_results": "Ingen resultat.",
-  "search_results.no_search_yet": "Prøv å søkja etter innlegg, profilar eller merkelappar.",
+  "search_results.nothing_found": "Kunne ikkje finne noko for desse søkeorda",
   "search_results.see_all": "Sjå alle",
   "search_results.statuses": "Tut",
-  "search_results.title": "Søk etter \"{q}\"",
+  "search_results.title": "Søk etter {q}",
   "server_banner.about_active_users": "Personar som har brukt denne tenaren dei siste 30 dagane (Månadlege Aktive Brukarar)",
   "server_banner.active_users": "aktive brukarar",
   "server_banner.administered_by": "Administrert av:",
@@ -853,7 +817,6 @@
   "status.reblogs.empty": "Ingen har framheva dette tutet enno. Om nokon gjer, så dukkar det opp her.",
   "status.redraft": "Slett & skriv på nytt",
   "status.remove_bookmark": "Fjern bokmerke",
-  "status.remove_favourite": "Fjern frå favorittar",
   "status.replied_in_thread": "Svara i tråden",
   "status.replied_to": "Svarte {name}",
   "status.reply": "Svar",
@@ -875,7 +838,6 @@
   "subscribed_languages.target": "Endre abonnerte språk for {target}",
   "tabs_bar.home": "Heim",
   "tabs_bar.notifications": "Varsel",
-  "terms_of_service.title": "Bruksvilkår",
   "time_remaining.days": "{number, plural, one {# dag} other {# dagar}} igjen",
   "time_remaining.hours": "{number, plural, one {# time} other {# timar}} igjen",
   "time_remaining.minutes": "{number, plural, one {# minutt} other {# minutt}} igjen",
@@ -891,12 +853,26 @@
   "upload_button.label": "Legg til medium",
   "upload_error.limit": "Du har gått over opplastingsgrensa.",
   "upload_error.poll": "Filopplasting er ikkje lov for rundspørjingar.",
+  "upload_form.audio_description": "Skildre for dei med nedsett høyrsel",
+  "upload_form.description": "Skildre for blinde og svaksynte",
   "upload_form.drag_and_drop.instructions": "For å plukka opp eit medievedlegg, trykkjer du på mellomrom eller enter. Når du dreg, brukar du piltastane for å flytta vedlegget i den retninga du vil. Deretter trykkjer du mellomrom eller enter att for å sleppa vedlegget på den nye plassen, eller trykk escape for å avbryta.",
   "upload_form.drag_and_drop.on_drag_cancel": "Du avbraut draginga. Medievedlegget {item} vart sleppt.",
   "upload_form.drag_and_drop.on_drag_end": "Medeivedlegget {item} vart sleppt.",
   "upload_form.drag_and_drop.on_drag_over": "Medievedlegget {item} vart flytta.",
   "upload_form.drag_and_drop.on_drag_start": "Plukka opp medievedlegget {item}.",
   "upload_form.edit": "Rediger",
+  "upload_form.thumbnail": "Bytt miniatyrbilete",
+  "upload_form.video_description": "Skildre for dei med nedsett høyrsel eller redusert syn",
+  "upload_modal.analyzing_picture": "Analyserer bilete…",
+  "upload_modal.apply": "Bruk",
+  "upload_modal.applying": "Utfører…",
+  "upload_modal.choose_image": "Vel bilete",
+  "upload_modal.description_placeholder": "Ein rask brun rev hoppar over den late hunden",
+  "upload_modal.detect_text": "Oppdag tekst i biletet",
+  "upload_modal.edit_media": "Rediger medium",
+  "upload_modal.hint": "Klikk og dra sirkelen på førehandsvisninga for å velja eit fokuspunkt som alltid vil vera synleg på miniatyrbilete.",
+  "upload_modal.preparing_ocr": "Førebur OCR…",
+  "upload_modal.preview_label": "Førehandsvis ({ratio})",
   "upload_progress.label": "Lastar opp...",
   "upload_progress.processing": "Handsamar…",
   "username.taken": "Dette brukernavnet er tatt. Prøv et annet",
@@ -906,6 +882,8 @@
   "video.expand": "Utvid video",
   "video.fullscreen": "Fullskjerm",
   "video.hide": "Gøym video",
+  "video.mute": "Demp lyd",
   "video.pause": "Pause",
-  "video.play": "Spel av"
+  "video.play": "Spel av",
+  "video.unmute": "Av-dempe lyd"
 }
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index a18ccdc0dc..bb8b363ec5 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -29,6 +29,7 @@
   "account.endorse": "Vis frem på profilen",
   "account.featured_tags.last_status_at": "Siste innlegg {date}",
   "account.featured_tags.last_status_never": "Ingen Innlegg",
+  "account.featured_tags.title": "{name} sine fremhevede emneknagger",
   "account.follow": "Følg",
   "account.follow_back": "Følg tilbake",
   "account.followers": "Følgere",
@@ -85,30 +86,7 @@
   "alert.unexpected.message": "En uventet feil oppstod.",
   "alert.unexpected.title": "Ups!",
   "alt_text_badge.title": "Alternativ tekst",
-  "alt_text_modal.add_alt_text": "Legg til alternativ tekst",
-  "alt_text_modal.add_text_from_image": "Legg til tekst fra bildet",
-  "alt_text_modal.cancel": "Avbryt",
-  "alt_text_modal.change_thumbnail": "Endre miniatyrbilde",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Beskriv dette for folk med hørselsproblemer…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Beskriv dette for folk med synsproblemer…",
-  "alt_text_modal.done": "Ferdig",
   "announcement.announcement": "Kunngjøring",
-  "annual_report.summary.archetype.booster": "Den iskalde jeger",
-  "annual_report.summary.archetype.lurker": "Det snikende ullteppe",
-  "annual_report.summary.archetype.oracle": "Allviteren",
-  "annual_report.summary.archetype.pollster": "Meningsmåleren",
-  "annual_report.summary.archetype.replier": "Festens midtpunkt",
-  "annual_report.summary.followers.followers": "følgere",
-  "annual_report.summary.followers.total": "{count} tilsammen",
-  "annual_report.summary.here_it_is": "Her er ditt {year} ditt sett sammlet:",
-  "annual_report.summary.highlighted_post.by_favourites": "mest likte innlegg",
-  "annual_report.summary.highlighted_post.by_reblogs": "mest opphøyde innlegg",
-  "annual_report.summary.highlighted_post.by_replies": "innlegg med de fleste tilbakemeldinger",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "mest brukte applikasjoner",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "mest brukte evne knagg",
-  "annual_report.summary.most_used_hashtag.none": "Ingen",
-  "annual_report.summary.new_posts.new_posts": "nye innlegg",
   "attachments_list.unprocessed": "(ubehandlet)",
   "audio.hide": "Skjul lyd",
   "block_modal.remote_users_caveat": "Vi vil be serveren {domain} om å respektere din beslutning. Det er imidlertid ingen garanti at det blir overholdt, siden noen servere kan håndtere blokkeringer på forskjellig vis. Offentlige innlegg kan fortsatt være synlige for ikke-innloggede brukere.",
@@ -132,6 +110,7 @@
   "bundle_column_error.routing.body": "Den forespurte siden ble ikke funnet. Er du sikker på at URL-en i adresselinjen er riktig?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Lukk",
+  "bundle_modal_error.message": "Noe gikk galt da denne komponenten lastet.",
   "bundle_modal_error.retry": "Prøv igjen",
   "closed_registrations.other_server_instructions": "Siden Mastodon er desentralizert, kan du opprette en konto på en annen server og fortsatt kommunisere med denne.",
   "closed_registrations_modal.description": "Opprettelse av en konto på {domain} er for tiden ikke mulig, men vær oppmerksom på at du ikke trenger en konto spesifikt på {domain} for å kunne bruke Mastodon.",
@@ -145,7 +124,6 @@
   "column.direct": "Private omtaler",
   "column.directory": "Bla gjennom profiler",
   "column.domain_blocks": "Skjulte domener",
-  "column.edit_list": "Rediger listen",
   "column.favourites": "Favoritter",
   "column.firehose": "Tidslinjer",
   "column.follow_requests": "Følgeforespørsler",
@@ -162,7 +140,6 @@
   "column_header.pin": "Fest",
   "column_header.show_settings": "Vis innstillinger",
   "column_header.unpin": "Løsne",
-  "column_search.cancel": "Avbryt",
   "column_subheading.settings": "Innstillinger",
   "community.column_settings.local_only": "Kun lokalt",
   "community.column_settings.media_only": "Media only",
@@ -181,6 +158,7 @@
   "compose_form.poll.duration": "Avstemningens varighet",
   "compose_form.poll.multiple": "Flervalg",
   "compose_form.poll.option_placeholder": "Valg {number}",
+  "compose_form.poll.single": "Velg en",
   "compose_form.poll.switch_to_multiple": "Endre avstemning til å tillate flere valg",
   "compose_form.poll.switch_to_single": "Endre avstemning til å tillate ett valg",
   "compose_form.poll.type": "Stil",
@@ -207,7 +185,6 @@
   "confirmations.logout.confirm": "Logg ut",
   "confirmations.logout.message": "Er du sikker på at du vil logge ut?",
   "confirmations.logout.title": "Logg ut?",
-  "confirmations.missing_alt_text.secondary": "Legg ut likevel",
   "confirmations.mute.confirm": "Demp",
   "confirmations.redraft.confirm": "Slett og skriv på nytt",
   "confirmations.redraft.message": "Er du sikker på at du vil slette dette innlegget og lagre det på nytt? Favoritter og fremhevinger vil gå tapt, og svar til det originale innlegget vil bli foreldreløse.",
@@ -236,6 +213,10 @@
   "disabled_account_banner.text": "Din konto {disabledAccount} er for øyeblikket deaktivert.",
   "dismissable_banner.community_timeline": "Dette er de nyeste offentlige innleggene fra personer med kontoer på {domain}.",
   "dismissable_banner.dismiss": "Avvis",
+  "dismissable_banner.explore_links": "Disse nyhetene snakker folk om akkurat nå på denne og andre servere i det desentraliserte nettverket.",
+  "dismissable_banner.explore_statuses": "Disse innleggene fra denne og andre servere i det desentraliserte nettverket får økt oppmerksomhet på denne serveren akkurat nå. Nyere innlegg med flere fremhevinger og favoritter er rangert høyere.",
+  "dismissable_banner.explore_tags": "Disse emneknaggene snakker folk om akkurat nå, på denne og andre servere i det desentraliserte nettverket.",
+  "dismissable_banner.public_timeline": "Dette er de siste offentlige innleggene fra mennesker på det sosiale nettet som folk på {domain} følger.",
   "domain_block_modal.block": "Blokker server",
   "domain_block_modal.block_account_instead": "Blokker @{name} i stedet",
   "domain_block_modal.they_can_interact_with_old_posts": "Personer fra denne serveren kan samhandle med dine gamle innlegg.",
@@ -290,6 +271,7 @@
   "empty_column.hashtag": "Det er ingenting i denne emneknaggen ennå.",
   "empty_column.home": "Hjem-tidslinjen din er tom! Følg flere folk for å fylle den. {suggestions}",
   "empty_column.list": "Det er ingenting i denne listen ennå. Når medlemmene av denne listen legger ut nye statuser vil de dukke opp her.",
+  "empty_column.lists": "Du har ingen lister enda. Når du lager en, vil den dukke opp her.",
   "empty_column.mutes": "Du har ikke dempet noen brukere enda.",
   "empty_column.notification_requests": "Alt klart! Det er ingenting her. Når du mottar nye varsler, vises de her i henhold til dine innstillinger.",
   "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.",
@@ -300,6 +282,7 @@
   "error.unexpected_crash.next_steps_addons": "Prøv å deaktivere dem og laste siden på nytt. Hvis det ikke hjelper, kan du fremdeles bruke Mastodon via en annen nettleser eller en annen app.",
   "errors.unexpected_crash.copy_stacktrace": "Kopier stacktrace-en til utklippstavlen",
   "errors.unexpected_crash.report_issue": "Rapporter en feil",
+  "explore.search_results": "Søkeresultater",
   "explore.suggested_follows": "Personer",
   "explore.title": "Utforsk",
   "explore.trending_links": "Nyheter",
@@ -348,6 +331,7 @@
   "footer.about": "Om",
   "footer.directory": "Profilkatalog",
   "footer.get_app": "Last ned appen",
+  "footer.invite": "Inviter folk",
   "footer.keyboard_shortcuts": "Hurtigtaster",
   "footer.privacy_policy": "Personvernregler",
   "footer.source_code": "Vis kildekode",
@@ -395,11 +379,17 @@
   "ignore_notifications_modal.not_followers_title": "Overse varsler fra folk som ikke følger deg?",
   "ignore_notifications_modal.not_following_title": "Overse varsler fra folk du ikke følger?",
   "ignore_notifications_modal.private_mentions_title": "Overse varsler fra uoppfordrede private omtaler?",
-  "info_button.label": "Hjelp",
-  "interaction_modal.go": "Gå",
-  "interaction_modal.no_account_yet": "Har du ikke en konto ennå?",
+  "interaction_modal.description.favourite": "Med en konto på Mastodon, kan du favorittmarkere dette innlegget for å la forfatteren vite at du satte pris på det, og lagre innlegget til senere.",
+  "interaction_modal.description.follow": "Med en konto på Mastodon, kan du følge {name} for å få innleggene deres i tidslinjen din.",
+  "interaction_modal.description.reblog": "Med en konto på Mastodon, kan du fremheve dette innlegget for å dele det med dine egne følgere.",
+  "interaction_modal.description.reply": "Med en konto på Mastodon, kan du svare på dette innlegget.",
+  "interaction_modal.login.action": "Ta meg hjem",
+  "interaction_modal.login.prompt": "Domenet til serveren din, eks. mastodon.social",
+  "interaction_modal.no_account_yet": "Ennå ikke på Mastodon?",
   "interaction_modal.on_another_server": "På en annen server",
   "interaction_modal.on_this_server": "På denne serveren",
+  "interaction_modal.sign_in": "Du er ikke logget inn på denne serveren. Hvor har du kontoen din?",
+  "interaction_modal.sign_in_hint": "Tips: Det er på nettstedet der du registrerte deg. Hvis du ikke husker det, kan du se etter velkomst e-posten i innboksen. Du kan også skrive inn hele brukernavnet ditt! (eks. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Favorittmarker innlegget til {name}",
   "interaction_modal.title.follow": "Følg {name}",
   "interaction_modal.title.reblog": "Fremhev {name} sitt innlegg",
@@ -449,19 +439,20 @@
   "link_preview.author": "Av {name}",
   "link_preview.more_from_author": "Mer fra {name}",
   "link_preview.shares": "{count, plural, one {{counter} innlegg} other {{counter} innlegg}}",
-  "lists.add_member": "Legg til",
-  "lists.add_to_list": "Legg til i listen",
-  "lists.create": "Opprett",
+  "lists.account.add": "Legg til i listen",
+  "lists.account.remove": "Fjern fra listen",
   "lists.delete": "Slett listen",
-  "lists.done": "Ferdig",
   "lists.edit": "Rediger listen",
-  "lists.no_results_found": "Ingen treff.",
-  "lists.remove_member": "Fjern",
+  "lists.edit.submit": "Endre tittel",
+  "lists.exclusive": "Skjul disse innleggene i tidslinjen",
+  "lists.new.create": "Legg til liste",
+  "lists.new.title_placeholder": "Ny listetittel",
   "lists.replies_policy.followed": "Enhver fulgt bruker",
   "lists.replies_policy.list": "Medlemmer i listen",
   "lists.replies_policy.none": "Ingen",
-  "lists.save": "Lagre",
-  "lists.search": "Søk",
+  "lists.replies_policy.title": "Vis svar på:",
+  "lists.search": "Søk blant personer du følger",
+  "lists.subheading": "Dine lister",
   "load_pending": "{count, plural,one {# ny gjenstand} other {# nye gjenstander}}",
   "loading_indicator.label": "Laster…",
   "media_gallery.hide": "Skjul",
@@ -579,19 +570,43 @@
   "notifications_permission_banner.enable": "Skru på skrivebordsvarsler",
   "notifications_permission_banner.how_to_control": "For å motta varsler når Mastodon ikke er åpne, aktiver desktop varsler. Du kan kontrollere nøyaktig hvilke typer interaksjoner genererer skrivebordsvarsler gjennom {icon} -knappen ovenfor når de er aktivert.",
   "notifications_permission_banner.title": "Aldri gå glipp av noe",
-  "onboarding.follows.back": "Tilbake",
-  "onboarding.follows.done": "Ferdig",
+  "onboarding.action.back": "Ta meg tilbake",
+  "onboarding.actions.back": "Ta meg tilbake",
+  "onboarding.actions.go_to_explore": "Se hva som er populært",
+  "onboarding.actions.go_to_home": "Gå til din tidslinje",
+  "onboarding.compose.template": "Hei #Mastodon!",
   "onboarding.follows.empty": "Dessverre kan ingen resultater vises akkurat nå. Du kan prøve å bruke søk eller bla gjennom utforske-siden for å finne folk å følge, eller prøve igjen senere.",
-  "onboarding.follows.search": "Søk",
+  "onboarding.follows.lead": "Hjem-skjermen din er den viktigste måten å oppleve Mastodon på. Jo flere du følger, jo mer aktiv og interessant blir det. For å komme i gang, er her noen forslag:",
+  "onboarding.follows.title": "Populært på Mastodon",
   "onboarding.profile.discoverable": "Gjør min profil synlig",
   "onboarding.profile.display_name": "Visningsnavn",
   "onboarding.profile.display_name_hint": "Ditt fulle navn eller ditt morsomme navn…",
+  "onboarding.profile.lead": "Du kan alltid fullføre dette senere i innstillingene, der enda flere tilpasningsalternativer er tilgjengelige.",
   "onboarding.profile.note": "Om meg",
   "onboarding.profile.note_hint": "Du kan @nevne andre eller #emneknagger…",
   "onboarding.profile.save_and_continue": "Lagre og fortsett",
   "onboarding.profile.title": "Konfigurering av profil",
   "onboarding.profile.upload_avatar": "Last opp profilbilde",
   "onboarding.profile.upload_header": "Last opp profiltoppbilde",
+  "onboarding.share.lead": "La folk vite hvordan de kan finne deg på Mastodon!",
+  "onboarding.share.message": "Jeg er {username} på #Mastodon! Kom og følg meg på {url}",
+  "onboarding.share.next_steps": "Mulige neste trinn:",
+  "onboarding.share.title": "Del profilen din",
+  "onboarding.start.lead": "Du er nå en del av Mastodon, en unik, desentralisert plattform for sosiale medier der du, ikke en algoritme, styrer din egen opplevelse. La oss få deg igang på dette nye sosiale eventyret:",
+  "onboarding.start.skip": "Vil du hoppe over dette?",
+  "onboarding.start.title": "Du klarte det!",
+  "onboarding.steps.follow_people.body": "Du bestemmer over din egen tidslinje. La oss fylle den med ineressante personer.",
+  "onboarding.steps.follow_people.title": "Tilpass hjem-skjermen din",
+  "onboarding.steps.publish_status.body": "Si hallo til verdenen med tekst, bilder, videoer, eller meningsmålinger {emoji}",
+  "onboarding.steps.publish_status.title": "Lag ditt første innlegg",
+  "onboarding.steps.setup_profile.body": "Få flere samhandlinger ved å ha en fullstendig profil.",
+  "onboarding.steps.setup_profile.title": "Tilpass profilen din",
+  "onboarding.steps.share_profile.body": "La vennene dine vite hvordan du finner deg på Mastodon",
+  "onboarding.steps.share_profile.title": "Del profilen din",
+  "onboarding.tips.2fa": "<strong>Visste du?</strong> Du kan sikre kontoen din ved å sette opp 2-trinnsinnlogging i kontoinnstillingene dine. Det fungerer med enhver TOTP-app du velger selv, intet telefonnummer nødvendig!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Visste du?</strong> Siden Mastodon er desentralisert, vil noen profiler du kommer over komme fra andre servere enn din egen. Du kan likevel samhandle med dem sømløst! Serveren deres finner du i den andre halvparten av brukernavnet!",
+  "onboarding.tips.migration": "<strong>Visste du?</strong> Hvis du føler at {domain} ikke er et flott servervalg for deg i fremtiden, kan du flytte til en annen Mastodon-server uten å miste følgerene dine. Du kan også være vert for din egen server!",
+  "onboarding.tips.verification": "<strong>Visste du?</strong> Du kan bekrefte kontoen din ved å legge en lenke til Mastodon-profilen din på ditt eget nettsted og legge nettstedet til i profilen din. Ingen gebyrer eller dokumenter nødvendig!",
   "password_confirmation.exceeds_maxlength": "Passordbekreftelsen overskrider den maksimale passordlengden",
   "password_confirmation.mismatching": "Passordene er ulike",
   "picture_in_picture.restore": "Legg den tilbake",
@@ -607,6 +622,7 @@
   "poll_button.remove_poll": "Fjern avstemningen",
   "privacy.change": "Juster synlighet",
   "privacy.direct.long": "Alle nevnt i innlegget",
+  "privacy.direct.short": "Spesifikke folk",
   "privacy.private.long": "Bare følgerne dine",
   "privacy.private.short": "Følgere",
   "privacy.public.long": "Alle på og utenfor Mastodon",
@@ -617,6 +633,8 @@
   "privacy_policy.title": "Personvernregler",
   "recommended": "Anbefalt",
   "refresh": "Oppfrisk",
+  "regeneration_indicator.label": "Laster…",
+  "regeneration_indicator.sublabel": "Dine tidslinje blir gjort klar!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# dag} other {# dager}} siden",
   "relative_time.full.hours": "{number, plural, one {# time} other {# timer}} siden",
@@ -697,9 +715,10 @@
   "search_results.accounts": "Profiler",
   "search_results.all": "Alle",
   "search_results.hashtags": "Emneknagger",
-  "search_results.no_results": "Ingen resultater.",
+  "search_results.nothing_found": "Fant ikke noe for disse søkeordene",
   "search_results.see_all": "Se alle",
   "search_results.statuses": "Innlegg",
+  "search_results.title": "Søk etter {q}",
   "server_banner.about_active_users": "Personer som har brukt denne serveren i løpet av de siste 30 dagene (aktive brukere månedlig)",
   "server_banner.active_users": "aktive brukere",
   "server_banner.administered_by": "Administrert av:",
@@ -763,7 +782,6 @@
   "subscribed_languages.target": "Endre abonnerte språk for {target}",
   "tabs_bar.home": "Hjem",
   "tabs_bar.notifications": "Varslinger",
-  "terms_of_service.title": "Bruksvilkår",
   "time_remaining.days": "{number, plural,one {# dag} other {# dager}} igjen",
   "time_remaining.hours": "{number, plural, one {# time} other {# timer}} igjen",
   "time_remaining.minutes": "{number, plural, one {# minutt} other {# minutter}} igjen",
@@ -779,7 +797,21 @@
   "upload_button.label": "Legg til media",
   "upload_error.limit": "Filopplastingsgrensen er oversteget.",
   "upload_error.poll": "Filopplasting er ikke tillatt for avstemninger.",
+  "upload_form.audio_description": "Beskriv det for folk med hørselstap",
+  "upload_form.description": "Beskriv for synshemmede",
   "upload_form.edit": "Rediger",
+  "upload_form.thumbnail": "Endre miniatyrbilde",
+  "upload_form.video_description": "Beskriv det for folk med hørselstap eller synshemminger",
+  "upload_modal.analyzing_picture": "Analyserer bildet …",
+  "upload_modal.apply": "Bruk",
+  "upload_modal.applying": "Utfører…",
+  "upload_modal.choose_image": "Velg et bilde",
+  "upload_modal.description_placeholder": "Når du en gang kommer, neste sommer, skal vi atter drikke vin",
+  "upload_modal.detect_text": "Oppdag tekst i bildet",
+  "upload_modal.edit_media": "Rediger media",
+  "upload_modal.hint": "Klikk eller dra sirkelen i forhåndsvisningen for å velge hovedpunktet som alltid vil bli vist i alle miniatyrbilder.",
+  "upload_modal.preparing_ocr": "Forbereder OCR…",
+  "upload_modal.preview_label": "Forhåndsvisning ({ratio})",
   "upload_progress.label": "Laster opp...",
   "upload_progress.processing": "Behandler…",
   "username.taken": "Dette brukernavnet er tatt. Prøv et annet",
@@ -789,6 +821,8 @@
   "video.expand": "Utvid video",
   "video.fullscreen": "Fullskjerm",
   "video.hide": "Skjul video",
+  "video.mute": "Skru av lyd",
   "video.pause": "Pause",
-  "video.play": "Spill av"
+  "video.play": "Spill av",
+  "video.unmute": "Skru på lyd"
 }
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 74ae8fad4d..c537d35898 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -26,6 +26,7 @@
   "account.endorse": "Mostrar pel perfil",
   "account.featured_tags.last_status_at": "Darrièra publicacion lo {date}",
   "account.featured_tags.last_status_never": "Cap de publicacion",
+  "account.featured_tags.title": "Etiquetas en avant de {name}",
   "account.follow": "Sègre",
   "account.follow_back": "Sègre en retorn",
   "account.followers": "Seguidors",
@@ -73,14 +74,7 @@
   "alert.unexpected.message": "Una error s’es producha.",
   "alert.unexpected.title": "Ops !",
   "alt_text_badge.title": "Tèxt alternatiu",
-  "alt_text_modal.add_alt_text": "Inserir tèxte alteratiu",
-  "alt_text_modal.add_text_from_image": "Apondre tèxte de l’imatge",
-  "alt_text_modal.cancel": "Anullar",
-  "alt_text_modal.change_thumbnail": "Cambiar de miniatura",
-  "alt_text_modal.done": "Acabat",
   "announcement.announcement": "Anóncia",
-  "annual_report.summary.followers.followers": "seguidors",
-  "annual_report.summary.followers.total": "{count} en total",
   "attachments_list.unprocessed": "(pas tractat)",
   "audio.hide": "Amagar àudio",
   "block_modal.show_less": "Ne veire mens",
@@ -93,6 +87,7 @@
   "bundle_column_error.return": "Tornar a l’acuèlh",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Tampar",
+  "bundle_modal_error.message": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.",
   "bundle_modal_error.retry": "Tornar ensajar",
   "closed_registrations_modal.find_another_server": "Trobar un autre servidor",
   "closed_registrations_modal.title": "S’inscriure a Mastodon",
@@ -176,6 +171,8 @@
   "disabled_account_banner.text": "Vòstre compte {disabledAccount} es actualament desactivat.",
   "dismissable_banner.community_timeline": "Vaquí las publicacions mai recentas del monde amb un compte albergat per {domain}.",
   "dismissable_banner.dismiss": "Ignorar",
+  "dismissable_banner.explore_links": "Aquestas istòrias ne parlan lo monde d’aqueste servidor e dels autres servidors del malhum descentralizat d’aquesta passa.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Embarcar aqueste estatut per lo far veire sus un site Internet en copiar lo còdi çai-jos.",
   "embed.preview": "Semblarà aquò :",
   "emoji_button.activity": "Activitats",
@@ -205,6 +202,7 @@
   "empty_column.hashtag": "I a pas encara de contengut ligat a aquesta etiqueta.",
   "empty_column.home": "Vòstre flux d’acuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
   "empty_column.list": "I a pas res dins la lista pel moment. Quand de membres d’aquesta lista publiquen de novèls estatuts los veiretz aquí.",
+  "empty_column.lists": "Encara avètz pas cap de lista. Quand ne creetz una, apareisserà aquí.",
   "empty_column.mutes": "Encara avètz pas mes en silenci degun.",
   "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
   "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autres servidors per garnir lo flux public",
@@ -214,6 +212,7 @@
   "error.unexpected_crash.next_steps_addons": "Ensajatz de los desactivar o actualizatz la pagina. Se aquò ajuda pas, podètz ensajar d’utilizar Mastodon via un autre navigador o una aplicacion nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar las traças al quichapapièrs",
   "errors.unexpected_crash.report_issue": "Senhalar un problèma",
+  "explore.search_results": "Resultats de recèrca",
   "explore.suggested_follows": "Personas",
   "explore.title": "Explorar",
   "explore.trending_links": "Novèlas",
@@ -237,6 +236,7 @@
   "footer.about": "A prepaus",
   "footer.directory": "Annuari de perfils",
   "footer.get_app": "Obténer l’aplicacion",
+  "footer.invite": "Convidar de monde",
   "footer.keyboard_shortcuts": "Acorchis clavièr",
   "footer.privacy_policy": "Politica de confidencialitat",
   "footer.source_code": "Veire lo còdi font",
@@ -311,11 +311,19 @@
   "limited_account_hint.action": "Afichar lo perfil de tota manièra",
   "limited_account_hint.title": "Aqueste perfil foguèt rescondut per la moderacion de {domain}.",
   "link_preview.author": "Per {name}",
+  "lists.account.add": "Ajustar a la lista",
+  "lists.account.remove": "Levar de la lista",
   "lists.delete": "Suprimir la lista",
   "lists.edit": "Modificar la lista",
+  "lists.edit.submit": "Cambiar lo títol",
+  "lists.new.create": "Ajustar una lista",
+  "lists.new.title_placeholder": "Títol de la nòva lista",
   "lists.replies_policy.followed": "Quin seguidor que siá",
   "lists.replies_policy.list": "Membres de la lista",
   "lists.replies_policy.none": "Degun",
+  "lists.replies_policy.title": "Mostrar las responsas a :",
+  "lists.search": "Cercar demest lo mond que seguètz",
+  "lists.subheading": "Vòstras listas",
   "load_pending": "{count, plural, one {# nòu element} other {# nòu elements}}",
   "loading_indicator.label": "Cargament…",
   "navigation_bar.about": "A prepaus",
@@ -386,8 +394,27 @@
   "notifications_permission_banner.enable": "Activar las notificacions burèu",
   "notifications_permission_banner.how_to_control": "Per recebre las notificacions de Mastodon quand es pas dobèrt, activatz las notificacions de burèu. Podètz precisar quin tipe de notificacion generarà una notificacion de burèu via lo boton {icon} dessús un còp activadas.",
   "notifications_permission_banner.title": "Manquetz pas jamai res",
+  "onboarding.action.back": "Tornar en rèire",
+  "onboarding.actions.back": "Tornar en rèire",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "Adiu #Mastodon !",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
   "onboarding.profile.display_name": "Nom d’afichatge",
   "onboarding.profile.note": "Biografia",
+  "onboarding.share.title": "Partejar vòstre perfil",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "Tot es prèst !",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Escrivètz vòstre primièr tut",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "Lo tornar",
   "poll.closed": "Tampat",
   "poll.refresh": "Actualizar",
@@ -404,6 +431,8 @@
   "privacy_policy.last_updated": "Darrièra actualizacion {date}",
   "privacy_policy.title": "Politica de confidencialitat",
   "refresh": "Actualizar",
+  "regeneration_indicator.label": "Cargament…",
+  "regeneration_indicator.sublabel": "Sèm a preparar vòstre flux d’acuèlh !",
   "relative_time.days": "fa {number}d",
   "relative_time.full.days": "{number, plural, one {# jorn} other {# jorns}} ago",
   "relative_time.full.hours": "fa {number, plural, one {# ora} other {# oras}}",
@@ -461,8 +490,10 @@
   "search_results.accounts": "Perfils",
   "search_results.all": "Tot",
   "search_results.hashtags": "Etiquetas",
+  "search_results.nothing_found": "Cap de resultat per aquestes tèrmes de recèrca",
   "search_results.see_all": "O veire tot",
   "search_results.statuses": "Tuts",
+  "search_results.title": "Recèrca : {q}",
   "server_banner.active_users": "utilizaires actius",
   "server_banner.administered_by": "Administrat per :",
   "server_banner.server_stats": "Estatisticas del servidor :",
@@ -539,7 +570,21 @@
   "upload_button.label": "Ajustar un mèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Talha maximum pels mandadís subrepassada.",
   "upload_error.poll": "Lo mandadís de fichièr es pas autorizat pels sondatges.",
+  "upload_form.audio_description": "Descriure per las personas amb pèrdas auditivas",
+  "upload_form.description": "Descripcion pels mal vesents",
   "upload_form.edit": "Modificar",
+  "upload_form.thumbnail": "Cambiar la vinheta",
+  "upload_form.video_description": "Descriure per las personas amb pèrdas auditivas o mal vesent",
+  "upload_modal.analyzing_picture": "Analisi de l’imatge…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.applying": "Aplicacion…",
+  "upload_modal.choose_image": "Causir un imatge",
+  "upload_modal.description_placeholder": "Lo dròlle bilingüe manja un yaourt de ròcs exagonals e kiwis verds farà un an mai",
+  "upload_modal.detect_text": "Detectar lo tèxt de l’imatge",
+  "upload_modal.edit_media": "Modificar lo mèdia",
+  "upload_modal.hint": "Clicatz o lisatz lo cercle de l’apercebut per causir lo ponch que serà totjorn visible dins las vinhetas.",
+  "upload_modal.preparing_ocr": "Preparacion de la ROC…",
+  "upload_modal.preview_label": "Apercebut ({ratio})",
   "upload_progress.label": "Mandadís…",
   "upload_progress.processing": "Tractament…",
   "username.taken": "Aqueste nom d’utilizaire es pres. Ensajatz-ne un autre",
@@ -549,6 +594,8 @@
   "video.expand": "Agrandir la vidèo",
   "video.fullscreen": "Ecran complèt",
   "video.hide": "Amagar la vidèo",
+  "video.mute": "Copar lo son",
   "video.pause": "Pausa",
-  "video.play": "Lectura"
+  "video.play": "Lectura",
+  "video.unmute": "Restablir lo son"
 }
diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json
index c294bcbddb..ea2c259984 100644
--- a/app/javascript/mastodon/locales/pa.json
+++ b/app/javascript/mastodon/locales/pa.json
@@ -57,19 +57,7 @@
   "admin.dashboard.retention.cohort_size": "ਨਵੇਂ ਵਰਤੋਂਕਾਰ",
   "alert.unexpected.title": "ਓਹੋ!",
   "alt_text_badge.title": "ਬਦਲੀ ਲਿਖਤ",
-  "alt_text_modal.cancel": "ਰੱਦ ਕਰੋ",
-  "alt_text_modal.done": "ਮੁਕੰਮਲ",
   "announcement.announcement": "ਹੋਕਾ",
-  "annual_report.summary.followers.followers": "ਫ਼ਾਲੋਅਰ",
-  "annual_report.summary.followers.total": "{count} ਕੁੱਲ",
-  "annual_report.summary.highlighted_post.by_favourites": "ਸਭ ਤੋਂ ਵੱਧ ਪਸੰਦ ਕੀਤੀ ਪੋਸਟ",
-  "annual_report.summary.highlighted_post.by_reblogs": "ਸਭ ਤੋਂ ਵੱਧ ਬੂਸਟ ਕੀਤੀ ਪੋਸਟ",
-  "annual_report.summary.highlighted_post.by_replies": "ਸਭ ਤੋਂ ਵੱਧ ਜਵਾਬ ਦਿੱਤੀ ਗਈ ਪੋਸਟ",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "ਸਭ ਤੋਂ ਵੱਧ ਵਰਤੀ ਐਪ",
-  "annual_report.summary.most_used_hashtag.none": "ਕੋਈ ਨਹੀਂ",
-  "annual_report.summary.new_posts.new_posts": "ਨਵੀਆਂ ਪੋਸਟਾਂ",
-  "annual_report.summary.thanks": "Mastodon ਦਾ ਹਿੱਸਾ ਬਣਨ ਵਾਸਤੇ ਧੰਨਵਾਦ ਹੈ!",
   "audio.hide": "ਆਡੀਓ ਨੂੰ ਲੁਕਾਓ",
   "block_modal.show_less": "ਘੱਟ ਦਿਖਾਓ",
   "block_modal.show_more": "ਵੱਧ ਦਿਖਾਓ",
@@ -82,22 +70,20 @@
   "bundle_column_error.return": "ਵਾਪਸ ਮੁੱਖ ਸਫ਼ੇ ਉੱਤੇ ਜਾਓ",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "ਬੰਦ ਕਰੋ",
+  "bundle_modal_error.message": "ਭਾਗ ਲੋਡ ਕਰਨ ਦੌਰਾਨ ਕੁਝ ਗਲਤ ਵਾਪਰਿਆ ਹੈ।",
   "bundle_modal_error.retry": "ਮੁੜ-ਕੋਸ਼ਿਸ਼ ਕਰੋ",
   "closed_registrations_modal.title": "Mastodon ਲਈ ਸਾਈਨ ਅੱਪ ਕਰੋ",
   "column.about": "ਸਾਡੇ ਬਾਰੇ",
   "column.blocks": "ਪਾਬੰਦੀ ਲਾਏ ਵਰਤੋਂਕਾਰ",
   "column.bookmarks": "ਬੁੱਕਮਾਰਕ",
   "column.community": "ਲੋਕਲ ਸਮਾਂ-ਲਾਈਨ",
-  "column.create_list": "ਸੂਚੀ ਬਣਾਓ",
   "column.direct": "ਨਿੱਜੀ ਜ਼ਿਕਰ",
   "column.directory": "ਪ੍ਰੋਫਾਈਲਾਂ ਨੂੰ ਦੇਖੋ",
   "column.domain_blocks": "ਪਾਬੰਦੀ ਲਾਏ ਡੋਮੇਨ",
-  "column.edit_list": "ਸੂਚੀ ਨੂੰ ਸੋਧੋ",
   "column.favourites": "ਮਨਪਸੰਦ",
   "column.firehose": "ਲਾਈਵ ਫੀਡ",
   "column.follow_requests": "ਫ਼ਾਲੋ ਦੀਆਂ ਬੇਨਤੀਆਂ",
   "column.home": "ਮੁੱਖ ਸਫ਼ਾ",
-  "column.list_members": "ਸੂਚੀ ਦੇ ਮੈਂਬਰ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰੋ",
   "column.lists": "ਸੂਚੀਆਂ",
   "column.mutes": "ਮੌਨ ਕੀਤੇ ਵਰਤੋਂਕਾਰ",
   "column.notifications": "ਸੂਚਨਾਵਾਂ",
@@ -109,7 +95,6 @@
   "column_header.pin": "ਟੰਗੋ",
   "column_header.show_settings": "ਸੈਟਿੰਗਾਂ ਦਿਖਾਓ",
   "column_header.unpin": "ਲਾਹੋ",
-  "column_search.cancel": "ਰੱਦ ਕਰੋ",
   "column_subheading.settings": "ਸੈਟਿੰਗਾਂ",
   "community.column_settings.local_only": "ਸਿਰਫ ਲੋਕਲ ਹੀ",
   "community.column_settings.media_only": "ਸਿਰਫ ਮੀਡੀਆ ਹੀ",
@@ -126,7 +111,6 @@
   "compose_form.lock_disclaimer.lock": "ਲਾਕ ਹੈ",
   "compose_form.placeholder": "ਤੁਹਾਡੇ ਮਨ ਵਿੱਚ ਕੀ ਹੈ?",
   "compose_form.poll.option_placeholder": "{number} ਚੋਣ",
-  "compose_form.poll.single": "ਇਕੱਲੀ ਚੋਣ",
   "compose_form.poll.type": "ਸਟਾਈਲ",
   "compose_form.publish": "ਪੋਸਟ",
   "compose_form.publish_form": "ਨਵੀਂ ਪੋਸਟ",
@@ -145,12 +129,9 @@
   "confirmations.delete_list.title": "ਸੂਚੀ ਨੂੰ ਹਟਾਉਣਾ ਹੈ?",
   "confirmations.discard_edit_media.confirm": "ਰੱਦ ਕਰੋ",
   "confirmations.edit.confirm": "ਸੋਧ",
-  "confirmations.follow_to_list.confirm": "ਫ਼ਾਲੋ ਕਰੋ ਅਤੇ ਲਿਸਟ 'ਚ ਜੋੜੋ",
-  "confirmations.follow_to_list.title": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਫ਼ਾਲੋ ਕਰਨਾ ਹੈ?",
   "confirmations.logout.confirm": "ਬਾਹਰ ਹੋਵੋ",
   "confirmations.logout.message": "ਕੀ ਤੁਸੀਂ ਲਾਗ ਆਉਟ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?",
   "confirmations.logout.title": "ਲਾਗ ਆਉਟ ਕਰਨਾ ਹੈ?",
-  "confirmations.missing_alt_text.secondary": "ਕਿਵੇਂ ਵੀ ਪੋਸਟ ਕਰੋ",
   "confirmations.mute.confirm": "ਮੌਨ ਕਰੋ",
   "confirmations.redraft.confirm": "ਹਟਾਓ ਤੇ ਮੁੜ-ਡਰਾਫਟ",
   "confirmations.reply.confirm": "ਜਵਾਬ ਦੇਵੋ",
@@ -173,6 +154,8 @@
   "disabled_account_banner.account_settings": "ਖਾਤੇ ਦੀਆਂ ਸੈਟਿੰਗਾਂ",
   "disabled_account_banner.text": "ਤੁਹਾਡਾ ਖਾਤਾ {disabledAccount} ਇਸ ਵੇਲੇ ਅਸਮਰੱਥ ਕੀਤਾ ਹੈ।",
   "dismissable_banner.dismiss": "ਰੱਦ ਕਰੋ",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "domain_block_modal.block": "ਸਰਵਰ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ",
   "domain_block_modal.block_account_instead": "ਇਸ ਦੀ ਬਜਾਏ @{name} ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ",
   "domain_block_modal.title": "ਡੋਮੇਨ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਉਣੀ ਹੈ?",
@@ -204,6 +187,7 @@
   "empty_column.home": "ਤੁਹਾਡੀ ਟਾਈਮ-ਲਾਈਨ ਖਾਲੀ ਹੈ! ਇਸ ਨੂੰ ਭਰਨ ਲਈ ਹੋਰ ਲੋਕਾਂ ਨੂੰ ਫ਼ਾਲੋ ਕਰੋ।",
   "empty_column.list": "ਇਸ ਸੂਚੀ ਵਿੱਚ ਹਾਲੇ ਕੁਝ ਵੀ ਨਹੀਂ ਹੈ। ਜਦੋਂ ਇਸ ਸੂਚੀ ਦੇ ਮੈਂਬਰ ਨਵੀਆਂ ਪੋਸਟਾਂ ਪਾਉਂਦੇ ਹਨ ਤਾਂ ਉਹ ਇੱਥੇ ਦਿਖਾਈ ਦੇਣਗੀਆਂ।",
   "errors.unexpected_crash.report_issue": "ਮੁੱਦੇ ਦੀ ਰਿਪੋਰਟ ਕਰੋ",
+  "explore.search_results": "ਖੋਜ ਦੇ ਨਤੀਜੇ",
   "explore.suggested_follows": "ਲੋਕ",
   "explore.title": "ਪੜਚੋਲ ਕਰੋ",
   "explore.trending_links": "ਖ਼ਬਰਾਂ",
@@ -233,11 +217,11 @@
   "footer.about": "ਸਾਡੇ ਬਾਰੇ",
   "footer.directory": "ਪਰੋਫਾਇਲ ਡਾਇਰੈਕਟਰੀ",
   "footer.get_app": "ਐਪ ਲਵੋ",
+  "footer.invite": "ਲੋਕਾਂ ਨੂੰ ਸੱਦਾ ਭੇਜੋ",
   "footer.keyboard_shortcuts": "ਕੀਬੋਰਡ ਸ਼ਾਰਟਕੱਟ",
   "footer.privacy_policy": "ਪਰਦੇਦਾਰੀ ਨੀਤੀ",
   "footer.source_code": "ਸਰੋਤ ਕੋਡ ਵੇਖੋ",
   "footer.status": "ਹਾਲਤ",
-  "footer.terms_of_service": "ਸੇਵਾ ਦੀਆਂ ਸ਼ਰਤਾਂ",
   "generic.saved": "ਸਾਂਭੀ ਗਈ",
   "getting_started.heading": "ਸ਼ੁਰੂ ਕਰੀਏ",
   "hashtag.column_header.tag_mode.all": "ਅਤੇ {additional}",
@@ -260,17 +244,14 @@
   "home.hide_announcements": "ਐਲਾਨਾਂ ਨੂੰ ਓਹਲੇ ਕਰੋ",
   "home.pending_critical_update.link": "ਅੱਪਡੇਟ ਵੇਖੋ",
   "ignore_notifications_modal.ignore": "ਨੋਟਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਅਣਡਿੱਠਾ ਕਰੋ",
-  "info_button.label": "ਮਦਦ",
-  "interaction_modal.go": "ਜਾਓ",
-  "interaction_modal.no_account_yet": "ਹਾਲੇ ਖਾਤਾ ਨਹੀਂ ਹੈ?",
+  "interaction_modal.login.action": "ਮੈਨੂੰ ਮੁੱਖ ਸਫ਼ੇ ਉੱਤੇ ਲੈ ਜਾਓ",
+  "interaction_modal.no_account_yet": "Mastodon ਉੱਤੇ ਨਹੀਂ ਹੋ?",
   "interaction_modal.on_another_server": "ਵੱਖਰੇ ਸਰਵਰ ਉੱਤੇ",
   "interaction_modal.on_this_server": "ਇਸ ਸਰਵਰ ਉੱਤੇ",
   "interaction_modal.title.favourite": "{name} ਦੀ ਪੋਸਟ ਨੂੰ ਪਸੰਦ ਕਰੋ",
   "interaction_modal.title.follow": "{name} ਨੂੰ ਫ਼ਾਲੋ ਕਰੋ",
   "interaction_modal.title.reblog": "{name} ਦੀ ਪੋਸਟ ਨੂੰ ਬੂਸਟ ਕਰੋ",
   "interaction_modal.title.reply": "{name} ਦੀ ਪੋਸਟ ਦਾ ਜਵਾਬ ਦਿਓ",
-  "interaction_modal.title.vote": "{name} ਦੀ ਚੋਣ ਵਾਸਤੇ ਵੋਟ ਪਾਓ",
-  "interaction_modal.username_prompt": "ਜਿਵੇਂ {example}",
   "intervals.full.days": "{number, plural, one {# ਦਿਨ} other {# ਦਿਨ}}",
   "intervals.full.hours": "{number, plural, one {# ਘੰਟਾ} other {# ਘੰਟੇ}}",
   "intervals.full.minutes": "{number, plural, one {# ਮਿੰਟ} other {# ਮਿੰਟ}}",
@@ -305,7 +286,6 @@
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toggle_sensitivity": "ਮੀਡੀਆ ਦਿਖਾਉਣ/ਲੁਕਾਉਣ ਲਈ",
   "keyboard_shortcuts.toot": "ਨਵੀਂ ਪੋਸਟ ਸ਼ੁਰੂ ਕਰੋ",
-  "keyboard_shortcuts.translate": "ਪੋਸਟ ਨੂੰ ਅਨੁਵਾਦ ਕਰਨ ਲਈ",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
   "keyboard_shortcuts.up": "ਸੂਚੀ ਵਿੱਚ ਉੱਤੇ ਭੇਜੋ",
   "lightbox.close": "ਬੰਦ ਕਰੋ",
@@ -315,27 +295,13 @@
   "link_preview.author": "{name} ਵਲੋਂ",
   "link_preview.more_from_author": "{name} ਵਲੋਂ ਹੋਰ",
   "link_preview.shares": "{count, plural, one {{counter} ਪੋਸਟ} other {{counter} ਪੋਸਟਾਂ}}",
-  "lists.add_member": "ਜੋੜੋ",
-  "lists.add_to_list": "ਸੂਚੀ ਵਿੱਚ ਜੋੜੋ",
-  "lists.create": "ਬਣਾਓ",
-  "lists.create_list": "ਸੂਚੀ ਬਣਾਓ",
+  "lists.account.add": "ਸੂਚੀ ਵਿੱਚ ਜੋੜੋ",
+  "lists.account.remove": "ਸੂਚੀ ਵਿਚੋਂ ਹਟਾਓ",
   "lists.delete": "ਸੂਚੀ ਹਟਾਓ",
-  "lists.done": "ਮੁਕੰਮਲ",
   "lists.edit": "ਸੂਚੀ ਨੂੰ ਸੋਧੋ",
-  "lists.find_users_to_add": "ਜੋੜਨ ਲਈ ਵਰਤੋਂਕਾਰ ਲੱਭੋ",
-  "lists.list_members": "ਮੈਂਬਰਾਂ ਦੀ ਸੂਚੀ",
-  "lists.list_members_count": "{count, plural, one {# ਮੈਂਬਰ} other {# ਮੈਂਬਰ}}",
-  "lists.list_name": "ਸੂਚੀ ਦਾ ਨਾਂ",
-  "lists.new_list_name": "ਨਵੀਂ ਸੂਚੀਂ ਦਾ ਨਾਂ",
-  "lists.no_lists_yet": "ਹਾਲੇ ਕੋਈ ਵੀ ਸੂਚੀ ਨਹੀਂ ਹੈ।",
-  "lists.no_members_yet": "ਹਾਲੇ ਮੈਂਬਰ ਨਹੀਂ ਹਨ।",
-  "lists.no_results_found": "ਕੋਈ ਨਤੀਜਾ ਨਹੀਂ ਮਿਲਿਆ।",
-  "lists.remove_member": "ਹਟਾਓ",
   "lists.replies_policy.followed": "ਕੋਈ ਵੀ ਫ਼ਾਲੋ ਕੀਤਾ ਵਰਤੋਂਕਾਰ",
   "lists.replies_policy.list": "ਸੂਚੀ ਦੇ ਮੈਂਬਰ",
   "lists.replies_policy.none": "ਕੋਈ ਨਹੀਂ",
-  "lists.save": "ਸੰਭਾਲੋ",
-  "lists.search": "ਖੋਜੋ",
   "loading_indicator.label": "ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ…",
   "media_gallery.hide": "ਲੁਕਾਓ",
   "mute_modal.hide_from_notifications": "ਨੋਟੀਫਿਕੇਸ਼ਨਾਂ ਵਿੱਚੋਂ ਲੁਕਾਓ",
@@ -423,13 +389,26 @@
   "notifications.policy.filter_not_followers_title": "ਲੋਕ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦੇ",
   "notifications.policy.filter_not_following_hint": "ਜਦ ਤੱਕ ਤੁਸੀਂ ਉਹਨਾਂ ਨੂੰ ਖੁਦ ਮਨਜ਼ੂਰੀ ਨਹੀਂ ਦਿੰਦੇ",
   "notifications_permission_banner.enable": "ਡੈਸਕਟਾਪ ਸੂਚਨਾਵਾਂ ਸਮਰੱਥ ਕਰੋ",
-  "onboarding.follows.back": "ਪਿੱਛੇ",
-  "onboarding.follows.done": "ਮੁਕੰਮਲ",
-  "onboarding.follows.search": "ਖੋਜੋ",
+  "onboarding.actions.go_to_explore": "ਮੈਨੂੰ ਰੁਝਾਨ ਵੇਖਾਓ",
+  "onboarding.actions.go_to_home": "ਮੇਰੀ ਮੁੱਖ ਫੀਡ ਉੱਤੇ ਲੈ ਜਾਓ",
+  "onboarding.follows.lead": "",
+  "onboarding.follows.title": "ਆਪਣੀ ਹੋਮ ਫੀਡ ਨੂੰ ਨਿੱਜੀ ਬਣਾਓ",
   "onboarding.profile.note": "ਜਾਣਕਾਰੀ",
   "onboarding.profile.save_and_continue": "ਸੰਭਾਲੋ ਅਤੇ ਜਾਰੀ ਰੱਖੋ",
   "onboarding.profile.title": "ਪਰੋਫਾਈਲ ਸੈਟਅੱਪ",
   "onboarding.profile.upload_avatar": "ਪਰੋਫਾਈਲ ਤਸਵੀਰ ਅੱਪਲੋਡ ਕਰੋ",
+  "onboarding.share.title": "ਆਪਣਾ ਪਰੋਫਾਈਲ ਸਾਂਝਾ ਕਰੋ",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਮਦਦ ਨਹੀਂ ਚਾਹੀਦੀ ਹੈ?",
+  "onboarding.start.title": "ਤੁਸੀਂ ਪੂਰਾ ਕਰਨ ਲਿਆ!",
+  "onboarding.steps.follow_people.body": "ਦਿਲਚਸਪ ਲੋਕਾਂ ਨੂੰ ਫ਼ਾਲੋ ਕਰੋ, ਇਹ ਤਾਂ ਮਸਟਾਡੋਨ ਹੈ।",
+  "onboarding.steps.follow_people.title": "ਆਪਣੀ ਹੋਮ ਫੀਡ ਨੂੰ ਨਿੱਜੀ ਬਣਾਓ",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "ਆਪਣੀ ਪਹਿਲੀ ਪੋਸਟ ਕਰੋ",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "ਆਪਣੇ ਪਰੋਫਾਈਲ ਨੂੰ ਆਪਣਾ ਬਣਾਓ",
+  "onboarding.steps.share_profile.body": "ਆਪਣੇ ਮਿੱਤਰਾਂ ਨੂੰ ਦੱਸੋ ਤੁਹਾਨੂੰ ਮਸਟਾਡੋਨ ਕਿਵੇਂ ਲੱਗਿਆ",
+  "onboarding.steps.share_profile.title": "ਆਪਣੇ ਮਸਟਾਡੋਨ ਪਰੋਫਾਈਲ ਨੂੰ ਸਾਂਝਾ ਕਰੋ",
   "poll.closed": "ਬੰਦ ਹੈ",
   "poll.refresh": "ਤਾਜ਼ਾ ਕਰੋ",
   "poll.reveal": "ਨਤੀਜਿਆਂ ਨੂੰ ਵੇਖੋ",
@@ -437,6 +416,7 @@
   "poll.voted": "ਤੁਸੀਂ ਇਸ ਜਵਾਬ ਲਈ ਵੋਟ ਕੀਤਾ",
   "privacy.change": "ਪੋਸਟ ਦੀ ਪਰਦੇਦਾਰੀ ਨੂੰ ਬਦਲੋ",
   "privacy.direct.long": "ਪੋਸਟ ਵਿੱਚ ਜ਼ਿਕਰ ਕੀਤੇ ਹਰ ਕੋਈ",
+  "privacy.direct.short": "ਖਾਸ ਲੋਕ",
   "privacy.private.long": "ਸਿਰਫ਼ ਤੁਹਾਡੇ ਫ਼ਾਲੋਅਰ ਹੀ",
   "privacy.private.short": "ਫ਼ਾਲੋਅਰ",
   "privacy.public.short": "ਜਨਤਕ",
@@ -444,7 +424,7 @@
   "privacy_policy.title": "ਪਰਦੇਦਾਰੀ ਨੀਤੀ",
   "recommended": "ਸਿਫ਼ਾਰਸ਼ੀ",
   "refresh": "ਤਾਜ਼ਾ ਕਰੋ",
-  "regeneration_indicator.please_stand_by": "ਕਿਰਪਾ ਕਰਕੇ ਉਡੀਕੋ।",
+  "regeneration_indicator.label": "ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ...",
   "relative_time.days": "{number}ਦਿਨ",
   "relative_time.full.days": "{number, plural, one {# ਦਿਨ} other {# ਦਿਨ}} ਪਹਿਲਾਂ",
   "relative_time.full.hours": "{number, plural, one {# ਘੰਟਾ} other {# ਘੰਟੇ}} ਪਹਿਲਾਂ",
@@ -501,9 +481,9 @@
   "search_results.accounts": "ਪਰੋਫਾਈਲ",
   "search_results.all": "ਸਭ",
   "search_results.hashtags": "ਹੈਸ਼ਟੈਗ",
-  "search_results.no_results": "ਕੋਈ ਨਤੀਜੇ ਨਹੀਂ ਹਨ।",
   "search_results.see_all": "ਸਭ ਵੇਖੋ",
   "search_results.statuses": "ਪੋਸਟਾਂ",
+  "search_results.title": "{q} ਲਈ ਖੋਜ",
   "server_banner.active_users": "ਸਰਗਰਮ ਵਰਤੋਂਕਾਰ",
   "sign_in_banner.create_account": "ਖਾਤਾ ਬਣਾਓ",
   "sign_in_banner.sign_in": "ਲਾਗਇਨ",
@@ -538,7 +518,6 @@
   "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "ਹਟਾਓ ਤੇ ਮੁੜ-ਡਰਾਫਟ",
   "status.remove_bookmark": "ਬੁੱਕਮਾਰਕ ਨੂੰ ਹਟਾਓ",
-  "status.remove_favourite": "ਮਨਪਸੰਦ ਵਿੱਚੋਂ ਹਟਾਓ",
   "status.replied_in_thread": "ਮਾਮਲੇ ਵਿੱਚ ਜਵਾਬ ਦਿਓ",
   "status.replied_to": "{name} ਨੂੰ ਜਵਾਬ ਦਿੱਤਾ",
   "status.reply": "ਜਵਾਬ ਦੇਵੋ",
@@ -557,7 +536,6 @@
   "subscribed_languages.save": "ਤਬਦੀਲੀਆਂ ਸੰਭਾਲੋ",
   "tabs_bar.home": "ਘਰ",
   "tabs_bar.notifications": "ਸੂਚਨਾਵਾਂ",
-  "terms_of_service.title": "ਸੇਵਾ ਦੀਆਂ ਸ਼ਰਤਾਂ",
   "time_remaining.days": "{number, plural, one {# ਦਿਨ} other {# ਦਿਨ}} ਬਾਕੀ",
   "time_remaining.hours": "{number, plural, one {# ਘੰਟਾ} other {# ਘੰਟੇ}} ਬਾਕੀ",
   "time_remaining.minutes": "{number, plural, one {# ਮਿੰਟ} other {# ਮਿੰਟ}} ਬਾਕੀ",
@@ -567,7 +545,14 @@
   "units.short.million": "{count}ਮਿ",
   "units.short.thousand": "{count}ਹਜ਼ਾਰ",
   "upload_button.label": "ਚਿੱਤਰ, ਵੀਡੀਓ ਜਾਂ ਆਡੀਓ ਫਾਇਲ ਨੂੰ ਜੋੜੋ",
+  "upload_form.audio_description": "ਬੋਲ਼ੇ ਜਾਂ ਸੁਣਨ ਵਿੱਚ ਮੁਸ਼ਕਿਲ ਵਾਲੇ ਲੋਕਾਂ ਲਈ ਵੇਰਵੇ",
+  "upload_form.description": "ਅੰਨ੍ਹੇ ਜਾਂ ਦੇਖਣ ਲਈ ਮੁਸ਼ਕਲ ਵਾਲੇ ਲੋਕਾਂ ਲਈ ਵੇਰਵੇ",
   "upload_form.edit": "ਸੋਧ",
+  "upload_form.video_description": "ਬੋਲ਼ੇ, ਸੁਣਨ ਵਿੱਚ ਮੁਸ਼ਕਿਲ, ਅੰਨ੍ਹੇ ਜਾਂ ਘੱਟ ਨਿਗ੍ਹਾ ਵਾਲੇ ਲੋਕਾਂ ਲਈ ਵੇਰਵਾ",
+  "upload_modal.apply": "ਲਾਗੂ ਕਰੋ",
+  "upload_modal.applying": "ਲਾਗੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…",
+  "upload_modal.choose_image": "ਤਸਵੀਰ ਚੁਣੋ",
+  "upload_modal.edit_media": "ਮੀਡੀਆ ਸੋਧੋ",
   "upload_progress.label": "ਅੱਪਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ...",
   "upload_progress.processing": "ਕਾਰਵਾਈ ਚੱਲ ਰਹੀ ਹੈ…",
   "username.taken": "ਉਹ ਵਰਤੋਂਕਾਰ ਨਾਂ ਪਹਿਲਾਂ ਹੀ ਲੈ ਲਿਆ ਹੈ। ਹੋਰ ਅਜ਼ਮਾਓ",
@@ -577,6 +562,8 @@
   "video.expand": "ਵੀਡੀਓ ਨੂੰ ਫੈਲਾਓ",
   "video.fullscreen": "ਪੂਰੀ ਸਕਰੀਨ",
   "video.hide": "ਵੀਡੀਓ ਨੂੰ ਲੁਕਾਓ",
+  "video.mute": "ਆਵਾਜ਼ ਨੂੰ ਬੰਦ ਕਰੋ",
   "video.pause": "ਠਹਿਰੋ",
-  "video.play": "ਚਲਾਓ"
+  "video.play": "ਚਲਾਓ",
+  "video.unmute": "ਆਵਾਜ਼ ਨੂੰ ਸੁਣਾਓ"
 }
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index ed827bcf29..a92a55ffcd 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -29,6 +29,7 @@
   "account.endorse": "Wyróżnij na profilu",
   "account.featured_tags.last_status_at": "Ostatni post {date}",
   "account.featured_tags.last_status_never": "Brak postów",
+  "account.featured_tags.title": "Polecane hasztagi {name}",
   "account.follow": "Obserwuj",
   "account.follow_back": "Również obserwuj",
   "account.followers": "Obserwujący",
@@ -85,33 +86,7 @@
   "alert.unexpected.message": "Wystąpił nieoczekiwany błąd.",
   "alert.unexpected.title": "Ups!",
   "alt_text_badge.title": "Tekst alternatywny",
-  "alt_text_modal.add_alt_text": "Dodaj tekst alternatywny",
-  "alt_text_modal.add_text_from_image": "Dodaj tekst ze zdjęcia",
-  "alt_text_modal.cancel": "Anuluj",
-  "alt_text_modal.change_thumbnail": "Zmień miniaturę",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Opisz to dla osób niedosłyszących…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Opisz to dla osób niedowidzących…",
-  "alt_text_modal.done": "Gotowe",
   "announcement.announcement": "Ogłoszenie",
-  "annual_report.summary.archetype.booster": "Łowca treści",
-  "annual_report.summary.archetype.lurker": "Czyhający",
-  "annual_report.summary.archetype.oracle": "Wyrocznia",
-  "annual_report.summary.archetype.pollster": "Ankieter",
-  "annual_report.summary.archetype.replier": "Motyl społeczny",
-  "annual_report.summary.followers.followers": "obserwujących",
-  "annual_report.summary.followers.total": "łącznie {count}",
-  "annual_report.summary.here_it_is": "Oto przegląd Twojego {year} roku:",
-  "annual_report.summary.highlighted_post.by_favourites": "najbardziej lubiany wpis",
-  "annual_report.summary.highlighted_post.by_reblogs": "najczęściej podbijany wpis",
-  "annual_report.summary.highlighted_post.by_replies": "wpis z największą liczbą komentarzy",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "najczęściej używana aplikacja",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "najczęściej używany hashtag",
-  "annual_report.summary.most_used_hashtag.none": "Brak",
-  "annual_report.summary.new_posts.new_posts": "nowe wpisy",
-  "annual_report.summary.percentile.text": "<topLabel>To plasuje cię w czołówce</topLabel><percentage></percentage><bottomLabel> użytkowników {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Nie powiemy Berniemu.",
-  "annual_report.summary.thanks": "Dziękujemy, że jesteś częścią Mastodona!",
   "attachments_list.unprocessed": "(nieprzetworzone)",
   "audio.hide": "Ukryj dźwięk",
   "block_modal.remote_users_caveat": "Poprosimy serwer {domain} o uszanowanie twojej decyzji. Nie jest to jednak gwarantowane, bo niektóre serwery mogą obsługiwać blokady w inny sposób. Publiczne wpisy mogą być nadal widoczne dla niezalogowanych użytkowników.",
@@ -135,7 +110,7 @@
   "bundle_column_error.routing.body": "Nie można odnaleźć tej strony. Czy URL w pasku adresu na pewno jest prawidłowy?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Zamknij",
-  "bundle_modal_error.message": "Coś poszło nie tak podczas wczytywania tego ekranu.",
+  "bundle_modal_error.message": "Coś poszło nie tak podczas ładowania tego składnika.",
   "bundle_modal_error.retry": "Spróbuj ponownie",
   "closed_registrations.other_server_instructions": "Ponieważ Mastodon jest zdecentralizowany, możesz założyć konto na innym serwerze i wciąż mieć możliwość wchodzenia w interakcję z tym serwerem.",
   "closed_registrations_modal.description": "Opcja tworzenia kont na {domain} jest aktualnie niedostępna, ale miej na uwadze to, że nie musisz mieć konta konkretnie na {domain} by używać Mastodona.",
@@ -146,16 +121,13 @@
   "column.blocks": "Zablokowani",
   "column.bookmarks": "Zakładki",
   "column.community": "Lokalna oś czasu",
-  "column.create_list": "Utwórz listę",
   "column.direct": "Wzmianki bezpośrednie",
   "column.directory": "Przeglądaj profile",
   "column.domain_blocks": "Zablokowane domeny",
-  "column.edit_list": "Edytuj listę",
   "column.favourites": "Ulubione",
   "column.firehose": "Aktualności",
   "column.follow_requests": "Prośby o obserwację",
   "column.home": "Strona główna",
-  "column.list_members": "Zarządzaj osobami na liście",
   "column.lists": "Listy",
   "column.mutes": "Wyciszeni",
   "column.notifications": "Powiadomienia",
@@ -168,7 +140,6 @@
   "column_header.pin": "Przypnij",
   "column_header.show_settings": "Pokaż ustawienia",
   "column_header.unpin": "Odepnij",
-  "column_search.cancel": "Anuluj",
   "column_subheading.settings": "Ustawienia",
   "community.column_settings.local_only": "Tylko lokalne",
   "community.column_settings.media_only": "Tylko multimedia",
@@ -187,7 +158,7 @@
   "compose_form.poll.duration": "Czas trwania ankiety",
   "compose_form.poll.multiple": "Możliwość wielokrotnego wyboru",
   "compose_form.poll.option_placeholder": "Opcja {number}",
-  "compose_form.poll.single": "Maksymalnie jedna odpowiedź",
+  "compose_form.poll.single": "Wybierz jedną",
   "compose_form.poll.switch_to_multiple": "Pozwól na zaznaczenie kilku odpowiedzi",
   "compose_form.poll.switch_to_single": "Pozwól na zaznaczenie tylko jednej odpowiedzi",
   "compose_form.poll.type": "Styl",
@@ -211,16 +182,9 @@
   "confirmations.edit.confirm": "Edytuj",
   "confirmations.edit.message": "Edytowanie wpisu nadpisze wiadomość, którą obecnie piszesz. Czy na pewno chcesz to zrobić?",
   "confirmations.edit.title": "Zastąpić wpis?",
-  "confirmations.follow_to_list.confirm": "Zaobserwuj i dodaj do listy",
-  "confirmations.follow_to_list.message": "Musisz obserwować {name}, aby dodać do listy.",
-  "confirmations.follow_to_list.title": "Zaobserwować?",
   "confirmations.logout.confirm": "Wyloguj",
   "confirmations.logout.message": "Czy na pewno chcesz się wylogować?",
   "confirmations.logout.title": "Wylogować?",
-  "confirmations.missing_alt_text.confirm": "Dodaj opis pomocniczy",
-  "confirmations.missing_alt_text.message": "Twój wpis zawiera multimedia bez tekstu alternatywnego. Dodanie opisów pomaga zwiększyć dostępność tych treści dla większej liczby osób.",
-  "confirmations.missing_alt_text.secondary": "Opublikuj mimo to ",
-  "confirmations.missing_alt_text.title": "Dodać tekst pomocniczy?",
   "confirmations.mute.confirm": "Wycisz",
   "confirmations.redraft.confirm": "Usuń i popraw",
   "confirmations.redraft.message": "Czy na pewno chcesz usunąć i poprawić ten wpis? Polubienia, podbicia i komentarze pierwotnego wpisu zostaną utracone.",
@@ -249,10 +213,10 @@
   "disabled_account_banner.text": "Twoje konto {disabledAccount} jest obecnie wyłączone.",
   "dismissable_banner.community_timeline": "To są najnowsze publiczne wpisy osób, które są na {domain}.",
   "dismissable_banner.dismiss": "Odrzuć",
-  "dismissable_banner.explore_links": "To są dzisiejsze najpopularniejsze wiadomości w fediwerum. Aktualności publikowane przez więcej różnych osób znajdują się wyżej w rankingu.",
-  "dismissable_banner.explore_statuses": "Te wpisy z całego fediwersum zyskują dziś na popularności. Nowsze wpisy z większą liczbą podbić i polubień są wyżej w rankingu.",
-  "dismissable_banner.explore_tags": "Te hashtagi zyskują dziś na popularności w fediwersum. Hashtagi, które są używane przez więcej różnych osób, są wyżej w rankingu.",
-  "dismissable_banner.public_timeline": "To są najnowsze publiczne wpisy w fediwersum od osób obserwowanych przez użytkowników {domain}.",
+  "dismissable_banner.explore_links": "Te wiadomości obecnie są komentowane przez osoby z tego serwera i pozostałych w zdecentralizowanej sieci.",
+  "dismissable_banner.explore_statuses": "Obecnie te wpisy z tego serwera i pozostałych serwerów w zdecentralizowanej sieci zyskują popularność na tym serwerze.",
+  "dismissable_banner.explore_tags": "Te hasztagi obecnie zyskują popularność wśród osób z tego serwera i pozostałych w zdecentralizowanej sieci.",
+  "dismissable_banner.public_timeline": "Są to najnowsze publiczne wpisy osób w serwisie społecznościowym, które obserwują ludzie w serwisie {domain}.",
   "domain_block_modal.block": "Blokuj serwer",
   "domain_block_modal.block_account_instead": "Zamiast tego zablokuj @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Osoby z tego serwera mogą wchodzić w interakcje z twoimi starymi wpisami.",
@@ -309,6 +273,7 @@
   "empty_column.hashtag": "Nie ma jeszcze wpisów oznaczonych tym hasztagiem.",
   "empty_column.home": "Twoja główna oś czasu jest pusta! Zaobserwuj więcej osób, aby coś zobaczyć.",
   "empty_column.list": "Nie ma jeszcze nic na tej liście. Kiedy osoby z tej listy opublikują nowe wpisy, pojawią się tutaj.",
+  "empty_column.lists": "Nie masz żadnych list. Kiedy utworzysz jedną, pojawi się tutaj.",
   "empty_column.mutes": "Nie wyciszono jeszcze żadnego użytkownika.",
   "empty_column.notification_requests": "Wszystko przeczytane! Gdy otrzymasz nowe powiadomienia, pojawią się tutaj zgodnie z twoimi ustawieniami.",
   "empty_column.notifications": "Nie masz jeszcze żadnych powiadomień. Gdy inne osoby wejdą z tobą w interakcję, zobaczysz to tutaj.",
@@ -319,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Spróbuj je wyłączyć i odświeżyć stronę. Jeśli to nie pomoże, nadal możesz korzystać z Mastodon za pośrednictwem innej przeglądarki lub aplikacji.",
   "errors.unexpected_crash.copy_stacktrace": "Skopiuj stacktrace do schowka",
   "errors.unexpected_crash.report_issue": "Zgłoś problem",
+  "explore.search_results": "Wyniki wyszukiwania",
   "explore.suggested_follows": "Ludzie",
   "explore.title": "Odkrywaj",
   "explore.trending_links": "Aktualności",
@@ -368,14 +334,13 @@
   "footer.about": "O serwerze",
   "footer.directory": "Katalog profili",
   "footer.get_app": "Pobierz aplikację",
+  "footer.invite": "Zaproś znajomych",
   "footer.keyboard_shortcuts": "Skróty klawiszowe",
   "footer.privacy_policy": "Polityka prywatności",
   "footer.source_code": "Zobacz kod źródłowy",
   "footer.status": "Status",
-  "footer.terms_of_service": "Regulamin",
   "generic.saved": "Zapisano",
   "getting_started.heading": "Pierwsze kroki",
-  "hashtag.admin_moderation": "Otwórz interfejs moderacji #{name}",
   "hashtag.column_header.tag_mode.all": "i {additional}",
   "hashtag.column_header.tag_mode.any": "lub {additional}",
   "hashtag.column_header.tag_mode.none": "bez {additional}",
@@ -417,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignorować powiadomienia od osób, które cię nie obserwują?",
   "ignore_notifications_modal.not_following_title": "Ignorować powiadomienia od osób, których nie obserwujesz?",
   "ignore_notifications_modal.private_mentions_title": "Ignorować powiadomienia od niechcianych wzmianek bezpośrednich?",
-  "info_button.label": "Pomoc",
-  "info_button.what_is_alt_text": "<h1>Czym jest tekst alternatywny?</h1><p>Tekst alternatywny zawiera opisy zdjęć dla osób niedowidzących, korzystających z połączeń o niskiej przepustowości lub szukających dodatkowego kontekstu.</p>\n<p>Możesz poprawić dostępność i czytelność dla wszystkich, pisząc jasny, zwięzły i precyzyjny tekst alternatywny.</p>\n<ul>\n<li>Podkreśl ważne elementy</li>\n<li>Streść tekst widoczny na zdjęciach</li>\n<li>Używaj poprawnej struktury zdań</li>\n<li>Unikaj zbędnych informacji</li>\n<li>Skoncentruj się na kluczowych informacjach zawartych w złożonych wizualizacjach (takich jak diagramy lub mapy)</li>\n</ul>",
-  "interaction_modal.action.favourite": "Aby kontynuować, musisz polubić ze swojego konta.",
-  "interaction_modal.action.follow": "Aby kontynuować, musisz obserwować ze swojego konta.",
-  "interaction_modal.action.reblog": "Aby kontynuować, musisz podbić ze swojego konta.",
-  "interaction_modal.action.reply": "Aby kontynuować, musisz komentować ze swojego konta.",
-  "interaction_modal.action.vote": "Aby kontynuować, musisz zagłosować ze swojego konta.",
-  "interaction_modal.go": "Dalej",
-  "interaction_modal.no_account_yet": "Nie masz jeszcze konta?",
+  "interaction_modal.description.favourite": "Mając konto na Mastodonie, możesz dodawać wpisy do ulubionych by dać znać jego autorowi, że podoba Ci się ten wpis i zachować go na później.",
+  "interaction_modal.description.follow": "Mając konto na Mastodonie, możesz śledzić {name} by widzieć jego wpisy na swojej głównej osi czasu.",
+  "interaction_modal.description.reblog": "Mając konto na Mastodonie, możesz podbić ten wpis i udostępnić go Twoim obserwującym.",
+  "interaction_modal.description.reply": "Mając konto na Mastodonie, możesz odpowiedzieć na ten wpis.",
+  "interaction_modal.login.action": "Wróć na stronę główną",
+  "interaction_modal.login.prompt": "Domena twojego serwera domowego, np. \"mastodon.social\"",
+  "interaction_modal.no_account_yet": "Nie masz konta na Mastodonie?",
   "interaction_modal.on_another_server": "Na innym serwerze",
   "interaction_modal.on_this_server": "Na tym serwerze",
+  "interaction_modal.sign_in": "Nie jesteś zalogowany(-a) na tym serwerze. Gdzie masz konto?",
+  "interaction_modal.sign_in_hint": "To strona na której się rejestrowałeś(-aś). Jeżeli nie pamiętasz, poszukaj mejla z przywitaniem. Możesz też wprowadzić pełną nazwę użytkownika, à la @Mastodon@mastodon.social!",
   "interaction_modal.title.favourite": "Polub wpis {name}",
   "interaction_modal.title.follow": "Obserwuj {name}",
   "interaction_modal.title.reblog": "Podbij wpis {name}",
   "interaction_modal.title.reply": "Odpowiedz na post {name}",
-  "interaction_modal.title.vote": "Głosuj w ankiecie {name}",
-  "interaction_modal.username_prompt": "Np. {example}",
   "intervals.full.days": "{number, plural, one {# dzień} few {# dni} many {# dni} other {# dni}}",
   "intervals.full.hours": "{number, plural, one {# godzina} few {# godziny} many {# godzin} other {# godzin}}",
   "intervals.full.minutes": "{number, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}}",
@@ -469,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Pokaż lub ukryj tekst z ostrzeżeniem",
   "keyboard_shortcuts.toggle_sensitivity": "Pokaż lub ukryj multimedia",
   "keyboard_shortcuts.toot": "Stwórz nowy wpis",
-  "keyboard_shortcuts.translate": "aby przetłumaczyć wpis",
   "keyboard_shortcuts.unfocus": "Opuść pole tekstowe",
   "keyboard_shortcuts.up": "Przesuń w górę na liście",
   "lightbox.close": "Zamknij",
@@ -482,32 +444,20 @@
   "link_preview.author": "{name}",
   "link_preview.more_from_author": "Więcej od {name}",
   "link_preview.shares": "{count, plural, one {{counter} wpis} few {{counter} wpisy} many {{counter} wpisów} other {{counter} wpisów}}",
-  "lists.add_member": "Dodaj",
-  "lists.add_to_list": "Dodaj do listy",
-  "lists.add_to_lists": "Dodaj {name} do list",
-  "lists.create": "Utwórz",
-  "lists.create_a_list_to_organize": "Utwórz nową listę, aby uporządkować swoją oś czasu",
-  "lists.create_list": "Utwórz listę",
+  "lists.account.add": "Dodaj do listy",
+  "lists.account.remove": "Usunąć z listy",
   "lists.delete": "Usuń listę",
-  "lists.done": "Gotowe",
   "lists.edit": "Edytuj listę",
-  "lists.exclusive": "Nie pokazuj w mojej osi czasu",
-  "lists.exclusive_hint": "Wpisy osób znajdujących się na tej liście nie będą wyświetlane w twojej osi czasu, aby uniknąć duplikowania tych samych wpisów.",
-  "lists.find_users_to_add": "Znajdź kogoś, aby dodać",
-  "lists.list_members": "Lista osób",
-  "lists.list_members_count": "{count, plural, one {# osoba} few {# osoby} many {# osób} other {# osób}}",
-  "lists.list_name": "Nazwa listy",
-  "lists.new_list_name": "Nazwa nowej listy",
-  "lists.no_lists_yet": "Brak list.",
-  "lists.no_members_yet": "Pusto.",
-  "lists.no_results_found": "Nic nie znaleziono.",
-  "lists.remove_member": "Usuń",
+  "lists.edit.submit": "Zmień tytuł",
+  "lists.exclusive": "Ukryj te posty w lokalnej osi czasu",
+  "lists.new.create": "Utwórz listę",
+  "lists.new.title_placeholder": "Wprowadź tytuł listy",
   "lists.replies_policy.followed": "Każdy obserwowany",
   "lists.replies_policy.list": "Osoby na liście",
   "lists.replies_policy.none": "Nikt",
-  "lists.save": "Zapisz",
-  "lists.search": "Szukaj",
-  "lists.show_replies_to": "Uwzględnij komentarze osób z listy do",
+  "lists.replies_policy.title": "Pokazuj odpowiedzi dla:",
+  "lists.search": "Szukaj wśród osób które obserwujesz",
+  "lists.subheading": "Twoje listy",
   "load_pending": "{count, plural, one {# nowa} few {# nowe} many {# nowych} other {# nowych}}",
   "loading_indicator.label": "Wczytywanie…",
   "media_gallery.hide": "Ukryj",
@@ -556,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} zgłosił(a) {target}",
   "notification.admin.sign_up": "{name} rejestruje się",
   "notification.admin.sign_up.name_and_others": "{name} i {count, plural, one {# inna osoba} few {# inne osoby}  other {# innych osób}} zarejestrowali się",
-  "notification.annual_report.message": "Twój {year} #Wrapstodon jest już dostępny! Zobacz swoje podsumowanie roku i niezapomniane chwile na Mastodon!",
-  "notification.annual_report.view": "Zobacz #Wrapstodon",
   "notification.favourite": "{name} lubi twój wpis",
   "notification.favourite.name_and_others_with_link": "{name} i <a>{count, plural, one {# inna osoba} few {# inne osoby} other {# innych osób}}</a> lubią twój wpis",
-  "notification.favourite_pm": "{name} lubi wzmiankę bezpośrednią od ciebie",
-  "notification.favourite_pm.name_and_others_with_link": "{name} i <a>{count, plural, one {# inna osoba} few {# inne osoby} other {# innych osób}}</a> lubią wzmiankę bezpośrednią od ciebie",
   "notification.follow": "{name} obserwuje cię",
   "notification.follow.name_and_others": "{name} i <a>{count, plural, one {# inna osoba} few {# inne osoby} other {# innych osób}}</a> zaobserwowali cię",
   "notification.follow_request": "{name} chce cię zaobserwować",
@@ -666,21 +612,44 @@
   "notifications_permission_banner.enable": "Włącz powiadomienia na pulpicie",
   "notifications_permission_banner.how_to_control": "Aby otrzymywać powiadomienia, gdy Mastodon nie jest otwarty, włącz powiadomienia na pulpicie. Możesz wybrać, które dokładnie typy interakcji generują powiadomienia na pulpicie za pomocą przycisku {icon} powyżej po ich włączeniu.",
   "notifications_permission_banner.title": "Nigdy niczego nie przegapisz",
-  "onboarding.follows.back": "Wróć",
-  "onboarding.follows.done": "Gotowe",
+  "onboarding.action.back": "Zabierz mnie z powrotem",
+  "onboarding.actions.back": "Zabierz mnie z powrotem",
+  "onboarding.actions.go_to_explore": "Zobacz co się dzieje",
+  "onboarding.actions.go_to_home": "Przejdź do swojego kanału głównego",
+  "onboarding.compose.template": "Witaj #Mastodon!",
   "onboarding.follows.empty": "Niestety, w tej chwili nie można nic wyświetlić. Możesz użyć wyszukiwania lub przeglądać stronę główną, aby znaleźć osoby, które chcesz obserwować, albo spróbuj ponownie później.",
-  "onboarding.follows.search": "Szukaj",
-  "onboarding.follows.title": "Zaobserwuj kogoś, aby zacząć",
+  "onboarding.follows.lead": "Zarządasz swoim własnym kanałem. Im więcej ludzi śledzisz, tym bardziej aktywny i ciekawy będzie Twój kanał. Te profile mogą być dobrym punktem wyjścia— możesz przestać je obserwować w dowolnej chwili!",
+  "onboarding.follows.title": "Popularne na Mastodonie",
   "onboarding.profile.discoverable": "Spraw, by mój profil był widoczny",
   "onboarding.profile.discoverable_hint": "Gdy zdecydujesz się na włączenie widoczności na Mastodon, twoje wpisy mogą pojawiać się w wynikach wyszukiwania i aktualnościach, a twój profil może być polecany osobom o podobnych zainteresowaniach.",
   "onboarding.profile.display_name": "Wyświetlana nazwa",
   "onboarding.profile.display_name_hint": "Twoje imię lub pseudonim…",
+  "onboarding.profile.lead": "Możesz wypełnić te dane później w menu ustawień, gdzie dostępnych jest jeszcze więcej opcji.",
   "onboarding.profile.note": "Opis",
   "onboarding.profile.note_hint": "Możesz @wzmiankować innych lub dodawać #hashtagi…",
   "onboarding.profile.save_and_continue": "Zapisz i kontynuuj",
   "onboarding.profile.title": "Ustawienia profilu",
   "onboarding.profile.upload_avatar": "Dodaj zdjęcie profilowe",
   "onboarding.profile.upload_header": "Dodaj baner",
+  "onboarding.share.lead": "Daj znać ludziom, jak mogą cię znaleźć na Mastodonie!",
+  "onboarding.share.message": "Jestem {username} na #Mastodon! Śledź mnie tutaj {url}",
+  "onboarding.share.next_steps": "Możliwe dalsze kroki:",
+  "onboarding.share.title": "Udostępnij swój profil",
+  "onboarding.start.lead": "Twoje nowe konto Mastodonie jest gotowe. Oto jak możesz je jak najlepiej wykorzystać:",
+  "onboarding.start.skip": "Chcesz pominąć wprowadzenie?",
+  "onboarding.start.title": "Udało się!",
+  "onboarding.steps.follow_people.body": "Zarządzasz swoim własnym kanałem. Wypełnij go interesującymi ludźmi.",
+  "onboarding.steps.follow_people.title": "Obserwuj {count, plural, one {jedną osobę} few {# osoby} many {# osób} other {# osób}}",
+  "onboarding.steps.publish_status.body": "Przywitaj się ze światem.",
+  "onboarding.steps.publish_status.title": "Utwórz swój pierwszy post",
+  "onboarding.steps.setup_profile.body": "Inni użytkownicy są bardziej skłonni do interakcji z Tobą jeśli posiadasz wypełniony profil.",
+  "onboarding.steps.setup_profile.title": "Spersonalizuj swój profil",
+  "onboarding.steps.share_profile.body": "Poinformuj swoich przyjaciół jak znaleźć cię na Mastodonie!",
+  "onboarding.steps.share_profile.title": "Udostępnij swój profil",
+  "onboarding.tips.2fa": "<strong>Czy wiesz?</strong> Możesz zabezpieczyć swoje konto poprzez skonfigurowanie uwierzytelniania dwuetapowego w ustawieniach konta. Działa z wybraną przez Ciebie aplikacją TOTP, żaden numer telefonu nie jest wymagany!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Czy wiesz?</strong> Ponieważ Mastodon jest zdecentralizowany, niektóre profile, z którymi się spotkasz, będą hostowane na serwerach innych niż twoje. A mimo to możesz z nimi bezproblemowo wchodzić w interakcje! Ich serwer jest w drugiej połowie ich nazwy użytkownika!",
+  "onboarding.tips.migration": "<strong>Czy wiesz?</strong> Jeśli uważasz, że {domain} nie jest dla Ciebie dobrym wyborem na serwer w przyszłości, możesz przenieść się na inny serwer Mastodona bez utraty obserwujących. Możesz nawet hostować swój własny serwer!",
+  "onboarding.tips.verification": "<strong>Czy wiesz?</strong> Możesz zweryfikować swoje konto, umieszczając link do profilu Mastodon na swojej własnej stronie internetowej, a następnie dodając stronę do swojego profilu. Żadne opłaty lub dokumenty nie są wymagane!",
   "password_confirmation.exceeds_maxlength": "Długość potwierdzonego hasła przekracza maksymalną długość hasła",
   "password_confirmation.mismatching": "Hasła nie są takie same",
   "picture_in_picture.restore": "Powrót",
@@ -696,7 +665,7 @@
   "poll_button.remove_poll": "Usuń ankietę",
   "privacy.change": "Dostosuj widoczność wpisów",
   "privacy.direct.long": "Wszyscy wzmiankowani w tym wpisie",
-  "privacy.direct.short": "Wzmianka bezpośrednia",
+  "privacy.direct.short": "Wzmianki bezpośrednie",
   "privacy.private.long": "Tylko obserwujący",
   "privacy.private.short": "Obserwujący",
   "privacy.public.long": "Każdy na i poza Mastodon",
@@ -708,8 +677,8 @@
   "privacy_policy.title": "Polityka prywatności",
   "recommended": "Zalecane",
   "refresh": "Odśwież",
-  "regeneration_indicator.please_stand_by": "Proszę czekać.",
-  "regeneration_indicator.preparing_your_home_feed": "Wczytywanie twojej osi czasu…",
+  "regeneration_indicator.label": "Ładuję…",
+  "regeneration_indicator.sublabel": "Twoja oś czasu jest przygotowywana!",
   "relative_time.days": "{number} dni",
   "relative_time.full.days": "{number, plural, one {# dzień} few {# dni} many {# dni} other {# dni}} temu",
   "relative_time.full.hours": "{number, plural, one {# godzinę} few {# godziny} many {# godzin} other {# godzin}} temu",
@@ -793,11 +762,10 @@
   "search_results.accounts": "Profile",
   "search_results.all": "Wszystkie",
   "search_results.hashtags": "Hasztagi",
-  "search_results.no_results": "Brak wyników.",
-  "search_results.no_search_yet": "Spróbuj poszukać wpisów, profili, lub hasztagów.",
+  "search_results.nothing_found": "Nie znaleziono innych wyników dla tego wyszukania",
   "search_results.see_all": "Pokaż wszystkie",
   "search_results.statuses": "Wpisy",
-  "search_results.title": "Wyszukiwanie \"{q}\"",
+  "search_results.title": "Wyszukiwanie {q}",
   "server_banner.about_active_users": "Osoby korzystające z tego serwera w ciągu ostatnich 30 dni (Miesięcznie aktywni użytkownicy)",
   "server_banner.active_users": "aktywni użytkownicy",
   "server_banner.administered_by": "Zarządzana przez:",
@@ -849,7 +817,6 @@
   "status.reblogs.empty": "Nikt nie podbił jeszcze tego wpisu. Gdy ktoś to zrobi, pojawi się tutaj.",
   "status.redraft": "Usuń i przeredaguj",
   "status.remove_bookmark": "Usuń zakładkę",
-  "status.remove_favourite": "Usuń z ulubionych",
   "status.replied_in_thread": "Odpowiedź w wątku",
   "status.replied_to": "Odpowiedź do wpisu użytkownika {name}",
   "status.reply": "Odpowiedz",
@@ -871,7 +838,6 @@
   "subscribed_languages.target": "Zmień subskrybowane języki dla {target}",
   "tabs_bar.home": "Strona główna",
   "tabs_bar.notifications": "Powiadomienia",
-  "terms_of_service.title": "Warunki korzystania z usługi",
   "time_remaining.days": "{number, plural, one {Pozostał # dzień} few {Pozostały # dni} many {Pozostało # dni} other {Pozostało # dni}}",
   "time_remaining.hours": "{number, plural, one {Pozostała # godzina} few {Pozostały # godziny} many {Pozostało # godzin} other {Pozostało # godzin}}",
   "time_remaining.minutes": "{number, plural, one {Pozostała # minuta} few {Pozostały # minuty} many {Pozostało # minut} other {Pozostało # minut}}",
@@ -887,12 +853,26 @@
   "upload_button.label": "Dodaj zdjęcia, filmy lub audio",
   "upload_error.limit": "Przekroczono limit plików do wysłania.",
   "upload_error.poll": "Dołączanie plików nie dozwolone z głosowaniami.",
+  "upload_form.audio_description": "Opisz dla osób niesłyszących i niedosłyszących",
+  "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących",
   "upload_form.drag_and_drop.instructions": "Naciśnij spację lub enter żeby podnieść załącznik. Podczas przeciągania, strzałki przesuwają załącznik. Naciśnięcie spacji lub entera upuści załącznik w nowym miejscu, a escape anuluje przesuwanie.",
   "upload_form.drag_and_drop.on_drag_cancel": "Przesuwanie anulowane. Załącznik {item} upuszczony.",
   "upload_form.drag_and_drop.on_drag_end": "Upuszczono załącznik {item}.",
   "upload_form.drag_and_drop.on_drag_over": "Przesunięto załącznik {item}.",
   "upload_form.drag_and_drop.on_drag_start": "Podniesiono załącznik {item}.",
   "upload_form.edit": "Edytuj",
+  "upload_form.thumbnail": "Zmień miniaturę",
+  "upload_form.video_description": "Opisz dla osób niesłyszących, niedosłyszących, niewidomych i niedowidzących",
+  "upload_modal.analyzing_picture": "Analizowanie obrazu…",
+  "upload_modal.apply": "Zastosuj",
+  "upload_modal.applying": "Stosowanie…",
+  "upload_modal.choose_image": "Wybierz obraz",
+  "upload_modal.description_placeholder": "Pchnąć w tę łódź jeża lub ośm skrzyń fig",
+  "upload_modal.detect_text": "Wykryj tekst z obrazu",
+  "upload_modal.edit_media": "Edytuj multimedia",
+  "upload_modal.hint": "Kliknij lub przeciągnij kółko na podglądzie by wybrać centralny punkt, który zawsze będzie na widoku na miniaturce.",
+  "upload_modal.preparing_ocr": "Przygotowywanie OCR…",
+  "upload_modal.preview_label": "Podgląd ({ratio})",
   "upload_progress.label": "Wysyłanie…",
   "upload_progress.processing": "Przetwarzanie…",
   "username.taken": "Ta nazwa użytkownika jest już zajęta. Wybierz inną",
@@ -902,6 +882,8 @@
   "video.expand": "Rozszerz film",
   "video.fullscreen": "Pełny ekran",
   "video.hide": "Ukryj film",
+  "video.mute": "Wycisz",
   "video.pause": "Pauzuj",
-  "video.play": "Odtwórz"
+  "video.play": "Odtwórz",
+  "video.unmute": "Cofnij wyciszenie"
 }
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 55cfeea582..d8a62d49c0 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -29,6 +29,7 @@
   "account.endorse": "Recomendar",
   "account.featured_tags.last_status_at": "Última publicação em {date}",
   "account.featured_tags.last_status_never": "Sem publicações",
+  "account.featured_tags.title": "Hashtags em destaque de {name}",
   "account.follow": "Seguir",
   "account.follow_back": "Seguir de volta",
   "account.followers": "Seguidores",
@@ -64,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} publicação} other {{counter} publicações}}",
   "account.unblock": "Desbloquear @{name}",
   "account.unblock_domain": "Desbloquear domínio {domain}",
-  "account.unblock_domain_short": "Desbloquear",
   "account.unblock_short": "Desbloquear",
   "account.unendorse": "Remover",
   "account.unfollow": "Deixar de seguir",
@@ -86,33 +86,7 @@
   "alert.unexpected.message": "Ocorreu um erro inesperado.",
   "alert.unexpected.title": "Eita!",
   "alt_text_badge.title": "Texto alternativo",
-  "alt_text_modal.add_alt_text": "Adicione texto alternativo",
-  "alt_text_modal.add_text_from_image": "Adicione texto da imagem",
-  "alt_text_modal.cancel": "Cancelar",
-  "alt_text_modal.change_thumbnail": "Alterar miniatura",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "…Descreva isso para pessoas com deficiências auditivas.…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Descreva isso para pessoas com deficiências visuais…",
-  "alt_text_modal.done": "Feito",
   "announcement.announcement": "Comunicados",
-  "annual_report.summary.archetype.booster": "Caçador legal",
-  "annual_report.summary.archetype.lurker": "O espreitador",
-  "annual_report.summary.archetype.oracle": "O oráculo",
-  "annual_report.summary.archetype.pollster": "O pesquisador",
-  "annual_report.summary.archetype.replier": "A borboleta social",
-  "annual_report.summary.followers.followers": "seguidores",
-  "annual_report.summary.followers.total": "{count} total",
-  "annual_report.summary.here_it_is": "Aqui está seu {year} em revisão:",
-  "annual_report.summary.highlighted_post.by_favourites": "publicação mais favoritada",
-  "annual_report.summary.highlighted_post.by_reblogs": "publicação mais impulsionada",
-  "annual_report.summary.highlighted_post.by_replies": "publicação com mais respostas",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "aplicativo mais usado",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag mais usada",
-  "annual_report.summary.most_used_hashtag.none": "Nenhuma",
-  "annual_report.summary.new_posts.new_posts": "novas publicações",
-  "annual_report.summary.percentile.text": "<topLabel>Isso lhe coloca no topo</topLabel><percentage></percentage><bottomLabel>de usuários de {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Não contaremos à Bernie.",
-  "annual_report.summary.thanks": "Obrigada por fazer parte do Mastodon!",
   "attachments_list.unprocessed": "(não processado)",
   "audio.hide": "Ocultar áudio",
   "block_modal.remote_users_caveat": "Pediremos ao servidor {domain} que respeite sua decisão. No entanto, a conformidade não é garantida, já que alguns servidores podem lidar com bloqueios de maneira diferente. As postagens públicas ainda podem estar visíveis para usuários não logados.",
@@ -136,7 +110,7 @@
   "bundle_column_error.routing.body": "A página solicitada não foi encontrada. Tem certeza de que o URL na barra de endereços está correta?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Fechar",
-  "bundle_modal_error.message": "Algo deu errado ao carregar esta tela.",
+  "bundle_modal_error.message": "Erro ao carregar este componente.",
   "bundle_modal_error.retry": "Tente novamente",
   "closed_registrations.other_server_instructions": "Como o Mastodon é descentralizado, você pode criar uma conta em outro servidor e ainda pode interagir com este.",
   "closed_registrations_modal.description": "Não é possível criar uma conta em {domain} no momento, mas atente que você não precisa de uma conta especificamente em {domain} para usar o Mastodon.",
@@ -147,16 +121,13 @@
   "column.blocks": "Usuários bloqueados",
   "column.bookmarks": "Salvos",
   "column.community": "Linha local",
-  "column.create_list": "Criar lista",
   "column.direct": "Menções privadas",
   "column.directory": "Explorar perfis",
   "column.domain_blocks": "Domínios bloqueados",
-  "column.edit_list": "Editar lista",
   "column.favourites": "Favoritos",
   "column.firehose": "Feeds ao vivo",
   "column.follow_requests": "Seguidores pendentes",
   "column.home": "Página inicial",
-  "column.list_members": "Gerenciar membros da lista",
   "column.lists": "Listas",
   "column.mutes": "Usuários silenciados",
   "column.notifications": "Notificações",
@@ -169,7 +140,6 @@
   "column_header.pin": "Fixar",
   "column_header.show_settings": "Mostrar configurações",
   "column_header.unpin": "Desafixar",
-  "column_search.cancel": "Cancelar",
   "column_subheading.settings": "Configurações",
   "community.column_settings.local_only": "Somente local",
   "community.column_settings.media_only": "Somente mídia",
@@ -188,7 +158,7 @@
   "compose_form.poll.duration": "Duração da enquete",
   "compose_form.poll.multiple": "Múltipla escolha",
   "compose_form.poll.option_placeholder": "Opção {number}",
-  "compose_form.poll.single": "Única escolha",
+  "compose_form.poll.single": "Escolha uma",
   "compose_form.poll.switch_to_multiple": "Permitir múltiplas escolhas",
   "compose_form.poll.switch_to_single": "Opção única",
   "compose_form.poll.type": "Estilo",
@@ -212,16 +182,9 @@
   "confirmations.edit.confirm": "Editar",
   "confirmations.edit.message": "Editar agora irá substituir a mensagem que está sendo criando. Tem certeza de que deseja continuar?",
   "confirmations.edit.title": "Sobrescrever o post?",
-  "confirmations.follow_to_list.confirm": "Seguir e adicionar à lista",
-  "confirmations.follow_to_list.message": "Você precisa seguir {name} para adicioná-lo à lista.",
-  "confirmations.follow_to_list.title": "Seguir usuário?",
   "confirmations.logout.confirm": "Sair",
   "confirmations.logout.message": "Você tem certeza de que deseja sair?",
   "confirmations.logout.title": "Sair da sessão?",
-  "confirmations.missing_alt_text.confirm": "Adicione texto alternativo",
-  "confirmations.missing_alt_text.message": "Seu post contém mídia sem texto alternativo. Adicionar descrições ajuda a tornar seu conteúdo acessível para mais pessoas.",
-  "confirmations.missing_alt_text.secondary": "Postar mesmo assim",
-  "confirmations.missing_alt_text.title": "Adicionar texto alternativo?",
   "confirmations.mute.confirm": "Silenciar",
   "confirmations.redraft.confirm": "Excluir e rascunhar",
   "confirmations.redraft.message": "Você tem certeza de que quer apagar essa postagem e rascunhá-la? Favoritos e impulsos serão perdidos, e respostas à postagem original ficarão órfãs.",
@@ -250,10 +213,10 @@
   "disabled_account_banner.text": "Sua conta {disabledAccount} está desativada no momento.",
   "dismissable_banner.community_timeline": "Estas são as publicações públicas mais recentes das pessoas cujas contas são hospedadas por {domain}.",
   "dismissable_banner.dismiss": "Dispensar",
-  "dismissable_banner.explore_links": "Estas novas histórias estão sendo compartilhadas bastante no fediverse. Histórias mais recentes postadas por pessoas diferentes são classificadas mais altamente.",
-  "dismissable_banner.explore_statuses": "Estas publicações através do fediverse estão ganhando atenção hoje. Publicações mais recentes com mais boosts e favoritos são classificados mais altamente.",
-  "dismissable_banner.explore_tags": "Estas hashtags estão ganhando atenção hoje no fediverse. Hashtags usadas por muitas pessoas diferentes são classificadas mais altamente.",
-  "dismissable_banner.public_timeline": "Estas são as publicações mais recentes das pessoas no fediverse que as pessoas do {domain} seguem.",
+  "dismissable_banner.explore_links": "Estas novas histórias estão sendo contadas por pessoas neste e em outros servidores da rede descentralizada no momento.",
+  "dismissable_banner.explore_statuses": "Estas são postagens de toda a rede social que estão ganhando tração hoje. Postagens mais recentes com mais impulsos e favoritos têm classificações mais altas.",
+  "dismissable_banner.explore_tags": "Estas hashtags estão ganhando popularidade no momento entre as pessoas deste e de outros servidores da rede descentralizada.",
+  "dismissable_banner.public_timeline": "Estas são as publicações públicas mais recentes de pessoas na rede social que pessoas em {domain} seguem.",
   "domain_block_modal.block": "Bloquear servidor",
   "domain_block_modal.block_account_instead": "Bloquear @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Pessoas deste servidor podem interagir com suas publicações antigas.",
@@ -310,6 +273,7 @@
   "empty_column.hashtag": "Nada aqui.",
   "empty_column.home": "Sua página inicial está vazia! Siga mais pessoas para começar: {suggestions}",
   "empty_column.list": "Nada aqui. Quando membros da lista tootarem, eles aparecerão aqui.",
+  "empty_column.lists": "Nada aqui. Quando você criar listas, elas aparecerão aqui.",
   "empty_column.mutes": "Nada aqui.",
   "empty_column.notification_requests": "Tudo limpo! Não há nada aqui. Quando você receber novas notificações, elas aparecerão aqui de acordo com suas configurações.",
   "empty_column.notifications": "Interaja com outros usuários para começar a conversar.",
@@ -320,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Tente desativá-los e atualizar a página. Se isso não ajudar, você ainda poderá usar o Mastodon por meio de um navegador diferente ou de um aplicativo nativo.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar dados do erro para área de transferência",
   "errors.unexpected_crash.report_issue": "Reportar problema",
+  "explore.search_results": "Resultado da pesquisa",
   "explore.suggested_follows": "Pessoas",
   "explore.title": "Explorar",
   "explore.trending_links": "Notícias",
@@ -369,14 +334,13 @@
   "footer.about": "Sobre",
   "footer.directory": "Diretório de perfis",
   "footer.get_app": "Baixe o app",
+  "footer.invite": "Convidar pessoas",
   "footer.keyboard_shortcuts": "Atalhos de teclado",
   "footer.privacy_policy": "Política de privacidade",
   "footer.source_code": "Exibir código-fonte",
   "footer.status": "Status",
-  "footer.terms_of_service": "Termos de serviço",
   "generic.saved": "Salvo",
   "getting_started.heading": "Primeiros passos",
-  "hashtag.admin_moderation": "Abrir interface de moderação para #{name}",
   "hashtag.column_header.tag_mode.all": "e {additional}",
   "hashtag.column_header.tag_mode.any": "ou {additional}",
   "hashtag.column_header.tag_mode.none": "sem {additional}",
@@ -418,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignorar notificações de pessoas que não seguem você?",
   "ignore_notifications_modal.not_following_title": "Ignorar notificações de pessoas que você não segue?",
   "ignore_notifications_modal.private_mentions_title": "Ignorar notificações de menções privadas não solicitadas?",
-  "info_button.label": "Ajuda",
-  "info_button.what_is_alt_text": "<h1>O que é texto alternativo?</h1><p>O texto alternativo fornece descrições de imagens para pessoas com deficiências visuais, conexões de internet de baixa largura de banda ou aquelas que buscam mais contexto.</p><p>Você pode melhorar a acessibilidade e a compreensão para todos escrevendo texto alternativo claro, conciso e objetivo.</p> <ul> <li>Capture elementos importantes</li> <li>Resuma textos em imagens</li> <li>Use estrutura de frases regular</li> <li>Evite informações redundantes</li> <li>Foque em tendências e descobertas principais em visuais complexos (como diagramas ou mapas)</li> </ul>",
-  "interaction_modal.action.favourite": "Para continuar, você precisa favoritar na sua conta.",
-  "interaction_modal.action.follow": "Para continuar, você precisa seguir da sua conta.",
-  "interaction_modal.action.reblog": "Para continuar, você precisa impulsionar na sua conta.",
-  "interaction_modal.action.reply": "Para continuar, você precisa responder na sua conta.",
-  "interaction_modal.action.vote": "Para continuar, você precisa votar na sua conta.",
-  "interaction_modal.go": "Ir",
-  "interaction_modal.no_account_yet": "Não possui uma conta ainda?",
+  "interaction_modal.description.favourite": "Com uma conta no Mastodon, você pode marcar esta publicação como favorita para que o autor saiba que você gostou e salvá-la para mais tarde.",
+  "interaction_modal.description.follow": "Com uma conta no Mastodon, você pode seguir {name} para receber publicações na sua página inicial.",
+  "interaction_modal.description.reblog": "Com uma conta no Mastodon, você pode impulsionar esta publicação para compartilhá-lo com seus próprios seguidores.",
+  "interaction_modal.description.reply": "Com uma conta no Mastodon, você pode responder a esta publicação.",
+  "interaction_modal.login.action": "Leve-me para casa",
+  "interaction_modal.login.prompt": "Domínio do seu servidor hospedeiro; por exemplo, mastodon.social",
+  "interaction_modal.no_account_yet": "Ainda não está no Mastodon?",
   "interaction_modal.on_another_server": "Em um servidor diferente",
   "interaction_modal.on_this_server": "Neste servidor",
+  "interaction_modal.sign_in": "Você não está credenciado neste servidor. Onde sua conta está hospedada?",
+  "interaction_modal.sign_in_hint": "Dica: Esse é o site onde você se cadastrou. Se não se lembra, procure o e-mail de boas-vindas na sua caixa de entrada. Você também pode informar seu nome de usuário completo! (exemplo @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Favoritar publicação de {name}",
   "interaction_modal.title.follow": "Seguir {name}",
   "interaction_modal.title.reblog": "Impulsionar publicação de {name}",
   "interaction_modal.title.reply": "Responder à publicação de {name}",
-  "interaction_modal.title.vote": "Votar na enquete de {name}",
-  "interaction_modal.username_prompt": "p. e.x.: {example}",
   "intervals.full.days": "{number, plural, one {# dia} other {# dias}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
@@ -470,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "expandir/ocultar aviso de conteúdo",
   "keyboard_shortcuts.toggle_sensitivity": "mostrar/ocultar mídia",
   "keyboard_shortcuts.toot": "compor novo toot",
-  "keyboard_shortcuts.translate": "Para traduzir um post",
   "keyboard_shortcuts.unfocus": "desfocar de tudo",
   "keyboard_shortcuts.up": "mover para cima",
   "lightbox.close": "Fechar",
@@ -483,32 +444,20 @@
   "link_preview.author": "Por {name}",
   "link_preview.more_from_author": "Mais de {name}",
   "link_preview.shares": "{count, plural, one {{counter} publicação} other {{counter} publicações}}",
-  "lists.add_member": "Adicionar",
-  "lists.add_to_list": "Adicionar à lista",
-  "lists.add_to_lists": "Adicionar {name} à lista",
-  "lists.create": "Criar",
-  "lists.create_a_list_to_organize": "Crie uma lista para organizar seu feed inicial",
-  "lists.create_list": "Criar lista",
+  "lists.account.add": "Adicionar à lista",
+  "lists.account.remove": "Remover da lista",
   "lists.delete": "Excluir lista",
-  "lists.done": "Concluído",
   "lists.edit": "Editar lista",
-  "lists.exclusive": "Ocultar membros no início",
-  "lists.exclusive_hint": "Se existe alguém nesta lista, oculte-os no seu feed inicial para evitar ver suas publicações duas vezes.",
-  "lists.find_users_to_add": "Encontrar usuários para adicionar",
-  "lists.list_members": "Membros da lista",
-  "lists.list_members_count": "{count, plural, one {# membro} other {# membros}}",
-  "lists.list_name": "Nome da lista",
-  "lists.new_list_name": "Nome novo da lista",
-  "lists.no_lists_yet": "Sem listas ainda.",
-  "lists.no_members_yet": "Nenhum membro ainda.",
-  "lists.no_results_found": "Nenhum resultado encontrado.",
-  "lists.remove_member": "Remover",
+  "lists.edit.submit": "Renomear lista",
+  "lists.exclusive": "Ocultar estes posts da página inicial",
+  "lists.new.create": "Criar lista",
+  "lists.new.title_placeholder": "Nome da lista",
   "lists.replies_policy.followed": "Qualquer usuário seguido",
   "lists.replies_policy.list": "Membros da lista",
   "lists.replies_policy.none": "Ninguém",
-  "lists.save": "Salvar",
-  "lists.search": "Buscar",
-  "lists.show_replies_to": "Incluir respostas de membros da lista para",
+  "lists.replies_policy.title": "Mostrar respostas para:",
+  "lists.search": "Procurar entre as pessoas que segue",
+  "lists.subheading": "Suas listas",
   "load_pending": "{count, plural, one {# novo item} other {# novos items}}",
   "loading_indicator.label": "Carregando…",
   "media_gallery.hide": "Ocultar",
@@ -557,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} denunciou {target}",
   "notification.admin.sign_up": "{name} se inscreveu",
   "notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {# other} other {# outros}}",
-  "notification.annual_report.message": "O #Wrapstodon do seu {year} está esperando! Desvende seus destaques do ano e momentos memoráveis no Mastodon!",
-  "notification.annual_report.view": "Ver #Wrapstodon",
   "notification.favourite": "{name} favoritou sua publicação",
   "notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {# outro} other {# others}}</a> favoritaram a publicação",
-  "notification.favourite_pm": "{name} favoritou sua menção privada",
-  "notification.favourite_pm.name_and_others_with_link": "{name} e <a>{count, plural, one {# outro} other {# outros}}</a> favoritou(ram) sua menção privada",
   "notification.follow": "{name} te seguiu",
   "notification.follow.name_and_others": "{name} e <a>{count, plural, one {# outro} other {# outros}}</a> seguiram você",
   "notification.follow_request": "{name} quer te seguir",
@@ -667,21 +612,44 @@
   "notifications_permission_banner.enable": "Ativar notificações no computador",
   "notifications_permission_banner.how_to_control": "Para receber notificações quando o Mastodon não estiver aberto, ative as notificações no computador. Você pode controlar precisamente quais tipos de interações geram notificações no computador através do botão {icon}.",
   "notifications_permission_banner.title": "Nunca perca nada",
-  "onboarding.follows.back": "Voltar",
-  "onboarding.follows.done": "Feito",
+  "onboarding.action.back": "Me leve de volta",
+  "onboarding.actions.back": "Me leve de volta",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Ir para sua página inicial",
+  "onboarding.compose.template": "Olá #Mastodon!",
   "onboarding.follows.empty": "Infelizmente, não é possível mostrar resultados agora. Você pode tentar usar a busca ou navegar na página de exploração para encontrar pessoas para seguir, ou tentar novamente mais tarde.",
-  "onboarding.follows.search": "Buscar",
-  "onboarding.follows.title": "Comece seguindo pessoas",
+  "onboarding.follows.lead": "Sua página inicial é a principal forma de explorar o Mastodon. Quanto mais pessoas você seguir, mais ativo e interessante ele será. Para começar, aqui estão algumas sugestões:",
+  "onboarding.follows.title": "Personalize sua página inicial",
   "onboarding.profile.discoverable": "Tornar meu perfil descobrível",
   "onboarding.profile.discoverable_hint": "Quando você aceita a capacidade de descoberta no Mastodon, suas postagens podem aparecer nos resultados de pesquisa e nas tendências, e seu perfil pode ser sugerido a pessoas com interesses similares aos seus.",
   "onboarding.profile.display_name": "Nome de exibição",
   "onboarding.profile.display_name_hint": "Seu nome completo ou apelido…",
+  "onboarding.profile.lead": "Você pode completar isso mais tarde nas configurações, onde ainda mais opções de personalização estão disponíveis.",
   "onboarding.profile.note": "Biografia",
   "onboarding.profile.note_hint": "Você pode @mencionar outras pessoas ou usar #hashtags…",
   "onboarding.profile.save_and_continue": "Salvar e continuar",
   "onboarding.profile.title": "Configuração do perfil",
   "onboarding.profile.upload_avatar": "Enviar imagem de perfil",
   "onboarding.profile.upload_header": "Carregar cabeçalho do perfil",
+  "onboarding.share.lead": "Deixe as pessoas saberem como elas podem te encontrar no Mastodon!",
+  "onboarding.share.message": "Eu sou {username} no #Mastodon! Me siga em {url}",
+  "onboarding.share.next_steps": "Possíveis próximos passos:",
+  "onboarding.share.title": "Compartilhe seu perfil",
+  "onboarding.start.lead": "Agora você faz parte do Mastodon, uma plataforma de mídia social única e descentralizada, onde você — e não um algoritmo — define sua própria experiência. Vamos ajudá-lo a começar nessa nova fronteira social:",
+  "onboarding.start.skip": "Não precisa de ajuda para começar?",
+  "onboarding.start.title": "Você conseguiu!",
+  "onboarding.steps.follow_people.body": "Seguir pessoas interessantes é o que o Mastodon tem de melhor.",
+  "onboarding.steps.follow_people.title": "Personalize sua página inicial",
+  "onboarding.steps.publish_status.body": "Diga olá para o mundo com texto, fotos, videos ou enquetes {emoji}",
+  "onboarding.steps.publish_status.title": "Crie sua primeira publicação",
+  "onboarding.steps.setup_profile.body": "Aumente suas interações com um perfil completo.",
+  "onboarding.steps.setup_profile.title": "Personalize seu perfil",
+  "onboarding.steps.share_profile.body": "Deixe seus amigos saberem como encontrar você no Mastodon",
+  "onboarding.steps.share_profile.title": "Compartilhe seu perfil no Mastodon",
+  "onboarding.tips.2fa": "<strong>Você sabia?</strong> Você pode proteger sua conta configurando a autenticação de dois fatores nas configurações de conta. Ela funciona com qualquer aplicativo de autenticação de sua escolha, nenhum número de telefone é necessário!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Você sabia?</strong> Como o Mastodon é descentralizado, alguns perfis que você encontrar serão hospedados em outros servidores que não os seus. E ainda assim você pode interagir com eles perfeitamente! O servidor deles está na segunda metade do nome de usuário!",
+  "onboarding.tips.migration": "<strong>Você sabia?</strong> Se você sente que {domain} não é uma boa escolha de servidor para você no futuro, você pode mudar para outro servidor do Mastodon sem perder seus seguidores. Você pode até mesmo hospedar seu próprio servidor!",
+  "onboarding.tips.verification": "<strong>Você sabia?</strong> Você pode verificar sua conta colocando um link para o seu perfil do Mastodon no seu próprio site e adicionando o site ao seu perfil. Não são necessárias taxas ou documentos!",
   "password_confirmation.exceeds_maxlength": "A confirmação da senha excede o tamanho máximo de senha",
   "password_confirmation.mismatching": "A confirmação de senha não corresponde",
   "picture_in_picture.restore": "Por de volta",
@@ -697,7 +665,7 @@
   "poll_button.remove_poll": "Remover enquete",
   "privacy.change": "Alterar privacidade do toot",
   "privacy.direct.long": "Todos mencionados na publicação",
-  "privacy.direct.short": "Menção privada",
+  "privacy.direct.short": "Pessoas específicas",
   "privacy.private.long": "Apenas seus seguidores",
   "privacy.private.short": "Seguidores",
   "privacy.public.long": "Qualquer um dentro ou fora do Mastodon",
@@ -709,8 +677,8 @@
   "privacy_policy.title": "Política de privacidade",
   "recommended": "Recomendado",
   "refresh": "Atualizar",
-  "regeneration_indicator.please_stand_by": "Aguarde um momento.",
-  "regeneration_indicator.preparing_your_home_feed": "Preparando seu feed inicial…",
+  "regeneration_indicator.label": "Carregando…",
+  "regeneration_indicator.sublabel": "Sua página inicial está sendo preparada!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# dia} other {# dias}} atrás",
   "relative_time.full.hours": "{number, plural, one {# hora} other {# horas}} atrás",
@@ -794,11 +762,10 @@
   "search_results.accounts": "Perfis",
   "search_results.all": "Tudo",
   "search_results.hashtags": "Hashtags",
-  "search_results.no_results": "Sem resultados.",
-  "search_results.no_search_yet": "Tente buscar por publicações, perfis e hashtags.",
+  "search_results.nothing_found": "Não foi possível encontrar nada para estes termos de busca",
   "search_results.see_all": "Ver tudo",
   "search_results.statuses": "Toots",
-  "search_results.title": "Buscar por \"{q}\"",
+  "search_results.title": "Buscar {q}",
   "server_banner.about_active_users": "Pessoas usando este servidor durante os últimos 30 dias (Usuários ativos mensalmente)",
   "server_banner.active_users": "usuários ativos",
   "server_banner.administered_by": "Administrado por:",
@@ -850,7 +817,6 @@
   "status.reblogs.empty": "Nada aqui. Quando alguém der boost, o usuário aparecerá aqui.",
   "status.redraft": "Excluir e rascunhar",
   "status.remove_bookmark": "Remover do Salvos",
-  "status.remove_favourite": "Remover dos favoritos",
   "status.replied_in_thread": "Respondido na conversa",
   "status.replied_to": "Em resposta a {name}",
   "status.reply": "Responder",
@@ -872,9 +838,6 @@
   "subscribed_languages.target": "Alterar idiomas inscritos para {target}",
   "tabs_bar.home": "Página inicial",
   "tabs_bar.notifications": "Notificações",
-  "terms_of_service.effective_as_of": "Em vigor a partir de {date}",
-  "terms_of_service.title": "Termos de serviço",
-  "terms_of_service.upcoming_changes_on": "Próximas mudanças em {date}",
   "time_remaining.days": "{number, plural, one {# dia restante} other {# dias restantes}}",
   "time_remaining.hours": "{number, plural, one {# hora restante} other {# horas restantes}}",
   "time_remaining.minutes": "{number, plural, one {# minuto restante} other {# minutos restantes}}",
@@ -890,12 +853,26 @@
   "upload_button.label": "Adicionar mídia",
   "upload_error.limit": "Limite de anexação alcançado.",
   "upload_error.poll": "Mídias não podem ser anexadas em toots com enquetes.",
+  "upload_form.audio_description": "Descrever para deficientes auditivos",
+  "upload_form.description": "Descrever para deficientes visuais",
   "upload_form.drag_and_drop.instructions": "Para pegar um anexo de mídia, pressione espaço ou enter. Enquanto arrastar, use as setas do teclado para mover o anexo de mídia em qualquer direção. Pressione espaço ou insira novamente para soltar o anexo de mídia em sua nova posição, ou pressione escape para cancelar.",
   "upload_form.drag_and_drop.on_drag_cancel": "O arrastamento foi cancelado. O anexo da mídia {item} foi descartado.",
   "upload_form.drag_and_drop.on_drag_end": "O anexo {item} foi removido.",
   "upload_form.drag_and_drop.on_drag_over": "O anexo de mídia {item} foi movido.",
   "upload_form.drag_and_drop.on_drag_start": "Foi coletado o anexo de mídia {item}.",
   "upload_form.edit": "Editar",
+  "upload_form.thumbnail": "Alterar miniatura",
+  "upload_form.video_description": "Descrever para deficientes auditivos ou visuais",
+  "upload_modal.analyzing_picture": "Analisando imagem…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.applying": "Aplicando…",
+  "upload_modal.choose_image": "Escolher imagem",
+  "upload_modal.description_placeholder": "Um pequeno jabuti xereta viu dez cegonhas felizes",
+  "upload_modal.detect_text": "Transcrever imagem",
+  "upload_modal.edit_media": "Editar mídia",
+  "upload_modal.hint": "Clique ou arraste o círculo na prévia para escolher o foco que ficará visível na miniatura.",
+  "upload_modal.preparing_ocr": "Preparando OCR…",
+  "upload_modal.preview_label": "Prévia ({ratio})",
   "upload_progress.label": "Enviando...",
   "upload_progress.processing": "Processando…",
   "username.taken": "Esse nome de usuário já está sendo usado. Por favor, tente outro.",
@@ -905,12 +882,8 @@
   "video.expand": "Abrir vídeo",
   "video.fullscreen": "Tela cheia",
   "video.hide": "Ocultar mídia",
-  "video.mute": "Silenciar",
+  "video.mute": "Sem som",
   "video.pause": "Pausar",
   "video.play": "Executar",
-  "video.skip_backward": "Retroceder",
-  "video.skip_forward": "Avançar",
-  "video.unmute": "Ativar som",
-  "video.volume_down": "Diminuir o volume",
-  "video.volume_up": "Aumentar o volume"
+  "video.unmute": "Com som"
 }
diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json
index 87c9e5846a..bd234c14d2 100644
--- a/app/javascript/mastodon/locales/pt-PT.json
+++ b/app/javascript/mastodon/locales/pt-PT.json
@@ -29,6 +29,7 @@
   "account.endorse": "Destacar no perfil",
   "account.featured_tags.last_status_at": "Última publicação em {date}",
   "account.featured_tags.last_status_never": "Sem publicações",
+  "account.featured_tags.title": "Etiquetas destacadas por {name}",
   "account.follow": "Seguir",
   "account.follow_back": "Seguir também",
   "account.followers": "Seguidores",
@@ -64,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} publicação} other {{counter} publicações}}",
   "account.unblock": "Desbloquear @{name}",
   "account.unblock_domain": "Desbloquear o domínio {domain}",
-  "account.unblock_domain_short": "Desbloquear",
   "account.unblock_short": "Desbloquear",
   "account.unendorse": "Não destacar no perfil",
   "account.unfollow": "Deixar de seguir",
@@ -86,33 +86,7 @@
   "alert.unexpected.message": "Ocorreu um erro inesperado.",
   "alert.unexpected.title": "Bolas!",
   "alt_text_badge.title": "Texto descritivo",
-  "alt_text_modal.add_alt_text": "Adicionar texto alternativo",
-  "alt_text_modal.add_text_from_image": "Adicionar texto a partir da imagem",
-  "alt_text_modal.cancel": "Cancelar",
-  "alt_text_modal.change_thumbnail": "Alterar miniatura",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Descreve isto para pessoas com problemas de audição…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Descreve isto para pessoas com problemas de visão…",
-  "alt_text_modal.done": "Concluído",
   "announcement.announcement": "Mensagem de manutenção",
-  "annual_report.summary.archetype.booster": "O caçador de frescura",
-  "annual_report.summary.archetype.lurker": "O espreitador",
-  "annual_report.summary.archetype.oracle": "O oráculo",
-  "annual_report.summary.archetype.pollster": "O sondagens",
-  "annual_report.summary.archetype.replier": "A borboleta social",
-  "annual_report.summary.followers.followers": "seguidores",
-  "annual_report.summary.followers.total": "{count} no total",
-  "annual_report.summary.here_it_is": "Aqui está um resumo do ano {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "publicação mais favorita",
-  "annual_report.summary.highlighted_post.by_reblogs": "publicação mais impulsionada",
-  "annual_report.summary.highlighted_post.by_replies": "publicação com o maior número de respostas",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "aplicação mais utilizada",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta mais utilizada",
-  "annual_report.summary.most_used_hashtag.none": "Nenhuma",
-  "annual_report.summary.new_posts.new_posts": "novas publicações",
-  "annual_report.summary.percentile.text": "<topLabel>Isso coloca-te no topo</topLabel><percentage></percentage><bottomLabel>dos utilizadores de {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Este segredo fica entre nós.",
-  "annual_report.summary.thanks": "Obrigado por fazeres parte do Mastodon!",
   "attachments_list.unprocessed": "(não processado)",
   "audio.hide": "Ocultar áudio",
   "block_modal.remote_users_caveat": "Vamos pedir ao servidor {domain} para respeitar a tua decisão. No entanto, não é garantido o seu cumprimento, uma vez que alguns servidores podem tratar os bloqueios de forma diferente. As publicações públicas podem continuar a ser visíveis para utilizadores não autenticados.",
@@ -136,7 +110,7 @@
   "bundle_column_error.routing.body": "A página solicitada não foi encontrada. Tens a certeza que o URL na barra de endereços está correto?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Fechar",
-  "bundle_modal_error.message": "Algo correu mal ao carregar este ecrã.",
+  "bundle_modal_error.message": "Algo correu mal ao carregar este componente.",
   "bundle_modal_error.retry": "Tenta de novo",
   "closed_registrations.other_server_instructions": "Visto que o Mastodon é descentralizado, podes criar uma conta noutro servidor e interagir com este na mesma.",
   "closed_registrations_modal.description": "Neste momento não é possível criar uma conta em {domain}, mas lembramos que não é preciso ter uma conta especificamente em {domain} para usar o Mastodon.",
@@ -147,16 +121,13 @@
   "column.blocks": "Utilizadores bloqueados",
   "column.bookmarks": "Marcadores",
   "column.community": "Cronologia local",
-  "column.create_list": "Criar lista",
   "column.direct": "Menções privadas",
   "column.directory": "Explorar perfis",
   "column.domain_blocks": "Domínios bloqueados",
-  "column.edit_list": "Editar lista",
   "column.favourites": "Favoritos",
   "column.firehose": "Cronologias em tempo real",
   "column.follow_requests": "Pedidos de seguidores",
   "column.home": "Início",
-  "column.list_members": "Gerir membros da lista",
   "column.lists": "Listas",
   "column.mutes": "Utilizadores ocultados",
   "column.notifications": "Notificações",
@@ -169,7 +140,6 @@
   "column_header.pin": "Afixar",
   "column_header.show_settings": "Mostrar configurações",
   "column_header.unpin": "Desafixar",
-  "column_search.cancel": "Cancelar",
   "column_subheading.settings": "Configurações",
   "community.column_settings.local_only": "Apenas local",
   "community.column_settings.media_only": "Apenas multimédia",
@@ -188,7 +158,7 @@
   "compose_form.poll.duration": "Duração da sondagem",
   "compose_form.poll.multiple": "Escolha múltipla",
   "compose_form.poll.option_placeholder": "Opção {number}",
-  "compose_form.poll.single": "Uma opção",
+  "compose_form.poll.single": "Escolhe uma",
   "compose_form.poll.switch_to_multiple": "Alterar a sondagem para permitir várias respostas",
   "compose_form.poll.switch_to_single": "Alterar a sondagem para permitir uma única resposta",
   "compose_form.poll.type": "Estilo",
@@ -212,16 +182,9 @@
   "confirmations.edit.confirm": "Editar",
   "confirmations.edit.message": "Editar agora irás substituir a mensagem que estás a compor. Tens a certeza de que desejas continuar?",
   "confirmations.edit.title": "Substituir publicação?",
-  "confirmations.follow_to_list.confirm": "Seguir e adicionar à lista",
-  "confirmations.follow_to_list.message": "Tens de seguir {name} para o adicionares a uma lista.",
-  "confirmations.follow_to_list.title": "Seguir utilizador?",
   "confirmations.logout.confirm": "Terminar sessão",
   "confirmations.logout.message": "Tens a certeza de que queres terminar a sessão?",
   "confirmations.logout.title": "Terminar sessão?",
-  "confirmations.missing_alt_text.confirm": "Adicionar texto alternativo",
-  "confirmations.missing_alt_text.message": "A tua publicação contém multimédia sem texto alternativo. A adição de descrições ajuda a tornar o conteúdo acessível a mais pessoas.",
-  "confirmations.missing_alt_text.secondary": "Publicar mesmo assim",
-  "confirmations.missing_alt_text.title": "Adicionar texto alternativo?",
   "confirmations.mute.confirm": "Ocultar",
   "confirmations.redraft.confirm": "Eliminar e reescrever",
   "confirmations.redraft.message": "Tens a certeza de que queres eliminar e tornar a escrever esta publicação? Os favoritos e as publicações impulsionadas perder-se-ão e as respostas à publicação original ficarão órfãs.",
@@ -250,10 +213,10 @@
   "disabled_account_banner.text": "A tua conta {disabledAccount} está atualmente desativada.",
   "dismissable_banner.community_timeline": "Estas são as publicações públicas mais recentes de pessoas cujas contas são hospedadas por {domain}.",
   "dismissable_banner.dismiss": "Descartar",
-  "dismissable_banner.explore_links": "Estas notícias estão a ser mais partilhadas no fediverso hoje. As notícias mais recentes publicadas por mais pessoas diferentes têm uma classificação mais elevada.",
-  "dismissable_banner.explore_statuses": "Estas publicações de todo o fediverso estão a ganhar força hoje. As publicações mais recentes com mais impulsos e favoritos têm uma classificação mais elevada.",
-  "dismissable_banner.explore_tags": "Estas etiquetas estão a ganhar força no fediverso atualmente. As etiquetas que são utilizadas por mais pessoas diferentes são classificadas numa posição mais elevada.",
-  "dismissable_banner.public_timeline": "Estas são as publicações públicas mais recentes de pessoas no fediverso que as pessoas em {domain} seguem.",
+  "dismissable_banner.explore_links": "Estas histórias de notícias estão neste momento a serem faladas por pessoas neste e noutros servidores da rede descentralizada.",
+  "dismissable_banner.explore_statuses": "Estas são as publicações de toda a rede social que estão a ganhar força atualmente. As mensagens mais recentes com mais impulsos e favoritos têm uma classificação mais elevada.",
+  "dismissable_banner.explore_tags": "Estas são as etiquetas que estão a ganhar força na rede social atualmente. As etiquetas que são utilizadas por mais pessoas diferentes têm uma classificação mais elevada.",
+  "dismissable_banner.public_timeline": "Estas são as publicações públicas mais recentes de pessoas na rede social que as pessoas em {domain} seguem.",
   "domain_block_modal.block": "Bloquear servidor",
   "domain_block_modal.block_account_instead": "Em vez disso, bloquear @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "As pessoas deste servidor podem interagir com as tuas publicações antigas.",
@@ -310,6 +273,7 @@
   "empty_column.hashtag": "Não foram encontradas publicações com esta #etiqueta.",
   "empty_column.home": "A tua linha cronológica inicial está vazia! Segue mais pessoas para a preencher.",
   "empty_column.list": "Ainda não existem publicações nesta lista. Quando os membros desta lista fizerem novas publicações, elas aparecerão aqui.",
+  "empty_column.lists": "Ainda não tens listas. Quando criares uma, ela irá aparecer aqui.",
   "empty_column.mutes": "Ainda não ocultaste nenhum utilizador.",
   "empty_column.notification_requests": "Tudo limpo! Não há nada aqui. Quando receberes novas notificações, elas aparecerão aqui conforme as tuas configurações.",
   "empty_column.notifications": "Ainda não tens quaisquer notificações. Quando outras pessoas interagirem contigo, verás isso aqui.",
@@ -320,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Tenta desativá-los e atualizar a página. Se isso não ajudar, poderá ainda ser possível utilizar o Mastodon através de um navegador diferente ou de uma aplicação nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar o stacktrace para a área de transferência",
   "errors.unexpected_crash.report_issue": "Reportar problema",
+  "explore.search_results": "Resultados da pesquisa",
   "explore.suggested_follows": "Pessoas",
   "explore.title": "Explorar",
   "explore.trending_links": "Notícias",
@@ -369,14 +334,13 @@
   "footer.about": "Sobre",
   "footer.directory": "Diretório de perfis",
   "footer.get_app": "Obter a aplicação",
+  "footer.invite": "Convidar pessoas",
   "footer.keyboard_shortcuts": "Atalhos de teclado",
   "footer.privacy_policy": "Política de privacidade",
   "footer.source_code": "Ver código-fonte",
   "footer.status": "Estado",
-  "footer.terms_of_service": "Termos de serviço",
   "generic.saved": "Guardado",
   "getting_started.heading": "Primeiros passos",
-  "hashtag.admin_moderation": "Abrir interface de moderação para #{name}",
   "hashtag.column_header.tag_mode.all": "e {additional}",
   "hashtag.column_header.tag_mode.any": "ou {additional}",
   "hashtag.column_header.tag_mode.none": "sem {additional}",
@@ -418,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ignorar notificações de pessoas que não te seguem?",
   "ignore_notifications_modal.not_following_title": "Ignorar notificações de pessoas que não segues?",
   "ignore_notifications_modal.private_mentions_title": "Ignorar notificações de menções privadas não solicitadas?",
-  "info_button.label": "Ajuda",
-  "info_button.what_is_alt_text": "<h1>O que é texto alternativo?</h1> <p>O texto alternativo fornece descrições de imagens para pessoas com deficiências visuais, conexões de baixa largura de banda ou pessoas que procuram um contexto adicional.</p> <p>Podes melhorar a acessibilidade e a compreensão para todos escrevendo um texto alternativo claro, conciso e objetivo.</p> <ul> <li>Capta elementos importantes</li> <li>Resume o texto que aparece nas imagens</li> <li>Usa uma estrutura de frase regular</li> <li>Evita informações redundantes</li> <li>Centra-te nas tendências e nas principais conclusões em imagens complexas (como diagramas ou mapas)</li> </ul>",
-  "interaction_modal.action.favourite": "Para continuar, tens de adicionar um favorito na tua conta.",
-  "interaction_modal.action.follow": "Para continuar, tens de seguir alguém na tua conta.",
-  "interaction_modal.action.reblog": "Para continuar, tens de impulsionar desde a tua conta.",
-  "interaction_modal.action.reply": "Para continuar, tens de fazer uma resposta na tua conta.",
-  "interaction_modal.action.vote": "Para continuar é necessário votar a partir da tua conta.",
-  "interaction_modal.go": "Ir",
-  "interaction_modal.no_account_yet": "Ainda não tens conta?",
+  "interaction_modal.description.favourite": "Com uma conta no Mastodon, podes assinalar esta publicação como favorita para que o autor saiba que gostaste e guardá-la para mais tarde.",
+  "interaction_modal.description.follow": "Com uma conta no Mastodon, podes seguir {name} para receberes as suas publicações na cronologia da tua página inicial.",
+  "interaction_modal.description.reblog": "Com uma conta no Mastodon, podes impulsionar esta publicação para partilhá-la com os teus seguidores.",
+  "interaction_modal.description.reply": "Com uma conta no Mastodon, podes responder a esta publicação.",
+  "interaction_modal.login.action": "Leve-me a casa",
+  "interaction_modal.login.prompt": "Domínio do teu servidor, por exemplo, mastodon.social",
+  "interaction_modal.no_account_yet": "Não estás no Mastodon?",
   "interaction_modal.on_another_server": "Num servidor diferente",
   "interaction_modal.on_this_server": "Neste servidor",
+  "interaction_modal.sign_in": "Não tens a sessão iniciada neste servidor. Em que servidor tens a tua conta?",
+  "interaction_modal.sign_in_hint": "Dica: o domínio do teu servidor é o site onde criaste a conta. Se não se lembrares, procura o e-mail de boas-vindas na tua caixa de entrada do e-mail. Também podes introduzir o teu nome de utilizador completo! (por exemplo, @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Assinalar a publicação de {name} como favorita",
   "interaction_modal.title.follow": "Seguir {name}",
   "interaction_modal.title.reblog": "Impulsionar a publicação de {name}",
   "interaction_modal.title.reply": "Responder à publicação de {name}",
-  "interaction_modal.title.vote": "Votar na sondagem de {name}",
-  "interaction_modal.username_prompt": "Por exemplo: {example}",
   "intervals.full.days": "{number, plural, one {# dia} other {# dias}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
@@ -470,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "mostrar / esconder texto atrás do aviso de conteúdo",
   "keyboard_shortcuts.toggle_sensitivity": "mostrar / ocultar multimédia",
   "keyboard_shortcuts.toot": "criar uma nova publicação",
-  "keyboard_shortcuts.translate": "traduzir uma publicação",
   "keyboard_shortcuts.unfocus": "remover o foco da área de texto / pesquisa",
   "keyboard_shortcuts.up": "mover para cima na lista",
   "lightbox.close": "Fechar",
@@ -483,32 +444,20 @@
   "link_preview.author": "Por {name}",
   "link_preview.more_from_author": "Mais de {name}",
   "link_preview.shares": "{count, plural, one {{counter} publicação} other {{counter} publicações}}",
-  "lists.add_member": "Adicionar",
-  "lists.add_to_list": "Adicionar à lista",
-  "lists.add_to_lists": "Adicionar {name} às listas",
-  "lists.create": "Criar",
-  "lists.create_a_list_to_organize": "Criar uma nova lista para organizar a cronologia na página inicial",
-  "lists.create_list": "Criar lista",
+  "lists.account.add": "Adicionar à lista",
+  "lists.account.remove": "Remover da lista",
   "lists.delete": "Eliminar lista",
-  "lists.done": "Concluído",
   "lists.edit": "Editar lista",
-  "lists.exclusive": "Ocultar membros na página inicial",
-  "lists.exclusive_hint": "Se alguém estiver nesta lista, oculta-o na cronologia da tua página inicial para evitar veres as publicações dessa pessoa duas vezes.",
-  "lists.find_users_to_add": "Encontrar utilizadores para adicionar",
-  "lists.list_members": "Membros da lista",
-  "lists.list_members_count": "{count, plural, one {# membro} other {# membros}}",
-  "lists.list_name": "Nome da lista",
-  "lists.new_list_name": "Nome da nova lista",
-  "lists.no_lists_yet": "Ainda não existem listas.",
-  "lists.no_members_yet": "Ainda não existem membros.",
-  "lists.no_results_found": "Nenhuns resultados encontrados.",
-  "lists.remove_member": "Remover",
+  "lists.edit.submit": "Mudar o título",
+  "lists.exclusive": "Ocultar estas publicações da página inicial",
+  "lists.new.create": "Adicionar lista",
+  "lists.new.title_placeholder": "Título da nova lista",
   "lists.replies_policy.followed": "Qualquer utilizador seguido",
   "lists.replies_policy.list": "Membros da lista",
   "lists.replies_policy.none": "Ninguém",
-  "lists.save": "Guardar",
-  "lists.search": "Pesquisar",
-  "lists.show_replies_to": "Incluir respostas da lista de membros para",
+  "lists.replies_policy.title": "Mostrar respostas a:",
+  "lists.search": "Pesquisar entre as pessoas que segues",
+  "lists.subheading": "As tuas listas",
   "load_pending": "{count, plural, one {# novo item} other {# novos itens}}",
   "loading_indicator.label": "A carregar…",
   "media_gallery.hide": "Esconder",
@@ -557,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} denunciou {target}",
   "notification.admin.sign_up": "{name} inscreveu-se",
   "notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {# outro} other {# outros}} inscreveram-se",
-  "notification.annual_report.message": "O teu #Wrapstodon {year} está à espera! Ele revela os destaques e os momentos memoráveis do teu ano no Mastodon!",
-  "notification.annual_report.view": "Ver #Wrapstodon",
   "notification.favourite": "{name} assinalou a tua publicação como favorita",
   "notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {# outro} other {# outros}}</a> assinalaram a tua publicação como favorita",
-  "notification.favourite_pm": "{name} assinalou como favorita a tua menção privada",
-  "notification.favourite_pm.name_and_others_with_link": "{name} e <a>{count, plural, one {# outro favoritou} other {# outros favoritaram}}</a> a tua menção privada",
   "notification.follow": "{name} começou a seguir-te",
   "notification.follow.name_and_others": "{name} e <a>{count, plural, one {# outro seguiram-te} other {# outros seguiram-te}}</a>",
   "notification.follow_request": "{name} pediu para seguir-te",
@@ -667,21 +612,44 @@
   "notifications_permission_banner.enable": "Ativar notificações no ambiente de trabalho",
   "notifications_permission_banner.how_to_control": "Para receberes notificações quando o Mastodon não estiver aberto, ativa as notificações no ambiente de trabalho. Após isso, podes controlar precisamente que tipos de interações geram notificações no ambiente de trabalho através do botão {icon} acima.",
   "notifications_permission_banner.title": "Nunca percas nada",
-  "onboarding.follows.back": "Voltar",
-  "onboarding.follows.done": "Concluído",
+  "onboarding.action.back": "Voltar atrás",
+  "onboarding.actions.back": "Voltar atrás",
+  "onboarding.actions.go_to_explore": "Ver tendências atuais",
+  "onboarding.actions.go_to_home": "Ir para a cronologia na tua página inicial",
+  "onboarding.compose.template": "Olá #Mastodon!",
   "onboarding.follows.empty": "Infelizmente não é possível mostrar resultados neste momento. Podes tentar pesquisar ou navegar na página \"Explorar\" para encontrares pessoas para seguires ou tentar novamente mais tarde.",
-  "onboarding.follows.search": "Pesquisar",
-  "onboarding.follows.title": "Segue pessoas para começar",
+  "onboarding.follows.lead": "A cronologia na tua página inicial é a principal forma de experimentares o Mastodon. Quanto mais pessoas seguires, mais ativo e interessante será a cronologia. Para começar, aqui estão algumas sugestões:",
+  "onboarding.follows.title": "Personaliza a cronologia na tua página inicial",
   "onboarding.profile.discoverable": "Permitir que o meu perfil seja descoberto",
   "onboarding.profile.discoverable_hint": "Quando optas pela possibilidade de seres descoberto no Mastodon, as tuas publicações podem aparecer nos resultados de pesquisa e nas tendências, e o teu perfil pode ser sugerido a pessoas com interesses semelhantes aos teus.",
   "onboarding.profile.display_name": "Nome a apresentar",
   "onboarding.profile.display_name_hint": "O teu nome completo ou o teu nome divertido…",
+  "onboarding.profile.lead": "Podes sempre completar isto mais tarde, nas configurações, onde ainda estão disponíveis mais opções de personalização.",
   "onboarding.profile.note": "Biografia",
   "onboarding.profile.note_hint": "Podes @mencionar outras pessoas e usar #etiquetas…",
   "onboarding.profile.save_and_continue": "Guardar e continuar",
   "onboarding.profile.title": "Configuração do perfil",
   "onboarding.profile.upload_avatar": "Enviar foto de perfil",
   "onboarding.profile.upload_header": "Enviar cabeçalho do perfil",
+  "onboarding.share.lead": "Deixa as pessoas saberem como te podem encontrar no Mastodon!",
+  "onboarding.share.message": "Eu sou {username} no #Mastodon! Segue-me em {url}",
+  "onboarding.share.next_steps": "Próximos passos possíveis:",
+  "onboarding.share.title": "Partilha o teu perfil",
+  "onboarding.start.lead": "Agora fazes parte do Mastodon, uma plataforma de redes sociais única e descentralizada onde tu - e não um algoritmo - crias a tua própria experiência. Vamos dar-te início a esta nova fronteira social:",
+  "onboarding.start.skip": "Não precisas de ajuda para começar?",
+  "onboarding.start.title": "Conseguiste!",
+  "onboarding.steps.follow_people.body": "Seguir pessoas interessantes é o propósito do Mastodon. ",
+  "onboarding.steps.follow_people.title": "Personaliza a cronologia na tua página inicial",
+  "onboarding.steps.publish_status.body": "Diz olá ao mundo com texto, fotos, vídeos ou sondagens {emoji}",
+  "onboarding.steps.publish_status.title": "Faz a tua primeira publicação",
+  "onboarding.steps.setup_profile.body": "Aumenta as tuas interações com um perfil completo.",
+  "onboarding.steps.setup_profile.title": "Personaliza o teu perfil",
+  "onboarding.steps.share_profile.body": "Informa os teus amigos, para saberem como podem encontrar-te no Mastodon",
+  "onboarding.steps.share_profile.title": "Partilha o teu perfil",
+  "onboarding.tips.2fa": "<strong>Sabias?</strong> Podes proteger a tua conta ativando a autenticação de dois fatores nas configurações de conta. Funciona com qualquer aplicação TOTP à tua escolha, sem necessitar de um número de telemóvel!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Sabias?</strong> Como o Mastodon é descentralizado, alguns perfis que encontras estarão hospedados noutros servidores que não os teus. E ainda assim podes interagir com eles perfeitamente! O servidor deles está na segunda metade do nome de utilizador!",
+  "onboarding.tips.migration": "<strong>Sabias?</strong> Se sentires que o {domain} não é um bom servidor para ti, no futuro podes mudar para outro servidor Mastodon sem perder os teus seguidores. Podes até mesmo hospedar o teu próprio servidor!",
+  "onboarding.tips.verification": "<strong>Sabias?</strong> Podes verificar a tua conta colocando uma hiperligação para o teu perfil Mastodon no teu próprio site e adicionando o site ao teu perfil. Sem taxas ou necessidade de apresentar documentos!",
   "password_confirmation.exceeds_maxlength": "A confirmação da palavra-passe excedeu o tamanho máximo ",
   "password_confirmation.mismatching": "A confirmação da palavra-passe não corresponde",
   "picture_in_picture.restore": "Colocá-lo de volta",
@@ -697,7 +665,7 @@
   "poll_button.remove_poll": "Remover sondagem",
   "privacy.change": "Alterar a privacidade da publicação",
   "privacy.direct.long": "Todos os mencionados na publicação",
-  "privacy.direct.short": "Menção privada",
+  "privacy.direct.short": "Pessoas específicas",
   "privacy.private.long": "Apenas os teus seguidores",
   "privacy.private.short": "Seguidores",
   "privacy.public.long": "Qualquer pessoa no Mastodon ou não",
@@ -709,8 +677,8 @@
   "privacy_policy.title": "Política de privacidade",
   "recommended": "Recomendado",
   "refresh": "Atualizar",
-  "regeneration_indicator.please_stand_by": "Aguarda um momento.",
-  "regeneration_indicator.preparing_your_home_feed": "A preparar a cronologia na tua página inicial…",
+  "regeneration_indicator.label": "A carregar…",
+  "regeneration_indicator.sublabel": "A cronologia na tua página inicial está a ser preparada!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural,one {# dia} other {# dias}} atrás",
   "relative_time.full.hours": "{number, plural,one {# hora}other {# horas}} atrás",
@@ -794,11 +762,10 @@
   "search_results.accounts": "Perfis",
   "search_results.all": "Tudo",
   "search_results.hashtags": "Etiquetas",
-  "search_results.no_results": "Sem resultados.",
-  "search_results.no_search_yet": "Tenta procurar por publicações, perfis ou etiquetas.",
+  "search_results.nothing_found": "Não foi possível encontrar resultados para os termos pesquisados",
   "search_results.see_all": "Ver todos",
   "search_results.statuses": "Publicações",
-  "search_results.title": "Pesquisar por \"{q}\"",
+  "search_results.title": "Pesquisar por {q}",
   "server_banner.about_active_users": "Pessoas que utilizaram este servidor nos últimos 30 dias (utilizadores ativos mensais)",
   "server_banner.active_users": "utilizadores ativos",
   "server_banner.administered_by": "Administrado por:",
@@ -850,7 +817,6 @@
   "status.reblogs.empty": "Ainda ninguém impulsionou esta publicação. Quando alguém o fizer, aparecerá aqui.",
   "status.redraft": "Eliminar e reescrever",
   "status.remove_bookmark": "Retirar dos marcadores",
-  "status.remove_favourite": "Remover dos favoritos",
   "status.replied_in_thread": "Responder na conversa",
   "status.replied_to": "Respondeu a {name}",
   "status.reply": "Responder",
@@ -872,9 +838,6 @@
   "subscribed_languages.target": "Alterar idiomas subscritos para {target}",
   "tabs_bar.home": "Início",
   "tabs_bar.notifications": "Notificações",
-  "terms_of_service.effective_as_of": "Válido a partir de {date}",
-  "terms_of_service.title": "Termos do serviço",
-  "terms_of_service.upcoming_changes_on": "Próximas aterações em {date}",
   "time_remaining.days": "{number, plural, one {# dia restante} other {# dias restantes}}",
   "time_remaining.hours": "{number, plural, one {# hora restante} other {# horas restantes}}",
   "time_remaining.minutes": "{number, plural, one {# minuto restante} other {# minutos restantes}}",
@@ -890,12 +853,26 @@
   "upload_button.label": "Adicionar imagens, um vídeo ou um ficheiro de som",
   "upload_error.limit": "Limite de envio de ficheiros excedido.",
   "upload_error.poll": "Não é permitido o envio de ficheiros em sondagens.",
+  "upload_form.audio_description": "Descreva para pessoas com diminuição da acuidade auditiva",
+  "upload_form.description": "Descreva para pessoas com diminuição da acuidade visual",
   "upload_form.drag_and_drop.instructions": "Para escolher um anexo multimédia, prima espaço ou enter. Enquanto arrasta, utilize as teclas de setas para mover o anexo multimédia em qualquer direção. Prima espaço ou enter novamente para largar o anexo multimédia na sua nova posição ou prima escape para cancelar.",
   "upload_form.drag_and_drop.on_drag_cancel": "O arrastamento foi cancelado. O anexo multimédia {item} foi descartado.",
   "upload_form.drag_and_drop.on_drag_end": "O anexo multimédia {item} foi descartado.",
   "upload_form.drag_and_drop.on_drag_over": "O anexo multimédia {item} foi movido.",
   "upload_form.drag_and_drop.on_drag_start": "O anexo multimédia {item} foi escolhido.",
   "upload_form.edit": "Editar",
+  "upload_form.thumbnail": "Alterar miniatura",
+  "upload_form.video_description": "Descreva para pessoas com diminuição da acuidade auditiva ou visual",
+  "upload_modal.analyzing_picture": "A analizar imagem…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.applying": "A aplicar…",
+  "upload_modal.choose_image": "Escolher imagem",
+  "upload_modal.description_placeholder": "Grave e cabisbaixo, o filho justo zelava pela querida mãe doente",
+  "upload_modal.detect_text": "Detetar texto na imagem",
+  "upload_modal.edit_media": "Editar multimédia",
+  "upload_modal.hint": "Clica ou arrasta o círculo na pré-visualização para escolher o ponto focal que será sempre visível em todas as miniaturas.",
+  "upload_modal.preparing_ocr": "A preparar o reconhecimento de caracteres (OCR)…",
+  "upload_modal.preview_label": "Pré-visualizar ({ratio})",
   "upload_progress.label": "A enviar...",
   "upload_progress.processing": "A processar…",
   "username.taken": "Esse nome de utilizador já está a ser utilizado. Por favor, tente outro",
@@ -905,6 +882,8 @@
   "video.expand": "Expandir vídeo",
   "video.fullscreen": "Ecrã completo",
   "video.hide": "Ocultar vídeo",
+  "video.mute": "Desativar som",
   "video.pause": "Pausar",
-  "video.play": "Reproduzir"
+  "video.play": "Reproduzir",
+  "video.unmute": "Ativar som"
 }
diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json
index 8fec42bbd0..5a80cc2df7 100644
--- a/app/javascript/mastodon/locales/ro.json
+++ b/app/javascript/mastodon/locales/ro.json
@@ -29,6 +29,7 @@
   "account.endorse": "Promovează pe profil",
   "account.featured_tags.last_status_at": "Ultima postare pe {date}",
   "account.featured_tags.last_status_never": "Fără postări",
+  "account.featured_tags.title": "Haștagurile recomandate de {name}",
   "account.follow": "Urmărește",
   "account.follow_back": "Urmăreşte înapoi",
   "account.followers": "Urmăritori",
@@ -85,20 +86,6 @@
   "alert.unexpected.title": "Ups!",
   "alt_text_badge.title": "Text alternativ",
   "announcement.announcement": "Anunț",
-  "annual_report.summary.archetype.lurker": "Pânditorul",
-  "annual_report.summary.archetype.oracle": "Oracolul",
-  "annual_report.summary.archetype.pollster": "Sondatorul",
-  "annual_report.summary.archetype.replier": "Fluturele social",
-  "annual_report.summary.followers.followers": "urmăritori",
-  "annual_report.summary.followers.total": "{count} total",
-  "annual_report.summary.here_it_is": "Iată rezumatul dvs. al anului {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "cea mai favorizată postare",
-  "annual_report.summary.highlighted_post.by_reblogs": "cea mai boostată postare",
-  "annual_report.summary.highlighted_post.by_replies": "postarea cu cele mai multe răspunsuri",
-  "annual_report.summary.most_used_app.most_used_app": "cea mai utilizată aplicație",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "cel mai utilizat hashtag",
-  "annual_report.summary.most_used_hashtag.none": "Niciunul",
-  "annual_report.summary.new_posts.new_posts": "postări noi",
   "attachments_list.unprocessed": "(neprocesate)",
   "audio.hide": "Ascunde audio",
   "boost_modal.combo": "Poți apăsa {combo} pentru a sări peste asta data viitoare",
@@ -112,6 +99,7 @@
   "bundle_column_error.routing.body": "Pagina solicitată nu a putut fi găsită. Ești sigur că adresa URL din bara de adrese este corectă?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Închide",
+  "bundle_modal_error.message": "A apărut o eroare la încărcarea acestui element.",
   "bundle_modal_error.retry": "Încearcă din nou",
   "closed_registrations.other_server_instructions": "Deoarece Mastodon este descentralizat, poți crea un cont pe un alt server și încă poți interacționa cu acesta.",
   "closed_registrations_modal.description": "Crearea unui cont pe {domain} nu este posibilă momentan, dar aveți în vedere că nu aveți nevoie de un cont specific pe {domain} pentru a utiliza Mastodon.",
@@ -159,6 +147,7 @@
   "compose_form.poll.duration": "Durata sondajului",
   "compose_form.poll.multiple": "Alegeri multiple",
   "compose_form.poll.option_placeholder": "Opțiune {number}",
+  "compose_form.poll.single": "Alegeți unul",
   "compose_form.poll.switch_to_multiple": "Modifică sondajul pentru a permite mai multe opțiuni",
   "compose_form.poll.switch_to_single": "Modifică sondajul pentru a permite o singură opțiune",
   "compose_form.poll.type": "Stil",
@@ -202,6 +191,10 @@
   "disabled_account_banner.text": "Contul tău {disabledAccount} este momentan dezactivat.",
   "dismissable_banner.community_timeline": "Acestea sunt cele mai recente postări publice de la persoane ale căror conturi sunt găzduite de {domain}.",
   "dismissable_banner.dismiss": "Renunțare",
+  "dismissable_banner.explore_links": "În acest moment, oamenii vorbesc despre aceste știri, pe acesta dar și pe alte servere ale rețelei descentralizate.",
+  "dismissable_banner.explore_statuses": "Acestea sunt postări de peste tot din rețeaua de socializare care câștigă teren azi. Postările mai noi cu mai multe amplificări și favorite sunt clasate mai sus.",
+  "dismissable_banner.explore_tags": "Aceste hashtag-uri câștigă teren în rândul oamenilor de pe acesta și pe alte servere ale rețelei descentralizate chiar acum.",
+  "dismissable_banner.public_timeline": "Acestea sunt cele mai recente postări publice de la persoane de pe social web pe care le urmăresc oamenii de pe {domain}.",
   "embed.instructions": "Integrează această postare în site-ul tău copiind codul de mai jos.",
   "embed.preview": "Iată cum va arăta:",
   "emoji_button.activity": "Activități",
@@ -232,6 +225,7 @@
   "empty_column.hashtag": "Acest hashtag încă nu a fost folosit.",
   "empty_column.home": "Nu există nimic în cronologia ta! Abonează-te la mai multe persoane pentru a o umple. {suggestions}",
   "empty_column.list": "Momentan nu există nimic în această listă. Când membrii ei vor posta ceva nou, vor apărea aici.",
+  "empty_column.lists": "Momentan nu ai nicio listă. Când vei crea una, va apărea aici.",
   "empty_column.mutes": "Momentan nu ai ignorat niciun utilizator.",
   "empty_column.notifications": "Momentan nu ai nicio notificare. Când alte persoane vor interacționa cu tine, îl vei vedea aici.",
   "empty_column.public": "Nu există nimic aici! Postează ceva public, sau abonează-te manual la utilizatori din alte servere pentru a umple cronologia",
@@ -241,6 +235,7 @@
   "error.unexpected_crash.next_steps_addons": "Încearcă să le dezactivezi și să reîmprospătezi pagina. Dacă tot nu funcționează, poți accesa Mastodon dintr-un alt navigator sau dintr-o aplicație nativă.",
   "errors.unexpected_crash.copy_stacktrace": "Copiere stacktrace în clipboard",
   "errors.unexpected_crash.report_issue": "Raportează o problemă",
+  "explore.search_results": "Rezultatele căutării",
   "explore.suggested_follows": "Persoane",
   "explore.title": "Explorează",
   "explore.trending_links": "Noutăți",
@@ -277,6 +272,7 @@
   "footer.about": "Despre",
   "footer.directory": "Catalogul de profiluri",
   "footer.get_app": "Obține aplicația",
+  "footer.invite": "Invită persoane",
   "footer.keyboard_shortcuts": "Comenzi rapide de la tastatură",
   "footer.privacy_policy": "Politica de confidenţialitate",
   "footer.source_code": "Vizualizează codul sursă",
@@ -301,8 +297,17 @@
   "home.pending_critical_update.link": "Vezi noutăți",
   "home.pending_critical_update.title": "Actualizare critică de securitate disponibilă!",
   "home.show_announcements": "Afișează anunțurile",
+  "interaction_modal.description.favourite": "Cu un cont pe Mastodon, poți adăuga această postare la favorite pentru a-l informa pe autorul ei că o apreciezi și pentru a o salva pentru mai târziu.",
+  "interaction_modal.description.follow": "Cu un cont Mastodon, poți urmări pe {name} pentru a vedea postările sale în cronologia ta principală.",
+  "interaction_modal.description.reblog": "Cu un cont pe Mastodon, poți distribui această postare pentru a le-o arăta și celor abonați ție.",
+  "interaction_modal.description.reply": "Cu un cont pe Mastodon, poți răspunde acestei postări.",
+  "interaction_modal.login.action": "Du-mă acasă",
+  "interaction_modal.login.prompt": "Adresa serverului tău acasă, de ex. mastodon.social",
+  "interaction_modal.no_account_yet": "Nu ești încă pe Mastodon?",
   "interaction_modal.on_another_server": "Pe un alt server",
   "interaction_modal.on_this_server": "Pe acest server",
+  "interaction_modal.sign_in": "Nu sunteți autentificat la acest server. Unde este găzduit contul dvs.?",
+  "interaction_modal.sign_in_hint": "Sfat: acesta este site-ul web pe care v-ați înscris. Dacă nu vă amintiți, căutați e-mailul de bun venit în inboxul dvs. De asemenea, puteți introduce numele de utilizator complet! (de exemplu, @Mastodon@mastodon.social)",
   "interaction_modal.title.follow": "Urmărește pe {name}",
   "interaction_modal.title.reblog": "Distribuie postarea lui {name}",
   "interaction_modal.title.reply": "Răspunde postării lui {name}",
@@ -347,11 +352,20 @@
   "limited_account_hint.action": "Afișează profilul oricum",
   "limited_account_hint.title": "Acest profil a fost ascuns de moderatorii domeniului {domain}.",
   "link_preview.author": "De {name}",
+  "lists.account.add": "Adaugă în listă",
+  "lists.account.remove": "Elimină din listă",
   "lists.delete": "Șterge lista",
   "lists.edit": "Modifică lista",
+  "lists.edit.submit": "Schimbă titlul",
+  "lists.exclusive": "Ascundeți aceste postări de acasă",
+  "lists.new.create": "Adaugă o listă",
+  "lists.new.title_placeholder": "Titlu pentru noua listă",
   "lists.replies_policy.followed": "Tuturor persoanelor la care te-ai abonat",
   "lists.replies_policy.list": "Membrilor din listă",
   "lists.replies_policy.none": "Nu afișa nimănui",
+  "lists.replies_policy.title": "Afișează răspunsurile:",
+  "lists.search": "Caută printre persoanele la care ești abonat",
+  "lists.subheading": "Listele tale",
   "load_pending": "{count, plural, one {# element nou} other {# elemente noi}}",
   "moved_to_account_banner.text": "Contul tău {disabledAccount} este în acest moment dezactivat deoarece te-ai mutat la {movedToAccount}.",
   "navigation_bar.about": "Despre",
@@ -419,7 +433,34 @@
   "notifications_permission_banner.enable": "Activează notificările pe desktop",
   "notifications_permission_banner.how_to_control": "Pentru a primi notificări când Mastodon nu este deschis, activează notificările pe desktop. Poți controla exact ce tipuri de interacțiuni generează notificări pe desktop apăsând pe butonul {icon} de mai sus odată ce sunt activate.",
   "notifications_permission_banner.title": "Rămâne la curent",
+  "onboarding.action.back": "Du-mă înapoi",
+  "onboarding.actions.back": "Du-mă înapoi",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "Salut, #Mastodon!",
   "onboarding.follows.empty": "Din păcate, nu pot fi afișate rezultate chiar acum. Poți încerca să cauți sau să navighezi pe pagina de explorare pentru a găsi oameni pe care să-i urmărești sau încearcă iar mai târziu.",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.profile.lead": "Puteți completa întotdeauna acest lucru mai târziu în setări, unde sunt disponibile și mai multe opțiuni de personalizare.",
+  "onboarding.share.lead": "Spune-le oamenilor cum te pot găsi pe Mastodon!",
+  "onboarding.share.message": "Sunt {username} pe #Mastodon! Vino și urmărește-mă pe {url}",
+  "onboarding.share.next_steps": "Pașii următori posibili:",
+  "onboarding.share.title": "Partajați-vă profilul",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "Ați reușit!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "Fă-ți prima postare",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
+  "onboarding.tips.2fa": "<strong>Știați că?</strong> Vă puteți securiza contul prin configurarea autentificării cu doi factori în setările contului dvs. Funcționează cu orice aplicație TOTP la alegerea dvs., niciun număr de telefon nu este necesar!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Știați că?</strong> Deoarece Mastodon este decentralizat, unele profiluri pe care le întâlniți vor fi găzduite pe alte servere decât ale dvs. Și totuși puteți interacționa cu ele fără probleme! Serverul lor se află în a doua jumătate a numelui lor de utilizator!",
+  "onboarding.tips.migration": "<strong>Știai că?</strong> Dacă simți că {domain} nu este o alegere bună de server in viitor, te poți muta pe un alt server de Mastodon fără a-ți pierde urmăritorii. Poți găzdui chiar si propriul server!",
+  "onboarding.tips.verification": "<strong>Știați că?</strong> Puteți să vă verificați contul punând un link către profilul dumneavoastră Mastodon pe propriul site și adăugând site-ul web la profilul dvs. Nu sunt necesare taxe sau documente!",
   "picture_in_picture.restore": "Pune-l înapoi",
   "poll.closed": "Închis",
   "poll.refresh": "Reîncarcă",
@@ -436,6 +477,8 @@
   "privacy_policy.last_updated": "Ultima actualizare în data de {date}",
   "privacy_policy.title": "Politică de confidențialitate",
   "refresh": "Reîncarcă",
+  "regeneration_indicator.label": "Se încarcă…",
+  "regeneration_indicator.sublabel": "Cronologia ta principală este în curs de pregătire!",
   "relative_time.days": "{number}z",
   "relative_time.full.days": "acum {number, plural, one {o zi} few {# zile} other {# de zile}}",
   "relative_time.full.hours": "acum {number, plural, one {o oră} few {# ore} other {# de ore}}",
@@ -508,8 +551,10 @@
   "search_results.accounts": "Profiluri",
   "search_results.all": "Toate",
   "search_results.hashtags": "Hashtag-uri",
+  "search_results.nothing_found": "Nu am putut găsi nimic care să corespundă termenilor de căutare",
   "search_results.see_all": "Vezi tot",
   "search_results.statuses": "Postări",
+  "search_results.title": "Caută „{q}”",
   "server_banner.about_active_users": "Persoane care au folosit acest server în ultimele 30 de zile (Utilizatori Lunari Activi)",
   "server_banner.active_users": "utilizatori activi",
   "server_banner.administered_by": "Administrat de:",
@@ -586,7 +631,21 @@
   "upload_button.label": "Adaugă media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Limita de încărcare a fișierului a fost depășită.",
   "upload_error.poll": "Încărcarea fișierului nu este permisă cu sondaje.",
+  "upload_form.audio_description": "Descrie pentru persoanele cu deficiență a auzului",
+  "upload_form.description": "Adaugă o descriere pentru persoanele cu deficiențe de vedere",
   "upload_form.edit": "Modifică",
+  "upload_form.thumbnail": "Schimbă miniatura",
+  "upload_form.video_description": "Adaugă o descriere pentru persoanele cu deficiențe vizuale sau auditive",
+  "upload_modal.analyzing_picture": "Se analizează imaginea…",
+  "upload_modal.apply": "Aplică",
+  "upload_modal.applying": "Se aplică…",
+  "upload_modal.choose_image": "Alege imaginea",
+  "upload_modal.description_placeholder": "Vând muzică de jazz și haine de bun-gust în New-York și Quebec la preț fix",
+  "upload_modal.detect_text": "Detectare text din imagine",
+  "upload_modal.edit_media": "Modifică media",
+  "upload_modal.hint": "Fă clic sau glisează cercul pe previzualizare pentru a alege punctul focal care va fi vizibil în toate miniaturile.",
+  "upload_modal.preparing_ocr": "Se pregătește OCR…",
+  "upload_modal.preview_label": "Previzualizare ({ratio})",
   "upload_progress.label": "Se încarcă...",
   "upload_progress.processing": "Se procesează…",
   "username.taken": "Acel nume de utilizator este luat. Încearcă altul",
@@ -596,6 +655,8 @@
   "video.expand": "Extinde video",
   "video.fullscreen": "Ecran complet",
   "video.hide": "Ascunde video",
+  "video.mute": "Oprește sonorul",
   "video.pause": "Pauză",
-  "video.play": "Redare"
+  "video.play": "Redare",
+  "video.unmute": "Repornește sunetul"
 }
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index aaa73f51be..c395f6bac9 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -3,7 +3,7 @@
   "about.contact": "Связаться:",
   "about.disclaimer": "Mastodon — свободное программное обеспечение с открытым исходным кодом и торговая марка Mastodon gGmbH.",
   "about.domain_blocks.no_reason_available": "Причина не указана",
-  "about.domain_blocks.preamble": "Mastodon обычно позволяет просматривать содержимое и взаимодействовать с пользователями любых других серверов в федивёрсе. Вот исключения, сделанные конкретно для этого сервера.",
+  "about.domain_blocks.preamble": "Mastodon обычно позволяет просматривать содержимое и взаимодействовать с другими пользователями любых серверов в Федиверсе. Вот исключения, сделанные конкретно для этого сервера.",
   "about.domain_blocks.silenced.explanation": "Как правило, вы не увидите профили и контент с этого сервера, если вы специально не будете их искать или не подпишетесь на них.",
   "about.domain_blocks.silenced.title": "Ограничивается",
   "about.domain_blocks.suspended.explanation": "Никакие данные с этого сервера не будут обрабатываться, храниться или обмениваться, что делает невозможным любое взаимодействие или связь с пользователями с этого сервера.",
@@ -18,20 +18,18 @@
   "account.block": "Заблокировать @{name}",
   "account.block_domain": "Заблокировать {domain}",
   "account.block_short": "Заблокировать",
-  "account.blocked": "Заблокирован(а)",
+  "account.blocked": "Заблокировано",
   "account.cancel_follow_request": "Отозвать запрос на подписку",
-  "account.copy": "Копировать ссылку на профиль",
+  "account.copy": "Скопировать ссылку на профиль",
   "account.direct": "Упомянуть @{name} лично",
   "account.disable_notifications": "Не уведомлять о постах от @{name}",
   "account.domain_blocked": "Домен заблокирован",
-  "account.edit_profile": "Редактировать",
+  "account.edit_profile": "Редактировать профиль",
   "account.enable_notifications": "Уведомлять о постах от @{name}",
   "account.endorse": "Рекомендовать в профиле",
-  "account.featured": "Избранное",
-  "account.featured.hashtags": "Хэштеги",
-  "account.featured.posts": "Посты",
   "account.featured_tags.last_status_at": "Последний пост {date}",
   "account.featured_tags.last_status_never": "Нет постов",
+  "account.featured_tags.title": "Избранные хэштеги {name}",
   "account.follow": "Подписаться",
   "account.follow_back": "Подписаться в ответ",
   "account.followers": "Подписчики",
@@ -42,20 +40,20 @@
   "account.follows.empty": "Этот пользователь пока ни на кого не подписался.",
   "account.go_to_profile": "Перейти к профилю",
   "account.hide_reblogs": "Скрыть продвижения от @{name}",
-  "account.in_memoriam": "In Memoriam.",
+  "account.in_memoriam": "Вечная память.",
   "account.joined_short": "Дата регистрации",
   "account.languages": "Изменить языки подписки",
   "account.link_verified_on": "Владение этой ссылкой было проверено {date}",
-  "account.locked_info": "Это закрытая учётная запись. Её владелец вручную одобряет подписчиков.",
+  "account.locked_info": "Это закрытый аккаунт. Его владелец вручную одобряет подписчиков.",
   "account.media": "Медиа",
   "account.mention": "Упомянуть @{name}",
-  "account.moved_to": "У {name} теперь новая учётная запись:",
+  "account.moved_to": "У {name} теперь новый аккаунт:",
   "account.mute": "Игнорировать @{name}",
   "account.mute_notifications_short": "Отключить уведомления",
-  "account.mute_short": "Игнорировать",
+  "account.mute_short": "Приглушить",
   "account.muted": "Игнорируется",
-  "account.mutual": "Взаимные подписки",
-  "account.no_bio": "Описание профиля отсутствует.",
+  "account.mutual": "Взаимно",
+  "account.no_bio": "Описание не предоставлено.",
   "account.open_original_page": "Открыть исходную страницу",
   "account.posts": "Посты",
   "account.posts_with_replies": "Посты и ответы",
@@ -67,55 +65,28 @@
   "account.statuses_counter": "{count, plural, one {{counter} пост} few {{counter} поста} other {{counter} постов}}",
   "account.unblock": "Разблокировать @{name}",
   "account.unblock_domain": "Разблокировать {domain}",
-  "account.unblock_domain_short": "Разблокировать",
   "account.unblock_short": "Разблокировать",
   "account.unendorse": "Не рекомендовать в профиле",
   "account.unfollow": "Отписаться",
-  "account.unmute": "Не игнорировать @{name}",
+  "account.unmute": "Перестать игнорировать @{name}",
   "account.unmute_notifications_short": "Включить уведомления",
   "account.unmute_short": "Не игнорировать",
   "account_note.placeholder": "Текст заметки",
   "admin.dashboard.daily_retention": "Уровень удержания пользователей после регистрации, в днях",
   "admin.dashboard.monthly_retention": "Уровень удержания пользователей после регистрации, в месяцах",
-  "admin.dashboard.retention.average": "В среднем",
+  "admin.dashboard.retention.average": "В среднем за всё время",
   "admin.dashboard.retention.cohort": "Месяц регистрации",
   "admin.dashboard.retention.cohort_size": "Новые пользователи",
   "admin.impact_report.instance_accounts": "Профили учетных записей, которые будут удалены",
   "admin.impact_report.instance_followers": "Подписчики, которых потеряют наши пользователи",
   "admin.impact_report.instance_follows": "Подписчики, которых потеряют их пользователи",
   "admin.impact_report.title": "Резюме воздействия",
-  "alert.rate_limited.message": "Подождите до {retry_time, time, medium}, прежде чем делать что-либо ещё.",
-  "alert.rate_limited.title": "Слишком много запросов",
+  "alert.rate_limited.message": "Пожалуйста, повторите после {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Ограничение количества запросов",
   "alert.unexpected.message": "Произошла непредвиденная ошибка.",
   "alert.unexpected.title": "Ой!",
   "alt_text_badge.title": "Альтернативный текст",
-  "alt_text_modal.add_alt_text": "Альтернативный текст",
-  "alt_text_modal.add_text_from_image": "Добавить текст из изображения",
-  "alt_text_modal.cancel": "Отмена",
-  "alt_text_modal.change_thumbnail": "Изменить обложку",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Опишите то, что слышите, для людей с нарушениями слуха…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Опишите то, что видите, для людей с нарушениями зрения…",
-  "alt_text_modal.done": "Готово",
   "announcement.announcement": "Объявление",
-  "annual_report.summary.archetype.booster": "Репостер",
-  "annual_report.summary.archetype.lurker": "Молчун",
-  "annual_report.summary.archetype.oracle": "Гуру",
-  "annual_report.summary.archetype.pollster": "Опросчик",
-  "annual_report.summary.archetype.replier": "Душа компании",
-  "annual_report.summary.followers.followers": "подписчиков",
-  "annual_report.summary.followers.total": "{count} за всё время",
-  "annual_report.summary.here_it_is": "Вот ваши итоги {year} года:",
-  "annual_report.summary.highlighted_post.by_favourites": "пост с наибольшим количеством звёздочек",
-  "annual_report.summary.highlighted_post.by_reblogs": "самый популярный пост",
-  "annual_report.summary.highlighted_post.by_replies": "пост с наибольшим количеством ответов",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "наиболее часто используемое приложение",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "наиболее часто используемый хэштег",
-  "annual_report.summary.most_used_hashtag.none": "Нет",
-  "annual_report.summary.new_posts.new_posts": "новых постов",
-  "annual_report.summary.percentile.text": "<topLabel>Всё это помещает вас в топ</topLabel><percentage></percentage><bottomLabel>пользователей {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Роскомнадзор об этом не узнает.",
-  "annual_report.summary.thanks": "Спасибо за то, что были вместе с Mastodon!",
   "attachments_list.unprocessed": "(не обработан)",
   "audio.hide": "Скрыть аудио",
   "block_modal.remote_users_caveat": "Мы попросим сервер {domain} уважать ваше решение, однако соблюдение им блокировки не гарантировано, поскольку некоторые серверы могут по-разному обрабатывать запросы. Публичные посты по-прежнему могут быть видны неавторизованным пользователям.",
@@ -128,38 +99,35 @@
   "block_modal.you_wont_see_mentions": "Вы не увидите посты, которые его упоминают.",
   "boost_modal.combo": "{combo}, чтобы пропустить это в следующий раз",
   "boost_modal.reblog": "Продвинуть пост?",
-  "boost_modal.undo_reblog": "Отменить продвижение?",
-  "bundle_column_error.copy_stacktrace": "Копировать отчёт об ошибке",
+  "boost_modal.undo_reblog": "Убрать продвижение?",
+  "bundle_column_error.copy_stacktrace": "Скопировать отчет об ошибке",
   "bundle_column_error.error.body": "Запрошенная страница не может быть отображена. Это может быть вызвано ошибкой в нашем коде или проблемой совместимости браузера.",
   "bundle_column_error.error.title": "О нет!",
-  "bundle_column_error.network.body": "При загрузке этой страницы произошла ошибка. Это может быть вызвано временными проблемами с вашим подключением к интернету или ошибкой на сервере.",
+  "bundle_column_error.network.body": "При загрузке этой страницы произошла ошибка. Это может быть связано с Вашим Интернет-соединением или неполадками на сервере.",
   "bundle_column_error.network.title": "Ошибка сети",
   "bundle_column_error.retry": "Попробовать снова",
   "bundle_column_error.return": "Вернуться на главную",
-  "bundle_column_error.routing.body": "Запрошенная страница не найдена. Вы уверены, что в адресной строке указан правильный URL?",
+  "bundle_column_error.routing.body": "Запрошенная страница не найдена. Вы уверены, что URL в адресной строке правильный?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Закрыть",
-  "bundle_modal_error.message": "Что-то пошло не так при загрузке этой страницы.",
+  "bundle_modal_error.message": "Что-то пошло не так при загрузке этого компонента.",
   "bundle_modal_error.retry": "Попробовать снова",
-  "closed_registrations.other_server_instructions": "Поскольку Mastodon децентрализован, вы можете зарегистрироваться на другом сервере и всё равно взаимодействовать с этим сервером.",
-  "closed_registrations_modal.description": "Зарегистрироваться на {domain} сейчас не выйдет, но имейте в виду, что вам не нужна учётная запись именно на {domain}, чтобы использовать Mastodon.",
+  "closed_registrations.other_server_instructions": "Поскольку Mastodon децентрализован, вы можете создать учетную запись на другом сервере и всё ещё взаимодействовать с этим сервером.",
+  "closed_registrations_modal.description": "Создать учётную запись на {domain} сейчас не выйдет, но имейте в виду, что вам не нужна учётная запись именно на {domain}, чтобы использовать Mastodon.",
   "closed_registrations_modal.find_another_server": "Найти другой сервер",
-  "closed_registrations_modal.preamble": "Mastodon децентрализован, поэтому независимо от того, где именно вы зарегистрируетесь, вы сможете подписываться и взаимодействовать с кем угодно на этом сервере. Вы даже можете создать свой собственный сервер!",
+  "closed_registrations_modal.preamble": "Mastodon децентрализован, поэтому независимо от того, где вы создадите свою учетную запись, вы сможете следить и взаимодействовать с кем угодно на этом сервере. Вы даже можете разместить свой собственный сервер!",
   "closed_registrations_modal.title": "Регистрация в Mastodon",
   "column.about": "О проекте",
   "column.blocks": "Заблокированные пользователи",
   "column.bookmarks": "Закладки",
   "column.community": "Локальная лента",
-  "column.create_list": "Создать список",
   "column.direct": "Личные упоминания",
   "column.directory": "Просмотр профилей",
   "column.domain_blocks": "Заблокированные домены",
-  "column.edit_list": "Редактировать список",
-  "column.favourites": "Избранное",
+  "column.favourites": "Избранные",
   "column.firehose": "Живая лента",
   "column.follow_requests": "Запросы на подписку",
   "column.home": "Главная",
-  "column.list_members": "Добавить или удалить из списка",
   "column.lists": "Списки",
   "column.mutes": "Игнорируемые пользователи",
   "column.notifications": "Уведомления",
@@ -172,66 +140,58 @@
   "column_header.pin": "Закрепить",
   "column_header.show_settings": "Показать настройки",
   "column_header.unpin": "Открепить",
-  "column_search.cancel": "Отмена",
   "column_subheading.settings": "Настройки",
   "community.column_settings.local_only": "Только локальные",
   "community.column_settings.media_only": "Только с медиафайлами",
-  "community.column_settings.remote_only": "Только с других серверов",
+  "community.column_settings.remote_only": "Только удалённые",
   "compose.language.change": "Изменить язык",
-  "compose.language.search": "Найти язык...",
+  "compose.language.search": "Поиск языков...",
   "compose.published.body": "Пост опубликован.",
   "compose.published.open": "Открыть",
   "compose.saved.body": "Пост отредактирован.",
   "compose_form.direct_message_warning_learn_more": "Узнать больше",
   "compose_form.encryption_warning": "Посты в Mastodon не защищены сквозным шифрованием. Не делитесь конфиденциальной информацией через Mastodon.",
-  "compose_form.hashtag_warning": "Этот пост не появится в поиске по хэштегам, так как он не обозначен как публичный. Только публичные посты можно найти по хэштегу.",
+  "compose_form.hashtag_warning": "Этот пост не будет виден ни под одним из хэштегов, так как он не публичный. Только публичные посты можно найти по хэштегу.",
   "compose_form.lock_disclaimer": "Ваша учётная запись {locked}. Любой пользователь сможет подписаться на вас и просматривать посты для подписчиков.",
   "compose_form.lock_disclaimer.lock": "не закрыта",
   "compose_form.placeholder": "О чём думаете?",
-  "compose_form.poll.duration": "Продолжительность",
+  "compose_form.poll.duration": "Продолжительность опроса",
   "compose_form.poll.multiple": "Несколько вариантов ответа",
   "compose_form.poll.option_placeholder": "Вариант {number}",
-  "compose_form.poll.single": "Один вариант ответа",
-  "compose_form.poll.switch_to_multiple": "Переключить в режим выбора нескольких вариантов ответа",
-  "compose_form.poll.switch_to_single": "Переключить в режим выбора одного варианта ответа",
+  "compose_form.poll.single": "Выберите один",
+  "compose_form.poll.switch_to_multiple": "Разрешить выбор нескольких вариантов",
+  "compose_form.poll.switch_to_single": "Переключить в режим выбора одного ответа",
   "compose_form.poll.type": "Тип",
   "compose_form.publish": "Опубликовать",
   "compose_form.publish_form": "Опубликовать",
   "compose_form.reply": "Ответить",
   "compose_form.save_changes": "Сохранить",
-  "compose_form.spoiler.marked": "Удалить предупреждение о содержании",
-  "compose_form.spoiler.unmarked": "Добавить предупреждение о содержании",
-  "compose_form.spoiler_placeholder": "Предупреждение о содержании (необязательно)",
+  "compose_form.spoiler.marked": "Текст скрыт за предупреждением",
+  "compose_form.spoiler.unmarked": "Текст не скрыт",
+  "compose_form.spoiler_placeholder": "Предупреждение о содержимом (необязательно)",
   "confirmation_modal.cancel": "Отмена",
   "confirmations.block.confirm": "Заблокировать",
   "confirmations.delete.confirm": "Удалить",
   "confirmations.delete.message": "Вы уверены, что хотите удалить этот пост?",
   "confirmations.delete.title": "Удалить пост?",
   "confirmations.delete_list.confirm": "Удалить",
-  "confirmations.delete_list.message": "Вы уверены, что хотите навсегда удалить этот список?",
+  "confirmations.delete_list.message": "Вы действительно хотите навсегда удалить этот список?",
   "confirmations.delete_list.title": "Удалить список?",
   "confirmations.discard_edit_media.confirm": "Сбросить",
-  "confirmations.discard_edit_media.message": "У вас есть несохранённые изменения, касающиеся описания медиа или области предпросмотра, сбросить их?",
+  "confirmations.discard_edit_media.message": "У вас есть несохранённые изменения в описании мультимедиа или предпросмотре, сбросить их?",
   "confirmations.edit.confirm": "Редактировать",
-  "confirmations.edit.message": "Если вы начнёте редактировать сейчас, то набираемый в данный момент пост будет стёрт. Вы уверены, что хотите продолжить?",
-  "confirmations.edit.title": "Стереть несохранённый черновик поста?",
-  "confirmations.follow_to_list.confirm": "Подписаться и добавить",
-  "confirmations.follow_to_list.message": "Чтобы добавить пользователя {name} в список, вы должны быть на него подписаны.",
-  "confirmations.follow_to_list.title": "Подписаться на пользователя?",
+  "confirmations.edit.message": "При редактировании, текст набираемого поста будет очищен. Продолжить?",
+  "confirmations.edit.title": "Переписать сообщение?",
   "confirmations.logout.confirm": "Выйти",
   "confirmations.logout.message": "Вы уверены, что хотите выйти?",
   "confirmations.logout.title": "Выйти?",
-  "confirmations.missing_alt_text.confirm": "Добавить",
-  "confirmations.missing_alt_text.message": "Ваш пост содержит медиафайлы без альтернативного текста. Добавляя описания, вы делаете ваш контент доступным для более широкого круга людей.",
-  "confirmations.missing_alt_text.secondary": "Опубликовать",
-  "confirmations.missing_alt_text.title": "Добавить альтернативный текст?",
   "confirmations.mute.confirm": "Игнорировать",
   "confirmations.redraft.confirm": "Удалить и исправить",
-  "confirmations.redraft.message": "Вы уверены, что хотите удалить этот пост и создать его заново? Взаимодействия, такие как добавление в избранное или продвижение, будут потеряны, а ответы к оригинальному посту перестанут на него ссылаться.",
-  "confirmations.redraft.title": "Удалить и создать пост заново?",
+  "confirmations.redraft.message": "Вы уверены, что хотите удалить и переписать этот пост? Отметки «избранного», продвижения и ответы к оригинальному посту будут потеряны.",
+  "confirmations.redraft.title": "Создать пост заново?",
   "confirmations.reply.confirm": "Ответить",
-  "confirmations.reply.message": "Если вы начнёте составлять ответ сейчас, то набираемый в данный момент пост будет стёрт. Вы уверены, что хотите продолжить?",
-  "confirmations.reply.title": "Стереть несохранённый черновик поста?",
+  "confirmations.reply.message": "При ответе, текст набираемого поста будет очищен. Продолжить?",
+  "confirmations.reply.title": "Перепишем пост?",
   "confirmations.unfollow.confirm": "Отписаться",
   "confirmations.unfollow.message": "Вы уверены, что хотите отписаться от {name}?",
   "confirmations.unfollow.title": "Отписаться?",
@@ -245,7 +205,7 @@
   "copy_icon_button.copied": "Скопировано в буфер обмена",
   "copypaste.copied": "Скопировано",
   "copypaste.copy_to_clipboard": "Копировать в буфер обмена",
-  "directory.federated": "Со всего федивёрса",
+  "directory.federated": "Со всей федерации",
   "directory.local": "Только с {domain}",
   "directory.new_arrivals": "Новички",
   "directory.recently_active": "Недавно активные",
@@ -253,12 +213,12 @@
   "disabled_account_banner.text": "Ваша учётная запись {disabledAccount} в настоящее время отключена.",
   "dismissable_banner.community_timeline": "Это самые новые публичные посты от тех пользователей, чьи учётные записи находятся на сервере {domain}.",
   "dismissable_banner.dismiss": "Закрыть",
-  "dismissable_banner.explore_links": "Об этих новостях говорят в федивёрсе прямо сейчас. Свежие новости, опубликованные несколькими разными людьми, ранжируются выше.",
-  "dismissable_banner.explore_statuses": "Эти посты со всего федивёрса прямо сейчас набирают популярность. Более новые посты с более высоким количеством взаимодействий ранжируются выше.",
-  "dismissable_banner.explore_tags": "Эти хэштеги набирают популярность в федивёрсе прямо сейчас. Хэштеги, используемые несколькими разными людьми, ранжируются выше.",
-  "dismissable_banner.public_timeline": "Это самые новые публичные посты от всех тех людей в федивёрсе, на которых подписаны пользователи {domain}.",
+  "dismissable_banner.explore_links": "Об этих новостях прямо сейчас говорят люди на этом и других серверах децентрализованной сети.",
+  "dismissable_banner.explore_statuses": "Эти посты привлекают людей на этом и других серверах децентрализованной сети прямо сейчас.",
+  "dismissable_banner.explore_tags": "Эти хэштеги привлекают людей на этом и других серверах децентрализованной сети прямо сейчас.",
+  "dismissable_banner.public_timeline": "Это самые новые публичные посты от тех пользователей этого и других серверов децентрализованной сети, на которых подписываются пользователи {domain}.",
   "domain_block_modal.block": "Заблокировать сервер",
-  "domain_block_modal.block_account_instead": "Заблокировать @{name}",
+  "domain_block_modal.block_account_instead": "Заблокировать только @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Пользователи с этого сервера смогут взаимодействовать с вашими старыми постами.",
   "domain_block_modal.they_cant_follow": "Пользователи с этого сервера не смогут подписаться на вас.",
   "domain_block_modal.they_wont_know": "Пользователи с этого сервера не будут знать, что вы их блокируете.",
@@ -296,15 +256,15 @@
   "emoji_button.search_results": "Результаты поиска",
   "emoji_button.symbols": "Символы",
   "emoji_button.travel": "Путешествия и места",
-  "empty_column.account_hides_collections": "Пользователь предпочёл не раскрывать эту информацию",
-  "empty_column.account_suspended": "Учётная запись заблокирована",
+  "empty_column.account_hides_collections": "Данный пользователь решил не предоставлять эту информацию",
+  "empty_column.account_suspended": "Учетная запись заблокирована",
   "empty_column.account_timeline": "Здесь нет постов!",
   "empty_column.account_unavailable": "Профиль недоступен",
   "empty_column.blocks": "Вы ещё никого не заблокировали.",
   "empty_column.bookmarked_statuses": "У вас пока нет закладок. Когда вы добавляете пост в закладки, он появляется здесь.",
   "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
   "empty_column.direct": "У вас пока нет личных сообщений. Как только вы отправите или получите сообщение, оно появится здесь.",
-  "empty_column.domain_blocks": "Заблокированных доменов пока нет.",
+  "empty_column.domain_blocks": "Скрытых доменов пока нет.",
   "empty_column.explore_statuses": "Нет актуального. Проверьте позже!",
   "empty_column.favourited_statuses": "Вы не добавили ни один пост в «Избранное». Как только вы это сделаете, он появится здесь.",
   "empty_column.favourites": "Никто ещё не добавил этот пост в «Избранное». Как только кто-то это сделает, это отобразится здесь.",
@@ -313,16 +273,18 @@
   "empty_column.hashtag": "С этим хэштегом пока ещё ничего не публиковали.",
   "empty_column.home": "Ваша лента совсем пуста! Подписывайтесь на других, чтобы заполнить её.",
   "empty_column.list": "В этом списке пока ничего нет. Когда пользователи в списке публикуют новые посты, они появляются здесь.",
+  "empty_column.lists": "У вас ещё нет списков. Созданные вами списки будут показаны здесь.",
   "empty_column.mutes": "Вы ещё никого не добавляли в список игнорируемых.",
   "empty_column.notification_requests": "Здесь ничего нет! Когда вы получите новые уведомления, они здесь появятся согласно вашим настройкам.",
   "empty_column.notifications": "У вас пока нет уведомлений. Взаимодействуйте с другими, чтобы завести разговор.",
-  "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других серверов, чтобы заполнить ленту",
+  "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту",
   "error.unexpected_crash.explanation": "Из-за несовместимого браузера или ошибки в нашем коде, эта страница не может быть корректно отображена.",
   "error.unexpected_crash.explanation_addons": "Эта страница не может быть корректно отображена. Скорее всего, эта ошибка вызвана расширением браузера или инструментом автоматического перевода.",
   "error.unexpected_crash.next_steps": "Попробуйте обновить страницу. Если проблема не исчезает, используйте Mastodon из-под другого браузера или приложения.",
   "error.unexpected_crash.next_steps_addons": "Попробуйте их отключить и перезагрузить страницу. Если это не поможет, вы по-прежнему сможете войти в Mastodon через другой браузер или приложение.",
   "errors.unexpected_crash.copy_stacktrace": "Скопировать диагностическую информацию",
   "errors.unexpected_crash.report_issue": "Сообщить о проблеме",
+  "explore.search_results": "Результаты поиска",
   "explore.suggested_follows": "Люди",
   "explore.title": "Обзор",
   "explore.trending_links": "Новости",
@@ -334,21 +296,21 @@
   "filter_modal.added.expired_title": "Истёкший фильтр!",
   "filter_modal.added.review_and_configure": "Для просмотра и настройки этой категории фильтра, перейдите в {settings_link}.",
   "filter_modal.added.review_and_configure_title": "Настройки фильтра",
-  "filter_modal.added.settings_link": "настройки",
+  "filter_modal.added.settings_link": "страница настроек",
   "filter_modal.added.short_explanation": "Этот пост был добавлен в следующую категорию фильтра: {title}.",
   "filter_modal.added.title": "Фильтр добавлен!",
   "filter_modal.select_filter.context_mismatch": "не применяется к этому контексту",
-  "filter_modal.select_filter.expired": "истёкший",
+  "filter_modal.select_filter.expired": "истекло",
   "filter_modal.select_filter.prompt_new": "Новая категория: {name}",
   "filter_modal.select_filter.search": "Поиск или создание",
   "filter_modal.select_filter.subtitle": "Используйте существующую категорию или создайте новую",
   "filter_modal.select_filter.title": "Фильтровать этот пост",
   "filter_modal.title.status": "Фильтровать пост",
   "filter_warning.matches_filter": "Соответствует фильтру \"<span>{title}</span>\"",
-  "filtered_notifications_banner.pending_requests": "От {count, plural, =0 {не известных вам людей} one {# возможно вам известного человека} other {# возможно вам известных человек}}",
+  "filtered_notifications_banner.pending_requests": "Вы можете знать {count, plural, =0 {ни одного человека} one {одного человека} other {# человек}}",
   "filtered_notifications_banner.title": "Отфильтрованные уведомления",
-  "firehose.all": "Всё вместе",
-  "firehose.local": "Этот сервер",
+  "firehose.all": "Все",
+  "firehose.local": "Текущий сервер",
   "firehose.remote": "Другие серверы",
   "follow_request.authorize": "Авторизовать",
   "follow_request.reject": "Отказать",
@@ -372,14 +334,13 @@
   "footer.about": "О проекте",
   "footer.directory": "Каталог профилей",
   "footer.get_app": "Скачать приложение",
+  "footer.invite": "Пригласить людей",
   "footer.keyboard_shortcuts": "Сочетания клавиш",
   "footer.privacy_policy": "Политика конфиденциальности",
   "footer.source_code": "Исходный код",
-  "footer.status": "Состояние сервера",
-  "footer.terms_of_service": "Пользовательское соглашение",
+  "footer.status": "Статус",
   "generic.saved": "Сохранено",
   "getting_started.heading": "Добро пожаловать",
-  "hashtag.admin_moderation": "Открыть интерфейс модератора для #{name}",
   "hashtag.column_header.tag_mode.all": "и {additional}",
   "hashtag.column_header.tag_mode.any": "или {additional}",
   "hashtag.column_header.tag_mode.none": "без {additional}",
@@ -395,14 +356,14 @@
   "hashtag.follow": "Подписаться на новые посты",
   "hashtag.unfollow": "Отписаться",
   "hashtags.and_other": "…и {count, plural, other {ещё #}}",
-  "hints.profiles.followers_may_be_missing": "Подписчики этого профиля могут отсутствовать.",
-  "hints.profiles.follows_may_be_missing": "Подписки этого профиля могут отсутствовать.",
-  "hints.profiles.posts_may_be_missing": "Некоторые сообщения этого профиля могут отсутствовать.",
-  "hints.profiles.see_more_followers": "Перейдите на {domain}, чтобы увидеть всех подписчиков",
-  "hints.profiles.see_more_follows": "Перейдите на {domain}, чтобы увидеть все подписки",
-  "hints.profiles.see_more_posts": "Перейдите на {domain}, чтобы увидеть все посты",
+  "hints.profiles.followers_may_be_missing": "Подписчики у этого профиля могут отсутствовать.",
+  "hints.profiles.follows_may_be_missing": "Фолловеры для этого профиля могут отсутствовать.",
+  "hints.profiles.posts_may_be_missing": "Некоторые сообщения из этого профиля могут отсутствовать.",
+  "hints.profiles.see_more_followers": "Посмотреть больше подписчиков на {domain}",
+  "hints.profiles.see_more_follows": "Смотрите другие материалы по теме {domain}",
+  "hints.profiles.see_more_posts": "Посмотреть другие сообщения на {domain}",
   "hints.threads.replies_may_be_missing": "Ответы с других серверов могут отсутствовать.",
-  "hints.threads.see_more": "Перейдите на {domain}, чтобы увидеть все ответы",
+  "hints.threads.see_more": "Посмотреть другие ответы на {domain}",
   "home.column_settings.show_reblogs": "Показывать продвижения",
   "home.column_settings.show_replies": "Показывать ответы",
   "home.hide_announcements": "Скрыть объявления",
@@ -410,34 +371,32 @@
   "home.pending_critical_update.link": "Посмотреть обновления",
   "home.pending_critical_update.title": "Доступно критическое обновление безопасности!",
   "home.show_announcements": "Показать объявления",
-  "ignore_notifications_modal.disclaimer": "Mastodon не может сообщить пользователям, что вы игнорируете их уведомления. Игнорирование уведомлений не остановит отправку самих сообщений.",
-  "ignore_notifications_modal.filter_instead": "Фильтровать",
-  "ignore_notifications_modal.filter_to_act_users": "Вы по-прежнему сможете принимать и отклонять запросы, а также отправлять жалобы на пользователей",
-  "ignore_notifications_modal.filter_to_avoid_confusion": "Фильтрация поможет избежать возможной путаницы",
-  "ignore_notifications_modal.filter_to_review_separately": "Отфильтрованные уведомления можно просматривать отдельно",
+  "ignore_notifications_modal.disclaimer": "Mastodon не может сообщить пользователям, что вы проигнорировали их уведомления. Игнорирование уведомлений не остановит отправку самих сообщений.",
+  "ignore_notifications_modal.filter_instead": "Фильтр вместо",
+  "ignore_notifications_modal.filter_to_act_users": "Вы и далее сможете принять, отвергнуть и жаловаться на пользователей",
+  "ignore_notifications_modal.filter_to_avoid_confusion": "Фильтрация помогает избежать потенциальной путаницы",
+  "ignore_notifications_modal.filter_to_review_separately": "Вы можете просматривать отфильтрованные уведомления отдельно",
   "ignore_notifications_modal.ignore": "Игнорировать уведомления",
-  "ignore_notifications_modal.limited_accounts_title": "Игнорировать уведомления от модерируемых учётных записей?",
-  "ignore_notifications_modal.new_accounts_title": "Игнорировать уведомления от новых учётных записей?",
-  "ignore_notifications_modal.not_followers_title": "Игнорировать уведомления от людей, не подписанных на вас?",
-  "ignore_notifications_modal.not_following_title": "Игнорировать уведомления от людей, на которых вы не подписаны?",
-  "ignore_notifications_modal.private_mentions_title": "Игнорировать уведомления о нежелательных личных упоминаниях?",
-  "info_button.label": "Помощь",
-  "info_button.what_is_alt_text": "<h1>Что это такое?</h1> <p>Альтернативный текст содержит описание изображения для людей с ограничениями зрения, медленным интернетом и для тех, кому нужен дополнительный контекст.</p> <p>Вы можете улучшить доступность и понимание для всех, написав четкий, краткий и объективный альтернативный текст.</p> <ul> <li>Уловите важные элементы</li> <li>Перескажите текстовую информацию на изображении</li> <li>Используйте правильную структуру предложений</li> <li>Избегайте избыточной информации</li> <li>Сосредоточьтесь на тенденциях и ключевых выводах при описании сложных визуализаций (таких как диаграммы или карты)</li> </ul>",
-  "interaction_modal.action.favourite": "Вы можете добавить этот пост в избранное со своей учётной записью.",
-  "interaction_modal.action.follow": "Вы можете подписаться со своей учётной записью.",
-  "interaction_modal.action.reblog": "Вы можете продвинуть этот пост со своей учётной записью.",
-  "interaction_modal.action.reply": "Вы можете ответить на этот пост со своей учётной записью.",
-  "interaction_modal.action.vote": "Вы можете проголосовать в этом опросе со своей учётной записью.",
-  "interaction_modal.go": "Вперёд!",
-  "interaction_modal.no_account_yet": "У вас нет никакой учётной записи?",
+  "ignore_notifications_modal.limited_accounts_title": "Игнорировать уведомления от модерируемых аккаунтов?",
+  "ignore_notifications_modal.new_accounts_title": "Игнорировать уведомления от новых аккаунтов?",
+  "ignore_notifications_modal.not_followers_title": "Игнорировать уведомления от людей, которые не следят за вами?",
+  "ignore_notifications_modal.not_following_title": "Игнорировать уведомления от людей, за которыми вы не следите?",
+  "ignore_notifications_modal.private_mentions_title": "Игнорировать уведомления о нежелательных личных сообщениях?",
+  "interaction_modal.description.favourite": "С учётной записью Mastodon, вы можете добавить этот пост в избранное, чтобы сохранить его на будущее и дать автору знать, что пост вам понравился.",
+  "interaction_modal.description.follow": "С учётной записью Mastodon вы можете подписаться на {name}, чтобы получать их посты в своей домашней ленте.",
+  "interaction_modal.description.reblog": "С учётной записью Mastodon, вы можете продвинуть этот пост, чтобы поделиться им со своими подписчиками.",
+  "interaction_modal.description.reply": "Вы можете ответить на этот пост с учётной записью Mastodon.",
+  "interaction_modal.login.action": "Перейти на домашнюю страницу",
+  "interaction_modal.login.prompt": "Домен вашего домашнего сервера, например, mastodon.social",
+  "interaction_modal.no_account_yet": "Еще не на Mastodon?",
   "interaction_modal.on_another_server": "На другом сервере",
   "interaction_modal.on_this_server": "На этом сервере",
+  "interaction_modal.sign_in": "Вы не вошли в систему на этом сервере. Где размещена ваша учетная запись?",
+  "interaction_modal.sign_in_hint": "Совет: Это сайт, на котором вы зарегистрировались. Если вы не помните, найдите приветственное письмо в своем почтовом ящике. Вы также можете ввести свое полное имя пользователя! (например, @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Добавить пост {name} в избранное",
   "interaction_modal.title.follow": "Подписаться на {name}",
   "interaction_modal.title.reblog": "Продвинуть пост {name}",
   "interaction_modal.title.reply": "Ответить на пост {name}",
-  "interaction_modal.title.vote": "Голосовать в опросе {name}",
-  "interaction_modal.username_prompt": "Например {example}",
   "intervals.full.days": "{number, plural, one {# день} few {# дня} other {# дней}}",
   "intervals.full.hours": "{number, plural, one {# час} few {# часа} other {# часов}}",
   "intervals.full.minutes": "{number, plural, one {# минута} few {# минуты} other {# минут}}",
@@ -473,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "показать/скрыть текст за предупреждением",
   "keyboard_shortcuts.toggle_sensitivity": "показать/скрыть медиафайлы",
   "keyboard_shortcuts.toot": "начать писать новый пост",
-  "keyboard_shortcuts.translate": "перевести пост",
   "keyboard_shortcuts.unfocus": "убрать фокус с поля ввода/поиска",
   "keyboard_shortcuts.up": "вверх по списку",
   "lightbox.close": "Закрыть",
@@ -486,32 +444,20 @@
   "link_preview.author": "Автор: {name}",
   "link_preview.more_from_author": "Больше от {name}",
   "link_preview.shares": "{count, plural, one {{counter} пост} other {{counter} посты}}",
-  "lists.add_member": "Добавить",
-  "lists.add_to_list": "Добавить в список",
-  "lists.add_to_lists": "Добавить {name} в списки",
-  "lists.create": "Создать",
-  "lists.create_a_list_to_organize": "Создать новый список, чтобы упорядочить домашнюю ленту",
-  "lists.create_list": "Создать список",
+  "lists.account.add": "Добавить в список",
+  "lists.account.remove": "Убрать из списка",
   "lists.delete": "Удалить список",
-  "lists.done": "Готово",
   "lists.edit": "Изменить список",
-  "lists.exclusive": "Не показывать участников в домашней ленте",
-  "lists.exclusive_hint": "Если кто-то есть в этом списке, скрыть его в домашней ленте, чтобы не видеть его посты дважды.",
-  "lists.find_users_to_add": "Найти пользователей для добавления",
-  "lists.list_members": "Пользователи в списке",
-  "lists.list_members_count": "{count, plural, one {# пользователь} few {# пользователя} other {# пользователей}}",
-  "lists.list_name": "Название списка",
-  "lists.new_list_name": "Новый список",
-  "lists.no_lists_yet": "Пока нет списков.",
-  "lists.no_members_yet": "Пока нет пользователей в списке.",
-  "lists.no_results_found": "Не найдено.",
-  "lists.remove_member": "Удалить",
+  "lists.edit.submit": "Изменить название",
+  "lists.exclusive": "Не показывать посты из этого списка в домашней ленте",
+  "lists.new.create": "Создать список",
+  "lists.new.title_placeholder": "Название для нового списка",
   "lists.replies_policy.followed": "Все пользователи, на которых вы подписаны",
   "lists.replies_policy.list": "Другие пользователи в списке",
   "lists.replies_policy.none": "Никого",
-  "lists.save": "Сохранить",
-  "lists.search": "Поиск",
-  "lists.show_replies_to": "Показывать ответы пользователей в списке на посты",
+  "lists.replies_policy.title": "Показать ответы только:",
+  "lists.search": "Искать среди подписок",
+  "lists.subheading": "Ваши списки",
   "load_pending": "{count, plural, one {# новый элемент} few {# новых элемента} other {# новых элементов}}",
   "loading_indicator.label": "Загрузка…",
   "media_gallery.hide": "Скрыть",
@@ -534,9 +480,9 @@
   "navigation_bar.compose": "Создать новый пост",
   "navigation_bar.direct": "Личные упоминания",
   "navigation_bar.discover": "Изучайте",
-  "navigation_bar.domain_blocks": "Заблокированные домены",
+  "navigation_bar.domain_blocks": "Скрытые домены",
   "navigation_bar.explore": "Обзор",
-  "navigation_bar.favourites": "Избранное",
+  "navigation_bar.favourites": "Избранные",
   "navigation_bar.filters": "Игнорируемые слова",
   "navigation_bar.follow_requests": "Запросы на подписку",
   "navigation_bar.followed_tags": "Отслеживаемые хэштеги",
@@ -560,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} пожаловался на {target}",
   "notification.admin.sign_up": "{name} зарегистрировался",
   "notification.admin.sign_up.name_and_others": "{name} и ещё {count, plural, one {# пользователь} few {# пользователя} other {# пользователей}} зарегистрировались",
-  "notification.annual_report.message": "#Wrapstodon за {year} год ждёт вас! Откройте для себя итоги и памятные моменты этого года в Mastodon!",
-  "notification.annual_report.view": "Перейти к #Wrapstodon",
   "notification.favourite": "{name} добавил(а) ваш пост в избранное",
   "notification.favourite.name_and_others_with_link": "{name} и ещё <a>{count, plural, one {# пользователь} few {# пользователя} other {# пользователей}}</a> добавили ваш пост в избранное",
-  "notification.favourite_pm": "{name} добавил(а) ваше личное сообщение в избранное",
-  "notification.favourite_pm.name_and_others_with_link": "{name} и ещё <a>{count, plural, one {# пользователь} few {# пользователя} other {# пользователей}}</a> добавили ваше личное сообщение в избранное",
   "notification.follow": "{name} подписался (-лась) на вас",
   "notification.follow.name_and_others": "{name} и ещё <a>{count, plural, one {# пользователь} few {# пользователя} other {# пользователей}}</a> подписались на вас",
   "notification.follow_request": "{name} отправил запрос на подписку",
@@ -586,7 +528,7 @@
   "notification.moderation_warning.action_silence": "Ваша учётная запись была ограничена.",
   "notification.moderation_warning.action_suspend": "Действие вашей учётной записи приостановлено.",
   "notification.own_poll": "Ваш опрос завершился",
-  "notification.poll": "Опрос, в котором вы приняли участие, завершился",
+  "notification.poll": "Голосование, в котором вы приняли участие, завершилось",
   "notification.reblog": "{name} продвинул(а) ваш пост",
   "notification.reblog.name_and_others_with_link": "{name} и ещё <a>{count, plural, one {# пользователь} few {# пользователя} other {# пользователей}}</a> продвинули ваш пост",
   "notification.relationships_severance_event": "Потеряно соединение с {name}",
@@ -598,20 +540,20 @@
   "notification.update": "{name} изменил(а) пост",
   "notification_requests.accept": "Принять",
   "notification_requests.accept_multiple": "{count, plural, one {Принять # запрос…} few {Принять # запроса…} other {Принять # запросов…}}",
-  "notification_requests.confirm_accept_multiple.button": "{count, plural, other {Принять запросы}}",
+  "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Принять запрос} other {Принять запросы}}",
   "notification_requests.confirm_accept_multiple.message": "Вы собираетесь принять {count, plural, one {# запрос на показ уведомлений} few {# запроса на показ уведомлений} other {# запросов на показ уведомлений}}. Продолжить?",
-  "notification_requests.confirm_accept_multiple.title": "Принять запросы на уведомления?",
-  "notification_requests.confirm_dismiss_multiple.button": "{count, plural, other {Отклонить запросы}}",
+  "notification_requests.confirm_accept_multiple.title": "Принимать запросы на уведомления?",
+  "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Отклонить запрос} other {Отклонить запросы}}",
   "notification_requests.confirm_dismiss_multiple.message": "Вы собираетесь отклонить {count, plural, one {# запрос на показ уведомлений} few {# запроса на показ уведомлений} other {# запросов на показ уведомлений}}. Вы не сможете просмотреть {count, plural, other {их}} потом. Продолжить?",
-  "notification_requests.confirm_dismiss_multiple.title": "Отклонить запросы на уведомления?",
+  "notification_requests.confirm_dismiss_multiple.title": "Отклонять запросы на уведомления?",
   "notification_requests.dismiss": "Отклонить",
   "notification_requests.dismiss_multiple": "{count, plural, one {Отклонить # запрос…} few {Отклонить # запроса…} other {Отклонить # запросов…}}",
-  "notification_requests.edit_selection": "Изменить",
+  "notification_requests.edit_selection": "Редактировать",
   "notification_requests.exit_selection": "Готово",
-  "notification_requests.explainer_for_limited_account": "Эта учётная запись ограничена модератором, поэтому уведомления от неё были отфильтрованы.",
-  "notification_requests.explainer_for_limited_remote_account": "Эта учётная запись или её сервер ограничены модератором, поэтому уведомления от неё были отфильтрованы.",
+  "notification_requests.explainer_for_limited_account": "Уведомления от этой учетной записи были отфильтрованы, поскольку учетная запись была ограничена модератором.",
+  "notification_requests.explainer_for_limited_remote_account": "Уведомления от этой учетной записи были отфильтрованы, поскольку учетная запись или ее сервер были ограничены модератором.",
   "notification_requests.maximize": "Развернуть",
-  "notification_requests.minimize_banner": "Свернуть панель отфильтрованных уведомлений в значок",
+  "notification_requests.minimize_banner": "Минимизация баннера отфильтрованных уведомлений",
   "notification_requests.notifications_from": "Уведомления от {name}",
   "notification_requests.title": "Отфильтрованные уведомления",
   "notification_requests.view": "Просмотр уведомлений",
@@ -621,9 +563,9 @@
   "notifications.column_settings.admin.report": "Новые жалобы:",
   "notifications.column_settings.admin.sign_up": "Новые регистрации:",
   "notifications.column_settings.alert": "Уведомления на рабочем столе",
-  "notifications.column_settings.favourite": "Ваш пост добавили в избранное:",
+  "notifications.column_settings.favourite": "Ваш пост добавили в избранные:",
   "notifications.column_settings.filter_bar.advanced": "Показать все категории",
-  "notifications.column_settings.filter_bar.category": "Панель быстрых фильтров",
+  "notifications.column_settings.filter_bar.category": "Панель сортировки",
   "notifications.column_settings.follow": "У вас новый подписчик:",
   "notifications.column_settings.follow_request": "Новые запросы на подписку:",
   "notifications.column_settings.group": "Группировать",
@@ -636,11 +578,11 @@
   "notifications.column_settings.status": "Новые посты:",
   "notifications.column_settings.unread_notifications.category": "Непрочитанные уведомления",
   "notifications.column_settings.unread_notifications.highlight": "Выделять непрочитанные уведомления",
-  "notifications.column_settings.update": "Пост был отредактирован:",
+  "notifications.column_settings.update": "Правки:",
   "notifications.filter.all": "Все",
   "notifications.filter.boosts": "Продвижения",
   "notifications.filter.favourites": "Избранное",
-  "notifications.filter.follows": "Новые подписчики",
+  "notifications.filter.follows": "Подписки",
   "notifications.filter.mentions": "Упоминания",
   "notifications.filter.polls": "Результаты опросов",
   "notifications.filter.statuses": "Обновления от людей, на которых вы подписаны",
@@ -670,21 +612,44 @@
   "notifications_permission_banner.enable": "Включить уведомления",
   "notifications_permission_banner.how_to_control": "Получайте уведомления даже когда Mastodon закрыт, включив уведомления на рабочем столе. А чтобы лишний шум не отвлекал, вы можете настроить какие уведомления вы хотите получать, нажав на кнопку {icon} выше.",
   "notifications_permission_banner.title": "Будьте в курсе происходящего",
-  "onboarding.follows.back": "Назад",
-  "onboarding.follows.done": "Готово",
+  "onboarding.action.back": "Верните меня",
+  "onboarding.actions.back": "Верните меня",
+  "onboarding.actions.go_to_explore": "Посмотреть, что актуально",
+  "onboarding.actions.go_to_home": "Перейти к домашней ленте новостей",
+  "onboarding.compose.template": "Привет, #Mastodon!",
   "onboarding.follows.empty": "К сожалению, сейчас нет результатов. Вы можете попробовать использовать поиск или просмотреть страницу \"Исследования\", чтобы найти людей, за которыми можно следить, или повторить попытку позже.",
-  "onboarding.follows.search": "Поиск",
-  "onboarding.follows.title": "Начните подписываться на людей",
+  "onboarding.follows.lead": "Вы сами формируете свою домашнюю ленту. Чем больше людей, за которыми вы следите, тем активнее и интереснее она будет. Эти профили могут быть хорошей отправной точкой - вы всегда можете от них отказаться!",
+  "onboarding.follows.title": "Популярно на Mastodon",
   "onboarding.profile.discoverable": "Сделать мой профиль открытым",
   "onboarding.profile.discoverable_hint": "Если вы соглашаетесь на открытость на Mastodon, ваши сообщения могут появляться в результатах поиска и трендах, а ваш профиль может быть предложен людям со схожими с вами интересами.",
   "onboarding.profile.display_name": "Отображаемое имя",
   "onboarding.profile.display_name_hint": "Ваше полное имя или псевдоним…",
+  "onboarding.profile.lead": "Вы всегда можете завершить это позже в настройках, где доступны еще более широкие возможности настройки.",
   "onboarding.profile.note": "О себе",
   "onboarding.profile.note_hint": "Вы можете @упоминать других людей или использовать #хэштеги…",
   "onboarding.profile.save_and_continue": "Сохранить и продолжить",
   "onboarding.profile.title": "Настройка профиля",
   "onboarding.profile.upload_avatar": "Загрузить фотографию профиля",
   "onboarding.profile.upload_header": "Загрузить заголовок профиля",
+  "onboarding.share.lead": "Расскажите людям, как найти вас на Mastodon!",
+  "onboarding.share.message": "Я {username} на #Mastodon! Следуйте за мной по адресу {url}",
+  "onboarding.share.next_steps": "Возможные дальнейшие шаги:",
+  "onboarding.share.title": "Поделиться вашим профилем",
+  "onboarding.start.lead": "Ваш новый аккаунт Mastodon готов к работе. Вот как вы можете использовать его по максимуму:",
+  "onboarding.start.skip": "Хотите сразу перейти к делу?",
+  "onboarding.start.title": "Вы сделали это!",
+  "onboarding.steps.follow_people.body": "Вы сами формируете свою ленту. Давайте наполним ее интересными людьми.",
+  "onboarding.steps.follow_people.title": "Подписаться на {count, plural, one {одного человека} other {# людей}}",
+  "onboarding.steps.publish_status.body": "Поздоровайтесь с миром.",
+  "onboarding.steps.publish_status.title": "Разместите свой первый пост",
+  "onboarding.steps.setup_profile.body": "Другие с большей вероятностью будут взаимодействовать с вами, если у вас заполненный профиль.",
+  "onboarding.steps.setup_profile.title": "Настройте свой профиль",
+  "onboarding.steps.share_profile.body": "Расскажите своим друзьям как найти вас на Mastodon!",
+  "onboarding.steps.share_profile.title": "Поделитесь вашим профилем",
+  "onboarding.tips.2fa": "<strong>А вы знали? </strong> Можно защитить свой аккаунт, настроив двухфакторную аутентификацию в настройках аккаунта. Она работает с любым приложением TOTP по вашему выбору, номер телефона не нужен!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Знали ли вы? </strong> Поскольку Mastodon децентрализован, некоторые профили, с которыми вы столкнетесь, будут размещены на серверах, отличных от вашего. И все же вы можете взаимодействовать с ними без проблем! Их сервер находится во второй половине имени пользователя!",
+  "onboarding.tips.migration": "<strong>Знаете ли вы? </strong> Если вы чувствуете, что {domain} не подходит вам в качестве сервера в будущем, вы можете переехать на другой сервер Mastodon без потери своих подписчиков. Вы даже можете разместить свой собственный сервер!",
+  "onboarding.tips.verification": "<strong>Знали ли вы? </strong> Вы можете подтвердить свою учетную запись, разместив ссылку на свой профиль Mastodon на собственном сайте и добавив сайт в свой профиль. Никаких сборов или документов не требуется!",
   "password_confirmation.exceeds_maxlength": "Срок подтверждения пароля превышает максимальную длину пароля",
   "password_confirmation.mismatching": "Введенные пароли не совпадают.",
   "picture_in_picture.restore": "Вернуть обратно",
@@ -700,20 +665,20 @@
   "poll_button.remove_poll": "Удалить опрос",
   "privacy.change": "Изменить видимость поста",
   "privacy.direct.long": "Все упомянутые в посте",
-  "privacy.direct.short": "Личное упоминание",
+  "privacy.direct.short": "Определённые люди",
   "privacy.private.long": "Только ваши подписчики",
-  "privacy.private.short": "Для подписчиков",
-  "privacy.public.long": "Кто угодно в интернете",
+  "privacy.private.short": "Подписчики",
+  "privacy.public.long": "Любой, находящийся на Mastodon и вне его",
   "privacy.public.short": "Публичный",
-  "privacy.unlisted.additional": "Похоже на «Публичный» за исключением того, что пост не появится ни в живых лентах, ни в лентах хэштегов, ни в разделе «Обзор», ни в поиске Mastodon, даже если вы разрешили поиск по своим постам в настройках профиля.",
+  "privacy.unlisted.additional": "Работает точно так же, как public, за исключением того, что пост не будет отображаться в прямых лентах, хэштегах, исследованиях или поиске Mastodon, даже если ваш аккаунт подписан на это на уровне всего аккаунта.",
   "privacy.unlisted.long": "Меньше алгоритмических фанфар",
   "privacy.unlisted.short": "Тихий публичный",
   "privacy_policy.last_updated": "Последнее обновление {date}",
   "privacy_policy.title": "Политика конфиденциальности",
   "recommended": "Рекомендуется",
   "refresh": "Обновить",
-  "regeneration_indicator.please_stand_by": "Пожалуйста, подождите.",
-  "regeneration_indicator.preparing_your_home_feed": "Готовим вашу ленту…",
+  "regeneration_indicator.label": "Загрузка…",
+  "regeneration_indicator.sublabel": "Один момент, мы подготавливаем вашу ленту!",
   "relative_time.days": "{number} д",
   "relative_time.full.days": "{number, plural, one {# день} many {# дней} other {# дня}} назад",
   "relative_time.full.hours": "{number, plural, one {# час} many {# часов} other {# часа}} назад",
@@ -769,8 +734,9 @@
   "report.unfollow": "Отписаться от @{name}",
   "report.unfollow_explanation": "Вы подписаны на этого пользователя. Чтобы не видеть его/её посты в своей домашней ленте, отпишитесь от него/неё.",
   "report_notification.attached_statuses": "{count, plural, one {{count} сообщение} few {{count} сообщения} many {{count} сообщений} other {{count} сообщений}} вложено",
-  "report_notification.categories.legal": "Нарушение закона",
-  "report_notification.categories.other": "Другое",
+  "report_notification.categories.legal": "Правовая информация",
+  "report_notification.categories.legal_sentence": "срамной контент",
+  "report_notification.categories.other": "Прочее",
   "report_notification.categories.other_sentence": "другое",
   "report_notification.categories.spam": "Спам",
   "report_notification.categories.spam_sentence": "спам",
@@ -796,18 +762,17 @@
   "search_results.accounts": "Профили",
   "search_results.all": "Все",
   "search_results.hashtags": "Хэштеги",
-  "search_results.no_results": "Ничего не найдено.",
-  "search_results.no_search_yet": "Попробуйте поискать посты, профили или хэштеги.",
+  "search_results.nothing_found": "Ничего не найдено по этому запросу",
   "search_results.see_all": "Показать все",
   "search_results.statuses": "Посты",
-  "search_results.title": "Поиск \"{q}\"",
+  "search_results.title": "Поиск {q}",
   "server_banner.about_active_users": "Люди, заходившие на этот сервер за последние 30 дней (ежемесячные активные пользователи)",
   "server_banner.active_users": "активные пользователи",
   "server_banner.administered_by": "Управляется:",
-  "server_banner.is_one_of_many": "{domain} — это один из многих независимых серверов Mastodon, которые вы можете использовать, чтобы присоединиться к сети Fediverse.",
+  "server_banner.is_one_of_many": "{domain} — это один из многих независимых серверов Mastodon, которые вы можете использовать для участия в сети Fediverse.",
   "server_banner.server_stats": "Статистика сервера:",
   "sign_in_banner.create_account": "Зарегистрироваться",
-  "sign_in_banner.follow_anyone": "Подписывайтесь на кого угодно в федивёрсе и читайте ленту в хронологическом порядке. Никаких алгоритмов, рекламы или кликбейта.",
+  "sign_in_banner.follow_anyone": "Подписывайтесь на кого угодно в федивёрсе и смотрите ленту в хронологическом порядке. Никаких алгоритмов, рекламы или кликбейта.",
   "sign_in_banner.mastodon_is": "Mastodon — лучший способ быть в курсе всего происходящего.",
   "sign_in_banner.sign_in": "Войти",
   "sign_in_banner.sso_redirect": "Войдите или Зарегистрируйтесь",
@@ -816,15 +781,15 @@
   "status.admin_status": "Открыть этот пост в интерфейсе модератора",
   "status.block": "Заблокировать @{name}",
   "status.bookmark": "Добавить в закладки",
-  "status.cancel_reblog_private": "Отменить продвижение",
+  "status.cancel_reblog_private": "Не продвигать",
   "status.cannot_reblog": "Этот пост не может быть продвинут",
-  "status.continued_thread": "Продолжение треда",
+  "status.continued_thread": "Продолжение темы",
   "status.copy": "Скопировать ссылку на пост",
   "status.delete": "Удалить",
   "status.detailed_status": "Подробный просмотр обсуждения",
   "status.direct": "Упомянуть @{name} лично",
   "status.direct_indicator": "Личное упоминание",
-  "status.edit": "Редактировать",
+  "status.edit": "Изменить",
   "status.edited": "Дата последнего изменения: {date}",
   "status.edited_x_times": "{count, plural, one {{count} изменение} many {{count} изменений} other {{count} изменения}}",
   "status.embed": "Встроить на свой сайт",
@@ -852,8 +817,7 @@
   "status.reblogs.empty": "Никто ещё не продвинул этот пост. Как только кто-то это сделает, они появятся здесь.",
   "status.redraft": "Создать заново",
   "status.remove_bookmark": "Убрать из закладок",
-  "status.remove_favourite": "Убрать из избранного",
-  "status.replied_in_thread": "Ответил(а) в треде",
+  "status.replied_in_thread": "Ответил в теме",
   "status.replied_to": "Ответил(а) {name}",
   "status.reply": "Ответить",
   "status.replyAll": "Ответить всем",
@@ -874,9 +838,6 @@
   "subscribed_languages.target": "Изменить языки подписки для {target}",
   "tabs_bar.home": "Главная",
   "tabs_bar.notifications": "Уведомления",
-  "terms_of_service.effective_as_of": "Действует с {date}",
-  "terms_of_service.title": "Пользовательское соглашение",
-  "terms_of_service.upcoming_changes_on": "Предстоящие изменения {date}",
   "time_remaining.days": "{number, plural, one {остался # день} few {осталось # дня} many {осталось # дней} other {осталось # дней}}",
   "time_remaining.hours": "{number, plural, one {остался # час} few {осталось # часа} many {осталось # часов} other {осталось # часов}}",
   "time_remaining.minutes": "{number, plural, one {осталась # минута} few {осталось # минуты} many {осталось # минут} other {осталось # минут}}",
@@ -892,12 +853,26 @@
   "upload_button.label": "Прикрепить фото, видео или аудио",
   "upload_error.limit": "Достигнут лимит загруженных файлов.",
   "upload_error.poll": "К опросам нельзя прикреплять файлы.",
+  "upload_form.audio_description": "Опишите аудиофайл для людей с нарушением слуха",
+  "upload_form.description": "Добавьте описание для людей с нарушениями зрения:",
   "upload_form.drag_and_drop.instructions": "Чтобы подобрать прикрепленный файл, нажмите \"Пробел\" (Space) или \"Ввод\" (Enter). При перетаскивании используйте клавиши со стрелками, чтобы переместить прикрепленные файлы в любом направлении. Нажмите \"Пробел\" (Space) или \"Ввод\" (Enter) еще раз, чтобы переместить вложение в новое место, или нажмите кнопку \"Выйти\" (Escape), чтобы отменить.",
   "upload_form.drag_and_drop.on_drag_cancel": "Перетаскивание было отменено. Вложение медиа {item} было удалено.",
   "upload_form.drag_and_drop.on_drag_end": "Медиа вложение {item} было удалено.",
   "upload_form.drag_and_drop.on_drag_over": "Медиа вложение {item} было перемещено.",
   "upload_form.drag_and_drop.on_drag_start": "Загружается медиафайл {item}.",
-  "upload_form.edit": "Редактировать",
+  "upload_form.edit": "Изменить",
+  "upload_form.thumbnail": "Изменить обложку",
+  "upload_form.video_description": "Опишите видео для людей с нарушением слуха или зрения",
+  "upload_modal.analyzing_picture": "Обработка изображения…",
+  "upload_modal.apply": "Применить",
+  "upload_modal.applying": "Применение…",
+  "upload_modal.choose_image": "Выбрать изображение",
+  "upload_modal.description_placeholder": "На дворе трава, на траве дрова",
+  "upload_modal.detect_text": "Найти текст на картинке",
+  "upload_modal.edit_media": "Изменить файл",
+  "upload_modal.hint": "Нажмите и перетащите круг в предпросмотре в точку фокуса, которая всегда будет видна на эскизах.",
+  "upload_modal.preparing_ocr": "Подготовка распознавания…",
+  "upload_modal.preview_label": "Предпросмотр ({ratio})",
   "upload_progress.label": "Загрузка...",
   "upload_progress.processing": "Обработка…",
   "username.taken": "Это имя пользователя уже занято. Выберите другое",
@@ -910,9 +885,5 @@
   "video.mute": "Выключить звук",
   "video.pause": "Пауза",
   "video.play": "Пуск",
-  "video.skip_backward": "Промотать назад",
-  "video.skip_forward": "Промотать вперёд",
-  "video.unmute": "Включить звук",
-  "video.volume_down": "Уменьшить громкость",
-  "video.volume_up": "Увеличить громкость"
+  "video.unmute": "Включить звук"
 }
diff --git a/app/javascript/mastodon/locales/ry.json b/app/javascript/mastodon/locales/ry.json
index ed9751634e..02d1c005cf 100644
--- a/app/javascript/mastodon/locales/ry.json
+++ b/app/javascript/mastodon/locales/ry.json
@@ -28,6 +28,7 @@
   "account.endorse": "Указовати на профілови",
   "account.featured_tags.last_status_at": "Датум послідньої публикації {date}",
   "account.featured_tags.last_status_never": "Ниє публикацій",
+  "account.featured_tags.title": "Ублюблені гештеґы {name}",
   "account.follow": "Пудписати ся",
   "account.follow_back": "Пудписати ся тоже",
   "account.followers": "Пудписникы",
@@ -98,6 +99,7 @@
   "bundle_column_error.routing.body": "Не можеме найти сяку сторунку. Бизувні сьте, ож URL у адресному шорикови є добрый?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Заперти",
+  "bundle_modal_error.message": "Штось ся показило, закидь сьме ладовали сись компонент.",
   "bundle_modal_error.retry": "Попробовати зась",
   "closed_registrations.other_server_instructions": "Mastodon є децентралізованов платформов, можете си учинити профіл и на другому серверови тай комуніковати из сим.",
   "closed_registrations_modal.description": "Раз не мож учинити профіл на {domain}, айбо не мусите мати профіл ипен на серверови {domain} обы хосновати Mastodon.",
@@ -138,6 +140,7 @@
   "compose_form.poll.duration": "Трывалость убзвідованя",
   "compose_form.poll.multiple": "Дакулько варіантув",
   "compose_form.poll.option_placeholder": "Варіант {number}",
+  "compose_form.poll.single": "Уберіт єден",
   "compose_form.poll.switch_to_multiple": "Змінити убзвідованя обы поволити дакулько варіантув",
   "compose_form.poll.switch_to_single": "Змінити убзвідованя обы поволити лишек єден варіант",
   "compose_form.poll.type": "Стіл",
diff --git a/app/javascript/mastodon/locales/sa.json b/app/javascript/mastodon/locales/sa.json
index ce88bda740..2d48f688d5 100644
--- a/app/javascript/mastodon/locales/sa.json
+++ b/app/javascript/mastodon/locales/sa.json
@@ -26,6 +26,7 @@
   "account.endorse": "व्यक्तिगतविवरणे वैशिष्ट्यम्",
   "account.featured_tags.last_status_at": "{date} दिने गतस्थापनम्",
   "account.featured_tags.last_status_never": "न पत्रम्",
+  "account.featured_tags.title": "{name} इत्यस्य विशेषहैस्टैगः",
   "account.follow": "अनुस्रियताम्",
   "account.followers": "अनुसर्तारः",
   "account.followers.empty": "नाऽनुसर्तारो वर्तन्ते",
@@ -82,6 +83,7 @@
   "bundle_column_error.routing.body": "अनुरोधितं पृष्ठं न लब्धम्। URL सङ्केतं सम्यगस्तीति कृपया दृढीकुरु।",
   "bundle_column_error.routing.title": "४०४",
   "bundle_modal_error.close": "पिधीयताम्",
+  "bundle_modal_error.message": "आरोपणे कश्चन दोषो जातः",
   "bundle_modal_error.retry": "पुनः यतताम्",
   "closed_registrations.other_server_instructions": "यतोहि मस्टोडोनक्रेन्द्रीयकृतमस्ति, अन्यास्मिन्सर्वरि एकौण्टं स्रष्टुं शक्नोषि एवञ्च एतेन संयोक्तुं शक्नोषि।",
   "closed_registrations_modal.description": "{domain} मध्ये एकौण्टं करणमधुना न सम्भवति, किन्तु कृपया अवधीयतां यन्मास्टोडोनमुपयोक्तुं {domain} मध्ये एकौण्टं करणं नावश्यकम्।",
@@ -159,6 +161,8 @@
   "disabled_account_banner.text": "तव एकौण्ट् {disabledAccount} अधुना निष्कृतमस्ति।",
   "dismissable_banner.community_timeline": "तानि तेषां जनानां नूतनतमानि सार्वजनिकानि पत्राणि सन्ति येषामेकौण्टः {domain} द्वारा होस्त् भवन्ति।",
   "dismissable_banner.dismiss": "अपास्य",
+  "dismissable_banner.explore_links": "एतासां वार्तानां विषये अधुना अकेन्द्रीकृतजालस्य अस्मिनन्येषु च सर्वर्षु जनैश्चर्चा क्रियते।",
+  "dismissable_banner.explore_tags": "अकेन्द्रीकृतजालस्य अस्मदन्येभ्यश्च सर्वर्भ्यः एतानि प्रचलितवस्तूनि इदानीमस्मिन्सर्वरि कर्षणं प्राप्नुवन्ति।",
   "embed.instructions": "पत्रमेतत्स्वीयजालस्थाने स्थापयितुमधो लिखितो विध्यादेशो युज्यताम्",
   "embed.preview": "अत्रैवं दृश्यते तत्:",
   "emoji_button.activity": "आचरणम्",
@@ -190,6 +194,7 @@
   "empty_column.hashtag": "नाऽस्मिन् प्रचलितवस्तुचिह्ने किमपि ।",
   "empty_column.home": "गृहसमयतालिका रिक्ताऽस्ति । गम्यतां {public} वाऽन्वेषणैः प्रारभ्यतां मेलनं क्रियताञ्च ।",
   "empty_column.list": "न किमपि वर्तते सूच्यामस्याम् । यदा सूच्याः सदस्या नवपत्राणि प्रकटीकुर्वन्ति तदाऽत्राऽऽयान्ति ।",
+  "empty_column.lists": "तव पार्श्वे न कापि सूचिर्वर्तते। यदैकां सृजसि तदा अत्र दृश्यते।",
   "empty_column.mutes": "त्वया अद्यापि नैकोऽप्युपभोक्ता मूकीकृतो वर्तते।",
   "empty_column.notifications": "तव पार्श्वे न अधुना पर्यन्तं किमपि विज्ञापनं वर्तते। यदा अन्या जना त्वया संयोजयन्ति तदा इह पश्यसि।",
   "empty_column.public": "न इह किमपि वर्तते! किञ्चिल्लिख सार्वजनिकरूपेण, उत स्वयमन्यसर्वर्तः उपभोक्तॄननुसर एतत्पूरयितुम्",
@@ -199,6 +204,7 @@
   "error.unexpected_crash.next_steps_addons": "तानि निष्क्रियं कृत्वा पृष्ठं रिफ्रेशं कर्तुं यतस्व। यदि तत्परेऽपि कार्यं नाकार्षीत्तर्ह्यप्यन्यब्रौसरा उत नेटिवेपा मास्टोडोनुपयोक्तुं शक्नोषि।",
   "errors.unexpected_crash.copy_stacktrace": "स्तेक्त्रेसमनुलिपिं कुरु क्लिप्फलकं",
   "errors.unexpected_crash.report_issue": "दोषमावेदय",
+  "explore.search_results": "परिणामानविच्छ",
   "explore.suggested_follows": "जनाः",
   "explore.title": "अन्विच्छ",
   "explore.trending_links": "वार्ताः",
@@ -227,6 +233,7 @@
   "footer.about": "विषये",
   "footer.directory": "मुखपार्श्वविभागः",
   "footer.get_app": "एप् लभस्व",
+  "footer.invite": "जनं निमन्त्रय",
   "footer.keyboard_shortcuts": "कीफलकहर्स्वमार्गाः",
   "footer.privacy_policy": "गोपनीयतानीतिः",
   "footer.source_code": "स्रोतविद्यादेशं दर्शय",
@@ -248,6 +255,9 @@
   "home.column_settings.show_replies": "उत्तराणि दर्शय",
   "home.hide_announcements": "विज्ञापनानि प्रच्छादय",
   "home.show_announcements": "विज्ञापनानि दर्शय",
+  "interaction_modal.description.follow": "मास्टोडोनि एकौण्टा {name} नाम्ना उपभोक्तारमनुसर्तुं शक्नोषि तस्य पत्राणि लब्धुं ते गृहनिरासे।",
+  "interaction_modal.description.reblog": "मास्टोडिनि एकौण्टा पत्रमिदं बुस्तिति कर्तुं शक्नोषि ते स्वानुसारिणो भागं कर्तुम्।",
+  "interaction_modal.description.reply": "मास्टोडोनि एकौण्टा पत्रमिदं प्रतिवादयितुं शक्नोषि।",
   "interaction_modal.on_another_server": "अन्यस्मिन्सर्वरि",
   "interaction_modal.on_this_server": "अस्मिन्सर्वरि",
   "interaction_modal.title.follow": "{name} अनुसर",
@@ -293,11 +303,19 @@
   "lightbox.previous": "पूर्वः",
   "limited_account_hint.action": "प्रोफैलं दर्शय कथञ्चित्",
   "limited_account_hint.title": "{domain} इत्यस्य प्रशासकैरयं प्रोफैल्प्रच्छन्नः।",
+  "lists.account.add": "सूचेर्मध्ये योजय",
+  "lists.account.remove": "सूचेर्मार्जय",
   "lists.delete": "सूचिं मार्जय",
   "lists.edit": "सूचिं सम्पादय",
+  "lists.edit.submit": "उपाधिं परिवर्तय",
+  "lists.new.create": "सूचिं योजय",
+  "lists.new.title_placeholder": "नूतनसूच्युपाधिः",
   "lists.replies_policy.followed": "कोऽप्यनुसारितोपभोक्ता",
   "lists.replies_policy.list": "सूचेस्सदस्याः",
   "lists.replies_policy.none": "न कोऽपि",
+  "lists.replies_policy.title": "एतमुत्तराणि दर्शय :",
+  "lists.search": "त्वया अनुसारितजनेषु अन्विष्य",
+  "lists.subheading": "तव सूचयः",
   "load_pending": "{count, plural, one {# नूतनवस्तु} other {# नूतनवस्तूनि}}",
   "moved_to_account_banner.text": "तव एकौण्ट् {disabledAccount} अधुना निष्कृतो यतोहि {movedToAccount} अस्मिन्त्वमसार्षीः।",
   "navigation_bar.about": "विषये",
@@ -363,7 +381,30 @@
   "notifications_permission_banner.enable": "देस्क्टप्विज्ञापनानि सशक्तं कुरु",
   "notifications_permission_banner.how_to_control": "यदा माटोडोन्नोद्घाटितस्तदा विज्ञापनानि प्राप्तुं देस्क्तप्विज्ञापनानि सशक्तं कुरु। यदा तानि सशक्तानि तदा {icon} गण्डस्य माध्यमेन केऽपि प्रकारास्संवादा देस्क्तप्विज्ञापनानि जनयन्तीति नियामकं कर्तुं शक्नोषि।",
   "notifications_permission_banner.title": "मा कदापि वस्तु त्यज",
+  "onboarding.action.back": "मां प्रत्यागमय",
+  "onboarding.actions.back": "मां प्रत्यागमय",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "नमस्ते #मास्टोडन्",
   "onboarding.follows.empty": "दुर्भाग्यतया इदानीं कोऽपि परिणामो न दृश्यते। अन्वेषणमुपयोजयितुं वा जनमनुसर्तुं गवेषणपृष्ठस्यान्वेषणं यतितुं शक्नोषि वा पश्चात्पुनर्यतस्व।",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.share.lead": "मास्टोडनि कथं ते त्वामन्वेषितुं शक्नुवन्ति तज्जनान्ज्ञापय!",
+  "onboarding.share.message": "अहं {username} #मास्टोडनि! आयाहि {url} इत्यस्मिननुसरन्तु।",
+  "onboarding.share.next_steps": "सम्भवपरपदानि :",
+  "onboarding.share.title": "स्वविवरणं विभाज्यताम्",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "चकृषे!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "स्वप्रथमप्रेषणं कुरु",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
+  "onboarding.tips.2fa": "<strong>वेत्सि वा?</strong> स्वैकौटं सुरक्षितुं द्विकारकप्रमाणीकरणमेकौत्सेटिंसि स्थापयितुं शक्नोषि। केनापि त्वया विकल्पितेन TOTP एपा एतत्कार्यं करोति, न काचिद्दूरवाणीसंख्या आवश्यका।",
   "picture_in_picture.restore": "तत्प्रतिस्थापय",
   "poll.closed": "बद्धम्",
   "poll.refresh": "नवीकुरु",
@@ -379,6 +420,8 @@
   "privacy_policy.last_updated": "अन्तिमवारं परिवर्तितम् {date}",
   "privacy_policy.title": "गोपनीयतानीतिः",
   "refresh": "नवीकुरु",
+  "regeneration_indicator.label": "आरोपयति…",
+  "regeneration_indicator.sublabel": "तव गृहनिरासः सज्जीकृतोऽस्ति!",
   "relative_time.days": "{number}दि",
   "relative_time.full.days": "{number, plural, one {# दिनं} other {# दिनानि}} पूर्वम्",
   "relative_time.full.hours": "{number, plural, one {# होरा} other {# होराः}} पूर्वम्",
@@ -437,7 +480,9 @@
   "search.search_or_paste": "URL अन्विच्छ वा लेपनं कुरु",
   "search_results.all": "सर्वम्",
   "search_results.hashtags": "प्रचलितवस्तूनि",
+  "search_results.nothing_found": "एतेभ्योऽन्वेषणपदेभ्यः किमपि न प्राप्तम्",
   "search_results.statuses": "पत्राणि",
+  "search_results.title": "{q} कृते अन्विष्य",
   "server_banner.about_active_users": "विगतेषु ३० दिनेषु सर्वरमिममुपयुज्यमाणा जनाः (मासिकसक्रियोपभोक्तारः)",
   "server_banner.active_users": "सक्रियोपभोक्तारः",
   "server_banner.administered_by": "इत्यनेन अधिकृतः : ",
@@ -474,5 +519,8 @@
   "status.redraft": "मार्जय पुनश्च लिख्यताम्",
   "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "Uploading…"
 }
diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json
index 79ef6f6ef5..4ee5627388 100644
--- a/app/javascript/mastodon/locales/sc.json
+++ b/app/javascript/mastodon/locales/sc.json
@@ -29,6 +29,7 @@
   "account.endorse": "Cussìgia in su profilu tuo",
   "account.featured_tags.last_status_at": "Ùrtima publicatzione in su {date}",
   "account.featured_tags.last_status_never": "Peruna publicatzione",
+  "account.featured_tags.title": "Etichetas de {name} in evidèntzia",
   "account.follow": "Sighi",
   "account.follow_back": "Sighi tue puru",
   "account.followers": "Sighiduras",
@@ -109,6 +110,7 @@
   "bundle_column_error.routing.body": "Impossìbile agatare sa pàgina rechesta. Seguru chi s'URL in sa barra de indiritzos est curretu?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Serra",
+  "bundle_modal_error.message": "Faddina in su carrigamentu de custu cumponente.",
   "bundle_modal_error.retry": "Torra·bi a proare",
   "closed_registrations.other_server_instructions": "Dae chi Mastodon est detzentralizadu, podes creare unu contu in un'àteru serbidore e interagire cun custu.",
   "closed_registrations_modal.description": "Sa creatzione de contos in {domain} no est possìbile in custu momentu, però tene in cunsideru chi non tenes bisòngiu de unu contu ispetzìficu in {domain} pro impreare Mastodon.",
@@ -155,6 +157,7 @@
   "compose_form.poll.duration": "Longària de su sondàgiu",
   "compose_form.poll.multiple": "Sèberu mùltiplu",
   "compose_form.poll.option_placeholder": "Optzione {number}",
+  "compose_form.poll.single": "Sèbera·nde una",
   "compose_form.poll.switch_to_multiple": "Muda su sondàgiu pro permìtere multi-optziones",
   "compose_form.poll.switch_to_single": "Muda su sondàgiu pro permìtere un'optzione isceti",
   "compose_form.poll.type": "Istile",
@@ -208,6 +211,8 @@
   "disabled_account_banner.text": "Su contu tuo {disabledAccount} no est ativu.",
   "dismissable_banner.community_timeline": "Custas sunt is publicatziones pùblicas prus reghentes dae gente cun contu in {domain}.",
   "dismissable_banner.dismiss": "Iscarta",
+  "dismissable_banner.explore_links": "Custas sunt is istòrias de noas prus cumpartzidas in sa rete oe. Is istòrias prus noas publicadas dae gente prus diversa ant a èssere priorizadas.",
+  "dismissable_banner.explore_statuses": "Custas sunt publicatziones dae sa rete detzentralizada chi sunt retzende atentzione oe. Is publicatziones prus noas cun prus cumpartziduras e preferèntzias ant a èssere priorizadas.",
   "domain_block_modal.block": "Bloca su serbidore",
   "domain_block_modal.block_account_instead": "Bloca imbetzes a @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Is persones de custu serbidore podent ancora interagire cun is publicatziones betzas tuas.",
@@ -253,6 +258,7 @@
   "empty_column.hashtag": "Ancora nudda in custa eticheta.",
   "empty_column.home": "Sa lìnia de tempus printzipale tua est bòida. Visita {public} o imprea sa chirca pro cumintzare e agatare àteras persones.",
   "empty_column.list": "Nudda ancora in custa lista. Cando is persones de custa lista ant a publicare, is publicatziones ant a aparèssere inoghe.",
+  "empty_column.lists": "Non tenes ancora peruna lista. Cando nd'as a creare una, at a èssere ammustrada inoghe.",
   "empty_column.mutes": "No as postu ancora nemos a sa muda.",
   "empty_column.notifications": "Non tenes ancora peruna notìfica. Chistiona cun una persone pro cumintzare un'arresonada.",
   "empty_column.public": "Nudda inoghe. Iscrie calicuna cosa pùblica, o sighi àteras persones de àteros serbidores pro prenare custu ispàtziu",
@@ -262,6 +268,7 @@
   "error.unexpected_crash.next_steps_addons": "Proa a ddos disabilitare e torra a carrigare sa pàgina. Si custu no acontzat su problema, podes chircare de impreare Mastodon in unu navigadore diferente o in un'aplicatzione nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Còpia stacktrace in punta de billete",
   "errors.unexpected_crash.report_issue": "Sinnala unu problema",
+  "explore.search_results": "Resurtados de sa chirca",
   "explore.suggested_follows": "Gente",
   "explore.title": "Esplora",
   "explore.trending_links": "Noas",
@@ -301,6 +308,7 @@
   "footer.about": "Informatziones",
   "footer.directory": "Diretòriu de profilos",
   "footer.get_app": "Otene s'aplicatzione",
+  "footer.invite": "Invita gente",
   "footer.keyboard_shortcuts": "Incurtzaduras de tecladu",
   "footer.privacy_policy": "Polìtica de riservadesa",
   "footer.source_code": "Ammustra su còdighe de orìgine",
@@ -337,6 +345,10 @@
   "ignore_notifications_modal.filter_instead": "Opuru filtra",
   "ignore_notifications_modal.filter_to_act_users": "As a pòdere ancora atzetare, refudare o sinnalare a utentes",
   "ignore_notifications_modal.filter_to_avoid_confusion": "Filtrare agiudat a evitare possìbiles confusiones",
+  "interaction_modal.description.reply": "Podes rispòndere a custa publicatzione cun unu contu de Mastodon.",
+  "interaction_modal.login.action": "Torra a sa pàgina printzipale",
+  "interaction_modal.login.prompt": "Su domìniu de su serbidore domèsticu tuo, pro esempru mastodon.social",
+  "interaction_modal.no_account_yet": "Non ses in Mastodon?",
   "interaction_modal.on_this_server": "In custu serbidore",
   "interaction_modal.title.follow": "Sighi a {name}",
   "interaction_modal.title.reply": "Risponde a sa publicatzione de {name}",
@@ -381,11 +393,20 @@
   "lightbox.previous": "Pretzedente",
   "limited_account_hint.title": "Custu profilu est istadu cuadu dae sa moderatzione de {domain}.",
   "link_preview.shares": "{count, plural, one {{counter} publicatzione} other {{counter} publicatziones}}",
+  "lists.account.add": "Agiunghe a sa lista",
+  "lists.account.remove": "Boga dae sa lista",
   "lists.delete": "Cantzella sa lista",
   "lists.edit": "Modìfica sa lista",
+  "lists.edit.submit": "Muda su tìtulu",
+  "lists.exclusive": "Cua custas publicatziones dae sa pàgina printzipale",
+  "lists.new.create": "Agiunghe lista",
+  "lists.new.title_placeholder": "Tìtulu de sa lista noa",
   "lists.replies_policy.followed": "Cale si siat persone chi sighis",
   "lists.replies_policy.list": "Persones de sa lista",
   "lists.replies_policy.none": "Nemos",
+  "lists.replies_policy.title": "Ammustra is rispostas a:",
+  "lists.search": "Chirca intre sa gente chi ses sighende",
+  "lists.subheading": "Is listas tuas",
   "load_pending": "{count, plural, one {# elementu nou} other {# elementos noos}}",
   "loading_indicator.label": "Carrighende…",
   "media_gallery.hide": "Cua",
@@ -522,8 +543,11 @@
   "notifications_permission_banner.enable": "Abilita is notìficas de iscrivania",
   "notifications_permission_banner.how_to_control": "Pro retzire notìficas cando Mastodon no est abertu, abilita is notìficas de iscrivania. Podes controllare cun pretzisione is castas de interatziones chi ingendrant notìficas de iscrivania pro mèdiu de su butone {icon} in subra, cando sunt abilitadas.",
   "notifications_permission_banner.title": "Non ti perdas mai nudda",
+  "onboarding.compose.template": "Salude #Mastodon!",
   "onboarding.profile.display_name": "Nòmine visìbile",
   "onboarding.profile.note": "Biografia",
+  "onboarding.steps.setup_profile.title": "Personaliza su profilu tuo",
+  "onboarding.steps.share_profile.title": "Cumpartzi su profilu tuo",
   "picture_in_picture.restore": "Torra·ddu a ue fiat",
   "poll.closed": "Serradu",
   "poll.refresh": "Atualiza",
@@ -537,6 +561,7 @@
   "poll_button.remove_poll": "Cantzella su sondàgiu",
   "privacy.change": "Modìfica s'istadu de riservadesa",
   "privacy.direct.long": "Totu is utentes mentovados in sa publicatzione",
+  "privacy.direct.short": "Persones ispetzìficas",
   "privacy.private.long": "Isceti chie ti sighit",
   "privacy.private.short": "Sighiduras",
   "privacy.public.long": "Cale si siat persone a intro o a foras de Mastodon",
@@ -545,6 +570,8 @@
   "privacy_policy.title": "Polìtica de riservadesa",
   "recommended": "Cussigiadu",
   "refresh": "Atualiza",
+  "regeneration_indicator.label": "Carrighende…",
+  "regeneration_indicator.sublabel": "Preparende sa lìnia de tempus printzipale tua.",
   "relative_time.days": "{number} dies a oe",
   "relative_time.full.days": "{number, plural, one {# die} other {# dies}} a oe",
   "relative_time.full.hours": "{number, plural, one {# ora} other {# oras}} a immoe",
@@ -616,8 +643,10 @@
   "search_results.accounts": "Profilos",
   "search_results.all": "Totus",
   "search_results.hashtags": "Etichetas",
+  "search_results.nothing_found": "Impossìbile agatare currispondèntzias pro custos tèrmines de chirca",
   "search_results.see_all": "Bide totu",
   "search_results.statuses": "Publicatziones",
+  "search_results.title": "Chirca {q}",
   "server_banner.about_active_users": "Gente chi at impreadu custu serbidore is ùrtimas 30 dies (Utentes cun Atividade a su Mese)",
   "server_banner.active_users": "utentes ativos",
   "server_banner.administered_by": "Amministradu dae:",
@@ -680,7 +709,20 @@
   "upload_button.label": "Agiunghe immàgines, unu vìdeu o unu documentu sonoru",
   "upload_error.limit": "Lìmite de càrriga de archìvios barigadu.",
   "upload_error.poll": "Non si permitit s'imbiu de archìvios in is sondàgios.",
+  "upload_form.audio_description": "Descritzione pro persones cun pèrdida auditiva",
+  "upload_form.description": "Descritzione pro persones cun pèrdida visuale",
   "upload_form.edit": "Modìfica",
+  "upload_form.thumbnail": "Càmbia sa miniadura",
+  "upload_form.video_description": "Descritzione pro persones cun pèrdida auditiva o visuale",
+  "upload_modal.analyzing_picture": "Analizende immàgine…",
+  "upload_modal.apply": "Àplica",
+  "upload_modal.choose_image": "Sèbera un'immàgine",
+  "upload_modal.description_placeholder": "Su margiane castàngiu brincat lestru a subra de su cane mandrone",
+  "upload_modal.detect_text": "Rileva testu de s'immàgine",
+  "upload_modal.edit_media": "Modìfica elementu multimediale",
+  "upload_modal.hint": "Incarca o traga su tzìrculu in sa previsualizatzione pro seberare su puntu focale chi at a èssere semper visìbile in totu is miniaduras.",
+  "upload_modal.preparing_ocr": "Ammaniende s'OCR…",
+  "upload_modal.preview_label": "Previsualiza ({ratio})",
   "upload_progress.label": "Carrighende...",
   "video.close": "Serra su vìdeu",
   "video.download": "Iscàrriga archìviu",
@@ -688,6 +730,8 @@
   "video.expand": "Ismànnia su vìdeu",
   "video.fullscreen": "Ischermu in mannària prena",
   "video.hide": "Cua vìdeu",
+  "video.mute": "A sa muda",
   "video.pause": "Pàusa",
-  "video.play": "Reprodue"
+  "video.play": "Reprodue",
+  "video.unmute": "Ativa sonu"
 }
diff --git a/app/javascript/mastodon/locales/sco.json b/app/javascript/mastodon/locales/sco.json
index 7a7f926d5a..7a04c214ae 100644
--- a/app/javascript/mastodon/locales/sco.json
+++ b/app/javascript/mastodon/locales/sco.json
@@ -1,7 +1,7 @@
 {
   "about.blocks": "Moderatit servers",
   "about.contact": "Contack:",
-  "about.disclaimer": "Join the Fediverse, become part of a community, and break free from Big Tech™'s stranglehold on public discourse.",
+  "about.disclaimer": "Mastodon is free, open-soorced saftware, an a trademairk o Mastodon gGmbH.",
   "about.domain_blocks.no_reason_available": "Raison no available",
   "about.domain_blocks.preamble": "On the hail, Mastodon lats ye view content frae an interack wi uisers fae onie ither server in the fediverse.",
   "about.domain_blocks.silenced.explanation": "Ye'll generally no see profiles an content frae this server, unless ye explicitly luik it up or opt intae it bi follaein.",
@@ -25,6 +25,7 @@
   "account.endorse": "Shaw oan profile",
   "account.featured_tags.last_status_at": "Last post oan {date}",
   "account.featured_tags.last_status_never": "Nae posts",
+  "account.featured_tags.title": "{name}'s hielichtit hashtags",
   "account.follow": "Follae",
   "account.followers": "Follaers",
   "account.followers.empty": "Naebody follaes this uiser yit.",
@@ -84,6 +85,7 @@
   "bundle_column_error.routing.body": "The requestit page cuidnae be fun. Are ye shair thit the URL in the addres baur is richt?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Shut",
+  "bundle_modal_error.message": "Somehin went wrang whilst loadin this component.",
   "bundle_modal_error.retry": "Gie it anither shot",
   "closed_registrations.other_server_instructions": "Seein Mastodon is decentralized ye kin mak a accoont on anither server an stull interact wi this ane.",
   "closed_registrations_modal.description": "Makkin a accoont on {domain} isnae possible the noo, but mind ye dinnae need a accoont on {domain} specific for tae uise Mastodon.",
@@ -161,6 +163,8 @@
   "disabled_account_banner.text": "Yer accoont {disabledAccount} is disabilt the noo.",
   "dismissable_banner.community_timeline": "Here the maist recent public posts fae fowk thit's accoonts is hostit bi {domain}.",
   "dismissable_banner.dismiss": "Pit awa",
+  "dismissable_banner.explore_links": "Thir news stories is bein talked aboot bi fowk on this an ither servers o the decentralized netwirk richt noo.",
+  "dismissable_banner.explore_tags": "Thir hashtags is gaitherin traction amang the fowk on thit an ither servers o the decentralized netwirk richt noo.",
   "embed.instructions": "Embed this post on yer wabsteid bi copyin the code ablow.",
   "embed.preview": "Here whit it'll luik lik:",
   "emoji_button.activity": "Activity",
@@ -190,6 +194,7 @@
   "empty_column.hashtag": "There naethin in this hashtag yit.",
   "empty_column.home": "Yer hame timeline is toum! Follae mair fowk fir tae full it up.",
   "empty_column.list": "There naethin in this list yit. Whan memmers o this list publish new posts, ye'll see them here.",
+  "empty_column.lists": "Ye dinnae hae onie lists yit. Ance ye mak ane, it'll shaw up here.",
   "empty_column.mutes": "Ye'v no wheesht onie uisers yit.",
   "empty_column.notifications": "Ye dinnae hae onie notes yit. Whan ither fowk interacks wi ye, ye'll see it here.",
   "empty_column.public": "There naethin here! Scrieve socht public, or follae uisers fae ither servers fir tae full it up",
@@ -199,6 +204,7 @@
   "error.unexpected_crash.next_steps_addons": "Try oot pittin them aff an rejiggin the page. Gin thon disnae wirk, ye kin mibbie uise Mastodon on a different brooser or native app.",
   "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace tae yer clipboord",
   "errors.unexpected_crash.report_issue": "Sen in a issue",
+  "explore.search_results": "Seirch finnins",
   "explore.title": "Splore",
   "filter_modal.added.context_mismatch_explanation": "This filter caitegory disnae apply tae the context thit ye'v uised tae access this post. Gin ye'r wantin the post tae be filtert in this context tae, ye'll hae tae edit the filter.",
   "filter_modal.added.context_mismatch_title": "Context disnae match!",
@@ -222,6 +228,7 @@
   "footer.about": "Aboot",
   "footer.directory": "Profiles directory",
   "footer.get_app": "Get the app",
+  "footer.invite": "Invite fowk",
   "footer.keyboard_shortcuts": "Keyboord shortcuts",
   "footer.privacy_policy": "Privacy policy",
   "footer.source_code": "View the soorce code",
@@ -242,6 +249,9 @@
   "home.column_settings.show_replies": "Shaw replies",
   "home.hide_announcements": "Hide annooncements",
   "home.show_announcements": "Shaw annooncements",
+  "interaction_modal.description.follow": "Wi a accoont on Mastodon, ye kin follae {name} tae get their posts on yer hame feed.",
+  "interaction_modal.description.reblog": "Wi a accoont on Mastodon, ye kin heeze this post tae ahare it wi yer ain follaers.",
+  "interaction_modal.description.reply": "Wi a accoont on Mastodon, ye kin sen a repone tae this post.",
   "interaction_modal.on_another_server": "On a different server",
   "interaction_modal.on_this_server": "On this server",
   "interaction_modal.title.follow": "Follae {name}",
@@ -287,11 +297,19 @@
   "lightbox.previous": "Last ane",
   "limited_account_hint.action": "Shaw profile onieweys",
   "limited_account_hint.title": "This profile haes been planked bi the moderators o {domain}.",
+  "lists.account.add": "Add tae list",
+  "lists.account.remove": "Tak aff o the list",
   "lists.delete": "Delete list",
   "lists.edit": "Edit list",
+  "lists.edit.submit": "Chynge title",
+  "lists.new.create": "Add list",
+  "lists.new.title_placeholder": "New list title",
   "lists.replies_policy.followed": "Onie follaed uiser",
   "lists.replies_policy.list": "Memmers o the list",
   "lists.replies_policy.none": "Naebody",
+  "lists.replies_policy.title": "Shaw replies tae:",
+  "lists.search": "Seirch amang the fowk ye ken",
+  "lists.subheading": "Yer lists",
   "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "moved_to_account_banner.text": "Yer accoont {disabledAccount} is disabilt the noo acause ye flittit tae {movedToAccount}.",
   "navigation_bar.about": "Aboot",
@@ -355,6 +373,19 @@
   "notifications_permission_banner.enable": "Turn on desktap notes",
   "notifications_permission_banner.how_to_control": "Fir tae get notes whan Mastodon isnae open, turn on desktap notes. Ye kin pick exactly whit types o interactions gie ye desktap notes throu the {icon} button abuin ance they'r turnt on.",
   "notifications_permission_banner.title": "Dinnae miss a hing",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "Pit it back",
   "poll.closed": "Shut",
   "poll.refresh": "Refresh",
@@ -370,6 +401,8 @@
   "privacy_policy.last_updated": "Last updatit {date}",
   "privacy_policy.title": "Privacy Policy",
   "refresh": "Refresh",
+  "regeneration_indicator.label": "Loadin…",
+  "regeneration_indicator.sublabel": "Yer hame feed is gettin sortit fir ye!",
   "relative_time.days": "{number}t",
   "relative_time.full.days": "{number, plural, one {# day} other {# days}} syne",
   "relative_time.full.hours": "{number, plural, one {# oor} other {# oors}} syne",
@@ -428,7 +461,9 @@
   "search.search_or_paste": "Seirch or paste URL",
   "search_results.all": "Aw",
   "search_results.hashtags": "Hashtags",
+  "search_results.nothing_found": "Cuidnae fin ocht fir thir seirch terms",
   "search_results.statuses": "Posts",
+  "search_results.title": "Seirch fir {q}",
   "server_banner.about_active_users": "Fowk uisin this server in the last 30 days (Monthly Active Uisers)",
   "server_banner.active_users": "active uisers",
   "server_banner.administered_by": "Administert bi:",
@@ -499,7 +534,21 @@
   "upload_button.label": "Add images, a video or a audio file",
   "upload_error.limit": "File upload limit exceedit.",
   "upload_error.poll": "File upload isnae allooed wi polls.",
+  "upload_form.audio_description": "Describe fir fowk wi hearin loss",
+  "upload_form.description": "Describe fir thaim wi visual impairments",
   "upload_form.edit": "Edit",
+  "upload_form.thumbnail": "Chynge thoomnail",
+  "upload_form.video_description": "Describe fir fowk wi hearin loss or wi visual impairment",
+  "upload_modal.analyzing_picture": "Analyzin picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.applying": "Applyin…",
+  "upload_modal.choose_image": "Pick image",
+  "upload_modal.description_placeholder": "A quick broon fox jamps ower the lazy dug",
+  "upload_modal.detect_text": "Detect text fae picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview tae pick the focal pynt thit wull aye be in view on aw thoomnails.",
+  "upload_modal.preparing_ocr": "Preparin OCR…",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Uploadin...",
   "upload_progress.processing": "Processin…",
   "video.close": "Shut video",
@@ -508,6 +557,8 @@
   "video.expand": "Expand video",
   "video.fullscreen": "Fu-screen",
   "video.hide": "Plank video",
+  "video.mute": "Wheesht soond",
   "video.pause": "Pause",
-  "video.play": "Pley"
+  "video.play": "Pley",
+  "video.unmute": "Unwheesht soond"
 }
diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json
index 7d909dbe1a..93ce9dd7e2 100644
--- a/app/javascript/mastodon/locales/si.json
+++ b/app/javascript/mastodon/locales/si.json
@@ -62,6 +62,7 @@
   "bundle_column_error.return": "ආපසු මුලට යන්න",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "වසන්න",
+  "bundle_modal_error.message": "මෙම සංරචකය පූරණයේ දී යම් දෙයක් වැරදී ඇත.",
   "bundle_modal_error.retry": "නැවත උත්සාහ කරන්න",
   "closed_registrations_modal.find_another_server": "වෙනත් සේවාදායක",
   "closed_registrations_modal.title": "මාස්ටඩන් හි ලියාපදිංචි වන්න",
@@ -165,6 +166,7 @@
   "empty_column.favourited_statuses": "ඔබ සතුව ප්‍රියතම ලිපි කිසිවක් නැත. ඔබ යමකට ප්‍රිය කළ විට එය මෙහි පෙන්වනු ඇත.",
   "empty_column.follow_requests": "ඔබට තවමත් අනුගමන ඉල්ලීම් ලැබී නැත. ඉල්ලීමක් ලැබුණු විට, එය මෙහි පෙන්වනු ඇත.",
   "empty_column.home": "මුල් පිටුව හිස් ය! මෙය පිරවීමට බොහෝ පුද්ගලයින් අනුගමනය කරන්න.",
+  "empty_column.lists": "ඔබට තවමත් ලැයිස්තු කිසිවක් නැත. ඔබ එකක් සාදන විට, එය මෙහි පෙන්වනු ඇත.",
   "empty_column.mutes": "ඔබ තවමත් කිසිදු පරිශීලකයෙකු නිහඬ කර නැත.",
   "empty_column.notifications": "ඔබට දැනුම්දීම් ලැබී නැත. අන් අය සහ ඔබ අතර අන්‍යෝන්‍ය බලපවත්වන දෑ මෙහි දිස්වනු ඇත.",
   "error.unexpected_crash.explanation": "අපගේ කේතයේ දෝෂයක් හෝ බ්‍රවුසර ගැළපුම් ගැටලුවක් හේතුවෙන්, මෙම පිටුව නිවැරදිව ප්‍රදර්ශනය කළ නොහැක.",
@@ -172,6 +174,7 @@
   "error.unexpected_crash.next_steps": "පිටුව නැවුම් කර බලන්න. එයින් ඵලක් නොවේ නම්, වෙනත් අතිරික්සුවක් හෝ නිසග යෙදුමක් හරහා මාස්ටඩන් භාවිතා කරන්න.",
   "error.unexpected_crash.next_steps_addons": "ඒවා අබල කර පිටුව නැවුම් කරන්න. එයින් ඵලක් නොවේ නම්, වෙනත් අතිරික්සුවක් හෝ නිසග යෙදුමක් හරහා මාස්ටඩන් භාවිතා කරන්න.",
   "errors.unexpected_crash.report_issue": "ගැටළුව වාර්තාව",
+  "explore.search_results": "සෙවුම් ප්‍රතිඵල",
   "explore.suggested_follows": "පුද්ගලයින්",
   "explore.title": "ගවේශනය",
   "explore.trending_links": "පුවත්",
@@ -195,6 +198,7 @@
   "footer.about": "පිළිබඳව",
   "footer.directory": "පැතිකඩ නාමාවලිය",
   "footer.get_app": "යෙදුම ගන්න",
+  "footer.invite": "ආරාධනා කරන්න",
   "footer.keyboard_shortcuts": "යතුරුපුවරුවේ කෙටිමං",
   "footer.privacy_policy": "රහස්‍යතා ප්‍රතිපත්තිය",
   "footer.source_code": "මූලාශ්‍ර කේතය බලන්න",
@@ -211,6 +215,7 @@
   "home.hide_announcements": "නිවේදන සඟවන්න",
   "home.pending_critical_update.link": "යාවත්කාල බලන්න",
   "home.show_announcements": "නිවේදන පෙන්වන්න",
+  "interaction_modal.login.action": "මුලට ගෙනයන්න",
   "interaction_modal.on_another_server": "වෙනත් සේවාදායකයක",
   "interaction_modal.on_this_server": "මෙම සේවාදායකයෙහි",
   "interaction_modal.title.favourite": "{name}ගේ ලිපිය ප්‍රිය කරන්න",
@@ -245,10 +250,17 @@
   "lightbox.next": "ඊළඟ",
   "lightbox.previous": "පෙර",
   "limited_account_hint.action": "කෙසේ හෝ පැතිකඩ පෙන්වන්න",
+  "lists.account.add": "ලැයිස්තුවට දමන්න",
+  "lists.account.remove": "ලැයිස්තුවෙන් ඉවතලන්න",
   "lists.delete": "ලැයිස්තුව මකන්න",
   "lists.edit": "ලැයිස්තුව සංස්කරණය",
+  "lists.edit.submit": "සිරැසිය සංශෝධනය",
+  "lists.new.create": "එකතු",
+  "lists.new.title_placeholder": "නව ලැයිස්තුවේ සිරැසිය",
   "lists.replies_policy.list": "ලැයිස්තුවේ සාමාජිකයින්",
   "lists.replies_policy.none": "කිසිවෙක් නැත",
+  "lists.replies_policy.title": "පිළිතුරු පෙන්වන්න:",
+  "lists.subheading": "ඔබගේ ලැයිස්තු",
   "navigation_bar.about": "පිළිබඳව",
   "navigation_bar.blocks": "අවහිර කළ අය",
   "navigation_bar.bookmarks": "පොත්යොමු",
@@ -302,6 +314,13 @@
   "notifications.mark_as_read": "සියළු දැනුම්දීම් කියවූ බව යොදන්න",
   "notifications_permission_banner.enable": "වැඩතල දැනුම්දීම් සබල කරන්න",
   "notifications_permission_banner.title": "කිසිවක් අතපසු නොකරන්න",
+  "onboarding.actions.go_to_explore": "නැගී එන දෑ වෙත ගෙනයන්න",
+  "onboarding.compose.template": "ආයුබෝ #මාස්ටඩන්!",
+  "onboarding.share.title": "ඔබගේ පැතිකඩ බෙදාගන්න",
+  "onboarding.steps.publish_status.title": "පළමු ලිපිය පළ කරන්න",
+  "onboarding.steps.setup_profile.title": "ඔබගේ පැතිකඩ අභිරුචිකරණය",
+  "onboarding.steps.share_profile.body": "මාස්ටඩන් හි ඔබව සොයා ගන්නේ කෙසේදැයි යහළුවන්ට දන්වන්න",
+  "onboarding.steps.share_profile.title": "ඔබගේ පැතිකඩ බෙදාගන්න",
   "poll.closed": "වසා ඇත",
   "poll.refresh": "නැවුම් කරන්න",
   "poll.reveal": "ප්‍රතිඵල බලන්න",
@@ -313,6 +332,7 @@
   "privacy.public.short": "ප්‍රසිද්ධ",
   "privacy_policy.title": "රහස්‍යතා ප්‍රතිපත්තිය",
   "refresh": "නැවුම් කරන්න",
+  "regeneration_indicator.label": "පූරණය වෙමින්…",
   "relative_time.days": "ද. {number}",
   "relative_time.full.days": "{number, plural, one {දවස් #} other {දවස් #}} කට පෙර",
   "relative_time.full.hours": "{number, plural, one {පැය #} other {පැය #}} කට පෙර",
@@ -380,8 +400,10 @@
   "search_popout.user": "පරිශ්‍රීලකයා",
   "search_results.accounts": "පැතිකඩ",
   "search_results.all": "සියල්ල",
+  "search_results.nothing_found": "මෙම සෙවුම් පද සඳහා කිසිවක් සොයාගත නොහැකි විය",
   "search_results.see_all": "සියල්ල බලන්න",
   "search_results.statuses": "ලිපි",
+  "search_results.title": "{q} සොයන්න",
   "server_banner.active_users": "සක්‍රිය පරිශ්‍රීලකයින්",
   "sign_in_banner.create_account": "ගිණුමක් සාදන්න",
   "sign_in_banner.sign_in": "පිවිසෙන්න",
@@ -435,7 +457,20 @@
   "upload_button.label": "රූප, දෘශ්‍යක හෝ හඬපට අමුණන්න",
   "upload_error.limit": "සීමාව ඉක්මවා ඇත.",
   "upload_error.poll": "මත විමසුම් සමඟ ගොනු යෙදීමට ඉඩ නොදේ.",
+  "upload_form.audio_description": "නොඇසෙන අය සඳහා විස්තර කරන්න",
+  "upload_form.description": "දෘශ්‍යාබාධිතයන් සඳහා විස්තර කරන්න",
   "upload_form.edit": "සංස්කරණය",
+  "upload_form.thumbnail": "සිඟිති රුව වෙනස් කරන්න",
+  "upload_form.video_description": "ශ්‍රවණාබාධ හෝ දෘශ්‍යාබාධිත පුද්ගලයන් සඳහා විස්තර කරන්න",
+  "upload_modal.analyzing_picture": "පින්තූරය විශ්ලේෂණය කරමින්…",
+  "upload_modal.apply": "යොදන්න",
+  "upload_modal.applying": "යොදමින්…",
+  "upload_modal.choose_image": "රූපයක් තෝරන්න",
+  "upload_modal.description_placeholder": "කඩිසර දුඹුරු හිවලෙක් කම්මැලි බල්ලා මතින් පනී",
+  "upload_modal.detect_text": "රූපයෙහි පෙළ අනාවරණය",
+  "upload_modal.edit_media": "මාධ්‍ය සංස්කරණය",
+  "upload_modal.preparing_ocr": "OCR සඳහා සැරසෙමින්…",
+  "upload_modal.preview_label": "පෙරදසුන ({ratio})",
   "upload_progress.label": "උඩුගත වෙමින්...",
   "upload_progress.processing": "සැකසෙමින්…",
   "username.taken": "නම දැනටමත් අරගෙන ඇත",
@@ -445,6 +480,8 @@
   "video.expand": "දෘශ්‍යකය විහිදන්න",
   "video.fullscreen": "පූර්ණ තිරය",
   "video.hide": "දෘශ්‍යකය සඟවන්න",
+  "video.mute": "ශබ්දය නිහඬ",
   "video.pause": "විරාමය",
-  "video.play": "ධාවනය"
+  "video.play": "ධාවනය",
+  "video.unmute": "ශබ්දය නොනිහඬ"
 }
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 6cbf799328..396daeee75 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -27,9 +27,9 @@
   "account.edit_profile": "Upraviť profil",
   "account.enable_notifications": "Zapnúť upozornenia na príspevky od @{name}",
   "account.endorse": "Zobraziť na vlastnom profile",
-  "account.featured.posts": "Príspevky",
   "account.featured_tags.last_status_at": "Posledný príspevok dňa {date}",
   "account.featured_tags.last_status_never": "Žiadne príspevky",
+  "account.featured_tags.title": "Odporúčané hashtagy účtu {name}",
   "account.follow": "Sledovať",
   "account.follow_back": "Sledovať späť",
   "account.followers": "Sledovatelia",
@@ -65,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} príspevok} other {{counter} príspevkov}}",
   "account.unblock": "Odblokovať @{name}",
   "account.unblock_domain": "Odblokovať doménu {domain}",
-  "account.unblock_domain_short": "Odblokovať",
   "account.unblock_short": "Odblokovať",
   "account.unendorse": "Nezobrazovať na vlastnom profile",
   "account.unfollow": "Zrušiť sledovanie",
@@ -87,23 +86,7 @@
   "alert.unexpected.message": "Vyskytla sa nečakaná chyba.",
   "alert.unexpected.title": "Ups!",
   "alt_text_badge.title": "Alternatívny popis",
-  "alt_text_modal.add_text_from_image": "Pridaj text z obrázka",
-  "alt_text_modal.cancel": "Zrušiť",
-  "alt_text_modal.done": "Hotovo",
   "announcement.announcement": "Oznámenie",
-  "annual_report.summary.archetype.oracle": "Veštec",
-  "annual_report.summary.followers.followers": "sledovatelia",
-  "annual_report.summary.followers.total": "{count} celkovo",
-  "annual_report.summary.here_it_is": "Tu je tvoja {year} v zhrnutí:",
-  "annual_report.summary.highlighted_post.by_favourites": "najviac obľúbený príspevok",
-  "annual_report.summary.highlighted_post.by_reblogs": "najviac vyzdvihovaný príspevok",
-  "annual_report.summary.highlighted_post.by_replies": "príspevok s najviac odpoveďami",
-  "annual_report.summary.most_used_app.most_used_app": "najviac používaná aplikácia",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "najviac užívaný hashtag",
-  "annual_report.summary.most_used_hashtag.none": "Žiaden",
-  "annual_report.summary.new_posts.new_posts": "nové príspevky",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Nepovieme Berniemu.",
-  "annual_report.summary.thanks": "Vďaka, že si súčasťou Mastodonu!",
   "attachments_list.unprocessed": "(nespracované)",
   "audio.hide": "Skryť zvuk",
   "block_modal.show_less": "Zobraziť menej",
@@ -125,6 +108,7 @@
   "bundle_column_error.routing.body": "Žiadaná stránka nebola nájdená. Ste si istí, že zadaná adresa URL je správna?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Zatvoriť",
+  "bundle_modal_error.message": "Pri načítavaní tohto komponentu nastala chyba.",
   "bundle_modal_error.retry": "Skúsiť znova",
   "closed_registrations.other_server_instructions": "Keďže Mastodon je decentralizovaný, môžete si vytvoriť účet na inom serveri a stále komunikovať s týmto serverom.",
   "closed_registrations_modal.description": "Vytvorenie účtu na {domain} nie je v súčasnosti možné, ale myslite na to, že na používanie Mastodonu nepotrebujete účet práve na {domain}.",
@@ -135,16 +119,13 @@
   "column.blocks": "Blokované účty",
   "column.bookmarks": "Záložky",
   "column.community": "Miestna časová os",
-  "column.create_list": "Vytvor zoznam",
   "column.direct": "Súkromné označenia",
   "column.directory": "Prehľadávať profily",
   "column.domain_blocks": "Blokované domény",
-  "column.edit_list": "Uprav zoznam",
   "column.favourites": "Obľúbené",
   "column.firehose": "Živé kanály",
   "column.follow_requests": "Žiadosti o sledovanie",
   "column.home": "Domov",
-  "column.list_members": "Spravuj členov zoznamu",
   "column.lists": "Zoznamy",
   "column.mutes": "Stíšené účty",
   "column.notifications": "Upozornenia",
@@ -157,7 +138,6 @@
   "column_header.pin": "Pripnúť",
   "column_header.show_settings": "Zobraziť nastavenia",
   "column_header.unpin": "Odopnúť",
-  "column_search.cancel": "Zruš",
   "column_subheading.settings": "Nastavenia",
   "community.column_settings.local_only": "Iba miestne",
   "community.column_settings.media_only": "Iba médiá",
@@ -176,7 +156,7 @@
   "compose_form.poll.duration": "Trvanie ankety",
   "compose_form.poll.multiple": "Viacero možností",
   "compose_form.poll.option_placeholder": "Možnosť {number}",
-  "compose_form.poll.single": "Jediná voľba",
+  "compose_form.poll.single": "Jediný výber",
   "compose_form.poll.switch_to_multiple": "Zmeniť anketu a povoliť viaceré možnosti",
   "compose_form.poll.switch_to_single": "Zmeniť anketu na jediný povolený výber",
   "compose_form.poll.type": "Typ",
@@ -200,13 +180,9 @@
   "confirmations.edit.confirm": "Upraviť",
   "confirmations.edit.message": "Úpravou prepíšete príspevok, ktorý máte rozpísaný. Určite chcete pokračovať?",
   "confirmations.edit.title": "Prepísať príspevok?",
-  "confirmations.follow_to_list.confirm": "Nasleduj a pridaj do zoznamu",
-  "confirmations.follow_to_list.message": "Musíš nasledovať {name} aby si ho/ju mohol/la pridať do zoznamu.",
-  "confirmations.follow_to_list.title": "Nasleduj užívateľa?",
   "confirmations.logout.confirm": "Odhlásiť sa",
   "confirmations.logout.message": "Určite sa chcete odhlásiť?",
   "confirmations.logout.title": "Odhlásiť sa?",
-  "confirmations.missing_alt_text.secondary": "Odošli aj tak",
   "confirmations.mute.confirm": "Stíšiť",
   "confirmations.redraft.confirm": "Vymazať a prepísať",
   "confirmations.redraft.message": "Určite chcete tento príspevok vymazať a prepísať? Prídete o jeho zdieľania a ohviezdičkovania a odpovede na pôvodný príspevok budú odlúčené.",
@@ -235,6 +211,10 @@
   "disabled_account_banner.text": "Váš účet {disabledAccount} je momentálne deaktivovaný.",
   "dismissable_banner.community_timeline": "Toto sú najnovšie verejné príspevky od účtov hostených na {domain}.",
   "dismissable_banner.dismiss": "Zrušiť",
+  "dismissable_banner.explore_links": "Toto sú správy zo sociálnej siete, ktoré sú dnes populárne. Novšie správy s viacerými ohviezdičkovaniami a zdieľaniami sú radené vyššie.",
+  "dismissable_banner.explore_statuses": "Toto sú príspevky z celej sociálnej siete, ktoré sú dnes populárne. Novšie príspevky s viacerými ohviezdičkovaniami a zdieľaniami sú radené vyššie.",
+  "dismissable_banner.explore_tags": "Toto sú hashtagy zo sociálnej siete, ktoré sú dnes populárne. Novšie hashtagy používané viacerými ľuďmi sú radené vyššie.",
+  "dismissable_banner.public_timeline": "Toto sú najnovšie verejné príspevky od účtov na sociálnej sieti, ktoré sú sledované účtami z {domain}.",
   "domain_block_modal.block": "Blokovať server",
   "domain_block_modal.block_account_instead": "Namiesto toho zablokuj @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Ľudia z tohto servera môžu interaktovať s tvojimi starými príspevkami.",
@@ -264,7 +244,6 @@
   "emoji_button.search_results": "Výsledky hľadania",
   "emoji_button.symbols": "Symboly",
   "emoji_button.travel": "Cestovanie a miesta",
-  "empty_column.account_featured": "Tento zoznam je prázdny",
   "empty_column.account_hides_collections": "Tento účet sa rozhodol túto informáciu nesprístupniť",
   "empty_column.account_suspended": "Účet bol pozastavený",
   "empty_column.account_timeline": "Nie sú tu žiadne príspevky.",
@@ -282,6 +261,7 @@
   "empty_column.hashtag": "Pod týmto hashtagom sa ešte nič nenachádza.",
   "empty_column.home": "Vaša domáca časová os je zatiaľ prázdna. Začnite sledovať ostatných a naplňte si ju.",
   "empty_column.list": "Tento zoznam je zatiaľ prázdny. Keď ale členovia tohoto zoznamu uverejnia nové príspevky, objavia sa tu.",
+  "empty_column.lists": "Zatiaľ nemáte žiadne zoznamy. Keď nejaký vytvoríte, zobrazí sa tu.",
   "empty_column.mutes": "Zatiaľ ste si nikoho nestíšili.",
   "empty_column.notification_requests": "Všetko čisté! Nič tu nieje. Keď dostaneš nové oboznámenia, zobrazia sa tu podľa tvojich nastavení.",
   "empty_column.notifications": "Zatiaľ nemáte žiadne upozornenia. Začnú vám pribúdať, keď s vami začnú interagovať ostatní.",
@@ -292,6 +272,7 @@
   "error.unexpected_crash.next_steps_addons": "Skúste ich vypnúť a stránku obnoviť. Ak to nepomôže, pravdepodobne budete stále môcť Mastodon používať cez iný prehliadač alebo natívnu aplikáciu.",
   "errors.unexpected_crash.copy_stacktrace": "Kopírovať stacktrace do schránky",
   "errors.unexpected_crash.report_issue": "Nahlásiť problém",
+  "explore.search_results": "Výsledky hľadania",
   "explore.suggested_follows": "Ľudia",
   "explore.title": "Objavovať",
   "explore.trending_links": "Správy",
@@ -339,15 +320,13 @@
   "footer.about": "Viac informácií",
   "footer.directory": "Adresár profilov",
   "footer.get_app": "Stiahnuť aplikáciu",
+  "footer.invite": "Pozvať ľudí",
   "footer.keyboard_shortcuts": "Klávesové skratky",
   "footer.privacy_policy": "Pravidlá ochrany súkromia",
   "footer.source_code": "Zobraziť zdrojový kód",
   "footer.status": "Stav",
-  "footer.terms_of_service": "Podmienky prevozu",
   "generic.saved": "Uložené",
   "getting_started.heading": "Začíname",
-  "hashtag.admin_moderation": "Otvor moderovacie rozhranie pre #{name}",
-  "hashtag.browse": "Prehľadávať príspevky pod #{hashtag}",
   "hashtag.column_header.tag_mode.all": "a {additional}",
   "hashtag.column_header.tag_mode.any": "alebo {additional}",
   "hashtag.column_header.tag_mode.none": "bez {additional}",
@@ -361,7 +340,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} príspevok} few {{counter} príspevky} many {{counter} príspevkov} other {{counter} príspevkov}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} príspevok} few {{counter} príspevky} many {{counter} príspevkov} other {{counter} príspevkov}} dnes",
   "hashtag.follow": "Sledovať hashtag",
-  "hashtag.mute": "Utlmiť #{hashtag}",
   "hashtag.unfollow": "Prestať sledovať hashtag",
   "hashtags.and_other": "…a {count, plural, other {# ďalších}}",
   "hints.profiles.followers_may_be_missing": "Nasledovatelia tohto profilu môžu chýbať.",
@@ -388,21 +366,21 @@
   "ignore_notifications_modal.not_followers_title": "Nevšímať si oznámenia od ľudí, ktorí ťa nenasledujú?",
   "ignore_notifications_modal.not_following_title": "Nevšímať si oznámenia od ľudí, ktorých nenasleduješ?",
   "ignore_notifications_modal.private_mentions_title": "Nevšímať si oznámenia o nevyžiadaných súkromných spomínaniach?",
-  "info_button.label": "Pomoc",
-  "interaction_modal.action.favourite": "Pre pokračovanie si musíš obľúbiť zo svojho účtu.",
-  "interaction_modal.action.follow": "Pre pokračovanie musíš nasledovať zo svojho účtu.",
-  "interaction_modal.action.reply": "Pre pokračovanie musíš odpovedať s tvojho účtu.",
-  "interaction_modal.action.vote": "Pre pokračovanie musíš hlasovať s tvojho účtu.",
-  "interaction_modal.go": "Prejdi",
-  "interaction_modal.no_account_yet": "Ešte nemáš účet?",
+  "interaction_modal.description.favourite": "S účtom na Mastodone môžete tento príspevok ohviezdičkovať, tak dať autorovi vedieť, že sa vám páči, a uložiť si ho na neskôr.",
+  "interaction_modal.description.follow": "S účtom na Mastodone môžete {name} sledovať a vidieť ich príspevky vo svojom domovskom kanáli.",
+  "interaction_modal.description.reblog": "S účtom na Mastodone môžete tento príspevok zdeľať so svojimi sledovateľmi.",
+  "interaction_modal.description.reply": "S účtom na Mastodone môžete na tento príspevok odpovedať.",
+  "interaction_modal.login.action": "Prejsť domov",
+  "interaction_modal.login.prompt": "Doména vášho domovského servera, napr. mastodon.social",
+  "interaction_modal.no_account_yet": "Nie ste na Mastodone?",
   "interaction_modal.on_another_server": "Na inom serveri",
   "interaction_modal.on_this_server": "Na tomto serveri",
+  "interaction_modal.sign_in": "Na tomto serveri nie ste prihlásený. Kde je váš účet hostený?",
+  "interaction_modal.sign_in_hint": "Tip: Toto je webová stránka, na ktorej ste sa zaregistrovali. Ak si nepamätáte, pohľadajte uvítací e-mail vo svojej schránke. Môžete tiež zadať svoje celé používateľské meno (napr. @Mastodon@mastodon.social).",
   "interaction_modal.title.favourite": "Ohviezdičkovať príspevok od {name}",
   "interaction_modal.title.follow": "Sledovať {name}",
   "interaction_modal.title.reblog": "Zdieľať príspevok od {name}",
   "interaction_modal.title.reply": "Odpovedať na príspevok od {name}",
-  "interaction_modal.title.vote": "Hlasuj v ankete od {name}",
-  "interaction_modal.username_prompt": "Napr. {example}",
   "intervals.full.days": "{number, plural, one {# deň} few {# dni} many {# dní} other {# dní}}",
   "intervals.full.hours": "{number, plural, one {# hodina} few {# hodiny} many {# hodín} other {# hodín}}",
   "intervals.full.minutes": "{number, plural, one {# minúta} few {# minúty} many {# minút} other {# minút}}",
@@ -438,7 +416,6 @@
   "keyboard_shortcuts.toggle_hidden": "Zobraziť/skryť text za varovaním o obsahu",
   "keyboard_shortcuts.toggle_sensitivity": "Zobraziť/skryť médiá",
   "keyboard_shortcuts.toot": "Vytvoriť nový príspevok",
-  "keyboard_shortcuts.translate": "preložiť príspevok",
   "keyboard_shortcuts.unfocus": "Odísť z textového poľa",
   "keyboard_shortcuts.up": "Posunúť sa vyššie v zozname",
   "lightbox.close": "Zatvoriť",
@@ -450,31 +427,20 @@
   "link_preview.author": "Autor: {name}",
   "link_preview.more_from_author": "Viac od {name}",
   "link_preview.shares": "{count, plural, one {{counter} príspevok} other {{counter} príspevkov}}",
-  "lists.add_member": "Pridaj",
-  "lists.add_to_list": "Pridaj do zoznamu",
-  "lists.add_to_lists": "Pridaj {name} do zoznamov",
-  "lists.create": "Vytvor",
-  "lists.create_a_list_to_organize": "Vytvor nový zoznam pre spravovanie tvojej domovskej osi",
-  "lists.create_list": "Vytvor zoznam",
+  "lists.account.add": "Pridať do zoznamu",
+  "lists.account.remove": "Odstrániť zo zoznamu",
   "lists.delete": "Vymazať zoznam",
-  "lists.done": "Hotovo",
   "lists.edit": "Upraviť zoznam",
-  "lists.exclusive": "Skryť členov na Domovskej osi",
-  "lists.exclusive_hint": "Ak je niekto na tomto zozname, skry ich na tvojej Domácej osi, aby si ich príspevky nevidel/a dvakrát.",
-  "lists.find_users_to_add": "Nájdi užívateľov na pridanie",
-  "lists.list_members": "Členovia zoznamu",
-  "lists.list_name": "Názov zoznamu",
-  "lists.new_list_name": "Názov nového zoznamu",
-  "lists.no_lists_yet": "Ešte žiadne zoznamy.",
-  "lists.no_members_yet": "Zatiaľ bez členov.",
-  "lists.no_results_found": "Žiadne výsledky nenájdené.",
-  "lists.remove_member": "Odstráň",
+  "lists.edit.submit": "Zmeniť názov",
+  "lists.exclusive": "Skryť tieto príspevky z domovskej stránky",
+  "lists.new.create": "Pridať zoznam",
+  "lists.new.title_placeholder": "Názov nového zoznamu",
   "lists.replies_policy.followed": "Akémukoľvek sledovanému účtu",
   "lists.replies_policy.list": "Členom zoznamu",
   "lists.replies_policy.none": "Nikomu",
-  "lists.save": "Ulož",
-  "lists.search": "Hľadaj",
-  "lists.show_replies_to": "Zahrnúť odpovede od členov zoznamu na",
+  "lists.replies_policy.title": "Zobraziť odpovede:",
+  "lists.search": "Vyhľadávať medzi účtami, ktoré sledujete",
+  "lists.subheading": "Vaše zoznamy",
   "load_pending": "{count, plural, one {# nová položka} few {# nové položky} many {# nových položiek} other {# nových položiek}}",
   "loading_indicator.label": "Načítavanie…",
   "media_gallery.hide": "Skryť",
@@ -521,7 +487,6 @@
   "notification.admin.report_statuses_other": "{name} nahlásil/a {target}",
   "notification.admin.sign_up": "Nová registráciu účtu {name}",
   "notification.favourite": "{name} hviezdičkuje váš príspevok",
-  "notification.favourite_pm": "{name} obľúbil/a tvoje súkromné spomenutie",
   "notification.follow": "{name} vás sleduje",
   "notification.follow_request": "{name} vás žiada sledovať",
   "notification.label.mention": "Zmienka",
@@ -604,21 +569,44 @@
   "notifications_permission_banner.enable": "Povoliť upozornenia na ploche",
   "notifications_permission_banner.how_to_control": "Ak chcete dostávať upozornenia, keď Mastodon nie je otvorený, povoľte upozornenia na ploche. Po ich zapnutí môžete presne kontrolovať, ktoré typy interakcií generujú upozornenia na ploche, a to prostredníctvom tlačidla {icon} vyššie.",
   "notifications_permission_banner.title": "Nenechajte si nič ujsť",
-  "onboarding.follows.back": "Späť",
-  "onboarding.follows.done": "Hotovo",
+  "onboarding.action.back": "Ísť späť",
+  "onboarding.actions.back": "Ísť späť",
+  "onboarding.actions.go_to_explore": "Prejsť na populárne",
+  "onboarding.actions.go_to_home": "Prejsť na domovský kanál",
+  "onboarding.compose.template": "Ahoj, #Mastodon!",
   "onboarding.follows.empty": "Žiaľ, momentálne sa nedajú zobraziť žiadne výsledky. Môžete skúsiť použiť vyhľadávanie alebo navštíviť stránku objavovania a nájsť ľudí, ktorých chcete sledovať, alebo to skúste znova neskôr.",
-  "onboarding.follows.search": "Hľadať",
-  "onboarding.follows.title": "Pre začiatok nasleduj ľudí",
+  "onboarding.follows.lead": "Váš domovský kanál je váš hlavný spôsob objavovania Mastodonu. Čím viac ľudí sledujete, tým bude aktívnejší a zaujímavejší. Tu je pár tipov na začiatok:",
+  "onboarding.follows.title": "Prispôsob si svoj domovský kanál",
   "onboarding.profile.discoverable": "Nastavte svoj profil ako objaviteľný",
   "onboarding.profile.discoverable_hint": "Keď si na Mastodone zapnete objaviteľnosť, vaše príspevky sa môžu zobrazovať vo výsledkoch vyhľadávania a v populárnych. Váš profil môže byť navyše navrhovaný ľuďom, s ktorými máte podobné záujmy.",
   "onboarding.profile.display_name": "Používateľské meno",
   "onboarding.profile.display_name_hint": "Vaše celé meno alebo pokojne aj vtipná prezývka…",
+  "onboarding.profile.lead": "Vždy si to môžete doplniť neskôr v nastaveniach, kde nájdete aj ďalšie možnosti prispôsobenia.",
   "onboarding.profile.note": "Niečo o vás",
   "onboarding.profile.note_hint": "Môžete @označiť iných ľudí alebo #hashtagy…",
   "onboarding.profile.save_and_continue": "Uložiť a pokračovať",
   "onboarding.profile.title": "Nastavenie profilu",
   "onboarding.profile.upload_avatar": "Nahrať profilový obrázok",
   "onboarding.profile.upload_header": "Nahrať obrázok záhlavia profilu",
+  "onboarding.share.lead": "Dajte ostatným vedieť, ako vás môžu na Mastodone nájsť.",
+  "onboarding.share.message": "Na #Mastodon⁠e som {username}. Príď ma sledovať na {url}!",
+  "onboarding.share.next_steps": "Ďalšie možné kroky:",
+  "onboarding.share.title": "Zdieľajte svoj profil",
+  "onboarding.start.lead": "Teraz ste súčasťou Mastodonu, jedinečnej decentralizovanej sociálnej platformy, kde o všetkom rozhodujete vy, nie algoritmus. Poďme sa pozrieť, ako môžete začať:",
+  "onboarding.start.skip": "Nepotrebujete pomoc so začiatkom?",
+  "onboarding.start.title": "Zvládli ste to!",
+  "onboarding.steps.follow_people.body": "Mastodon je vybudovaný okolo sledovania zaujímavých ľudí.",
+  "onboarding.steps.follow_people.title": "Prispôsobte si svoj domovský kanál",
+  "onboarding.steps.publish_status.body": "Predstavte sa svetu textom, fotkami, videami či anketami {emoji}",
+  "onboarding.steps.publish_status.title": "Vytvorte svoj prvý príspevok",
+  "onboarding.steps.setup_profile.body": "Plnší profil vám pomôže mať viac interakcií.",
+  "onboarding.steps.setup_profile.title": "Upravte si profil",
+  "onboarding.steps.share_profile.body": "Dajte svojej partii vedieť, ako vás môžu na Mastodone nájsť.",
+  "onboarding.steps.share_profile.title": "Zdieľajte svoj profil na Mastodone",
+  "onboarding.tips.2fa": "<strong>Vedeli ste?</strong> Svoj účet môžete zabezpečiť nastavením dvojfaktorového overenia v nastaveniach účtu. Funguje to s akoukoľvek aplikáciou TOTP podľa vášho výberu, nie je potrebné žiadne telefónne číslo!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Vedeli ste?</strong> Keďže Mastodon je decentralizovaný, niektoré profily, s ktorými sa stretnete, budú na iných serveroch, ako je váš. Aj napriek tomu s nimi môžete bezproblémovo komunikovať! Ich server je v druhej časti ich používateľského mena!",
+  "onboarding.tips.migration": "<strong>Vedeli ste?</strong> Ak máte pocit, že doména {domain} pre vás v budúcnosti nebude skvelou voľbou, môžete prejsť na iný server Mastodon bez straty svojich sledovateľov. Môžete dokonca hostiť svoj vlastný server!",
+  "onboarding.tips.verification": "<strong>Vedeli ste?</strong> Svoj účet môžete overiť umiestnením odkazu na svoj profil na Mastodone na svoju vlastnú webovú lokalitu a pridaním webovej lokality do svojho profilu. Nie sú potrebné žiadne poplatky ani doklady!",
   "password_confirmation.exceeds_maxlength": "Potvrdené heslo presahuje maximálnu dĺžku hesla",
   "password_confirmation.mismatching": "Zadané heslá sa nezhodujú",
   "picture_in_picture.restore": "Vrátiť späť",
@@ -634,7 +622,7 @@
   "poll_button.remove_poll": "Odstrániť anketu",
   "privacy.change": "Zmeniť nastavenia súkromia príspevku",
   "privacy.direct.long": "Všetci spomenutí v príspevku",
-  "privacy.direct.short": "Spomenutie v súkromí",
+  "privacy.direct.short": "Konkrétni ľudia",
   "privacy.private.long": "Iba vaši sledovatelia",
   "privacy.private.short": "Sledovatelia",
   "privacy.public.long": "Ktokoľvek na Mastodone aj mimo neho",
@@ -646,8 +634,8 @@
   "privacy_policy.title": "Pravidlá ochrany súkromia",
   "recommended": "Odporúčané",
   "refresh": "Obnoviť",
-  "regeneration_indicator.please_stand_by": "Prosím, čakajte.",
-  "regeneration_indicator.preparing_your_home_feed": "Pripravuje sa tvoj domáci kanál…",
+  "regeneration_indicator.label": "Načítavanie…",
+  "regeneration_indicator.sublabel": "Tvoj domovský kanál sa pripravuje!",
   "relative_time.days": "{number} dní",
   "relative_time.full.days": "Pred {number, plural, one {# dňom} other {# dňami}}",
   "relative_time.full.hours": "Pred {number, plural, one {# hodinou} other {# hodinami}}",
@@ -730,11 +718,10 @@
   "search_results.accounts": "Profily",
   "search_results.all": "Všetky",
   "search_results.hashtags": "Hashtagy",
-  "search_results.no_results": "Žiadne výsledky.",
-  "search_results.no_search_yet": "Skús vyhľadávať príspevky, profily, alebo hashtagy.",
+  "search_results.nothing_found": "Pre tieto výrazy nebolo možné nič nájsť",
   "search_results.see_all": "Zobraziť všetky",
   "search_results.statuses": "Príspevky",
-  "search_results.title": "Hľadaj \"{q}\"",
+  "search_results.title": "Hľadať {q}",
   "server_banner.about_active_users": "Ľudia používajúci tento server za posledných 30 dní (aktívni používatelia za mesiac)",
   "server_banner.active_users": "Aktívne účty",
   "server_banner.administered_by": "Správa servera:",
@@ -782,7 +769,6 @@
   "status.reblogs.empty": "Nikto ešte tento príspevok nezdieľal. Keď tak niekto urobí, zobrazí sa to tu.",
   "status.redraft": "Vymazať a prepísať",
   "status.remove_bookmark": "Odstrániť záložku",
-  "status.remove_favourite": "Odstráň z obľúbených",
   "status.replied_in_thread": "Odpovedal/a vo vlákne",
   "status.replied_to": "Odpoveď na {name}",
   "status.reply": "Odpovedať",
@@ -804,7 +790,6 @@
   "subscribed_languages.target": "Zmeniť prihlásené jazyky pre {target}",
   "tabs_bar.home": "Domov",
   "tabs_bar.notifications": "Upozornenia",
-  "terms_of_service.title": "Podmienky prevozu",
   "time_remaining.days": "Ostáva{number, plural, one { # deň} few {jú # dni} many { # dní} other { # dní}}",
   "time_remaining.hours": "Ostáva{number, plural, one { # hodina} few {jú # hodiny} many { # hodín} other { # hodín}}",
   "time_remaining.minutes": "Ostáva{number, plural, one { # minúta} few {jú # minúty} many { # minút} other { # minút}}",
@@ -820,7 +805,21 @@
   "upload_button.label": "Pridať obrázky, video alebo zvukový súbor",
   "upload_error.limit": "Limit pre nahrávanie súborov bol prekročený.",
   "upload_error.poll": "Nahrávanie súborov nie je pri anketách možné.",
+  "upload_form.audio_description": "Popis pre sluchovo postihnutých ľudí",
+  "upload_form.description": "Popis pre zrakovo postihnutých ľudí",
   "upload_form.edit": "Upraviť",
+  "upload_form.thumbnail": "Zmeniť miniatúru",
+  "upload_form.video_description": "Popís pre ľudí so zrakovým alebo sluchovým postihnutím",
+  "upload_modal.analyzing_picture": "Prebieha analýza obrázka…",
+  "upload_modal.apply": "Použiť",
+  "upload_modal.applying": "Ukladanie…",
+  "upload_modal.choose_image": "Vybrať obrázok",
+  "upload_modal.description_placeholder": "Kde bolo, tam bolo, bol raz jeden Mastodon",
+  "upload_modal.detect_text": "Rozpoznať text z obrázka",
+  "upload_modal.edit_media": "Upraviť médiá",
+  "upload_modal.hint": "Kliknite alebo potiahnite kruh na ukážke, a tak vyberte bod, ktorý bude viditeľný na všetkých náhľadoch.",
+  "upload_modal.preparing_ocr": "Pripravujem rozpoznávanie…",
+  "upload_modal.preview_label": "Náhľad ({ratio})",
   "upload_progress.label": "Nahráva sa…",
   "upload_progress.processing": "Spracovávanie…",
   "username.taken": "Používateľské meno je obsadené. Skúste iné",
@@ -830,9 +829,8 @@
   "video.expand": "Zväčšiť video",
   "video.fullscreen": "Zobraziť na celú obrazovku",
   "video.hide": "Skryť video",
-  "video.mute": "Stíšiť",
+  "video.mute": "Stlmiť zvuk",
   "video.pause": "Pozastaviť",
   "video.play": "Prehrať",
-  "video.volume_down": "Hlasitosť nadol",
-  "video.volume_up": "Hlasitosť nahor"
+  "video.unmute": "Zapnúť zvuk"
 }
diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json
index eef20456de..8e4358122f 100644
--- a/app/javascript/mastodon/locales/sl.json
+++ b/app/javascript/mastodon/locales/sl.json
@@ -1,10 +1,10 @@
 {
   "about.blocks": "Moderirani strežniki",
   "about.contact": "Stik:",
-  "about.disclaimer": "Mastodon je prosto, odprtokodno programje in blagovna znamka podjetja Mastodon gGmbH.",
+  "about.disclaimer": "Mastodon je prosto, odprto-kodno programje in blagovna znamka Mastodon gGmbH.",
   "about.domain_blocks.no_reason_available": "Razlog ni na voljo",
-  "about.domain_blocks.preamble": "Mastodon vam na splošno omogoča ogled vsebin in interakcijo z uporabniki z vseh drugih strežnikov v fediverzumu. Tu so navedene izjeme, ki jih postavlja ta strežnik.",
-  "about.domain_blocks.silenced.explanation": "V splošnem ne boste videli profilov in vsebin s tega strežnika, razen če jih izrecno poiščete ali jim začnete slediti.",
+  "about.domain_blocks.preamble": "Mastodon vam splošno omogoča ogled vsebin in interakcijo z uporabniki iz vseh drugih strežnikov v fediverzumu. To so izjeme, opravljene na tem strežniku.",
+  "about.domain_blocks.silenced.explanation": "V splošnem ne boste videli profilov in vsebin s tega strežnika, če jih eksplicino ne poiščete ali nanje naročite s sledenjem.",
   "about.domain_blocks.silenced.title": "Omejeno",
   "about.domain_blocks.suspended.explanation": "Nobeni podatki s tega strežnika ne bodo obdelani, shranjeni ali izmenjani, zaradi česar je nemogoča kakršna koli interakcija ali komunikacija z uporabniki s tega strežnika.",
   "about.domain_blocks.suspended.title": "Suspendiran",
@@ -29,10 +29,11 @@
   "account.endorse": "Izpostavi v profilu",
   "account.featured_tags.last_status_at": "Zadnja objava {date}",
   "account.featured_tags.last_status_never": "Ni objav",
+  "account.featured_tags.title": "Izpostavljeni ključniki {name}",
   "account.follow": "Sledi",
   "account.follow_back": "Sledi nazaj",
   "account.followers": "Sledilci",
-  "account.followers.empty": "Nihče še ne sledi temu uporabniku.",
+  "account.followers.empty": "Nihče ne sledi temu uporabniku.",
   "account.followers_counter": "{count, plural, one {{counter} sledilec} two {{counter} sledilca} few {{counter} sledilci} other {{counter} sledilcev}}",
   "account.following": "Sledim",
   "account.following_counter": "{count, plural, one {{counter} sleden} two {{counter} sledena} few {{counter} sledeni} other {{counter} sledenih}}",
@@ -44,9 +45,9 @@
   "account.languages": "Spremeni naročene jezike",
   "account.link_verified_on": "Lastništvo te povezave je bilo preverjeno {date}",
   "account.locked_info": "Stanje zasebnosti računa je nastavljeno na zaklenjeno. Lastnik ročno pregleda, kdo ga lahko spremlja.",
-  "account.media": "Predstavnosti",
+  "account.media": "Mediji",
   "account.mention": "Omeni @{name}",
-  "account.moved_to": "{name} sporoča, da ima zdaj nov račun:",
+  "account.moved_to": "{name} nakazuje, da ima zdaj nov račun:",
   "account.mute": "Utišaj @{name}",
   "account.mute_notifications_short": "Utišaj obvestila",
   "account.mute_short": "Utišaj",
@@ -67,14 +68,14 @@
   "account.unblock_short": "Odblokiraj",
   "account.unendorse": "Ne vključi v profil",
   "account.unfollow": "Ne sledi več",
-  "account.unmute": "Povrni glas @{name}",
+  "account.unmute": "Odtišaj @{name}",
   "account.unmute_notifications_short": "Izklopi utišanje obvestil",
-  "account.unmute_short": "Povrni glas",
-  "account_note.placeholder": "Kliknite, da dodate opombo",
+  "account.unmute_short": "Odtišaj",
+  "account_note.placeholder": "Kliknite za dodajanje opombe",
   "admin.dashboard.daily_retention": "Mera ohranjanja uporabnikov po dnevih od registracije",
   "admin.dashboard.monthly_retention": "Mera ohranjanja uporabnikov po mesecih od registracije",
   "admin.dashboard.retention.average": "Povprečje",
-  "admin.dashboard.retention.cohort": "Mesec registracije",
+  "admin.dashboard.retention.cohort": "Mesec prijave",
   "admin.dashboard.retention.cohort_size": "Novi uporabniki",
   "admin.impact_report.instance_accounts": "Profili računov, ki bi jih s tem izbrisali",
   "admin.impact_report.instance_followers": "Sledilci, ki bi jih izgubili naši uporabniki",
@@ -85,77 +86,48 @@
   "alert.unexpected.message": "Zgodila se je nepričakovana napaka.",
   "alert.unexpected.title": "Ojoj!",
   "alt_text_badge.title": "Nadomestno besedilo",
-  "alt_text_modal.add_alt_text": "Dodaj nadomestno besedilo",
-  "alt_text_modal.add_text_from_image": "Dodaj besedilo iz slike",
-  "alt_text_modal.cancel": "Prekliči",
-  "alt_text_modal.change_thumbnail": "Spremeni sličico",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Podaj opis za ljudi s težavami s sluhom ...",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Podaj opis za slabovidne ...",
-  "alt_text_modal.done": "Opravljeno",
-  "announcement.announcement": "Oznanilo",
-  "annual_report.summary.archetype.booster": "Lovec na trende",
-  "annual_report.summary.archetype.lurker": "Tiholazec",
-  "annual_report.summary.archetype.oracle": "Orakelj",
-  "annual_report.summary.archetype.pollster": "Anketar",
-  "annual_report.summary.archetype.replier": "Priljudnež",
-  "annual_report.summary.followers.followers": "sledilcev",
-  "annual_report.summary.followers.total": "skupaj {count}",
-  "annual_report.summary.here_it_is": "Tule je povzetek vašega leta {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "- najpriljubljenejša objava",
-  "annual_report.summary.highlighted_post.by_reblogs": "- največkrat izpostavljena objava",
-  "annual_report.summary.highlighted_post.by_replies": "- objava z največ odgovori",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "najpogosteje uporabljena aplikacija",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "največkrat uporabljen ključnik",
-  "annual_report.summary.most_used_hashtag.none": "Brez",
-  "annual_report.summary.new_posts.new_posts": "nove objave",
-  "annual_report.summary.percentile.text": "<topLabel>S tem ste se uvrstili med zgornjih </topLabel><percentage></percentage><bottomLabel> uporabnikov domene {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Živi duši ne bomo povedali.",
-  "annual_report.summary.thanks": "Hvala, ker ste del Mastodona!",
+  "announcement.announcement": "Obvestilo",
   "attachments_list.unprocessed": "(neobdelano)",
   "audio.hide": "Skrij zvok",
-  "block_modal.remote_users_caveat": "Strežnik {domain} bomo pozvali, naj spoštuje vašo odločitev. Kljub temu pa ni gotovo, da bo strežnik prošnjo upošteval, saj nekateri strežniki blokiranja obravnavajo drugače. Javne objave bodo morda še vedno vidne neprijavljenim uporabnikom.",
+  "block_modal.remote_users_caveat": "Od strežnika {domain} bomo zahtevali, da spoštuje vašo odločitev. Izpolnjevanje zahteve ni zagotovljeno, ker nekateri strežniki blokiranja obravnavajo drugače. Javne objave bodo morda še vedno vidne neprijavljenim uporabnikom.",
   "block_modal.show_less": "Pokaži manj",
   "block_modal.show_more": "Pokaži več",
-  "block_modal.they_cant_mention": "Ne more vas omenjati ali vam slediti.",
-  "block_modal.they_cant_see_posts": "Ne vidi vaših objav, vi pa ne njegovih.",
-  "block_modal.they_will_know": "Ne more videti, da je blokiran.",
-  "block_modal.title": "Blokiram uporabnika?",
-  "block_modal.you_wont_see_mentions": "Objav, ki ga omenjajo, ne boste videli.",
-  "boost_modal.combo": "Če želite naslednjič to preskočiti, lahko pritisnete {combo}",
-  "boost_modal.reblog": "Izpostavim objavo?",
+  "block_modal.they_cant_mention": "Ne morejo vas omenjati ali vam slediti.",
+  "block_modal.they_cant_see_posts": "Ne vidijo vaših objav, vi pa ne njihovih.",
+  "block_modal.they_will_know": "Ne morejo videti, da so blokirani.",
+  "block_modal.title": "Blokiraj uporabnika?",
+  "block_modal.you_wont_see_mentions": "Objav, ki jih omenjajo, ne boste videli.",
+  "boost_modal.combo": "Če želite preskočiti to, lahko pritisnete {combo}",
+  "boost_modal.reblog": "Izpostavi objavo?",
   "boost_modal.undo_reblog": "Ali želite preklicati izpostavitev objave?",
   "bundle_column_error.copy_stacktrace": "Kopiraj poročilo o napaki",
   "bundle_column_error.error.body": "Zahtevane strani ni mogoče upodobiti. Vzrok težave je morda hrošč v naši kodi ali pa nezdružljivost z brskalnikom.",
   "bundle_column_error.error.title": "Oh, ne!",
   "bundle_column_error.network.body": "Pri poskusu nalaganja te strani je prišlo do napake. Vzrok je lahko začasna težava z vašo internetno povezavo ali s tem strežnikom.",
-  "bundle_column_error.network.title": "Omrežna napaka",
+  "bundle_column_error.network.title": "Napaka v omrežju",
   "bundle_column_error.retry": "Poskusi znova",
   "bundle_column_error.return": "Nazaj domov",
   "bundle_column_error.routing.body": "Zahtevane strani ni mogoče najti. Ali ste prepričani, da je naslov URL v naslovni vrstici pravilen?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Zapri",
-  "bundle_modal_error.message": "Med nalaganjem prikaza je prišlo do napake.",
+  "bundle_modal_error.message": "Med nalaganjem te komponente je prišlo do napake.",
   "bundle_modal_error.retry": "Poskusi znova",
   "closed_registrations.other_server_instructions": "Ker je Mastodon decentraliziran, lahko ustvarite račun na drugem strežniku in ste še vedno v interakciji s tem.",
-  "closed_registrations_modal.description": "Odpiranje računa na domeni {domain} trenutno ni možno, upoštevajte pa, da ne potrebujete računa prav na domeni {domain}, da bi uporabljali Mastodon.",
+  "closed_registrations_modal.description": "Odpiranje računa na {domain} trenutno ni možno, upoštevajte pa, da ne potrebujete računa prav na {domain}, da bi uporabljali Mastodon.",
   "closed_registrations_modal.find_another_server": "Najdi drug strežnik",
-  "closed_registrations_modal.preamble": "Mastodon je decentraliziran, kar pomeni, da ni pomembno, kje ustvarite svoj račun; od koder koli je mogoče slediti in komunicirati z vsemi s tega strežnika. Strežnik lahko gostite tudi sami!",
+  "closed_registrations_modal.preamble": "Mastodon je decentraliziran, kar pomeni, da ni pomembno, kje ustvarite svoj račun; od koder koli je omogočeno sledenje in interakcija z vsemi s tega strežnika. Strežnik lahko gostite tudi sami!",
   "closed_registrations_modal.title": "Registracija v Mastodon",
   "column.about": "O programu",
   "column.blocks": "Blokirani uporabniki",
   "column.bookmarks": "Zaznamki",
   "column.community": "Krajevna časovnica",
-  "column.create_list": "Ustvari seznam",
   "column.direct": "Zasebne omembe",
   "column.directory": "Prebrskaj profile",
   "column.domain_blocks": "Blokirane domene",
-  "column.edit_list": "Uredi seznam",
   "column.favourites": "Priljubljeni",
   "column.firehose": "Viri v živo",
-  "column.follow_requests": "Prošnje za sledenje",
+  "column.follow_requests": "Sledi prošnjam",
   "column.home": "Domov",
-  "column.list_members": "Upravljaj člane seznama",
   "column.lists": "Seznami",
   "column.mutes": "Utišani uporabniki",
   "column.notifications": "Obvestila",
@@ -168,28 +140,27 @@
   "column_header.pin": "Pripni",
   "column_header.show_settings": "Pokaži nastavitve",
   "column_header.unpin": "Odpni",
-  "column_search.cancel": "Prekliči",
   "column_subheading.settings": "Nastavitve",
   "community.column_settings.local_only": "Samo krajevno",
-  "community.column_settings.media_only": "Samo predstavnosti",
+  "community.column_settings.media_only": "Samo mediji",
   "community.column_settings.remote_only": "Samo oddaljeno",
   "compose.language.change": "Spremeni jezik",
-  "compose.language.search": "Poišči jezike ...",
+  "compose.language.search": "Poišči jezik ...",
   "compose.published.body": "Objavljeno.",
   "compose.published.open": "Odpri",
   "compose.saved.body": "Objava shranjena.",
-  "compose_form.direct_message_warning_learn_more": "Več o tem",
-  "compose_form.encryption_warning": "Objave na Mastodonu niso šifrirane od konca do konca. Prek Mastodona ne delite nobenih občutljivih informacij.",
+  "compose_form.direct_message_warning_learn_more": "Izvej več",
+  "compose_form.encryption_warning": "Objave na Mastodonu niso šifrirane od kraja do kraja. Prek Mastodona ne delite nobenih občutljivih informacij.",
   "compose_form.hashtag_warning": "Ta objava ne bo navedena pod nobenim ključnikom, ker ni javna. Samo javne objave lahko iščete s ključniki.",
   "compose_form.lock_disclaimer": "Vaš račun ni {locked}. Vsakdo vam lahko sledi in si ogleda objave, ki so namenjene samo sledilcem.",
   "compose_form.lock_disclaimer.lock": "zaklenjen",
   "compose_form.placeholder": "O čem razmišljate?",
   "compose_form.poll.duration": "Trajanje ankete",
-  "compose_form.poll.multiple": "Izbira več možnosti",
+  "compose_form.poll.multiple": "Več možnosti",
   "compose_form.poll.option_placeholder": "Možnost {number}",
-  "compose_form.poll.single": "Izbira ene možnosti",
-  "compose_form.poll.switch_to_multiple": "Spremenite anketo, da omogočite izbiro več možnosti",
-  "compose_form.poll.switch_to_single": "Spremenite anketo, da omogočite izbiro ene možnosti",
+  "compose_form.poll.single": "Izberite eno možnost",
+  "compose_form.poll.switch_to_multiple": "Spremenite anketo, da omogočite več izbir",
+  "compose_form.poll.switch_to_single": "Spremenite anketo, da omogočite eno izbiro",
   "compose_form.poll.type": "Slog",
   "compose_form.publish": "Objavi",
   "compose_form.publish_form": "Objavi",
@@ -207,24 +178,17 @@
   "confirmations.delete_list.message": "Ali ste prepričani, da želite trajno izbrisati ta seznam?",
   "confirmations.delete_list.title": "Želite izbrisati seznam?",
   "confirmations.discard_edit_media.confirm": "Opusti",
-  "confirmations.discard_edit_media.message": "Spremenjenega opisa predstavnosti ali predogleda niste shranili. Želite spremembe kljub temu opustiti?",
+  "confirmations.discard_edit_media.message": "Imate ne shranjene spremembe za medijski opis ali predogled; jih želite kljub temu opustiti?",
   "confirmations.edit.confirm": "Uredi",
   "confirmations.edit.message": "Urejanje bo prepisalo sporočilo, ki ga trenutno sestavljate. Ali ste prepričani, da želite nadaljevati?",
   "confirmations.edit.title": "Želite prepisati objavo?",
-  "confirmations.follow_to_list.confirm": "Sledi in dodaj na seznam",
-  "confirmations.follow_to_list.message": "Osebi {name} morate slediti, preden jo lahko dodate na seznam.",
-  "confirmations.follow_to_list.title": "Naj sledim uporabniku?",
   "confirmations.logout.confirm": "Odjava",
   "confirmations.logout.message": "Ali ste prepričani, da se želite odjaviti?",
   "confirmations.logout.title": "Se želite odjaviti?",
-  "confirmations.missing_alt_text.confirm": "Dodaj nadomestno besedilo",
-  "confirmations.missing_alt_text.message": "Vaša objava vsebuje predstavnosti brez nadomestnega besedila. Če jih dodatno opišete, bodo dostopne večji množici ljudi.",
-  "confirmations.missing_alt_text.secondary": "Vseeno objavi",
-  "confirmations.missing_alt_text.title": "Dodam nadomestno besedilo?",
-  "confirmations.mute.confirm": "Utišaj",
+  "confirmations.mute.confirm": "Utišanje",
   "confirmations.redraft.confirm": "Izbriši in preoblikuj",
-  "confirmations.redraft.message": "Ali ste prepričani, da želite izbrisati to objavo in jo preoblikovati? Izkazi priljubljenosti in izpostavitve bodo izgubljeni, odgovori na izvirno objavo pa bodo osiroteli.",
-  "confirmations.redraft.title": "Želite izbrisati in preoblikovati objavo?",
+  "confirmations.redraft.message": "Ali ste prepričani, da želite izbrisati ta status in ga preoblikovati? Vzljubi in izpostavitve bodo izgubljeni, odgovori na izvirno objavo pa bodo osiroteli.",
+  "confirmations.redraft.title": "Želite izbrisati in predelati objavo?",
   "confirmations.reply.confirm": "Odgovori",
   "confirmations.reply.message": "Odgovarjanje bo prepisalo sporočilo, ki ga trenutno sestavljate. Ali ste prepričani, da želite nadaljevati?",
   "confirmations.reply.title": "Želite prepisati objavo?",
@@ -240,7 +204,7 @@
   "conversation.with": "Z {names}",
   "copy_icon_button.copied": "Kopirano v odložišče",
   "copypaste.copied": "Kopirano",
-  "copypaste.copy_to_clipboard": "Kopiraj v odložišče",
+  "copypaste.copy_to_clipboard": "Kopiraj na odložišče",
   "directory.federated": "Iz znanega fediverzuma",
   "directory.local": "Samo iz {domain}",
   "directory.new_arrivals": "Novi prišleki",
@@ -249,34 +213,32 @@
   "disabled_account_banner.text": "Vaš račun {disabledAccount} je trenutno onemogočen.",
   "dismissable_banner.community_timeline": "To so najnovejše javne objave oseb, katerih računi gostujejo na {domain}.",
   "dismissable_banner.dismiss": "Opusti",
-  "dismissable_banner.explore_links": "Danes po fediverzumu najbolj odmevajo te novice. Višje na seznamu so novejše vesti bolj raznolikih objaviteljev.",
-  "dismissable_banner.explore_statuses": "Danes so po fediverzumu pozornost pritegnile te objave. Višje na seznamu so novejše, bolj izpostavljene in bolj priljubljene objave.",
-  "dismissable_banner.explore_tags": "Danes se po fediverzumu najpogosteje uporabljajo ti ključniki. Višje na seznamu so ključniki, ki jih uporablja več različnih ljudi.",
-  "dismissable_banner.public_timeline": "To so najnovejše javne objave ljudi s fediverzuma, ki jim sledijo ljudje na domeni {domain}.",
+  "dismissable_banner.explore_links": "O teh novicah ravno zdaj veliko govorijo osebe na tem in drugih strežnikih decentraliziranega omrežja.",
+  "dismissable_banner.explore_statuses": "Te objave s tega in drugih strežnikov v decentraliziranem omrežju pridobivajo ravno zdaj veliko pozornosti na tem strežniku.",
+  "dismissable_banner.explore_tags": "Ravno zdaj dobivajo ti ključniki veliko pozoronosti med osebami na tem in drugih strežnikih decentraliziranega omrežja.",
+  "dismissable_banner.public_timeline": "To so najnovejše javne objave oseb z družabnega omrežja, ki jim sledijo osebe na {domain}.",
   "domain_block_modal.block": "Blokiraj strežnik",
   "domain_block_modal.block_account_instead": "Namesto tega blokiraj @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Osebe s tega strežnika se lahko odzivajo na vaše stare objave.",
   "domain_block_modal.they_cant_follow": "Nihče s tega strežnika vam ne more slediti.",
   "domain_block_modal.they_wont_know": "Ne bodo vedeli, da so blokirani.",
   "domain_block_modal.title": "Blokiraj domeno?",
-  "domain_block_modal.you_will_lose_num_followers": "Izgubili boste {followersCount, plural, one {{followersCountDisplay} sledilca} two {{followersCountDisplay} sledilca} few {{followersCountDisplay} sledilce} other {{followersCountDisplay} sledilcev}} in {followingCount, plural, one {{followingCountDisplay} osebo, ki ji sledite} two {{followingCountDisplay} osebi, ki jima sledite} few {{followingCountDisplay} osebe, ki jim sledite} other {{followingCountDisplay} oseb, ki jim sledite}}.",
-  "domain_block_modal.you_will_lose_relationships": "Izgubili boste vse sledilce in ljudi, ki jim sledite na tem strežniku.",
   "domain_block_modal.you_wont_see_posts": "Objav ali obvestil uporabnikov s tega strežnika ne boste videli.",
-  "domain_pill.activitypub_lets_connect": "Omogoča vam povezovanje in interakcijo z ljudmi, ki niso samo na Mastodonu, ampak tudi na drugih družbenih platformah.",
-  "domain_pill.activitypub_like_language": "Protokol ActivityPub je kot jezik, v katerem se Mastodon pogovarja z drugimi družabnimi omrežji.",
+  "domain_pill.activitypub_lets_connect": "Omogoča vam povezovanje in interakcijo z ljudmi, ki niso samo na Mastodonu, ampak tudi na drugih družabnih platformah.",
+  "domain_pill.activitypub_like_language": "Protokol ActivityPub je kot jezik, s katerim se Mastodon pogovarja z drugimi družabnimi omrežji.",
   "domain_pill.server": "Strežnik",
-  "domain_pill.their_handle": "Njegova/njena ročica:",
-  "domain_pill.their_server": "Njegovo/njeno digitalno domovanje, kjer bivajo vse njegove/njene objave.",
-  "domain_pill.their_username": "Njegov/njen edinstveni identifikator na njegovem/njenem strežniku. Uporabnike z istim uporabniškim imenom lahko najdete na različnih strežnikih.",
+  "domain_pill.their_handle": "Njihova ročica:",
+  "domain_pill.their_server": "Njihovo digitalno domovanje, kjer bivajo vse njihove objave.",
+  "domain_pill.their_username": "Njihov edinstveni identifikator na njihovem strežniku. Uporabnike z istim uporabniškim imenom lahko najdete na različnih strežnikih.",
   "domain_pill.username": "Uporabniško ime",
   "domain_pill.whats_in_a_handle": "Kaj je v ročici?",
-  "domain_pill.who_they_are": "Ker ročice povedo, kdo je kdo in kje je, lahko komunicirate z ljudmi po vsem spletu družbenih <button>platform, ki jih poganja ActivityPub</button>.",
-  "domain_pill.who_you_are": "Ker ročice povedo, kdo ste in kje ste, lahko komunicirate z ljudmi po vsem spletu družbenih <button>platform, ki jih poganja ActivityPub</button>.",
+  "domain_pill.who_they_are": "Ker ročice povedo, kdo je kdo in kje so, ste lahko z osebami v interakciji prek družabnega spleta <button>platform, ki jih poganja ActivityPub</button>.",
+  "domain_pill.who_you_are": "Ker ročice povedo, kdo ste in kje ste, ste lahko z osebami v interakciji prek družabnega spleta <button>platform, ki jih poganja ActivityPub</button>.",
   "domain_pill.your_handle": "Vaša ročica:",
-  "domain_pill.your_server": "Vaše digitalno domovanje, kjer bivajo vse vaše objave. Vam ni všeč? Kadar koli ga prenesite med strežniki in z njim tudi svoje sledilce.",
+  "domain_pill.your_server": "Vaše digitalno domovanje, kjer bivajo vse vaše objave. Vam ta ni všeč? Prenesite ga med strežniki kadar koli in z njim tudi svoje sledilce.",
   "domain_pill.your_username": "Vaš edinstveni identifikator na tem strežniku. Uporabnike z istim uporabniškim imenom je možno najti na različnih strežnikih.",
   "embed.instructions": "Vstavite to objavo na svojo spletno stran tako, da kopirate spodnjo kodo.",
-  "embed.preview": "Takole bo videti:",
+  "embed.preview": "Tako bo izgledalo:",
   "emoji_button.activity": "Dejavnost",
   "emoji_button.clear": "Počisti",
   "emoji_button.custom": "Po meri",
@@ -292,33 +254,35 @@
   "emoji_button.search_results": "Rezultati iskanja",
   "emoji_button.symbols": "Simboli",
   "emoji_button.travel": "Potovanja in kraji",
-  "empty_column.account_hides_collections": "Ta uporabnik se je odločil, da te informacije ne bo delil",
+  "empty_column.account_hides_collections": "Ta uporabnik se je odločil, da te informacije ne bo dal na voljo",
   "empty_column.account_suspended": "Račun je suspendiran",
   "empty_column.account_timeline": "Tukaj ni objav!",
   "empty_column.account_unavailable": "Profil ni na voljo",
   "empty_column.blocks": "Niste še blokirali nobenega uporabnika.",
   "empty_column.bookmarked_statuses": "Zaenkrat še nimate zaznamovanih objav. Ko objavo zaznamujete, se pojavi tukaj.",
-  "empty_column.community": "Krajevna časovnica je prazna. Napišite nekaj javnega, da se začne polniti!",
+  "empty_column.community": "Krajevna časovnica je prazna. Napišite nekaj javnega, da se bo snežna kepa zakotalila!",
   "empty_column.direct": "Nimate še nobenih zasebnih omemb. Ko jih boste poslali ali prejeli, se bodo prikazale tukaj.",
   "empty_column.domain_blocks": "Zaenkrat ni blokiranih domen.",
-  "empty_column.explore_statuses": "Trenutno ni novih trendov. Preverite znova kasneje!",
-  "empty_column.favourited_statuses": "Nimate priljubljenih objav. Ko boste kakšno dodali med priljubljene, bo prikazana tukaj.",
-  "empty_column.favourites": "Nihče še ni vzljubil te objave. Ko jo bo nekdo, bo naveden tukaj.",
+  "empty_column.explore_statuses": "Trenutno ni nič v trendu. Preverite znova kasneje!",
+  "empty_column.favourited_statuses": "Nimate priljubljenih objav. Ko boste vzljubili kakšno, bo prikazana tukaj.",
+  "empty_column.favourites": "Nihče še ni vzljubil te objave. Ko jo bo nekdo, se bo pojavila tukaj.",
   "empty_column.follow_requests": "Nimate prošenj za sledenje. Ko boste prejeli kakšno, se bo prikazala tukaj.",
-  "empty_column.followed_tags": "Zaenkrat ne sledite še nobenemu ključniku. Ko boste, se bo pojavil tukaj.",
-  "empty_column.hashtag": "V tem ključniku ni še nič.",
-  "empty_column.home": "Vaša domača časovnica je prazna! Sledite več osebam, da jo zapolnite.",
-  "empty_column.list": "Na tem seznamu ni ničesar. Ko bodo člani tega seznama kaj objavili, se bodo te objave pojavile tukaj.",
+  "empty_column.followed_tags": "Zaenkrat ne sledite še nobenemu ključniku. Ko boste, se bodo pojavili tukaj.",
+  "empty_column.hashtag": "V tem ključniku še ni nič.",
+  "empty_column.home": "Vaša domača časovnica je prazna! Sledite več osebam, da jo zapolnite. {suggestions}",
+  "empty_column.list": "Na tem seznamu ni ničesar. Ko bodo člani tega seznama objavili nove statuse, se bodo pojavili tukaj.",
+  "empty_column.lists": "Nimate seznamov. Ko ga boste ustvarili, se bo prikazal tukaj.",
   "empty_column.mutes": "Niste utišali še nobenega uporabnika.",
   "empty_column.notification_requests": "Vse prebrano! Tu ni ničesar več. Ko prejmete nova obvestila, se bodo pojavila tu glede na vaše nastavitve.",
   "empty_column.notifications": "Nimate še nobenih obvestil. Povežite se z drugimi, da začnete pogovor.",
-  "empty_column.public": "Tukaj ni ničesar! Napišite nekaj javnega ali pa ročno sledite uporabnikom iz drugih strežnikov, da se bo napolnilo",
+  "empty_column.public": "Tukaj ni ničesar! Da ga napolnite, napišite nekaj javnega ali pa ročno sledite uporabnikom iz drugih strežnikov",
   "error.unexpected_crash.explanation": "Zaradi hrošča v naši kodi ali težave z združljivostjo brskalnika te strani ni mogoče ustrezno prikazati.",
-  "error.unexpected_crash.explanation_addons": "Te strani ni mogoče ustrezno prikazati. To napako najverjetneje povzroča dodatek brskalnika ali samodejna orodja za prevajanje.",
+  "error.unexpected_crash.explanation_addons": "Te strani ni mogoče ustrezno prikazati. To napako najverjetneje povzroča dodatek briskalnika ali samodejna orodja za prevajanje.",
   "error.unexpected_crash.next_steps": "Poskusite osvežiti stran. Če to ne pomaga, boste morda še vedno lahko uporabljali Mastodon prek drugega brskalnika ali z domorodno aplikacijo.",
   "error.unexpected_crash.next_steps_addons": "Poskusite jih onemogočiti in osvežiti stran. Če to ne pomaga, boste morda še vedno lahko uporabljali Mastodon prek drugega brskalnika ali z domorodno aplikacijo.",
-  "errors.unexpected_crash.copy_stacktrace": "Kopiraj sled sklada na odložišče",
+  "errors.unexpected_crash.copy_stacktrace": "Kopiraj sledenje skladu na odložišče",
   "errors.unexpected_crash.report_issue": "Prijavi težavo",
+  "explore.search_results": "Rezultati iskanja",
   "explore.suggested_follows": "Ljudje",
   "explore.title": "Razišči",
   "explore.trending_links": "Novice",
@@ -326,7 +290,7 @@
   "explore.trending_tags": "Ključniki",
   "filter_modal.added.context_mismatch_explanation": "Ta kategorija filtra ne velja za kontekst, v katerem ste dostopali do te objave. Če želite, da je objava filtrirana tudi v tem kontekstu, morate urediti filter.",
   "filter_modal.added.context_mismatch_title": "Neujemanje konteksta!",
-  "filter_modal.added.expired_explanation": "Ta kategorija filtra je pretekla. Morali boste spremeniti datum veljavnosti, da bo veljal še naprej.",
+  "filter_modal.added.expired_explanation": "Ta kategorija filtra je pretekla, morali boste spremeniti datum veljavnosti, da bo veljal še naprej.",
   "filter_modal.added.expired_title": "Filter je pretekel!",
   "filter_modal.added.review_and_configure": "Če želite pregledati in nadalje prilagoditi kategorijo filtra, obiščite {settings_link}.",
   "filter_modal.added.review_and_configure_title": "Nastavitve filtra",
@@ -341,7 +305,6 @@
   "filter_modal.select_filter.title": "Filtriraj to objavo",
   "filter_modal.title.status": "Filtrirajte objavo",
   "filter_warning.matches_filter": "Se ujema s filtrom »<span>{title}</span>«",
-  "filtered_notifications_banner.pending_requests": "Od {count, plural, =0 {nikogar, ki bi ga poznali} one {nekoga, ki ga morda poznate} two {dveh ljudi, ki ju morda poznate} other {ljudi, ki jih morda poznate}}",
   "filtered_notifications_banner.title": "Filtrirana obvestila",
   "firehose.all": "Vse",
   "firehose.local": "Ta strežnik",
@@ -368,14 +331,13 @@
   "footer.about": "O Mastodonu",
   "footer.directory": "Imenik profilov",
   "footer.get_app": "Prenesite aplikacijo",
+  "footer.invite": "Povabite osebe",
   "footer.keyboard_shortcuts": "Tipkovne bližnjice",
   "footer.privacy_policy": "Pravilnik o zasebnosti",
   "footer.source_code": "Pokaži izvorno kodo",
   "footer.status": "Stanje",
-  "footer.terms_of_service": "Pogoji uporabe",
   "generic.saved": "Shranjeno",
   "getting_started.heading": "Kako začeti",
-  "hashtag.admin_moderation": "Odpri vmesnik za moderiranje za #{name}",
   "hashtag.column_header.tag_mode.all": "in {additional}",
   "hashtag.column_header.tag_mode.any": "ali {additional}",
   "hashtag.column_header.tag_mode.none": "brez {additional}",
@@ -390,12 +352,9 @@
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objav} other {{counter} objav}}",
   "hashtag.follow": "Sledi ključniku",
   "hashtag.unfollow": "Nehaj slediti ključniku",
-  "hashtags.and_other": "… in še {count, plural, other {#}}",
-  "hints.profiles.followers_may_be_missing": "Sledilci za ta profil morda manjkajo.",
-  "hints.profiles.follows_may_be_missing": "Osebe, ki jim ta profil sledi, morda manjkajo.",
+  "hashtags.and_other": "…in še {count, plural, other {#}}",
   "hints.profiles.posts_may_be_missing": "Nekatere objave s tega profila morda manjkajo.",
   "hints.profiles.see_more_followers": "Pokaži več sledilcev na {domain}",
-  "hints.profiles.see_more_follows": "Pokaži več sledenih ljudi na zbirališču {domain}",
   "hints.profiles.see_more_posts": "Pokaži več objav na {domain}",
   "hints.threads.replies_may_be_missing": "Odgovori z drugih strežnikov morda manjkajo.",
   "hints.threads.see_more": "Pokaži več odgovorov na {domain}",
@@ -406,34 +365,24 @@
   "home.pending_critical_update.link": "Glejte posodobitve",
   "home.pending_critical_update.title": "Na voljo je kritična varnostna posodobbitev!",
   "home.show_announcements": "Pokaži obvestila",
-  "ignore_notifications_modal.disclaimer": "Mastodon ne more obveščati uporabnikov, da ste prezrli njihova obvestila. Tudi če jih prezrete, jih lahko uporabniki še vedno pošiljajo.",
-  "ignore_notifications_modal.filter_instead": "Raje filtriraj",
-  "ignore_notifications_modal.filter_to_act_users": "Še vedno boste lahko sprejeli, zavrnili ali prijavili uporabnike",
   "ignore_notifications_modal.filter_to_avoid_confusion": "Filtriranje pomaga pri izogibanju morebitni zmedi",
   "ignore_notifications_modal.filter_to_review_separately": "Filtrirana obvestila lahko pregledate ločeno",
   "ignore_notifications_modal.ignore": "Prezri obvestila",
-  "ignore_notifications_modal.limited_accounts_title": "Naj prezrem obvestila moderiranih računov?",
-  "ignore_notifications_modal.new_accounts_title": "Naj prezrem obvestila novih računov?",
-  "ignore_notifications_modal.not_followers_title": "Naj prezrem obvestila ljudi, ki vam ne sledijo?",
-  "ignore_notifications_modal.not_following_title": "Naj prezrem obvestila ljudi, ki jim ne sledite?",
-  "ignore_notifications_modal.private_mentions_title": "Naj prezrem obvestila od nezaželenih zasebnih omemb?",
-  "info_button.label": "Pomoč",
-  "info_button.what_is_alt_text": "<h1>Kaj je nadomestno besedilo?</h1> <p>Z nadomestnim besedilom dodatno opišemo sliko in tako pomagamo slabovidnim, ljudem s slabo internetno povezavo in tistim, ki jim manjka kontekst.</p> <p>Vaša objava bo veliko bolj dostopna in razumljiva, če boste napisali jasno, jedrnato in nepristransko nadomestno besedilo.</p> <ul> <li>Izpostavite pomembne elemente.</li> <li>Povzemite besedilo v slikah.</li> <li>Pišite v celih stavkih.</li> <li>Zajemite bistvo, ne dolgovezite.</li> <li>Opišite težnje in ključna odkritja, ki ste jih razbrali iz zapletenih grafik (npr. diagramov ali zemljevidov).</li> </ul>",
-  "interaction_modal.action.favourite": "Med priljubljene lahko dodate, ko se vpišete v svoj račun.",
-  "interaction_modal.action.follow": "Sledite lahko šele, ko se vpišete v svoj račun.",
-  "interaction_modal.action.reblog": "Izpostavite lahko šele, ko se vpišete v svoj račun.",
-  "interaction_modal.action.reply": "Odgovorite lahko šele, ko se vpišete v svoj račun.",
-  "interaction_modal.action.vote": "Glasujete lahko šele, ko se vpišete v svoj račun.",
-  "interaction_modal.go": "Naprej",
-  "interaction_modal.no_account_yet": "Še nimate računa?",
+  "interaction_modal.description.favourite": "Z računom na Mastodonu lahko to objavo postavite med priljubljene in tako avtorju nakažete, da jo cenite, in jo shranite za kasneje.",
+  "interaction_modal.description.follow": "Z računom na Mastodonu lahko sledite {name}, da prejemate njihove objave v svoj domači vir.",
+  "interaction_modal.description.reblog": "Z računom na Mastodonu lahko izpostavite to objavo, tako da jo delite s svojimi sledilci.",
+  "interaction_modal.description.reply": "Z računom na Mastodonu lahko odgovorite na to objavo.",
+  "interaction_modal.login.action": "Vrni me domov",
+  "interaction_modal.login.prompt": "Domena vašega domačega strežnika, npr. mastodon.social",
+  "interaction_modal.no_account_yet": "Niste na Mastodonu?",
   "interaction_modal.on_another_server": "Na drugem strežniku",
   "interaction_modal.on_this_server": "Na tem strežniku",
+  "interaction_modal.sign_in": "Niste prijavljeni v ta strežnik. Kje gostuje vaš račun?",
+  "interaction_modal.sign_in_hint": "Nasvet: To je spletno mesto, na katerem ste se prijavili. Če se ne spomnite, poiščite pozdravno e-poštno sporočilo v svojem e-poštnem predalu. Vpišete lahko tudi svoje celotno uporabniško ime (npr. @Mastodon@mastodon.social)!",
   "interaction_modal.title.favourite": "Daj objavo {name} med priljubljene",
   "interaction_modal.title.follow": "Sledi {name}",
   "interaction_modal.title.reblog": "Izpostavi objavo {name}",
   "interaction_modal.title.reply": "Odgovori na objavo {name}",
-  "interaction_modal.title.vote": "Izpolni anketo uporabnika/ce {name}",
-  "interaction_modal.username_prompt": "Npr. {example}",
   "intervals.full.days": "{number, plural, one {# dan} two {# dni} few {# dni} other {# dni}}",
   "intervals.full.hours": "{number, plural, one {# ura} two {# uri} few {# ure} other {# ur}}",
   "intervals.full.minutes": "{number, plural, one {# minuta} two {# minuti} few {# minute} other {# minut}}",
@@ -458,7 +407,7 @@
   "keyboard_shortcuts.muted": "Odpri seznam utišanih uporabnikov",
   "keyboard_shortcuts.my_profile": "Odprite svoj profil",
   "keyboard_shortcuts.notifications": "Odpri stolpec z obvestili",
-  "keyboard_shortcuts.open_media": "Odpri predstavnost",
+  "keyboard_shortcuts.open_media": "Odpri medij",
   "keyboard_shortcuts.pinned": "Odpri seznam pripetih objav",
   "keyboard_shortcuts.profile": "Odpri avtorjev profil",
   "keyboard_shortcuts.reply": "Odgovori na objavo",
@@ -467,47 +416,32 @@
   "keyboard_shortcuts.spoilers": "Pokaži/skrij polje CW",
   "keyboard_shortcuts.start": "Odpri stolpec \"začni\"",
   "keyboard_shortcuts.toggle_hidden": "Pokaži/skrij besedilo za CW",
-  "keyboard_shortcuts.toggle_sensitivity": "Pokaži/skrij predstavnosti",
+  "keyboard_shortcuts.toggle_sensitivity": "Pokaži/skrij medije",
   "keyboard_shortcuts.toot": "Začni povsem novo objavo",
-  "keyboard_shortcuts.translate": "za prevod objave",
   "keyboard_shortcuts.unfocus": "Odstrani pozornost z območja za sestavljanje besedila/iskanje",
   "keyboard_shortcuts.up": "Premakni navzgor po seznamu",
   "lightbox.close": "Zapri",
   "lightbox.next": "Naslednji",
   "lightbox.previous": "Prejšnji",
-  "lightbox.zoom_in": "Približaj na dejansko velikost",
-  "lightbox.zoom_out": "Čez cel prikaz",
   "limited_account_hint.action": "Vseeno pokaži profil",
   "limited_account_hint.title": "Profil so moderatorji strežnika {domain} skrili.",
-  "link_preview.author": "Avtor/ica {name}",
+  "link_preview.author": "Avtor_ica {name}",
   "link_preview.more_from_author": "Več od {name}",
   "link_preview.shares": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objave} other {{counter} objav}}",
-  "lists.add_member": "Dodaj",
-  "lists.add_to_list": "Dodaj na seznam",
-  "lists.add_to_lists": "Dodaj {name} na sezname",
-  "lists.create": "Ustvari",
-  "lists.create_a_list_to_organize": "Uredite si domači vir z novim seznamom",
-  "lists.create_list": "Ustvari seznam",
+  "lists.account.add": "Dodaj na seznam",
+  "lists.account.remove": "Odstrani s seznama",
   "lists.delete": "Izbriši seznam",
-  "lists.done": "Opravljeno",
   "lists.edit": "Uredi seznam",
-  "lists.exclusive": "Skrij člane v domovanju",
-  "lists.exclusive_hint": "Objave vseh, ki so na tem seznamu, se ne pokažejo v vašem domačem viru. Tako se izognete podvojenim objavam.",
-  "lists.find_users_to_add": "Poišči člane za dodajanje",
-  "lists.list_members": "Člani seznama",
-  "lists.list_members_count": "{count, plural, one {# član} two {# člana} few {# člani} other {# članov}}",
-  "lists.list_name": "Ime seznama",
-  "lists.new_list_name": "Novo ime seznama",
-  "lists.no_lists_yet": "Ni seznamov.",
-  "lists.no_members_yet": "Ni še nobenega člana.",
-  "lists.no_results_found": "Ni rezultatov.",
-  "lists.remove_member": "Odstrani",
+  "lists.edit.submit": "Spremeni naslov",
+  "lists.exclusive": "Skrij te objave od doma",
+  "lists.new.create": "Dodaj seznam",
+  "lists.new.title_placeholder": "Nov naslov seznama",
   "lists.replies_policy.followed": "Vsem sledenim uporabnikom",
   "lists.replies_policy.list": "Članom seznama",
   "lists.replies_policy.none": "Nikomur",
-  "lists.save": "Shrani",
-  "lists.search": "Iskanje",
-  "lists.show_replies_to": "Vključi odgovore, katerih pošiljatelji so člani seznama in prejemniki",
+  "lists.replies_policy.title": "Pokaži odgovore:",
+  "lists.search": "Iščite med ljudmi, katerim sledite",
+  "lists.subheading": "Vaši seznami",
   "load_pending": "{count, plural, one {# nov element} two {# nova elementa} few {# novi elementi} other {# novih elementov}}",
   "loading_indicator.label": "Nalaganje …",
   "media_gallery.hide": "Skrij",
@@ -555,17 +489,9 @@
   "notification.admin.report_statuses": "{name} je prijavil/a {target} zaradi {category}",
   "notification.admin.report_statuses_other": "{name} je prijavil/a {target}",
   "notification.admin.sign_up": "{name} se je vpisal/a",
-  "notification.admin.sign_up.name_and_others": "Prijavili so se {name} in {count, plural, one {# druga oseba} two {# drugi osebi} few {# druge osebe} other {# drugih oseb}}",
-  "notification.annual_report.message": "Čaka vas vaš #Wrapstodon {year}! Razkrijte svoje letošnje nepozabne trenutke na Mastodonu!",
-  "notification.annual_report.view": "Pokaži #Wrapstodon",
   "notification.favourite": "{name} je vzljubil/a vašo objavo",
-  "notification.favourite.name_and_others_with_link": "{name} in <a>{count, plural, one {# druga oseba} two {# drugi osebi} few {# druge osebe} other {# drugih oseb}}</a> je dodalo vašo objavo med priljubljene",
-  "notification.favourite_pm": "{name} je dodalo vašo zasebno omembo med priljubljene",
-  "notification.favourite_pm.name_and_others_with_link": "{name} in <a>{count, plural, one {# druga oseba} two {# drugi osebi} few {# druge osebe} other {# drugih oseb}}</a> je dodalo vašo zasebno omembo med priljubljene",
   "notification.follow": "{name} vam sledi",
-  "notification.follow.name_and_others": "{name} in {count, plural, one {<a># druga oseba</a> sta ti sledila} two {<a># drugi osebi</a> so ti sledili} few {<a># druge osebe</a> so ti sledili} other {<a># drugih oseb</a> ti je sledilo}}",
   "notification.follow_request": "{name} vam želi slediti",
-  "notification.follow_request.name_and_others": "{name} in {count, plural, one {# druga oseba bi ti rada sledila} two {# drugi osebi bi ti radi sledili} few {# druge osebe bi ti radi sledili} other {# drugih oseb bi ti radi sledili}}",
   "notification.label.mention": "Omemba",
   "notification.label.private_mention": "Zasebna omemba",
   "notification.label.private_reply": "Zasebni odgovor",
@@ -584,7 +510,6 @@
   "notification.own_poll": "Vaša anketa je zaključena",
   "notification.poll": "Anketa, v kateri ste sodelovali, je zaključena",
   "notification.reblog": "{name} je izpostavila/a vašo objavo",
-  "notification.reblog.name_and_others_with_link": "{name} in <a>{count, plural, one {# druga oseba</a> sta izpostavila tvojo objavo} two {# drugi osebi</a> so izpostavili tvojo objavo} few {# druge osebe</a> so izpostavili tvojo objavo} other {# drugih oseb</a> so izpostavili tvojo objavo}}",
   "notification.relationships_severance_event": "Povezave z {name} prekinjene",
   "notification.relationships_severance_event.account_suspension": "Skrbnik na {from} je suspendiral račun {target}, kar pomeni, da od računa ne morete več prejemati posodobitev ali imeti z njim interakcij.",
   "notification.relationships_severance_event.domain_block": "Skrbnik na {from} je blokiral domeno {target}, vključno z vašimi sledilci ({followersCount}) in {followingCount, plural, one {# računom, ki mu sledite} two {# računoma, ki jima sledite} few {# računi, ki jim sledite} other {# računi, ki jim sledite}}.",
@@ -593,21 +518,12 @@
   "notification.status": "{name} je pravkar objavil/a",
   "notification.update": "{name} je uredil(a) objavo",
   "notification_requests.accept": "Sprejmi",
-  "notification_requests.accept_multiple": "{count, plural, one {Sprejmi # prošnjo …} two {Sprejmi # prošnji …} few {Sprejmi # prošnje …} other {Sprejmi # prošenj …}}",
-  "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Sprejmi prošnjo} two {Sprejmi prošnji} other {Sprejmi prošnje}}",
-  "notification_requests.confirm_accept_multiple.message": "Sprejeti nameravate {count, plural, one {eno prošnjo za obvestila} two {dve prošnji za obvestila} few {# prošnje za obvestila} other {# prošenj za obvestila}}. Ali ste prepričani?",
   "notification_requests.confirm_accept_multiple.title": "Ali želite sprejeti zahteve za obvestila?",
-  "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Zavrni prošnjo} two {Zavrni prošnji} other {Zavrni prošnje}}",
-  "notification_requests.confirm_dismiss_multiple.message": "Zavrniti nameravate {count, plural, one {eno prošnjo za obvestila} two {dve prošnji za obvestila} few {# prošnje za obvestila} other {# prošenj za obvestila}}. Do {count, plural, one {nje} two {njiju} other {njih}} ne boste več mogli dostopati. Ali ste prepričani?",
   "notification_requests.confirm_dismiss_multiple.title": "Želite opustiti zahteve za obvestila?",
   "notification_requests.dismiss": "Zavrni",
-  "notification_requests.dismiss_multiple": "{count, plural, one {Zavrni # prošnjo …} two {Zavrni # prošnji …} few {Zavrni # prošnje …} other {Zavrni # prošenj …}}",
   "notification_requests.edit_selection": "Uredi",
   "notification_requests.exit_selection": "Opravljeno",
-  "notification_requests.explainer_for_limited_account": "Obvestila za ta račun so bila filtrirana, ker je ta račun omejil moderator.",
-  "notification_requests.explainer_for_limited_remote_account": "Obvestila za ta račun so bila filtrirana, ker je račun ali njegov strežnik omejil moderator.",
   "notification_requests.maximize": "Maksimiraj",
-  "notification_requests.minimize_banner": "Zloži pasico filtriranih obvestil",
   "notification_requests.notifications_from": "Obvestila od {name}",
   "notification_requests.title": "Filtrirana obvestila",
   "notification_requests.view": "Pokaži obvestila",
@@ -622,7 +538,6 @@
   "notifications.column_settings.filter_bar.category": "Vrstica za hitro filtriranje",
   "notifications.column_settings.follow": "Novi sledilci:",
   "notifications.column_settings.follow_request": "Nove prošnje za sledenje:",
-  "notifications.column_settings.group": "Združi",
   "notifications.column_settings.mention": "Omembe:",
   "notifications.column_settings.poll": "Rezultati ankete:",
   "notifications.column_settings.push": "Potisna obvestila",
@@ -649,9 +564,6 @@
   "notifications.policy.accept": "Sprejmi",
   "notifications.policy.accept_hint": "Pokaži med obvestili",
   "notifications.policy.drop": "Prezri",
-  "notifications.policy.drop_hint": "Pošlji v pozabo, od koder se nikdar nič ne vrne",
-  "notifications.policy.filter": "Filtriraj",
-  "notifications.policy.filter_hint": "Pošlji med filtrirana prejeta obvestila",
   "notifications.policy.filter_limited_accounts_hint": "Omejeno s strani moderatorjev strežnika",
   "notifications.policy.filter_limited_accounts_title": "Moderirani računi",
   "notifications.policy.filter_new_accounts.hint": "Ustvarjen v {days, plural, one {zadnjem # dnevu} two {zadnjih # dnevih} few {zadnjih # dnevih} other {zadnjih # dnevih}}",
@@ -666,21 +578,44 @@
   "notifications_permission_banner.enable": "Omogoči obvestila na namizju",
   "notifications_permission_banner.how_to_control": "Če želite prejemati obvestila, ko Mastodon ni odprt, omogočite namizna obvestila. Natančno lahko nadzirate, katere vrste interakcij naj tvorijo namizna obvestila; ko so omogočena, za to uporabite gumb {icon} zgoraj.",
   "notifications_permission_banner.title": "Nikoli ne zamudite ničesar",
-  "onboarding.follows.back": "Nazaj",
-  "onboarding.follows.done": "Opravljeno",
+  "onboarding.action.back": "Pelji me nazaj",
+  "onboarding.actions.back": "Pelji me nazaj",
+  "onboarding.actions.go_to_explore": "Poglejte, kaj je v trendu",
+  "onboarding.actions.go_to_home": "Pojdite na svoj domači vir",
+  "onboarding.compose.template": "Pozdravljen, #Mastodon!",
   "onboarding.follows.empty": "Žal trenutno ni mogoče prikazati nobenih rezultatov. Lahko poskusite z iskanjem ali brskanjem po strani za raziskovanje, da poiščete osebe, ki jim želite slediti, ali poskusite znova pozneje.",
-  "onboarding.follows.search": "Išči",
-  "onboarding.follows.title": "Vaš prvi korak je, da sledite ljudem",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Priljubljeno na Mastodonu",
   "onboarding.profile.discoverable": "Naj bo moj profil mogoče najti",
   "onboarding.profile.discoverable_hint": "Ko se odločite za razkrivanje na Mastodonu, se lahko vaše objave pojavijo v rezultatih iskanja in trendih, vaš profil pa se lahko predlaga ljudem, ki imajo podobne interese kot vi.",
   "onboarding.profile.display_name": "Pojavno ime",
   "onboarding.profile.display_name_hint": "Vaše polno ime ali lažno ime ...",
+  "onboarding.profile.lead": "To lahko vedno dokončate med nastavitvami, kjer je na voljo še več možnosti prilagajanja.",
   "onboarding.profile.note": "Biografija",
-  "onboarding.profile.note_hint": "Lahko @omenite druge osebe ali dodate #ključnike ...",
+  "onboarding.profile.note_hint": "Druge osebe lahko @omenite ali #ključite ...",
   "onboarding.profile.save_and_continue": "Shrani in nadaljuj",
   "onboarding.profile.title": "Nastavitev profila",
   "onboarding.profile.upload_avatar": "Naloži sliko profila",
   "onboarding.profile.upload_header": "Naloži glavo profila",
+  "onboarding.share.lead": "Povejte vsem, kako vas lahko najdejo na Mastodonu!",
+  "onboarding.share.message": "Sem {username} na #Mastodon! Sledite mi na {url}",
+  "onboarding.share.next_steps": "Možni naslednji koraki:",
+  "onboarding.share.title": "Delite svoj profil z drugimi",
+  "onboarding.start.lead": "Vaš novi račun Mastodon je pripravljen za uporabo. Takole ga lahko najbolje izkoristite:",
+  "onboarding.start.skip": "Ali želite preskočiti vse to?",
+  "onboarding.start.title": "Uspelo vam je!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Pozdravite cel svet.",
+  "onboarding.steps.publish_status.title": "Ustvarite svojo prvo objavo",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Prilagodite svoj profil",
+  "onboarding.steps.share_profile.body": "Naj prijatelji izvejo, kako vas najdejo na Mastodonu!",
+  "onboarding.steps.share_profile.title": "Delite svoj profil z drugimi",
+  "onboarding.tips.2fa": "<strong>Ali veste?</strong> Račun lahko zavarujete tako, da v nastavitvah računa nastavite dvostopenjsko overjanje. Deluje s poljubnim programom TOTP po vaši izbiri, telefonska številka ni potrebna!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Ali veste?</strong> Ker je Mastodon decentraliziran, bodo nekateri profili, na katere naletite, gostovali na strežnikih, ki ni vaš. Pa vendar lahko z njimi brezhibno komunicirate! Njihov strežnik je zapisan v drugi polovici njihovega uporabniškega imena!",
+  "onboarding.tips.migration": "<strong>Ali veste?</strong> Če menite, da {domain} za vas v prihodnosti ni najboljša izbira strežnika, se lahko preselite na drug strežnik Mastodon, ne da bi izgubili sledilce. Lahko celo gostite svoj strežnik!",
+  "onboarding.tips.verification": "<strong>Ali veste?</strong> Svoj račun lahko preverite tako, da na svoje spletno mesto postavite povezavo do svojega profila Mastodon in spletno stran dodate v svoj profil. Brez stroškov ali dokumentov!",
   "password_confirmation.exceeds_maxlength": "Potrditev gesla presega največjo dolžino gesla",
   "password_confirmation.mismatching": "Potrdilo gesla se ne ujema.",
   "picture_in_picture.restore": "Postavi nazaj",
@@ -696,7 +631,7 @@
   "poll_button.remove_poll": "Odstrani anketo",
   "privacy.change": "Spremeni zasebnost objave",
   "privacy.direct.long": "Vsem omenjenim v objavi",
-  "privacy.direct.short": "Zasebna omemba",
+  "privacy.direct.short": "Določenim ljudem",
   "privacy.private.long": "Samo vašim sledilcem",
   "privacy.private.short": "Sledilcem",
   "privacy.public.long": "Vsem, ki so ali niso na Mastodonu",
@@ -708,8 +643,8 @@
   "privacy_policy.title": "Pravilnik o zasebnosti",
   "recommended": "Priporočeno",
   "refresh": "Osveži",
-  "regeneration_indicator.please_stand_by": "Prosimo, počakajte.",
-  "regeneration_indicator.preparing_your_home_feed": "Pripravljamo vaš domači vir …",
+  "regeneration_indicator.label": "Nalaganje …",
+  "regeneration_indicator.sublabel": "Vaš domači vir se pripravlja!",
   "relative_time.days": "{number} d",
   "relative_time.full.days": "{number, plural, one {pred # dnem} two {pred # dnevoma} few {pred # dnevi} other {pred # dnevi}}",
   "relative_time.full.hours": "{number, plural, one {pred # uro} two {pred # urama} few {pred # urami} other {pred # urami}}",
@@ -745,7 +680,7 @@
   "report.reasons.dislike": "Ni mi všeč",
   "report.reasons.dislike_description": "To ni tisto, kar želim videti",
   "report.reasons.legal": "To ni legalno",
-  "report.reasons.legal_description": "Sem mnenja, da krši zakonodajo moje države ali države strežnika",
+  "report.reasons.legal_description": "Ste mnenja, da krši zakonodajo vaše države ali države strežnika",
   "report.reasons.other": "Gre za nekaj drugega",
   "report.reasons.other_description": "Težava ne sodi v druge kategorije",
   "report.reasons.spam": "To je neželena vsebina",
@@ -757,10 +692,10 @@
   "report.statuses.subtitle": "Izberite vse, kar ustreza",
   "report.statuses.title": "Ali so kakšne objave, ki dokazujejo trditve iz te prijave?",
   "report.submit": "Pošlji",
-  "report.target": "Prijavljate {target}",
+  "report.target": "Prijavi {target}",
   "report.thanks.take_action": "Tukaj so vaše možnosti za nadzor tistega, kar vidite na Mastodonu:",
   "report.thanks.take_action_actionable": "Medtem, ko to pregledujemo, lahko proti @{name} ukrepate:",
-  "report.thanks.title": "Ali ne želite videti tega?",
+  "report.thanks.title": "Ali ne želite tega videti?",
   "report.thanks.title_actionable": "Hvala za prijavo, bomo preverili.",
   "report.unfollow": "Ne sledi več @{name}",
   "report.unfollow_explanation": "Temu računu sledite. Da ne boste več videli njegovih objav v svojem domačem viru, mu prenehajte slediti.",
@@ -784,7 +719,7 @@
   "search.search_or_paste": "Iščite ali prilepite URL",
   "search_popout.full_text_search_disabled_message": "Ni dostopno na {domain}.",
   "search_popout.full_text_search_logged_out_message": "Na voljo le, če ste prijavljeni.",
-  "search_popout.language_code": "Jezikovna koda ISO",
+  "search_popout.language_code": "Koda ISO jezika",
   "search_popout.options": "Možnosti iskanja",
   "search_popout.quick_actions": "Hitra dejanja",
   "search_popout.recent": "Nedavna iskanja",
@@ -793,11 +728,10 @@
   "search_results.accounts": "Profili",
   "search_results.all": "Vse",
   "search_results.hashtags": "Ključniki",
-  "search_results.no_results": "Ni rezultatov.",
-  "search_results.no_search_yet": "Pobrskajte med objavami, profili in ključniki.",
+  "search_results.nothing_found": "Za ta iskalni niz ni zadetkov",
   "search_results.see_all": "Poglej vse",
   "search_results.statuses": "Objave",
-  "search_results.title": "Zadetki za \"{q}\"",
+  "search_results.title": "Išči {q}",
   "server_banner.about_active_users": "Osebe, ki so uporabljale ta strežnik zadnjih 30 dni (dejavni uporabniki meseca)",
   "server_banner.active_users": "dejavnih uporabnikov",
   "server_banner.administered_by": "Upravlja:",
@@ -815,7 +749,6 @@
   "status.bookmark": "Dodaj med zaznamke",
   "status.cancel_reblog_private": "Prekliči izpostavitev",
   "status.cannot_reblog": "Te objave ni mogoče izpostaviti",
-  "status.continued_thread": "Nadaljevanje niti",
   "status.copy": "Kopiraj povezavo do objave",
   "status.delete": "Izbriši",
   "status.detailed_status": "Podroben pogled pogovora",
@@ -825,7 +758,7 @@
   "status.edited": "Zadnje urejanje {date}",
   "status.edited_x_times": "Urejeno {count, plural, one {#-krat} two {#-krat} few {#-krat} other {#-krat}}",
   "status.embed": "Pridobite kodo za vgradnjo",
-  "status.favourite": "Priljubljen/a",
+  "status.favourite": "Priljubljen_a",
   "status.favourites": "{count, plural, one {priljubitev} two {priljubitvi} few {priljubitve} other {priljubitev}}",
   "status.filter": "Filtriraj to objavo",
   "status.history.created": "{name}: ustvarjeno {date}",
@@ -833,7 +766,7 @@
   "status.load_more": "Naloži več",
   "status.media.open": "Kliknite za odpiranje",
   "status.media.show": "Kliknite za prikaz",
-  "status.media_hidden": "Predstavnosti so skrite",
+  "status.media_hidden": "Mediji so skriti",
   "status.mention": "Omeni @{name}",
   "status.more": "Več",
   "status.mute": "Utišaj @{name}",
@@ -849,8 +782,6 @@
   "status.reblogs.empty": "Nihče še ni izpostavil te objave. Ko se bo to zgodilo, se bodo pojavile tukaj.",
   "status.redraft": "Izbriši in preoblikuj",
   "status.remove_bookmark": "Odstrani zaznamek",
-  "status.remove_favourite": "Odstrani iz priljubljenih",
-  "status.replied_in_thread": "Odgovor iz niti",
   "status.replied_to": "Odgovoril/a {name}",
   "status.reply": "Odgovori",
   "status.replyAll": "Odgovori na nit",
@@ -860,7 +791,7 @@
   "status.show_less_all": "Prikaži manj za vse",
   "status.show_more_all": "Pokaži več za vse",
   "status.show_original": "Pokaži izvirnik",
-  "status.title.with_attachments": "{user} je objavil/a {attachmentCount, plural, one {{attachmentCount} priponko} two {{attachmentCount} priponki} few {{attachmentCount} priponke} other {{attachmentCount} priponk}}",
+  "status.title.with_attachments": "{user} je objavil_a {attachmentCount, plural, one {{attachmentCount} priponko} two {{attachmentCount} priponki} few {{attachmentCount} priponke} other {{attachmentCount} priponk}}",
   "status.translate": "Prevedi",
   "status.translated_from_with": "Prevedeno iz {lang} s pomočjo {provider}",
   "status.uncached_media_warning": "Predogled ni na voljo",
@@ -871,9 +802,6 @@
   "subscribed_languages.target": "Spremeni naročene jezike za {target}",
   "tabs_bar.home": "Domov",
   "tabs_bar.notifications": "Obvestila",
-  "terms_of_service.effective_as_of": "Veljavno od {date}",
-  "terms_of_service.title": "Pogoji uporabe",
-  "terms_of_service.upcoming_changes_on": "Spremembe začnejo veljati {date}",
   "time_remaining.days": "{number, plural, one {preostaja # dan} two {preostajata # dneva} few {preostajajo # dnevi} other {preostaja # dni}}",
   "time_remaining.hours": "{number, plural, one {# ura} other {# ur}} je ostalo",
   "time_remaining.minutes": "{number, plural, one {# minuta} other {# minut}} je ostalo",
@@ -889,12 +817,21 @@
   "upload_button.label": "Dodajte slike, video ali zvočno datoteko",
   "upload_error.limit": "Omejitev prenosa datoteke je presežena.",
   "upload_error.poll": "Prenos datoteke z anketami ni dovoljen.",
-  "upload_form.drag_and_drop.instructions": "Predstavnostno priponko lahko poberete tako, da pritisnete preslednico ali vnašalko. S puščicami na tipkovnici premikate priponko v posamezno smer. Priponko lahko odložite na novem položaju s ponovnim pritiskom na preslednico ali vnašalko ali pa dejanje prekličete s tipko ubežnica.",
-  "upload_form.drag_and_drop.on_drag_cancel": "Premikanje priponke je preklicano. Predstavnostna priponka {item} je padla nazaj na prejšnje mesto.",
-  "upload_form.drag_and_drop.on_drag_end": "Predstavnostna priponka {item} je padla nazaj.",
-  "upload_form.drag_and_drop.on_drag_over": "Priponka {item} je bila premaknjena.",
-  "upload_form.drag_and_drop.on_drag_start": "Pobrana priponka {item}.",
+  "upload_form.audio_description": "Opiši za osebe z okvaro sluha",
+  "upload_form.description": "Opišite za slabovidne",
   "upload_form.edit": "Uredi",
+  "upload_form.thumbnail": "Spremeni sličico",
+  "upload_form.video_description": "Opišite za osebe z okvaro sluha in/ali vida",
+  "upload_modal.analyzing_picture": "Analiziranje slike …",
+  "upload_modal.apply": "Uveljavi",
+  "upload_modal.applying": "Uveljavljanje poteka …",
+  "upload_modal.choose_image": "Izberite sliko",
+  "upload_modal.description_placeholder": "Pri Jakcu bom vzel šest čudežnih fig",
+  "upload_modal.detect_text": "Zaznaj besedilo v sliki",
+  "upload_modal.edit_media": "Uredi medij",
+  "upload_modal.hint": "Kliknite ali povlecite krog v predogledu, da izberete točko pozornosti, ki bo vedno vidna na vseh oglednih sličicah.",
+  "upload_modal.preparing_ocr": "Priprava optične prepoznave znakov (OCR) …",
+  "upload_modal.preview_label": "Predogled ({ratio})",
   "upload_progress.label": "Pošiljanje ...",
   "upload_progress.processing": "Obdelovanje …",
   "username.taken": "To uporabniško ime je zasedeno. Poskusite z drugim.",
@@ -904,6 +841,8 @@
   "video.expand": "Razširi video",
   "video.fullscreen": "Celozaslonski način",
   "video.hide": "Skrij video",
+  "video.mute": "Utišaj zvok",
   "video.pause": "Premor",
-  "video.play": "Predvajaj"
+  "video.play": "Predvajaj",
+  "video.unmute": "Vklopi zvok"
 }
diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json
index a42a25b374..ea92d611f3 100644
--- a/app/javascript/mastodon/locales/sq.json
+++ b/app/javascript/mastodon/locales/sq.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Përpunoni profilin",
   "account.enable_notifications": "Njoftomë, kur poston @{name}",
   "account.endorse": "Pasqyrojeni në profil",
-  "account.featured": "Të zgjedhur",
-  "account.featured.hashtags": "Hashtag-ë",
-  "account.featured.posts": "Postime",
   "account.featured_tags.last_status_at": "Postimi i fundit më {date}",
   "account.featured_tags.last_status_never": "Pa postime",
+  "account.featured_tags.title": "Hashtagë të zgjedhur të {name}",
   "account.follow": "Ndiqeni",
   "account.follow_back": "Ndiqe gjithashtu",
   "account.followers": "Ndjekës",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} postim} other {{counter} postime}}",
   "account.unblock": "Zhbllokoje @{name}",
   "account.unblock_domain": "Zhblloko përkatësinë {domain}",
-  "account.unblock_domain_short": "Zhbllokoje",
   "account.unblock_short": "Zhbllokoje",
   "account.unendorse": "Mos e përfshi në profil",
   "account.unfollow": "Resht së ndjekuri",
@@ -89,28 +86,7 @@
   "alert.unexpected.message": "Ndodhi një gabim të papritur.",
   "alert.unexpected.title": "Hëm!",
   "alt_text_badge.title": "Tekst alternativ",
-  "alt_text_modal.add_alt_text": "Shtoni tekst alternativ",
-  "alt_text_modal.add_text_from_image": "Shtoni tekst nga figura",
-  "alt_text_modal.cancel": "Anuloje",
-  "alt_text_modal.change_thumbnail": "Ndryshoni miniaturën",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Përshkruajeni këtë për persona me mangësi dëgjimi…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Përshkruajeni këtë për persona me mangësi shikimi…",
-  "alt_text_modal.done": "U bë",
   "announcement.announcement": "Lajmërim",
-  "annual_report.summary.followers.followers": "ndjekës",
-  "annual_report.summary.followers.total": "{count} gjithsej",
-  "annual_report.summary.here_it_is": "Ja {year} juaj e shqyrtuar:",
-  "annual_report.summary.highlighted_post.by_favourites": "potimi më i parapëlqyer",
-  "annual_report.summary.highlighted_post.by_reblogs": "postimi me më shumë përforcime",
-  "annual_report.summary.highlighted_post.by_replies": "postimi me më tepër përgjigje",
-  "annual_report.summary.highlighted_post.possessive": "nga {name}",
-  "annual_report.summary.most_used_app.most_used_app": "aplikacioni më i përdorur",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag-u më i përdorur",
-  "annual_report.summary.most_used_hashtag.none": "Asnjë",
-  "annual_report.summary.new_posts.new_posts": "postime të reja",
-  "annual_report.summary.percentile.text": "<topLabel>Kjo ju vendos te</topLabel><percentage></percentage><bottomLabel> kryesues të përdoruesve të {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Nuk do t’ia themi Bernit.",
-  "annual_report.summary.thanks": "Faleminderit që jeni pjesë e Mastodon-it!",
   "attachments_list.unprocessed": "(e papërpunuar)",
   "audio.hide": "Fshihe audion",
   "block_modal.remote_users_caveat": "Do t’i kërkojmë shërbyesit {domain} të respektojë vendimin tuaj. Por, pajtimi s’është i garantuar, ngaqë disa shërbyes mund t’i trajtojnë ndryshe bllokimet. Psotimet publike mundet të jenë ende të dukshme për përdorues pa bërë hyrje në llogari.",
@@ -134,7 +110,7 @@
   "bundle_column_error.routing.body": "Faqja e kërkuar s’u gjet dot. Jeni i sigurt se URL-ja te shtylla e adresave është e saktë?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Mbylle",
-  "bundle_modal_error.message": "Diç shkoi ters, teksa ngarkohej kjo skenë.",
+  "bundle_modal_error.message": "Diç shkoi ters teksa ngarkohej ky përbërës.",
   "bundle_modal_error.retry": "Riprovoni",
   "closed_registrations.other_server_instructions": "Ngaqë Mastodon-i është i decentralizuar, mund të krijoni një llogari në një tjetër shërbyes dhe prapë të ndëveproni me këtë këtu.",
   "closed_registrations_modal.description": "Krijimi i një llogarie te {domain} aktualisht është i pamundur, por kini parasysh se s’keni nevojë për një llogari posaçërisht në {domain} që të përdorni Mastodon-in.",
@@ -145,16 +121,13 @@
   "column.blocks": "Përdorues të bllokuar",
   "column.bookmarks": "Faqerojtës",
   "column.community": "Rrjedhë kohore vendore",
-  "column.create_list": "Krijo listë",
   "column.direct": "Përmendje private",
   "column.directory": "Shfletoni profile",
   "column.domain_blocks": "Përkatësi të bllokuara",
-  "column.edit_list": "Përpunoni listën",
   "column.favourites": "Të parapëlqyer",
   "column.firehose": "Prurje “live”",
   "column.follow_requests": "Kërkesa për ndjekje",
   "column.home": "Kreu",
-  "column.list_members": "Administroni anëtarë liste",
   "column.lists": "Lista",
   "column.mutes": "Përdorues të heshtuar",
   "column.notifications": "Njoftime",
@@ -167,7 +140,6 @@
   "column_header.pin": "Fiksoje",
   "column_header.show_settings": "Shfaq rregullime",
   "column_header.unpin": "Shfiksoje",
-  "column_search.cancel": "Anuloje",
   "column_subheading.settings": "Rregullime",
   "community.column_settings.local_only": "Vetëm vendore",
   "community.column_settings.media_only": "Vetëm Media",
@@ -186,7 +158,7 @@
   "compose_form.poll.duration": "Kohëzgjatje pyetësori",
   "compose_form.poll.multiple": "Shumë zgjedhje",
   "compose_form.poll.option_placeholder": "Mundësia {number}",
-  "compose_form.poll.single": "Zgjedhje njëshe",
+  "compose_form.poll.single": "Zgjidhni një",
   "compose_form.poll.switch_to_multiple": "Ndrysho votimin për të lejuar shumë zgjedhje",
   "compose_form.poll.switch_to_single": "Ndrysho votimin për të lejuar vetëm një zgjedhje",
   "compose_form.poll.type": "Stil",
@@ -210,16 +182,9 @@
   "confirmations.edit.confirm": "Përpunojeni",
   "confirmations.edit.message": "Përpunimi tani do të sjellë mbishkrim të mesazhit që po hartoni aktualisht. Jeni i sigurt se doni të vazhdohet?",
   "confirmations.edit.title": "Të mbishkruhet postimi?",
-  "confirmations.follow_to_list.confirm": "Ndiqe dhe shtoje te listë",
-  "confirmations.follow_to_list.message": "Lypset të jeni duke e ndjekur {name}, që të shtohte te një listë.",
-  "confirmations.follow_to_list.title": "Të ndiqet përdoruesi?",
   "confirmations.logout.confirm": "Dilni",
   "confirmations.logout.message": "Jeni i sigurt se doni të dilet?",
   "confirmations.logout.title": "Të dilet?",
-  "confirmations.missing_alt_text.confirm": "Shtoni tekst alternativ",
-  "confirmations.missing_alt_text.message": "Postimi juaj përmban media pa tekst alternativ. Shtimi i përshkrimeve ndihmon të bëhet lënda juaj e përdorshme nga më tepër njerëz.",
-  "confirmations.missing_alt_text.secondary": "Postoje, sido qoftë",
-  "confirmations.missing_alt_text.title": "Të shtohet tekst alternativ?",
   "confirmations.mute.confirm": "Heshtoje",
   "confirmations.redraft.confirm": "Fshijeni & rihartojeni",
   "confirmations.redraft.message": "Jeni i sigurt se doni të fshihet kjo gjendje dhe të rihartohet? Të parapëlqyerit dhe përforcimet do të humbin, ndërsa përgjigjet te postimi origjinal do të bëhen jetime.",
@@ -248,10 +213,10 @@
   "disabled_account_banner.text": "Llogaria juaj {disabledAccount} është aktualisht e çaktivizuar.",
   "dismissable_banner.community_timeline": "Këto janë postimet më të freskëta publike nga persona llogaritë e të cilëve strehohen nga {domain}.",
   "dismissable_banner.dismiss": "Hidhe tej",
-  "dismissable_banner.explore_links": "Këto lajme po ndahen më shumë se të tjerat në fedivers sot. Lajme më të reja të postuara nga më tepër persona të ndryshëm klasifikohen më lart.",
-  "dismissable_banner.explore_statuses": "Këto postime nga fediversi po tërheqin vëmendjen sot. Postimet më të reja me më tepër përforcime dhe parapëlqime klasifikohen më lart.",
-  "dismissable_banner.explore_tags": "Këta hashtag-ë po tërheqin vëmendjen në fedivers sot. Hashtag-ët që përdoren nga më tepër persona klasifikohen më lart.",
-  "dismissable_banner.public_timeline": "Këto janë postimet publike më të freskëta nga persona në fedivers që ndjekin personat në {domain}.",
+  "dismissable_banner.explore_links": "Këto histori të reja po tirren nga persona në këtë shërbyes dhe të tjerë të tillë të rrjetit të decentralizuar mu tani.",
+  "dismissable_banner.explore_statuses": "Këto janë postime nga rrjeti shoqëror që po tërheqin vëmendjen tani. Postimet më të reja me më përforcime dhe më të parapëlqyera nga njerëzit renditen më sipër.",
+  "dismissable_banner.explore_tags": "Këta hashtag-ë po tërheqin vëmendjen mes personave në këtë shërbyes dhe të tjerë të tillë të rrjetit të decentralizuar mu tani.",
+  "dismissable_banner.public_timeline": "Këto janë postimet më të reja publike prej personash në rrjetin shoqëror që ndjekin njerëzit në {domain}.",
   "domain_block_modal.block": "Bllokoje shërbyesin",
   "domain_block_modal.block_account_instead": "Blloko @{name} në vend të kësaj",
   "domain_block_modal.they_can_interact_with_old_posts": "Persona nga ky shërbyes mund të ndërveprojnë me postimet tuaja të vjetra.",
@@ -291,7 +256,6 @@
   "emoji_button.search_results": "Përfundime kërkimi",
   "emoji_button.symbols": "Simbole",
   "emoji_button.travel": "Udhëtime & Vende",
-  "empty_column.account_featured": "Kjo listë është e zbrazët",
   "empty_column.account_hides_collections": "Ky përdorues ka zgjedhur të mos e japë këtë informacion",
   "empty_column.account_suspended": "Llogaria u pezullua",
   "empty_column.account_timeline": "S’ka mesazhe këtu!",
@@ -309,6 +273,7 @@
   "empty_column.hashtag": "Ende s’ka gjë nën këtë hashtag.",
   "empty_column.home": "Rrjedha juaj kohore është e zbrazët! Vizitoni {public} ose përdorni kërkimin që t’ia filloni dhe të takoni përdorues të tjerë.",
   "empty_column.list": "Në këtë listë ende s’ka gjë. Kur anëtarë të kësaj liste postojnë gjendje të reja, ato do të shfaqen këtu.",
+  "empty_column.lists": "Ende s’keni ndonjë listë. Kur të krijoni një të tillë, do të duket këtu.",
   "empty_column.mutes": "S’keni heshtuar ende ndonjë përdorues.",
   "empty_column.notification_requests": "Gjithçka si duhet! S’ka ç’bëhet këtu. Kur merrni njoftime të reja, do të shfaqen këtu, në përputhje me rregullimet tuaja.",
   "empty_column.notifications": "Ende s’keni ndonjë njoftim. Ndërveproni me të tjerët që të nisë biseda.",
@@ -319,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Provoni t’i çaktivizoni dhe të rifreskoni faqen. Nëse kjo s’bën punë, mundeni prapë të jeni në gjendje të përdorni Mastodon-in përmes një shfletuesi tjetër, apo një aplikacioni prej Mastodon-it.",
   "errors.unexpected_crash.copy_stacktrace": "Kopjo stacktrace-in në të papastër",
   "errors.unexpected_crash.report_issue": "Raportoni problemin",
+  "explore.search_results": "Përfundime kërkimi",
   "explore.suggested_follows": "Persona",
   "explore.title": "Eksploroni",
   "explore.trending_links": "Lajme",
@@ -368,16 +334,13 @@
   "footer.about": "Mbi",
   "footer.directory": "Drejtori profilesh",
   "footer.get_app": "Merreni aplikacionin",
+  "footer.invite": "Ftoni njerëz",
   "footer.keyboard_shortcuts": "Shkurtore tastiere",
   "footer.privacy_policy": "Rregulla privatësie",
   "footer.source_code": "Shihni kodin burim",
   "footer.status": "Gjendje",
-  "footer.terms_of_service": "Kushte shërbimi",
   "generic.saved": "U ruajt",
   "getting_started.heading": "Si t’ia fillohet",
-  "hashtag.admin_moderation": "Hap ndërfaqe moderimi për #{name}",
-  "hashtag.browse": "Shfletoni postime me #{hashtag}",
-  "hashtag.browse_from_account": "Shfletoni postime nga @{name} me #{hashtag}",
   "hashtag.column_header.tag_mode.all": "dhe {additional}",
   "hashtag.column_header.tag_mode.any": "ose {additional}",
   "hashtag.column_header.tag_mode.none": "pa {additional}",
@@ -391,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} postim} other {{counter} postime}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} postim} other {{counter} postime}} sot",
   "hashtag.follow": "Ndiqe hashtag-un",
-  "hashtag.mute": "Heshtoje #{hashtag}",
   "hashtag.unfollow": "Hiqe ndjekjen e hashtag-ut",
   "hashtags.and_other": "…dhe {count, plural, one {}other {# më tepër}}",
   "hints.profiles.followers_may_be_missing": "Mund të mungojnë ndjekës për këtë profil.",
@@ -420,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Të shpërfillen njoftime nga persona që s’ju ndjekin?",
   "ignore_notifications_modal.not_following_title": "Të shpërfillen njoftime nga persona që s’i ndiqni?",
   "ignore_notifications_modal.private_mentions_title": "Të shpërfillen njoftime nga Përmendje Private të pakërkuara?",
-  "info_button.label": "Ndihmë",
-  "info_button.what_is_alt_text": "<h1>Ç’është teksti alternativ?</h1> <p>Teksti alternativ jep përshkrime figurash për persona me mangësi në të parët, lidhje me gjerësi bande të ulët, ose për ata që duan kontekst shtesë.</p> <p>Mund të përmirësoni përdorimin nga persona me aftësi të kufizuara dhe kuptimin për këto, duke shkruar tekst alternativ të qartë, konciz dhe objektiv.</p> <ul> <li>Rrokni elementët e rëndësishëm</li> <li>Përmblidhni tekst në figura</li> <li>Përdorni strukturë të rregullt fjalish</li> <li>Shmangni përsëritje informacioni</li> <li>Në aspekte pamore të ndërlikuara (fjala vjen, diagrame ose harta) përqendrohuni te prirje dhe gjetje gjërash kyçe</li> </ul>",
-  "interaction_modal.action.favourite": "Që të vazhdoni, lypset t’i vini shenjë si i parapëlqyer që nga llogaria juaj.",
-  "interaction_modal.action.follow": "Që të vazhdoni, lypset ta ndiqni që nga llogaria juaj.",
-  "interaction_modal.action.reblog": "Që të vazhdoni, lypset ta riblogoni që nga llogaria juaj.",
-  "interaction_modal.action.reply": "Që të vazhdoni, lypset të përgjigjeni që nga llogaria juaj.",
-  "interaction_modal.action.vote": "Që të vazhdoni, lypset të votoni që nga llogaria juaj.",
-  "interaction_modal.go": "Shko",
-  "interaction_modal.no_account_yet": "S’keni ende një llogari?",
+  "interaction_modal.description.favourite": "Me një llogari në Mastodon, mund ta tregoni këtë postim si të parapëlqyer, për t’i bërë të ditur autorit se e çmoni dhe e ruani për më vonë.",
+  "interaction_modal.description.follow": "Me një llogari në Mastodon, mund ta ndiqni {name} për të marrë postimet e tyre në prurjen tuaj të kreut.",
+  "interaction_modal.description.reblog": "Me një llogari në Mastodon, mund ta përforconi këtë postim për ta ndarë me ndjekësit tuaj.",
+  "interaction_modal.description.reply": "Me një llogari në Mastodon, mund t’i përgjigjeni këtij postimi.",
+  "interaction_modal.login.action": "Shpjemëni në shtëpi",
+  "interaction_modal.login.prompt": "Përkatësia e shërbyesit tuaj vatër, p.sh. mastodon.social",
+  "interaction_modal.no_account_yet": "S’gjendeni në Mastodon?",
   "interaction_modal.on_another_server": "Në një tjetër shërbyes",
   "interaction_modal.on_this_server": "Në këtë shërbyes",
+  "interaction_modal.sign_in": "S’keni bërë hyrjen në këtë shërbyes. Ku strehoet llogaria juaj?",
+  "interaction_modal.sign_in_hint": "Ndihmës: Ky është sajti ku u regjistruat. Nëse s’e mbani mend, shihni te email-et tuaj për email-in e mirëseardhjes. Mudeni edhe të jepni emrin tuaj të plotë të përdoruesi! (p.sh. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Bëje të parapëlqyer postimin e {name}",
   "interaction_modal.title.follow": "Ndiq {name}",
   "interaction_modal.title.reblog": "Përforconi postimin e {name}",
   "interaction_modal.title.reply": "Përgjigjuni postimit të {name}",
-  "interaction_modal.title.vote": "Votoni te pyetësori nga {name}",
-  "interaction_modal.username_prompt": "P.sh., {example}",
   "intervals.full.days": "{number, plural, one {# ditë} other {# ditë}}",
   "intervals.full.hours": "{number, plural, one {# orë} other {# orë}}",
   "intervals.full.minutes": "{number, plural, one {# minutë} other {# minuta}}",
@@ -472,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Për shfaqje/fshehje teksti pas CW",
   "keyboard_shortcuts.toggle_sensitivity": "Për shfaqje/fshehje mediash",
   "keyboard_shortcuts.toot": "Për të filluar një mesazh të ri",
-  "keyboard_shortcuts.translate": "për të përkthyer një postim",
   "keyboard_shortcuts.unfocus": "Për heqjen e fokusit nga fusha e hartimit të mesazheve apo kërkimeve",
   "keyboard_shortcuts.up": "Për ngjitje sipër nëpër listë",
   "lightbox.close": "Mbylle",
@@ -485,32 +444,20 @@
   "link_preview.author": "Nga {name}",
   "link_preview.more_from_author": "Më tepër nga {name}",
   "link_preview.shares": "{count, plural, one {{counter} post} other {{counter} postime}}",
-  "lists.add_member": "Shtoje",
-  "lists.add_to_list": "Shto në listë",
-  "lists.add_to_lists": "Shtoje {name} në lista",
-  "lists.create": "Krijoje",
-  "lists.create_a_list_to_organize": "Krijoni një listë të re të sistemoni prurjen tuaj Kreu",
-  "lists.create_list": "Krijo listë",
+  "lists.account.add": "Shto në listë",
+  "lists.account.remove": "Hiqe nga lista",
   "lists.delete": "Fshije listën",
-  "lists.done": "U bë",
   "lists.edit": "Përpunoni listën",
-  "lists.exclusive": "Fshihni anëtarët në Krye",
-  "lists.exclusive_hint": "Nëse dikush gjendje në këtë listë, fshihini ata te prurja juaj e Kreut, që të shmangni parjen dy herë të postimeve të tyre.",
-  "lists.find_users_to_add": "Gjeni përdorues për t’i shtuar",
-  "lists.list_members": "Shfaq anëtarë",
-  "lists.list_members_count": "{count, plural, one {# anëtar} other {# anëtarë}}",
-  "lists.list_name": "Emër liste",
-  "lists.new_list_name": "Emër liste të re",
-  "lists.no_lists_yet": "Ende pa lista.",
-  "lists.no_members_yet": "Ende pa anëtarë.",
-  "lists.no_results_found": "S’u gjetën përfundime.",
-  "lists.remove_member": "Hiqe",
+  "lists.edit.submit": "Ndryshoni titullin",
+  "lists.exclusive": "Fshihi këto postime prej kreut",
+  "lists.new.create": "Shtoni listë",
+  "lists.new.title_placeholder": "Titull liste të re",
   "lists.replies_policy.followed": "Cilido përdorues i ndjekur",
   "lists.replies_policy.list": "Anëtarë të listës",
   "lists.replies_policy.none": "Askush",
-  "lists.save": "Ruaje",
-  "lists.search": "Kërko",
-  "lists.show_replies_to": "Përfshi përgjigje nga anëtarë liste te",
+  "lists.replies_policy.title": "Shfaq përgjigje për:",
+  "lists.search": "Kërkoni mes personash që ndiqni",
+  "lists.subheading": "Listat tuaja",
   "load_pending": "{count, plural,one {# objekt i ri }other {# objekte të rinj }}",
   "loading_indicator.label": "Po ngarkohet…",
   "media_gallery.hide": "Fshihe",
@@ -559,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} raportoi {target}",
   "notification.admin.sign_up": "{name} u regjistrua",
   "notification.admin.sign_up.name_and_others": "U regjistrua {name} dhe {count, plural, one {# tjetër} other {# të tjerë}}",
-  "notification.annual_report.message": "#Wrapstodon juaj për {year} pret! Zbuloni pikat e theksuara dhe çastet e paharrueshëm të këtij viti për ju në Mastodon!",
-  "notification.annual_report.view": "Shihni #Wrapstodon",
   "notification.favourite": "{name} i vuri shenjë postimit tuaj si të parapëlqyer",
   "notification.favourite.name_and_others_with_link": "{name} dhe <a>{count, plural, one {# tjetër} other {# të tjerë}}</a> i vunë shenjë postimit tuaj si të parapëlqyer",
-  "notification.favourite_pm": "{name} i vuri shenjë si të parapëlqyer përmendjes tuaj private",
-  "notification.favourite_pm.name_and_others_with_link": "{name} dhe <a>{count, plural, one {# tjetër} other {# të tjerë}}</a> i vunë shenjë si të parapëlqyer përmendjes tuaj private",
   "notification.follow": "{name} zuri t’ju ndjekë",
   "notification.follow.name_and_others": "Ju ndoqi {name} dhe <a>{count, plural, one {# tjetër} other {# të tjerë}}</a>",
   "notification.follow_request": "{name} ka kërkuar t’ju ndjekë",
@@ -669,21 +612,44 @@
   "notifications_permission_banner.enable": "Aktivizo njoftime në desktop",
   "notifications_permission_banner.how_to_control": "Për të marrë njoftime, kur Mastodon-i s’është i hapur, aktivizoni njoftime në desktop. Përmes butoni {icon} më sipër, mund të kontrolloni me përpikëri cilat lloje ndërveprimesh prodhojnë njoftime në desktop, pasi të jenë aktivizuar.",
   "notifications_permission_banner.title": "Mos t’ju shpëtojë gjë",
-  "onboarding.follows.back": "Mbrapsht",
-  "onboarding.follows.done": "U bë",
+  "onboarding.action.back": "Kthemëni pas",
+  "onboarding.actions.back": "Kthemëni pas",
+  "onboarding.actions.go_to_explore": "Shihni ç’është në modë",
+  "onboarding.actions.go_to_home": "Kaloni te prurja juaj kryesore",
+  "onboarding.compose.template": "Tungjatjeta #Mastodon!",
   "onboarding.follows.empty": "Mjerisht, s’mund të shfaqen përfundime tani. Mund të provoni të përdorni kërkimin, ose të shfletoni faqen e eksplorimit, që të gjeni persona për ndjekje, ose të riprovoni më vonë.",
-  "onboarding.follows.search": "Kërkoni",
-  "onboarding.follows.title": "Që t’ia filloni, ndiqni persona",
+  "onboarding.follows.lead": "Ju kujdeseni për prurjen tuaj. Sa më tepër persona të tjerë të ndiqni, aq më aktive dhe interesante do të bëhet ajo. Këto profile mund të jenë një pikënisje e mirë—mundeni përherë të ndërpritni ndjekjen e tyre më vonë!",
+  "onboarding.follows.title": "Popullore në Mastodon",
   "onboarding.profile.discoverable": "Bëje profilin tim të zbulueshëm",
   "onboarding.profile.discoverable_hint": "Kur zgjidhni të jeni i zbulueshëm në Mastodon, postimet tuaja mund të shfaqen në përfundime kërkimesh dhe gjëra në modë dhe profili juaj mund t’u sugjerohet njerëzve me interesa të ngjashme me ju.",
   "onboarding.profile.display_name": "Emër në ekran",
   "onboarding.profile.display_name_hint": "Emri juaj i plotë, ose ç’të doni…",
+  "onboarding.profile.lead": "Këtë mund ta plotësoni përherë më vonë, te rregullimet, ku ka edhe më tepër mundësi përshtatjeje.",
   "onboarding.profile.note": "Jetëshkrim",
   "onboarding.profile.note_hint": "Mund të @përmendni persona të tjerë, ose #hashtagë…",
   "onboarding.profile.save_and_continue": "Ruaje dhe vazhdo",
   "onboarding.profile.title": "Udjisje profili",
   "onboarding.profile.upload_avatar": "Ngarkoni foto profili",
   "onboarding.profile.upload_header": "Ngarkoni krye profili",
+  "onboarding.share.lead": "Bëjuni të ditur njerëzve se si mund t’ju gjejnë në Mastodon!",
+  "onboarding.share.message": "Jam {username} në #Mastodon! Ejani dhe ndiqmëni te {url}",
+  "onboarding.share.next_steps": "Hapa pasues të mundshëm:",
+  "onboarding.share.title": "Ndani me të tjerët profilin tuaj",
+  "onboarding.start.lead": "Llogaria juaj e re Mastodon është gati për punë. Ja se si të përfitoni maksimumin prej saj:",
+  "onboarding.start.skip": "Doni të hidheni drejt e në punë?",
+  "onboarding.start.title": "Ia dolët!",
+  "onboarding.steps.follow_people.body": "Ju kujdeseni për prurjen tuaj. Le ta mbushim me persona interesantë.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Përshëndetni botën.",
+  "onboarding.steps.publish_status.title": "Shkruani postimin tuaj të parë",
+  "onboarding.steps.setup_profile.body": "Ka më tepër gjasa që të tjerët të ndërveprojnë me ju, kur keni një profil të plotësuar.",
+  "onboarding.steps.setup_profile.title": "Personalizoni profilin tuaj",
+  "onboarding.steps.share_profile.body": "Bëjuni të ditur shokëve si t’ju gjejnë në Mastodon!",
+  "onboarding.steps.share_profile.title": "Ndani me të tjerët profilin tuaj",
+  "onboarding.tips.2fa": "<strong>E dini?</strong> Mund të siguroni llogarinë tuaj duke ujdisur mirëfilltësim dyfaktorësh, që nga rregullimet e llogarisë tuaj. Funksionon me çfarëdo aplikacioni TOTP që doni, pa pasur nevojë për numër telefoni!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>E dini?</strong> Ngaqë Mastodon-i është i decentralizuar, disa profile që hasni, do të jenë të strehuar në shërbyes të tjerë nga ai i juaji. E megjithatë, mundeni të ndërveproni me ta në mënyrë të pacen! Shërbyesi i tyre gjendet në pjesën e dytë të emrit të përdoruesit të gjithkujt!",
+  "onboarding.tips.migration": "<strong>E dini?</strong> Nëse ju duket se {domain} s’është zgjidhje kushedi për shërbyes për ju në të ardhmen, mund të kaloni te një shërbyes tjetër Mastodon, pa humbur ndjekësit tuaj. Mundeni edhe të strehoni vetë shërbyesin tuaj!",
+  "onboarding.tips.verification": "<strong>E dini?</strong> Mund të verifikoni llogarinë tuaj duke vendosur në sajtin tuaj një lidhje për te profili juaj Mastodon dhe duke shtuar sajtin tuaj te profili juaj. Pa tarifa, apo pa u dashur dokumente!",
   "password_confirmation.exceeds_maxlength": "Fjalëkalimi i ripohuar tejkalon gjatësinë maksimum të fjalëkalimeve",
   "password_confirmation.mismatching": "Fjalëkalimi i ripohuar nuk përkon",
   "picture_in_picture.restore": "Ktheje ku qe",
@@ -699,7 +665,7 @@
   "poll_button.remove_poll": "Hiqe pyetësorin",
   "privacy.change": "Rregulloni privatësi mesazhesh",
   "privacy.direct.long": "Gjithkënd i përmendur te postimi",
-  "privacy.direct.short": "Përmendje private",
+  "privacy.direct.short": "Persona të veçantë",
   "privacy.private.long": "Vetëm ndjekësit tuaj",
   "privacy.private.short": "Ndjekës",
   "privacy.public.long": "Cilido që hyn e del në Mastodon",
@@ -711,8 +677,8 @@
   "privacy_policy.title": "Rregulla Privatësie",
   "recommended": "E rekomanduar",
   "refresh": "Rifreskoje",
-  "regeneration_indicator.please_stand_by": "Ju lutemi, mos u largoni.",
-  "regeneration_indicator.preparing_your_home_feed": "Po përgatitet prurja juaj e kreut…",
+  "regeneration_indicator.label": "Po ngarkohet…",
+  "regeneration_indicator.sublabel": "Prurja juaj vetjake po përgatitet!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# ditë} other {# ditë}} më parë",
   "relative_time.full.hours": "{number, plural, one {# orë} other {# orë}} më parë",
@@ -796,11 +762,10 @@
   "search_results.accounts": "Profile",
   "search_results.all": "Krejt",
   "search_results.hashtags": "Hashtag-ë",
-  "search_results.no_results": "S’ka përfundime.",
-  "search_results.no_search_yet": "Provoni të kërkoni për postime, profile ose hashtag-ë.",
+  "search_results.nothing_found": "S’u gjet gjë për këto terma kërkimi",
   "search_results.see_all": "Shihni krejt",
   "search_results.statuses": "Mesazhe",
-  "search_results.title": "Kërko për “{q}”",
+  "search_results.title": "Kërkoni për {q}",
   "server_banner.about_active_users": "Persona që përdorin këtë shërbyes gjatë 30 ditëve të fundit (Përdorues Mujorë Aktivë)",
   "server_banner.active_users": "përdorues aktivë",
   "server_banner.administered_by": "Administruar nga:",
@@ -852,7 +817,6 @@
   "status.reblogs.empty": "Këtë mesazh s’e ka përforcuar njeri deri tani. Kur ta bëjë dikush, kjo do të duket këtu.",
   "status.redraft": "Fshijeni & rihartojeni",
   "status.remove_bookmark": "Hiqe faqerojtësin",
-  "status.remove_favourite": "Hiqe nga të parapëlqyerat",
   "status.replied_in_thread": "U përgjigj te rrjedha",
   "status.replied_to": "Iu përgjigj {name}",
   "status.reply": "Përgjigjuni",
@@ -874,9 +838,6 @@
   "subscribed_languages.target": "Ndryshoni gjuhë pajtimesh për {target}",
   "tabs_bar.home": "Kreu",
   "tabs_bar.notifications": "Njoftime",
-  "terms_of_service.effective_as_of": "Hyn në fuqi që prej {date}",
-  "terms_of_service.title": "Kushte Shërbimi",
-  "terms_of_service.upcoming_changes_on": "Ndryshime të ardhshme më {date}",
   "time_remaining.days": "Edhe {number, plural, one {# ditë} other {# ditë}}",
   "time_remaining.hours": "Edhe {number, plural, one {# orë} other {# orë}}",
   "time_remaining.minutes": "Edhe {number, plural, one {# minutë} other {# minuta}}",
@@ -892,12 +853,26 @@
   "upload_button.label": "Shtoni figura, një video ose një kartelë audio",
   "upload_error.limit": "U tejkalua kufi ngarkimi kartelash.",
   "upload_error.poll": "Me pyetësorët s’lejohet ngarkim kartelash.",
+  "upload_form.audio_description": "Përshkruajeni për persona me dëgjim të kufizuar",
+  "upload_form.description": "Përshkruajeni për persona me probleme shikimi",
   "upload_form.drag_and_drop.instructions": "Që të merrni një bashkëngjitje media, shtypni tastin Space ose Enter. Teksa bëhet tërheqje, përdorni tastet shigjetë për ta shpënë bashkëngjitjen media në cilëndo drejtori që doni. Shtypni sërish Space ose Enter që të lihet bashkëngjitja media në pozicionin e vet të ri, ose shtypni Esc, që të anulohet veprimi.",
   "upload_form.drag_and_drop.on_drag_cancel": "Tërheqja u anulua. Bashkëngjitja media {item} u la.",
   "upload_form.drag_and_drop.on_drag_end": "Bashkëngjitja media {item} u la.",
   "upload_form.drag_and_drop.on_drag_over": "Bashkëngjitja media {item} u lëviz.",
   "upload_form.drag_and_drop.on_drag_start": "U mor bashkëngjitja media {item}.",
   "upload_form.edit": "Përpunoni",
+  "upload_form.thumbnail": "Ndryshoni miniaturën",
+  "upload_form.video_description": "Përshkruajeni për persona me dëgjim të kufizuar ose probleme shikimi",
+  "upload_modal.analyzing_picture": "Po analizohet fotoja…",
+  "upload_modal.apply": "Aplikoje",
+  "upload_modal.applying": "Po zbatohet…",
+  "upload_modal.choose_image": "Zgjidhni figurë",
+  "upload_modal.description_placeholder": "Deshe Korçën, Korçën të dhamë",
+  "upload_modal.detect_text": "Pikase tekstin prej fotoje",
+  "upload_modal.edit_media": "Përpunoni media",
+  "upload_modal.hint": "Që të zgjidhni pikën vatrore e cila do të jetë përherë e dukshme në krejt miniaturat, klikojeni ose tërhiqeni rrethin te paraparja.",
+  "upload_modal.preparing_ocr": "Po përgatitet OCR-ja…",
+  "upload_modal.preview_label": "Paraparje ({ratio})",
   "upload_progress.label": "Po ngarkohet…",
   "upload_progress.processing": "Po përpunon…",
   "username.taken": "Ai emër përdoruesi është zënë. Provoni tjetër",
@@ -907,12 +882,8 @@
   "video.expand": "Zgjeroje videon",
   "video.fullscreen": "Sa krejt ekrani",
   "video.hide": "Fshihe videon",
-  "video.mute": "Heshtoje",
+  "video.mute": "Hiqi zërin",
   "video.pause": "Ndalesë",
   "video.play": "Luaje",
-  "video.skip_backward": "Anashkalo pararendësen",
-  "video.skip_forward": "Anashkalo pasardhësen",
-  "video.unmute": "Hiqi heshtimin",
-  "video.volume_down": "Ulje volumi",
-  "video.volume_up": "Ngritje volumi"
+  "video.unmute": "Riktheji zërin"
 }
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index 2d00533e0e..1053d581c3 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -28,6 +28,7 @@
   "account.endorse": "Istakni na profilu",
   "account.featured_tags.last_status_at": "Poslednja objava {date}",
   "account.featured_tags.last_status_never": "Nema objava",
+  "account.featured_tags.title": "Istaknute heš oznake korisnika {name}",
   "account.follow": "Prati",
   "account.follow_back": "Uzvrati praćenje",
   "account.followers": "Pratioci",
@@ -105,6 +106,7 @@
   "bundle_column_error.routing.body": "Nije moguće pronaći traženu stranicu. Da li ste sigurni da je URL u adresnom polju ispravan?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Zatvori",
+  "bundle_modal_error.message": "Nešto je pošlo naopako tokom učitavanja ove komponente.",
   "bundle_modal_error.retry": "Pokušajte ponovo",
   "closed_registrations.other_server_instructions": "Pošto je Mastodon decentralizovan, možete napraviti nalog na drugom serveru ali i dalje komunicirati sa ovim.",
   "closed_registrations_modal.description": "Kreiranje naloga na {domain} trenutno nije moguće, ali imajte u vidu da vam ne treba nalog zasebno na {domain} da biste koristili Mastodon.",
@@ -152,6 +154,7 @@
   "compose_form.poll.duration": "Trajanje ankete",
   "compose_form.poll.multiple": "Višestruki izbor",
   "compose_form.poll.option_placeholder": "Opcija {number}",
+  "compose_form.poll.single": "Odaberite jedno",
   "compose_form.poll.switch_to_multiple": "Promenite anketu da biste omogućili više izbora",
   "compose_form.poll.switch_to_single": "Promenite anketu da biste omogućili jedan izbor",
   "compose_form.poll.type": "Stil",
@@ -196,6 +199,10 @@
   "disabled_account_banner.text": "Vaš nalog {disabledAccount} je trenutno onemogućen.",
   "dismissable_banner.community_timeline": "Ovo su najnovije javne objave ljudi čije naloge hostuje {domain}.",
   "dismissable_banner.dismiss": "Odbaci",
+  "dismissable_banner.explore_links": "Ovo su vesti koje se danas najviše dele na društvenoj mreži. Novije vesti koje je objavilo više različitih ljudi su bolje rangirane.",
+  "dismissable_banner.explore_statuses": "Ovo su objave širom društvenog veba koje danas postaju sve popularnije. Novije objave sa više podržavanja i omiljene su rangirane više.",
+  "dismissable_banner.explore_tags": "Ovo su heš oznake koje danas postaju sve popularnije na društvenoj mreži. Heš oznake koje koristi više različitih ljudi su rangirane više.",
+  "dismissable_banner.public_timeline": "Ovo su najnovije javne objave ljudi sa društvenog veba koje ljudi na {domain}-u prate.",
   "domain_block_modal.block": "Blokiraj server",
   "domain_block_modal.block_account_instead": "Umesto toga, blokiraj @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Ljudi sa ovog servera mogu da imaju interakciju sa vašim starim objavama.",
@@ -250,6 +257,7 @@
   "empty_column.hashtag": "Još uvek nema ničega u ovoj heš oznaci.",
   "empty_column.home": "Vaša početna vremenska linija je prazna! Pratite više ljudi da biste je popunili.",
   "empty_column.list": "U ovoj listi još nema ničega. Kada članovi ove liste objave nešto novo, pojaviće se ovde.",
+  "empty_column.lists": "Još uvek nemate nijednu listu. Kada napravite jednu, ona će se pojaviti ovde.",
   "empty_column.mutes": "Još uvek ne ignorišete nijednog korisnika.",
   "empty_column.notification_requests": "Sve je čisto! Ovde nema ničega. Kada dobijete nova obaveštenja, ona će se pojaviti ovde u skladu sa vašim podešavanjima.",
   "empty_column.notifications": "Još uvek nemate nikakva obaveštenja. Kada drugi ljudi budu u interakciji sa vama, videćete to ovde.",
@@ -260,6 +268,7 @@
   "error.unexpected_crash.next_steps_addons": "Pokušajte da ih onemogućite i osvežite stranicu. Ako to ne pomogne, možete da nastavite da koristite Mastodon korišćenjem drugog pregledača ili matične aplikacije.",
   "errors.unexpected_crash.copy_stacktrace": "Kopiraj „stacktrace” u klipbord",
   "errors.unexpected_crash.report_issue": "Prijavi problem",
+  "explore.search_results": "Rezultati pretrage",
   "explore.suggested_follows": "Ljudi",
   "explore.title": "Istraži",
   "explore.trending_links": "Vesti",
@@ -307,6 +316,7 @@
   "footer.about": "Osnovni podaci",
   "footer.directory": "Direktorijum profila",
   "footer.get_app": "Preuzmite aplikaciju",
+  "footer.invite": "Pozovi osobe",
   "footer.keyboard_shortcuts": "Tasterske prečice",
   "footer.privacy_policy": "Politika privatnosti",
   "footer.source_code": "Prikaži izvorni kod",
@@ -335,8 +345,17 @@
   "home.pending_critical_update.link": "Pogledajte ažuriranja",
   "home.pending_critical_update.title": "Dostupno je kritično bezbednosno ažuriranje!",
   "home.show_announcements": "Prijaži najave",
+  "interaction_modal.description.favourite": "Sa nalogom na Mastodon-u, možete označiti ovu objavu kao omiljenu kako biste dali do znanja autoru da vam se sviđa i sačuvali je za kasnije.",
+  "interaction_modal.description.follow": "Sa nalogom na Mastodon-u, možete pratiti korisnika {name} kako biste primali njegove objave na početnoj stranici.",
+  "interaction_modal.description.reblog": "Sa nalogom na Mastodon-u, možete podržati ovu objavu kako bite je podelili sa svojim pratiocima.",
+  "interaction_modal.description.reply": "Sa nalogom na Mastodon-u, možete odgovoriti na ovu objavu.",
+  "interaction_modal.login.action": "Vodi me na početnu stranicu",
+  "interaction_modal.login.prompt": "Domen vašeg matičnog servera, npr. mastodon.social",
+  "interaction_modal.no_account_yet": "Niste na Mastodon-u?",
   "interaction_modal.on_another_server": "Na drugom serveru",
   "interaction_modal.on_this_server": "Na ovom serveru",
+  "interaction_modal.sign_in": "Niste prijavljeni na ovaj server. Gde je hostovan vaš nalog?",
+  "interaction_modal.sign_in_hint": "Savet: To je veb sajt na kome ste se registrovali. Ako se ne sećate, potražite e-poruku dobrodošlice u svom prijemnom sandučetu. Takođe možete uneti svoje puno korisničko ime! (npr. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Označi objavu korisnika {name} kao omiljenu",
   "interaction_modal.title.follow": "Zaprati {name}",
   "interaction_modal.title.reblog": "Podrži objavu korisnika {name}",
@@ -386,11 +405,20 @@
   "link_preview.author": "Po {name}",
   "link_preview.more_from_author": "Više od {name}",
   "link_preview.shares": "{count, plural, one {{counter} objava} few {{counter} objave} other {{counter} objava}}",
+  "lists.account.add": "Dodaj na listu",
+  "lists.account.remove": "Ukloni sa liste",
   "lists.delete": "Izbriši listu",
   "lists.edit": "Uredi listu",
+  "lists.edit.submit": "Promeni naslov",
+  "lists.exclusive": "Sakrijte ove objave sa početne stranice",
+  "lists.new.create": "Dodaj listu",
+  "lists.new.title_placeholder": "Naslov nove liste",
   "lists.replies_policy.followed": "Svakom praćenom korisniku",
   "lists.replies_policy.list": "Članovima liste",
   "lists.replies_policy.none": "Nikome",
+  "lists.replies_policy.title": "Prikaži odgovore:",
+  "lists.search": "Pretraži među ljudima koje pratite",
+  "lists.subheading": "Vaše liste",
   "load_pending": "{count, plural, one {# nova stavka} few {# nove stavke} other {# novih stavki}}",
   "loading_indicator.label": "Učitavanje…",
   "moved_to_account_banner.text": "Vaš nalog {disabledAccount} je trenutno onemogućen jer ste prešli na {movedToAccount}.",
@@ -500,17 +528,44 @@
   "notifications_permission_banner.enable": "Omogućiti obaveštenja na radnoj površini",
   "notifications_permission_banner.how_to_control": "Da biste primali obaveštenja kada Mastodon nije otvoren, omogućite obaveštenja na radnoj površini. Kada su obaveštenja na radnoj površini omogućena vrste interakcija koje ona generišu mogu se podešavati pomoću dugmeta {icon}.",
   "notifications_permission_banner.title": "Nikada ništa ne propustite",
+  "onboarding.action.back": "Vrati me nazad",
+  "onboarding.actions.back": "Vrati me nazad",
+  "onboarding.actions.go_to_explore": "Odvedi me u trending",
+  "onboarding.actions.go_to_home": "Odvedi me na početnu stranicu",
+  "onboarding.compose.template": "Zdravo #Mastodon!",
   "onboarding.follows.empty": "Nažalost, trenutno se ne mogu prikazati rezultati. Možete pokušati sa korišćenjem pretrage ili pregledanjem stranice za istraživanje da biste pronašli ljude koje ćete pratiti ili pokušajte ponovo kasnije.",
+  "onboarding.follows.lead": "Vaša početna stranica je primarni način da doživite Mastodon. Što više ljudi budete pratili, to će biti aktivnije i zanimljivije. Da biste započeli, evo nekoliko predloga:",
+  "onboarding.follows.title": "Personalizujte svoju početnu stranicu",
   "onboarding.profile.discoverable": "Neka se moj profil može otkriti drugima",
   "onboarding.profile.discoverable_hint": "Kada omogućite mogućnost otkrivanja na Mastodon-u, vaše objave se mogu pojaviti u rezultatima pretrage i u trendu, a vaš profil može biti predložen ljudima sa sličnim interesovanjima.",
   "onboarding.profile.display_name": "Ime za prikaz",
   "onboarding.profile.display_name_hint": "Vaše puno ime ili nadimak…",
+  "onboarding.profile.lead": "Ovo možete uvek dovršiti kasnije u podešavanjima, gde je dostupno još više opcija prilagođavanja.",
   "onboarding.profile.note": "Biografija",
   "onboarding.profile.note_hint": "Možete da @pomenete druge ljude ili #heš oznake…",
   "onboarding.profile.save_and_continue": "Sačuvaj i nastavi",
   "onboarding.profile.title": "Podešavanje profila",
   "onboarding.profile.upload_avatar": "Otpremi sliku profila",
   "onboarding.profile.upload_header": "Otpremi zaglavlje profila",
+  "onboarding.share.lead": "Neka ljudi znaju kako mogu da vas pronađu na Mastodon-u!",
+  "onboarding.share.message": "Ja sam {username} na #Mastodon-u! Pratite me na {url}",
+  "onboarding.share.next_steps": "Mogući sledeći koraci:",
+  "onboarding.share.title": "Podelite svoj profil",
+  "onboarding.start.lead": "Sada ste deo Mastodon-a, jedinstvene, decentralizovane platforme društvenih medija na kojoj vi – a ne algoritam – birate svoje iskustvo. Hajde da počnemo na ovoj novoj društvenoj granici:",
+  "onboarding.start.skip": "Ne treba vam pomoć za početak?",
+  "onboarding.start.title": "Uspeli ste!",
+  "onboarding.steps.follow_people.body": "Praćenje zanimljivih ljudi je ono o čemu se radi u Mastodon-u.",
+  "onboarding.steps.follow_people.title": "Personalizujte svoju početnu stranicu",
+  "onboarding.steps.publish_status.body": "Pozdravite svet tekstom, slikama, video snimcima ili anketama {emoji}",
+  "onboarding.steps.publish_status.title": "Napišite svoju prvu objavu",
+  "onboarding.steps.setup_profile.body": "Pojačajte svoje interakcije tako što ćete imati sveobuhvatan profil.",
+  "onboarding.steps.setup_profile.title": "Personalizujte svoj profil",
+  "onboarding.steps.share_profile.body": "Neka vaši prijatelji znaju kako da vas pronađu na Mastodon-u!",
+  "onboarding.steps.share_profile.title": "Podelite svoj Mastodon profil",
+  "onboarding.tips.2fa": "<strong>Da li ste znali?</strong> Možete da zaštitite svoj nalog podešavanjem dvostruke potvrde identiteta u podešavanjima naloga. Radi sa bilo kojom TOTP aplikacijom po vašem izboru, nije potreban broj telefona!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Da li ste znali?</strong> Pošto je Mastodon decentralizovan, neki profili na koje naiđete biće smešteni na serverima različitim od vašeg. A ipak možete da komunicirate sa njima besprekorno! Njihov server je u drugoj polovini njihovog korisničkog imena!",
+  "onboarding.tips.migration": "<strong>Da li ste znali?</strong> Ako smatrate da {domain} nije odličan izbor servera za vas u budućnosti, možete da pređete na drugi Mastodon server bez gubitka pratilaca. Možete čak i da hostujete sopstveni server!",
+  "onboarding.tips.verification": "<strong>Da li ste znali?</strong> Možete da verifikujete svoj nalog tako što ćete staviti vezu do svog Mastodon profila na svoj veb sajt i dodati veb sajt svom profilu. Nisu potrebne nikakve naknade ili dokumenti!",
   "password_confirmation.exceeds_maxlength": "Potvrda lozinke premašuje maksimalnu dužinu lozinke",
   "password_confirmation.mismatching": "Potvrda lozinke se ne podudara",
   "picture_in_picture.restore": "Vrati nazad",
@@ -526,6 +581,7 @@
   "poll_button.remove_poll": "Ukloni anketu",
   "privacy.change": "Promeni privatnost objave",
   "privacy.direct.long": "Svi pomenuti u objavi",
+  "privacy.direct.short": "Određeni ljudi",
   "privacy.private.long": "Samo vaši pratioci",
   "privacy.private.short": "Pratioci",
   "privacy.public.long": "Bilo ko na Mastodon-u i van njega",
@@ -537,6 +593,8 @@
   "privacy_policy.title": "Politika privatnosti",
   "recommended": "Preporučeno",
   "refresh": "Osveži",
+  "regeneration_indicator.label": "Učitavanje…",
+  "regeneration_indicator.sublabel": "Vaša početna stranica se priprema!",
   "relative_time.days": "{number} dan.",
   "relative_time.full.days": "Pre {number, plural, one {# dan} few {# dana} other {# dana}}",
   "relative_time.full.hours": "pre {number, plural, one {# sat} few {# sata} other {# sati}}",
@@ -616,8 +674,10 @@
   "search_results.accounts": "Profili",
   "search_results.all": "Sve",
   "search_results.hashtags": "Heš oznake",
+  "search_results.nothing_found": "Nije moguće pronaći ništa za ove termine za pretragu",
   "search_results.see_all": "Vidi sve",
   "search_results.statuses": "Objave",
+  "search_results.title": "Traži {q}",
   "server_banner.about_active_users": "Ljudi koji su koristili ovaj server u prethodnih 30 dana (mesečno aktivnih korisnika)",
   "server_banner.active_users": "aktivnih korisnika",
   "server_banner.administered_by": "Administrira:",
@@ -702,7 +762,21 @@
   "upload_button.label": "Dodaj slike, video ili audio datoteku",
   "upload_error.limit": "Dostignuto je ograničenje za otpremanje datoteka.",
   "upload_error.poll": "Otpremanje datoteka nije dozvoljeno kod anketa.",
+  "upload_form.audio_description": "Dodajte opis za osobe sa oštećenim sluhom",
+  "upload_form.description": "Dodajte opis za osobe sa oštećenim vidom",
   "upload_form.edit": "Uredi",
+  "upload_form.thumbnail": "Promeni sličicu",
+  "upload_form.video_description": "Opišite za osobe sa oštećenim sluhom ili vidom",
+  "upload_modal.analyzing_picture": "Analiziranje slike…",
+  "upload_modal.apply": "Primeni",
+  "upload_modal.applying": "Primena…",
+  "upload_modal.choose_image": "Odaberite sliku",
+  "upload_modal.description_placeholder": "Ljubazni fenjerdžija čađavog lica hoće da mi pokaže štos",
+  "upload_modal.detect_text": "Pronađi tekst sa slike",
+  "upload_modal.edit_media": "Uredi multimediju",
+  "upload_modal.hint": "Kliknite ili prevucite kružić na pregledu za izbor tačke fokusa koja će uvek biti vidljiva na svim sličicama.",
+  "upload_modal.preparing_ocr": "Priprema OCR-a…",
+  "upload_modal.preview_label": "Pregled ({ratio})",
   "upload_progress.label": "Otpremanje...",
   "upload_progress.processing": "Obrada…",
   "username.taken": "To korisničko ime je zauzeto. Pokušajte drugo",
@@ -712,6 +786,8 @@
   "video.expand": "Proširi video",
   "video.fullscreen": "Ceo ekran",
   "video.hide": "Sakrij video",
+  "video.mute": "Isključi zvuk",
   "video.pause": "Pauziraj",
-  "video.play": "Reprodukuj"
+  "video.play": "Reprodukuj",
+  "video.unmute": "Uključi zvuk"
 }
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index af323bed27..9529c41fe3 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -28,6 +28,7 @@
   "account.endorse": "Истакни на профилу",
   "account.featured_tags.last_status_at": "Последња објава {date}",
   "account.featured_tags.last_status_never": "Нема објава",
+  "account.featured_tags.title": "Истакнуте хеш ознаке корисника {name}",
   "account.follow": "Прати",
   "account.follow_back": "Узврати праћење",
   "account.followers": "Пратиоци",
@@ -105,6 +106,7 @@
   "bundle_column_error.routing.body": "Није могуће пронаћи тражену страницу. Да ли сте сигурни да је URL у адресном пољу исправан?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Затвори",
+  "bundle_modal_error.message": "Нешто је пошло наопако током учитавања ове компоненте.",
   "bundle_modal_error.retry": "Покушајте поново",
   "closed_registrations.other_server_instructions": "Пошто је Mastodon децентрализован, можете направити налог на другом серверу али и даље комуницирати са овим.",
   "closed_registrations_modal.description": "Креирање налога на {domain} тренутно није могуће, али имајте у виду да вам не треба налог засебно на {domain} да бисте користили Mastodon.",
@@ -152,6 +154,7 @@
   "compose_form.poll.duration": "Трајање анкете",
   "compose_form.poll.multiple": "Вишеструки избор",
   "compose_form.poll.option_placeholder": "Опција {number}",
+  "compose_form.poll.single": "Одаберите једно",
   "compose_form.poll.switch_to_multiple": "Промените анкету да бисте омогућили више избора",
   "compose_form.poll.switch_to_single": "Промените анкету да бисте омогућили један избор",
   "compose_form.poll.type": "Стил",
@@ -196,6 +199,10 @@
   "disabled_account_banner.text": "Ваш налог {disabledAccount} је тренутно онемогућен.",
   "dismissable_banner.community_timeline": "Ово су најновије јавне објаве људи чије налоге хостује {domain}.",
   "dismissable_banner.dismiss": "Одбаци",
+  "dismissable_banner.explore_links": "Ово су вести које се данас највише деле на друштвеној мрежи. Новије вести које је објавило више различитих људи су боље рангиране.",
+  "dismissable_banner.explore_statuses": "Ово су објаве широм друштвеног веба које данас постају све популарније. Новије објаве са више подржавања и омиљене су рангиране више.",
+  "dismissable_banner.explore_tags": "Ово су хеш ознаке које данас постају све популарније на друштвеној мрежи. Хеш ознаке које користи више различитих људи су рангиране више.",
+  "dismissable_banner.public_timeline": "Ово су најновије јавне објаве људи са друштвеног веба које људи на {domain}-у прате.",
   "domain_block_modal.block": "Блокирај сервер",
   "domain_block_modal.block_account_instead": "Уместо тога, блокирај @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Људи са овог сервера могу да имају интеракцију са вашим старим објавама.",
@@ -250,6 +257,7 @@
   "empty_column.hashtag": "Још увек нема ничега у овој хеш ознаци.",
   "empty_column.home": "Ваша почетна временска линија је празна! Пратите више људи да бисте је попунили.",
   "empty_column.list": "У овој листи још нема ничега. Када чланови ове листе објаве нешто ново, појавиће се овде.",
+  "empty_column.lists": "Још увек немате ниједну листу. Када направите једну, она ће се појавити овде.",
   "empty_column.mutes": "Још увек не игноришете ниједног корисника.",
   "empty_column.notification_requests": "Све је чисто! Овде нема ничега. Када добијете нова обавештења, она ће се појавити овде у складу са вашим подешавањима.",
   "empty_column.notifications": "Још увек немате никаква обавештења. Када други људи буду у интеракцији са вама, видећете то овде.",
@@ -260,6 +268,7 @@
   "error.unexpected_crash.next_steps_addons": "Покушајте да их онемогућите и освежите страницу. Ако то не помогне, можете да наставите да користите Mastodon коришћењем другог прегледача или матичне апликације.",
   "errors.unexpected_crash.copy_stacktrace": "Копирај „stacktrace” у клипборд",
   "errors.unexpected_crash.report_issue": "Пријави проблем",
+  "explore.search_results": "Резултати претраге",
   "explore.suggested_follows": "Људи",
   "explore.title": "Истражи",
   "explore.trending_links": "Вести",
@@ -307,6 +316,7 @@
   "footer.about": "Основни подаци",
   "footer.directory": "Директоријум профила",
   "footer.get_app": "Преузмите апликацију",
+  "footer.invite": "Позови особе",
   "footer.keyboard_shortcuts": "Тастерске пречице",
   "footer.privacy_policy": "Политика приватности",
   "footer.source_code": "Прикажи изворни код",
@@ -335,8 +345,17 @@
   "home.pending_critical_update.link": "Погледајте ажурирања",
   "home.pending_critical_update.title": "Доступно је критично безбедносно ажурирање!",
   "home.show_announcements": "Пријажи најаве",
+  "interaction_modal.description.favourite": "Са налогом на Mastodon-у, можете означити ову објаву као омиљену како бисте дали до знања аутору да вам се свиђа и сачували је за касније.",
+  "interaction_modal.description.follow": "Са налогом на Mastodon-у, можете пратити корисника {name} како бисте примали његове објаве на почетној страници.",
+  "interaction_modal.description.reblog": "Са налогом на Mastodon-у, можете подржати ову објаву како бите је поделили са својим пратиоцима.",
+  "interaction_modal.description.reply": "Са налогом на Mastodon-у, можете одговорити на ову објаву.",
+  "interaction_modal.login.action": "Води ме на почетну страницу",
+  "interaction_modal.login.prompt": "Домен вашег матичног сервера, нпр. mastodon.social",
+  "interaction_modal.no_account_yet": "Нисте на Mastodon-у?",
   "interaction_modal.on_another_server": "На другом серверу",
   "interaction_modal.on_this_server": "На овом серверу",
+  "interaction_modal.sign_in": "Нисте пријављени на овај сервер. Где је хостован ваш налог?",
+  "interaction_modal.sign_in_hint": "Савет: То је веб сајт на коме сте се регистровали. Ако се не сећате, потражите е-поруку добродошлице у свом пријемном сандучету. Такође можете унети своје пуно корисничко име! (нпр. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Означи објаву корисника {name} као омиљену",
   "interaction_modal.title.follow": "Запрати {name}",
   "interaction_modal.title.reblog": "Подржи објаву корисника {name}",
@@ -386,11 +405,20 @@
   "link_preview.author": "По {name}",
   "link_preview.more_from_author": "Више од {name}",
   "link_preview.shares": "{count, plural, one {{counter} објава} few {{counter} објаве} other {{counter} објава}}",
+  "lists.account.add": "Додај на листу",
+  "lists.account.remove": "Уклони са листе",
   "lists.delete": "Избриши листу",
   "lists.edit": "Уреди листу",
+  "lists.edit.submit": "Промени наслов",
+  "lists.exclusive": "Сакријте ове објаве са почетне странице",
+  "lists.new.create": "Додај листу",
+  "lists.new.title_placeholder": "Наслов нове листе",
   "lists.replies_policy.followed": "Сваком праћеном кориснику",
   "lists.replies_policy.list": "Члановима листе",
   "lists.replies_policy.none": "Никоме",
+  "lists.replies_policy.title": "Прикажи одговоре:",
+  "lists.search": "Претражи међу људима које пратите",
+  "lists.subheading": "Ваше листе",
   "load_pending": "{count, plural, one {# нова ставка} few {# нове ставке} other {# нових ставки}}",
   "loading_indicator.label": "Учитавање…",
   "moved_to_account_banner.text": "Ваш налог {disabledAccount} је тренутно онемогућен јер сте прешли на {movedToAccount}.",
@@ -500,17 +528,44 @@
   "notifications_permission_banner.enable": "Омогућити обавештења на радној површини",
   "notifications_permission_banner.how_to_control": "Да бисте примали обавештења када Mastodon није отворен, омогућите обавештења на радној површини. Kада су обавештења на радној површини омогућена врсте интеракција које она генеришу могу се подешавати помоћу дугмета {icon}.",
   "notifications_permission_banner.title": "Никада ништа не пропустите",
+  "onboarding.action.back": "Врати ме назад",
+  "onboarding.actions.back": "Врати ме назад",
+  "onboarding.actions.go_to_explore": "Одведи ме у трендинг",
+  "onboarding.actions.go_to_home": "Одведи ме на почетну страницу",
+  "onboarding.compose.template": "Здраво #Mastodon!",
   "onboarding.follows.empty": "Нажалост, тренутно се не могу приказати резултати. Можете покушати са коришћењем претраге или прегледањем странице за истраживање да бисте пронашли људе које ћете пратити или покушајте поново касније.",
+  "onboarding.follows.lead": "Ваша почетна страница је примарни начин да доживите Mastodon. Што више људи будете пратили, то ће бити активније и занимљивије. Да бисте започели, ево неколико предлога:",
+  "onboarding.follows.title": "Персонализујте своју почетну страницу",
   "onboarding.profile.discoverable": "Нека се мој профил може открити другима",
   "onboarding.profile.discoverable_hint": "Када омогућите могућност откривања на Mastodon-у, ваше објаве се могу појавити у резултатима претраге и у тренду, а ваш профил може бити предложен људима са сличним интересовањима.",
   "onboarding.profile.display_name": "Име за приказ",
   "onboarding.profile.display_name_hint": "Ваше пуно име или надимак…",
+  "onboarding.profile.lead": "Ово можете увек довршити касније у подешавањима, где је доступно још више опција прилагођавања.",
   "onboarding.profile.note": "Биографија",
   "onboarding.profile.note_hint": "Можете да @поменете друге људе или #хеш ознаке…",
   "onboarding.profile.save_and_continue": "Сачувај и настави",
   "onboarding.profile.title": "Подешавање профила",
   "onboarding.profile.upload_avatar": "Отпреми слику профила",
   "onboarding.profile.upload_header": "Отпреми заглавље профила",
+  "onboarding.share.lead": "Нека људи знају како могу да вас пронађу на Mastodon-у!",
+  "onboarding.share.message": "Ја сам {username} на #Mastodon-у! Пратите ме на {url}",
+  "onboarding.share.next_steps": "Могући следећи кораци:",
+  "onboarding.share.title": "Поделите свој профил",
+  "onboarding.start.lead": "Сада сте део Mastodon-а, јединствене, децентрализоване платформе друштвених медија на којој ви – а не алгоритам – бирате своје искуство. Хајде да почнемо на овој новој друштвеној граници:",
+  "onboarding.start.skip": "Не треба вам помоћ за почетак?",
+  "onboarding.start.title": "Успели сте!",
+  "onboarding.steps.follow_people.body": "Праћење занимљивих људи је оно о чему се ради у Mastodon-у.",
+  "onboarding.steps.follow_people.title": "Персонализујте своју почетну страницу",
+  "onboarding.steps.publish_status.body": "Поздравите свет текстом, сликама, видео снимцима или анкетама {emoji}",
+  "onboarding.steps.publish_status.title": "Напишите своју прву објаву",
+  "onboarding.steps.setup_profile.body": "Појачајте своје интеракције тако што ћете имати свеобухватан профил.",
+  "onboarding.steps.setup_profile.title": "Персонализујте свој профил",
+  "onboarding.steps.share_profile.body": "Нека ваши пријатељи знају како да вас пронађу на Mastodon-у!",
+  "onboarding.steps.share_profile.title": "Поделите свој Mastodon профил",
+  "onboarding.tips.2fa": "<strong>Да ли сте знали?</strong> Можете да заштитите свој налог подешавањем двоструке потврде идентитета у подешавањима налога. Ради са било којом TOTP апликацијом по вашем избору, није потребан број телефона!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Да ли сте знали?</strong> Пошто је Mastodon децентрализован, неки профили на које наиђете биће смештени на серверима различитим од вашег. А ипак можете да комуницирате са њима беспрекорно! Њихов сервер је у другој половини њиховог корисничког имена!",
+  "onboarding.tips.migration": "<strong>Да ли сте знали?</strong> Ако сматрате да {domain} није одличан избор сервера за вас у будућности, можете да пређете на други Mastodon сервер без губитка пратилаца. Можете чак и да хостујете сопствени сервер!",
+  "onboarding.tips.verification": "<strong>Да ли сте знали?</strong> Можете да верификујете свој налог тако што ћете ставити везу до свог Mastodon профила на свој веб сајт и додати веб сајт свом профилу. Нису потребне никакве накнаде или документи!",
   "password_confirmation.exceeds_maxlength": "Потврда лозинке премашује максималну дужину лозинке",
   "password_confirmation.mismatching": "Потврда лозинке се не подудара",
   "picture_in_picture.restore": "Врати назад",
@@ -526,6 +581,7 @@
   "poll_button.remove_poll": "Уклони анкету",
   "privacy.change": "Промени приватност објаве",
   "privacy.direct.long": "Сви поменути у објави",
+  "privacy.direct.short": "Одређени људи",
   "privacy.private.long": "Само ваши пратиоци",
   "privacy.private.short": "Пратиоци",
   "privacy.public.long": "Било ко на Mastodon-у и ван њега",
@@ -537,6 +593,8 @@
   "privacy_policy.title": "Политика приватности",
   "recommended": "Препоручено",
   "refresh": "Освежи",
+  "regeneration_indicator.label": "Учитавање…",
+  "regeneration_indicator.sublabel": "Ваша почетна страница се припрема!",
   "relative_time.days": "{number} дан.",
   "relative_time.full.days": "Пре {number, plural, one {# дан} few {# дана} other {# дана}}",
   "relative_time.full.hours": "пре {number, plural, one {# сат} few {# сата} other {# сати}}",
@@ -616,8 +674,10 @@
   "search_results.accounts": "Профили",
   "search_results.all": "Све",
   "search_results.hashtags": "Хеш ознаке",
+  "search_results.nothing_found": "Није могуће пронаћи ништа за ове термине за претрагу",
   "search_results.see_all": "Види све",
   "search_results.statuses": "Објаве",
+  "search_results.title": "Тражи {q}",
   "server_banner.about_active_users": "Људи који су користили овај сервер у претходних 30 дана (месечно активних корисника)",
   "server_banner.active_users": "активних корисника",
   "server_banner.administered_by": "Администрира:",
@@ -702,7 +762,21 @@
   "upload_button.label": "Додај слике, видео или аудио датотеку",
   "upload_error.limit": "Достигнуто је ограничење за отпремање датотека.",
   "upload_error.poll": "Отпремање датотека није дозвољено код анкета.",
+  "upload_form.audio_description": "Додајте опис за особе са оштећеним слухом",
+  "upload_form.description": "Додајте опис за особе са оштећеним видом",
   "upload_form.edit": "Уреди",
+  "upload_form.thumbnail": "Промени сличицу",
+  "upload_form.video_description": "Опишите за особе са оштећеним слухом или видом",
+  "upload_modal.analyzing_picture": "Анализирање слике…",
+  "upload_modal.apply": "Примени",
+  "upload_modal.applying": "Примена…",
+  "upload_modal.choose_image": "Одаберите слику",
+  "upload_modal.description_placeholder": "Љубазни фењерџија чађавог лица хоће да ми покаже штос",
+  "upload_modal.detect_text": "Пронађи текст са слике",
+  "upload_modal.edit_media": "Уреди мултимедију",
+  "upload_modal.hint": "Кликните или превуците кружић на прегледу за избор тачке фокуса која ће увек бити видљива на свим сличицама.",
+  "upload_modal.preparing_ocr": "Припрема OCR-а…",
+  "upload_modal.preview_label": "Преглед ({ratio})",
   "upload_progress.label": "Отпремање...",
   "upload_progress.processing": "Обрада…",
   "username.taken": "То корисничко име је заузето. Покушајте друго",
@@ -712,6 +786,8 @@
   "video.expand": "Прошири видео",
   "video.fullscreen": "Цео екран",
   "video.hide": "Сакриј видео",
+  "video.mute": "Искључи звук",
   "video.pause": "Паузирај",
-  "video.play": "Репродукуј"
+  "video.play": "Репродукуј",
+  "video.unmute": "Укључи звук"
 }
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index c23a645135..2ff8be55ab 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -29,6 +29,7 @@
   "account.endorse": "Visa på profil",
   "account.featured_tags.last_status_at": "Senaste inlägg den {date}",
   "account.featured_tags.last_status_never": "Inga inlägg",
+  "account.featured_tags.title": "{name}s utvalda hashtaggar",
   "account.follow": "Följ",
   "account.follow_back": "Följ tillbaka",
   "account.followers": "Följare",
@@ -64,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} inlägg} other {{counter} inlägg}}",
   "account.unblock": "Avblockera @{name}",
   "account.unblock_domain": "Avblockera {domain}",
-  "account.unblock_domain_short": "Avblockera",
   "account.unblock_short": "Avblockera",
   "account.unendorse": "Visa inte på profil",
   "account.unfollow": "Sluta följ",
@@ -86,33 +86,7 @@
   "alert.unexpected.message": "Ett oväntat fel uppstod.",
   "alert.unexpected.title": "Hoppsan!",
   "alt_text_badge.title": "Alt-Text",
-  "alt_text_modal.add_alt_text": "Lägg till alt-text",
-  "alt_text_modal.add_text_from_image": "Lägg till text från bild",
-  "alt_text_modal.cancel": "Avbryt",
-  "alt_text_modal.change_thumbnail": "Ändra miniatyr",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Beskriv detta för personer med hörselnedsättning…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Beskriv detta för personer med synnedsättning…",
-  "alt_text_modal.done": "Klar",
   "announcement.announcement": "Meddelande",
-  "annual_report.summary.archetype.booster": "Häftighetsjägaren",
-  "annual_report.summary.archetype.lurker": "Smygaren",
-  "annual_report.summary.archetype.oracle": "Oraklet",
-  "annual_report.summary.archetype.pollster": "Frågaren",
-  "annual_report.summary.archetype.replier": "Den sociala fjärilen",
-  "annual_report.summary.followers.followers": "följare",
-  "annual_report.summary.followers.total": "{count} totalt",
-  "annual_report.summary.here_it_is": "Här är en tillbakablick på ditt {year}:",
-  "annual_report.summary.highlighted_post.by_favourites": "mest favoritmarkerat inlägg",
-  "annual_report.summary.highlighted_post.by_reblogs": "mest boostat inlägg",
-  "annual_report.summary.highlighted_post.by_replies": "inlägg med flest svar",
-  "annual_report.summary.highlighted_post.possessive": "{name}s",
-  "annual_report.summary.most_used_app.most_used_app": "mest använda app",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "mest använda hashtag",
-  "annual_report.summary.most_used_hashtag.none": "Inga",
-  "annual_report.summary.new_posts.new_posts": "nya inlägg",
-  "annual_report.summary.percentile.text": "<topLabel>Det placerar dig i topp</topLabel><percentage></percentage><bottomLabel>bland {domain} användare.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Vi berättar inte för Bernie.",
-  "annual_report.summary.thanks": "Tack för att du är en del av Mastodon!",
   "attachments_list.unprocessed": "(obehandlad)",
   "audio.hide": "Dölj audio",
   "block_modal.remote_users_caveat": "Vi kommer att be servern {domain} att respektera ditt beslut. Dock garanteras inte efterlevnad eftersom vissa servrar kan hantera blockeringar på olika sätt. Offentliga inlägg kan fortfarande vara synliga för icke-inloggade användare.",
@@ -136,7 +110,7 @@
   "bundle_column_error.routing.body": "Den begärda sidan kunde inte hittas. Är du säker på att adressen angivits korrekt?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Stäng",
-  "bundle_modal_error.message": "Något gick fel när skärmen laddades.",
+  "bundle_modal_error.message": "Något gick fel när komponenten skulle läsas in.",
   "bundle_modal_error.retry": "Försök igen",
   "closed_registrations.other_server_instructions": "Eftersom Mastodon är decentraliserat kan du skapa ett konto på en annan server och fortfarande interagera med denna.",
   "closed_registrations_modal.description": "Det är för närvarande inte möjligt att skapa ett konto på {domain} men kom ihåg att du inte behöver ett konto specifikt på {domain} för att använda Mastodon.",
@@ -147,16 +121,13 @@
   "column.blocks": "Blockerade användare",
   "column.bookmarks": "Bokmärken",
   "column.community": "Lokal tidslinje",
-  "column.create_list": "Skapa lista",
   "column.direct": "Privata omnämnande",
   "column.directory": "Bläddra bland profiler",
   "column.domain_blocks": "Blockerade domäner",
-  "column.edit_list": "Redigera lista",
   "column.favourites": "Favoriter",
   "column.firehose": "Direktflöden",
   "column.follow_requests": "Följarförfrågningar",
   "column.home": "Hem",
-  "column.list_members": "Hantera listmedlemmar",
   "column.lists": "Listor",
   "column.mutes": "Tystade användare",
   "column.notifications": "Notifikationer",
@@ -169,7 +140,6 @@
   "column_header.pin": "Fäst",
   "column_header.show_settings": "Visa inställningar",
   "column_header.unpin": "Ångra fäst",
-  "column_search.cancel": "Avbryt",
   "column_subheading.settings": "Inställningar",
   "community.column_settings.local_only": "Endast lokalt",
   "community.column_settings.media_only": "Endast media",
@@ -188,7 +158,7 @@
   "compose_form.poll.duration": "Varaktighet för omröstning",
   "compose_form.poll.multiple": "Flera val",
   "compose_form.poll.option_placeholder": "Alternativ {number}",
-  "compose_form.poll.single": "Enval",
+  "compose_form.poll.single": "Välj en",
   "compose_form.poll.switch_to_multiple": "Ändra enkät för att tillåta flera val",
   "compose_form.poll.switch_to_single": "Ändra enkät för att tillåta ett enda val",
   "compose_form.poll.type": "Stil",
@@ -212,16 +182,9 @@
   "confirmations.edit.confirm": "Redigera",
   "confirmations.edit.message": "Om du svarar nu kommer det att ersätta meddelandet du håller på att skapa. Är du säker på att du vill fortsätta?",
   "confirmations.edit.title": "Skriva över inlägg?",
-  "confirmations.follow_to_list.confirm": "Följ och lägg till i listan",
-  "confirmations.follow_to_list.message": "Du måste följa {name} för att lägga till dem i en lista.",
-  "confirmations.follow_to_list.title": "Följ användare?",
   "confirmations.logout.confirm": "Logga ut",
   "confirmations.logout.message": "Är du säker på att du vill logga ut?",
   "confirmations.logout.title": "Logga ut?",
-  "confirmations.missing_alt_text.confirm": "Lägg till alt-text",
-  "confirmations.missing_alt_text.message": "Ditt inlägg innehåller media utan alt-text. Om du lägger till beskrivningar blir ditt innehåll tillgängligt för fler personer.",
-  "confirmations.missing_alt_text.secondary": "Posta ändå",
-  "confirmations.missing_alt_text.title": "Lägg till alt-text?",
   "confirmations.mute.confirm": "Tysta",
   "confirmations.redraft.confirm": "Radera & gör om",
   "confirmations.redraft.message": "Är du säker på att du vill radera detta inlägg och göra om det? Favoritmarkeringar, boostar och svar till det ursprungliga inlägget kommer förlora sitt sammanhang.",
@@ -250,10 +213,10 @@
   "disabled_account_banner.text": "Ditt konto {disabledAccount} är för närvarande inaktiverat.",
   "dismissable_banner.community_timeline": "Dessa är de senaste offentliga inläggen från personer vars konton tillhandahålls av {domain}.",
   "dismissable_banner.dismiss": "Avfärda",
-  "dismissable_banner.explore_links": "Dessa nyhetshistorier delas mest på fediversum idag. Nyare nyhetshistorier som publiceras av fler olika personer rankas högre.",
-  "dismissable_banner.explore_statuses": "Dessa inlägg från fediversum vinner dragkraft idag. Nyare inlägg som många boostar och favoritmarkerar rankas högre.",
-  "dismissable_banner.explore_tags": "De här hashtaggarna vinner dragkraft i fediversum idag. Hashtaggar som används av fler olika personer rankas högre.",
-  "dismissable_banner.public_timeline": "De här är de aktuella publika inlägg från personer i fediversum som personer i {domain} följer.",
+  "dismissable_banner.explore_links": "Dessa nyheter pratas det om just nu, på denna och på andra servrar i det decentraliserade nätverket.",
+  "dismissable_banner.explore_statuses": "Dessa inlägg, från denna och andra servrar i det decentraliserade nätverket, pratas det om just nu på denna server.",
+  "dismissable_banner.explore_tags": "Dessa hashtaggar pratas det om just nu bland folk på denna och andra servrar i det decentraliserade nätverket.",
+  "dismissable_banner.public_timeline": "De här är de aktuella publika inlägg från personer på det sociala nätet som personer i {domain} följer.",
   "domain_block_modal.block": "Blockera server",
   "domain_block_modal.block_account_instead": "Blockera @{name} istället",
   "domain_block_modal.they_can_interact_with_old_posts": "Personer från denna server kan interagera med dina gamla inlägg.",
@@ -310,6 +273,7 @@
   "empty_column.hashtag": "Det finns inget i denna hashtag ännu.",
   "empty_column.home": "Din hemma-tidslinje är tom! Följ fler användare för att fylla den.",
   "empty_column.list": "Det finns inget i denna lista än. När listmedlemmar publicerar nya inlägg kommer de synas här.",
+  "empty_column.lists": "Du har inga listor än. När skapar en kommer den dyka upp här.",
   "empty_column.mutes": "Du har ännu inte tystat några användare.",
   "empty_column.notification_requests": "Allt klart! Det finns inget mer här. När du får nya meddelanden visas de här enligt dina inställningar.",
   "empty_column.notifications": "Du har inga meddelanden än. Interagera med andra för att starta konversationen.",
@@ -320,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Prova att avaktivera dem och uppdatera sidan. Om detta inte hjälper kan du försöka använda Mastodon med en annan webbläsare eller en app.",
   "errors.unexpected_crash.copy_stacktrace": "Kopiera stacktrace till urklipp",
   "errors.unexpected_crash.report_issue": "Rapportera problem",
+  "explore.search_results": "Sökresultat",
   "explore.suggested_follows": "Personer",
   "explore.title": "Utforska",
   "explore.trending_links": "Nyheter",
@@ -369,14 +334,13 @@
   "footer.about": "Om",
   "footer.directory": "Profilkatalog",
   "footer.get_app": "Skaffa appen",
+  "footer.invite": "Bjud in personer",
   "footer.keyboard_shortcuts": "Tangentbordsgenvägar",
   "footer.privacy_policy": "Integritetspolicy",
   "footer.source_code": "Visa källkod",
   "footer.status": "Status",
-  "footer.terms_of_service": "Användarvillkor",
   "generic.saved": "Sparad",
   "getting_started.heading": "Kom igång",
-  "hashtag.admin_moderation": "Öppet modereringsgränssnittet för #{name}",
   "hashtag.column_header.tag_mode.all": "och {additional}",
   "hashtag.column_header.tag_mode.any": "eller {additional}",
   "hashtag.column_header.tag_mode.none": "utan {additional}",
@@ -418,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Vill du ignorera aviseringar från personer som inte följer dig?",
   "ignore_notifications_modal.not_following_title": "Vill du blockera aviseringar från personer som du inte följer dig?",
   "ignore_notifications_modal.private_mentions_title": "Vill du ignorera aviseringar från oombedda privata omnämnanden?",
-  "info_button.label": "Hjälp",
-  "info_button.what_is_alt_text": "<h1>Vad är alt-text?</h1> <p>alt-text ger bildbeskrivningar för personer med synnedsättning, anslutningar med låg bandbredd eller de som söker extra sammanhang.</p> <p>Du kan förbättra tillgängligheten och förståelsen för alla genom att skriva en tydlig, koncis och objektiv alt-text.</p> <ul> <li>Fånga viktiga element</li> <li>Sammanfatta text i bilder</li> <li>Använd vanlig meningsstruktur</li> <li>Undvik överflödig information</li> <li>Fokus på trender och viktiga resultat i komplexa bilder (som diagram eller kartor)</li> </ul>",
-  "interaction_modal.action.favourite": "För att fortsätta, måste du favoritmarkera från ditt konto.",
-  "interaction_modal.action.follow": "För att fortsätta, måste du följa från ditt konto.",
-  "interaction_modal.action.reblog": "För att fortsätta, måste du boosta från ditt konto.",
-  "interaction_modal.action.reply": "För att fortsätta, måste du svara från ditt konto.",
-  "interaction_modal.action.vote": "För att fortsätta, måste du rösta från ditt konto.",
-  "interaction_modal.go": "Vidare",
-  "interaction_modal.no_account_yet": "Har du inget konto än?",
+  "interaction_modal.description.favourite": "Med ett Mastodon-konto kan du favoritmarkera detta inlägg för att visa författaren att du gillar det och för att spara det till senare.",
+  "interaction_modal.description.follow": "Med ett Mastodon-konto kan du följa {name} för att se deras inlägg i ditt hemflöde.",
+  "interaction_modal.description.reblog": "Med ett Mastodon-konto kan du boosta detta inlägg för att dela den med dina egna följare.",
+  "interaction_modal.description.reply": "Med ett Mastodon-konto kan du svara på detta inlägg.",
+  "interaction_modal.login.action": "Ta mig hem",
+  "interaction_modal.login.prompt": "Domän för din hemserver, t.ex. mastodon.social",
+  "interaction_modal.no_account_yet": "Inte på Mastodon?",
   "interaction_modal.on_another_server": "På en annan server",
   "interaction_modal.on_this_server": "På denna server",
+  "interaction_modal.sign_in": "Du är inte inloggad på den här servern. Var är ditt konto ifrån?",
+  "interaction_modal.sign_in_hint": "Tips: Det är den webbplats där du registrerade dig. Om du inte kommer ihåg, leta efter välkomstmeddelandet i din mejlinkorg. Du kan också ange ditt fullständiga användarnamn! (t.ex. @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Favoritmarkera {name}s inlägg",
   "interaction_modal.title.follow": "Följ {name}",
   "interaction_modal.title.reblog": "Boosta {name}s inlägg",
   "interaction_modal.title.reply": "Svara på {name}s inlägg",
-  "interaction_modal.title.vote": "Rösta i {name}s enkät",
-  "interaction_modal.username_prompt": "T.ex. {example}",
   "intervals.full.days": "{number, plural, one {# dag} other {# dagar}}",
   "intervals.full.hours": "{number, plural, one {# timme} other {# timmar}}",
   "intervals.full.minutes": "{number, plural, one {# minut} other {# minuter}}",
@@ -470,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Visa/gömma text bakom CW",
   "keyboard_shortcuts.toggle_sensitivity": "Visa/gömma media",
   "keyboard_shortcuts.toot": "Starta nytt inlägg",
-  "keyboard_shortcuts.translate": "för att översätta ett inlägg",
   "keyboard_shortcuts.unfocus": "Avfokusera skrivfält/sökfält",
   "keyboard_shortcuts.up": "Flytta uppåt i listan",
   "lightbox.close": "Stäng",
@@ -483,32 +444,20 @@
   "link_preview.author": "Av {name}",
   "link_preview.more_from_author": "Mer från {name}",
   "link_preview.shares": "{count, plural, one {{counter} inlägg} other {{counter} inlägg}}",
-  "lists.add_member": "Lägg till",
-  "lists.add_to_list": "Lägg till i lista",
-  "lists.add_to_lists": "Lägg till {name} i listor",
-  "lists.create": "Skapa",
-  "lists.create_a_list_to_organize": "Skapa en ny lista för att organisera ditt hemtidlinje",
-  "lists.create_list": "Skapa lista",
+  "lists.account.add": "Lägg till i lista",
+  "lists.account.remove": "Ta bort från lista",
   "lists.delete": "Radera lista",
-  "lists.done": "Klar",
   "lists.edit": "Redigera lista",
-  "lists.exclusive": "Dölj medlemmar i Hem flödet",
-  "lists.exclusive_hint": "Om någon är med på den här listan, göm dem i ditt Hemtidlinje för att undvika att se deras inlägg två gånger.",
-  "lists.find_users_to_add": "Hitta användare att lägga till",
-  "lists.list_members": "Lista medlemmar",
-  "lists.list_members_count": "{count, plural, one {# medlem} other {# medlemmar}}",
-  "lists.list_name": "Listnamn",
-  "lists.new_list_name": "Nytt listnamn",
-  "lists.no_lists_yet": "Ännu inga listor.",
-  "lists.no_members_yet": "Inga medlemmar ännu.",
-  "lists.no_results_found": "Inga resultat hittades.",
-  "lists.remove_member": "Ta bort",
+  "lists.edit.submit": "Ändra titel",
+  "lists.exclusive": "Dölj dessa inlägg från hemflödet",
+  "lists.new.create": "Lägg till lista",
+  "lists.new.title_placeholder": "Ny listrubrik",
   "lists.replies_policy.followed": "Alla användare som följs",
   "lists.replies_policy.list": "Medlemmar i listan",
   "lists.replies_policy.none": "Ingen",
-  "lists.save": "Spara",
-  "lists.search": "Sök",
-  "lists.show_replies_to": "Inkludera svar från listmedlemmar till",
+  "lists.replies_policy.title": "Visa svar till:",
+  "lists.search": "Sök bland personer du följer",
+  "lists.subheading": "Dina listor",
   "load_pending": "{count, plural, one {# nytt objekt} other {# nya objekt}}",
   "loading_indicator.label": "Laddar…",
   "media_gallery.hide": "Dölj",
@@ -557,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} rapporterade {target}",
   "notification.admin.sign_up": "{name} registrerade sig",
   "notification.admin.sign_up.name_and_others": "{name} och {count, plural, one {# en annan} other {# andra}} har registrerat sig",
-  "notification.annual_report.message": "Din {year} #Wrapstodon väntar! Upptäck ditt års höjdpunkter och minnesvärda stunder på Mastodon!",
-  "notification.annual_report.view": "Visa #Wrapstodon",
   "notification.favourite": "{name} favoritmarkerade ditt inlägg",
   "notification.favourite.name_and_others_with_link": "{name} och <a>{count, plural, one {# annan} other {# andra}}</a> har favoritmarkerat ditt inlägg",
-  "notification.favourite_pm": "{name} favoritmarkerade ditt privata omnämnande",
-  "notification.favourite_pm.name_and_others_with_link": "{name} och <a>{count, plural, one {# annan} other {# andra}}</a> favoritmarkerade ditt privata omnämnande",
   "notification.follow": "{name} följer dig",
   "notification.follow.name_and_others": "{name} och <a>{count, plural, one {# annan} other {# andra}}</a> följer dig",
   "notification.follow_request": "{name} har begärt att följa dig",
@@ -667,21 +612,44 @@
   "notifications_permission_banner.enable": "Aktivera skrivbordsaviseringar",
   "notifications_permission_banner.how_to_control": "För att ta emot aviseringar när Mastodon inte är öppet, aktivera skrivbordsaviseringar. När de är aktiverade kan du styra exakt vilka typer av interaktioner som aviseras via {icon} -knappen ovan.",
   "notifications_permission_banner.title": "Missa aldrig något",
-  "onboarding.follows.back": "Tillbaka",
-  "onboarding.follows.done": "Färdig",
+  "onboarding.action.back": "Ta mig tillbaka",
+  "onboarding.actions.back": "Ta mig tillbaka",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Ta mig till mitt hemflöde",
+  "onboarding.compose.template": "Hallå #Mastodon!",
   "onboarding.follows.empty": "Tyvärr kan inga resultat visas just nu. Du kan prova att använda sökfunktionen eller utforska sidan för att hitta personer att följa, eller försök igen senare.",
-  "onboarding.follows.search": "Sök",
-  "onboarding.follows.title": "Följ människor för att komma igång",
+  "onboarding.follows.lead": "Ditt hemflöde är det primära sättet att uppleva Mastodon. Ju fler människor du följer, desto mer aktiv och intressant blir det. För att komma igång, är här några förslag:",
+  "onboarding.follows.title": "Anpassa ditt hemflöde",
   "onboarding.profile.discoverable": "Gör min profil upptäckbar",
   "onboarding.profile.discoverable_hint": "När du väljer att vara upptäckbar på Mastodon kan dina inlägg visas i sök- och trendresultat, och din profil kan föreslås för personer med liknande intressen som du.",
   "onboarding.profile.display_name": "Visningsnamn",
   "onboarding.profile.display_name_hint": "Fullständigt namn eller ditt roliga namn…",
+  "onboarding.profile.lead": "Du kan alltid slutföra detta senare i inställningarna, där ännu fler anpassningsalternativ finns tillgängliga.",
   "onboarding.profile.note": "Bio",
   "onboarding.profile.note_hint": "Du kan @nämna andra personer eller #hashtags…",
   "onboarding.profile.save_and_continue": "Spara och fortsätt",
   "onboarding.profile.title": "Konfiguration av profil",
   "onboarding.profile.upload_avatar": "Ladda upp profilbild",
   "onboarding.profile.upload_header": "Ladda upp profilbanner",
+  "onboarding.share.lead": "Låt folk veta hur de kan hitta dig på Mastodon!",
+  "onboarding.share.message": "Jag är {username} på #Mastodon! Följ mig på {url}",
+  "onboarding.share.next_steps": "Möjliga nästa steg:",
+  "onboarding.share.title": "Dela din profil",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "Du klarade det!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Säg hej till världen med text, foton, videor eller omröstningar {emoji}",
+  "onboarding.steps.publish_status.title": "Gör ditt första inlägg",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Dela din profil",
+  "onboarding.tips.2fa": "<strong>Visste du att?</strong> Du kan säkra ditt konto genom att ställa in tvåfaktorsautentisering i dina kontoinställningar. Det fungerar med valfri TOTP-app; inget telefonnummer behövs!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Visste du att?</strong> Eftersom Mastodon är decentraliserat kommer vissa profiler som du stöter på att finnas på andra servrar än dina. Ändå kan du interagera med dem sömlöst! Deras server är i den andra halvan av deras användarnamn!",
+  "onboarding.tips.migration": "<strong>Visste du?</strong> Om du känner för att {domain} inte är ett bra serverval för dig i framtiden, kan du flytta till en annan Mastodon-server utan att förlora dina följare. Du kan även vara värd för din egen server!",
+  "onboarding.tips.verification": "<strong>Visste du?</strong> Du kan verifiera ditt konto genom att sätta en länk till din Mastodon-profil på din egen webbplats och lägga till webbplatsen i din profil. Inga avgifter eller dokument behövs!",
   "password_confirmation.exceeds_maxlength": "Lösenordsbekräftelsen överskrider den maximala längden på lösenordet",
   "password_confirmation.mismatching": "Lösenordsbekräftelsen matchar inte",
   "picture_in_picture.restore": "Lägg tillbaka det",
@@ -697,7 +665,7 @@
   "poll_button.remove_poll": "Ta bort omröstning",
   "privacy.change": "Ändra inläggsintegritet",
   "privacy.direct.long": "Alla som nämns i inlägget",
-  "privacy.direct.short": "Privat omnämnande",
+  "privacy.direct.short": "Särskilda personer",
   "privacy.private.long": "Endast dina följare",
   "privacy.private.short": "Följare",
   "privacy.public.long": "Alla på och utanför Mastodon",
@@ -709,8 +677,8 @@
   "privacy_policy.title": "Integritetspolicy",
   "recommended": "Rekommenderas",
   "refresh": "Läs om",
-  "regeneration_indicator.please_stand_by": "Vänligen vänta.",
-  "regeneration_indicator.preparing_your_home_feed": "Förbereder ditt hemflöde…",
+  "regeneration_indicator.label": "Laddar…",
+  "regeneration_indicator.sublabel": "Ditt hemmaflöde förbereds!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# dag} other {# dagar}} sedan",
   "relative_time.full.hours": "{number, plural, one {# timme} other {# timmar}} sedan",
@@ -794,11 +762,10 @@
   "search_results.accounts": "Profiler",
   "search_results.all": "Alla",
   "search_results.hashtags": "Hashtaggar",
-  "search_results.no_results": "Inga resultat.",
-  "search_results.no_search_yet": "Prova att söka efter inlägg, profiler eller hashtags.",
+  "search_results.nothing_found": "Kunde inte hitta något för dessa sökord",
   "search_results.see_all": "Visa alla",
   "search_results.statuses": "Inlägg",
-  "search_results.title": "Sök efter \"{q}\"",
+  "search_results.title": "Sök efter {q}",
   "server_banner.about_active_users": "Personer som använt denna server de senaste 30 dagarna (månatligt aktiva användare)",
   "server_banner.active_users": "aktiva användare",
   "server_banner.administered_by": "Administrerad av:",
@@ -850,7 +817,6 @@
   "status.reblogs.empty": "Ingen har boostat detta inlägg än. När någon gör det kommer de synas här.",
   "status.redraft": "Radera & gör om",
   "status.remove_bookmark": "Ta bort bokmärke",
-  "status.remove_favourite": "Ta bort från Favoriter",
   "status.replied_in_thread": "Svarade i tråden",
   "status.replied_to": "Svarade på {name}",
   "status.reply": "Svara",
@@ -872,7 +838,6 @@
   "subscribed_languages.target": "Ändra språkprenumerationer för {target}",
   "tabs_bar.home": "Hem",
   "tabs_bar.notifications": "Aviseringar",
-  "terms_of_service.title": "Användarvillkor",
   "time_remaining.days": "{number, plural, one {# dag} other {# dagar}} kvar",
   "time_remaining.hours": "{number, plural, one {# timme} other {# timmar}} kvar",
   "time_remaining.minutes": "{number, plural, one {# minut} other {# minuter}} kvar",
@@ -888,12 +853,26 @@
   "upload_button.label": "Lägg till media",
   "upload_error.limit": "Filöverföringsgränsen överskriden.",
   "upload_error.poll": "Filuppladdning tillåts inte med omröstningar.",
+  "upload_form.audio_description": "Beskriv för personer med hörselnedsättning",
+  "upload_form.description": "Beskriv för synskadade",
   "upload_form.drag_and_drop.instructions": "För att plocka upp en mediebilaga, tryck på mellanslag eller enter. Använd piltangenterna för att flytta mediebilagan. Tryck på mellanslag eller enter igen för att släppa mediebilagan i sin nya position, eller tryck på escape för att avbryta.",
   "upload_form.drag_and_drop.on_drag_cancel": "Flytten avbröts. Mediebilagan {item} släpptes.",
   "upload_form.drag_and_drop.on_drag_end": "Mediebilagan {item} släpptes.",
   "upload_form.drag_and_drop.on_drag_over": "Mediebilagan {item} flyttades.",
   "upload_form.drag_and_drop.on_drag_start": "Mediebilagan {item} plockades upp.",
   "upload_form.edit": "Redigera",
+  "upload_form.thumbnail": "Ändra miniatyr",
+  "upload_form.video_description": "Beskriv för personer med hörsel- eller synnedsättning",
+  "upload_modal.analyzing_picture": "Analyserar bild…",
+  "upload_modal.apply": "Verkställ",
+  "upload_modal.applying": "Verkställer…",
+  "upload_modal.choose_image": "Välj bild",
+  "upload_modal.description_placeholder": "En snabb brun räv hoppar över den lata hunden",
+  "upload_modal.detect_text": "Upptäck bildens text",
+  "upload_modal.edit_media": "Redigera media",
+  "upload_modal.hint": "Klicka eller dra cirkeln på förhandstitten för att välja den fokusering som alltid kommer synas på alla miniatyrer.",
+  "upload_modal.preparing_ocr": "Förbereder OCR…",
+  "upload_modal.preview_label": "Förhandstitt ({ratio})",
   "upload_progress.label": "Laddar upp...",
   "upload_progress.processing": "Bearbetar…",
   "username.taken": "Det användarnamnet är upptaget. Försök med ett annat",
@@ -903,12 +882,8 @@
   "video.expand": "Expandera video",
   "video.fullscreen": "Helskärm",
   "video.hide": "Dölj video",
-  "video.mute": "Tysta",
+  "video.mute": "Stäng av ljud",
   "video.pause": "Pausa",
   "video.play": "Spela upp",
-  "video.skip_backward": "Hoppa bakåt",
-  "video.skip_forward": "Hoppa framåt",
-  "video.unmute": "Avtysta",
-  "video.volume_down": "Volym ned",
-  "video.volume_up": "Volym upp"
+  "video.unmute": "Spela upp ljud"
 }
diff --git a/app/javascript/mastodon/locales/szl.json b/app/javascript/mastodon/locales/szl.json
index b7b930422c..404f7e2fd4 100644
--- a/app/javascript/mastodon/locales/szl.json
+++ b/app/javascript/mastodon/locales/szl.json
@@ -31,6 +31,8 @@
   "compose_form.spoiler.marked": "Text is hidden behind warning",
   "compose_form.spoiler.unmarked": "Text is not hidden",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "empty_column.account_timeline": "No toots here!",
   "empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
@@ -73,6 +75,19 @@
   "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
   "notification.reblog": "{name} boosted your status",
   "notifications.column_settings.status": "New toots:",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "privacy.change": "Adjust status privacy",
   "report.placeholder": "Type or paste additional comments",
   "report.submit": "Submit report",
@@ -88,5 +103,8 @@
   "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "Uploading…"
 }
diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json
index 69c0029b7f..87d6660f05 100644
--- a/app/javascript/mastodon/locales/ta.json
+++ b/app/javascript/mastodon/locales/ta.json
@@ -70,6 +70,7 @@
   "bundle_column_error.routing.body": "கேட்கப்பட்ட பக்கத்தைக் காணவில்லை. நீங்கள் உள்ளிட்ட முகவரி சரியனதா?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "மூடுக",
+  "bundle_modal_error.message": "இக்கூற்றை ஏற்றம் செய்யும்பொழுது ஏதோ தவறு ஏற்பட்டுள்ளது.",
   "bundle_modal_error.retry": "மீண்டும் முயற்சி செய்",
   "closed_registrations.other_server_instructions": "மேச்டடான் இரு பரவலாக்கப்பட்ட மென்பொருள் என்பதால், நீங்கள் வேரு ஒரு வழங்கியில் கணக்கை உருவாக்கியிருந்தாலும் இந்த வழங்கியில் பயன்படுத்தலாம்.",
   "closed_registrations_modal.description": "{domain} இல் இப்பொழுது கணக்குகள் உருவாக்க முடியாது. நீங்கள் மேச்டடான் பயன்படுத்த, குறிப்பாக {domain} முகவரியில் கணக்கைத் துவங்க வேண்டும் என்ற அவசியமில்லை என்பதை மனதில் வைத்துக் கொள்ளவும்.",
@@ -143,6 +144,8 @@
   "directory.local": "{domain} களத்திலிருந்து மட்டும்",
   "directory.new_arrivals": "புதிய வரவு",
   "directory.recently_active": "சற்றுமுன் செயல்பாட்டில் இருந்தவர்கள்",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "இந்தப் பதிவை உங்கள் வலைதளத்தில் பொதிக்கக் கீழே உள்ள வரிகளை காப்பி செய்யவும்.",
   "embed.preview": "பார்க்க இப்படி இருக்கும்:",
   "emoji_button.activity": "செயல்பாடு",
@@ -171,6 +174,7 @@
   "empty_column.hashtag": "இந்த சிட்டையில் இதுவரை ஏதும் இல்லை.",
   "empty_column.home": "உங்கள் மாஸ்டடான் வீட்டில் யாரும் இல்லை. {public} -இல் சென்று பார்க்கவும், அல்லது தேடல் கருவியைப் பயன்படுத்திப் பிற பயனர்களைக் கண்டடையவும்.",
   "empty_column.list": "இந்தப் பட்டியலில் இதுவரை ஏதும் இல்லை. இப்பட்டியலின் உறுப்பினர்கள் புதிய டூட்டுகளை இட்டால். அவை இங்கே காண்பிக்கப்படும்.",
+  "empty_column.lists": "இதுவரை நீங்கள் எந்தப் பட்டியலையும் உருவாக்கவில்லை. உருவாக்கினால், அது இங்கே காண்பிக்கப்படும்.",
   "empty_column.mutes": "நீங்கள் இதுவரை எந்தப் பயனர்களையும் முடக்கியிருக்கவில்லை.",
   "empty_column.notifications": "உங்களுக்காக எந்த அறிவிப்புகளும் இல்லை. உரையாடலைத் துவங்க பிறரைத் தொடர்புகொள்ளவும்.",
   "empty_column.public": "இங்கு எதுவும் இல்லை! இவ்விடத்தை நிரப்ப எதையேனும் எழுதவும், அல்லது வேறு சர்வர்களில் உள்ள பயனர்களைப் பின்தொடரவும்",
@@ -238,8 +242,15 @@
   "lightbox.close": "நெருக்கமாக",
   "lightbox.next": "அடுத்த",
   "lightbox.previous": "சென்ற",
+  "lists.account.add": "பட்டியலில் சேர்",
+  "lists.account.remove": "பட்டியலில் இருந்து அகற்று",
   "lists.delete": "பட்டியலை நீக்கு",
   "lists.edit": "பட்டியலை திருத்து",
+  "lists.edit.submit": "தலைப்பு மாற்றவும்",
+  "lists.new.create": "பட்டியலில் சேர்",
+  "lists.new.title_placeholder": "புதிய பட்டியல் தலைப்பு",
+  "lists.search": "நீங்கள் பின்தொடரும் நபர்கள் மத்தியில் தேடுதல்",
+  "lists.subheading": "உங்கள் பட்டியல்கள்",
   "load_pending": "{count, plural,one {# புதியது}other {# புதியவை}}",
   "navigation_bar.blocks": "தடுக்கப்பட்ட பயனர்கள்",
   "navigation_bar.bookmarks": "அடையாளக்குறிகள்",
@@ -281,6 +292,19 @@
   "notifications.filter.mentions": "குறிப்பிடுகிறார்",
   "notifications.filter.polls": "கருத்துக்கணிப்பு முடிவுகள்",
   "notifications.group": "{count} அறிவிப்புகள்",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "poll.closed": "மூடிய",
   "poll.refresh": "பத்துயிர்ப்ப?ட்டு",
   "poll.total_people": "{count, plural, one {# நபர்} other {# நபர்கள்}}",
@@ -292,6 +316,8 @@
   "privacy.change": "நிலை தனியுரிமை",
   "privacy.public.short": "பொது",
   "refresh": "புதுப்பி",
+  "regeneration_indicator.label": "சுமையேற்றம்…",
+  "regeneration_indicator.sublabel": "உங்கள் வீட்டு ஊட்டம் தயார் செய்யப்படுகிறது!",
   "relative_time.days": "{number}நா",
   "relative_time.hours": "{number}ம",
   "relative_time.just_now": "இப்பொழுது",
@@ -359,7 +385,19 @@
   "upload_button.label": "மீடியாவைச் சேர்க்கவும் (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "கோப்பு பதிவேற்ற வரம்பு மீறப்பட்டது.",
   "upload_error.poll": "கோப்பு பதிவேற்றம் அனுமதிக்கப்படவில்லை.",
+  "upload_form.audio_description": "செவித்திறன் குறைபாடு உள்ளவர்களுக்காக விளக்குக‌",
+  "upload_form.description": "பார்வையற்ற விவரிக்கவும்",
   "upload_form.edit": "தொகு",
+  "upload_form.thumbnail": "சிறுபடத்தை மாற்ற",
+  "upload_form.video_description": "செவித்திறன் மற்றும் பார்வைக் குறைபாடு உள்ளவர்களுக்காக விளக்குக‌",
+  "upload_modal.analyzing_picture": "படம் ஆராயப்படுகிறது…",
+  "upload_modal.apply": "உபயோகி",
+  "upload_modal.choose_image": "படத்தைத் தேர்வுசெய்ய",
+  "upload_modal.description_placeholder": "பொருள் விளக்கம்",
+  "upload_modal.detect_text": "படத்தில் இருக்கும் எழுத்தை கண்டறி",
+  "upload_modal.edit_media": "படத்தைத் தொகு",
+  "upload_modal.hint": "எல்லா வில்லைப்பட்த்திலும் தெரியவேண்டிய, படத்தின் முக்கிய குவியப்புள்ளிக்கு, வட்டத்தை சொடுக்கி இழுத்துச்செல்லவும்.",
+  "upload_modal.preview_label": "முன்னோட்டம் ({ratio})",
   "upload_progress.label": "ஏற்றுகிறது ...",
   "video.close": "வீடியோவை மூடு",
   "video.download": "கோப்பைப் பதிவிறக்கவும்",
@@ -367,6 +405,8 @@
   "video.expand": "வீடியோவை விரிவாக்கு",
   "video.fullscreen": "முழுத்திரை",
   "video.hide": "வீடியோவை மறை",
+  "video.mute": "ஒலி முடக்கவும்",
   "video.pause": "இடைநிறுத்து",
-  "video.play": "விளையாடு"
+  "video.play": "விளையாடு",
+  "video.unmute": "ஒலி மெளனமாக இல்லை"
 }
diff --git a/app/javascript/mastodon/locales/tai.json b/app/javascript/mastodon/locales/tai.json
index 1e7548e378..6a86088fa8 100644
--- a/app/javascript/mastodon/locales/tai.json
+++ b/app/javascript/mastodon/locales/tai.json
@@ -19,6 +19,8 @@
   "compose_form.spoiler.marked": "Î-tû luē-iông kíng-kò",
   "compose_form.spoiler.unmarked": "Tsing-ka luē-iông kíng-kò",
   "confirmations.delete.message": "Lí kám bueh thâi-tiāu tsi̍t-ē huah-siann?",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "empty_column.account_timeline": "No toots here!",
   "empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
@@ -61,6 +63,19 @@
   "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
   "notification.reblog": "{name} boosted your status",
   "notifications.column_settings.status": "New toots:",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "privacy.change": "Adjust status privacy",
   "report.placeholder": "Type or paste additional comments",
   "report.submit": "Submit report",
@@ -76,5 +91,8 @@
   "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "Uploading…"
 }
diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json
index 959c380787..b7aa196d7c 100644
--- a/app/javascript/mastodon/locales/te.json
+++ b/app/javascript/mastodon/locales/te.json
@@ -36,6 +36,7 @@
   "boost_modal.combo": "మీరు తదుపరిసారి దీనిని దాటవేయడానికి {combo} నొక్కవచ్చు",
   "bundle_column_error.retry": "మళ్ళీ ప్రయత్నించండి",
   "bundle_modal_error.close": "మూసివేయు",
+  "bundle_modal_error.message": "ఈ భాగం లోడ్ అవుతున్నప్పుడు ఏదో తప్పు జరిగింది.",
   "bundle_modal_error.retry": "మళ్ళీ ప్రయత్నించండి",
   "column.blocks": "బ్లాక్ చేయబడిన వినియోగదారులు",
   "column.community": "స్థానిక కాలక్రమం",
@@ -78,6 +79,8 @@
   "confirmations.reply.message": "ఇప్పుడే ప్రత్యుత్తరం ఇస్తే మీరు ప్రస్తుతం వ్రాస్తున్న సందేశం తిరగరాయబడుతుంది. మీరు ఖచ్చితంగా కొనసాగించాలనుకుంటున్నారా?",
   "confirmations.unfollow.confirm": "అనుసరించవద్దు",
   "confirmations.unfollow.message": "{name}ను మీరు ఖచ్చితంగా అనుసరించవద్దనుకుంటున్నారా?",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "దిగువ కోడ్ను కాపీ చేయడం ద్వారా మీ వెబ్సైట్లో ఈ స్టేటస్ ని పొందుపరచండి.",
   "embed.preview": "అది ఈ క్రింది విధంగా కనిపిస్తుంది:",
   "emoji_button.activity": "కార్యకలాపాలు",
@@ -103,6 +106,7 @@
   "empty_column.hashtag": "ఇంకా హాష్ ట్యాగ్లో ఏమీ లేదు.",
   "empty_column.home": "మీ హోమ్ కాలక్రమం ఖాళీగా ఉంది! {Public} ను సందర్శించండి లేదా ఇతర వినియోగదారులను కలుసుకోవడానికి మరియు అన్వేషణ కోసం శోధనను ఉపయోగించండి.",
   "empty_column.list": "ఇంకా ఈ జాబితాలో ఏదీ లేదు. ఈ జాబితాలోని సభ్యులు కొత్త స్టేటస్ లను పోస్ట్ చేసినప్పుడు, అవి ఇక్కడ కనిపిస్తాయి.",
+  "empty_column.lists": "మీకు ఇంకా జాబితాలు ఏమీ లేవు. మీరు ఒకటి సృష్టించగానే, అది ఇక్కడ కనబడుతుంది.",
   "empty_column.mutes": "మీరు ఇంకా ఏ వినియోగదారులనూ మ్యూట్ చేయలేదు.",
   "empty_column.notifications": "మీకు ఇంకా ఏ నోటిఫికేషన్లు లేవు. సంభాషణను ప్రారంభించడానికి ఇతరులతో ప్రతిస్పందించండి.",
   "empty_column.public": "ఇక్కడ ఏమీ లేదు! దీన్ని నింపడానికి బహిరంగంగా ఏదైనా వ్రాయండి, లేదా ఇతర సేవికల నుండి వినియోగదారులను అనుసరించండి",
@@ -154,8 +158,15 @@
   "lightbox.close": "మూసివేయు",
   "lightbox.next": "తరువాత",
   "lightbox.previous": "మునుపటి",
+  "lists.account.add": "జాబితాకు జోడించు",
+  "lists.account.remove": "జాబితా నుండి తొలగించు",
   "lists.delete": "జాబితాను తొలగించు",
   "lists.edit": "జాబితాను సవరించు",
+  "lists.edit.submit": "శీర్షిక మార్చు",
+  "lists.new.create": "జాబితాను జోడించు",
+  "lists.new.title_placeholder": "కొత్త జాబితా శీర్షిక",
+  "lists.search": "మీరు అనుసరించే వ్యక్తులలో శోధించండి",
+  "lists.subheading": "మీ జాబితాలు",
   "navigation_bar.blocks": "బ్లాక్ చేయబడిన వినియోగదారులు",
   "navigation_bar.community_timeline": "స్థానిక కాలక్రమం",
   "navigation_bar.compose": "కొత్త టూట్ను రాయండి",
@@ -191,6 +202,19 @@
   "notifications.filter.mentions": "పేర్కొన్నవి",
   "notifications.filter.polls": "ఎన్నిక ఫలితాలు",
   "notifications.group": "{count} ప్రకటనలు",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "poll.closed": "మూసివేయబడినవి",
   "poll.refresh": "నవీకరించు",
   "poll.vote": "ఎన్నుకోండి",
@@ -198,6 +222,8 @@
   "poll_button.remove_poll": "ఎన్నికను తొలగించు",
   "privacy.change": "స్టేటస్ గోప్యతను సర్దుబాటు చేయండి",
   "privacy.public.short": "ప్రజా",
+  "regeneration_indicator.label": "లోడ్ అవుతోంది…",
+  "regeneration_indicator.sublabel": "మీ హోమ్ ఫీడ్ సిద్ధమవుతోంది!",
   "relative_time.just_now": "ఇప్పుడు",
   "reply_indicator.cancel": "రద్దు చెయ్యి",
   "report.forward": "{target}కి ఫార్వార్డ్ చేయండి",
@@ -209,6 +235,7 @@
   "search.placeholder": "శోధన",
   "search_results.hashtags": "హాష్ ట్యాగ్లు",
   "search_results.statuses": "టూట్లు",
+  "search_results.title": "{q}",
   "sign_in_banner.sign_in": "Sign in",
   "status.admin_account": "@{name} కొరకు సమన్వయ వినిమయసీమను తెరువు",
   "status.admin_status": "సమన్వయ వినిమయసీమలో ఈ స్టేటస్ ను తెరవండి",
@@ -251,12 +278,17 @@
   "ui.beforeunload": "మీరు మాస్టొడొన్ను వదిలివేస్తే మీ డ్రాఫ్ట్లు పోతాయి.",
   "upload_area.title": "అప్లోడ్ చేయడానికి డ్రాగ్ & డ్రాప్ చేయండి",
   "upload_button.label": "మీడియాను జోడించండి (JPEG, PNG, GIF, WebM, MP4, MOV)",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "దృష్టి లోపమున్న వారి కోసం వివరించండి",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "అప్లోడ్ అవుతోంది...",
   "video.close": "వీడియోని మూసివేయి",
   "video.exit_fullscreen": "పూర్తి స్క్రీన్ నుండి నిష్క్రమించు",
   "video.expand": "వీడియోను విస్తరించండి",
   "video.fullscreen": "పూర్తి స్క్రీన్",
   "video.hide": "వీడియోను దాచు",
+  "video.mute": "ధ్వనిని మ్యూట్ చేయి",
   "video.pause": "పాజ్ చేయి",
-  "video.play": "ప్లే చేయి"
+  "video.play": "ప్లే చేయి",
+  "video.unmute": "ధ్వనిని అన్మ్యూట్ చేయి"
 }
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 429d0db428..6bc92281d8 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -29,6 +29,7 @@
   "account.endorse": "แสดงในโปรไฟล์",
   "account.featured_tags.last_status_at": "โพสต์ล่าสุดเมื่อ {date}",
   "account.featured_tags.last_status_never": "ไม่มีโพสต์",
+  "account.featured_tags.title": "แฮชแท็กที่น่าสนใจของ {name}",
   "account.follow": "ติดตาม",
   "account.follow_back": "ติดตามกลับ",
   "account.followers": "ผู้ติดตาม",
@@ -64,7 +65,6 @@
   "account.statuses_counter": "{count, plural, other {{counter} โพสต์}}",
   "account.unblock": "เลิกปิดกั้น @{name}",
   "account.unblock_domain": "เลิกปิดกั้นโดเมน {domain}",
-  "account.unblock_domain_short": "เลิกปิดกั้น",
   "account.unblock_short": "เลิกปิดกั้น",
   "account.unendorse": "ไม่แสดงในโปรไฟล์",
   "account.unfollow": "เลิกติดตาม",
@@ -86,31 +86,7 @@
   "alert.unexpected.message": "เกิดข้อผิดพลาดที่ไม่คาดคิด",
   "alert.unexpected.title": "อุปส์!",
   "alt_text_badge.title": "ข้อความแสดงแทน",
-  "alt_text_modal.add_alt_text": "เพิ่มข้อความแสดงแทน",
-  "alt_text_modal.add_text_from_image": "เพิ่มข้อความจากภาพ",
-  "alt_text_modal.cancel": "ยกเลิก",
-  "alt_text_modal.change_thumbnail": "เปลี่ยนภาพขนาดย่อ",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "อธิบายสิ่งนี้สำหรับผู้คนที่มีความบกพร่องทางการได้ยิน…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "อธิบายสิ่งนี้สำหรับผู้คนที่มีความบกพร่องทางการมองเห็น…",
-  "alt_text_modal.done": "เสร็จสิ้น",
   "announcement.announcement": "ประกาศ",
-  "annual_report.summary.archetype.booster": "ผู้ล่าความเจ๋ง",
-  "annual_report.summary.archetype.lurker": "ผู้ซุ่มอ่านข่าว",
-  "annual_report.summary.archetype.oracle": "ผู้ให้คำปรึกษา",
-  "annual_report.summary.archetype.pollster": "ผู้สำรวจความคิดเห็น",
-  "annual_report.summary.archetype.replier": "ผู้ชอบเข้าสังคม",
-  "annual_report.summary.followers.followers": "ผู้ติดตาม",
-  "annual_report.summary.followers.total": "รวม {count}",
-  "annual_report.summary.highlighted_post.by_favourites": "โพสต์ที่ได้รับการชื่นชอบมากที่สุด",
-  "annual_report.summary.highlighted_post.by_reblogs": "โพสต์ที่ได้รับการดันมากที่สุด",
-  "annual_report.summary.highlighted_post.by_replies": "โพสต์ที่มีการตอบกลับมากที่สุด",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "แอปที่ใช้มากที่สุด",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "แฮชแท็กที่ใช้มากที่สุด",
-  "annual_report.summary.most_used_hashtag.none": "ไม่มี",
-  "annual_report.summary.new_posts.new_posts": "โพสต์ใหม่",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "เราจะไม่บอก Bernie",
-  "annual_report.summary.thanks": "ขอบคุณสำหรับการเป็นส่วนหนึ่งของ Mastodon!",
   "attachments_list.unprocessed": "(ยังไม่ได้ประมวลผล)",
   "audio.hide": "ซ่อนเสียง",
   "block_modal.remote_users_caveat": "เราจะขอให้เซิร์ฟเวอร์ {domain} เคารพการตัดสินใจของคุณ อย่างไรก็ตาม ไม่รับประกันการปฏิบัติตามข้อกำหนดเนื่องจากเซิร์ฟเวอร์บางแห่งอาจจัดการการปิดกั้นแตกต่างกัน โพสต์สาธารณะอาจยังคงปรากฏแก่ผู้ใช้ที่ไม่ได้เข้าสู่ระบบ",
@@ -134,7 +110,7 @@
   "bundle_column_error.routing.body": "ไม่พบหน้าที่ขอ คุณแน่ใจหรือไม่ว่า URL ในแถบที่อยู่ถูกต้อง?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "ปิด",
-  "bundle_modal_error.message": "มีบางอย่างผิดพลาดขณะโหลดหน้าจอนี้",
+  "bundle_modal_error.message": "มีบางอย่างผิดพลาดขณะโหลดส่วนประกอบนี้",
   "bundle_modal_error.retry": "ลองอีกครั้ง",
   "closed_registrations.other_server_instructions": "เนื่องจาก Mastodon เป็นแบบกระจายศูนย์ คุณสามารถสร้างบัญชีในเซิร์ฟเวอร์อื่นและยังคงโต้ตอบกับเซิร์ฟเวอร์นี้",
   "closed_registrations_modal.description": "ไม่สามารถสร้างบัญชีใน {domain} ได้ในปัจจุบัน แต่โปรดจำไว้ว่าคุณไม่จำเป็นต้องมีบัญชีใน {domain} โดยเฉพาะเพื่อใช้ Mastodon",
@@ -145,16 +121,13 @@
   "column.blocks": "ผู้ใช้ที่ปิดกั้นอยู่",
   "column.bookmarks": "ที่คั่นหน้า",
   "column.community": "เส้นเวลาในเซิร์ฟเวอร์",
-  "column.create_list": "สร้างรายการ",
   "column.direct": "การกล่าวถึงแบบส่วนตัว",
   "column.directory": "เรียกดูโปรไฟล์",
   "column.domain_blocks": "โดเมนที่ปิดกั้นอยู่",
-  "column.edit_list": "แก้ไขรายการ",
   "column.favourites": "รายการโปรด",
   "column.firehose": "ฟีดสด",
   "column.follow_requests": "คำขอติดตาม",
   "column.home": "หน้าแรก",
-  "column.list_members": "จัดการสมาชิกของรายการ",
   "column.lists": "รายการ",
   "column.mutes": "ผู้ใช้ที่ซ่อนอยู่",
   "column.notifications": "การแจ้งเตือน",
@@ -167,7 +140,6 @@
   "column_header.pin": "ปักหมุด",
   "column_header.show_settings": "แสดงการตั้งค่า",
   "column_header.unpin": "ถอนหมุด",
-  "column_search.cancel": "ยกเลิก",
   "column_subheading.settings": "การตั้งค่า",
   "community.column_settings.local_only": "ในเซิร์ฟเวอร์เท่านั้น",
   "community.column_settings.media_only": "สื่อเท่านั้น",
@@ -186,7 +158,7 @@
   "compose_form.poll.duration": "ระยะเวลาการสำรวจความคิดเห็น",
   "compose_form.poll.multiple": "หลายตัวเลือก",
   "compose_form.poll.option_placeholder": "ตัวเลือก {number}",
-  "compose_form.poll.single": "ตัวเลือกเดียว",
+  "compose_form.poll.single": "เลือกอย่างใดอย่างหนึ่ง",
   "compose_form.poll.switch_to_multiple": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตหลายตัวเลือก",
   "compose_form.poll.switch_to_single": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตตัวเลือกเดียว",
   "compose_form.poll.type": "ลักษณะ",
@@ -210,15 +182,9 @@
   "confirmations.edit.confirm": "แก้ไข",
   "confirmations.edit.message": "การแก้ไขในตอนนี้จะเขียนทับข้อความที่คุณกำลังเขียนในปัจจุบัน คุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?",
   "confirmations.edit.title": "เขียนทับโพสต์?",
-  "confirmations.follow_to_list.confirm": "ติดตามและเพิ่มไปยังรายการ",
-  "confirmations.follow_to_list.title": "ติดตามผู้ใช้?",
   "confirmations.logout.confirm": "ออกจากระบบ",
   "confirmations.logout.message": "คุณแน่ใจหรือไม่ว่าต้องการออกจากระบบ?",
   "confirmations.logout.title": "ออกจากระบบ?",
-  "confirmations.missing_alt_text.confirm": "เพิ่มข้อความแสดงแทน",
-  "confirmations.missing_alt_text.message": "โพสต์ของคุณมีสื่อที่ไม่มีข้อความแสดงแทน การเพิ่มคำอธิบายช่วยทำให้เนื้อหาของคุณเข้าถึงผู้คนได้มากขึ้น",
-  "confirmations.missing_alt_text.secondary": "โพสต์ต่อไป",
-  "confirmations.missing_alt_text.title": "เพิ่มข้อความแสดงแทน?",
   "confirmations.mute.confirm": "ซ่อน",
   "confirmations.redraft.confirm": "ลบแล้วร่างใหม่",
   "confirmations.redraft.message": "คุณแน่ใจหรือไม่ว่าต้องการลบโพสต์นี้แล้วร่างโพสต์ใหม่? รายการโปรดและการดันจะสูญหาย และการตอบกลับโพสต์ดั้งเดิมจะไม่มีความเกี่ยวพัน",
@@ -247,6 +213,10 @@
   "disabled_account_banner.text": "มีการปิดใช้งานบัญชีของคุณ {disabledAccount} ในปัจจุบัน",
   "dismissable_banner.community_timeline": "นี่คือโพสต์สาธารณะล่าสุดจากผู้คนที่บัญชีได้รับการโฮสต์โดย {domain}",
   "dismissable_banner.dismiss": "ปิด",
+  "dismissable_banner.explore_links": "นี่เป็นเรื่องข่าวที่ได้รับการแชร์มากที่สุดในเว็บสังคมวันนี้ เรื่องข่าวที่ใหม่กว่าที่มีคนโพสต์มากกว่าจะได้รับการจัดอันดับที่สูงกว่า",
+  "dismissable_banner.explore_statuses": "นี่คือโพสต์จากทั่วทั้งเว็บสังคมที่กำลังได้รับความสนใจวันนี้ โพสต์ที่ใหม่กว่าที่มีการดันและรายการโปรดมากกว่าจะได้รับการจัดอันดับที่สูงกว่า",
+  "dismissable_banner.explore_tags": "นี่คือแฮชแท็กที่กำลังได้รับความสนใจในเว็บสังคมวันนี้ แฮชแท็กที่มีการใช้โดยผู้คนต่าง ๆ มากกว่าจะได้รับการจัดอันดับที่สูงกว่า",
+  "dismissable_banner.public_timeline": "นี่คือโพสต์สาธารณะล่าสุดจากผู้คนในเว็บสังคมที่ผู้คนใน {domain} ติดตาม",
   "domain_block_modal.block": "ปิดกั้นเซิร์ฟเวอร์",
   "domain_block_modal.block_account_instead": "ปิดกั้น @{name} แทน",
   "domain_block_modal.they_can_interact_with_old_posts": "ผู้คนจากเซิร์ฟเวอร์นี้สามารถโต้ตอบกับโพสต์เก่า ๆ ของคุณ",
@@ -303,6 +273,7 @@
   "empty_column.hashtag": "ยังไม่มีสิ่งใดในแฮชแท็กนี้",
   "empty_column.home": "เส้นเวลาหน้าแรกของคุณว่างเปล่า! ติดตามผู้คนเพิ่มเติมเพื่อเติมเส้นเวลาให้เต็ม",
   "empty_column.list": "ยังไม่มีสิ่งใดในรายการนี้ เมื่อสมาชิกของรายการนี้โพสต์โพสต์ใหม่ โพสต์จะปรากฏที่นี่",
+  "empty_column.lists": "คุณยังไม่มีรายการใด ๆ เมื่อคุณสร้างรายการ รายการจะปรากฏที่นี่",
   "empty_column.mutes": "คุณยังไม่ได้ซ่อนผู้ใช้ใด ๆ",
   "empty_column.notification_requests": "โล่งทั้งหมด! ไม่มีสิ่งใดที่นี่ เมื่อคุณได้รับการแจ้งเตือนใหม่ การแจ้งเตือนจะปรากฏที่นี่ตามการตั้งค่าของคุณ",
   "empty_column.notifications": "คุณยังไม่มีการแจ้งเตือนใด ๆ เมื่อผู้คนอื่น ๆ โต้ตอบกับคุณ คุณจะเห็นการแจ้งเตือนที่นี่",
@@ -313,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "ลองปิดใช้งานส่วนเสริมหรือเครื่องมือเหล่านั้นและรีเฟรชหน้า หากนั่นไม่ช่วย คุณอาจยังสามารถใช้ Mastodon ได้ผ่านเบราว์เซอร์อื่นหรือแอปเนทีฟ",
   "errors.unexpected_crash.copy_stacktrace": "คัดลอกการติดตามสแตกไปยังคลิปบอร์ด",
   "errors.unexpected_crash.report_issue": "รายงานปัญหา",
+  "explore.search_results": "ผลลัพธ์การค้นหา",
   "explore.suggested_follows": "ผู้คน",
   "explore.title": "สำรวจ",
   "explore.trending_links": "ข่าว",
@@ -362,14 +334,13 @@
   "footer.about": "เกี่ยวกับ",
   "footer.directory": "ไดเรกทอรีโปรไฟล์",
   "footer.get_app": "รับแอป",
+  "footer.invite": "เชิญผู้คน",
   "footer.keyboard_shortcuts": "แป้นพิมพ์ลัด",
   "footer.privacy_policy": "นโยบายความเป็นส่วนตัว",
   "footer.source_code": "ดูโค้ดต้นฉบับ",
   "footer.status": "สถานะ",
-  "footer.terms_of_service": "เงื่อนไขการให้บริการ",
   "generic.saved": "บันทึกแล้ว",
   "getting_started.heading": "เริ่มต้นใช้งาน",
-  "hashtag.admin_moderation": "เปิดส่วนติดต่อการกลั่นกรองสำหรับ #{name}",
   "hashtag.column_header.tag_mode.all": "และ {additional}",
   "hashtag.column_header.tag_mode.any": "หรือ {additional}",
   "hashtag.column_header.tag_mode.none": "โดยไม่มี {additional}",
@@ -411,22 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "เพิกเฉยการแจ้งเตือนจากผู้คนที่ไม่ได้ติดตามคุณ?",
   "ignore_notifications_modal.not_following_title": "เพิกเฉยการแจ้งเตือนจากผู้คนที่คุณไม่ได้ติดตาม?",
   "ignore_notifications_modal.private_mentions_title": "เพิกเฉยการแจ้งเตือนจากการกล่าวถึงแบบส่วนตัวที่ไม่พึงประสงค์?",
-  "info_button.label": "ช่วยเหลือ",
-  "interaction_modal.action.favourite": "เพื่อดำเนินการต่อ คุณจำเป็นต้องชื่นชอบจากบัญชีของคุณ",
-  "interaction_modal.action.follow": "เพื่อดำเนินการต่อ คุณจำเป็นต้องติดตามจากบัญชีของคุณ",
-  "interaction_modal.action.reblog": "เพื่อดำเนินการต่อ คุณจำเป็นต้องดันจากบัญชีของคุณ",
-  "interaction_modal.action.reply": "เพื่อดำเนินการต่อ คุณจำเป็นต้องตอบกลับจากบัญชีของคุณ",
-  "interaction_modal.action.vote": "เพื่อดำเนินการต่อ คุณจำเป็นต้องลงคะแนนจากบัญชีของคุณ",
-  "interaction_modal.go": "ไป",
-  "interaction_modal.no_account_yet": "ยังไม่มีบัญชี?",
+  "interaction_modal.description.favourite": "ด้วยบัญชีใน Mastodon คุณสามารถชื่นชอบโพสต์นี้เพื่อแจ้งให้ผู้สร้างทราบว่าคุณชื่นชมโพสต์และบันทึกโพสต์ไว้สำหรับภายหลัง",
+  "interaction_modal.description.follow": "ด้วยบัญชีใน Mastodon คุณสามารถติดตาม {name} เพื่อรับโพสต์ของเขาในฟีดหน้าแรกของคุณ",
+  "interaction_modal.description.reblog": "ด้วยบัญชีใน Mastodon คุณสามารถดันโพสต์นี้เพื่อแชร์โพสต์กับผู้ติดตามของคุณเอง",
+  "interaction_modal.description.reply": "ด้วยบัญชีใน Mastodon คุณสามารถตอบกลับโพสต์นี้",
+  "interaction_modal.login.action": "นำฉันกลับบ้าน",
+  "interaction_modal.login.prompt": "โดเมนของเซิร์ฟเวอร์บ้านของคุณ เช่น mastodon.social",
+  "interaction_modal.no_account_yet": "ไม่ได้อยู่ใน Mastodon?",
   "interaction_modal.on_another_server": "ในเซิร์ฟเวอร์อื่น",
   "interaction_modal.on_this_server": "ในเซิร์ฟเวอร์นี้",
+  "interaction_modal.sign_in": "คุณไม่ได้เข้าสู่ระบบเซิร์ฟเวอร์นี้ บัญชีของคุณโฮสต์อยู่ที่ไหน?",
+  "interaction_modal.sign_in_hint": "เคล็ดลับ: นั่นคือเว็บไซต์ที่คุณได้ลงทะเบียน หากคุณจำไม่ได้ มองหาอีเมลต้อนรับในกล่องขาเข้าของคุณ คุณยังสามารถป้อนชื่อผู้ใช้เต็มของคุณ! (เช่น @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "ชื่นชอบโพสต์ของ {name}",
   "interaction_modal.title.follow": "ติดตาม {name}",
   "interaction_modal.title.reblog": "ดันโพสต์ของ {name}",
   "interaction_modal.title.reply": "ตอบกลับโพสต์ของ {name}",
-  "interaction_modal.title.vote": "ลงคะแนนในการสำรวจความคิดเห็นของ {name}",
-  "interaction_modal.username_prompt": "เช่น {example}",
   "intervals.full.days": "{number, plural, other {# วัน}}",
   "intervals.full.hours": "{number, plural, other {# ชั่วโมง}}",
   "intervals.full.minutes": "{number, plural, other {# นาที}}",
@@ -462,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "แสดง/ซ่อนข้อความที่อยู่หลังคำเตือนเนื้อหา",
   "keyboard_shortcuts.toggle_sensitivity": "แสดง/ซ่อนสื่อ",
   "keyboard_shortcuts.toot": "เริ่มโพสต์ใหม่",
-  "keyboard_shortcuts.translate": "เพื่อแปลโพสต์",
   "keyboard_shortcuts.unfocus": "เลิกโฟกัสพื้นที่เขียนข้อความ/การค้นหา",
   "keyboard_shortcuts.up": "ย้ายขึ้นในรายการ",
   "lightbox.close": "ปิด",
@@ -475,32 +444,20 @@
   "link_preview.author": "โดย {name}",
   "link_preview.more_from_author": "เพิ่มเติมจาก {name}",
   "link_preview.shares": "{count, plural, other {{counter} โพสต์}}",
-  "lists.add_member": "เพิ่ม",
-  "lists.add_to_list": "เพิ่มไปยังรายการ",
-  "lists.add_to_lists": "เพิ่ม {name} ไปยังรายการ",
-  "lists.create": "สร้าง",
-  "lists.create_a_list_to_organize": "สร้างรายการใหม่เพื่อจัดระเบียบฟีดหน้าแรกของคุณ",
-  "lists.create_list": "สร้างรายการ",
+  "lists.account.add": "เพิ่มไปยังรายการ",
+  "lists.account.remove": "เอาออกจากรายการ",
   "lists.delete": "ลบรายการ",
-  "lists.done": "เสร็จสิ้น",
   "lists.edit": "แก้ไขรายการ",
-  "lists.exclusive": "ซ่อนสมาชิกในหน้าแรก",
-  "lists.exclusive_hint": "หากใครสักคนอยู่ในรายการนี้ ให้ซ่อนเขาในฟีดหน้าแรกของคุณเพื่อหลีกเลี่ยงการเห็นโพสต์ของเขาสองครั้ง",
-  "lists.find_users_to_add": "ค้นหาผู้ใช้ที่จะเพิ่ม",
-  "lists.list_members": "สมาชิกของรายการ",
-  "lists.list_members_count": "{count, plural, other {# สมาชิก}}",
-  "lists.list_name": "ชื่อรายการ",
-  "lists.new_list_name": "ชื่อรายการใหม่",
-  "lists.no_lists_yet": "ยังไม่มีรายการ",
-  "lists.no_members_yet": "ยังไม่มีสมาชิก",
-  "lists.no_results_found": "ไม่พบผลลัพธ์",
-  "lists.remove_member": "เอาออก",
+  "lists.edit.submit": "เปลี่ยนชื่อเรื่อง",
+  "lists.exclusive": "ซ่อนโพสต์เหล่านี้จากหน้าแรก",
+  "lists.new.create": "เพิ่มรายการ",
+  "lists.new.title_placeholder": "ชื่อเรื่องรายการใหม่",
   "lists.replies_policy.followed": "ผู้ใช้ใด ๆ ที่ติดตาม",
   "lists.replies_policy.list": "สมาชิกของรายการ",
   "lists.replies_policy.none": "ไม่มีใคร",
-  "lists.save": "บันทึก",
-  "lists.search": "ค้นหา",
-  "lists.show_replies_to": "รวมการตอบกลับจากสมาชิกของรายการถึง",
+  "lists.replies_policy.title": "แสดงการตอบกลับแก่:",
+  "lists.search": "ค้นหาในหมู่ผู้คนที่คุณติดตาม",
+  "lists.subheading": "รายการของคุณ",
   "load_pending": "{count, plural, other {# รายการใหม่}}",
   "loading_indicator.label": "กำลังโหลด…",
   "media_gallery.hide": "ซ่อน",
@@ -549,11 +506,8 @@
   "notification.admin.report_statuses_other": "{name} ได้รายงาน {target}",
   "notification.admin.sign_up": "{name} ได้ลงทะเบียน",
   "notification.admin.sign_up.name_and_others": "{name} และ {count, plural, other {# อื่น ๆ}} ได้ลงทะเบียน",
-  "notification.annual_report.view": "ดู #Wrapstodon",
   "notification.favourite": "{name} ได้ชื่นชอบโพสต์ของคุณ",
   "notification.favourite.name_and_others_with_link": "{name} และ <a>{count, plural, other {# อื่น ๆ}}</a> ได้ชื่นชอบโพสต์ของคุณ",
-  "notification.favourite_pm": "{name} ได้ชื่นชอบการกล่าวถึงแบบส่วนตัวของคุณ",
-  "notification.favourite_pm.name_and_others_with_link": "{name} และ <a>{count, plural, other {# อื่น ๆ}}</a> ได้ชื่นชอบการกล่าวถึงแบบส่วนตัวของคุณ",
   "notification.follow": "{name} ได้ติดตามคุณ",
   "notification.follow.name_and_others": "{name} และ <a>{count, plural, other {# อื่น ๆ}}</a> ได้ติดตามคุณ",
   "notification.follow_request": "{name} ได้ขอติดตามคุณ",
@@ -658,21 +612,44 @@
   "notifications_permission_banner.enable": "เปิดใช้งานการแจ้งเตือนบนเดสก์ท็อป",
   "notifications_permission_banner.how_to_control": "เพื่อรับการแจ้งเตือนเมื่อ Mastodon ไม่ได้เปิด เปิดใช้งานการแจ้งเตือนบนเดสก์ท็อป คุณสามารถควบคุมชนิดของการโต้ตอบที่สร้างการแจ้งเตือนบนเดสก์ท็อปได้อย่างแม่นยำผ่านปุ่ม {icon} ด้านบนเมื่อเปิดใช้งานการแจ้งเตือน",
   "notifications_permission_banner.title": "ไม่พลาดสิ่งใด",
-  "onboarding.follows.back": "ย้อนกลับ",
-  "onboarding.follows.done": "เสร็จสิ้น",
+  "onboarding.action.back": "นำฉันกลับ",
+  "onboarding.actions.back": "นำฉันกลับ",
+  "onboarding.actions.go_to_explore": "นำฉันไปยังกำลังนิยม",
+  "onboarding.actions.go_to_home": "นำฉันไปยังฟีดหน้าแรกของฉัน",
+  "onboarding.compose.template": "สวัสดี #Mastodon!",
   "onboarding.follows.empty": "น่าเสียดาย ไม่สามารถแสดงผลลัพธ์ได้ในตอนนี้ คุณสามารถลองใช้การค้นหาหรือเรียกดูหน้าสำรวจเพื่อค้นหาผู้คนที่จะติดตาม หรือลองอีกครั้งในภายหลัง",
-  "onboarding.follows.search": "ค้นหา",
-  "onboarding.follows.title": "ติดตามผู้คนเพื่อเริ่มต้นใช้งาน",
+  "onboarding.follows.lead": "ฟีดหน้าแรกของคุณเป็นวิธีหลักในการสัมผัส Mastodon ยิ่งคุณติดตามผู้คนมากเท่าไร ฟีดหน้าแรกก็จะยิ่งมีการใช้งานและน่าสนใจมากขึ้นเท่านั้น เพื่อช่วยให้คุณเริ่มต้นใช้งาน นี่คือข้อเสนอแนะบางส่วน:",
+  "onboarding.follows.title": "ปรับแต่งฟีดหน้าแรกของคุณ",
   "onboarding.profile.discoverable": "ทำให้โปรไฟล์ของฉันสามารถค้นพบได้",
   "onboarding.profile.discoverable_hint": "เมื่อคุณเลือกรับความสามารถในการค้นพบใน Mastodon โพสต์ของคุณอาจปรากฏในผลลัพธ์การค้นหาและกำลังนิยม และอาจเสนอแนะโปรไฟล์ของคุณให้กับผู้คนที่มีความสนใจคล้ายกับคุณ",
   "onboarding.profile.display_name": "ชื่อที่แสดง",
   "onboarding.profile.display_name_hint": "ชื่อเต็มของคุณหรือชื่อแบบสนุกสนานของคุณ…",
+  "onboarding.profile.lead": "คุณสามารถทำสิ่งนี้ให้เสร็จสมบูรณ์ในภายหลังได้เสมอในการตั้งค่า ที่ซึ่งตัวเลือกการปรับแต่งเพิ่มเติมพร้อมใช้งาน",
   "onboarding.profile.note": "ชีวประวัติ",
   "onboarding.profile.note_hint": "คุณสามารถ @กล่าวถึง ผู้คนอื่น ๆ หรือ #แฮชแท็ก…",
   "onboarding.profile.save_and_continue": "บันทึกและดำเนินการต่อ",
   "onboarding.profile.title": "การตั้งค่าโปรไฟล์",
   "onboarding.profile.upload_avatar": "อัปโหลดรูปภาพโปรไฟล์",
   "onboarding.profile.upload_header": "อัปโหลดส่วนหัวโปรไฟล์",
+  "onboarding.share.lead": "แจ้งให้ผู้คนทราบวิธีที่เขาสามารถค้นหาคุณใน Mastodon!",
+  "onboarding.share.message": "ฉันคือ {username} ใน #Mastodon! มาติดตามฉันที่ {url}",
+  "onboarding.share.next_steps": "ขั้นตอนถัดไปที่เป็นไปได้:",
+  "onboarding.share.title": "แชร์โปรไฟล์ของคุณ",
+  "onboarding.start.lead": "ตอนนี้คุณเป็นส่วนหนึ่งของ Mastodon แพลตฟอร์มสื่อสังคมแบบกระจายศูนย์ที่มีเอกลักษณ์เฉพาะตัว ที่ซึ่งคุณ ไม่ใช่อัลกอริทึม เรียบเรียงประสบการณ์ของคุณเอง มาช่วยให้คุณเริ่มต้นใช้งานพรมแดนทางสังคมใหม่นี้กันเลย:",
+  "onboarding.start.skip": "ไม่ต้องการความช่วยเหลือในการเริ่มต้นใช้งาน?",
+  "onboarding.start.title": "คุณทำสำเร็จแล้ว!",
+  "onboarding.steps.follow_people.body": "การติดตามผู้คนที่น่าสนใจคือสิ่งที่ Mastodon ให้ความสำคัญ",
+  "onboarding.steps.follow_people.title": "ปรับแต่งฟีดหน้าแรกของคุณ",
+  "onboarding.steps.publish_status.body": "กล่าวสวัสดีชาวโลกด้วยข้อความ, รูปภาพ, วิดีโอ หรือการสำรวจความคิดเห็น {emoji}",
+  "onboarding.steps.publish_status.title": "สร้างโพสต์แรกของคุณ",
+  "onboarding.steps.setup_profile.body": "เพิ่มการโต้ตอบของคุณโดยการมีโปรไฟล์ที่ครอบคลุม",
+  "onboarding.steps.setup_profile.title": "ปรับแต่งโปรไฟล์ของคุณ",
+  "onboarding.steps.share_profile.body": "แจ้งให้เพื่อน ๆ ของคุณทราบวิธีค้นหาคุณใน Mastodon",
+  "onboarding.steps.share_profile.title": "แชร์โปรไฟล์ Mastodon ของคุณ",
+  "onboarding.tips.2fa": "<strong>คุณทราบหรือไม่?</strong> คุณสามารถรักษาความปลอดภัยบัญชีของคุณได้โดยตั้งค่าการรับรองความถูกต้องด้วยสองปัจจัยในการตั้งค่าบัญชีของคุณ การรับรองความถูกต้องด้วยสองปัจจัยทำงานร่วมกับแอป TOTP ใด ๆ ที่คุณเลือก ไม่จำเป็นต้องมีหมายเลขโทรศัพท์!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>คุณทราบหรือไม่?</strong> เนื่องจาก Mastodon เป็นแบบกระจายศูนย์ โปรไฟล์บางส่วนที่คุณเจอจะได้รับการโฮสต์ในเซิร์ฟเวอร์อื่น ๆ ที่ไม่ใช่ของคุณ และคุณยังสามารถโต้ตอบกับเขาได้อย่างไร้รอยต่อ! เซิร์ฟเวอร์ของเขาอยู่ในครึ่งหลังของชื่อผู้ใช้ของเขา!",
+  "onboarding.tips.migration": "<strong>คุณทราบหรือไม่?</strong> หากคุณรู้สึกว่า {domain} ไม่ใช่ตัวเลือกเซิร์ฟเวอร์ที่ยอดเยี่ยมสำหรับคุณในอนาคต คุณสามารถย้ายไปยังเซิร์ฟเวอร์ Mastodon อื่นได้โดยไม่สูญเสียผู้ติดตามของคุณ คุณยังสามารถโฮสต์เซิร์ฟเวอร์ของคุณเองได้อีกด้วย!",
+  "onboarding.tips.verification": "<strong>คุณทราบหรือไม่?</strong> คุณสามารถยืนยันบัญชีของคุณได้โดยใส่ลิงก์ไปยังโปรไฟล์ Mastodon ของคุณในเว็บไซต์ของคุณเองและเพิ่มเว็บไซต์ไปยังโปรไฟล์ของคุณ ไม่จำเป็นต้องมีค่าธรรมเนียมหรือเอกสาร!",
   "password_confirmation.exceeds_maxlength": "การยืนยันรหัสผ่านเกินความยาวรหัสผ่านสูงสุดแล้ว",
   "password_confirmation.mismatching": "การยืนยันรหัสผ่านไม่ตรงกัน",
   "picture_in_picture.restore": "นำหน้าต่างย่อยในหน้าต่างหลักกลับมา",
@@ -688,7 +665,7 @@
   "poll_button.remove_poll": "เอาการสำรวจความคิดเห็นออก",
   "privacy.change": "เปลี่ยนความเป็นส่วนตัวของโพสต์",
   "privacy.direct.long": "ทุกคนที่กล่าวถึงในโพสต์",
-  "privacy.direct.short": "การกล่าวถึงแบบส่วนตัว",
+  "privacy.direct.short": "ผู้คนที่เฉพาะเจาะจง",
   "privacy.private.long": "เฉพาะผู้ติดตามของคุณเท่านั้น",
   "privacy.private.short": "ผู้ติดตาม",
   "privacy.public.long": "ใครก็ตามที่อยู่ในและนอก Mastodon",
@@ -700,8 +677,8 @@
   "privacy_policy.title": "นโยบายความเป็นส่วนตัว",
   "recommended": "แนะนำ",
   "refresh": "รีเฟรช",
-  "regeneration_indicator.please_stand_by": "โปรดรอสักครู่",
-  "regeneration_indicator.preparing_your_home_feed": "กำลังเตรียมฟีดหน้าแรกของคุณ…",
+  "regeneration_indicator.label": "กำลังโหลด…",
+  "regeneration_indicator.sublabel": "กำลังเตรียมฟีดหน้าแรกของคุณ!",
   "relative_time.days": "{number} วัน",
   "relative_time.full.days": "{number, plural, other {# วัน}}ที่แล้ว",
   "relative_time.full.hours": "{number, plural, other {# ชั่วโมง}}ที่แล้ว",
@@ -785,10 +762,10 @@
   "search_results.accounts": "โปรไฟล์",
   "search_results.all": "ทั้งหมด",
   "search_results.hashtags": "แฮชแท็ก",
-  "search_results.no_results": "ไม่มีผลลัพธ์",
+  "search_results.nothing_found": "ไม่พบสิ่งใดสำหรับคำค้นหาเหล่านี้",
   "search_results.see_all": "ดูทั้งหมด",
   "search_results.statuses": "โพสต์",
-  "search_results.title": "ค้นหาสำหรับ \"{q}\"",
+  "search_results.title": "ค้นหาสำหรับ {q}",
   "server_banner.about_active_users": "ผู้คนที่ใช้เซิร์ฟเวอร์นี้ในระหว่าง 30 วันที่ผ่านมา (ผู้ใช้ที่ใช้งานอยู่รายเดือน)",
   "server_banner.active_users": "ผู้ใช้ที่ใช้งานอยู่",
   "server_banner.administered_by": "ดูแลโดย:",
@@ -840,7 +817,6 @@
   "status.reblogs.empty": "ยังไม่มีใครดันโพสต์นี้ เมื่อใครสักคนดัน เขาจะปรากฏที่นี่",
   "status.redraft": "ลบแล้วร่างใหม่",
   "status.remove_bookmark": "เอาที่คั่นหน้าออก",
-  "status.remove_favourite": "เอาออกจากรายการโปรด",
   "status.replied_in_thread": "ตอบกลับในกระทู้",
   "status.replied_to": "ตอบกลับ {name}",
   "status.reply": "ตอบกลับ",
@@ -862,7 +838,6 @@
   "subscribed_languages.target": "เปลี่ยนภาษาที่บอกรับสำหรับ {target}",
   "tabs_bar.home": "หน้าแรก",
   "tabs_bar.notifications": "การแจ้งเตือน",
-  "terms_of_service.title": "เงื่อนไขการให้บริการ",
   "time_remaining.days": "เหลืออีก {number, plural, other {# วัน}}",
   "time_remaining.hours": "เหลืออีก {number, plural, other {# ชั่วโมง}}",
   "time_remaining.minutes": "เหลืออีก {number, plural, other {# นาที}}",
@@ -878,12 +853,26 @@
   "upload_button.label": "เพิ่มไฟล์ภาพ, วิดีโอ หรือเสียง",
   "upload_error.limit": "เกินขีดจำกัดการอัปโหลดไฟล์แล้ว",
   "upload_error.poll": "ไม่อนุญาตการอัปโหลดไฟล์โดยมีการสำรวจความคิดเห็น",
+  "upload_form.audio_description": "อธิบายสำหรับผู้ที่สูญเสียการได้ยิน",
+  "upload_form.description": "อธิบายสำหรับผู้คนที่พิการทางการมองเห็นหรือมีสายตาเลือนราง",
   "upload_form.drag_and_drop.instructions": "เพื่อหยิบไฟล์แนบสื่อ กดปุ่มเว้นวรรคหรือขึ้นบรรทัดใหม่ ขณะลาก ใช้ปุ่มลูกศรเพื่อย้ายไฟล์แนบสื่อในทิศทางใดก็ตามที่กำหนด กดปุ่มเว้นวรรคหรือขึ้นบรรทัดใหม่อีกครั้งเพื่อปล่อยไฟล์แนบสื่อในตำแหน่งใหม่ หรือกดปุ่ม Escape เพื่อยกเลิก",
   "upload_form.drag_and_drop.on_drag_cancel": "ยกเลิกการลากแล้ว ปล่อยไฟล์แนบสื่อ {item} แล้ว",
   "upload_form.drag_and_drop.on_drag_end": "ปล่อยไฟล์แนบสื่อ {item} แล้ว",
   "upload_form.drag_and_drop.on_drag_over": "ย้ายไฟล์แนบสื่อ {item} แล้ว",
   "upload_form.drag_and_drop.on_drag_start": "หยิบไฟล์แนบสื่อ {item} แล้ว",
   "upload_form.edit": "แก้ไข",
+  "upload_form.thumbnail": "เปลี่ยนภาพขนาดย่อ",
+  "upload_form.video_description": "อธิบายสำหรับผู้คนที่พิการทางการได้ยิน ได้ยินไม่ชัด พิการทางการมองเห็น หรือมีสายตาเลือนราง",
+  "upload_modal.analyzing_picture": "กำลังวิเคราะห์รูปภาพ…",
+  "upload_modal.apply": "นำไปใช้",
+  "upload_modal.applying": "กำลังนำไปใช้…",
+  "upload_modal.choose_image": "เลือกภาพ",
+  "upload_modal.description_placeholder": "สุนัขจิ้งจอกสีน้ำตาลที่ว่องไวกระโดดข้ามสุนัขขี้เกียจ",
+  "upload_modal.detect_text": "ตรวจหาข้อความจากรูปภาพ",
+  "upload_modal.edit_media": "แก้ไขสื่อ",
+  "upload_modal.hint": "คลิกหรือลากวงกลมในตัวอย่างเพื่อเลือกจุดโฟกัส ซึ่งจะอยู่ในมุมมองของภาพขนาดย่อทั้งหมดเสมอ",
+  "upload_modal.preparing_ocr": "กำลังเตรียม OCR…",
+  "upload_modal.preview_label": "ตัวอย่าง ({ratio})",
   "upload_progress.label": "กำลังอัปโหลด...",
   "upload_progress.processing": "กำลังประมวลผล…",
   "username.taken": "มีการใช้ชื่อผู้ใช้นั้นแล้ว ลองอย่างอื่น",
diff --git a/app/javascript/mastodon/locales/tok.json b/app/javascript/mastodon/locales/tok.json
index 08ce6fd324..4ace4745a9 100644
--- a/app/javascript/mastodon/locales/tok.json
+++ b/app/javascript/mastodon/locales/tok.json
@@ -29,6 +29,7 @@
   "account.endorse": "lipu jan la o suli e ni",
   "account.featured_tags.last_status_at": "sitelen pini pi jan ni li lon tenpo {date}",
   "account.featured_tags.last_status_never": "toki ala li lon",
+  "account.featured_tags.title": "{name} la kulupu ni pi toki suli li pona",
   "account.follow": "o kute",
   "account.follow_back": "jan ni li kute e sina. o kute",
   "account.followers": "jan kute",
@@ -64,7 +65,7 @@
   "account.statuses_counter": "{count, plural, other {toki {counter}}}",
   "account.unblock": "o weka ala e jan {name}",
   "account.unblock_domain": "o weka ala e ma {domain}",
-  "account.unblock_short": "o pini weka",
+  "account.unblock_short": "o weka ala",
   "account.unendorse": "lipu jan la o suli ala e ni",
   "account.unfollow": "o kute ala",
   "account.unmute": "o len ala e @{name}",
@@ -85,36 +86,12 @@
   "alert.unexpected.message": "pakala li lon",
   "alert.unexpected.title": "pakala a!",
   "alt_text_badge.title": "toki sona sitelen",
-  "alt_text_modal.add_alt_text": "o pana e toki pi sona lukin",
-  "alt_text_modal.add_text_from_image": "o kama jo e toki sitelen tan sitelen ni",
-  "alt_text_modal.cancel": "weka",
-  "alt_text_modal.change_thumbnail": "o ante e sitelen lili",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "jan li ken ala kute la o pana e toki pi sona kalama…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "jan li ken ala lukin la o pana e toki pi sona lukin…",
-  "alt_text_modal.done": "pini",
   "announcement.announcement": "toki suli",
-  "annual_report.summary.archetype.booster": "jan ni li alasa e pona",
-  "annual_report.summary.archetype.lurker": "jan ni li lukin taso",
-  "annual_report.summary.archetype.oracle": "jan ni li sona suli",
-  "annual_report.summary.archetype.pollster": "jan ni li wile sona e pilin jan",
-  "annual_report.summary.archetype.replier": "jan ni li toki tawa jan mute",
-  "annual_report.summary.followers.followers": "jan kute sina",
-  "annual_report.summary.followers.total": "ale la {count}",
-  "annual_report.summary.here_it_is": "toki lili la tenpo sike nanpa {year} li sama ni tawa sina:",
-  "annual_report.summary.highlighted_post.by_favourites": "toki pi olin nanpa wan",
-  "annual_report.summary.highlighted_post.by_reblogs": "toki pi sike nanpa wan",
-  "annual_report.summary.highlighted_post.by_replies": "toki li jo e toki kama pi nanpa wan",
-  "annual_report.summary.highlighted_post.possessive": "tan jan {name}",
-  "annual_report.summary.most_used_hashtag.none": "ala",
-  "annual_report.summary.new_posts.new_posts": "toki suli sin",
-  "annual_report.summary.percentile.text": "<topLabel>ni la sina nanpa sewi</topLabel><percentage></percentage><bottomLabel>pi jan ale lon {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "sona ni li len tawa ale.",
-  "annual_report.summary.thanks": "sina jan pi kulupu Masoton la sina pona a!",
   "attachments_list.unprocessed": "(nasin open)",
   "audio.hide": "o len e kalama",
   "block_modal.remote_users_caveat": "mi pana e wile sina tawa ma {domain}. taso, o sona: ma li ken kepeken nasin len ante la pakala li ken lon. toki pi lukin ale la jan pi ma ala li ken lukin.",
-  "block_modal.show_less": "o pana e lili",
-  "block_modal.show_more": "o pana e mute",
+  "block_modal.show_less": "o lili e lukin",
+  "block_modal.show_more": "o suli e lukin",
   "block_modal.they_cant_mention": "ona li ken ala toki tawa sina li ken ala kute e sina.",
   "block_modal.they_cant_see_posts": "ona li ken ala lukin e toki sina. sina ken ala lukin e toki ona.",
   "block_modal.they_will_know": "ona li sona e ni: sina weka e lukin ona.",
@@ -144,16 +121,13 @@
   "column.blocks": "kulupu pi jan weka",
   "column.bookmarks": "awen toki",
   "column.community": "linja tenpo pi ma ni",
-  "column.create_list": "o pali e kulupu",
   "column.direct": "mu len",
   "column.directory": "o lukin e jan",
   "column.domain_blocks": "ma pi wile ala lukin",
-  "column.edit_list": "o ante e kulupu",
   "column.favourites": "ijo pona",
   "column.firehose": "toki pi tenpo ni",
   "column.follow_requests": "wile alasa pi jan ante",
   "column.home": "lipu open",
-  "column.list_members": "o ante e kulupu jan",
   "column.lists": "kulupu lipu",
   "column.mutes": "jan len",
   "column.notifications": "mu pi sona sin",
@@ -166,7 +140,6 @@
   "column_header.pin": "o sewi",
   "column_header.show_settings": "o lukin e lawa",
   "column_header.unpin": "o sewi ala",
-  "column_search.cancel": "o ala",
   "column_subheading.settings": "ken ilo",
   "community.column_settings.local_only": "toki tan ni taso",
   "community.column_settings.media_only": "sitelen taso",
@@ -184,7 +157,7 @@
   "compose_form.poll.duration": "tenpo pana",
   "compose_form.poll.multiple": "pana mute",
   "compose_form.poll.option_placeholder": "ken nanpa {number}",
-  "compose_form.poll.single": "toki pi wan taso",
+  "compose_form.poll.single": "o wile e wan taso",
   "compose_form.poll.switch_to_multiple": "o ante e nasin pana. pana mute o ken",
   "compose_form.poll.switch_to_single": "o ante e nasin pana. pana wan taso o lon",
   "compose_form.poll.type": "nasin",
@@ -208,15 +181,9 @@
   "confirmations.edit.confirm": "o ante",
   "confirmations.edit.message": "sina ante e toki sina la toki pali sina li weka. sina wile ala wile e ni?",
   "confirmations.edit.title": "o weka ala weka e toki? ni la, toki li kama toki sin.",
-  "confirmations.follow_to_list.confirm": "o kute, o pana tawa lipu jan",
-  "confirmations.follow_to_list.message": "sina wile pana e {name} tawa lipu jan la o kama kute e ona.",
-  "confirmations.follow_to_list.title": "sina wile ala wile kute?",
   "confirmations.logout.confirm": "o weka",
   "confirmations.logout.message": "sina wile ala wile weka",
   "confirmations.logout.title": "o weka?",
-  "confirmations.missing_alt_text.confirm": "pana e toki pi sona lukin",
-  "confirmations.missing_alt_text.message": "toki ni la sitelen li lon. taso toki pi sona lukin li lon ala. toki pi sona lukin li pona tan ni: jan ale li ken sona e toki.",
-  "confirmations.missing_alt_text.title": "o pana e toki pi sona lukin",
   "confirmations.mute.confirm": "o len",
   "confirmations.redraft.confirm": "o weka o pali sin e toki",
   "confirmations.redraft.message": "pali sin e toki ni la sina wile ala wile weka e ona? sina ni la suli pi toki ni en wawa pi toki ni li weka. kin la toki lon toki ni li jo e mama ala.",
@@ -237,7 +204,6 @@
   "copy_icon_button.copied": "toki li awen lon ilo sina",
   "copypaste.copied": "sina jo e toki",
   "copypaste.copy_to_clipboard": "o awen lon ilo sina",
-  "directory.federated": "tan lipu ante sona",
   "directory.local": "tan {domain} taso",
   "directory.new_arrivals": "jan pi kama sin",
   "directory.recently_active": "jan lon tenpo poka",
@@ -245,8 +211,10 @@
   "disabled_account_banner.text": "sina ken ala kepeken e lipu jan sina pi nimi {disabledAccount}.",
   "dismissable_banner.community_timeline": "ni li toki pi tenpo poka tawa ale tan jan lon ma lawa pi nimi {domain}.",
   "dismissable_banner.dismiss": "o weka",
-  "dismissable_banner.explore_links": "tenpo suno ni la jan pi kulupu ale li toki e ijo sin ni. ijo sin pi jan ante mute li sewi lon lipu ni.",
-  "dismissable_banner.explore_statuses": "jan mute li lukin e toki ni tan ma ilo weka. toki sin en toki pi wawa mute li lon sewi.",
+  "dismissable_banner.explore_links": "ni li toki pi ijo sin ꞏ jan mute li pana e ni lon tenpo suno ni ꞏ sin la jan mute li pana la ni li kama suli",
+  "dismissable_banner.explore_statuses": "suni ni la jan mute li lukin e toki ni. jan mute li wawa e toki li suli e toki la toki ni li lon sewi. toki li sin la toki ni li lon sewi.",
+  "dismissable_banner.explore_tags": "suni ni la jan mute li lukin e toki pi toki ni. jan mute li kepeken toki la toki ni li lon sewi.",
+  "dismissable_banner.public_timeline": "toki ni li sin. jan li pali e toki ni la jan ante mute pi ma {domain} li kute e jan ni.",
   "domain_block_modal.block": "o weka e ma",
   "domain_block_modal.they_wont_know": "ona li sona ala e ni: sina weka e ona.",
   "domain_block_modal.title": "sina wile weka ala weka e ma?",
@@ -280,7 +248,6 @@
   "emoji_button.symbols": "sitelen",
   "emoji_button.travel": "ma en tawa",
   "empty_column.account_hides_collections": "jan ni li wile len e sona ni",
-  "empty_column.account_suspended": "lipu ni li weka",
   "empty_column.account_timeline": "toki ala li lon!",
   "empty_column.account_unavailable": "ken ala lukin e lipu jan",
   "empty_column.blocks": "jan ala li weka tawa sina.",
@@ -293,9 +260,11 @@
   "empty_column.hashtag": "ala li lon toki ni",
   "empty_column.home": "ala a li lon lipu open sina! sina wile lon e ijo lon ni la o kute e jan pi toki suli.",
   "empty_column.list": "ala li lon kulupu lipu ni. jan pi kulupu lipu ni li toki sin la toki ni li lon ni.",
+  "empty_column.lists": "sina jo ala e kulupu lipu. sina pali sin e kulupu lipu la ona li lon ni.",
   "empty_column.mutes": "jan ala li len tawa sina.",
   "error.unexpected_crash.explanation": "ilo li ken ala pana e lipu ni. ni li ken tan pakala mi tan pakala pi ilo sina.",
   "errors.unexpected_crash.report_issue": "o toki e pakala tawa lawa",
+  "explore.search_results": "ijo pi alasa ni",
   "explore.suggested_follows": "jan",
   "explore.title": "o alasa",
   "explore.trending_links": "sin",
@@ -333,18 +302,15 @@
   "hashtag.counter_by_uses": "{count, plural, other {toki {counter}}}",
   "hashtag.follow": "o kute e kulupu lipu",
   "hashtag.unfollow": "o kute ala e kulupu lipu",
-  "home.column_settings.show_reblogs": "lukin e wawa",
   "home.pending_critical_update.link": "o lukin e ijo ilo sin",
-  "info_button.label": "sona",
-  "interaction_modal.go": "o tawa ma ni",
+  "interaction_modal.login.action": "o lon tomo",
   "interaction_modal.on_another_server": "lon ma ante",
   "interaction_modal.on_this_server": "lon ma ni",
+  "interaction_modal.sign_in_hint": "ni li ma pi ilo nanpa ni: ona li pali e jan sina lon ona. sona sina ni li weka la, o lukin e lipu. kin la, sina ken pana e nimi jan suli sina a! (ni li ken ni: @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "o suli e toki {name}",
   "interaction_modal.title.follow": "o kute e {name}",
   "interaction_modal.title.reblog": "o wawa e toki {name}",
   "interaction_modal.title.reply": "o toki lon toki pi jan {name}",
-  "interaction_modal.title.vote": "o pana tawa wile sona pi jan {name}",
-  "interaction_modal.username_prompt": "ni li sama ni: {example}",
   "intervals.full.days": "{number, plural, other {suni #}}",
   "intervals.full.hours": "{number, plural, other {tenpo suli #}}",
   "keyboard_shortcuts.blocked": "o lukin e lipu sina pi jan weka",
@@ -364,25 +330,26 @@
   "lightbox.next": "sinpin",
   "lightbox.previous": "monsi",
   "link_preview.author": "tan {name}",
+  "lists.account.add": "o pana tawa kulupu lipu",
+  "lists.account.remove": "o weka tan kulupu lipu",
   "lists.delete": "o weka e kulupu lipu",
-  "lists.done": "ale li pini",
   "lists.edit": "o ante e kulupu lipu",
-  "lists.list_members_count": "{count, plural, other {jan #}}",
-  "lists.list_name": "nimi kulupu",
-  "lists.new_list_name": "nimi pi kulupu sin",
-  "lists.no_lists_yet": "kulupu li lon ala.",
-  "lists.remove_member": "o weka",
+  "lists.edit.submit": "o ante e nimi",
+  "lists.exclusive": "o len e toki lon lipu open",
+  "lists.new.create": "o sin e kulupu lipu",
+  "lists.new.title_placeholder": "nimi pi kulupu sin",
   "lists.replies_policy.followed": "jan kute ale",
   "lists.replies_policy.list": "jan pi kulupu ni taso",
   "lists.replies_policy.none": "jan ala",
-  "lists.search": "o alasa",
+  "lists.replies_policy.title": "jan ni li ken lukin e toki lili:",
+  "lists.search": "o alasa lon kulupu jan ni: sina kute e ona",
+  "lists.subheading": "kulupu lipu sina",
   "load_pending": "{count, plural, other {ijo sin #}}",
   "loading_indicator.label": "ni li kama…",
   "mute_modal.title": "sina wile ala wile kute e jan ni?",
   "navigation_bar.about": "sona",
   "navigation_bar.blocks": "jan weka",
   "navigation_bar.compose": "o pali e toki sin",
-  "navigation_bar.domain_blocks": "kulupu pi ma weka",
   "navigation_bar.favourites": "ijo pona",
   "navigation_bar.filters": "nimi len",
   "navigation_bar.lists": "kulupu lipu",
@@ -411,9 +378,34 @@
   "notifications.filter.favourites": "ijo pona",
   "notifications.filter.mentions": "toki pi toki sina",
   "notifications.filter.polls": "pana lon pana ni",
-  "onboarding.follows.search": "o alasa",
+  "onboarding.action.back": "o tawa monsi",
+  "onboarding.actions.back": "o tawa monsi",
+  "onboarding.actions.go_to_explore": "seme li pona tawa jan mute",
+  "onboarding.actions.go_to_home": "o tawa lipu open mi",
+  "onboarding.compose.template": "toki a, kulupu #Mastodon o!",
+  "onboarding.follows.lead": "lipu open li nasin nanpa wan pi ilo Masoton. sina kute e jan mute la, musi mute li lon. open la, ni li ken pona:",
+  "onboarding.follows.title": "o ante e lipu open sina",
   "onboarding.profile.display_name": "nimi tawa jan ante",
+  "onboarding.profile.lead": "sina ken pana e ni lon tenpo kama, lon lipu pi ante nasin. ona la, nasin ante mute li lon.",
   "onboarding.profile.note": "sona sina",
+  "onboarding.share.lead": "o toki lon nasin Masoton pi alasa sina tawa jan",
+  "onboarding.share.message": "ilo #Mastodon la mi jan {username} a! o kute e mi lon ni: {url}",
+  "onboarding.share.next_steps": "ken la ni li pali kama pona:",
+  "onboarding.share.title": "o pana e lipu sina",
+  "onboarding.start.lead": "ni la sina lon kulupu Masoton. kulupu ante ala li sama ona. ona li jo e jan lawa pi wan taso ala. ilo li pana ala e ijo pi wile ala tawa sina, sina ken lon e wile sina. nasin kulupu sin ni la mi o open:",
+  "onboarding.start.skip": "sina wile ala kama sona e nasin open anu seme?",
+  "onboarding.start.title": "sina o kama pona a!",
+  "onboarding.steps.follow_people.body": "lipu Masoton la, sina ken kute e jan namako.",
+  "onboarding.steps.follow_people.title": "o ante e lipu open sina",
+  "onboarding.steps.publish_status.body": "o toki tawa ale kepeken sitelen nimi, kepeken sitelen kule, kepeken sitelen tawa, kepeken alasa sona kulupu {emoji}",
+  "onboarding.steps.publish_status.title": "o pali e toki suli sina nanpa wan",
+  "onboarding.steps.setup_profile.body": "lipu sina li jo e sona mute la jan mute li wile toki tawa sina.",
+  "onboarding.steps.setup_profile.title": "o ante e lipu sina",
+  "onboarding.steps.share_profile.body": "jan pona sina o ken alasa e sina lon lipu Masoton",
+  "onboarding.steps.share_profile.title": "o pana e lipu sina",
+  "onboarding.tips.accounts_from_other_servers": "<strong>sina sona ala sona?</strong> kulupu Masoton li jo e jan lawa mute e ma mute la, sina ken lukin e jan pi ma ilo ante. taso sina ken toki tawa ona kepeken wawa lili a! nimi jan la, nimi nanpa wan li nimi jan, nimi nanpa tu li nimi ma!",
+  "onboarding.tips.migration": "<strong>sina sona ala sona e ni?</strong> tenpo kama la sina pilin ike tawa ma {domain} la, sina ken tawa ma ante lon ilo Masoton. jan li kute e sina la jan ni li awen kute e sina. kin la sina ken lawa e ma pi sina taso a!",
+  "onboarding.tips.verification": "<strong>sina sona ala sona?</strong> sina ken pana e lipu ilo sina tawa lipu sina pi ilo Masoton. sina ken pala e lipu sina pi ilo Masoton tawa lipu ilo sina. sina ni tu la, jan ale li sona e ni: nimi sina la sina toki e lon. ni li wile ala e mani e lipu jan lawa a!",
   "poll.closed": "ona li pini",
   "poll.total_people": "{count, plural, other {jan #}}",
   "poll.total_votes": "{count, plural, other {pana #}}",
@@ -422,6 +414,7 @@
   "poll.votes": "{votes, plural, other {pana #}}",
   "privacy.direct.long": "jan ale lon toki",
   "privacy.public.short": "tawa ale",
+  "regeneration_indicator.label": "ni li kama…",
   "relative_time.days": "{number}d",
   "relative_time.full.just_now": "tenpo ni",
   "relative_time.hours": "{number}h",
@@ -459,8 +452,10 @@
   "search_results.accounts": "lipu jan",
   "search_results.all": "ale",
   "search_results.hashtags": "kulupu pi toki suli",
+  "search_results.nothing_found": "nimi alasa ni la mi lukin e ala",
   "search_results.see_all": "ale",
   "search_results.statuses": "toki",
+  "search_results.title": "o alasa e {q}",
   "server_banner.administered_by": "jan lawa:",
   "status.block": "o weka e @{name}",
   "status.cancel_reblog_private": "o pini e pana",
@@ -479,8 +474,6 @@
   "status.pin": "o sewi lon lipu sina",
   "status.pinned": "toki sewi",
   "status.reblog": "o wawa",
-  "status.reblogged_by": "jan {name} li wawa",
-  "status.reblogs.empty": "jan ala li wawa e toki ni. jan li wawa la, nimi ona li sitelen lon ni.",
   "status.share": "o pana tawa ante",
   "status.show_less_all": "o lili e ale",
   "status.show_more_all": "o suli e ale",
@@ -498,7 +491,17 @@
   "units.short.thousand": "{count}K",
   "upload_button.label": "o pana e sitelen anu kalama",
   "upload_error.limit": "ilo li ken ala e suli pi ijo ni.",
+  "upload_form.audio_description": "o toki e ijo kute tawa jan pi kute ala, tawa jan pi kute lili",
+  "upload_form.description": "o toki e ijo lukin tawa jan pi lukin ala, tawa jan pi lukin lili",
   "upload_form.edit": "o ante",
+  "upload_form.thumbnail": "o ante e sitelen lili",
+  "upload_form.video_description": "o toki e ijo kute tawa jan pi kute ala, tawa jan pi kute lili, e ijo lukin tawa jan pi lukin ala, tawa jan pi lukin lili",
+  "upload_modal.analyzing_picture": "ilo li lukin e sitelen...",
+  "upload_modal.choose_image": "o wile e sitelen",
+  "upload_modal.description_placeholder": "mi pu jaki tan soweli",
+  "upload_modal.detect_text": "ilo o alasa e nimi tan sitelen",
+  "upload_modal.edit_media": "o ante e sitelen",
+  "upload_modal.preparing_ocr": "ilo li open e alasa nimi lon sitelen…",
   "upload_progress.label": "ilo li kama jo e ijo sina...",
   "upload_progress.processing": "ilo li pali…",
   "username.taken": "jan ante li kepeken e nimi ni. sina o kepeken e nimi sin",
@@ -507,5 +510,7 @@
   "video.exit_fullscreen": "o weka tan sitelen suli",
   "video.expand": "o suli e ni",
   "video.hide": "o len e sitelen",
-  "video.pause": "o lape e ni"
+  "video.mute": "o kalama ala",
+  "video.pause": "o lape e ni",
+  "video.unmute": "o kalama"
 }
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 1f703e0748..335922c8b0 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Profili düzenle",
   "account.enable_notifications": "@{name} kişisinin gönderi bildirimlerini aç",
   "account.endorse": "Profilimde öne çıkar",
-  "account.featured": "Öne çıkan",
-  "account.featured.hashtags": "Etiketler",
-  "account.featured.posts": "Gönderiler",
   "account.featured_tags.last_status_at": "Son gönderinin tarihi {date}",
   "account.featured_tags.last_status_never": "Gönderi yok",
+  "account.featured_tags.title": "{name} kişisinin öne çıkan etiketleri",
   "account.follow": "Takip et",
   "account.follow_back": "Geri takip et",
   "account.followers": "Takipçi",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} gönderi} other {{counter} gönderi}}",
   "account.unblock": "@{name} adlı kişinin engelini kaldır",
   "account.unblock_domain": "{domain} alan adının engelini kaldır",
-  "account.unblock_domain_short": "Engeli kaldır",
   "account.unblock_short": "Engeli kaldır",
   "account.unendorse": "Profilimde öne çıkarma",
   "account.unfollow": "Takibi bırak",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Beklenmedik bir hata oluştu.",
   "alert.unexpected.title": "Hay aksi!",
   "alt_text_badge.title": "Alternatif metin",
-  "alt_text_modal.add_alt_text": "Alternatif metin ekle",
-  "alt_text_modal.add_text_from_image": "Resimden metin ekle",
-  "alt_text_modal.cancel": "İptal et",
-  "alt_text_modal.change_thumbnail": "Küçük resmi değiştir",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Bunu işitme sorunu yaşayan kişiler için betimleyin…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Bunu görme bozukluğu yaşayan kişiler için betimleyin…",
-  "alt_text_modal.done": "Tamamlandı",
   "announcement.announcement": "Duyuru",
-  "annual_report.summary.archetype.booster": "Trend takipçisi",
-  "annual_report.summary.archetype.lurker": "Gizli meraklı",
-  "annual_report.summary.archetype.oracle": "Kahin",
-  "annual_report.summary.archetype.pollster": "Anketör",
-  "annual_report.summary.archetype.replier": "Sosyal kelebek",
-  "annual_report.summary.followers.followers": "takipçiler",
-  "annual_report.summary.followers.total": "{count} toplam",
-  "annual_report.summary.here_it_is": "İşte {year} yılı değerlendirmeniz:",
-  "annual_report.summary.highlighted_post.by_favourites": "en çok beğenilen gönderi",
-  "annual_report.summary.highlighted_post.by_reblogs": "en çok paylaşılan gönderi",
-  "annual_report.summary.highlighted_post.by_replies": "en çok yanıt alan gönderi",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "en çok kullanılan uygulama",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "en çok kullanılan etiket",
-  "annual_report.summary.most_used_hashtag.none": "Yok",
-  "annual_report.summary.new_posts.new_posts": "yeni gönderiler",
-  "annual_report.summary.percentile.text": "<bottomLabel>{domain} kullanıcılarının</bottomLabel><percentage></percentage><topLabel>üst dilimindesiniz</topLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Bernie'ye söylemeyiz.",
-  "annual_report.summary.thanks": "Mastodon'un bir parçası olduğunuz için teşekkürler!",
   "attachments_list.unprocessed": "(işlenmemiş)",
   "audio.hide": "Sesi gizle",
   "block_modal.remote_users_caveat": "{domain} sunucusundan kararınıza saygı duymasını isteyeceğiz. Ancak, Uymaları garanti değildir çünkü bazı sunucular engellemeyi farklı şekilde yapıyorlar. Herkese açık gönderiler giriş yapmamış kullanıcılara görüntülenmeye devam edebilir.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "İstenen sayfa bulunamadı. Adres çubuğundaki URL'nin doğru olduğundan emin misiniz?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Kapat",
-  "bundle_modal_error.message": "Bu ekran yüklenirken bir şeyler ters gitti.",
+  "bundle_modal_error.message": "Bu bileşen yüklenirken bir şeyler ters gitti.",
   "bundle_modal_error.retry": "Tekrar deneyin",
   "closed_registrations.other_server_instructions": "Mastodon merkeziyetsiz olduğu için, başka bir sunucuda bir hesap oluşturabilir ve bu sunucuyla etkileşimde bulunmaya devam edebilirsiniz.",
   "closed_registrations_modal.description": "{domain} adresinde hesap oluşturmak şu an mümkün değil ancak unutmayın ki Mastodon kullanmak için özellikle {domain} adresinde hesap oluşturmanız gerekmez.",
@@ -150,16 +121,13 @@
   "column.blocks": "Engellenen kullanıcılar",
   "column.bookmarks": "Yer İşaretleri",
   "column.community": "Yerel ağ akışı",
-  "column.create_list": "Liste oluştur",
   "column.direct": "Özel mesajlar",
   "column.directory": "Profillere göz at",
   "column.domain_blocks": "Engellenen alan adları",
-  "column.edit_list": "Listeyi düzenle",
   "column.favourites": "Gözdelerin",
   "column.firehose": "Anlık Akışlar",
   "column.follow_requests": "Takip istekleri",
   "column.home": "Anasayfa",
-  "column.list_members": "Liste üyelerini yönet",
   "column.lists": "Listeler",
   "column.mutes": "Sessize alınmış kullanıcılar",
   "column.notifications": "Bildirimler",
@@ -172,7 +140,6 @@
   "column_header.pin": "Sabitle",
   "column_header.show_settings": "Ayarları göster",
   "column_header.unpin": "Sabitlemeyi kaldır",
-  "column_search.cancel": "İptal",
   "column_subheading.settings": "Ayarlar",
   "community.column_settings.local_only": "Sadece yerel",
   "community.column_settings.media_only": "Sadece medya",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Anket süresi",
   "compose_form.poll.multiple": "Çoktan seçmeli",
   "compose_form.poll.option_placeholder": "Seçenek {number}",
-  "compose_form.poll.single": "Tekli seçim",
+  "compose_form.poll.single": "Birini seç",
   "compose_form.poll.switch_to_multiple": "Birden çok seçeneğe izin vermek için anketi değiştir",
   "compose_form.poll.switch_to_single": "Tek bir seçeneğe izin vermek için anketi değiştir",
   "compose_form.poll.type": "Stil",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Düzenle",
   "confirmations.edit.message": "Şimdi düzenlersen şu an oluşturduğun iletinin üzerine yazılır. Devam etmek istediğine emin misin?",
   "confirmations.edit.title": "Gönderinin üzerine yaz?",
-  "confirmations.follow_to_list.confirm": "Takip et ve yapılacaklar listesine ekle",
-  "confirmations.follow_to_list.message": "Bir listeye eklemek için {name} kişisini takip etmeniz gerekiyor.",
-  "confirmations.follow_to_list.title": "Kullanıcıyı takip et?",
   "confirmations.logout.confirm": "Oturumu kapat",
   "confirmations.logout.message": "Oturumu kapatmak istediğinden emin misin?",
   "confirmations.logout.title": "Oturumu kapat?",
-  "confirmations.missing_alt_text.confirm": "Alternatif metin ekle",
-  "confirmations.missing_alt_text.message": "Gönderiniz alternatif metni olmayan medya içeriyor. Tanımlamalar eklemek, içeriğinizi insanlar açısından daha erişilebilir kılar.",
-  "confirmations.missing_alt_text.secondary": "Yine de gönder",
-  "confirmations.missing_alt_text.title": "Alternatif metin ekle?",
   "confirmations.mute.confirm": "Sessize al",
   "confirmations.redraft.confirm": "Sil Düzenle ve yeniden paylaş",
   "confirmations.redraft.message": "Bu gönderiyi silip taslak haline getirmek istediğinize emin misiniz? Mevcut favoriler ve boostlar silinecek ve gönderiye verilen yanıtlar başıboş kalacak.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "{disabledAccount} hesabınız şu an devre dışı.",
   "dismissable_banner.community_timeline": "Bunlar, {domain} sunucusunda hesabı olanların yakın zamandaki herkese açık gönderileridir.",
   "dismissable_banner.dismiss": "Yoksay",
-  "dismissable_banner.explore_links": "Bu haberler şimdilerde sosyal ağlarda en çok paylaşılıyor. Farklı kişilerin yayınladığı daha yeni haberler daha üst sıralarda yer alır.",
-  "dismissable_banner.explore_statuses": "Fediverse genelinden bu gönderiler bugün ilgi gören gönderiler. Daha çok yinelenen ve favorilenen yeni gönderiler daha üst sıralarda yer alır.",
-  "dismissable_banner.explore_tags": "Bu etiketler, bugün fediverse üzerinde dikkat çekiyorlar. Çok farklı kişiler tarafından kullanılan etiketler üst sıralarda görünür.",
-  "dismissable_banner.public_timeline": "Bunlar, {domain} üzerindeki insanların takip ettiği fediverse üzerindeki kişilerin en son ve herkese açık gönderileridir.",
+  "dismissable_banner.explore_links": "Bunlar şimdilerde sosyal ağlarda en çok paylaşılan haberler. Farklı kişilerin yayınladığı daha yeni haberler daha üst sıralarda yer alır.",
+  "dismissable_banner.explore_statuses": "Bunlar, sosyal ağ genelinde bugün ilgi gören gönderiler. Daha çok yinelenen ve favorilenen yeni gönderiler daha üst sıralarda yer alır.",
+  "dismissable_banner.explore_tags": "Bu etiketler, merkeziyetsiz ağda bulunan bu ve diğer sunuculardaki insanların şimdilerde ilgisini çekiyor.",
+  "dismissable_banner.public_timeline": "Bunlar, {domain} üzerindeki insanların, sosyal ağ da takip ettiği insanlarca gönderilen en son ve herkese açık gönderilerdir.",
   "domain_block_modal.block": "Sunucuyu engelle",
   "domain_block_modal.block_account_instead": "Bunun yerine {name} hesabını engelle",
   "domain_block_modal.they_can_interact_with_old_posts": "Bu sunucudan kişiler eski gönderilerinizle etkileşebilirler.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Arama sonuçları",
   "emoji_button.symbols": "Semboller",
   "emoji_button.travel": "Seyahat ve Yerler",
-  "empty_column.account_featured": "Bu liste boş",
   "empty_column.account_hides_collections": "Bu kullanıcı bu bilgiyi sağlamayı tercih etmemiştir",
   "empty_column.account_suspended": "Hesap askıya alındı",
   "empty_column.account_timeline": "Burada hiç gönderi yok!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Henüz bu etikete sahip hiçbir gönderi yok.",
   "empty_column.home": "Ana zaman tünelin boş! Akışını doldurmak için daha fazla kişiyi takip ediniz.",
   "empty_column.list": "Henüz bu listede bir şey yok. Bu listenin üyeleri bir şey paylaşığında burada gözükecek.",
+  "empty_column.lists": "Henüz listen yok. Liste oluşturduğunda burada görünür.",
   "empty_column.mutes": "Henüz bir kullanıcıyı sessize almadınız.",
   "empty_column.notification_requests": "Hepsi tamam! Burada yeni bir şey yok. Yeni bildirim aldığınızda, ayarlarınıza göre burada görüntülenecekler.",
   "empty_column.notifications": "Henüz bildiriminiz yok. Sohbete başlamak için başkalarıyla etkileşim kurun.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Bunları devre dışı bırakmayı ve sayfayı yenilemeyi deneyin. Bu yardımcı olmazsa, Mastodon'u başka bir tarayıcı veya yerel uygulama aracılığıyla kullanabilirsiniz.",
   "errors.unexpected_crash.copy_stacktrace": "Yığın izlemeyi panoya kopyala",
   "errors.unexpected_crash.report_issue": "Sorun bildir",
+  "explore.search_results": "Arama sonuçları",
   "explore.suggested_follows": "Kullanıcılar",
   "explore.title": "Keşfet",
   "explore.trending_links": "Haberler",
@@ -373,16 +334,13 @@
   "footer.about": "Hakkında",
   "footer.directory": "Profil dizini",
   "footer.get_app": "Uygulamayı indir",
+  "footer.invite": "Kullacıları davet et",
   "footer.keyboard_shortcuts": "Klavye kısayolları",
   "footer.privacy_policy": "Gizlilik politikası",
   "footer.source_code": "Kaynak kodu görüntüle",
   "footer.status": "Durum",
-  "footer.terms_of_service": "Hizmet şartları",
   "generic.saved": "Kaydet",
   "getting_started.heading": "Başlarken",
-  "hashtag.admin_moderation": "#{name} için denetim arayüzünü açın",
-  "hashtag.browse": "#{hashtag} gönderilerine gözat",
-  "hashtag.browse_from_account": "@{name} kişisinin #{hashtag} gönderilerine gözat",
   "hashtag.column_header.tag_mode.all": "ve {additional}",
   "hashtag.column_header.tag_mode.any": "ya da {additional}",
   "hashtag.column_header.tag_mode.none": "{additional} olmadan",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} gönderi} other {{counter} gönderi}}",
   "hashtag.counter_by_uses_today": "bugün {count, plural, one {{counter} gönderi} other {{counter} gönderi}}",
   "hashtag.follow": "Etiketi takip et",
-  "hashtag.mute": "#{hashtag} gönderilerini sessize al",
   "hashtag.unfollow": "Etiketi takibi bırak",
   "hashtags.and_other": "…ve {count, plural, one {}other {# fazlası}}",
   "hints.profiles.followers_may_be_missing": "Bu profilin takipçileri eksik olabilir.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Takip etmeyen kişilerin bildirimlerini yoksay?",
   "ignore_notifications_modal.not_following_title": "Takip etmediğin kişilerin bildirimlerini yoksay?",
   "ignore_notifications_modal.private_mentions_title": "İstenmeyen özel bahsetmelerden gelen bildirimleri yoksay?",
-  "info_button.label": "Yardım",
-  "info_button.what_is_alt_text": "<h1>Alternatif metin nedir?</h1><p>Alternatif metin, görme bozukluğu olan, düşük bant genişliğine sahip bağlantıları olan veya ekstra bağlam arayan kişiler için görsel açıklamaları sağlar.</p><p>Net, sade ve nesnel alternatif metin yazarak herkes için erişilebilirliği ve anlaşılabilirliği iyileştirebilirsiniz.</p><ul><li>Önemlileri yakalayın</li><li>Resimlerdeki metni özetleyin</li><li>Düzenli cümle yapısı kullanın</li><li>Gereksiz bilgilerden kaçının</li><li>Karmaşık görsellerde (şemalar veya haritalar gibi) trendlere ve temel bulgulara odaklanın</li></ul>",
-  "interaction_modal.action.favourite": "Devam etmek için, hesabınızı kullanarak beğenmelisiniz.",
-  "interaction_modal.action.follow": "Devam etmek için, hesabınızı kullanarak takip etmelisiniz.",
-  "interaction_modal.action.reblog": "Devam etmek için, hesabınızı kullanarak tekrar göndermelisiniz.",
-  "interaction_modal.action.reply": "Devam etmek için, hesabınızı kullanarak yanıt vermelisiniz.",
-  "interaction_modal.action.vote": "Devam etmek için, hesabınızı kullanarak oylamalısınız.",
-  "interaction_modal.go": "Git",
-  "interaction_modal.no_account_yet": "Henüz bir hesabınız yok mu?",
+  "interaction_modal.description.favourite": "Bir Mastodon hesabıyla bu gönderiyi favorilerinize ekleyerek yazara gönderiyi beğendiğinizi bildirebilir ve daha sonrası için kaydedebilirsiniz.",
+  "interaction_modal.description.follow": "Mastodon'daki bir hesapla, {name} kişisini, ana akışınızdaki gönderilerini görmek üzere takip edebilirsiniz.",
+  "interaction_modal.description.reblog": "Mastodon'daki bir hesapla, bu gönderiyi takipçilerinizle paylaşmak için tuşlayabilirsiniz.",
+  "interaction_modal.description.reply": "Mastodon'daki bir hesapla, bu gönderiye yanıt verebilirsiniz.",
+  "interaction_modal.login.action": "Anasayfaya geri dön",
+  "interaction_modal.login.prompt": "Ev sunucunuzun etki alanı, örneğin mastodon.social",
+  "interaction_modal.no_account_yet": "Mastodon açık değil?",
   "interaction_modal.on_another_server": "Farklı bir sunucuda",
   "interaction_modal.on_this_server": "Bu sunucuda",
+  "interaction_modal.sign_in": "Bu sunucuya giriş yapmadınız. Hesabınız nerede barındırılıyor?",
+  "interaction_modal.sign_in_hint": "İpucu: Kaydolduğunuz web sitesi budur. Hatırlamıyorsanız, gelen kutunuzdaki hoş geldiniz e-postasını arayın. Tam kullanıcı adınızı da girebilirsiniz! (örneğin @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "{name} kişisinin gönderisini favorilerine ekle",
   "interaction_modal.title.follow": "{name} kişisini takip et",
   "interaction_modal.title.reblog": "{name} kişisinin gönderisini yeniden paylaş",
   "interaction_modal.title.reply": "{name} kişisinin gönderisine yanıt ver",
-  "interaction_modal.title.vote": "{name} kullanıcısının anketinde oy kullan",
-  "interaction_modal.username_prompt": "Örnek: {example}",
   "intervals.full.days": "{number, plural, one {# gün} other {# gün}}",
   "intervals.full.hours": "{number, plural, one {# saat} other {# saat}}",
   "intervals.full.minutes": "{number, plural, one {# dakika} other {# dakika}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "CW'den önceki yazıyı göstermek/gizlemek için",
   "keyboard_shortcuts.toggle_sensitivity": "Medyayı göstermek/gizlemek için",
   "keyboard_shortcuts.toot": "Yeni bir gönderi başlat",
-  "keyboard_shortcuts.translate": "bir gönderiyi çevirmek için",
   "keyboard_shortcuts.unfocus": "Aramada bir gönderiye odaklanmamak için",
   "keyboard_shortcuts.up": "Listede yukarıya çıkmak için",
   "lightbox.close": "Kapat",
@@ -490,32 +444,20 @@
   "link_preview.author": "Yazar: {name}",
   "link_preview.more_from_author": "{name} kişisinden daha fazlası",
   "link_preview.shares": "{count, plural, one {{counter} gönderi} other {{counter} gönderi}}",
-  "lists.add_member": "Ekle",
-  "lists.add_to_list": "Listeye ekle",
-  "lists.add_to_lists": "{name} kişisini listelere ekle",
-  "lists.create": "Oluştur",
-  "lists.create_a_list_to_organize": "Anasayfa akışınızı düzenlemek için yeni bir liste oluşturun",
-  "lists.create_list": "Liste oluştur",
+  "lists.account.add": "Listeye ekle",
+  "lists.account.remove": "Listeden kaldır",
   "lists.delete": "Listeyi sil",
-  "lists.done": "Tamamlandı",
   "lists.edit": "Listeleri düzenle",
-  "lists.exclusive": "Anasayfada üyeleri gizle",
-  "lists.exclusive_hint": "Birisi bu listede yer alıyorsa, gönderilerini iki kez görmekten kaçınmak için onu anasayfa akışınızda gizleyin.",
-  "lists.find_users_to_add": "Eklenecek kullanıcıları bul",
-  "lists.list_members": "Liste üyeleri",
-  "lists.list_members_count": "{count, plural, one {# üye} other {# üye}}",
-  "lists.list_name": "Liste adı",
-  "lists.new_list_name": "Yeni liste adı",
-  "lists.no_lists_yet": "Henüz liste yok.",
-  "lists.no_members_yet": "Henüz üye yok.",
-  "lists.no_results_found": "Sonuç bulunamadı.",
-  "lists.remove_member": "Kaldır",
+  "lists.edit.submit": "Başlığı değiştir",
+  "lists.exclusive": "Bu gönderileri Anasayfadan gizle",
+  "lists.new.create": "Liste ekle",
+  "lists.new.title_placeholder": "Yeni liste başlığı",
   "lists.replies_policy.followed": "Takip edilen herhangi bir kullanıcı",
   "lists.replies_policy.list": "Listenin üyeleri",
   "lists.replies_policy.none": "Hiç kimse",
-  "lists.save": "Kaydet",
-  "lists.search": "Ara",
-  "lists.show_replies_to": "Liste üyelerinin yanıtlarını içer",
+  "lists.replies_policy.title": "Yanıtları göster:",
+  "lists.search": "Takip ettiğiniz kişiler arasından arayın",
+  "lists.subheading": "Listeleriniz",
   "load_pending": "{count, plural, one {# yeni öğe} other {# yeni öğe}}",
   "loading_indicator.label": "Yükleniyor…",
   "media_gallery.hide": "Gizle",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name}, {target} kişisini bildirdi",
   "notification.admin.sign_up": "{name} kaydoldu",
   "notification.admin.sign_up.name_and_others": "{name} ve {count, plural, one {# diğer kişi} other {# diğer kişi}} kaydoldu",
-  "notification.annual_report.message": "{year} yılı #Wrapstodon'unuz bekliyor! Yılınızın Mastodon'daki öne çıkanlarını ve anılarınızı gösterin!",
-  "notification.annual_report.view": "#Wrapstodon'u Görüntüle",
   "notification.favourite": "{name} gönderinizi beğendi",
   "notification.favourite.name_and_others_with_link": "{name} ve <a>{count, plural, one {# diğer kişi} other {# diğer kişi}}</a> gönderinizi beğendi",
-  "notification.favourite_pm": "{name} özel değininizi beğendi",
-  "notification.favourite_pm.name_and_others_with_link": "{name} ve <a>{count, plural, one {# diğer kişi} other {# diğer kişi}}</a> özel değininizi beğendi",
   "notification.follow": "{name} seni takip etti",
   "notification.follow.name_and_others": "{name} ve <a>{count, plural, one {# diğer kişi} other {# diğer kişi}}</a> sizi takip etti",
   "notification.follow_request": "{name} size takip isteği gönderdi",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Masaüstü bildirimlerini etkinleştir",
   "notifications_permission_banner.how_to_control": "Mastodon açık olmadığında bildirim almak için masaüstü bildirimlerini etkinleştirin. Etkinleştirildikten sonra, yukarıdaki{icon} düğmesi aracılığıyla hangi etkileşim türlerinin masaüstü bildirimi oluşturacağını tam olarak kontrol edebilirsiniz.",
   "notifications_permission_banner.title": "Hiçbir şeyi kaçırmayın",
-  "onboarding.follows.back": "Geri",
-  "onboarding.follows.done": "Tamamlandı",
+  "onboarding.action.back": "Beni geri götür",
+  "onboarding.actions.back": "Beni geri götür",
+  "onboarding.actions.go_to_explore": "Öne çıkanları gör",
+  "onboarding.actions.go_to_home": "Ana akışına git",
+  "onboarding.compose.template": "Merhaba #Mastodon!",
   "onboarding.follows.empty": "Maalesef şu an bir sonuç gösterilemiyor. Takip edilecek kişileri bulmak için arama veya keşfet sayfasına gözatmayı kullanabilirsiniz veya daha sonra tekrar deneyin.",
-  "onboarding.follows.search": "Ara",
-  "onboarding.follows.title": "Başlamak için insanları takip edin",
+  "onboarding.follows.lead": "Kendi ana akışınızı siz düzenliyorsunuz. Siz daha fazla insanı takip ettikçe, daha etkin ve ilgi çekici olacaktır. Bu profiller iyi bir başlangıç olabilir, isterseniz izlemeyi daha sonra bırakabilirsiniz:",
+  "onboarding.follows.title": "Mastodon'da Popüler",
   "onboarding.profile.discoverable": "Profilimi keşfedilebilir yap",
   "onboarding.profile.discoverable_hint": "Mastodon'da keşfedilebilirliği etkinleştirdiğinizde, gönderileriniz arama sonuçlarında ve trendlerde görünebilir aynı zamanda profiliniz sizinle benzer ilgi alanlarına sahip kişilere önerilebilir.",
   "onboarding.profile.display_name": "Görünen isim",
   "onboarding.profile.display_name_hint": "Tam adınız veya kullanıcı adınız…",
+  "onboarding.profile.lead": "Bunu her zaman daha sonra ayarlardan tamamlayabilirsiniz, hatta daha fazla özelleştirme seçeneğine de ulaşabilirsiniz.",
   "onboarding.profile.note": "Kişisel bilgiler",
   "onboarding.profile.note_hint": "Diğer insanlara @değinebilir veya #etiketler kullanabilirsiniz…",
   "onboarding.profile.save_and_continue": "Kaydet ve ilerle",
   "onboarding.profile.title": "Profilini ayarla",
   "onboarding.profile.upload_avatar": "Profil resmi yükle",
   "onboarding.profile.upload_header": "Profil başlığı yükle",
+  "onboarding.share.lead": "Kullanıcılara Mastodon'da size nasıl ulaşabileceklerini ifade edin!",
+  "onboarding.share.message": "#Mastodon'da kullanıcı adım {username}! Beni takip etmek için {url} bağlantısını kullanın",
+  "onboarding.share.next_steps": "Olası sonraki adımlar:",
+  "onboarding.share.title": "Profilinizi paylaşın",
+  "onboarding.start.lead": "Yeni Mastodon hesabınız kullanıma hazır. Ondan nasıl yararlanabilirsiniz:",
+  "onboarding.start.skip": "Şimdilik bunların hepsini atlamak mı istiyorsunuz?",
+  "onboarding.start.title": "Başardınız!",
+  "onboarding.steps.follow_people.body": "Kendi akışınızı düzenliyorsunuz. Hadi onu ilginç kullacılarla dolduralım.",
+  "onboarding.steps.follow_people.title": "{count, plural, one {Bir kişiyi} other {# kişiyi}} takip edin",
+  "onboarding.steps.publish_status.body": "Dünyaya merhaba deyin.",
+  "onboarding.steps.publish_status.title": "İlk gönderinizi oluşturun",
+  "onboarding.steps.setup_profile.body": "Diğer kişiler muhtemelen dolu bir profille etkileşecektir.",
+  "onboarding.steps.setup_profile.title": "Profilinizi özelleştirin",
+  "onboarding.steps.share_profile.body": "Arkadaşlarınıza Mastodon'da size nasıl ulaşabileceklerini söyleyin!",
+  "onboarding.steps.share_profile.title": "Profilinizi paylaşın",
+  "onboarding.tips.2fa": "<strong>Biliyor muydunuz?</strong> Hesabınızı, hesap ayarlarında iki aşamalı doğrılamayı ayarlayarak güvenli kılabilirsiniz. Sizin seçiminiz olan herhangi bir İki Faktörlü TOTP uygulamasıyla çalışır, telefon numarası da gerekmiyor!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Biliyor muydunuz?</strong> Mastodon ademi merkeziyetçi olduğu için, karşılaştığınız bazı profiller sizinkinden farklı bir sunucuda barındırılacaktır. Buna rağmen onlarla sorunsuz bir şekilde etkileşebilirsiniz! Sunucuları, kullanıcı adlarının ikinci yarısıdır!",
+  "onboarding.tips.migration": "<strong>Biliyor muydunuz?</strong> Eğer gelecekte {domain} sunucusunu çok iyi bulmazsanız, takipçilerinizi kaybetmeden başka bir Mastodon sunucusuna taşınabilirsiniz. Kendi sunucunuzu bile oluşturabilirsiniz!",
+  "onboarding.tips.verification": "<strong>Biliyor muydunuz?</strong> Hesabınızı, kendi web sitenize Mastodon profilinize bir bağlantı koyarak, sonra da web sitenizin bağlantısını profilinize ekleyerek doğrulayabilirsiniz. Ne ücret ne de bir belge gerekiyor!",
   "password_confirmation.exceeds_maxlength": "Parola onayı azami parola uzunluğunu aşıyor",
   "password_confirmation.mismatching": "Parola onayı eşleşmiyor",
   "picture_in_picture.restore": "Onu geri koy",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Anketi kaldır",
   "privacy.change": "Gönderi gizliliğini değiştir",
   "privacy.direct.long": "Gönderide değinilen herkes",
-  "privacy.direct.short": "Özel bahsetme",
+  "privacy.direct.short": "Belirli kişiler",
   "privacy.private.long": "Sadece takipçileriniz",
   "privacy.private.short": "Takipçiler",
   "privacy.public.long": "Mastodon'da olan olmayan herkes",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Gizlilik Politikası",
   "recommended": "Önerilen",
   "refresh": "Yenile",
-  "regeneration_indicator.please_stand_by": "Lütfen bekleyin.",
-  "regeneration_indicator.preparing_your_home_feed": "Ana akışınız hazırlanıyor…",
+  "regeneration_indicator.label": "Yükleniyor…",
+  "regeneration_indicator.sublabel": "Ana akışın hazırlanıyor!",
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# gün} other {# gün}} önce",
   "relative_time.full.hours": "{number, plural, one {# saat} other {# saat}} önce",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Profiller",
   "search_results.all": "Tümü",
   "search_results.hashtags": "Etiketler",
-  "search_results.no_results": "Sonuç yok.",
-  "search_results.no_search_yet": "Gönderiler, profiller veya etiketler için aramayı deneyin.",
+  "search_results.nothing_found": "Bu arama seçenekleriyle bir sonuç bulunamadı",
   "search_results.see_all": "Tümünü gör",
   "search_results.statuses": "Gönderiler",
-  "search_results.title": "\"{q}\" için arama",
+  "search_results.title": "{q} araması",
   "server_banner.about_active_users": "Bu sunucuyu son 30 günde kullanan insanlar (Aylık Etkin Kullanıcılar)",
   "server_banner.active_users": "etkin kullanıcılar",
   "server_banner.administered_by": "Yönetici:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Henüz hiç kimse bu gönderiyi yeniden paylaşmadı. Herhangi bir kullanıcı yeniden paylaştığında burada görüntülenecek.",
   "status.redraft": "Sil,Düzenle ve yeniden-paylaş",
   "status.remove_bookmark": "Yer işaretini kaldır",
-  "status.remove_favourite": "Favorilerden kaldır",
   "status.replied_in_thread": "Akışta yanıtlandı",
   "status.replied_to": "{name} kullanıcısına yanıt verdi",
   "status.reply": "Yanıtla",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "{target} abone olduğu dilleri değiştir",
   "tabs_bar.home": "Anasayfa",
   "tabs_bar.notifications": "Bildirimler",
-  "terms_of_service.effective_as_of": "{date} itibariyle yürürlükte",
-  "terms_of_service.title": "Hizmet Şartları",
-  "terms_of_service.upcoming_changes_on": "{date} tarihinde gerçekleşecek değişiklikler",
   "time_remaining.days": "{number, plural, one {# gün} other {# gün}} kaldı",
   "time_remaining.hours": "{number, plural, one {# saat} other {# saat}} kaldı",
   "time_remaining.minutes": "{number, plural, one {# dakika} other {# dakika}} kaldı",
@@ -897,12 +853,26 @@
   "upload_button.label": "Resim, video veya ses dosyası ekleyin",
   "upload_error.limit": "Dosya yükleme sınırı aşıldı.",
   "upload_error.poll": "Anketlerde dosya yüklemesine izin verilmez.",
+  "upload_form.audio_description": "İşitme kaybı olan kişiler için yazı ekleyiniz",
+  "upload_form.description": "Görme engelliler için açıklama",
   "upload_form.drag_and_drop.instructions": "Bir medya eklentisini taşımak için, boşluk veya enter tuşuna basın. Sürükleme sırasında medya eklentisini herhangi bir yöne hareket ettirmek için ok tuşlarını kullanın. Medya eklentisini yeni konumuna bırakmak için tekrar boşluk veya enter tuşuna basın veya işlemi iptal etmek için escape tuşuna basın.",
   "upload_form.drag_and_drop.on_drag_cancel": "Sürükleme iptal edildi. Medya eklentisi {item} bırakıldı.",
   "upload_form.drag_and_drop.on_drag_end": "Medya eklentisi {item} bırakıldı.",
   "upload_form.drag_and_drop.on_drag_over": "Medya eklentisi {item} hareket ettirildi.",
   "upload_form.drag_and_drop.on_drag_start": "Medya eklentisi {item} tutuldu.",
   "upload_form.edit": "Düzenle",
+  "upload_form.thumbnail": "Küçük resmi değiştir",
+  "upload_form.video_description": "İşitme kaybı veya görme engeli olan kişiler için açıklama ekleyiniz",
+  "upload_modal.analyzing_picture": "Görsel analiz ediliyor…",
+  "upload_modal.apply": "Uygula",
+  "upload_modal.applying": "Uygulanıyor…",
+  "upload_modal.choose_image": "Görsel seç",
+  "upload_modal.description_placeholder": "Pijamalı hasta yağız şoföre çabucak güvendi",
+  "upload_modal.detect_text": "Resimdeki metni algıla",
+  "upload_modal.edit_media": "Medyayı düzenle",
+  "upload_modal.hint": "Her zaman tüm küçük resimlerde görüntülenecek odak noktasını seçmek için ön izlemedeki daireyi tıklayın veya sürükleyin.",
+  "upload_modal.preparing_ocr": "OCR hazırlanıyor…",
+  "upload_modal.preview_label": "Ön izleme ({ratio})",
   "upload_progress.label": "Yükleniyor...",
   "upload_progress.processing": "İşleniyor…",
   "username.taken": "Bu kullanıcı adı alınmış. Farklı bir tane deneyin",
@@ -912,12 +882,8 @@
   "video.expand": "Videoyu genişlet",
   "video.fullscreen": "Tam ekran",
   "video.hide": "Videoyu gizle",
-  "video.mute": "Sessiz",
+  "video.mute": "Sesi sustur",
   "video.pause": "Duraklat",
   "video.play": "Oynat",
-  "video.skip_backward": "Geriye atla",
-  "video.skip_forward": "İleriye atla",
-  "video.unmute": "Sesi aç",
-  "video.volume_down": "Sesi kıs",
-  "video.volume_up": "Sesi yükselt"
+  "video.unmute": "Sesi aç"
 }
diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json
index ee50180ced..ce2e5ec954 100644
--- a/app/javascript/mastodon/locales/tt.json
+++ b/app/javascript/mastodon/locales/tt.json
@@ -19,7 +19,6 @@
   "account.block_short": "Блокла",
   "account.blocked": "Блокланган",
   "account.cancel_follow_request": "Киләсе сорау",
-  "account.copy": "Профиль сылтамасын күчереп ал",
   "account.disable_notifications": "@{name} язулары өчен белдерүләр сүндерү",
   "account.domain_blocked": "Домен блокланган",
   "account.edit_profile": "Профильне үзгәртү",
@@ -27,6 +26,7 @@
   "account.endorse": "Профильдә тәкъдим итү",
   "account.featured_tags.last_status_at": "Соңгы хәбәр {date}",
   "account.featured_tags.last_status_never": "Хәбәрләр юк",
+  "account.featured_tags.title": "{name} тәкъдим ителгән хэштеглар",
   "account.follow": "Язылу",
   "account.followers": "Язылучы",
   "account.followers.empty": "Әле беркем дә язылмаган.",
@@ -43,8 +43,6 @@
   "account.mention": "@{name} искәртү",
   "account.moved_to": "{name} аларның яңа счеты хәзер күрсәтте:",
   "account.mute": "@{name} кулланучыга әһәмият бирмәү",
-  "account.mute_notifications_short": "Искәртүләрне сүндер",
-  "account.mute_short": "Тавышсыз",
   "account.muted": "Әһәмият бирмәнгән",
   "account.open_original_page": "Чыганак битен ачу",
   "account.posts": "Язма",
@@ -60,7 +58,6 @@
   "account.unendorse": "Профильдә тәкъдим итмәү",
   "account.unfollow": "Язылуны туктату",
   "account.unmute": "Kабызыгыз @{name}",
-  "account.unmute_notifications_short": "Искәртүләрне кабыз",
   "account.unmute_short": "Kабызыгыз",
   "account_note.placeholder": "Click to add a note",
   "admin.dashboard.daily_retention": "Теркәлгәннән соң икенче көнне кулланучыларны тоту коэффициенты",
@@ -90,6 +87,7 @@
   "bundle_column_error.routing.body": "Сорау бите табылмады. URL адресы дөрес күрсәтелгәненә ышанасызмы?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Ябу",
+  "bundle_modal_error.message": "Бу компонентны Йөкләгәндә нәрсәдер дөрес булмаган.",
   "bundle_modal_error.retry": "Тагын сынап кара",
   "closed_registrations.other_server_instructions": "Mastodon үзәкләштерелмәгәнгә, сез бүтән серверда хисап язмасы булдыра аласыз һәм аның белән аралаша аласыз.",
   "closed_registrations_modal.description": "Хисап язмасы булдыру {domain} бу хәзерге вакытта мөмкин түгел, ләкин зинһар, онытмагыз, сезгә махсус хисап кирәк түгел {domain} Мастодонны куллану өчен.",
@@ -100,11 +98,9 @@
   "column.blocks": "Блокланган кулланучылар",
   "column.bookmarks": "Кыстыргычлар",
   "column.community": "Локаль вакыт сызыгы",
-  "column.create_list": "Исемлек яса",
   "column.direct": "Хосусый искә алулар",
   "column.directory": "Профильләрне карау",
   "column.domain_blocks": "Блокланган доменнар",
-  "column.edit_list": "Исемлекне үзгәрт",
   "column.follow_requests": "Язылу сораулары",
   "column.home": "Баш бит",
   "column.lists": "Исемлекләр",
@@ -168,6 +164,8 @@
   "disabled_account_banner.text": "Сезнең хисап {disabledAccount} хәзерге вакытта инвалид.",
   "dismissable_banner.community_timeline": "Бу счетлары урнаштырылган кешеләрдән иң соңгы җәмәгать хәбәрләре {domain}.",
   "dismissable_banner.dismiss": "Ябу",
+  "dismissable_banner.explore_links": "Бу яңалыклар турында хәзерге вакытта кешеләр һәм башка үзәкләштерелмәгән челтәр серверларында сөйләшәләр.",
+  "dismissable_banner.explore_tags": "Бу хэштеглар хәзерге вакытта үзәкләштерелмәгән челтәрнең бүтән серверларында кешеләр арасында кызыксыну уята.",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "Менә ул нинди булыр:",
   "emoji_button.activity": "Активлык",
@@ -192,6 +190,7 @@
   "empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
   "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
   "errors.unexpected_crash.report_issue": "Хата турында белдерү",
+  "explore.search_results": "Эзләү нәтиҗәләре",
   "explore.suggested_follows": "Кешеләр",
   "explore.title": "Күзәтү",
   "explore.trending_links": "Яңалыклар",
@@ -211,6 +210,7 @@
   "footer.about": "Проект турында",
   "footer.directory": "Профильләр каталогы",
   "footer.get_app": "Кушымта алыгыз",
+  "footer.invite": "Кешеләрне чакырыгыз",
   "footer.keyboard_shortcuts": "Төймә комбинацияләре",
   "footer.privacy_policy": "Хосусыйлык сәясәте",
   "footer.source_code": "Чыганак кодын карау",
@@ -232,6 +232,9 @@
   "home.column_settings.show_replies": "Җаваплар күрсәтү",
   "home.hide_announcements": "Игъланнарны яшерү",
   "home.show_announcements": "Белдерүләр бирегез",
+  "interaction_modal.description.follow": "Mastodon аккаунты белән сез иярә аласыз {name} аларның язмаларын өй тасмасында алу өчен.",
+  "interaction_modal.description.reblog": "Mastodon аккаунты ярдәмендә сез бу язманы үз шәкертләрегез белән уртаклашу өчен арттыра аласыз.",
+  "interaction_modal.description.reply": "Mastodon аккаунты белән сез бу язмага җавап бирә аласыз.",
   "interaction_modal.on_another_server": "Башка серверда",
   "interaction_modal.on_this_server": "Бу серверда",
   "interaction_modal.title.follow": "Иярү {name}",
@@ -277,10 +280,16 @@
   "lightbox.previous": "Алдагы",
   "limited_account_hint.action": "Барыбер профильне күрсәтергә",
   "limited_account_hint.title": "Бу профильне модераторлар яшергән {domain}.",
+  "lists.account.add": "Исемлеккә өстәргә",
+  "lists.account.remove": "Исемлектән бетерергә",
   "lists.delete": "Исемлекне бетерегез",
   "lists.edit": "Исемлекне үзгәртү",
+  "lists.edit.submit": "Исемен үзгәртү",
+  "lists.new.create": "Исемлек өстәгез",
+  "lists.new.title_placeholder": "Яңа исемлек башламы",
   "lists.replies_policy.list": "Исемлек әгъзалары",
   "lists.replies_policy.none": "Һичкем",
+  "lists.subheading": "Исемлегегегезләр",
   "load_pending": "{count, plural, one {# яңа элемент} other {# яңа элемент}}",
   "navigation_bar.about": "Проект турында",
   "navigation_bar.blocks": "Блокланган кулланучылар",
@@ -313,6 +322,25 @@
   "notifications.group": "{count} искәртү",
   "notifications_permission_banner.enable": "Эш өстәле искәртүләрен кабызу",
   "notifications_permission_banner.title": "Әйберне мәңге югалтмаска",
+  "onboarding.action.back": "Кире кайту",
+  "onboarding.actions.back": "Кире кайту",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "Сәлам #Mastodon!",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.share.message": "Мин #Mastodon-да {username} кулланучы исеме! Миңа иярү өчен {url} адресы кулланыгыз",
+  "onboarding.share.next_steps": "Мөмкин булган киләсе адымнар:",
+  "onboarding.share.title": "Профилегезне уртаклашу",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "picture_in_picture.restore": "Кире кайтару",
   "poll.closed": "Ябык",
   "poll.refresh": "Яңарту",
@@ -327,6 +355,7 @@
   "privacy_policy.last_updated": "Соңгы яңарту {date}",
   "privacy_policy.title": "Хосусыйлык Сәясәте",
   "refresh": "Яңарту",
+  "regeneration_indicator.label": "Йөкләү...",
   "relative_time.days": "{number}к",
   "relative_time.full.days": "{number, plural, one {# көн} other {# көн}} элек",
   "relative_time.full.hours": "{number, plural, one {# сәгать} other {# сәгать}} элек",
@@ -371,6 +400,7 @@
   "search_results.all": "Барысы да",
   "search_results.hashtags": "Хәштәгләр",
   "search_results.statuses": "Язмалар",
+  "search_results.title": "{q} өчен эзләү",
   "server_banner.administered_by": "Идарә итүче:",
   "server_banner.server_stats": "Сервер статистикасы:",
   "sign_in_banner.create_account": "Аккаунтны ясау",
@@ -409,7 +439,17 @@
   "units.short.billion": "{count} млрд",
   "units.short.million": "{count} млн",
   "units.short.thousand": "{count} мең",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
   "upload_form.edit": "Үзгәртү",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
+  "upload_modal.analyzing_picture": "Рәсемгә анализ ясау…",
+  "upload_modal.apply": "Куллан",
+  "upload_modal.applying": "Куллану…",
+  "upload_modal.choose_image": "Рәсемне сайлау",
+  "upload_modal.detect_text": "Рәсемнән текстны ачыклау",
+  "upload_modal.edit_media": "Медиа-файлны үзгәртү",
+  "upload_modal.preparing_ocr": "OCR әзерләү…",
   "upload_progress.label": "Йөкләү...",
   "upload_progress.processing": "Эшкәртелә…",
   "video.close": "Видеоны ябу",
@@ -418,6 +458,8 @@
   "video.expand": "Видеоны җәю",
   "video.fullscreen": "Тулы экран",
   "video.hide": "Видеоны яшерү",
+  "video.mute": "Тавышны ябу",
   "video.pause": "Туктату",
-  "video.play": "Уйнату"
+  "video.play": "Уйнату",
+  "video.unmute": "Тавышны ачу"
 }
diff --git a/app/javascript/mastodon/locales/ug.json b/app/javascript/mastodon/locales/ug.json
index e550d7e678..7e7d6836da 100644
--- a/app/javascript/mastodon/locales/ug.json
+++ b/app/javascript/mastodon/locales/ug.json
@@ -1,34 +1,23 @@
 {
-  "about.blocks": "باشقۇرۇلىدىغان مۇلازىمېتىر",
-  "about.contact": "ئالاقە:",
-  "about.disclaimer": "Mastodon ھەقسىز، ئوچۇق كودلۇق يۇمشاق دېتال تاۋار ماركىسى Mastodon gGmbH غا تەۋە.",
-  "about.domain_blocks.no_reason_available": "سەۋەبىنى ئىشلەتكىلى بولمايدۇ",
-  "account.badges.bot": "ماشىنا ئادەم",
-  "account.cancel_follow_request": "ئەگىشىش ئىلتىماسىدىن ۋاز كەچ",
-  "account.posts": "يازما",
-  "account.posts_with_replies": "يازما ۋە ئىنكاس",
-  "account.report": "@{name} نى پاش قىل",
+  "about.blocks": "ئوتتۇراھال مۇلازىمېتىر",
+  "about.contact": "ئالاقىلاشقۇچى:",
+  "account.badges.bot": "Bot",
+  "account.cancel_follow_request": "Withdraw follow request",
+  "account.posts": "Toots",
+  "account.posts_with_replies": "Toots and replies",
   "account.requested": "Awaiting approval",
-  "account_note.placeholder": "چېكىلسە ئىزاھات قوشىدۇ",
-  "column.pins": "چوققىلانغان يازما",
-  "community.column_settings.media_only": "ۋاسىتەلا",
+  "account_note.placeholder": "Click to add a note",
+  "column.pins": "Pinned toot",
+  "community.column_settings.media_only": "Media only",
   "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
   "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
   "compose_form.placeholder": "What is on your mind?",
-  "compose_form.publish_form": "يېڭى يازما",
-  "compose_form.reply": "جاۋاب",
-  "compose_form.save_changes": "يېڭىلا",
-  "compose_form.spoiler.marked": "مەزمۇن ئاگاھلاندۇرۇشىنى چىقىرىۋەت",
+  "compose_form.publish_form": "Publish",
+  "compose_form.spoiler.marked": "Text is hidden behind warning",
   "compose_form.spoiler.unmarked": "Text is not hidden",
-  "compose_form.spoiler_placeholder": "مەزمۇن ئاگاھلاندۇرۇشى (تاللاشچان)",
-  "confirmation_modal.cancel": "ۋاز كەچ",
-  "confirmations.block.confirm": "توس",
-  "confirmations.delete.message": "بۇ يازمىنى راستىنلا ئۆچۈرەمسىز؟",
-  "confirmations.delete.title": "يازما ئۆچۈرەمدۇ؟",
-  "confirmations.delete_list.confirm": "ئۆچۈر",
-  "confirmations.delete_list.message": "بۇ تىزىمنى راستتىنلا مەڭگۈلۈك ئۆچۈرەمسىز؟",
-  "confirmations.delete_list.title": "تىزىمنى ئۆچۈرەمدۇ؟",
-  "confirmations.discard_edit_media.confirm": "تاشلىۋەت",
+  "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "empty_column.account_timeline": "No toots here!",
   "empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
@@ -71,6 +60,19 @@
   "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
   "notification.reblog": "{name} boosted your status",
   "notifications.column_settings.status": "New toots:",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "privacy.change": "Adjust status privacy",
   "report.placeholder": "Type or paste additional comments",
   "report.submit": "Submit report",
@@ -86,5 +88,8 @@
   "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "Uploading…"
 }
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 6d37e0e1db..51b0d99750 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Редагувати профіль",
   "account.enable_notifications": "Повідомляти мене про дописи @{name}",
   "account.endorse": "Рекомендувати у моєму профілі",
-  "account.featured": "Рекомендоване",
-  "account.featured.hashtags": "Хештеги",
-  "account.featured.posts": "Дописи",
   "account.featured_tags.last_status_at": "Останній допис {date}",
   "account.featured_tags.last_status_never": "Немає дописів",
+  "account.featured_tags.title": "{name} виділяє хештеґи",
   "account.follow": "Підписатися",
   "account.follow_back": "Стежити також",
   "account.followers": "Підписники",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} допис}}",
   "account.unblock": "Розблокувати @{name}",
   "account.unblock_domain": "Розблокувати {domain}",
-  "account.unblock_domain_short": "Розблокувати",
   "account.unblock_short": "Розблокувати",
   "account.unendorse": "Не публікувати у профілі",
   "account.unfollow": "Відписатися",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Сталася неочікувана помилка.",
   "alert.unexpected.title": "Ой!",
   "alt_text_badge.title": "Альтернативний текст",
-  "alt_text_modal.add_alt_text": "Додати альтернативний текст",
-  "alt_text_modal.add_text_from_image": "Додати текст із малюнку",
-  "alt_text_modal.cancel": "Скасувати",
-  "alt_text_modal.change_thumbnail": "Змінити мініатюру",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Опишіть цю ідею для людей із порушеннями слуху…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Опишіть цю ідею для людей із порушеннями зору…",
-  "alt_text_modal.done": "Готово",
   "announcement.announcement": "Оголошення",
-  "annual_report.summary.archetype.booster": "Мисливець на дописи",
-  "annual_report.summary.archetype.lurker": "Причаєнець",
-  "annual_report.summary.archetype.oracle": "Оракул",
-  "annual_report.summary.archetype.pollster": "Опитувач",
-  "annual_report.summary.archetype.replier": "Душа компанії",
-  "annual_report.summary.followers.followers": "підписники",
-  "annual_report.summary.followers.total": "Загалом {count}",
-  "annual_report.summary.here_it_is": "Ось ваші підсумки {year} року:",
-  "annual_report.summary.highlighted_post.by_favourites": "найуподобаніші дописи",
-  "annual_report.summary.highlighted_post.by_reblogs": "найпоширюваніші дописи",
-  "annual_report.summary.highlighted_post.by_replies": "найкоментованіші дописи",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "найчастіше використовуваний застосунок",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "найчастіший хештег",
-  "annual_report.summary.most_used_hashtag.none": "Немає",
-  "annual_report.summary.new_posts.new_posts": "нові дописи",
-  "annual_report.summary.percentile.text": "<topLabel>Це виводить вас у топ</topLabel><percentage></percentage><bottomLabel> користувачів Mastodon.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Ми не скажемо Bernie.",
-  "annual_report.summary.thanks": "Дякуємо, що ви є частиною Mastodon!",
   "attachments_list.unprocessed": "(не оброблено)",
   "audio.hide": "Сховати аудіо",
   "block_modal.remote_users_caveat": "Ми попросимо сервер {domain} поважати ваше рішення. Однак дотримання вимог не гарантується, оскільки деякі сервери можуть обробляти блоки по-різному. Загальнодоступні дописи все ще можуть бути видимими для користувачів, які не увійшли в систему.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "Запитувана сторінка не знайдена. Ви впевнені, що URL-адреса у панелі адрес правильна?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Закрити",
-  "bundle_modal_error.message": "Щось пішло не так під час завантаження цього екрану.",
+  "bundle_modal_error.message": "Щось пішло не так під час завантаження цього компоненту.",
   "bundle_modal_error.retry": "Спробувати ще раз",
   "closed_registrations.other_server_instructions": "Оскільки Mastodon децентралізований, ви можете створити обліковий запис на іншому сервері й досі взаємодіяти з ним.",
   "closed_registrations_modal.description": "Створення облікового запису на {domain} наразі неможливе, але майте на увазі, що вам не потрібен обліковий запис саме на {domain}, щоб використовувати Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Заблоковані користувачі",
   "column.bookmarks": "Закладки",
   "column.community": "Локальна стрічка",
-  "column.create_list": "Створити список",
   "column.direct": "Особисті згадки",
   "column.directory": "Переглянути профілі",
   "column.domain_blocks": "Заблоковані домени",
-  "column.edit_list": "Редагувати список",
   "column.favourites": "Уподобане",
   "column.firehose": "Стрічка новин",
   "column.follow_requests": "Запити на підписку",
   "column.home": "Головна",
-  "column.list_members": "Керувати учасниками списку",
   "column.lists": "Списки",
   "column.mutes": "Приховані користувачі",
   "column.notifications": "Сповіщення",
@@ -172,7 +140,6 @@
   "column_header.pin": "Закріпити",
   "column_header.show_settings": "Показати налаштування",
   "column_header.unpin": "Відкріпити",
-  "column_search.cancel": "Скасувати",
   "column_subheading.settings": "Налаштування",
   "community.column_settings.local_only": "Лише локальні",
   "community.column_settings.media_only": "Лише з медіа",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "Тривалість опитування",
   "compose_form.poll.multiple": "Кілька варіантів",
   "compose_form.poll.option_placeholder": "Варіант {number}",
-  "compose_form.poll.single": "Один вибір",
+  "compose_form.poll.single": "Виберіть варіант",
   "compose_form.poll.switch_to_multiple": "Дозволити вибір декількох відповідей",
   "compose_form.poll.switch_to_single": "Перемкнути у режим вибору однієї відповіді",
   "compose_form.poll.type": "Стиль",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Змінити",
   "confirmations.edit.message": "Редагування перезапише повідомлення, яке ви зараз пишете. Ви впевнені, що хочете продовжити?",
   "confirmations.edit.title": "Перезаписати допис?",
-  "confirmations.follow_to_list.confirm": "Підписатися і додати до списку",
-  "confirmations.follow_to_list.message": "Ви повинні слідувати за {name}, щоб додати до списку.",
-  "confirmations.follow_to_list.title": "Підписатися на користувача?",
   "confirmations.logout.confirm": "Вийти",
   "confirmations.logout.message": "Ви впевнені, що хочете вийти?",
   "confirmations.logout.title": "Вийти?",
-  "confirmations.missing_alt_text.confirm": "Додати альтернативний текст",
-  "confirmations.missing_alt_text.message": "У вашому дописі є медіа без альтернативного тексту. Додавання опису допоможе зробити ваші матеріали доступними для більшої кількості людей.",
-  "confirmations.missing_alt_text.secondary": "Все одно опублікувати",
-  "confirmations.missing_alt_text.title": "Додати альтернативний текст?",
   "confirmations.mute.confirm": "Приховати",
   "confirmations.redraft.confirm": "Видалити та виправити",
   "confirmations.redraft.message": "Ви впевнені, що хочете видалити цей допис та переписати його? Додавання у вибране та поширення буде втрачено, а відповіді на оригінальний допис залишаться без першоджерела.",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "Ваш обліковий запис {disabledAccount} наразі вимкнений.",
   "dismissable_banner.community_timeline": "Це останні публічні дописи від людей, чиї облікові записи розміщені на {domain}.",
   "dismissable_banner.dismiss": "Відхилити",
-  "dismissable_banner.explore_links": "Ці новини сьогодні найбільше поширюють у fediverse. Свіжіші новини, опубліковані більшою кількістю різних людей, оцінюються вище.",
-  "dismissable_banner.explore_statuses": "Ці дописи з усього fediverse сьогодні набирають популярності. Новіші дописи з більшою кількістю посилень і додавань у вибрані мають вищий рейтинг.",
-  "dismissable_banner.explore_tags": "Ці хештеги сьогодні набувають популярності у fediverse. Хештеги, якими користується більше людей, займають вищі позиції.",
-  "dismissable_banner.public_timeline": "Це найновіші загальнодоступні дописи від людей у федіверсі, на яких підписані люди в {domain}.",
+  "dismissable_banner.explore_links": "Ці новини, які сьогодні широко поширені на цьому та інших серверах. Новіші новини, написані різними людьми, мають вищий рейтинг.",
+  "dismissable_banner.explore_statuses": "Ці дописи з цього та інших серверів децентралізованої мережі зараз набирають популярності на цьому сервері. Новіші дописи з частішим поширенням та додаванням до вподобаного мають вищий рейтинг.",
+  "dismissable_banner.explore_tags": "Ці хештеги зараз набирають популярності серед людей на цьому та інших серверах децентралізованої мережі. Хештеги, які використовуються більшою кількістю людей, мають вищий рейтинг.",
+  "dismissable_banner.public_timeline": "Це найновіші загальнодоступні дописи від людей в соціальній мережі, на які підписані люди в {domain}.",
   "domain_block_modal.block": "Блокувати сервер",
   "domain_block_modal.block_account_instead": "Блокувати @{name} натомість",
   "domain_block_modal.they_can_interact_with_old_posts": "Люди з цього сервера можуть взаємодіяти зі своїми старими дописами.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Результати пошуку",
   "emoji_button.symbols": "Символи",
   "emoji_button.travel": "Подорожі та місця",
-  "empty_column.account_featured": "Список порожній",
   "empty_column.account_hides_collections": "Цей користувач вирішив не робити цю інформацію доступною",
   "empty_column.account_suspended": "Обліковий запис заблоковано",
   "empty_column.account_timeline": "Тут немає дописів!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Дописів з цим гештеґом поки не існує.",
   "empty_column.home": "Ваша стрічка порожня! Підпишіться на інших, щоб її заповнити.",
   "empty_column.list": "Цей список порожній. Коли його учасники додадуть нові дописи, вони з'являться тут.",
+  "empty_column.lists": "У вас ще немає списків. Коли ви їх створите, вони з'являться тут.",
   "empty_column.mutes": "Ви ще не приховали жодного користувача.",
   "empty_column.notification_requests": "Усе чисто! Тут нічого немає. Коли ви отримаєте нові сповіщення, вони з'являться тут відповідно до ваших налаштувань.",
   "empty_column.notifications": "У вас ще немає сповіщень. Коли інші люди почнуть взаємодіяти з вами, ви побачите їх тут.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Спробуйте їх вимкнути та оновити сторінку. Якщо це не допомагає, ви можете використовувати Mastodon через інший браузер або окремий застосунок.",
   "errors.unexpected_crash.copy_stacktrace": "Скопіювати трасування стека у буфер обміну",
   "errors.unexpected_crash.report_issue": "Повідомити про проблему",
+  "explore.search_results": "Результати пошуку",
   "explore.suggested_follows": "Люди",
   "explore.title": "Огляд",
   "explore.trending_links": "Новини",
@@ -373,14 +334,13 @@
   "footer.about": "Про проєкт",
   "footer.directory": "Каталог профілів",
   "footer.get_app": "Завантажити застосунок",
+  "footer.invite": "Запросити людей",
   "footer.keyboard_shortcuts": "Комбінації клавіш",
   "footer.privacy_policy": "Політика приватності",
   "footer.source_code": "Перегляд програмного коду",
   "footer.status": "Статус",
-  "footer.terms_of_service": "Умови використання",
   "generic.saved": "Збережено",
   "getting_started.heading": "Розпочати",
-  "hashtag.admin_moderation": "Відкрити інтерфейс модерації для #{name}",
   "hashtag.column_header.tag_mode.all": "та {additional}",
   "hashtag.column_header.tag_mode.any": "або {additional}",
   "hashtag.column_header.tag_mode.none": "без {additional}",
@@ -394,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} допис}}",
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} допис}} сьогодні",
   "hashtag.follow": "Стежити за хештегом",
-  "hashtag.mute": "Ігнорувати #{hashtag}",
   "hashtag.unfollow": "Не стежити за хештегом",
   "hashtags.and_other": "…і {count, plural, other {ще #}}",
   "hints.profiles.followers_may_be_missing": "Підписники цього профілю можуть бути не показані.",
@@ -423,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Ігнорувати сповіщення від людей, які не підписані на вас?",
   "ignore_notifications_modal.not_following_title": "Ігнорувати сповіщення від людей, на яких ви не підписалися?",
   "ignore_notifications_modal.private_mentions_title": "Ігнорувати сповіщення від небажаних приватних згадок?",
-  "info_button.label": "Довідка",
-  "info_button.what_is_alt_text": "<h1>Що таке альтернативний текст?</h1> <p>Альтернативний текст містить описи зображень для людей з вадами зору, низькошвидкісними з'єднаннями або тих, хто шукає додатковий контекст.</p> <p>Ви можете покращити доступність і розуміння для всіх, написавши чіткий та лаконічний альтернативний текст.</p> <ul> <li>Позначайте важливі елементи</li> <li>Охоплюйте текст у картинках</li> <li>Використовуйте звичайну структуру речень</li> <li>Уникайте надлишкової інформації</li> <li>Зосередьтеся на тенденціях і ключових висновках у складних візуальних формах (наприклад, діаграмах або картах)</li> </ul>",
-  "interaction_modal.action.favourite": "Щоб продовжити, потрібно додати улюблене з вашого облікового запису.",
-  "interaction_modal.action.follow": "Щоб іти далі, потрібно підписатися з вашого облікового запису.",
-  "interaction_modal.action.reblog": "Щоб іти далі, потрібно зробити реблог з вашого облікового запису.",
-  "interaction_modal.action.reply": "Щоб іти далі, потрібно відповісти з вашого облікового запису.",
-  "interaction_modal.action.vote": "Щоб іти далі, потрібно проголосувати з вашим обліковим записом.",
-  "interaction_modal.go": "Вперед",
-  "interaction_modal.no_account_yet": "Ще не зареєстровані?",
+  "interaction_modal.description.favourite": "Маючи обліковий запис на Mastodon, ви можете вподобати цей допис, щоб дати автору знати, що ви його цінуєте, і зберегти його на потім.",
+  "interaction_modal.description.follow": "Маючи обліковий запис на Mastodon, ви можете підписатися на {name}, щоб отримувати дописи цього користувача у свою стрічку.",
+  "interaction_modal.description.reblog": "Маючи обліковий запис на Mastodon, ви можете поширити цей допис, щоб поділитися ним зі своїми підписниками.",
+  "interaction_modal.description.reply": "Маючи обліковий запис на Mastodon, ви можете відповісти на цей допис.",
+  "interaction_modal.login.action": "На домашню сторінку",
+  "interaction_modal.login.prompt": "Домен вашого домашнього сервера, наприклад, mastodon.social",
+  "interaction_modal.no_account_yet": "Не зареєстровані в Mastodon?",
   "interaction_modal.on_another_server": "На іншому сервері",
   "interaction_modal.on_this_server": "На цьому сервері",
+  "interaction_modal.sign_in": "Ви не ввійшли на цей сервер. Де розміщений ваш обліковий запис?",
+  "interaction_modal.sign_in_hint": "Підказка: це сайт, на якому ви зареєструвалися. Якщо ви не пам'ятаєте, знайдіть привітальний електронний лист у теці \"Вхідні\". Ви також можете ввести повне ім'я користувача! (наприклад, @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Уподобати допис {name}",
   "interaction_modal.title.follow": "Підписатися на {name}",
   "interaction_modal.title.reblog": "Поширити допис {name}",
   "interaction_modal.title.reply": "Відповісти на допис {name}",
-  "interaction_modal.title.vote": "Проголосувати в опитуванні {name}",
-  "interaction_modal.username_prompt": "Наприклад, %{example}",
   "intervals.full.days": "{number, plural, one {# день} few {# дні} other {# днів}}",
   "intervals.full.hours": "{number, plural, one {# година} few {# години} other {# годин}}",
   "intervals.full.minutes": "{number, plural, one {# хвилина} few {# хвилини} other {# хвилин}}",
@@ -475,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "Показати/приховати текст під попередженням про вміст",
   "keyboard_shortcuts.toggle_sensitivity": "Показати/приховати медіа",
   "keyboard_shortcuts.toot": "Створити новий допис",
-  "keyboard_shortcuts.translate": "перекласти допис",
   "keyboard_shortcuts.unfocus": "Розфокусуватися з нового допису чи пошуку",
   "keyboard_shortcuts.up": "Рухатися вгору списком",
   "lightbox.close": "Закрити",
@@ -488,32 +444,20 @@
   "link_preview.author": "Від {name}",
   "link_preview.more_from_author": "Більше від {name}",
   "link_preview.shares": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} допис}}",
-  "lists.add_member": "Додати",
-  "lists.add_to_list": "Додати до списку",
-  "lists.add_to_lists": "Додати {name} до списку",
-  "lists.create": "Створити",
-  "lists.create_a_list_to_organize": "Створіть новий список, щоб упорядкувати домашню стрічку",
-  "lists.create_list": "Створити список",
+  "lists.account.add": "Додати до списку",
+  "lists.account.remove": "Вилучити зі списку",
   "lists.delete": "Видалити список",
-  "lists.done": "Готово",
   "lists.edit": "Редагувати список",
-  "lists.exclusive": "Сховати учасників на головній сторінці",
-  "lists.exclusive_hint": "Якщо хтось є у цьому списку, сховайте їх на своїй домашній сторінці, щоб не бачити їхні дописи двічі.",
-  "lists.find_users_to_add": "Знайти користувачів, щоб додати їх",
-  "lists.list_members": "Учасники списку",
-  "lists.list_members_count": "{count, plural, one {# member} other {# members}}",
-  "lists.list_name": "Назва списку",
-  "lists.new_list_name": "Нова назва списку",
-  "lists.no_lists_yet": "Поки що немає списків.",
-  "lists.no_members_yet": "Ще немає учасників.",
-  "lists.no_results_found": "Результатів не знайдено.",
-  "lists.remove_member": "Видалити",
+  "lists.edit.submit": "Змінити назву",
+  "lists.exclusive": "Сховати ці дописи з домашньої сторінки",
+  "lists.new.create": "Додати список",
+  "lists.new.title_placeholder": "Нова назва списку",
   "lists.replies_policy.followed": "Будь-який відстежуваний користувач",
   "lists.replies_policy.list": "Учасники списку",
   "lists.replies_policy.none": "Ніхто",
-  "lists.save": "Зберегти",
-  "lists.search": "Пошук",
-  "lists.show_replies_to": "Включати відповіді також зі списку учасників",
+  "lists.replies_policy.title": "Показати відповіді для:",
+  "lists.search": "Шукати серед людей, на яких ви підписані",
+  "lists.subheading": "Ваші списки",
   "load_pending": "{count, plural, one {# новий елемент} other {# нових елементів}}",
   "loading_indicator.label": "Завантаження…",
   "media_gallery.hide": "Сховати",
@@ -562,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} повідомляє про {target}",
   "notification.admin.sign_up": "{name} приєдналися",
   "notification.admin.sign_up.name_and_others": "{name} та {count, plural, one {# інший} few {# інших} many {# інших} other {# інший}} зареєструвалися",
-  "notification.annual_report.message": "#Wrapstodon за {year} чекає на вас! Дізнайтеся про найяскравіші та пам'ятні моменти вашого року на Mastodon!",
-  "notification.annual_report.view": "Переглянути #Wrapstodon",
   "notification.favourite": "Ваш допис сподобався {name}",
   "notification.favourite.name_and_others_with_link": "{name} та <a>{count, plural, one {# інший} few {# інших} many {# інших} other {# інший}}</a> вподобали ваш допис",
-  "notification.favourite_pm": "{name} додає вашу особисту згадку до вибраного",
-  "notification.favourite_pm.name_and_others_with_link": "{name} та <a>{count, plural, one {# інший} few {# інших} many {# інших} other {# інший}}</a> додали вашу особисту згадку до вибраного",
   "notification.follow": "{name} підписалися на вас",
   "notification.follow.name_and_others": "{name} та <a>{count, plural, one {# інший} few {# інших} many {# інших} other {# інший}}</a> стежать за вами",
   "notification.follow_request": "{name} відправили запит на підписку",
@@ -672,21 +612,44 @@
   "notifications_permission_banner.enable": "Увімкнути сповіщення стільниці",
   "notifications_permission_banner.how_to_control": "Щоб отримувати сповіщення, коли Mastodon не відкрито, увімкніть сповіщення стільниці. Ви можете контролювати, які типи взаємодій створюють сповіщення через кнопку {icon} вгорі після їхнього увімкнення.",
   "notifications_permission_banner.title": "Не проґавте нічого",
-  "onboarding.follows.back": "Назад",
-  "onboarding.follows.done": "Готово",
+  "onboarding.action.back": "Повернутися назад",
+  "onboarding.actions.back": "Повернутися назад",
+  "onboarding.actions.go_to_explore": "Переглянути тенденції",
+  "onboarding.actions.go_to_home": "Перейти до вашої домашньої стрічки",
+  "onboarding.compose.template": "Привіт #Mastodon!",
   "onboarding.follows.empty": "На жаль, жоден результат не може бути показаний просто зараз. Ви можете спробувати скористатися пошуком або переглядом сторінки огляду, щоб знайти людей для слідкування або повторіть спробу пізніше.",
-  "onboarding.follows.search": "Пошук",
-  "onboarding.follows.title": "Слідкуйте за людьми, щоб почати",
+  "onboarding.follows.lead": "Ваша домашня стрічка - основний спосіб роботи Mastodon. Чим більше людей, які ви підписані, тим активнішою і цікавою. Ось деякі пропозиції на початок:",
+  "onboarding.follows.title": "Персоналізуйте домашню стрічку",
   "onboarding.profile.discoverable": "Зробити мій профіль видимим",
   "onboarding.profile.discoverable_hint": "Якщо ви погоджуєтеся на видимість у Mastodon, ваші дописи можуть з'являтися в результатах пошуку і трендах, і ваш профіль може бути запропоновано людям зі схожими з вашими інтересами.",
   "onboarding.profile.display_name": "Видиме ім'я",
   "onboarding.profile.display_name_hint": "Ваше повне ім'я або ваш псевдонім…",
+  "onboarding.profile.lead": "Ви завжди можете завершити це пізніше в Налаштуваннях, де доступно ще більше опцій налаштування.",
   "onboarding.profile.note": "Біографія",
   "onboarding.profile.note_hint": "Ви можете @згадувати інших людей або #гештеґи…",
   "onboarding.profile.save_and_continue": "Зберегти і продовжити",
   "onboarding.profile.title": "Налаштування профілю",
   "onboarding.profile.upload_avatar": "Завантажити зображення профілю",
   "onboarding.profile.upload_header": "Завантажити заголовок профілю",
+  "onboarding.share.lead": "Розкажіть людям про те, як вони можуть знайти вас на Mastodon!",
+  "onboarding.share.message": "Я {username} на #Mastodon! Стежте за мною на {url}",
+  "onboarding.share.next_steps": "Можливі такі кроки:",
+  "onboarding.share.title": "Поділитися своїм профілем",
+  "onboarding.start.lead": "Тепер ви — частина Mastodon, унікальної децентралізованої платформи соціальних медіа, де ви, а не алгоритми керують вашими вподобаннями. Розпочнімо роботу:",
+  "onboarding.start.skip": "Хочете пропустити?",
+  "onboarding.start.title": "У вас вийшло!",
+  "onboarding.steps.follow_people.body": "Ви керуєте головною стрічкою. Заповнюйте її цікавими людьми.",
+  "onboarding.steps.follow_people.title": "Персоналізуйте домашню стрічку",
+  "onboarding.steps.publish_status.body": "Привітайтеся зі світом, за допомогою тексту, світлин, відео або опитувань {emoji}",
+  "onboarding.steps.publish_status.title": "Напишіть свій перший допис",
+  "onboarding.steps.setup_profile.body": "Інші, ймовірно, швидше взаємодіятимуть з вами, якщо ви заповните профіль.",
+  "onboarding.steps.setup_profile.title": "Персоналізуйте свій профіль",
+  "onboarding.steps.share_profile.body": "Розкажіть друзям, як знайти вас на Mastodon",
+  "onboarding.steps.share_profile.title": "Поділитися своїм профілем Mastodon",
+  "onboarding.tips.2fa": "<strong>Чи знаєте ви?</strong> Ви можете захистити свій обліковий запис, налаштувавши двофакторну автентифікацію в налаштуваннях свого облікового запису. Це працює з будь-яким TOTP-застосунком, без потреби номера телефону!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Чи знаєте ви?</strong> Оскільки Mastodon децентралізований, деякі профілі, які ви відвідуєте, будуть розміщені на інших серверах. І все ж ви можете взаємодіяти з ними! Назва їхнього сервера розміщена у другій половині імені!",
+  "onboarding.tips.migration": "<strong>Чи знаєте ви?</strong> Якщо ви відчуваєте, що {domain} не найкращий вибір сервера в майбутньому, ви можете перейти на інший сервер Mastodon, не втративши при цьому своїх підписників. Ви навіть можете створити власний сервер!",
+  "onboarding.tips.verification": "<strong>Чи знаєте ви?</strong> Ви можете перевірити свій обліковий запис, розмістивши посилання на ваш профіль Mastodon на вашому вебсайті та додавши сайт до вашого профілю. Без плати чи документів!",
   "password_confirmation.exceeds_maxlength": "Підтвердження пароля перевищує максимально допустиму довжину пароля",
   "password_confirmation.mismatching": "Підтвердження пароля не збігається",
   "picture_in_picture.restore": "Повернути назад",
@@ -702,7 +665,7 @@
   "poll_button.remove_poll": "Видалити опитування",
   "privacy.change": "Змінити видимість допису",
   "privacy.direct.long": "Усі згадані в дописі",
-  "privacy.direct.short": "Особиста згадка",
+  "privacy.direct.short": "Певні люди",
   "privacy.private.long": "Лише ваші підписники",
   "privacy.private.short": "Підписники",
   "privacy.public.long": "Усі з Mastodon",
@@ -714,8 +677,8 @@
   "privacy_policy.title": "Політика приватності",
   "recommended": "Рекомендовано",
   "refresh": "Оновити",
-  "regeneration_indicator.please_stand_by": "Будь ласка, очікуйте.",
-  "regeneration_indicator.preparing_your_home_feed": "Готування вашої головної стрічки новин…",
+  "regeneration_indicator.label": "Завантаження…",
+  "regeneration_indicator.sublabel": "Хвилинку, ми готуємо вашу стрічку!",
   "relative_time.days": "{number}д",
   "relative_time.full.days": "{number, plural, one {# день} few {# дні} other {# днів}} тому",
   "relative_time.full.hours": "{number, plural, one {# година} few {# години} other {# годин}} тому",
@@ -799,11 +762,10 @@
   "search_results.accounts": "Профілі",
   "search_results.all": "Усі",
   "search_results.hashtags": "Хештеґи",
-  "search_results.no_results": "Жодних результатів.",
-  "search_results.no_search_yet": "Спробуйте пошукати дописи, профілі або хештеґи.",
+  "search_results.nothing_found": "Нічого не вдалося знайти за цими пошуковими термінами",
   "search_results.see_all": "Показати все",
   "search_results.statuses": "Дописів",
-  "search_results.title": "Шукати \"{q}\"",
+  "search_results.title": "Шукати {q}",
   "server_banner.about_active_users": "Люди, які використовують цей сервер протягом останніх 30 днів (Щомісячні Активні Користувачі)",
   "server_banner.active_users": "активні користувачі",
   "server_banner.administered_by": "Адміністратор:",
@@ -855,7 +817,6 @@
   "status.reblogs.empty": "Ніхто ще не поширив цей допис. Коли хтось це зроблять, вони будуть зображені тут.",
   "status.redraft": "Видалити та виправити",
   "status.remove_bookmark": "Видалити закладку",
-  "status.remove_favourite": "Вилучити з улюбленого",
   "status.replied_in_thread": "Відповідь у потоці",
   "status.replied_to": "Відповідь для {name}",
   "status.reply": "Відповісти",
@@ -877,9 +838,6 @@
   "subscribed_languages.target": "Змінити підписані мови для {target}",
   "tabs_bar.home": "Головна",
   "tabs_bar.notifications": "Сповіщення",
-  "terms_of_service.effective_as_of": "Ефективний на {date}",
-  "terms_of_service.title": "Умови використання",
-  "terms_of_service.upcoming_changes_on": "Майбутні зміни {date}",
   "time_remaining.days": "{number, plural, one {# день} few {# дні} other {# днів}}",
   "time_remaining.hours": "{number, plural, one {# година} few {# години} other {# годин}}",
   "time_remaining.minutes": "{number, plural, one {# хвилина} few {# хвилини} other {# хвилин}}",
@@ -895,12 +853,26 @@
   "upload_button.label": "Додати зображення, відео або аудіо",
   "upload_error.limit": "Ви перевищили ліміт завантаження файлів.",
   "upload_error.poll": "Не можна завантажувати файли до опитувань.",
+  "upload_form.audio_description": "Опишіть для людей із вадами слуху",
+  "upload_form.description": "Опишіть для людей з вадами зору",
   "upload_form.drag_and_drop.instructions": "Щоб вибрати медіавкладення, натисніть пробіл або Enter. Під час перетягування, використайте клавіші зі стрілками для переміщення вкладення в будь-якому напрямку. Натисніть пробіл або Enter знову, щоб залишити медіавкладення в новому положенні, або натисніть клавішу Escape, щоб скасувати.",
   "upload_form.drag_and_drop.on_drag_cancel": "Перетягування скасовано. Медіавкладення {item} прибрано.",
   "upload_form.drag_and_drop.on_drag_end": "Медіавкладення {item} прибрано.",
   "upload_form.drag_and_drop.on_drag_over": "Медіавкладення {item} переміщено.",
   "upload_form.drag_and_drop.on_drag_start": "Медіавкладення {item} вибрано.",
   "upload_form.edit": "Змінити",
+  "upload_form.thumbnail": "Змінити мініатюру",
+  "upload_form.video_description": "Опишіть для людей із вадами слуху або зору",
+  "upload_modal.analyzing_picture": "Аналізуємо зображення…",
+  "upload_modal.apply": "Застосувати",
+  "upload_modal.applying": "Застосування…",
+  "upload_modal.choose_image": "Вибрати зображення",
+  "upload_modal.description_placeholder": "Щурячий бугай із їжаком-харцизом в'ючись підписали ґешефт у єнах",
+  "upload_modal.detect_text": "Виявити текст на зображенні",
+  "upload_modal.edit_media": "Редагувати медіа",
+  "upload_modal.hint": "Клацніть або перетягніть коло на превʼю, щоб обрати точку, яку буде завжди видно на мініатюрах.",
+  "upload_modal.preparing_ocr": "Підготовка OCR…",
+  "upload_modal.preview_label": "Переглянути ({ratio})",
   "upload_progress.label": "Вивантаження...",
   "upload_progress.processing": "Обробка…",
   "username.taken": "Це ім'я користувача вже зайнято. Спробуйте інше",
@@ -910,6 +882,8 @@
   "video.expand": "Розгорнути відео",
   "video.fullscreen": "На весь екран",
   "video.hide": "Приховати відео",
+  "video.mute": "Вимкнути звук",
   "video.pause": "Призупинити",
-  "video.play": "Програвати"
+  "video.play": "Програвати",
+  "video.unmute": "Увімкнути звук"
 }
diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json
index e28ad93828..cb5dfa63cd 100644
--- a/app/javascript/mastodon/locales/ur.json
+++ b/app/javascript/mastodon/locales/ur.json
@@ -22,6 +22,7 @@
   "account.endorse": "مشکص پر نمایاں کریں",
   "account.featured_tags.last_status_at": "آخری پوسٹ {date} کو",
   "account.featured_tags.last_status_never": "کوئی مراسلہ نہیں",
+  "account.featured_tags.title": "{name} کے نمایاں ہیش ٹیگز",
   "account.follow": "پیروی کریں",
   "account.follow_back": "اکاؤنٹ کو فالو بیک ",
   "account.followers": "پیروکار",
@@ -76,6 +77,7 @@
   "bundle_column_error.return": "واپس گھر جاؤ",
   "bundle_column_error.routing.title": "۴۰۴",
   "bundle_modal_error.close": "بند کریں",
+  "bundle_modal_error.message": "اس عنصر کو برآمد کرتے وقت کچھ خرابی پیش آئی ہے.",
   "bundle_modal_error.retry": "دوبارہ کوشش کریں",
   "column.about": "متعلق",
   "column.blocks": "مسدود صارفین",
@@ -139,6 +141,8 @@
   "directory.new_arrivals": "نئے آنے والے",
   "directory.recently_active": "حال میں میں ایکٹیو",
   "dismissable_banner.dismiss": "برخاست کریں",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "یہ اس طرح نظر آئے گا:",
   "emoji_button.activity": "سرگرمی",
@@ -158,6 +162,7 @@
   "empty_column.hashtag": "ابھی یہ ہیش ٹیگ خالی ہے.",
   "empty_column.home": "آپ کا خانگی جدول خالی ہے! {public} دیکھیں یا شروعات کیلئے تلاش کریں اور دیگر صارفین سے ملیں.",
   "empty_column.list": "یہ فہرست ابھی خالی ہے. جب اس فہرست کے ارکان کچھ تحریر کریں گے، یہاں نظر آئے گا.",
+  "empty_column.lists": "ابھی آپ کی کوئی فہرست نہیں ہے. جب آپ بنائیں گے، وہ یہاں نظر آئے گی.",
   "empty_column.mutes": "آپ نے ابھی کسی صارف کو خاموش نہیں کیا ہے.",
   "empty_column.notifications": "ابھی آپ کیلئے کوئی اطلاعات نہیں ہیں. گفتگو شروع کرنے کے لئے دیگر صارفین سے متعامل ہوں.",
   "empty_column.public": "یہاں کچھ بھی نہیں ہے! کچھ عمومی تحریر کریں یا اس جگہ کو پُر کرنے کے لئے از خود دیگر سرورس کے صارفین کی پیروی کریں",
@@ -237,6 +242,19 @@
   "notifications.clear_confirmation": "کیا آپ واقعی اپنی تمام اطلاعات کو صاف کرنا چاہتے ہیں؟",
   "notifications.column_settings.alert": "ڈیسک ٹاپ اطلاعات",
   "notifications.column_settings.status": "New toots:",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "privacy.change": "Adjust status privacy",
   "report.placeholder": "Type or paste additional comments",
   "report.submit": "Submit report",
@@ -252,5 +270,8 @@
   "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_progress.label": "Uploading…"
 }
diff --git a/app/javascript/mastodon/locales/uz.json b/app/javascript/mastodon/locales/uz.json
index 6dd350651d..6dae368ffc 100644
--- a/app/javascript/mastodon/locales/uz.json
+++ b/app/javascript/mastodon/locales/uz.json
@@ -25,6 +25,7 @@
   "account.endorse": "Profildagi xususiyat",
   "account.featured_tags.last_status_at": "Oxirgi post: {date}",
   "account.featured_tags.last_status_never": "Habarlar yo'q",
+  "account.featured_tags.title": "{name} ning taniqli hashtaglari",
   "account.follow": "Obuna bo‘lish",
   "account.followers": "Obunachilar",
   "account.followers.empty": "Bu foydalanuvchini hali hech kim kuzatmaydi.",
@@ -80,6 +81,7 @@
   "bundle_column_error.routing.body": "Soʻralgan sahifani topib boʻlmadi. Manzil satridagi URL to'g'ri ekanligiga ishonchingiz komilmi?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Yopish",
+  "bundle_modal_error.message": "Ushbu mahsulotni qayta belgilashda xatolik yuz berdi.",
   "bundle_modal_error.retry": "Qayta urinib ko'rish",
   "closed_registrations.other_server_instructions": "Mastodon markazlashtirilmaganligi sababli, siz boshqa serverda hisob yaratishingiz va u bilan o'zaro aloqada bo'lishingiz mumkin.",
   "closed_registrations_modal.description": "{domain} da hisob yaratish hozircha imkonsiz, lekin Mastodondan foydalanish uchun maxsus {domain} hisob qaydnomasi kerak emasligini yodda tuting.",
@@ -153,6 +155,8 @@
   "disabled_account_banner.text": "{disabledAccount} hisobingiz hozirda oʻchirib qoʻyilgan.",
   "dismissable_banner.community_timeline": "Bular akkauntlari {domain} tomonidan joylashtirilgan odamlarning eng soʻnggi ochiq postlari.",
   "dismissable_banner.dismiss": "Bekor qilish",
+  "dismissable_banner.explore_links": "Ushbu yangiliklar haqida hozirda markazlashtirilmagan tarmoqning ushbu va boshqa serverlarida odamlar gaplashmoqda.",
+  "dismissable_banner.explore_tags": "Ushbu hashtaglar hozirda markazlashtirilmagan tarmoqning ushbu va boshqa serverlarida odamlar orasida qiziqish uyg'otmoqda.",
   "embed.instructions": "Quyidagi kodni nusxalash orqali ushbu postni veb-saytingizga joylashtiring.",
   "embed.preview": "Bu qanday ko'rinishda bo'ladi:",
   "emoji_button.activity": "Faoliyat",
@@ -183,6 +187,7 @@
   "empty_column.hashtag": "Ushbu hashtagda hali hech narsa yo'q.",
   "empty_column.home": "Bosh sahifa yilnomangiz boʻsh! Uni to'ldirish uchun ko'proq odamlarni kuzatib boring. {suggestions}",
   "empty_column.list": "Bu ro'yxatda hali hech narsa yo'q. Ushbu roʻyxat aʼzolari yangi xabarlarni nashr qilganda, ular shu yerda paydo boʻladi.",
+  "empty_column.lists": "Sizda hali hech qanday roʻyxat yoʻq. Uni yaratganingizda, u shu yerda paydo bo'ladi.",
   "empty_column.mutes": "Siz hali hech bir foydalanuvchining ovozini o‘chirmagansiz.",
   "empty_column.notifications": "Sizda hali hech qanday bildirishnoma yo‘q. Boshqa odamlar siz bilan muloqot qilganda, buni shu yerda ko'rasiz.",
   "empty_column.public": "Bu erda hech narsa yo'q! Biror narsani ochiq yozing yoki uni toʻldirish uchun boshqa serverlardagi foydalanuvchilarni qoʻlda kuzatib boring",
@@ -192,6 +197,7 @@
   "error.unexpected_crash.next_steps_addons": "Ularni o'chirib ko'ring va sahifani yangilang. Agar bu yordam bermasa, siz boshqa brauzer yoki mahalliy ilova orqali Mastodondan foydalanishingiz mumkin.",
   "errors.unexpected_crash.copy_stacktrace": "Stacktrace-ni vaqtinchalik xotiraga nusxalash",
   "errors.unexpected_crash.report_issue": "Muammo haqida xabar berish",
+  "explore.search_results": "Qidiruv natijalari",
   "explore.title": "Ko'rib chiqish",
   "explore.trending_links": "Yangiliklar",
   "explore.trending_statuses": "Postlar",
@@ -219,6 +225,7 @@
   "footer.about": "Haqida",
   "footer.directory": "Profillar katalogi",
   "footer.get_app": "Ilovani yuklab oling",
+  "footer.invite": "Odamlarni taklif qilish",
   "footer.keyboard_shortcuts": "Klaviatura yorliqlari",
   "footer.privacy_policy": "Maxfiylik siyosati",
   "footer.source_code": "Kodini ko'rish",
@@ -239,6 +246,9 @@
   "home.column_settings.show_replies": "Javoblarni ko'rish",
   "home.hide_announcements": "E'lonlarni yashirish",
   "home.show_announcements": "E'lonlarni ko'rsatish",
+  "interaction_modal.description.follow": "Mastodon’da akkauntga ega bo‘lgan holda, siz {name} ga obuna bo‘lib, ularning postlarini bosh sahifangizga olishingiz mumkin.",
+  "interaction_modal.description.reblog": "Mastodon-dagi akkaunt yordamida siz ushbu postni o'z izdoshlaringiz bilan baham ko'rish uchun oshirishingiz mumkin.",
+  "interaction_modal.description.reply": "Mastodondagi akkaunt bilan siz ushbu xabarga javob berishingiz mumkin.",
   "interaction_modal.on_another_server": "Boshqa serverda",
   "interaction_modal.on_this_server": "Shu serverda",
   "interaction_modal.title.follow": "{name} ga ergashing",
@@ -278,10 +288,16 @@
   "keyboard_shortcuts.up": "Roʻyxatda yuqoriga koʻtarish",
   "lightbox.close": "Yopish",
   "limited_account_hint.action": "Baribir profilni ko'rsatish",
+  "lists.account.add": "Ro‘yxatga qo‘shish",
+  "lists.account.remove": "Roʻyxatdan o'chirish",
   "lists.delete": "Roʻyxatni o'chirish",
   "lists.edit": "Roʻyxatni tahrirlash",
+  "lists.new.create": "Ro‘yxatga qo‘shish",
   "lists.replies_policy.list": "Ro'yxat a'zolari",
   "lists.replies_policy.none": "Hech kim",
+  "lists.replies_policy.title": "Javoblarni ko'rsatish:",
+  "lists.search": "Siz kuzatadigan odamlar orasidan qidiring",
+  "lists.subheading": "Sizning ro'yxatlaringiz",
   "load_pending": "{count, plural, one {# yangi element} other {# yangi elementlar}}",
   "moved_to_account_banner.text": "{movedToAccount} hisobiga koʻchganingiz uchun {disabledAccount} hisobingiz hozirda oʻchirib qoʻyilgan.",
   "navigation_bar.about": "Haqida",
@@ -307,6 +323,19 @@
   "not_signed_in_indicator.not_signed_in": "Ushbu manbaga kirish uchun tizimga kirishingiz kerak.",
   "notification.own_poll": "So‘rovingiz tugadi",
   "notification.reblog": "{name} boosted your status",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "privacy.change": "Adjust status privacy",
   "report.placeholder": "Type or paste additional comments",
   "report.submit": "Submit report",
@@ -319,5 +348,6 @@
   "status.open": "Expand this status",
   "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
+  "upload_form.audio_description": "Describe for people who are hard of hearing",
   "upload_progress.label": "Uploading…"
 }
diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json
index cabf3a5a82..5582827469 100644
--- a/app/javascript/mastodon/locales/vi.json
+++ b/app/javascript/mastodon/locales/vi.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "Sửa hồ sơ",
   "account.enable_notifications": "Nhận thông báo khi @{name} đăng tút",
   "account.endorse": "Tôn vinh người này",
-  "account.featured": "Nổi bật",
-  "account.featured.hashtags": "Hashtag",
-  "account.featured.posts": "Tút",
   "account.featured_tags.last_status_at": "Tút gần nhất {date}",
   "account.featured_tags.last_status_never": "Chưa có tút",
+  "account.featured_tags.title": "Hashtag của {name}",
   "account.follow": "Theo dõi",
   "account.follow_back": "Theo dõi lại",
   "account.followers": "Người theo dõi",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, other {{counter} Tút}}",
   "account.unblock": "Bỏ chặn @{name}",
   "account.unblock_domain": "Bỏ ẩn {domain}",
-  "account.unblock_domain_short": "Bỏ chặn",
   "account.unblock_short": "Bỏ chặn",
   "account.unendorse": "Ngưng tôn vinh người này",
   "account.unfollow": "Bỏ theo dõi",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "Đã xảy ra lỗi không mong muốn.",
   "alert.unexpected.title": "Ốiii!",
   "alt_text_badge.title": "Văn bản thay thế",
-  "alt_text_modal.add_alt_text": "Thêm văn bản thay thế",
-  "alt_text_modal.add_text_from_image": "Thêm văn bản từ ảnh",
-  "alt_text_modal.cancel": "Hủy bỏ",
-  "alt_text_modal.change_thumbnail": "Đổi thumbnail",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "Mô tả cho người khiếm thính…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "Mô tả cho người khiếm thị…",
-  "alt_text_modal.done": "Xong",
   "announcement.announcement": "Có gì mới?",
-  "annual_report.summary.archetype.booster": "Hiệp sĩ ngầu",
-  "annual_report.summary.archetype.lurker": "Kẻ rình mò",
-  "annual_report.summary.archetype.oracle": "Nhà tiên tri",
-  "annual_report.summary.archetype.pollster": "Chuyên gia khảo sát",
-  "annual_report.summary.archetype.replier": "Bướm xã hội",
-  "annual_report.summary.followers.followers": "người theo dõi",
-  "annual_report.summary.followers.total": "tổng {count}",
-  "annual_report.summary.here_it_is": "Nhìn lại năm {year} của bạn:",
-  "annual_report.summary.highlighted_post.by_favourites": "tút được thích nhiều nhất",
-  "annual_report.summary.highlighted_post.by_reblogs": "tút được đăng lại nhiều nhất",
-  "annual_report.summary.highlighted_post.by_replies": "tút được trả lời nhiều nhất",
-  "annual_report.summary.highlighted_post.possessive": "{name}",
-  "annual_report.summary.most_used_app.most_used_app": "app dùng nhiều nhất",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag dùng nhiều nhất",
-  "annual_report.summary.most_used_hashtag.none": "Không có",
-  "annual_report.summary.new_posts.new_posts": "tút mới",
-  "annual_report.summary.percentile.text": "<topLabel>Bạn thuộc top</topLabel><percentage></percentage><bottomLabel>thành viên của {domain}.</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "Chúng tôi sẽ không kể cho Bernie.",
-  "annual_report.summary.thanks": "Cảm ơn đã trở thành một phần của Mastodon!",
   "attachments_list.unprocessed": "(chưa xử lí)",
   "audio.hide": "Ẩn âm thanh",
   "block_modal.remote_users_caveat": "Chúng tôi sẽ yêu cầu {domain} tôn trọng quyết định của bạn. Tuy nhiên, việc tuân thủ không được đảm bảo vì một số máy chủ có thể xử lý việc chặn theo cách khác nhau. Các tút công khai vẫn có thể hiển thị đối với người dùng chưa đăng nhập.",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "Không thể tìm thấy trang cần tìm. Bạn có chắc URL trong thanh địa chỉ là chính xác?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Đóng",
-  "bundle_modal_error.message": "Đã có lỗi xảy ra trong khi tải màn hình này.",
+  "bundle_modal_error.message": "Đã có lỗi xảy ra trong khi tải nội dung này.",
   "bundle_modal_error.retry": "Thử lại",
   "closed_registrations.other_server_instructions": "Tạo tài khoản trên máy chủ khác và vẫn tương tác với máy chủ này.",
   "closed_registrations_modal.description": "{domain} hiện tắt đăng ký, nhưng hãy lưu ý rằng bạn không cần một tài khoản riêng trên {domain} để sử dụng Mastodon.",
@@ -150,16 +121,13 @@
   "column.blocks": "Người đã chặn",
   "column.bookmarks": "Những tút đã lưu",
   "column.community": "Máy chủ này",
-  "column.create_list": "Tạo danh sách",
   "column.direct": "Nhắn riêng",
   "column.directory": "Tìm người cùng sở thích",
   "column.domain_blocks": "Máy chủ đã chặn",
-  "column.edit_list": "Sửa danh sách",
   "column.favourites": "Những tút đã thích",
   "column.firehose": "Bảng tin",
   "column.follow_requests": "Yêu cầu theo dõi",
   "column.home": "Trang chủ",
-  "column.list_members": "Quản lý danh sách",
   "column.lists": "Danh sách",
   "column.mutes": "Người đã ẩn",
   "column.notifications": "Thông báo",
@@ -172,7 +140,6 @@
   "column_header.pin": "Ghim",
   "column_header.show_settings": "Hiện bộ lọc",
   "column_header.unpin": "Không ghim",
-  "column_search.cancel": "Hủy bỏ",
   "column_subheading.settings": "Cài đặt",
   "community.column_settings.local_only": "Chỉ máy chủ của bạn",
   "community.column_settings.media_only": "Chỉ hiện tút có media",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "Sửa",
   "confirmations.edit.message": "Nội dung tút cũ sẽ bị ghi đè, bạn có tiếp tục?",
   "confirmations.edit.title": "Ghi đè lên tút cũ",
-  "confirmations.follow_to_list.confirm": "Theo dõi và thêm vào danh sách",
-  "confirmations.follow_to_list.message": "Bạn cần theo dõi {name} trước khi thêm họ vào danh sách.",
-  "confirmations.follow_to_list.title": "Theo dõi người này?",
   "confirmations.logout.confirm": "Đăng xuất",
   "confirmations.logout.message": "Bạn có chắc muốn thoát?",
   "confirmations.logout.title": "Đăng xuất",
-  "confirmations.missing_alt_text.confirm": "Thêm văn bản thay thế",
-  "confirmations.missing_alt_text.message": "Tút của bạn chứa media không có văn bản thay thế. Thêm mô tả giúp nội dung của bạn dễ tiếp cận với nhiều người hơn.",
-  "confirmations.missing_alt_text.secondary": "Đăng luôn",
-  "confirmations.missing_alt_text.title": "Thêm văn bản thay thế?",
   "confirmations.mute.confirm": "Ẩn",
   "confirmations.redraft.confirm": "Xóa & viết lại",
   "confirmations.redraft.message": "Điều này sẽ khiến những lượt thích và đăng lại của tút bị mất, cũng như những trả lời sẽ không còn nội dung gốc.",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "Kết quả tìm kiếm",
   "emoji_button.symbols": "Biểu tượng",
   "emoji_button.travel": "Du lịch",
-  "empty_column.account_featured": "Danh sách trống",
   "empty_column.account_hides_collections": "Người này đã chọn ẩn thông tin",
   "empty_column.account_suspended": "Tài khoản vô hiệu hóa",
   "empty_column.account_timeline": "Chưa có tút nào!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "Chưa có tút nào dùng hashtag này.",
   "empty_column.home": "Trang chủ của bạn đang trống! Hãy theo dõi nhiều người hơn để lấp đầy.",
   "empty_column.list": "Chưa có tút. Khi những người trong danh sách này đăng tút mới, chúng sẽ xuất hiện ở đây.",
+  "empty_column.lists": "Bạn chưa tạo danh sách nào.",
   "empty_column.mutes": "Bạn chưa ẩn bất kỳ ai.",
   "empty_column.notification_requests": "Sạch sẽ! Không còn gì ở đây. Khi bạn nhận được thông báo mới, chúng sẽ xuất hiện ở đây theo cài đặt của bạn.",
   "empty_column.notifications": "Bạn chưa có thông báo nào. Hãy thử theo dõi hoặc nhắn riêng cho ai đó.",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "Hãy tắt add-on và làm tươi trang. Nếu vẫn không được, bạn nên thử đăng nhập Mastodon trên trình duyệt khác hoặc app khác.",
   "errors.unexpected_crash.copy_stacktrace": "Sao chép stacktrace vào clipboard",
   "errors.unexpected_crash.report_issue": "Báo cáo lỗi",
+  "explore.search_results": "Kết quả tìm kiếm",
   "explore.suggested_follows": "Mọi người",
   "explore.title": "Khám phá",
   "explore.trending_links": "Tin tức",
@@ -373,16 +334,13 @@
   "footer.about": "Giới thiệu",
   "footer.directory": "Cộng đồng",
   "footer.get_app": "Ứng dụng",
+  "footer.invite": "Mời bạn bè",
   "footer.keyboard_shortcuts": "Phím tắt",
   "footer.privacy_policy": "Bảo mật",
   "footer.source_code": "Mã nguồn",
   "footer.status": "Trạng thái",
-  "footer.terms_of_service": "Điều khoản dịch vụ",
   "generic.saved": "Đã lưu",
   "getting_started.heading": "Quản lý",
-  "hashtag.admin_moderation": "Mở giao diện quản trị #{name}",
-  "hashtag.browse": "Tìm tút #{hashtag}",
-  "hashtag.browse_from_account": "Tìm tút của @{name} có chứa #{hashtag}",
   "hashtag.column_header.tag_mode.all": "và {additional}",
   "hashtag.column_header.tag_mode.any": "hoặc {additional}",
   "hashtag.column_header.tag_mode.none": "mà không {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, other {{counter} tút}}",
   "hashtag.counter_by_uses_today": "{count, plural, other {{counter} tút}} hôm nay",
   "hashtag.follow": "Theo dõi hashtag",
-  "hashtag.mute": "Ẩn #{hashtag}",
   "hashtag.unfollow": "Bỏ theo dõi hashtag",
   "hashtags.and_other": "…và {count, plural, other {# nữa}}",
   "hints.profiles.followers_may_be_missing": "Số người theo dõi có thể không đầy đủ.",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "Bỏ qua thông báo từ những người chưa theo dõi bạn?",
   "ignore_notifications_modal.not_following_title": "Bỏ qua thông báo từ những người bạn không theo dõi?",
   "ignore_notifications_modal.private_mentions_title": "Bỏ qua thông báo từ những lượt Nhắn Riêng không mong muốn?",
-  "info_button.label": "Trợ giúp",
-  "info_button.what_is_alt_text": "<h1>Văn bản thay thế là gì?</h1> <p>Văn bản thay thế giúp mô tả hình ảnh cho những người khiếm thị, kết nối mạng chậm hoặc những người muốn biết ngữ cảnh bổ sung.</p> <p>Bạn có thể cải thiện khả năng tiếp cận và giải thích kỹ hơn cho mọi người bằng cách viết văn bản thay thế rõ ràng, ngắn gọn và khách quan.</p> <ul> <li>Nắm bắt thành phần quan trọng</li> <li>Tóm tắt văn bản trong hình</li> <li>Dùng cấu trúc câu đơn</li> <li>Tránh giải thích rối rắmn</li> <li>Tập trung vào các xu hướng và phát hiện chính trong hình ảnh phức tạp (như biểu đồ hoặc bản đồ)</li> </ul>",
-  "interaction_modal.action.favourite": "Để thích, bạn cần dùng tài khoản của bạn.",
-  "interaction_modal.action.follow": "Để theo dõi, bạn cần dùng tài khoản của bạn.",
-  "interaction_modal.action.reblog": "Để đăng lại, bạn cần dùng tài khoản của bạn.",
-  "interaction_modal.action.reply": "Để trả lời, bạn cần dùng tài khoản của bạn.",
-  "interaction_modal.action.vote": "Để bình chọn, bạn cần dùng tài khoản của bạn.",
-  "interaction_modal.go": "Đi",
-  "interaction_modal.no_account_yet": "Chưa có tài khoản?",
+  "interaction_modal.description.favourite": "Với tài khoản Mastodon, bạn có thể cho người đăng biết bạn thích tút này và lưu lại tút.",
+  "interaction_modal.description.follow": "Với tài khoản Mastodon, bạn có thể theo dõi {name} để tút của họ hiện trên bảng tin của mình.",
+  "interaction_modal.description.reblog": "Với tài khoản Mastodon, bạn có thể đăng lại tút này để chia sẻ nó với những người đang theo dõi bạn.",
+  "interaction_modal.description.reply": "Với tài khoản Mastodon, bạn có thể trả lời tút này.",
+  "interaction_modal.login.action": "Đăng nhập ngay",
+  "interaction_modal.login.prompt": "Địa chỉ máy chủ của bạn, vd: mastodon.social",
+  "interaction_modal.no_account_yet": "Chưa có tài khoản Mastodon?",
   "interaction_modal.on_another_server": "Trên máy chủ khác",
   "interaction_modal.on_this_server": "Trên máy chủ này",
+  "interaction_modal.sign_in": "Bạn chưa đăng nhập. Bạn đã có tài khoản ở máy chủ khác?",
+  "interaction_modal.sign_in_hint": "Mẹo: Đó là trang web nơi bạn đã đăng ký. Nếu không nhớ, lục lại trong email của bạn. Bạn cũng có thể nhập địa chỉ Mastodon đầy đủ! (vd: @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "Thích tút của {name}",
   "interaction_modal.title.follow": "Theo dõi {name}",
   "interaction_modal.title.reblog": "Đăng lại tút của {name}",
   "interaction_modal.title.reply": "Trả lời tút của {name}",
-  "interaction_modal.title.vote": "Bình chọn cùng {name}",
-  "interaction_modal.username_prompt": "Ví dụ: {example}",
   "intervals.full.days": "{number, plural, other {# ngày}}",
   "intervals.full.hours": "{number, plural, other {# giờ}}",
   "intervals.full.minutes": "{number, plural, other {# phút}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "ẩn/hiện nội dung ẩn",
   "keyboard_shortcuts.toggle_sensitivity": "ẩn/hiện ảnh hoặc video",
   "keyboard_shortcuts.toot": "soạn tút mới",
-  "keyboard_shortcuts.translate": "dịch tút",
   "keyboard_shortcuts.unfocus": "đưa con trỏ ra khỏi ô soạn thảo hoặc ô tìm kiếm",
   "keyboard_shortcuts.up": "di chuyển lên trên danh sách",
   "lightbox.close": "Đóng",
@@ -490,32 +444,20 @@
   "link_preview.author": "Bởi {name}",
   "link_preview.more_from_author": "Viết bởi {name}",
   "link_preview.shares": "{count, plural, other {{counter} lượt chia sẻ}}",
-  "lists.add_member": "Thêm",
-  "lists.add_to_list": "Thêm vào danh sách",
-  "lists.add_to_lists": "Thêm {name} vào danh sách",
-  "lists.create": "Tạo",
-  "lists.create_a_list_to_organize": "Tạo một danh sách để sắp xếp Bảng tin",
-  "lists.create_list": "Tạo danh sách",
+  "lists.account.add": "Thêm vào danh sách",
+  "lists.account.remove": "Xóa khỏi danh sách",
   "lists.delete": "Xóa danh sách",
-  "lists.done": "Xong",
   "lists.edit": "Sửa danh sách",
-  "lists.exclusive": "Ẩn thành viên trong Trang chủ",
-  "lists.exclusive_hint": "Nếu ai đó có trong danh sách này, ẩn họ trong Trang chủ để tránh thấy tút của họ hiện trùng lặp.",
-  "lists.find_users_to_add": "Tìm người để thêm vào",
-  "lists.list_members": "Liệt kê các thành viên",
-  "lists.list_members_count": "{count, plural, other {# thành viên}}",
-  "lists.list_name": "Tên danh sách",
-  "lists.new_list_name": "Tên danh sách mới",
-  "lists.no_lists_yet": "Chưa có danh sách nào.",
-  "lists.no_members_yet": "Chưa có thành viên nào.",
-  "lists.no_results_found": "Không tìm thấy kết quả nào.",
-  "lists.remove_member": "Xóa",
+  "lists.edit.submit": "Thay đổi tiêu đề",
+  "lists.exclusive": "Ẩn những tút này khỏi bảng tin",
+  "lists.new.create": "Tạo mới",
+  "lists.new.title_placeholder": "Tên danh sách",
   "lists.replies_policy.followed": "Người theo dõi",
   "lists.replies_policy.list": "Người trong danh sách",
   "lists.replies_policy.none": "Không ai",
-  "lists.save": "Lưu",
-  "lists.search": "Tìm kiếm",
-  "lists.show_replies_to": "Bao gồm lượt trả lời từ thành viên danh sách",
+  "lists.replies_policy.title": "Cho phép trả lời với:",
+  "lists.search": "Tìm kiếm những người mà bạn quan tâm",
+  "lists.subheading": "Danh sách của bạn",
   "load_pending": "{count, plural, one {# tút mới} other {# tút mới}}",
   "loading_indicator.label": "Đang tải…",
   "media_gallery.hide": "Ẩn",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} báo cáo {target}",
   "notification.admin.sign_up": "{name} tham gia máy chủ của bạn",
   "notification.admin.sign_up.name_and_others": "{name} và {count, plural, other {# người}} đã đăng ký",
-  "notification.annual_report.message": "#Wrapstodon {year} của bạn có rồi đây! Hãy chia sẻ những điểm nhấn và khoảnh khắc đáng nhớ trên Mastodon của bạn trong năm qua!",
-  "notification.annual_report.view": "Xem #Wrapstodon",
   "notification.favourite": "{name} đã thích tút của bạn",
   "notification.favourite.name_and_others_with_link": "{name} và <a>{count, plural, other {# người khác}}</a> đã thích tút của bạn",
-  "notification.favourite_pm": "{name} đã thích lượt nhắn riêng của bạn",
-  "notification.favourite_pm.name_and_others_with_link": "{name} và <a>{count, plural, other {# người khác}}</a> đã thích lượt nhắn riêng của bạn",
   "notification.follow": "{name} đã theo dõi bạn",
   "notification.follow.name_and_others": "{name} và <a>{count, plural, other {# người khác}}</a> theo dõi bạn",
   "notification.follow_request": "{name} yêu cầu theo dõi bạn",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "Cho phép thông báo trên màn hình",
   "notifications_permission_banner.how_to_control": "Hãy bật thông báo trên màn hình để không bỏ lỡ những thông báo từ Mastodon. Một khi đã bật, bạn có thể lựa chọn từng loại thông báo khác nhau thông qua {icon} nút bên dưới.",
   "notifications_permission_banner.title": "Không bỏ lỡ điều thú vị nào",
-  "onboarding.follows.back": "Quay lại",
-  "onboarding.follows.done": "Xong",
+  "onboarding.action.back": "Quay lại",
+  "onboarding.actions.back": "Quay lại",
+  "onboarding.actions.go_to_explore": "Xem những gì đang thịnh hành",
+  "onboarding.actions.go_to_home": "Đến trang chủ",
+  "onboarding.compose.template": "Xin chào #Mastodon!",
   "onboarding.follows.empty": "Không có kết quả có thể được hiển thị lúc này. Bạn có thể thử sử dụng tính năng tìm kiếm hoặc duyệt qua trang khám phá để tìm những người theo dõi hoặc thử lại sau.",
-  "onboarding.follows.search": "Tìm kiếm",
-  "onboarding.follows.title": "Tìm người để theo dõi",
+  "onboarding.follows.lead": "Bạn quản lý bảng tin của riêng bạn. Bạn càng theo dõi nhiều người, nó sẽ càng sôi động và thú vị. Để bắt đầu, đây là vài gợi ý:",
+  "onboarding.follows.title": "Thịnh hành trên Mastodon",
   "onboarding.profile.discoverable": "Bật khám phá cho hồ sơ của tôi",
   "onboarding.profile.discoverable_hint": "Khi bạn bật khám phá trên Mastodon, các tút của bạn có thể xuất hiện trong kết quả tìm kiếm và xu hướng, đồng thời hồ sơ của bạn sẽ được đề xuất cho những người có cùng sở thích với bạn.",
   "onboarding.profile.display_name": "Biệt danh",
   "onboarding.profile.display_name_hint": "Tên đầy đủ hoặc biệt danh đều được…",
+  "onboarding.profile.lead": "Bạn có thể cài đặt lại trong phần cài đặt, nơi thậm chí còn có nhiều tùy chọn hơn.",
   "onboarding.profile.note": "Giới thiệu",
   "onboarding.profile.note_hint": "Bạn có thể @aiđó hoặc #hashtags…",
   "onboarding.profile.save_and_continue": "Lưu và tiếp tục",
   "onboarding.profile.title": "Thiết lập hồ sơ",
   "onboarding.profile.upload_avatar": "Tải lên ảnh đại diện",
   "onboarding.profile.upload_header": "Tải lên ảnh bìa",
+  "onboarding.share.lead": "Hãy cho mọi người biết làm thế nào họ có thể tìm thấy bạn trên Mastodon!",
+  "onboarding.share.message": "Tôi là {username} trên #Mastodon! Hãy theo dõi tôi tại {url}",
+  "onboarding.share.next_steps": "Các bước kế tiếp:",
+  "onboarding.share.title": "Chia sẻ hồ sơ",
+  "onboarding.start.lead": "Tài khoản Mastodon mới của bạn đã sẵn sàng hoạt động. Đây là cách bạn có thể tận dụng tối đa nó:",
+  "onboarding.start.skip": "Muốn bỏ qua luôn?",
+  "onboarding.start.title": "Xong rồi bạn!",
+  "onboarding.steps.follow_people.body": "Theo dõi những người thú vị trên Mastodon.",
+  "onboarding.steps.follow_people.title": "Cá nhân hóa trang chủ",
+  "onboarding.steps.publish_status.body": "Chào cộng đồng bằng lời nói, ảnh hoặc video {emoji}",
+  "onboarding.steps.publish_status.title": "Đăng tút đầu tiên",
+  "onboarding.steps.setup_profile.body": "Tạo sự tương tác bằng một hồ sơ hoàn chỉnh.",
+  "onboarding.steps.setup_profile.title": "Tùy biến hồ sơ",
+  "onboarding.steps.share_profile.body": "Hãy để bạn bè của bạn biết cách tìm thấy bạn trên Mastodon!",
+  "onboarding.steps.share_profile.title": "Chia sẻ hồ sơ Mastodon của bạn",
+  "onboarding.tips.2fa": "<strong>Bạn có biết?</strong> Bạn có thể bảo mật tài khoản của mình bằng cách thiết lập xác thực hai yếu tố trong cài đặt tài khoản của mình. Nó hoạt động với bất kỳ ứng dụng OTP nào bạn chọn, không cần số điện thoại!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>Bạn có biết?</strong> Vì Mastodon liên hợp, một số hồ sơ bạn gặp sẽ được lưu trữ trên các máy chủ không giống bạn. Tuy nhiên, bạn có thể tương tác với họ một cách liền mạch! Máy chủ của họ nằm ở nửa sau tên người dùng của họ!",
+  "onboarding.tips.migration": "<strong>Bạn có biết?</strong> Nếu bạn thấy {domain} không phải là lựa chọn máy chủ tuyệt vời cho bạn trong tương lai, bạn có thể chuyển sang máy chủ Mastodon khác mà không bị mất người theo dõi. Bạn thậm chí có thể lưu trữ máy chủ của riêng bạn!",
+  "onboarding.tips.verification": "<strong>Bạn có biết?</strong> Bạn có thể xác minh tài khoản của mình bằng cách đặt liên kết tới hồ sơ Mastodon trên trang web của riêng bạn và thêm trang web vào hồ sơ của bạn. Không có lệ phí hoặc tài liệu cần thiết!",
   "password_confirmation.exceeds_maxlength": "Mật khẩu vượt quá độ dài mật khẩu tối đa",
   "password_confirmation.mismatching": "Mật khẩu không trùng khớp",
   "picture_in_picture.restore": "Hiển thị bình thường",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "Xóa bình chọn",
   "privacy.change": "Chọn kiểu tút",
   "privacy.direct.long": "Những người được nhắc trong tút",
-  "privacy.direct.short": "Nhắn riêng",
+  "privacy.direct.short": "Người cụ thể",
   "privacy.private.long": "Chỉ người theo dõi",
   "privacy.private.short": "Người theo dõi",
   "privacy.public.long": "Bất cứ ai",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "Chính sách bảo mật",
   "recommended": "Đề xuất",
   "refresh": "Làm mới",
-  "regeneration_indicator.please_stand_by": "Hãy chờ đã.",
-  "regeneration_indicator.preparing_your_home_feed": "Đang chuẩn bị bảng tin…",
+  "regeneration_indicator.label": "Đang tải…",
+  "regeneration_indicator.sublabel": "Trang chủ của bạn đang được cập nhật!",
   "relative_time.days": "{number} ngày",
   "relative_time.full.days": "{number, plural, other {# ngày}}",
   "relative_time.full.hours": "{number, plural, other {# giờ}}",
@@ -801,11 +762,10 @@
   "search_results.accounts": "Mọi người",
   "search_results.all": "Toàn bộ",
   "search_results.hashtags": "Hashtag",
-  "search_results.no_results": "Không có kết quả.",
-  "search_results.no_search_yet": "Thử tìm tút, người dùng hoặc hashtag.",
+  "search_results.nothing_found": "Không tìm thấy gì",
   "search_results.see_all": "Xem tất cả",
   "search_results.statuses": "Tút",
-  "search_results.title": "Tìm kiếm \"{q}\"",
+  "search_results.title": "Tìm kiếm {q}",
   "server_banner.about_active_users": "Những người ở máy chủ này trong 30 ngày qua (MAU)",
   "server_banner.active_users": "người hoạt động",
   "server_banner.administered_by": "Vận hành:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "Tút này chưa có ai đăng lại. Nếu có, nó sẽ hiển thị ở đây.",
   "status.redraft": "Xóa và viết lại",
   "status.remove_bookmark": "Bỏ lưu",
-  "status.remove_favourite": "Bỏ thích",
   "status.replied_in_thread": "Trả lời thảo luận",
   "status.replied_to": "Trả lời {name}",
   "status.reply": "Trả lời",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "Đổi ngôn ngữ mong muốn cho {target}",
   "tabs_bar.home": "Trang chủ",
   "tabs_bar.notifications": "Thông báo",
-  "terms_of_service.effective_as_of": "Có hiệu lực vào {date}",
-  "terms_of_service.title": "Điều khoản Dịch vụ",
-  "terms_of_service.upcoming_changes_on": "Sắp thay đổi vào {date}",
   "time_remaining.days": "{number, plural, other {# ngày}}",
   "time_remaining.hours": "{number, plural, other {# giờ}}",
   "time_remaining.minutes": "{number, plural, other {# phút}}",
@@ -897,12 +853,26 @@
   "upload_button.label": "Thêm media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Tập tin tải lên vượt quá giới hạn cho phép.",
   "upload_error.poll": "Không cho phép đính kèm tập tin.",
+  "upload_form.audio_description": "Mô tả cho người mất thính giác",
+  "upload_form.description": "Mô tả cho người khiếm thị",
   "upload_form.drag_and_drop.instructions": "Để chọn tập tin đính kèm, hãy nhấn phím cách hoặc phím Enter. Trong khi kéo, sử dụng các phím mũi tên để di chuyển tập tin đính kèm theo bất kỳ hướng nào. Nhấn phím cách hoặc phím Enter một lần nữa để thả tập tin đính kèm vào vị trí mới hoặc nhấn phím thoát để hủy.",
   "upload_form.drag_and_drop.on_drag_cancel": "Kéo thả đã bị hủy bỏ. Tập tin đính kèm {item} bị bỏ qua.",
   "upload_form.drag_and_drop.on_drag_end": "Tập tin đính kèm {item} bị bỏ qua.",
   "upload_form.drag_and_drop.on_drag_over": "Tập tin đính kèm {item} đã bị dời.",
   "upload_form.drag_and_drop.on_drag_start": "Đã chọn tập tin đính kèm {item}.",
   "upload_form.edit": "Biên tập",
+  "upload_form.thumbnail": "Đổi ảnh thumbnail",
+  "upload_form.video_description": "Mô tả cho người mất thị lực hoặc không thể nghe",
+  "upload_modal.analyzing_picture": "Phân tích hình ảnh",
+  "upload_modal.apply": "Áp dụng",
+  "upload_modal.applying": "Đang áp dụng…",
+  "upload_modal.choose_image": "Chọn ảnh",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Trích văn bản từ trong ảnh",
+  "upload_modal.edit_media": "Biên tập",
+  "upload_modal.hint": "Nhấp hoặc kéo vòng tròn trên bản xem trước để chọn phần hiển thị trên hình thu nhỏ.",
+  "upload_modal.preparing_ocr": "Đang nhận dạng ký tự…",
+  "upload_modal.preview_label": "Xem trước ({ratio})",
   "upload_progress.label": "Đang tải lên...",
   "upload_progress.processing": "Đang tải lên…",
   "username.taken": "Tên người dùng đã được sử dụng. Hãy thử tên khác",
@@ -915,9 +885,5 @@
   "video.mute": "Tắt tiếng",
   "video.pause": "Tạm dừng",
   "video.play": "Phát",
-  "video.skip_backward": "Chuyển lùi lại",
-  "video.skip_forward": "Chuyển tới trước",
-  "video.unmute": "Bật tiếng",
-  "video.volume_down": "Giảm âm lượng",
-  "video.volume_up": "Tăng âm lượng"
+  "video.unmute": "Mở tiếng"
 }
diff --git a/app/javascript/mastodon/locales/zgh.json b/app/javascript/mastodon/locales/zgh.json
index 334365acbe..2fe63fe83c 100644
--- a/app/javascript/mastodon/locales/zgh.json
+++ b/app/javascript/mastodon/locales/zgh.json
@@ -56,6 +56,8 @@
   "confirmations.unfollow.message": "ⵉⵙ ⵏⵉⵜ ⵜⵅⵙⴷ ⴰⴷ ⵜⴽⴽⵙⴷ ⴰⴹⴼⴼⵓⵕ ⵉ {name}?",
   "conversation.delete": "ⴽⴽⵙ ⴰⵎⵙⴰⵡⴰⵍ",
   "conversation.with": "ⴰⴽⴷ {names}",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "emoji_button.flags": "ⵉⵛⵏⵢⴰⵍⵏ",
   "emoji_button.food": "ⵓⵜⵛⵉ & ⵜⵉⵙⵙⵉ",
@@ -105,9 +107,16 @@
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
   "keyboard_shortcuts.up": "to move up in the list",
   "lightbox.close": "ⵔⴳⵍ",
+  "lists.account.add": "ⵔⵏⵓ ⵖⵔ ⵜⵍⴳⴰⵎⵜ",
+  "lists.account.remove": "ⴽⴽⵙ ⵙⴳ ⵜⵍⴳⴰⵎⵜ",
   "lists.delete": "ⴽⴽⵙ ⵜⴰⵍⴳⴰⵎⵜ",
   "lists.edit": "ⵙⵏⴼⵍ ⵜⴰⵍⴳⴰⵎⵜ",
+  "lists.edit.submit": "ⵙⵏⴼⵍ ⴰⵣⵡⵍ",
+  "lists.new.create": "ⵙⴽⵔ ⵜⴰⵍⴳⴰⵎⵜ",
+  "lists.new.title_placeholder": "ⴰⵣⵡⵍ ⵏ ⵜⵍⴳⴰⵎⵜ ⵜⴰⵎⴰⵢⵏⵓⵜ",
   "lists.replies_policy.none": "ⴰⵡⴷ ⵢⴰⵏ",
+  "lists.replies_policy.title": "ⵙⴽⵏ ⵜⵉⵔⴰⵔⵉⵏ ⵉ:",
+  "lists.subheading": "ⵜⵉⵍⴳⴰⵎⵉⵏ ⵏⵏⴽ",
   "load_pending": "{count, plural, one {# ⵓⴼⵔⴷⵉⵙ ⴰⵎⴰⵢⵏⵓ} other {# ⵉⴼⵔⴷⴰⵙ ⵉⵎⴰⵢⵏⵓⵜⵏ}}",
   "navigation_bar.compose": "Compose new toot",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -123,6 +132,19 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.filter.all": "ⴰⴽⴽⵯ",
   "notifications.group": "{count} ⵜⵏⵖⵎⵉⵙⵉⵏ",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
   "poll.closed": "ⵉⵜⵜⵓⵔⴳⵍ",
   "poll.total_people": "{count, plural, one {# ⵓⴼⴳⴰⵏ} other {# ⵉⴼⴳⴰⵏⵏ}}",
   "poll.total_votes": "{count, plural, one {# ⵓⵙⵜⵜⴰⵢ} other {# ⵉⵙⵜⵜⴰⵢⵏ}}",
@@ -130,6 +152,7 @@
   "poll_button.remove_poll": "ⵙⵙⵉⵜⵢ ⵉⴷⵣ",
   "privacy.change": "Adjust status privacy",
   "privacy.public.short": "ⵜⴰⴳⴷⵓⴷⴰⵏⵜ",
+  "regeneration_indicator.label": "ⴰⵣⴷⴰⵎ…",
   "relative_time.days": "{number}ⴰⵙ",
   "relative_time.hours": "{number}ⵙⵔⴳ",
   "relative_time.just_now": "ⴷⵖⵉ",
@@ -169,7 +192,11 @@
   "time_remaining.days": "{number, plural, one {# ⵡⴰⵙⵙ} other {# ⵡⵓⵙⵙⴰⵏ}} ⵉⵇⵇⵉⵎⵏ",
   "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
   "upload_button.label": "ⵔⵏⵓ ⵜⴰⵡⵍⴰⴼⵜ, ⴰⴼⵉⴷⵢⵓ ⵏⵖ ⴰⴼⴰⵢⵍⵓ ⵙ ⵉⵎⵙⵍⵉ",
+  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.description": "Describe for the visually impaired",
   "upload_form.edit": "ⵙⵏⴼⵍ",
+  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
+  "upload_modal.choose_image": "ⴷⵖⵔ ⵜⴰⵡⵍⴰⴼⵜ",
   "upload_progress.label": "Uploading…",
   "video.close": "ⵔⴳⵍ ⴰⴼⵉⴷⵢⵓ",
   "video.download": "ⴰⴳⵎ ⴰⴼⴰⵢⵍⵓ",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index fc3f914e6a..914eb30e91 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "修改个人资料",
   "account.enable_notifications": "当 @{name} 发布嘟文时通知我",
   "account.endorse": "在个人资料中推荐此用户",
-  "account.featured": "精选",
-  "account.featured.hashtags": "话题",
-  "account.featured.posts": "嘟文",
   "account.featured_tags.last_status_at": "上次发言于 {date}",
   "account.featured_tags.last_status_never": "暂无嘟文",
+  "account.featured_tags.title": "{name} 的精选标签",
   "account.follow": "关注",
   "account.follow_back": "回关",
   "account.followers": "关注者",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, other {{counter} 条嘟文}}",
   "account.unblock": "取消屏蔽 @{name}",
   "account.unblock_domain": "取消屏蔽 {domain} 域名",
-  "account.unblock_domain_short": "取消屏蔽",
   "account.unblock_short": "取消屏蔽",
   "account.unendorse": "不在个人资料中推荐此用户",
   "account.unfollow": "取消关注",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "发生了意外错误。",
   "alert.unexpected.title": "哎呀!",
   "alt_text_badge.title": "替代文本",
-  "alt_text_modal.add_alt_text": "添加替代文本",
-  "alt_text_modal.add_text_from_image": "从图像中添加文本",
-  "alt_text_modal.cancel": "取消",
-  "alt_text_modal.change_thumbnail": "更改缩略图",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "请为听力障碍人士描述此内容…",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "请为视力障碍人士描述此内容…",
-  "alt_text_modal.done": "完成",
   "announcement.announcement": "公告",
-  "annual_report.summary.archetype.booster": "潮流捕手",
-  "annual_report.summary.archetype.lurker": "吃瓜群众",
-  "annual_report.summary.archetype.oracle": "未卜先知",
-  "annual_report.summary.archetype.pollster": "民调专家",
-  "annual_report.summary.archetype.replier": "社交蝴蝶",
-  "annual_report.summary.followers.followers": "关注者",
-  "annual_report.summary.followers.total": "共 {count} 人",
-  "annual_report.summary.here_it_is": "您的 {year} 年度回顾在此:",
-  "annual_report.summary.highlighted_post.by_favourites": "最受欢迎的嘟文",
-  "annual_report.summary.highlighted_post.by_reblogs": "传播最广的嘟文",
-  "annual_report.summary.highlighted_post.by_replies": "评论最多的嘟文",
-  "annual_report.summary.highlighted_post.possessive": "{name} 的",
-  "annual_report.summary.most_used_app.most_used_app": "最常用的应用",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "最常用的话题",
-  "annual_report.summary.most_used_hashtag.none": "无",
-  "annual_report.summary.new_posts.new_posts": "新嘟文",
-  "annual_report.summary.percentile.text": "<topLabel>这使你跻身 {domain} 用户的前</topLabel><percentage></percentage><bottomLabel></bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": " ",
-  "annual_report.summary.thanks": "感谢您成为 Mastodon 的一员!",
   "attachments_list.unprocessed": "(未处理)",
   "audio.hide": "隐藏音频",
   "block_modal.remote_users_caveat": "我们将要求站点 {domain} 尊重你的决定。然而,我们无法保证对方一定遵从,因为某些站点可能会以不同的方案处理屏蔽操作。公开嘟文仍然可能对未登录用户可见。",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "找不到请求的页面。你确定地址栏中的网址输入正确吗?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "关闭",
-  "bundle_modal_error.message": "载入此页面时发生了错误。",
+  "bundle_modal_error.message": "载入这个组件时发生了错误。",
   "bundle_modal_error.retry": "重试",
   "closed_registrations.other_server_instructions": "基于 Mastodon 去中心化的特性,你可以其他服务器上创建账号,并继续与此服务器互动。",
   "closed_registrations_modal.description": "你目前无法在 {domain} 上创建账号,但请注意,使用 Mastodon 并非需要专门在 {domain} 上注册账号。",
@@ -150,16 +121,13 @@
   "column.blocks": "屏蔽的用户",
   "column.bookmarks": "书签",
   "column.community": "本站时间线",
-  "column.create_list": "创建列表",
   "column.direct": "私下提及",
   "column.directory": "浏览用户资料",
   "column.domain_blocks": "已屏蔽的域名",
-  "column.edit_list": "编辑列表",
   "column.favourites": "喜欢",
   "column.firehose": "实时动态",
   "column.follow_requests": "关注请求",
   "column.home": "主页",
-  "column.list_members": "管理列表成员",
   "column.lists": "列表",
   "column.mutes": "已隐藏的用户",
   "column.notifications": "通知",
@@ -172,7 +140,6 @@
   "column_header.pin": "置顶",
   "column_header.show_settings": "显示设置",
   "column_header.unpin": "取消置顶",
-  "column_search.cancel": "取消",
   "column_subheading.settings": "设置",
   "community.column_settings.local_only": "仅限本站",
   "community.column_settings.media_only": "仅媒体",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "编辑",
   "confirmations.edit.message": "编辑此消息将会覆盖当前正在撰写的信息。仍要继续吗?",
   "confirmations.edit.title": "确定要重写嘟文?",
-  "confirmations.follow_to_list.confirm": "关注并添加到列表",
-  "confirmations.follow_to_list.message": "你需要先关注 {name},才能将其添加到列表。",
-  "confirmations.follow_to_list.title": "确定要关注此用户?",
   "confirmations.logout.confirm": "退出登录",
   "confirmations.logout.message": "确定要退出登录吗?",
   "confirmations.logout.title": "确定要退出登录?",
-  "confirmations.missing_alt_text.confirm": "添加替代文本",
-  "confirmations.missing_alt_text.message": "你的帖子包含没有替代文本的媒体。添加描述有助于使更多用户理解你的内容。",
-  "confirmations.missing_alt_text.secondary": "就这样发布",
-  "confirmations.missing_alt_text.title": "添加替代文本?",
   "confirmations.mute.confirm": "隐藏",
   "confirmations.redraft.confirm": "删除并重新编辑",
   "confirmations.redraft.message": "确定删除这条嘟文并重写吗?所有相关的喜欢和转嘟都将丢失,嘟文的回复也会失去关联。",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "你的账号 {disabledAccount} 目前已被禁用。",
   "dismissable_banner.community_timeline": "这些是来自 {domain} 用户的最新公开嘟文。",
   "dismissable_banner.dismiss": "忽略",
-  "dismissable_banner.explore_links": "以下是今天联邦宇宙中的热门资讯。新发布的资讯与被更多用户分享的资讯排名更高。",
-  "dismissable_banner.explore_statuses": "以下是目前在联邦宇宙中引起关注的嘟文。嘟文被转嘟和喜欢的次数越多,排名越高。",
-  "dismissable_banner.explore_tags": "以下是目前在联邦宇宙中引起关注的话题。话题使用人数越多,排名越高。",
-  "dismissable_banner.public_timeline": "以下是联邦宇宙中 {domain} 上的用户关注的人发布的最新公开嘟文。",
+  "dismissable_banner.explore_links": "这些新闻故事正被本站和分布式网络上其他站点的用户谈论。",
+  "dismissable_banner.explore_statuses": "这些是目前在社交网络上引起关注的嘟文。嘟文的喜欢和转嘟次数越多,排名越高。",
+  "dismissable_banner.explore_tags": "这些标签正在本站和分布式网络上其他站点的用户中引起关注。",
+  "dismissable_banner.public_timeline": "这些是在 {domain} 上关注的人们最新发布的公开嘟文。",
   "domain_block_modal.block": "屏蔽服务器",
   "domain_block_modal.block_account_instead": "改为屏蔽 @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "该站点的用户可以与你之前的嘟文交互。",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "搜索结果",
   "emoji_button.symbols": "符号",
   "emoji_button.travel": "旅行与地点",
-  "empty_column.account_featured": "这个列表为空",
   "empty_column.account_hides_collections": "该用户选择不公开此信息",
   "empty_column.account_suspended": "账号已被停用",
   "empty_column.account_timeline": "这里没有嘟文!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "该话题下暂无内容。",
   "empty_column.home": "你的主页时间线还没有内容!快去关注更多人吧。",
   "empty_column.list": "列表中还没有任何内容。当列表成员发布新嘟文时,它们将出现在这里。",
+  "empty_column.lists": "你还没有创建过列表。你创建的列表会在这里显示。",
   "empty_column.mutes": "你没有隐藏任何用户。",
   "empty_column.notification_requests": "一扫而空!这里没有任何未读通知。当收到新的通知时,将根据你的设置显示在这里。",
   "empty_column.notifications": "你还没有收到过任何通知,快和其他用户互动吧。",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "请尝试禁用它们并刷新页面。如果没有帮助,你仍可以尝试使用其他浏览器或原生应用来使用 Mastodon。",
   "errors.unexpected_crash.copy_stacktrace": "复制堆栈跟踪信息到剪贴板",
   "errors.unexpected_crash.report_issue": "报告问题",
+  "explore.search_results": "搜索结果",
   "explore.suggested_follows": "用户",
   "explore.title": "探索",
   "explore.trending_links": "新闻",
@@ -373,14 +334,13 @@
   "footer.about": "关于",
   "footer.directory": "用户列表",
   "footer.get_app": "获取应用",
+  "footer.invite": "邀请",
   "footer.keyboard_shortcuts": "快捷键",
   "footer.privacy_policy": "隐私政策",
   "footer.source_code": "查看源代码",
   "footer.status": "状态",
-  "footer.terms_of_service": "服务条款",
   "generic.saved": "已保存",
   "getting_started.heading": "开始使用",
-  "hashtag.admin_moderation": "打开 #{name} 的管理界面",
   "hashtag.column_header.tag_mode.all": "以及 {additional}",
   "hashtag.column_header.tag_mode.any": "或是 {additional}",
   "hashtag.column_header.tag_mode.none": "而不用 {additional}",
@@ -394,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, other {{counter} 条嘟文}}",
   "hashtag.counter_by_uses_today": "今日 {count, plural, other {{counter} 条嘟文}}",
   "hashtag.follow": "关注话题",
-  "hashtag.mute": "停止提醒 #{hashtag}",
   "hashtag.unfollow": "取消关注话题",
   "hashtags.and_other": "… 和另外 {count, plural, other {# 个话题}}",
   "hints.profiles.followers_may_be_missing": "该账号的关注者列表可能没有完全显示。",
@@ -423,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "是否忽略未关注你的人的通知?",
   "ignore_notifications_modal.not_following_title": "是否忽略你未关注的人的通知?",
   "ignore_notifications_modal.private_mentions_title": "是否忽略不请自来的私下提及?",
-  "info_button.label": "帮助",
-  "info_button.what_is_alt_text": "<h1>什么是替代文本?</h1> <p>替代文本为视力障碍者、低带宽连接用户或需要额外背景信息的用户提供图像描述。</p> <p>通过编写清晰、简洁、客观的替代文本,可以提升所有人的可访问性和理解力。</p> <ul> <li>捕捉重要元素</li> <li>总结图像中的文本</li> <li>使用常规句子结构</li> <li>避免冗余信息</li> <li>关注复杂视觉内容(如图表或地图)中的趋势和关键信息</li> </ul>",
-  "interaction_modal.action.favourite": "你需要切换到自己的账号,再发送喜欢。",
-  "interaction_modal.action.follow": "你需要切换到自己的账号,再进行关注。",
-  "interaction_modal.action.reblog": "你需要切换到自己的账号,再进行转嘟。",
-  "interaction_modal.action.reply": "你需要切换到自己的账号,再发送回复。",
-  "interaction_modal.action.vote": "你需要切换到自己的账号,再发送投票。",
-  "interaction_modal.go": "跳转",
-  "interaction_modal.no_account_yet": "还没有账号?",
+  "interaction_modal.description.favourite": "只需一个 Mastodon 账号,即可喜欢这条嘟文,向作者展示你欣赏的态度,并将其保存以供日后查看。",
+  "interaction_modal.description.follow": "只需一个 Mastodon 账号,即可关注 {name} 并在自己的主页接收对方的新嘟文。",
+  "interaction_modal.description.reblog": "只需一个 Mastodon 账号,即可转发此嘟文,向你的关注者分享它。",
+  "interaction_modal.description.reply": "只需一个 Mastodon 账号,即可回复此嘟文。",
+  "interaction_modal.login.action": "转到主页",
+  "interaction_modal.login.prompt": "你所入驻的站点域名,如:mastodon.social",
+  "interaction_modal.no_account_yet": "还没加入 Mastodon?",
   "interaction_modal.on_another_server": "在另一服务器",
   "interaction_modal.on_this_server": "在此服务器",
+  "interaction_modal.sign_in": "你尚未登录此服务器,你的账号是在哪里注册的?",
+  "interaction_modal.sign_in_hint": "提示:这是你注册的网站,如果你不记得了,请在邮箱的收件箱中查找欢迎邮件。你还可以输入完整的用户名!(例如 @Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "喜欢 {name} 的嘟文",
   "interaction_modal.title.follow": "关注 {name}",
   "interaction_modal.title.reblog": "转发 {name} 的嘟文",
   "interaction_modal.title.reply": "回复 {name} 的嘟文",
-  "interaction_modal.title.vote": "参与 {name} 的投票",
-  "interaction_modal.username_prompt": "例如: {example}",
   "intervals.full.days": "{number} 天",
   "intervals.full.hours": "{number} 小时",
   "intervals.full.minutes": "{number} 分钟",
@@ -475,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "显示或隐藏被折叠的正文",
   "keyboard_shortcuts.toggle_sensitivity": "显示/隐藏媒体",
   "keyboard_shortcuts.toot": "发送新嘟文",
-  "keyboard_shortcuts.translate": "翻译嘟文",
   "keyboard_shortcuts.unfocus": "取消输入/搜索",
   "keyboard_shortcuts.up": "在列表中让光标上移",
   "lightbox.close": "关闭",
@@ -488,32 +444,20 @@
   "link_preview.author": "由 {name}",
   "link_preview.more_from_author": "查看 {name} 的更多内容",
   "link_preview.shares": "{count, plural, other {{counter} 条嘟文}}",
-  "lists.add_member": "添加",
-  "lists.add_to_list": "添加到列表",
-  "lists.add_to_lists": "把 {name} 添加到列表",
-  "lists.create": "创建",
-  "lists.create_a_list_to_organize": "新建一个列表,整理你的主页动态",
-  "lists.create_list": "创建列表",
+  "lists.account.add": "添加到列表",
+  "lists.account.remove": "从列表中移除",
   "lists.delete": "删除列表",
-  "lists.done": "完成",
   "lists.edit": "编辑列表",
-  "lists.exclusive": "在主页动态中隐藏列表成员",
-  "lists.exclusive_hint": "列表成员的嘟文将不会在你的主页动态中显示,以免重复阅读。",
-  "lists.find_users_to_add": "查找要添加的用户",
-  "lists.list_members": "列表成员",
-  "lists.list_members_count": "{count, plural, other {# 人}}",
-  "lists.list_name": "列表名称",
-  "lists.new_list_name": "新列表名称",
-  "lists.no_lists_yet": "尚无列表。",
-  "lists.no_members_yet": "尚无成员。",
-  "lists.no_results_found": "未找到结果。",
-  "lists.remove_member": "移除",
+  "lists.edit.submit": "更改标题",
+  "lists.exclusive": "在主页中隐藏这些嘟文",
+  "lists.new.create": "新建列表",
+  "lists.new.title_placeholder": "新列表的标题",
   "lists.replies_policy.followed": "所有我关注的用户",
   "lists.replies_policy.list": "列表成员",
   "lists.replies_policy.none": "不显示",
-  "lists.save": "保存",
-  "lists.search": "搜索",
-  "lists.show_replies_to": "列表成员回复的显示范围",
+  "lists.replies_policy.title": "回复显示范围:",
+  "lists.search": "搜索你关注的人",
+  "lists.subheading": "你的列表",
   "load_pending": "{count} 项",
   "loading_indicator.label": "加载中…",
   "media_gallery.hide": "隐藏",
@@ -562,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} 举报了 {target}",
   "notification.admin.sign_up": "{name} 注册了",
   "notification.admin.sign_up.name_and_others": "{name} 和 {count, plural, other {另外 # 人}}注册了",
-  "notification.annual_report.message": "你的 {year} #Wrapstodon 年度回顾来啦!快来看看这一年你在 Mastodon 上的精彩瞬间!",
-  "notification.annual_report.view": "查看 #Wrapstodon 年度回顾",
   "notification.favourite": "{name} 喜欢了你的嘟文",
   "notification.favourite.name_and_others_with_link": "{name} 和 <a>{count, plural, other {另外 # 人}}</a> 喜欢了你的嘟文",
-  "notification.favourite_pm": "{name} 喜欢了你的私下提及",
-  "notification.favourite_pm.name_and_others_with_link": "{name} 和<a>{count, plural, other {另外 # 人}}</a>喜欢了你的私下提及",
   "notification.follow": "{name} 关注了你",
   "notification.follow.name_and_others": "{name} 和 <a>{count, plural, other {另外 # 人}}</a> 关注了你",
   "notification.follow_request": "{name} 向你发送了关注请求",
@@ -672,21 +612,44 @@
   "notifications_permission_banner.enable": "启用桌面通知",
   "notifications_permission_banner.how_to_control": "启用桌面通知以在 Mastodon 未打开时接收通知。你可以通过交互通过上面的 {icon} 按钮来精细控制可以发送桌面通知的交互类型。",
   "notifications_permission_banner.title": "精彩不容错过",
-  "onboarding.follows.back": "返回",
-  "onboarding.follows.done": "完成",
+  "onboarding.action.back": "带我返回",
+  "onboarding.actions.back": "带我返回",
+  "onboarding.actions.go_to_explore": "看看有什么新鲜事",
+  "onboarding.actions.go_to_home": "转到主页动态",
+  "onboarding.compose.template": "你好 #Mastodon!",
   "onboarding.follows.empty": "很抱歉,现在无法显示任何结果。你可以尝试使用搜索或浏览探索页面来查找要关注的人,或稍后再试。",
-  "onboarding.follows.search": "搜索",
-  "onboarding.follows.title": "关注用户,玩转 Mastodon",
+  "onboarding.follows.lead": "你管理你自己的家庭饲料。你关注的人越多,它将越活跃和有趣。 这些配置文件可能是一个很好的起点——你可以随时取消关注它们!",
+  "onboarding.follows.title": "定制你的主页动态",
   "onboarding.profile.discoverable": "让我的账号可被他人发现",
   "onboarding.profile.discoverable_hint": "当你在 Mastodon 上启用发现功能时,你的嘟文可能会出现在搜索结果与热门中,你的账号可能会被推荐给与你兴趣相似的人。",
   "onboarding.profile.display_name": "昵称",
   "onboarding.profile.display_name_hint": "你的全名或昵称…",
+  "onboarding.profile.lead": "你可以稍后在设置中完成此操作,设置中有更多的自定义选项。",
   "onboarding.profile.note": "简介",
   "onboarding.profile.note_hint": "你可以提及 @其他人 或使用 #话题…",
   "onboarding.profile.save_and_continue": "保存并继续",
   "onboarding.profile.title": "设置个人资料",
   "onboarding.profile.upload_avatar": "上传头像",
   "onboarding.profile.upload_header": "上传账号封面图",
+  "onboarding.share.lead": "让人们知道他们如何在Mastodon找到你!",
+  "onboarding.share.message": "我是来自 #Mastodon 的 {username}!请在 {url} 关注我。",
+  "onboarding.share.next_steps": "可能的下一步:",
+  "onboarding.share.title": "分享你的个人资料",
+  "onboarding.start.lead": "你的新 Mastodon 账号已准备好。下面是如何最大限度地利用它:",
+  "onboarding.start.skip": "想要在前面跳过吗?",
+  "onboarding.start.title": "你已经成功了!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "定制你的主页动态",
+  "onboarding.steps.publish_status.body": "向世界问声好吧。",
+  "onboarding.steps.publish_status.title": "发布你的第一篇嘟文",
+  "onboarding.steps.setup_profile.body": "完善个人资料,提升你的互动体验。",
+  "onboarding.steps.setup_profile.title": "自定义你的账号",
+  "onboarding.steps.share_profile.body": "让你的朋友知道怎样在 Mastodon 找到你",
+  "onboarding.steps.share_profile.title": "分享你的个人资料",
+  "onboarding.tips.2fa": "<strong>你知道吗?</strong>你可以在账号设置中配置双因素认证来保护账号安全。可以使用你选择的任何 TOTP 应用,无需电话号码!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>你知道吗?</strong> Mastodon 是去中心化的,所以你看到的一些账号实际上是在别的服务器上。不过你仍然可以和他们无缝交流!他们的服务器地址就在他们用户名的后半部分!",
+  "onboarding.tips.migration": "<strong>你知道吗?</strong>如果你将来觉得 {domain} 不再符合您的需求,你可以在保留现有关注者的情况下迁移至其他 Mastodon 服务器。你甚至可以部署自己的服务器!",
+  "onboarding.tips.verification": "<strong>你知道吗?</strong> 你可以在自己的网站上添加指向你 Mastodon 个人资料的链接,并在你的 Mastodon 个人资料中添加对应的网站链接,以此来验证您的账号。此验证方式无需任何费用或文件。",
   "password_confirmation.exceeds_maxlength": "密码确认超过最大密码长度",
   "password_confirmation.mismatching": "确认密码与密码不一致。",
   "picture_in_picture.restore": "恢复",
@@ -702,7 +665,7 @@
   "poll_button.remove_poll": "移除投票",
   "privacy.change": "设置嘟文的可见范围",
   "privacy.direct.long": "嘟文中提到的每个人",
-  "privacy.direct.short": "私下提及",
+  "privacy.direct.short": "特定的人",
   "privacy.private.long": "仅限你的关注者",
   "privacy.private.short": "关注者",
   "privacy.public.long": "所有 Mastodon 内外的人",
@@ -714,8 +677,8 @@
   "privacy_policy.title": "隐私政策",
   "recommended": "推荐",
   "refresh": "刷新",
-  "regeneration_indicator.please_stand_by": "请稍候。",
-  "regeneration_indicator.preparing_your_home_feed": "正在为你准备主页时间线...",
+  "regeneration_indicator.label": "加载中…",
+  "regeneration_indicator.sublabel": "你的主页动态正在准备中!",
   "relative_time.days": "{number} 天前",
   "relative_time.full.days": "{number, plural, other {# 天}}前",
   "relative_time.full.hours": "{number, plural, other {# 小时}}前",
@@ -799,11 +762,10 @@
   "search_results.accounts": "用户",
   "search_results.all": "全部",
   "search_results.hashtags": "话题",
-  "search_results.no_results": "未找到结果。",
-  "search_results.no_search_yet": "不妨试下搜索嘟文、账号或话题。",
+  "search_results.nothing_found": "无法找到符合这些搜索词的任何内容",
   "search_results.see_all": "查看全部",
   "search_results.statuses": "嘟文",
-  "search_results.title": "搜索 “{q}”",
+  "search_results.title": "搜索 {q}",
   "server_banner.about_active_users": "过去 30 天内使用此服务器的人(月活跃用户)",
   "server_banner.active_users": "活跃用户",
   "server_banner.administered_by": "本站管理员:",
@@ -855,7 +817,6 @@
   "status.reblogs.empty": "没有人转嘟过此条嘟文。如果有人转嘟了,就会显示在这里。",
   "status.redraft": "删除并重新编辑",
   "status.remove_bookmark": "移除书签",
-  "status.remove_favourite": "从喜欢列表中移除",
   "status.replied_in_thread": "回复嘟文串",
   "status.replied_to": "回复 {name}",
   "status.reply": "回复",
@@ -877,9 +838,6 @@
   "subscribed_languages.target": "更改 {target} 的订阅语言",
   "tabs_bar.home": "主页",
   "tabs_bar.notifications": "通知",
-  "terms_of_service.effective_as_of": "自 {date} 起生效",
-  "terms_of_service.title": "服务条款",
-  "terms_of_service.upcoming_changes_on": "将于 {date} 进行变更",
   "time_remaining.days": "剩余 {number, plural, other {# 天}}",
   "time_remaining.hours": "剩余 {number, plural, other {# 小时}}",
   "time_remaining.minutes": "剩余 {number, plural, other {# 分钟}}",
@@ -895,12 +853,26 @@
   "upload_button.label": "上传图片、视频或音频",
   "upload_error.limit": "文件大小超过限制。",
   "upload_error.poll": "投票中不允许上传文件。",
+  "upload_form.audio_description": "为听障人士添加文字描述",
+  "upload_form.description": "为视觉障碍人士添加文字说明",
   "upload_form.drag_and_drop.instructions": "要选中某个媒体附件,请按空格键或回车键。在拖拽时,使用方向键将媒体附件移动到任何给定方向。再次按空格键或回车键可将媒体附件放置在新位置,或按 Esc 键取消。",
   "upload_form.drag_and_drop.on_drag_cancel": "拖拽已终止。媒体附件 {item} 已被丢弃。",
   "upload_form.drag_and_drop.on_drag_end": "媒体附件 {item} 已被丢弃。",
   "upload_form.drag_and_drop.on_drag_over": "媒体附件 {item} 已被移动。",
   "upload_form.drag_and_drop.on_drag_start": "已选中媒体附件 {item}。",
   "upload_form.edit": "编辑",
+  "upload_form.thumbnail": "更改缩略图",
+  "upload_form.video_description": "为听障人士与视障人士添加文字描述",
+  "upload_modal.analyzing_picture": "正在分析图片…",
+  "upload_modal.apply": "应用",
+  "upload_modal.applying": "正在应用…",
+  "upload_modal.choose_image": "选择图片",
+  "upload_modal.description_placeholder": "在这里写下你的描述",
+  "upload_modal.detect_text": "从图片中检测文本",
+  "upload_modal.edit_media": "编辑媒体",
+  "upload_modal.hint": "在预览图上点击或拖动圆圈,以选择缩略图的焦点。",
+  "upload_modal.preparing_ocr": "正在准备文字识别…",
+  "upload_modal.preview_label": "预览 ({ratio})",
   "upload_progress.label": "上传中…",
   "upload_progress.processing": "正在处理…",
   "username.taken": "此用户名已被占用。请换用其它用户名",
@@ -910,12 +882,8 @@
   "video.expand": "展开视频",
   "video.fullscreen": "全屏",
   "video.hide": "隐藏视频",
-  "video.mute": "停止提醒",
+  "video.mute": "静音",
   "video.pause": "暂停",
   "video.play": "播放",
-  "video.skip_backward": "后退",
-  "video.skip_forward": "前进",
-  "video.unmute": "恢复提醒",
-  "video.volume_down": "音量减小",
-  "video.volume_up": "提高音量"
+  "video.unmute": "取消静音"
 }
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index 493d06b672..eb75c76d39 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -29,6 +29,7 @@
   "account.endorse": "在個人檔案中推薦對方",
   "account.featured_tags.last_status_at": "上次帖文於 {date}",
   "account.featured_tags.last_status_never": "暫無文章",
+  "account.featured_tags.title": "{name} 的精選標籤",
   "account.follow": "關注",
   "account.follow_back": "追蹤對方",
   "account.followers": "追蹤者",
@@ -83,8 +84,6 @@
   "alert.unexpected.message": "發生意外錯誤。",
   "alert.unexpected.title": "失敗!",
   "alt_text_badge.title": "替代文字",
-  "alt_text_modal.cancel": "取消",
-  "alt_text_modal.done": "完成",
   "announcement.announcement": "公告",
   "attachments_list.unprocessed": "(未處理)",
   "audio.hide": "隱藏音訊",
@@ -107,6 +106,7 @@
   "bundle_column_error.routing.body": "找不到請求的頁面。您確定網址欄中的 URL 正確嗎?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "關閉",
+  "bundle_modal_error.message": "加載本組件出錯。",
   "bundle_modal_error.retry": "重試",
   "closed_registrations.other_server_instructions": "基於Mastodon去中心化的特性,你可以在其他伺服器上創建賬戶並與本站互動。",
   "closed_registrations_modal.description": "目前無法在 {domain} 建立新帳號,但您並不一定需要擁有 {domain} 的帳號亦能使用 Mastodon 。",
@@ -136,7 +136,6 @@
   "column_header.pin": "置頂",
   "column_header.show_settings": "顯示設定",
   "column_header.unpin": "取消置頂",
-  "column_search.cancel": "取消",
   "column_subheading.settings": "設定",
   "community.column_settings.local_only": "只顯示本站",
   "community.column_settings.media_only": "只顯示多媒體",
@@ -155,7 +154,7 @@
   "compose_form.poll.duration": "投票期限",
   "compose_form.poll.multiple": "多選",
   "compose_form.poll.option_placeholder": "選項 {number}",
-  "compose_form.poll.single": "單一選擇",
+  "compose_form.poll.single": "選擇一個",
   "compose_form.poll.switch_to_multiple": "變更投票為允許多個選項",
   "compose_form.poll.switch_to_single": "變更投票為限定單一選項",
   "compose_form.poll.type": "風格",
@@ -206,6 +205,10 @@
   "disabled_account_banner.text": "您的帳號 {disabledAccount} 目前已停用。",
   "dismissable_banner.community_timeline": "這些是 {domain} 上用戶的最新公開帖文。",
   "dismissable_banner.dismiss": "關閉",
+  "dismissable_banner.explore_links": "這些新聞內容正在被本站以及去中心化網路上其他伺服器的人們熱烈討論。",
+  "dismissable_banner.explore_statuses": "這些是今天在社交網絡上受到關注的帖文。新的帖文如果有較多轉推和最愛會排得更高。",
+  "dismissable_banner.explore_tags": "這些主題標籤正在被本站以及去中心化網路上的人們熱烈討論。",
+  "dismissable_banner.public_timeline": "這些是 {domain} 使用者追蹤的社交網絡上最新的公開帖文。",
   "domain_block_modal.block": "封鎖伺服器",
   "domain_block_modal.block_account_instead": "封鎖 @{name} 即可",
   "domain_block_modal.they_can_interact_with_old_posts": "此伺服器的人們可與你的舊帖文互動。",
@@ -260,6 +263,7 @@
   "empty_column.hashtag": "這個標籤暫時未有內容。",
   "empty_column.home": "你還沒有關注任何使用者。快看看{public},向其他使用者搭訕吧。",
   "empty_column.list": "這個列表暫時未有內容。",
+  "empty_column.lists": "你還沒有建立任何名單。這裡將會顯示你所建立的名單。",
   "empty_column.mutes": "你尚未靜音任何使用者。",
   "empty_column.notification_requests": "沒有新通知了!當有新通知時,會根據設定顯示在這裏。",
   "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。",
@@ -270,6 +274,7 @@
   "error.unexpected_crash.next_steps_addons": "請嘗試停止使用這些附加元件然後重新載入頁面。如果問題沒有解決,你仍然可以使用不同的瀏覽器或 Mastodon 應用程式來檢視。",
   "errors.unexpected_crash.copy_stacktrace": "複製 stacktrace 到剪貼簿",
   "errors.unexpected_crash.report_issue": "舉報問題",
+  "explore.search_results": "搜尋結果",
   "explore.suggested_follows": "使用者",
   "explore.title": "探索",
   "explore.trending_links": "最新消息",
@@ -317,6 +322,7 @@
   "footer.about": "關於",
   "footer.directory": "個人檔案目錄",
   "footer.get_app": "取得應用程式",
+  "footer.invite": "邀請他人",
   "footer.keyboard_shortcuts": "鍵盤快速鍵",
   "footer.privacy_policy": "私隱政策",
   "footer.source_code": "查看原始碼",
@@ -346,9 +352,17 @@
   "home.pending_critical_update.title": "有重要的安全更新!",
   "home.show_announcements": "顯示公告",
   "ignore_notifications_modal.ignore": "忽略推播通知",
-  "info_button.label": "幫助",
+  "interaction_modal.description.favourite": "有了 Mastodon 的帳號,你便可以把這篇帖文加入最愛,讓作者知道你欣賞他的作品,並可以稍後再閱讀。",
+  "interaction_modal.description.follow": "在 Mastodon 上有個帳號的話,您可以追蹤 {name} 以於首頁時間軸接收他們的帖文。",
+  "interaction_modal.description.reblog": "在 Mastodon 上有個帳號的話,您可以向自己的追縱者們轉發此帖文。",
+  "interaction_modal.description.reply": "在 Mastodon 上擁有帳號的話,您可以回覆此帖文。",
+  "interaction_modal.login.action": "帶我到主頁",
+  "interaction_modal.login.prompt": "主伺服器的網域,例如 mastodon.social",
+  "interaction_modal.no_account_yet": "還未加入 Mastodon?",
   "interaction_modal.on_another_server": "於不同伺服器",
   "interaction_modal.on_this_server": "於此伺服器",
+  "interaction_modal.sign_in": "你尚未登入此伺服器。你的帳號是在哪裡託管的?",
+  "interaction_modal.sign_in_hint": "提示:那是你註冊的網站。如果忘記了,你可以在收件箱搜尋歡迎郵件。你也可以輸入你的完整使用者名稱!(例如:@Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "把 {name} 的帖文加入最愛",
   "interaction_modal.title.follow": "追蹤 {name}",
   "interaction_modal.title.reblog": "轉發 {name} 的帖文",
@@ -396,12 +410,20 @@
   "limited_account_hint.action": "一律顯示個人檔案",
   "limited_account_hint.title": "此個人檔案已被 {domain} 的管理員隱藏。",
   "link_preview.author": "由 {name} 提供",
+  "lists.account.add": "新增到列表",
+  "lists.account.remove": "從列表刪除",
   "lists.delete": "刪除列表",
-  "lists.done": "完成",
   "lists.edit": "編輯列表",
+  "lists.edit.submit": "變更標題",
+  "lists.exclusive": "從主頁隱藏這些帖文",
+  "lists.new.create": "新增列表",
+  "lists.new.title_placeholder": "新列表標題",
   "lists.replies_policy.followed": "任何已關注的用戶",
   "lists.replies_policy.list": "列表中的用戶",
   "lists.replies_policy.none": "無人",
+  "lists.replies_policy.title": "顯示回應文章︰",
+  "lists.search": "從你關注的人搜索",
+  "lists.subheading": "列表",
   "load_pending": "{count, plural, other {# 個新項目}}",
   "loading_indicator.label": "載入中…",
   "media_gallery.hide": "隱藏",
@@ -465,7 +487,6 @@
   "notification.update": "{name} 編輯了帖文",
   "notification_requests.accept": "接受",
   "notification_requests.dismiss": "忽略",
-  "notification_requests.exit_selection": "完成",
   "notification_requests.notifications_from": "來自 {name} 的通知",
   "notification_requests.title": "已過濾之通知",
   "notifications.clear": "清空通知紀錄",
@@ -512,18 +533,44 @@
   "notifications_permission_banner.enable": "啟用桌面通知",
   "notifications_permission_banner.how_to_control": "只要啟用桌面通知,便可在 Mastodon 網站沒有打開時收到通知。在已經啟用桌面通知的時候,你可以透過上面的 {icon} 按鈕準確控制哪些類型的互動會產生桌面通知。",
   "notifications_permission_banner.title": "不放過任何事情",
-  "onboarding.follows.done": "完成",
+  "onboarding.action.back": "返回",
+  "onboarding.actions.back": "返回",
+  "onboarding.actions.go_to_explore": "See what's trending",
+  "onboarding.actions.go_to_home": "Go to your home feed",
+  "onboarding.compose.template": "哈囉 #Mastodon!",
   "onboarding.follows.empty": "很遺憾,現在無法顯示任何結果。你可以嘗試搜尋或瀏覽探索頁面來找使用者來追蹤,或者稍後再試。",
+  "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
+  "onboarding.follows.title": "Popular on Mastodon",
   "onboarding.profile.discoverable": "將個人檔案設為可被搜尋",
   "onboarding.profile.discoverable_hint": "當你在 Mastodon 上選擇可被搜尋時,你的帖文可能會出現在搜尋結果和熱門,你的個人檔案也可能被推薦給與你興趣相似的人。",
   "onboarding.profile.display_name": "顯示名稱",
   "onboarding.profile.display_name_hint": "你的全名或暱稱…",
+  "onboarding.profile.lead": "你可以隨時在設定中完成此動作,那裏有更多自訂選項。",
   "onboarding.profile.note": "簡介",
   "onboarding.profile.note_hint": "你可以 @提及他人 或使用 #標籤…",
   "onboarding.profile.save_and_continue": "儲存並繼續",
   "onboarding.profile.title": "個人檔案設定",
   "onboarding.profile.upload_avatar": "上載個人檔案頭像",
   "onboarding.profile.upload_header": "上載個人檔案橫幅圖片",
+  "onboarding.share.lead": "讓大家知道如何在 Mastodon 上找到你吧!",
+  "onboarding.share.message": "我在 #Mastodon 的使用者名稱是 {username}!快來追蹤我吧 {url}",
+  "onboarding.share.next_steps": "接下來你可以:",
+  "onboarding.share.title": "分享你的個人檔案",
+  "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
+  "onboarding.start.skip": "Want to skip right ahead?",
+  "onboarding.start.title": "你做到了!",
+  "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
+  "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
+  "onboarding.steps.publish_status.body": "Say hello to the world.",
+  "onboarding.steps.publish_status.title": "發佈你的第一篇帖文",
+  "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
+  "onboarding.steps.setup_profile.title": "Customize your profile",
+  "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
+  "onboarding.steps.share_profile.title": "Share your profile",
+  "onboarding.tips.2fa": "<strong>你知道嗎?</strong>你可以在帳號設定中設定雙重認證來保護帳號。它可以配合你選擇的 TOTP 應用程式使用,毋須提供電話號碼!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>你知道嗎?</strong>由於 Mastodon 是去中心化的,所以你可能會遇到託管在其他伺服器的個人檔案,但你仍然可以無障礙地與他們互動!他們的伺服器名稱,就是使用者名稱的後半部份!",
+  "onboarding.tips.migration": "<strong>你知道嗎?</strong>如果你日後覺得 {domain} 伺服器不是最佳的選擇,你可以轉移到另一個 Mastodon 伺服器而不會失去你的追蹤者。你甚至可以託管你自己的伺服器!",
+  "onboarding.tips.verification": "<strong>你知道嗎?</strong>你可以透過在自己的網站放一條連結到你的 Mastodon 個人檔案,並在你的個人檔案加入該網站來驗證帳號,而毋須支付費用或提供文件!",
   "password_confirmation.exceeds_maxlength": "確認密碼欄超出了密碼長度限制",
   "password_confirmation.mismatching": "密碼確認不符",
   "picture_in_picture.restore": "還原影片播放器",
@@ -539,6 +586,7 @@
   "poll_button.remove_poll": "移除投票",
   "privacy.change": "調整私隱設定",
   "privacy.direct.long": "帖文提及的人",
+  "privacy.direct.short": "特定的人",
   "privacy.private.long": "只有你的追蹤者",
   "privacy.private.short": "追蹤者",
   "privacy.public.long": "Mastodon 內外的任何人",
@@ -550,6 +598,8 @@
   "privacy_policy.title": "私隱政策",
   "recommended": "推薦",
   "refresh": "重新整理",
+  "regeneration_indicator.label": "載入中……",
+  "regeneration_indicator.sublabel": "你的主頁時間軸正在準備中!",
   "relative_time.days": "{number}日前",
   "relative_time.full.days": "{number, plural, one {# 天} other {# 天}}前",
   "relative_time.full.hours": "{number, plural, one {# 小時} other {# 小時}}前",
@@ -629,8 +679,10 @@
   "search_results.accounts": "個人檔案",
   "search_results.all": "全部",
   "search_results.hashtags": "標籤",
+  "search_results.nothing_found": "找不到與搜尋字詞相關的內容",
   "search_results.see_all": "顯示全部",
   "search_results.statuses": "文章",
+  "search_results.title": "搜尋 {q}",
   "server_banner.about_active_users": "在最近 30 天內內使用此伺服器的人 (月活躍用戶)",
   "server_banner.active_users": "活躍用戶",
   "server_banner.administered_by": "管理者:",
@@ -712,7 +764,21 @@
   "upload_button.label": "加入圖片、影片或音訊檔",
   "upload_error.limit": "已達到檔案上傳限制。",
   "upload_error.poll": "不允許在投票上傳檔案。",
+  "upload_form.audio_description": "簡單描述內容給聽障人士",
+  "upload_form.description": "為視覺障礙人士添加文字說明",
   "upload_form.edit": "編輯",
+  "upload_form.thumbnail": "更改預覽圖",
+  "upload_form.video_description": "簡單描述給聽障或視障人士",
+  "upload_modal.analyzing_picture": "正在分析圖片…",
+  "upload_modal.apply": "套用",
+  "upload_modal.applying": "套用中…",
+  "upload_modal.choose_image": "選擇圖片",
+  "upload_modal.description_placeholder": "一隻敏捷的狐狸,輕巧地跳過那隻懶洋洋的狗",
+  "upload_modal.detect_text": "從圖片偵測文字",
+  "upload_modal.edit_media": "編輯媒體",
+  "upload_modal.hint": "點擊或拖曳圓圈以選擇預覽縮圖。",
+  "upload_modal.preparing_ocr": "準備辨識圖片文字…",
+  "upload_modal.preview_label": "預覽 ({ratio})",
   "upload_progress.label": "上載中……",
   "upload_progress.processing": "處理中...",
   "username.taken": "這個使用者名稱已被使用。試試另一個",
@@ -722,6 +788,8 @@
   "video.expand": "展開影片",
   "video.fullscreen": "全螢幕",
   "video.hide": "隱藏影片",
+  "video.mute": "靜音",
   "video.pause": "暫停",
-  "video.play": "播放"
+  "video.play": "播放",
+  "video.unmute": "解除靜音"
 }
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index 0d37e7f6c5..f77437cf18 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -27,11 +27,9 @@
   "account.edit_profile": "編輯個人檔案",
   "account.enable_notifications": "當 @{name} 嘟文時通知我",
   "account.endorse": "於個人檔案推薦對方",
-  "account.featured": "精選內容",
-  "account.featured.hashtags": "主題標籤",
-  "account.featured.posts": "嘟文",
   "account.featured_tags.last_status_at": "上次嘟文於 {date}",
   "account.featured_tags.last_status_never": "沒有嘟文",
+  "account.featured_tags.title": "{name} 的推薦主題標籤",
   "account.follow": "跟隨",
   "account.follow_back": "跟隨回去",
   "account.followers": "跟隨者",
@@ -67,7 +65,6 @@
   "account.statuses_counter": "{count, plural, other {{count} 則嘟文}}",
   "account.unblock": "解除封鎖 @{name}",
   "account.unblock_domain": "解除封鎖網域 {domain}",
-  "account.unblock_domain_short": "解除封鎖",
   "account.unblock_short": "解除封鎖",
   "account.unendorse": "取消於個人檔案推薦對方",
   "account.unfollow": "取消跟隨",
@@ -89,33 +86,7 @@
   "alert.unexpected.message": "發生非預期的錯誤。",
   "alert.unexpected.title": "哎呀!",
   "alt_text_badge.title": "ALT 說明文字",
-  "alt_text_modal.add_alt_text": "新增說明文字",
-  "alt_text_modal.add_text_from_image": "自圖片新增說明文字",
-  "alt_text_modal.cancel": "取消",
-  "alt_text_modal.change_thumbnail": "變更預覽圖",
-  "alt_text_modal.describe_for_people_with_hearing_impairments": "替聽覺障礙人士描述...",
-  "alt_text_modal.describe_for_people_with_visual_impairments": "替視覺障礙人士描述...",
-  "alt_text_modal.done": "完成",
   "announcement.announcement": "公告",
-  "annual_report.summary.archetype.booster": "酷炫獵人",
-  "annual_report.summary.archetype.lurker": "潛水高手",
-  "annual_report.summary.archetype.oracle": "先知",
-  "annual_report.summary.archetype.pollster": "民調專家",
-  "annual_report.summary.archetype.replier": "社交菁英",
-  "annual_report.summary.followers.followers": "跟隨者",
-  "annual_report.summary.followers.total": "總共 {count}",
-  "annual_report.summary.here_it_is": "以下是您的 {year} 年度回顧:",
-  "annual_report.summary.highlighted_post.by_favourites": "最多被加到最愛的嘟文",
-  "annual_report.summary.highlighted_post.by_reblogs": "最多轉嘟的嘟文",
-  "annual_report.summary.highlighted_post.by_replies": "最多回覆的嘟文",
-  "annual_report.summary.highlighted_post.possessive": "{name} 的",
-  "annual_report.summary.most_used_app.most_used_app": "最常使用的應用程式",
-  "annual_report.summary.most_used_hashtag.most_used_hashtag": "最常使用的主題標籤",
-  "annual_report.summary.most_used_hashtag.none": "無最常用之主題標籤",
-  "annual_report.summary.new_posts.new_posts": "新嘟文",
-  "annual_report.summary.percentile.text": "<topLabel>這讓您成為前</topLabel><percentage></percentage><bottomLabel>{domain} 的使用者。</bottomLabel>",
-  "annual_report.summary.percentile.we_wont_tell_bernie": "我們不會告訴 Bernie。",
-  "annual_report.summary.thanks": "感謝您成為 Mastodon 的一份子!",
   "attachments_list.unprocessed": "(未經處理)",
   "audio.hide": "隱藏音訊",
   "block_modal.remote_users_caveat": "我們會要求 {domain} 伺服器尊重您的決定。然而,我們無法保證所有伺服器皆會遵守,某些伺服器可能以不同方式處理封鎖。未登入之使用者仍可能看見您的公開嘟文。",
@@ -139,7 +110,7 @@
   "bundle_column_error.routing.body": "找不到請求的頁面。您確定網址列中的 URL 是正確的嗎?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "關閉",
-  "bundle_modal_error.message": "載入此畫面時發生錯誤。",
+  "bundle_modal_error.message": "載入此元件時發生錯誤。",
   "bundle_modal_error.retry": "重試",
   "closed_registrations.other_server_instructions": "因為 Mastodon 是去中心化的,所以您也能於其他伺服器上建立帳號,並仍然與這個伺服器互動。",
   "closed_registrations_modal.description": "目前無法於 {domain} 建立新帳號,但也請別忘了,您並不一定需要有 {domain} 伺服器的帳號,也能使用 Mastodon。",
@@ -150,16 +121,13 @@
   "column.blocks": "已封鎖的使用者",
   "column.bookmarks": "書籤",
   "column.community": "本站時間軸",
-  "column.create_list": "建立列表",
   "column.direct": "私訊",
   "column.directory": "瀏覽個人檔案",
   "column.domain_blocks": "已封鎖網域",
-  "column.edit_list": "編輯列表",
   "column.favourites": "最愛",
   "column.firehose": "即時內容",
   "column.follow_requests": "跟隨請求",
   "column.home": "首頁",
-  "column.list_members": "管理列表成員",
   "column.lists": "列表",
   "column.mutes": "已靜音的使用者",
   "column.notifications": "推播通知",
@@ -172,7 +140,6 @@
   "column_header.pin": "釘選",
   "column_header.show_settings": "顯示設定",
   "column_header.unpin": "取消釘選",
-  "column_search.cancel": "取消",
   "column_subheading.settings": "設定",
   "community.column_settings.local_only": "只顯示本站",
   "community.column_settings.media_only": "只顯示媒體",
@@ -191,7 +158,7 @@
   "compose_form.poll.duration": "投票期限",
   "compose_form.poll.multiple": "多選",
   "compose_form.poll.option_placeholder": "選項 {number}",
-  "compose_form.poll.single": "單一選擇",
+  "compose_form.poll.single": "選擇一個",
   "compose_form.poll.switch_to_multiple": "變更投票為允許多個選項",
   "compose_form.poll.switch_to_single": "變更投票為允許單一選項",
   "compose_form.poll.type": "投票方式",
@@ -215,16 +182,9 @@
   "confirmations.edit.confirm": "編輯",
   "confirmations.edit.message": "編輯嘟文將覆蓋掉您目前正在撰寫之嘟文內容。您是否仍要繼續?",
   "confirmations.edit.title": "是否覆寫該嘟文?",
-  "confirmations.follow_to_list.confirm": "跟隨並加入至列表",
-  "confirmations.follow_to_list.message": "您必須先跟隨 {name} 以將其加入至列表。",
-  "confirmations.follow_to_list.title": "是否跟隨該使用者?",
   "confirmations.logout.confirm": "登出",
   "confirmations.logout.message": "您確定要登出嗎?",
   "confirmations.logout.title": "您確定要登出嗎?",
-  "confirmations.missing_alt_text.confirm": "新增說明文字",
-  "confirmations.missing_alt_text.message": "您的嘟文中的多媒體內容未附上說明文字。添加說明文字描述能幫助更多人存取您的內容。",
-  "confirmations.missing_alt_text.secondary": "仍要發嘟",
-  "confirmations.missing_alt_text.title": "是否新增說明文字?",
   "confirmations.mute.confirm": "靜音",
   "confirmations.redraft.confirm": "刪除並重新編輯",
   "confirmations.redraft.message": "您確定要刪除這則嘟文並重新編輯嗎?您將失去這則嘟文之轉嘟及最愛,且對此嘟文之回覆會變成獨立的嘟文。",
@@ -253,10 +213,10 @@
   "disabled_account_banner.text": "您的帳號 {disabledAccount} 目前已停用。",
   "dismissable_banner.community_timeline": "這些是託管於 {domain} 上帳號之最新公開嘟文。",
   "dismissable_banner.dismiss": "關閉",
-  "dismissable_banner.explore_links": "這些新聞故事正在被聯邦宇宙上的人們熱烈討論著。越多不同人所嘟出的新聞排名更高。",
-  "dismissable_banner.explore_statuses": "這些來自於聯邦宇宙發出的嘟文正在被此伺服器上的人們熱烈討論著。越多不同人轉嘟及最愛排名更高。",
-  "dismissable_banner.explore_tags": "這些主題標籤正在被聯邦宇宙上的人們熱烈討論著。越多不同人所嘟出的主題標籤排名更高。",
-  "dismissable_banner.public_timeline": "這些是聯邦宇宙上來自 {domain} 使用者們跟隨中帳號所發表之最新公開嘟文。",
+  "dismissable_banner.explore_links": "這些新聞故事正在被此伺服器以及去中心化網路上的人們熱烈討論著。越多不同人所嘟出的新聞排名更高。",
+  "dismissable_banner.explore_statuses": "這些於此伺服器以及去中心化網路中其他伺服器發出的嘟文正在被此伺服器上的人們熱烈討論著。越多不同人轉嘟及最愛排名更高。",
+  "dismissable_banner.explore_tags": "這些主題標籤正在被此伺服器以及去中心化網路上的人們熱烈討論著。越多不同人所嘟出的主題標籤排名更高。",
+  "dismissable_banner.public_timeline": "這些是來自 {domain} 使用者們跟隨中帳號所發表之最新公開嘟文。",
   "domain_block_modal.block": "封鎖伺服器",
   "domain_block_modal.block_account_instead": "改為封鎖 @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "來自此伺服器之使用者能與您以往的嘟文互動。",
@@ -296,7 +256,6 @@
   "emoji_button.search_results": "搜尋結果",
   "emoji_button.symbols": "符號",
   "emoji_button.travel": "旅遊與地點",
-  "empty_column.account_featured": "此列表為空",
   "empty_column.account_hides_collections": "這位使用者選擇不提供此資訊",
   "empty_column.account_suspended": "帳號已被停權",
   "empty_column.account_timeline": "這裡還沒有嘟文!",
@@ -314,6 +273,7 @@
   "empty_column.hashtag": "這個主題標籤下什麼也沒有。",
   "empty_column.home": "您的首頁時間軸是空的!跟隨更多人來將它填滿吧!",
   "empty_column.list": "這份列表下什麼也沒有。當此列表的成員嘟出新的嘟文時,它們將顯示於此。",
+  "empty_column.lists": "您還沒有新增任何列表。當您新增列表時,它將於此顯示。",
   "empty_column.mutes": "您尚未靜音任何使用者。",
   "empty_column.notification_requests": "清空啦!已經沒有任何推播通知。當您收到新推播通知時,它們將依照您的設定於此顯示。",
   "empty_column.notifications": "您還沒有收到任何推播通知,當您與別人開始互動時,它將於此顯示。",
@@ -324,6 +284,7 @@
   "error.unexpected_crash.next_steps_addons": "請嘗試關閉它們然後重新整理頁面。如果狀況沒有改善,您可以使用不同的瀏覽器或應用程式來檢視來使用 Mastodon。",
   "errors.unexpected_crash.copy_stacktrace": "複製 stacktrace 到剪貼簿",
   "errors.unexpected_crash.report_issue": "回報問題",
+  "explore.search_results": "搜尋結果",
   "explore.suggested_follows": "使用者",
   "explore.title": "探索",
   "explore.trending_links": "最新消息",
@@ -373,16 +334,13 @@
   "footer.about": "關於",
   "footer.directory": "個人檔案目錄",
   "footer.get_app": "取得應用程式",
+  "footer.invite": "邀請朋友加入",
   "footer.keyboard_shortcuts": "鍵盤快速鍵",
   "footer.privacy_policy": "隱私權政策",
   "footer.source_code": "檢視原始碼",
   "footer.status": "狀態",
-  "footer.terms_of_service": "服務條款",
   "generic.saved": "已儲存",
   "getting_started.heading": "開始使用",
-  "hashtag.admin_moderation": "開啟 #{name} 的管理介面",
-  "hashtag.browse": "瀏覽於 #{hashtag} 之嘟文",
-  "hashtag.browse_from_account": "瀏覽來自 @{name} 於 #{hashtag} 之嘟文",
   "hashtag.column_header.tag_mode.all": "以及 {additional}",
   "hashtag.column_header.tag_mode.any": "或是 {additional}",
   "hashtag.column_header.tag_mode.none": "而無需 {additional}",
@@ -396,7 +354,6 @@
   "hashtag.counter_by_uses": "{count, plural, one {{counter} 則} other {{counter} 則}}嘟文",
   "hashtag.counter_by_uses_today": "本日有 {count, plural, one {{counter} 則} other {{counter} 則}}嘟文",
   "hashtag.follow": "跟隨主題標籤",
-  "hashtag.mute": "靜音 #{hashtag}",
   "hashtag.unfollow": "取消跟隨主題標籤",
   "hashtags.and_other": "…及其他 {count, plural, other {# 個}}",
   "hints.profiles.followers_may_be_missing": "此個人檔案之跟隨者或有缺失。",
@@ -425,23 +382,21 @@
   "ignore_notifications_modal.not_followers_title": "忽略來自未跟隨您帳號之推播通知?",
   "ignore_notifications_modal.not_following_title": "忽略來自您未跟隨帳號之推播通知?",
   "ignore_notifications_modal.private_mentions_title": "忽略來自不請自來私訊之推播通知?",
-  "info_button.label": "幫助",
-  "info_button.what_is_alt_text": "<h1>何謂 ALT 說明文字?</h1> <p>ALT 說明文字為視覺障礙者、低網路頻寬或尋求額外上下文語境的人們提供圖片描述。</p> <p>您可以透過撰寫清晰、簡潔及客觀的說明文字以替所有人改善無障礙特性與協助理解。</p> <ul> <li>掌握幾個重要元素</li> <li>替圖片提供文字摘要</li> <li>使用常規行文結構</li> <li>避免冗贅資訊</li> <li>聚焦於趨勢與複雜視覺中之關鍵(如圖表或地圖)</li> </ul>",
-  "interaction_modal.action.favourite": "若欲繼續,您必須自您的帳號加入最愛。",
-  "interaction_modal.action.follow": "若欲繼續,您必須自您的帳號跟隨。",
-  "interaction_modal.action.reblog": "若欲繼續,您必須自您的帳號轉嘟。",
-  "interaction_modal.action.reply": "若欲繼續,您必須自您的帳號回覆。",
-  "interaction_modal.action.vote": "若欲繼續,您必須自您的帳號投票。",
-  "interaction_modal.go": "Go!",
-  "interaction_modal.no_account_yet": "仍尚未有帳號嗎?",
+  "interaction_modal.description.favourite": "若於 Mastodon 上有個帳號,您可以將此嘟文加入最愛使作者知道您欣賞它且將它儲存下來。",
+  "interaction_modal.description.follow": "若於 Mastodon 上有個帳號,您可以跟隨 {name} 以於首頁時間軸接收他們的嘟文。",
+  "interaction_modal.description.reblog": "若於 Mastodon 上有個帳號,您可以轉嘟此嘟文以向您的跟隨者們分享。",
+  "interaction_modal.description.reply": "若於 Mastodon 上有個帳號,您可以回覆此嘟文。",
+  "interaction_modal.login.action": "返回首頁",
+  "interaction_modal.login.prompt": "您帳號所屬伺服器之網域,例如:mastodon.social",
+  "interaction_modal.no_account_yet": "還沒有 Mastodon 帳號嗎?",
   "interaction_modal.on_another_server": "於不同伺服器",
   "interaction_modal.on_this_server": "於此伺服器",
+  "interaction_modal.sign_in": "您未登入於此伺服器。您的帳號是於何方託管呢?",
+  "interaction_modal.sign_in_hint": "提示:這是您所註冊之網站。若您無法回想起,請檢查您電子郵件收件夾內之歡迎信。您也能輸入完整帳號名稱!(如:@Mastodon@mastodon.social)",
   "interaction_modal.title.favourite": "將 {name} 之嘟文加入最愛",
   "interaction_modal.title.follow": "跟隨 {name}",
   "interaction_modal.title.reblog": "轉嘟 {name} 的嘟文",
   "interaction_modal.title.reply": "回覆 {name} 的嘟文",
-  "interaction_modal.title.vote": "參與 {name} 之投票",
-  "interaction_modal.username_prompt": "例如 {example}",
   "intervals.full.days": "{number, plural, other {# 天}}",
   "intervals.full.hours": "{number, plural, other {# 小時}}",
   "intervals.full.minutes": "{number, plural, other {# 分鐘}}",
@@ -477,7 +432,6 @@
   "keyboard_shortcuts.toggle_hidden": "顯示或隱藏於內容警告之後的嘟文",
   "keyboard_shortcuts.toggle_sensitivity": "顯示或隱藏媒體",
   "keyboard_shortcuts.toot": "發個新嘟文",
-  "keyboard_shortcuts.translate": "翻譯嘟文",
   "keyboard_shortcuts.unfocus": "跳離文字撰寫區塊或搜尋框",
   "keyboard_shortcuts.up": "向上移動",
   "lightbox.close": "關閉",
@@ -490,32 +444,20 @@
   "link_preview.author": "來自 {name}",
   "link_preview.more_from_author": "來自 {name} 之更多內容",
   "link_preview.shares": "{count, plural, other {{count} 則嘟文}}",
-  "lists.add_member": "新增",
-  "lists.add_to_list": "新增至列表",
-  "lists.add_to_lists": "新增 {name} 至列表",
-  "lists.create": "建立",
-  "lists.create_a_list_to_organize": "建立新列表以整理您的首頁時間軸",
-  "lists.create_list": "建立列表",
+  "lists.account.add": "新增至列表",
+  "lists.account.remove": "自列表中移除",
   "lists.delete": "刪除列表",
-  "lists.done": "完成",
   "lists.edit": "編輯列表",
-  "lists.exclusive": "於首頁隱藏成員",
-  "lists.exclusive_hint": "如果某個帳號於此列表中,將自您的首頁時間軸中隱藏此帳號,以防重複見到他們的嘟文。",
-  "lists.find_users_to_add": "尋找欲新增之使用者",
-  "lists.list_members": "列表成員",
-  "lists.list_members_count": "{count, plural, other {# 個成員}}",
-  "lists.list_name": "列表名稱",
-  "lists.new_list_name": "新列表名稱",
-  "lists.no_lists_yet": "尚無列表。",
-  "lists.no_members_yet": "尚無成員。",
-  "lists.no_results_found": "找不到結果。",
-  "lists.remove_member": "移除",
+  "lists.edit.submit": "變更標題",
+  "lists.exclusive": "於首頁時間軸隱藏這些嘟文",
+  "lists.new.create": "新增列表",
+  "lists.new.title_placeholder": "新列表標題",
   "lists.replies_policy.followed": "任何跟隨的使用者",
   "lists.replies_policy.list": "列表成員",
   "lists.replies_policy.none": "沒有人",
-  "lists.save": "儲存",
-  "lists.search": "搜尋",
-  "lists.show_replies_to": "包含來自列表成員的回覆到",
+  "lists.replies_policy.title": "顯示回覆:",
+  "lists.search": "搜尋您跟隨之使用者",
+  "lists.subheading": "您的列表",
   "load_pending": "{count, plural, other {# 個新項目}}",
   "loading_indicator.label": "正在載入...",
   "media_gallery.hide": "隱藏",
@@ -564,12 +506,8 @@
   "notification.admin.report_statuses_other": "{name} 已檢舉 {target}",
   "notification.admin.sign_up": "{name} 已經註冊",
   "notification.admin.sign_up.name_and_others": "{name} 與{count, plural, other {其他 # 個人}}已註冊",
-  "notification.annual_report.message": "您的 {year} #Wrapstodon 正等著您!揭開您 Mastodon 上的年度精彩時刻與值得回憶的難忘時光!",
-  "notification.annual_report.view": "檢視 #Wrapstodon",
   "notification.favourite": "{name} 已將您的嘟文加入最愛",
   "notification.favourite.name_and_others_with_link": "{name} 與<a>{count, plural, other {其他 # 個人}}</a>已將您的嘟文加入最愛",
-  "notification.favourite_pm": "{name} 將您的私人提及加入最愛",
-  "notification.favourite_pm.name_and_others_with_link": "{name} 與<a>{count, plural, other {其他 # 個人}}</a>已將您的私人提及加入最愛",
   "notification.follow": "{name} 已跟隨您",
   "notification.follow.name_and_others": "{name} 與<a>{count, plural, other {其他 # 個人}}</a>已跟隨您",
   "notification.follow_request": "{name} 要求跟隨您",
@@ -674,21 +612,44 @@
   "notifications_permission_banner.enable": "啟用桌面通知",
   "notifications_permission_banner.how_to_control": "啟用桌面通知以於 Mastodon 沒有開啟的時候接收通知。啟用桌面通知後,您可以透過上面的 {icon} 按鈕準確的控制哪些類型的互動會產生桌面通知。",
   "notifications_permission_banner.title": "不要錯過任何東西!",
-  "onboarding.follows.back": "返回",
-  "onboarding.follows.done": "完成",
+  "onboarding.action.back": "返回",
+  "onboarding.actions.back": "返回",
+  "onboarding.actions.go_to_explore": "看看發生什麼新鮮事",
+  "onboarding.actions.go_to_home": "前往您的首頁時間軸",
+  "onboarding.compose.template": "哈囉 #Mastodon!",
   "onboarding.follows.empty": "很遺憾,目前未能顯示任何結果。您可以嘗試使用搜尋、瀏覽探索頁面以找尋人們跟隨、或稍候再試。",
-  "onboarding.follows.search": "搜尋",
-  "onboarding.follows.title": "開始跟隨一些人吧",
+  "onboarding.follows.lead": "您的首頁時間軸是 Mastodon 的核心體驗。若您跟隨更多人,它將會變得更活躍有趣。這些個人檔案也許是個好起點,您可以隨時取消跟隨他們!",
+  "onboarding.follows.title": "客製化您的首頁時間軸",
   "onboarding.profile.discoverable": "使我的個人檔案可以被找到",
   "onboarding.profile.discoverable_hint": "當您於 Mastodon 上選擇加入可發現性時,您的嘟文可能會顯示於搜尋結果與趨勢中。您的個人檔案可能會被推薦至與您志趣相投的人。",
   "onboarding.profile.display_name": "顯示名稱",
   "onboarding.profile.display_name_hint": "完整名稱或暱稱...",
+  "onboarding.profile.lead": "您隨時可以稍候於設定中完成此操作,將有更多自訂選項可使用。",
   "onboarding.profile.note": "個人簡介",
   "onboarding.profile.note_hint": "您可以 @mention 其他人或者使用 #主題標籤...",
   "onboarding.profile.save_and_continue": "儲存並繼續",
   "onboarding.profile.title": "個人檔案設定",
   "onboarding.profile.upload_avatar": "上傳個人檔案大頭貼",
   "onboarding.profile.upload_header": "上傳個人檔案封面圖片",
+  "onboarding.share.lead": "讓其他人知道他們如何於 Mastodon 上面找到您!",
+  "onboarding.share.message": "我是 #Mastodon 上的 {username}!歡迎於 {url} 跟隨我",
+  "onboarding.share.next_steps": "可能的下一步:",
+  "onboarding.share.title": "分享您的個人檔案",
+  "onboarding.start.lead": "您現在是 Mastodon 的一份子啦!一個獨特的去中心化社群網路平台,您(而不是演算法)能準備您自己的獨特體驗。以下是您能最有效地使用它的小撇步:",
+  "onboarding.start.skip": "想要直接跳過入門協助嗎?",
+  "onboarding.start.title": "噹噹!完成啦!",
+  "onboarding.steps.follow_people.body": "Mastodon 的趣味就是跟隨些有趣的人們!",
+  "onboarding.steps.follow_people.title": "客製化您的首頁時間軸",
+  "onboarding.steps.publish_status.body": "透過文字、照片、影片或投票 {emoji} 向新世界打聲招呼吧。",
+  "onboarding.steps.publish_status.title": "撰寫您第一則嘟文",
+  "onboarding.steps.setup_profile.body": "若您完整填寫個人檔案,其他人比較願意與您互動。",
+  "onboarding.steps.setup_profile.title": "客製化您的個人檔案",
+  "onboarding.steps.share_profile.body": "讓您的朋友們知道如何於 Mastodon 找到您!",
+  "onboarding.steps.share_profile.title": "分享您的 Mastodon 個人檔案",
+  "onboarding.tips.2fa": "<strong>您知道嗎?</strong> 您可以透過於帳號設定中啟用兩階段驗證以加強您的帳號安全。它適用於任何您偏好的 TOTP 應用程式,不需要電話號碼!",
+  "onboarding.tips.accounts_from_other_servers": "<strong>您知道嗎?</strong> 由於 Mastodon 是去中心化的,有些您巧遇過的個人檔案託管於這邊以外的其他伺服器。即便如此,您也能無縫地與他們互動!他們的伺服器位址是他們帳號的後半部分!",
+  "onboarding.tips.migration": "<strong>您知道嗎?</strong> 若您認為 {domain} 已不再對您的未來使用是好的伺服器選擇,您可以遷移至另一個 Mastodon 伺服器而不流失您現有的跟隨者。您甚至可以運行您自己的伺服器!",
+  "onboarding.tips.verification": "<strong>您知道嗎?</strong> 您可以藉由於您自己的網站上設定 Mastodon 個人檔案連結並於個人檔案中連結該網站的方式以驗證您的帳號。不需要任何費用或是文件!",
   "password_confirmation.exceeds_maxlength": "密碼驗證欄超過最長密碼長度限制",
   "password_confirmation.mismatching": "密碼驗證欄與密碼不一致",
   "picture_in_picture.restore": "還原",
@@ -704,7 +665,7 @@
   "poll_button.remove_poll": "移除投票",
   "privacy.change": "調整嘟文隱私狀態",
   "privacy.direct.long": "此嘟文提及之所有人",
-  "privacy.direct.short": "私訊",
+  "privacy.direct.short": "指定使用者",
   "privacy.private.long": "只有跟隨您之使用者能看到",
   "privacy.private.short": "跟隨者",
   "privacy.public.long": "所有人 (無論在 Mastodon 上與否)",
@@ -716,8 +677,8 @@
   "privacy_policy.title": "隱私權政策",
   "recommended": "推薦設定",
   "refresh": "重新整理",
-  "regeneration_indicator.please_stand_by": "請稍候。",
-  "regeneration_indicator.preparing_your_home_feed": "正在準備您的首頁時間軸...",
+  "regeneration_indicator.label": "載入中…",
+  "regeneration_indicator.sublabel": "正在準備您的首頁時間軸!",
   "relative_time.days": "{number} 天",
   "relative_time.full.days": "{number, plural, other {# 天}}前",
   "relative_time.full.hours": "{number, plural, other {# 小時}}前",
@@ -801,11 +762,10 @@
   "search_results.accounts": "個人檔案",
   "search_results.all": "全部",
   "search_results.hashtags": "主題標籤",
-  "search_results.no_results": "沒有結果。",
-  "search_results.no_search_yet": "嘗試搜尋嘟文、個人檔案或主題標籤。",
+  "search_results.nothing_found": "無法找到符合搜尋條件之結果",
   "search_results.see_all": "檢視全部",
   "search_results.statuses": "嘟文",
-  "search_results.title": "搜尋「{q}」",
+  "search_results.title": "搜尋:{q}",
   "server_banner.about_active_users": "最近三十日內使用此伺服器的人(月活躍使用者)",
   "server_banner.active_users": "活躍使用者",
   "server_banner.administered_by": "管理者:",
@@ -857,7 +817,6 @@
   "status.reblogs.empty": "還沒有人轉嘟過這則嘟文。當有人轉嘟時,它將於此顯示。",
   "status.redraft": "刪除並重新編輯",
   "status.remove_bookmark": "自書籤中移除",
-  "status.remove_favourite": "自最愛中移除",
   "status.replied_in_thread": "於討論串中回覆",
   "status.replied_to": "回覆 {name}",
   "status.reply": "回覆",
@@ -879,9 +838,6 @@
   "subscribed_languages.target": "變更 {target} 的訂閱語言",
   "tabs_bar.home": "首頁",
   "tabs_bar.notifications": "通知",
-  "terms_of_service.effective_as_of": "{date} 起生效",
-  "terms_of_service.title": "服務條款",
-  "terms_of_service.upcoming_changes_on": "{date} 起即將發生之異動",
   "time_remaining.days": "剩餘 {number, plural, other {# 天}}",
   "time_remaining.hours": "剩餘{number, plural, other {# 小時}}",
   "time_remaining.minutes": "剩餘{number, plural, other {# 分鐘}}",
@@ -897,12 +853,26 @@
   "upload_button.label": "上傳圖片、影片、或者音訊檔案",
   "upload_error.limit": "已達到檔案上傳限制。",
   "upload_error.poll": "不允許於投票時上傳檔案。",
+  "upload_form.audio_description": "為聽覺障礙人士增加文字說明",
+  "upload_form.description": "為視覺障礙人士增加文字說明",
   "upload_form.drag_and_drop.instructions": "請按空白鍵或 Enter 鍵取多媒體附加檔案。使用方向鍵移動多媒體附加檔案。按下空白鍵或 Enter 鍵於新位置放置多媒體附加檔案,或按下 ESC 鍵取消。",
   "upload_form.drag_and_drop.on_drag_cancel": "移動已取消。多媒體附加檔案 {item} 已被放置。",
   "upload_form.drag_and_drop.on_drag_end": "多媒體附加檔案 {item} 已被放置。",
   "upload_form.drag_and_drop.on_drag_over": "多媒體附加檔案 {item} 已被移動。",
   "upload_form.drag_and_drop.on_drag_start": "多媒體附加檔案 {item} 已被選取。",
   "upload_form.edit": "編輯",
+  "upload_form.thumbnail": "變更預覽圖",
+  "upload_form.video_description": "為聽障或視障人士增加文字說明",
+  "upload_modal.analyzing_picture": "正在分析圖片...",
+  "upload_modal.apply": "套用",
+  "upload_modal.applying": "正在套用...",
+  "upload_modal.choose_image": "選擇圖片",
+  "upload_modal.description_placeholder": "我能吞下玻璃而不傷身體",
+  "upload_modal.detect_text": "自圖片中偵測文字",
+  "upload_modal.edit_media": "編輯媒體",
+  "upload_modal.hint": "於預覽中點擊或拖曳圓圈以選擇將於所有縮圖中顯示的焦點。",
+  "upload_modal.preparing_ocr": "正在準備 OCR...",
+  "upload_modal.preview_label": "預覽 ({ratio})",
   "upload_progress.label": "上傳中...",
   "upload_progress.processing": "處理中...",
   "username.taken": "這個帳號已經被註冊過囉。請嘗試其他帳號",
@@ -915,9 +885,5 @@
   "video.mute": "靜音",
   "video.pause": "暫停",
   "video.play": "播放",
-  "video.skip_backward": "上一部",
-  "video.skip_forward": "下一部",
-  "video.unmute": "取消靜音",
-  "video.volume_down": "降低音量",
-  "video.volume_up": "提高音量"
+  "video.unmute": "解除靜音"
 }
diff --git a/app/javascript/mastodon/models/account.ts b/app/javascript/mastodon/models/account.ts
index 55dbbcbb34..29ae1d9911 100644
--- a/app/javascript/mastodon/models/account.ts
+++ b/app/javascript/mastodon/models/account.ts
@@ -1,5 +1,5 @@
 import type { RecordOf } from 'immutable';
-import { List as ImmutableList, Record as ImmutableRecord } from 'immutable';
+import { List, Record as ImmutableRecord } from 'immutable';
 
 import escapeTextContentForBrowser from 'escape-html';
 
@@ -10,11 +10,12 @@ import type {
   ApiAccountJSON,
   ApiServerFeaturesJSON,
 } from 'mastodon/api_types/accounts';
+import type { ApiCustomEmojiJSON } from 'mastodon/api_types/custom_emoji';
 import emojify from 'mastodon/features/emoji/emoji';
 import { unescapeHTML } from 'mastodon/utils/html';
 
-import { CustomEmojiFactory, makeEmojiMap } from './custom_emoji';
-import type { CustomEmoji, EmojiMap } from './custom_emoji';
+import { CustomEmojiFactory } from './custom_emoji';
+import type { CustomEmoji } from './custom_emoji';
 
 // AccountField
 interface AccountFieldShape extends Required<ApiAccountFieldJSON> {
@@ -78,9 +79,9 @@ export interface AccountShape
   extends Required<
     Omit<ApiAccountJSON, 'emojis' | 'fields' | 'roles' | 'moved'>
   > {
-  emojis: ImmutableList<CustomEmoji>;
-  fields: ImmutableList<AccountField>;
-  roles: ImmutableList<AccountRole>;
+  emojis: List<CustomEmoji>;
+  fields: List<AccountField>;
+  roles: List<AccountRole>;
   display_name_html: string;
   note_emojified: string;
   note_plain: string | null;
@@ -101,8 +102,8 @@ export const accountDefaultValues: AccountShape = {
   display_name: '',
   display_name_html: '',
   server_features: AccountServerFeaturesFactory(),
-  emojis: ImmutableList<CustomEmoji>(),
-  fields: ImmutableList<AccountField>(),
+  emojis: List<CustomEmoji>(),
+  fields: List<AccountField>(),
   group: false,
   header: '',
   header_static: '',
@@ -113,7 +114,7 @@ export const accountDefaultValues: AccountShape = {
   note: '',
   note_emojified: '',
   note_plain: 'string',
-  roles: ImmutableList<AccountRole>(),
+  roles: List<AccountRole>(),
   uri: '',
   url: '',
   username: '',
@@ -135,6 +136,15 @@ export const accountDefaultValues: AccountShape = {
 
 const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);
 
+type EmojiMap = Record<string, ApiCustomEmojiJSON>;
+
+function makeEmojiMap(emojis: ApiCustomEmojiJSON[]) {
+  return emojis.reduce<EmojiMap>((obj, emoji) => {
+    obj[`:${emoji.shortcode}:`] = emoji;
+    return obj;
+  }, {});
+}
+
 function createAccountField(
   jsonField: ApiAccountFieldJSON,
   emojiMap: EmojiMap,
@@ -163,15 +173,11 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
   return AccountFactory({
     ...accountJSON,
     moved: moved?.id,
-    fields: ImmutableList(
+    fields: List(
       serverJSON.fields.map((field) => createAccountField(field, emojiMap)),
     ),
-    emojis: ImmutableList(
-      serverJSON.emojis.map((emoji) => CustomEmojiFactory(emoji)),
-    ),
-    roles: ImmutableList(
-      serverJSON.roles?.map((role) => AccountRoleFactory(role)),
-    ),
+    emojis: List(serverJSON.emojis.map((emoji) => CustomEmojiFactory(emoji))),
+    roles: List(serverJSON.roles?.map((role) => AccountRoleFactory(role))),
     display_name_html: emojify(
       escapeTextContentForBrowser(displayName),
       emojiMap,
diff --git a/app/javascript/mastodon/models/alert.ts b/app/javascript/mastodon/models/alert.ts
deleted file mode 100644
index bc492eff3c..0000000000
--- a/app/javascript/mastodon/models/alert.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import type { MessageDescriptor } from 'react-intl';
-
-export type TranslatableString = string | MessageDescriptor;
-
-export type TranslatableValues = Record<string, string | number | Date>;
-
-export interface Alert {
-  key: number;
-  title?: TranslatableString;
-  message: TranslatableString;
-  action?: TranslatableString;
-  values?: TranslatableValues;
-  onClick?: () => void;
-}
diff --git a/app/javascript/mastodon/models/annual_report.ts b/app/javascript/mastodon/models/annual_report.ts
deleted file mode 100644
index c0a101e6c8..0000000000
--- a/app/javascript/mastodon/models/annual_report.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-export interface Percentiles {
-  followers: number;
-  statuses: number;
-}
-
-export interface NameAndCount {
-  name: string;
-  count: number;
-}
-
-export interface TimeSeriesMonth {
-  month: number;
-  statuses: number;
-  following: number;
-  followers: number;
-}
-
-export interface TopStatuses {
-  by_reblogs: number;
-  by_favourites: number;
-  by_replies: number;
-}
-
-export type Archetype =
-  | 'lurker'
-  | 'booster'
-  | 'pollster'
-  | 'replier'
-  | 'oracle';
-
-interface AnnualReportV1 {
-  most_used_apps: NameAndCount[];
-  percentiles: Percentiles;
-  top_hashtags: NameAndCount[];
-  top_statuses: TopStatuses;
-  time_series: TimeSeriesMonth[];
-  archetype: Archetype;
-}
-
-export interface AnnualReport {
-  year: number;
-  schema_version: number;
-  data: AnnualReportV1;
-}
diff --git a/app/javascript/mastodon/models/antenna.ts b/app/javascript/mastodon/models/antenna.ts
deleted file mode 100644
index c9d6055e9e..0000000000
--- a/app/javascript/mastodon/models/antenna.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { RecordOf } from 'immutable';
-import { Record } from 'immutable';
-
-import type { ApiAntennaJSON } from 'mastodon/api_types/antennas';
-
-type AntennaShape = Required<ApiAntennaJSON>; // no changes from server shape
-export type Antenna = RecordOf<AntennaShape>;
-
-const AntennaFactory = Record<AntennaShape>({
-  id: '',
-  title: '',
-  stl: false,
-  ltl: false,
-  insert_feeds: false,
-  with_media_only: false,
-  ignore_reblog: false,
-  favourite: true,
-  list: null,
-  list_id: undefined,
-});
-
-export function createAntenna(attributes: Partial<AntennaShape>) {
-  return AntennaFactory(attributes);
-}
diff --git a/app/javascript/mastodon/models/bookmark_category.ts b/app/javascript/mastodon/models/bookmark_category.ts
deleted file mode 100644
index 79c38af3af..0000000000
--- a/app/javascript/mastodon/models/bookmark_category.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { RecordOf } from 'immutable';
-import { Record } from 'immutable';
-
-import type { ApiBookmarkCategoryJSON } from 'mastodon/api_types/bookmark_categories';
-
-type BookmarkCategoryShape = Required<ApiBookmarkCategoryJSON>; // no changes from server shape
-export type BookmarkCategory = RecordOf<BookmarkCategoryShape>;
-
-const BookmarkCategoryFactory = Record<BookmarkCategoryShape>({
-  id: '',
-  title: '',
-});
-
-export function createBookmarkCategory(
-  attributes: Partial<BookmarkCategoryShape>,
-) {
-  return BookmarkCategoryFactory(attributes);
-}
diff --git a/app/javascript/mastodon/models/circle.ts b/app/javascript/mastodon/models/circle.ts
deleted file mode 100644
index 60995ce97f..0000000000
--- a/app/javascript/mastodon/models/circle.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { RecordOf } from 'immutable';
-import { Record } from 'immutable';
-
-import type { ApiCircleJSON } from 'mastodon/api_types/circles';
-
-type CircleShape = Required<ApiCircleJSON>; // no changes from server shape
-export type Circle = RecordOf<CircleShape>;
-
-const CircleFactory = Record<CircleShape>({
-  id: '',
-  title: '',
-});
-
-export function createCircle(attributes: Partial<CircleShape>) {
-  return CircleFactory(attributes);
-}
diff --git a/app/javascript/mastodon/models/custom_emoji.ts b/app/javascript/mastodon/models/custom_emoji.ts
index 0af38ffd7a..96ab4bc612 100644
--- a/app/javascript/mastodon/models/custom_emoji.ts
+++ b/app/javascript/mastodon/models/custom_emoji.ts
@@ -1,12 +1,12 @@
-import type { RecordOf, List as ImmutableList } from 'immutable';
-import { Record as ImmutableRecord, isList } from 'immutable';
+import type { RecordOf } from 'immutable';
+import { Record } from 'immutable';
 
 import type { ApiCustomEmojiJSON } from 'mastodon/api_types/custom_emoji';
 
 type CustomEmojiShape = Required<ApiCustomEmojiJSON>; // no changes from server shape
 export type CustomEmoji = RecordOf<CustomEmojiShape>;
 
-export const CustomEmojiFactory = ImmutableRecord<CustomEmojiShape>({
+export const CustomEmojiFactory = Record<CustomEmojiShape>({
   shortcode: '',
   static_url: '',
   url: '',
@@ -18,20 +18,3 @@ export const CustomEmojiFactory = ImmutableRecord<CustomEmojiShape>({
   aliases: [],
   license: '',
 });
-
-export type EmojiMap = Record<string, ApiCustomEmojiJSON>;
-
-export function makeEmojiMap(
-  emojis: ApiCustomEmojiJSON[] | ImmutableList<CustomEmoji>,
-) {
-  if (isList(emojis)) {
-    return emojis.reduce<EmojiMap>((obj, emoji) => {
-      obj[`:${emoji.shortcode}:`] = emoji.toJS() as ApiCustomEmojiJSON;
-      return obj;
-    }, {});
-  } else
-    return emojis.reduce<EmojiMap>((obj, emoji) => {
-      obj[`:${emoji.shortcode}:`] = emoji;
-      return obj;
-    }, {});
-}
diff --git a/app/javascript/mastodon/models/dropdown_menu.ts b/app/javascript/mastodon/models/dropdown_menu.ts
deleted file mode 100644
index ceea9ad4dd..0000000000
--- a/app/javascript/mastodon/models/dropdown_menu.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-interface BaseMenuItem {
-  text: string;
-  dangerous?: boolean;
-}
-
-export interface ActionMenuItem extends BaseMenuItem {
-  action: () => void;
-}
-
-export interface LinkMenuItem extends BaseMenuItem {
-  to: string;
-}
-
-export interface ExternalLinkMenuItem extends BaseMenuItem {
-  href: string;
-  target?: string;
-  method?: 'post' | 'put' | 'delete';
-}
-
-export type MenuItem =
-  | ActionMenuItem
-  | LinkMenuItem
-  | ExternalLinkMenuItem
-  | null;
diff --git a/app/javascript/mastodon/models/list.ts b/app/javascript/mastodon/models/list.ts
deleted file mode 100644
index 077591d2e0..0000000000
--- a/app/javascript/mastodon/models/list.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { RecordOf } from 'immutable';
-import { Record } from 'immutable';
-
-import type { ApiListJSON } from 'mastodon/api_types/lists';
-
-type ListShape = Required<ApiListJSON>; // no changes from server shape
-export type List = RecordOf<ListShape>;
-
-const ListFactory = Record<ListShape>({
-  id: '',
-  title: '',
-  exclusive: false,
-  replies_policy: 'list',
-  notify: false,
-  favourite: true,
-  antennas: [],
-});
-
-export function createList(attributes: Partial<ListShape>) {
-  return ListFactory(attributes);
-}
diff --git a/app/javascript/mastodon/models/notification_group.ts b/app/javascript/mastodon/models/notification_group.ts
index e293d9f27c..62f4b83b79 100644
--- a/app/javascript/mastodon/models/notification_group.ts
+++ b/app/javascript/mastodon/models/notification_group.ts
@@ -1,7 +1,6 @@
 import type {
   ApiAccountRelationshipSeveranceEventJSON,
   ApiAccountWarningJSON,
-  ApiAnnualReportEventJSON,
   BaseNotificationGroupJSON,
   ApiNotificationGroupJSON,
   ApiNotificationJSON,
@@ -93,12 +92,6 @@ export interface NotificationGroupSeveredRelationships
   event: AccountRelationshipSeveranceEvent;
 }
 
-type AnnualReportEvent = ApiAnnualReportEventJSON;
-export interface NotificationGroupAnnualReport
-  extends BaseNotification<'annual_report'> {
-  annualReport: AnnualReportEvent;
-}
-
 interface Report extends Omit<ApiReportJSON, 'target_account'> {
   targetAccountId: string;
 }
@@ -123,8 +116,7 @@ export type NotificationGroup =
   | NotificationGroupModerationWarning
   | NotificationGroupSeveredRelationships
   | NotificationGroupAdminSignUp
-  | NotificationGroupAdminReport
-  | NotificationGroupAnnualReport;
+  | NotificationGroupAdminReport;
 
 function createReportFromJSON(reportJSON: ApiReportJSON): Report {
   const { target_account, ...report } = reportJSON;
@@ -164,12 +156,6 @@ function createEmojiReactionGroupsFromJSON(
   ];
 }
 
-function createAnnualReportEventFromJSON(
-  eventJson: ApiAnnualReportEventJSON,
-): AnnualReportEvent {
-  return eventJson;
-}
-
 export function createNotificationGroupFromJSON(
   groupJson: ApiNotificationGroupJSON,
 ): NotificationGroup {
@@ -232,6 +218,7 @@ export function createNotificationGroupFromJSON(
         event: createAccountRelationshipSeveranceEventFromJSON(group.event),
         sampleAccountIds,
       };
+
     case 'moderation_warning': {
       const { moderation_warning, ...groupWithoutModerationWarning } = group;
       return {
@@ -241,15 +228,7 @@ export function createNotificationGroupFromJSON(
         sampleAccountIds,
       };
     }
-    case 'annual_report': {
-      const { annual_report, ...groupWithoutAnnualReport } = group;
-      return {
-        ...groupWithoutAnnualReport,
-        partial: false,
-        annualReport: createAnnualReportEventFromJSON(annual_report),
-        sampleAccountIds,
-      };
-    }
+
     default:
       return {
         sampleAccountIds,
diff --git a/app/javascript/mastodon/models/poll.ts b/app/javascript/mastodon/models/poll.ts
deleted file mode 100644
index 6f5655680d..0000000000
--- a/app/javascript/mastodon/models/poll.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import escapeTextContentForBrowser from 'escape-html';
-
-import type { ApiPollJSON, ApiPollOptionJSON } from 'mastodon/api_types/polls';
-import emojify from 'mastodon/features/emoji/emoji';
-
-import { CustomEmojiFactory, makeEmojiMap } from './custom_emoji';
-import type { CustomEmoji, EmojiMap } from './custom_emoji';
-
-interface PollOptionTranslation {
-  title: string;
-  titleHtml: string;
-}
-
-export interface PollOption extends ApiPollOptionJSON {
-  voted: boolean;
-  titleHtml: string;
-  translation: PollOptionTranslation | null;
-}
-
-export function createPollOptionTranslationFromServerJSON(
-  translation: { title: string },
-  emojiMap: EmojiMap,
-) {
-  return {
-    ...translation,
-    titleHtml: emojify(
-      escapeTextContentForBrowser(translation.title),
-      emojiMap,
-    ),
-  } as PollOptionTranslation;
-}
-
-export interface Poll
-  extends Omit<ApiPollJSON, 'emojis' | 'options' | 'own_votes'> {
-  emojis: CustomEmoji[];
-  options: PollOption[];
-  own_votes?: number[];
-}
-
-const pollDefaultValues = {
-  expired: false,
-  multiple: false,
-  voters_count: 0,
-  votes_count: 0,
-  voted: false,
-  own_votes: [],
-};
-
-export function createPollFromServerJSON(
-  serverJSON: ApiPollJSON,
-  previousPoll?: Poll,
-) {
-  const emojiMap = makeEmojiMap(serverJSON.emojis);
-
-  return {
-    ...pollDefaultValues,
-    ...serverJSON,
-    emojis: serverJSON.emojis.map((emoji) => CustomEmojiFactory(emoji)),
-    options: serverJSON.options.map((optionJSON, index) => {
-      const option = {
-        ...optionJSON,
-        voted: serverJSON.own_votes?.includes(index) || false,
-        titleHtml: emojify(
-          escapeTextContentForBrowser(optionJSON.title),
-          emojiMap,
-        ),
-      } as PollOption;
-
-      const prevOption = previousPoll?.options[index];
-      if (prevOption?.translation && prevOption.title === option.title) {
-        const { translation } = prevOption;
-
-        option.translation = createPollOptionTranslationFromServerJSON(
-          translation,
-          emojiMap,
-        );
-      }
-
-      return option;
-    }),
-  } as Poll;
-}
diff --git a/app/javascript/mastodon/models/search.ts b/app/javascript/mastodon/models/search.ts
deleted file mode 100644
index 75a65bf99c..0000000000
--- a/app/javascript/mastodon/models/search.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { ApiSearchResultsJSON } from 'mastodon/api_types/search';
-import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
-
-export type SearchType = 'account' | 'hashtag' | 'accounts' | 'statuses';
-
-export interface RecentSearch {
-  q: string;
-  type?: SearchType;
-}
-
-export interface SearchResults {
-  accounts: string[];
-  statuses: string[];
-  hashtags: ApiHashtagJSON[];
-}
-
-export const createSearchResults = (serverJSON: ApiSearchResultsJSON) => ({
-  accounts: serverJSON.accounts.map((account) => account.id),
-  statuses: serverJSON.statuses.map((status) => status.id),
-  hashtags: serverJSON.hashtags,
-});
diff --git a/app/javascript/mastodon/models/suggestion.ts b/app/javascript/mastodon/models/suggestion.ts
deleted file mode 100644
index 037eed480c..0000000000
--- a/app/javascript/mastodon/models/suggestion.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { ApiSuggestionJSON } from 'mastodon/api_types/suggestions';
-
-export interface Suggestion extends Omit<ApiSuggestionJSON, 'account'> {
-  account_id: string;
-}
-
-export const createSuggestion = (
-  serverJSON: ApiSuggestionJSON,
-): Suggestion => ({
-  sources: serverJSON.sources,
-  account_id: serverJSON.account.id,
-});
diff --git a/app/javascript/mastodon/models/tags.ts b/app/javascript/mastodon/models/tags.ts
deleted file mode 100644
index 3a4b1fb23e..0000000000
--- a/app/javascript/mastodon/models/tags.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
-
-export type Hashtag = ApiHashtagJSON;
-
-export const createHashtag = (serverJSON: ApiHashtagJSON): Hashtag => ({
-  ...serverJSON,
-});
diff --git a/app/javascript/mastodon/permissions.ts b/app/javascript/mastodon/permissions.ts
index d7695d2f5c..8f015610ea 100644
--- a/app/javascript/mastodon/permissions.ts
+++ b/app/javascript/mastodon/permissions.ts
@@ -1,6 +1,5 @@
 export const PERMISSION_INVITE_USERS = 0x0000000000010000;
 export const PERMISSION_MANAGE_USERS = 0x0000000000000400;
-export const PERMISSION_MANAGE_TAXONOMIES = 0x0000000000000100;
 export const PERMISSION_MANAGE_FEDERATION = 0x0000000000000020;
 
 export const PERMISSION_MANAGE_REPORTS = 0x0000000000000010;
diff --git a/app/javascript/mastodon/reducers/alerts.js b/app/javascript/mastodon/reducers/alerts.js
new file mode 100644
index 0000000000..1ca9b62a02
--- /dev/null
+++ b/app/javascript/mastodon/reducers/alerts.js
@@ -0,0 +1,30 @@
+import { List as ImmutableList } from 'immutable';
+
+import {
+  ALERT_SHOW,
+  ALERT_DISMISS,
+  ALERT_CLEAR,
+} from '../actions/alerts';
+
+const initialState = ImmutableList([]);
+
+let id = 0;
+
+const addAlert = (state, alert) =>
+  state.push({
+    key: id++,
+    ...alert,
+  });
+
+export default function alerts(state = initialState, action) {
+  switch(action.type) {
+  case ALERT_SHOW:
+    return addAlert(state, action.alert);
+  case ALERT_DISMISS:
+    return state.filterNot(item => item.key === action.alert.key);
+  case ALERT_CLEAR:
+    return state.clear();
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/alerts.ts b/app/javascript/mastodon/reducers/alerts.ts
deleted file mode 100644
index 30108744ae..0000000000
--- a/app/javascript/mastodon/reducers/alerts.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { createReducer } from '@reduxjs/toolkit';
-
-import { showAlert, dismissAlert, clearAlerts } from 'mastodon/actions/alerts';
-import type { Alert } from 'mastodon/models/alert';
-
-const initialState: Alert[] = [];
-
-let id = 0;
-
-export const alertsReducer = createReducer(initialState, (builder) => {
-  builder
-    .addCase(showAlert, (state, { payload }) => {
-      state.push({
-        key: id++,
-        ...payload,
-      });
-    })
-    .addCase(dismissAlert, (state, { payload: { key } }) => {
-      return state.filter((item) => item.key !== key);
-    })
-    .addCase(clearAlerts, () => {
-      return [];
-    });
-});
diff --git a/app/javascript/mastodon/reducers/antenna_adder.js b/app/javascript/mastodon/reducers/antenna_adder.js
new file mode 100644
index 0000000000..aae4a43a0f
--- /dev/null
+++ b/app/javascript/mastodon/reducers/antenna_adder.js
@@ -0,0 +1,58 @@
+import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+
+import {
+  ANTENNA_ADDER_RESET,
+  ANTENNA_ADDER_SETUP,
+  ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST,
+  ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS,
+  ANTENNA_ADDER_ANTENNAS_FETCH_FAIL,
+  ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST,
+  ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS,
+  ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL,
+  ANTENNA_EDITOR_ADD_SUCCESS,
+  ANTENNA_EDITOR_REMOVE_SUCCESS,
+  ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS,
+  ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS,
+} from '../actions/antennas';
+
+const initialState = ImmutableMap({
+  accountId: null,
+
+  antennas: ImmutableMap({
+    items: ImmutableList(),
+    loaded: false,
+    isLoading: false,
+  }),
+});
+
+export default function antennaAdderReducer(state = initialState, action) {
+  switch(action.type) {
+  case ANTENNA_ADDER_RESET:
+    return initialState;
+  case ANTENNA_ADDER_SETUP:
+    return state.withMutations(map => {
+      map.set('accountId', action.account.get('id'));
+    });
+  case ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST:
+  case ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST:
+    return state.setIn(['antennas', 'isLoading'], true);
+  case ANTENNA_ADDER_ANTENNAS_FETCH_FAIL:
+  case ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL:
+    return state.setIn(['antennas', 'isLoading'], false);
+  case ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS:
+  case ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS:
+    return state.update('antennas', antennas => antennas.withMutations(map => {
+      map.set('isLoading', false);
+      map.set('loaded', true);
+      map.set('items', ImmutableList(action.antennas.map(item => item.id)));
+    }));
+  case ANTENNA_EDITOR_ADD_SUCCESS:
+  case ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS:
+    return state.updateIn(['antennas', 'items'], antenna => antenna.unshift(action.antennaId));
+  case ANTENNA_EDITOR_REMOVE_SUCCESS:
+  case ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS:
+    return state.updateIn(['antennas', 'items'], antenna => antenna.filterNot(item => item === action.antennaId));
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/antenna_editor.js b/app/javascript/mastodon/reducers/antenna_editor.js
new file mode 100644
index 0000000000..c647d108b2
--- /dev/null
+++ b/app/javascript/mastodon/reducers/antenna_editor.js
@@ -0,0 +1,116 @@
+import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+
+import {
+  ANTENNA_CREATE_REQUEST,
+  ANTENNA_CREATE_FAIL,
+  ANTENNA_CREATE_SUCCESS,
+  ANTENNA_UPDATE_REQUEST,
+  ANTENNA_UPDATE_FAIL,
+  ANTENNA_UPDATE_SUCCESS,
+  ANTENNA_EDITOR_RESET,
+  ANTENNA_EDITOR_SETUP,
+  ANTENNA_EDITOR_TITLE_CHANGE,
+  ANTENNA_ACCOUNTS_FETCH_REQUEST,
+  ANTENNA_ACCOUNTS_FETCH_SUCCESS,
+  ANTENNA_ACCOUNTS_FETCH_FAIL,
+  ANTENNA_EXCLUDE_ACCOUNTS_FETCH_REQUEST,
+  ANTENNA_EXCLUDE_ACCOUNTS_FETCH_SUCCESS,
+  ANTENNA_EXCLUDE_ACCOUNTS_FETCH_FAIL,
+  ANTENNA_EDITOR_SUGGESTIONS_READY,
+  ANTENNA_EDITOR_SUGGESTIONS_CLEAR,
+  ANTENNA_EDITOR_SUGGESTIONS_CHANGE,
+  ANTENNA_EDITOR_ADD_SUCCESS,
+  ANTENNA_EDITOR_REMOVE_SUCCESS,
+  ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS,
+  ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS,
+} from '../actions/antennas';
+
+const initialState = ImmutableMap({
+  antennaId: null,
+  isSubmitting: false,
+  isChanged: false,
+  title: '',
+  accountsCount: 0,
+
+  accounts: ImmutableMap({
+    items: ImmutableList(),
+    loaded: false,
+    isLoading: false,
+  }),
+
+  suggestions: ImmutableMap({
+    value: '',
+    items: ImmutableList(),
+  }),
+});
+
+export default function antennaEditorReducer(state = initialState, action) {
+  switch(action.type) {
+  case ANTENNA_EDITOR_RESET:
+    return initialState;
+  case ANTENNA_EDITOR_SETUP:
+    return state.withMutations(map => {
+      map.set('antennaId', action.antenna.get('id'));
+      map.set('title', action.antenna.get('title'));
+      map.set('accountsCount', action.antenna.get('accounts_count'));
+      map.set('isSubmitting', false);
+    });
+  case ANTENNA_EDITOR_TITLE_CHANGE:
+    return state.withMutations(map => {
+      map.set('title', action.value);
+      map.set('isChanged', true);
+    });
+  case ANTENNA_CREATE_REQUEST:
+  case ANTENNA_UPDATE_REQUEST:
+    return state.withMutations(map => {
+      map.set('isSubmitting', true);
+      map.set('isChanged', false);
+    });
+  case ANTENNA_CREATE_FAIL:
+  case ANTENNA_UPDATE_FAIL:
+    return state.set('isSubmitting', false);
+  case ANTENNA_CREATE_SUCCESS:
+  case ANTENNA_UPDATE_SUCCESS:
+    return state.withMutations(map => {
+      map.set('isSubmitting', false);
+      map.set('antennaId', action.antenna.id);
+    });
+  case ANTENNA_ACCOUNTS_FETCH_REQUEST:
+    return state.setIn(['accounts', 'isLoading'], true);
+  case ANTENNA_ACCOUNTS_FETCH_FAIL:
+    return state.setIn(['accounts', 'isLoading'], false);
+  case ANTENNA_ACCOUNTS_FETCH_SUCCESS:
+    return state.update('accounts', accounts => accounts.withMutations(map => {
+      map.set('isLoading', false);
+      map.set('loaded', true);
+      map.set('items', ImmutableList(action.accounts.map(item => item.id)));
+    }));
+  case ANTENNA_EXCLUDE_ACCOUNTS_FETCH_REQUEST:
+    return state.setIn(['accounts', 'isLoading'], true);
+  case ANTENNA_EXCLUDE_ACCOUNTS_FETCH_FAIL:
+    return state.setIn(['accounts', 'isLoading'], false);
+  case ANTENNA_EXCLUDE_ACCOUNTS_FETCH_SUCCESS:
+    return state.update('accounts', accounts => accounts.withMutations(map => {
+      map.set('isLoading', false);
+      map.set('loaded', true);
+      map.set('items', ImmutableList(action.accounts.map(item => item.id)));
+    }));
+  case ANTENNA_EDITOR_SUGGESTIONS_CHANGE:
+    return state.setIn(['suggestions', 'value'], action.value);
+  case ANTENNA_EDITOR_SUGGESTIONS_READY:
+    return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id)));
+  case ANTENNA_EDITOR_SUGGESTIONS_CLEAR:
+    return state.update('suggestions', suggestions => suggestions.withMutations(map => {
+      map.set('items', ImmutableList());
+      map.set('value', '');
+    }));
+  case ANTENNA_EDITOR_ADD_SUCCESS:
+  case ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS:
+    return state.updateIn(['accounts', 'items'], antenna => antenna.unshift(action.accountId));
+  case ANTENNA_EDITOR_REMOVE_SUCCESS:
+  case ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS:
+    return state.updateIn(['accounts', 'items'], antenna => antenna.filterNot(item => item === action.accountId));
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/antennas.js b/app/javascript/mastodon/reducers/antennas.js
new file mode 100644
index 0000000000..7ef16f8dd2
--- /dev/null
+++ b/app/javascript/mastodon/reducers/antennas.js
@@ -0,0 +1,106 @@
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+
+import {
+  ANTENNA_FETCH_SUCCESS,
+  ANTENNA_FETCH_FAIL,
+  ANTENNAS_FETCH_SUCCESS,
+  ANTENNA_CREATE_SUCCESS,
+  ANTENNA_UPDATE_SUCCESS,
+  ANTENNA_DELETE_SUCCESS,
+  ANTENNA_EDITOR_ADD_SUCCESS,
+  ANTENNA_EDITOR_REMOVE_SUCCESS,
+  ANTENNA_EDITOR_ADD_DOMAIN_SUCCESS,
+  ANTENNA_EDITOR_REMOVE_DOMAIN_SUCCESS,
+  ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_SUCCESS,
+  ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_SUCCESS,
+  ANTENNA_EDITOR_FETCH_DOMAINS_SUCCESS,
+  ANTENNA_EDITOR_ADD_KEYWORD_SUCCESS,
+  ANTENNA_EDITOR_REMOVE_KEYWORD_SUCCESS,
+  ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_SUCCESS,
+  ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS,
+  ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS,
+  ANTENNA_EDITOR_ADD_TAG_SUCCESS,
+  ANTENNA_EDITOR_REMOVE_TAG_SUCCESS,
+  ANTENNA_EDITOR_ADD_EXCLUDE_TAG_SUCCESS,
+  ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_SUCCESS,
+  ANTENNA_EDITOR_FETCH_TAGS_SUCCESS,
+} from '../actions/antennas';
+
+const initialState = ImmutableMap();
+
+const normalizeAntenna = (state, antenna) => {
+  const old = state.get(antenna.id);
+  if (old === false) {
+    return state;
+  }
+  
+  let s = state.set(antenna.id, fromJS(antenna));
+  if (old) {
+    s = s.setIn([antenna.id, 'domains'], old.get('domains'));
+    s = s.setIn([antenna.id, 'exclude_domains'], old.get('exclude_domains'));
+    s = s.setIn([antenna.id, 'keywords'], old.get('keywords'));
+    s = s.setIn([antenna.id, 'exclude_keywords'], old.get('exclude_keywords'));
+    s = s.setIn([antenna.id, 'accounts_count'], old.get('accounts_count'));
+    s = s.setIn([antenna.id, 'domains_count'], old.get('domains_count'));
+    s = s.setIn([antenna.id, 'keywords_count'], old.get('keywords_count'));
+  }
+  return s;
+};
+
+const normalizeAntennas = (state, antennas) => {
+  antennas.forEach(antenna => {
+    state = normalizeAntenna(state, antenna);
+  });
+
+  return state;
+};
+
+export default function antennas(state = initialState, action) {
+  switch(action.type) {
+  case ANTENNA_FETCH_SUCCESS:
+  case ANTENNA_CREATE_SUCCESS:
+  case ANTENNA_UPDATE_SUCCESS:
+    return normalizeAntenna(state, action.antenna);
+  case ANTENNAS_FETCH_SUCCESS:
+    return normalizeAntennas(state, action.antennas);
+  case ANTENNA_DELETE_SUCCESS:
+  case ANTENNA_FETCH_FAIL:
+    return state.set(action.id, false);
+  case ANTENNA_EDITOR_ADD_SUCCESS:
+    return state.setIn([action.antennaId, 'accounts_count'], state.getIn([action.antennaId, 'accounts_count']) + 1);
+  case ANTENNA_EDITOR_REMOVE_SUCCESS:
+    return state.setIn([action.antennaId, 'accounts_count'], state.getIn([action.antennaId, 'accounts_count']) - 1);
+  case ANTENNA_EDITOR_ADD_DOMAIN_SUCCESS:
+    return state.setIn([action.antennaId, 'domains_count'], state.getIn([action.antennaId, 'domains_count']) + 1).updateIn([action.antennaId, 'domains', 'domains'], domains => (ImmutableList(domains || [])).push(action.domain));
+  case ANTENNA_EDITOR_REMOVE_DOMAIN_SUCCESS:
+    return state.setIn([action.antennaId, 'domains_count'], state.getIn([action.antennaId, 'domains_count']) - 1).updateIn([action.antennaId, 'domains', 'domains'], domains => (ImmutableList(domains || [])).filterNot(domain => domain === action.domain));
+  case ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_SUCCESS:
+    return state.updateIn([action.antennaId, 'domains', 'exclude_domains'], domains => (ImmutableList(domains || [])).push(action.domain));
+  case ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_SUCCESS:
+    return state.updateIn([action.antennaId, 'domains', 'exclude_domains'], domains => (ImmutableList(domains || [])).filterNot(domain => domain === action.domain));
+  case ANTENNA_EDITOR_FETCH_DOMAINS_SUCCESS:
+    return state.setIn([action.id, 'domains'], ImmutableMap({ domains: ImmutableList(action.domains.domains), exclude_domains: ImmutableList(action.domains.exclude_domains) }));
+  case ANTENNA_EDITOR_ADD_KEYWORD_SUCCESS:
+    return state.setIn([action.antennaId, 'keywords_count'], state.getIn([action.antennaId, 'keywords_count']) + 1).updateIn([action.antennaId, 'keywords', 'keywords'], keywords => (ImmutableList(keywords || [])).push(action.keyword));
+  case ANTENNA_EDITOR_REMOVE_KEYWORD_SUCCESS:
+    return state.setIn([action.antennaId, 'keywords_count'], state.getIn([action.antennaId, 'keywords_count']) - 1).updateIn([action.antennaId, 'keywords', 'keywords'], keywords => (ImmutableList(keywords || [])).filterNot(keyword => keyword === action.keyword));
+  case ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_SUCCESS:
+    return state.updateIn([action.antennaId, 'keywords', 'exclude_keywords'], keywords => (ImmutableList(keywords || [])).push(action.keyword));
+  case ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS:
+    return state.updateIn([action.antennaId, 'keywords', 'exclude_keywords'], keywords => (ImmutableList(keywords || [])).filterNot(keyword => keyword === action.keyword));
+  case ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS:
+    return state.setIn([action.id, 'keywords'], ImmutableMap({ keywords: ImmutableList(action.keywords.keywords), exclude_keywords: ImmutableList(action.keywords.exclude_keywords) }));
+  case ANTENNA_EDITOR_ADD_TAG_SUCCESS:
+    return state.setIn([action.antennaId, 'tags_count'], state.getIn([action.antennaId, 'tags_count']) + 1).updateIn([action.antennaId, 'tags', 'tags'], tags => (ImmutableList(tags || [])).push(action.tag));
+  case ANTENNA_EDITOR_REMOVE_TAG_SUCCESS:
+    return state.setIn([action.antennaId, 'tags_count'], state.getIn([action.antennaId, 'tags_count']) - 1).updateIn([action.antennaId, 'tags', 'tags'], tags => (ImmutableList(tags || [])).filterNot(tag => tag === action.tag));
+  case ANTENNA_EDITOR_ADD_EXCLUDE_TAG_SUCCESS:
+    return state.updateIn([action.antennaId, 'tags', 'exclude_tags'], tags => (ImmutableList(tags || [])).push(action.tag));
+  case ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_SUCCESS:
+    return state.updateIn([action.antennaId, 'tags', 'exclude_tags'], tags => (ImmutableList(tags || [])).filterNot(tag => tag === action.tag));
+  case ANTENNA_EDITOR_FETCH_TAGS_SUCCESS:
+    return state.setIn([action.id, 'tags'], ImmutableMap({ tags: ImmutableList(action.tags.tags), exclude_tags: ImmutableList(action.tags.exclude_tags) }));
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/antennas.ts b/app/javascript/mastodon/reducers/antennas.ts
deleted file mode 100644
index 2805fc134e..0000000000
--- a/app/javascript/mastodon/reducers/antennas.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Reducer } from '@reduxjs/toolkit';
-import { Map as ImmutableMap } from 'immutable';
-
-import { createAntenna, updateAntenna } from 'mastodon/actions/antennas_typed';
-import type { ApiAntennaJSON } from 'mastodon/api_types/antennas';
-import { createAntenna as createAntennaFromJSON } from 'mastodon/models/antenna';
-import type { Antenna } from 'mastodon/models/antenna';
-
-import {
-  ANTENNA_FETCH_SUCCESS,
-  ANTENNA_FETCH_FAIL,
-  ANTENNAS_FETCH_SUCCESS,
-  ANTENNA_DELETE_SUCCESS,
-} from '../actions/antennas';
-
-const initialState = ImmutableMap<string, Antenna | null>();
-type State = typeof initialState;
-
-const normalizeAntenna = (state: State, antenna: ApiAntennaJSON) =>
-  state.set(antenna.id, createAntennaFromJSON(antenna));
-
-const normalizeAntennas = (state: State, antennas: ApiAntennaJSON[]) => {
-  antennas.forEach((antenna) => {
-    state = normalizeAntenna(state, antenna);
-  });
-
-  return state;
-};
-
-export const antennasReducer: Reducer<State> = (
-  state = initialState,
-  action,
-) => {
-  if (
-    createAntenna.fulfilled.match(action) ||
-    updateAntenna.fulfilled.match(action)
-  ) {
-    return normalizeAntenna(state, action.payload);
-  } else {
-    switch (action.type) {
-      case ANTENNA_FETCH_SUCCESS:
-        return normalizeAntenna(state, action.antenna as ApiAntennaJSON);
-      case ANTENNAS_FETCH_SUCCESS:
-        return normalizeAntennas(state, action.antennas as ApiAntennaJSON[]);
-      case ANTENNA_DELETE_SUCCESS:
-      case ANTENNA_FETCH_FAIL:
-        return state.set(action.id as string, null);
-      default:
-        return state;
-    }
-  }
-};
diff --git a/app/javascript/mastodon/reducers/bookmark_categories.js b/app/javascript/mastodon/reducers/bookmark_categories.js
new file mode 100644
index 0000000000..f39e1b4437
--- /dev/null
+++ b/app/javascript/mastodon/reducers/bookmark_categories.js
@@ -0,0 +1,127 @@
+import { Map as ImmutableMap, fromJS, OrderedSet as ImmutableOrderedSet } from 'immutable';
+
+import {
+  BOOKMARK_CATEGORY_FETCH_SUCCESS,
+  BOOKMARK_CATEGORY_FETCH_FAIL,
+  BOOKMARK_CATEGORIES_FETCH_SUCCESS,
+  BOOKMARK_CATEGORY_CREATE_SUCCESS,
+  BOOKMARK_CATEGORY_UPDATE_SUCCESS,
+  BOOKMARK_CATEGORY_DELETE_SUCCESS,
+  BOOKMARK_CATEGORY_STATUSES_FETCH_REQUEST,
+  BOOKMARK_CATEGORY_STATUSES_FETCH_SUCCESS,
+  BOOKMARK_CATEGORY_STATUSES_FETCH_FAIL,
+  BOOKMARK_CATEGORY_STATUSES_EXPAND_REQUEST,
+  BOOKMARK_CATEGORY_STATUSES_EXPAND_SUCCESS,
+  BOOKMARK_CATEGORY_STATUSES_EXPAND_FAIL,
+  BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS,
+  BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS,
+} from '../actions/bookmark_categories';
+import {
+  UNBOOKMARK_SUCCESS,
+} from '../actions/interactions';
+
+const initialState = ImmutableMap();
+
+const normalizeBookmarkCategory = (state, category) => {
+  const old = state.get(category.id);
+  state = state.set(category.id, fromJS(category));
+  if (old) {
+    state = state.setIn([category.id, 'items'], old.get('items'));
+  }
+  return state;
+};
+
+const normalizeBookmarkCategories = (state, bookmarkCategories) => {
+  bookmarkCategories.forEach(bookmarkCategory => {
+    state = normalizeBookmarkCategory(state, bookmarkCategory);
+  });
+
+  return state;
+};
+
+const normalizeBookmarkCategoryStatuses = (state, bookmarkCategoryId, statuses, next) => {
+  return state.update(bookmarkCategoryId, listMap => listMap.withMutations(map => {
+    map.set('next', next);
+    map.set('loaded', true);
+    map.set('isLoading', false);
+    map.set('items', ImmutableOrderedSet(statuses.map(item => item.id)));
+  }));
+};
+
+const appendToBookmarkCategoryStatuses = (state, bookmarkCategoryId, statuses, next) => {
+  return appendToBookmarkCategoryStatusesById(state, bookmarkCategoryId, statuses.map(item => item.id), next);
+};
+
+const appendToBookmarkCategoryStatusesById = (state, bookmarkCategoryId, statuses, next) => {
+  return state.update(bookmarkCategoryId, listMap => listMap.withMutations(map => {
+    if (typeof next !== 'undefined') {
+      map.set('next', next);
+    }
+    map.set('isLoading', false);
+    if (map.get('items')) {
+      map.set('items', map.get('items').union(statuses));
+    }
+  }));
+};
+
+const prependToBookmarkCategoryStatusesById = (state, bookmarkCategoryId, statuses) => {
+  return state.update(bookmarkCategoryId, listMap => listMap.withMutations(map => {
+    map.set('isLoading', false);
+    if (map.get('items')) {
+      map.update('items', list => ImmutableOrderedSet([statuses]).union(list));
+    }
+  }));
+};
+
+const removeStatusFromBookmarkCategoryById = (state, bookmarkCategoryId, status) => {
+  if (state.getIn([bookmarkCategoryId, 'items'])) {
+    return state.updateIn([bookmarkCategoryId, 'items'], items => items.delete(status));
+  }
+  return state;
+};
+
+const removeStatusFromAllBookmarkCategories = (state, status) => {
+  return removeStatusFromAllBookmarkCategoriesById(state, status.get('id'));
+};
+
+const removeStatusFromAllBookmarkCategoriesById = (state, status) => {
+  state.toList().forEach((bookmarkCategory) => {
+    if (state.getIn([bookmarkCategory.get('id'), 'items'])) {
+      state = state.updateIn([bookmarkCategory.get('id'), 'items'], items => items.delete(status));
+    }
+  });
+  return state;
+};
+
+export default function bookmarkCategories(state = initialState, action) {
+  switch(action.type) {
+  case BOOKMARK_CATEGORY_FETCH_SUCCESS:
+  case BOOKMARK_CATEGORY_CREATE_SUCCESS:
+    return normalizeBookmarkCategory(state, action.bookmarkCategory);
+  case BOOKMARK_CATEGORY_UPDATE_SUCCESS:
+    return state.setIn([action.bookmarkCategory.id, 'title'], action.bookmarkCategory.title);
+  case BOOKMARK_CATEGORIES_FETCH_SUCCESS:
+    return normalizeBookmarkCategories(state, action.bookmarkCategories);
+  case BOOKMARK_CATEGORY_DELETE_SUCCESS:
+  case BOOKMARK_CATEGORY_FETCH_FAIL:
+    return state.set(action.id, false);
+  case BOOKMARK_CATEGORY_STATUSES_FETCH_REQUEST:
+  case BOOKMARK_CATEGORY_STATUSES_EXPAND_REQUEST:
+    return state.setIn([action.id, 'isLoading'], true);
+  case BOOKMARK_CATEGORY_STATUSES_FETCH_FAIL:
+  case BOOKMARK_CATEGORY_STATUSES_EXPAND_FAIL:
+    return state.setIn([action.id, 'isLoading'], false);
+  case BOOKMARK_CATEGORY_STATUSES_FETCH_SUCCESS:
+    return normalizeBookmarkCategoryStatuses(state, action.id, action.statuses, action.next);
+  case BOOKMARK_CATEGORY_STATUSES_EXPAND_SUCCESS:
+    return appendToBookmarkCategoryStatuses(state, action.id, action.statuses, action.next);
+  case BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS:
+    return prependToBookmarkCategoryStatusesById(state, action.bookmarkCategoryId, action.statusId);
+  case BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS:
+    return removeStatusFromBookmarkCategoryById(state, action.bookmarkCategoryId, action.statusId);
+  case UNBOOKMARK_SUCCESS:
+    return removeStatusFromAllBookmarkCategories(state, action.status);
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/bookmark_categories.ts b/app/javascript/mastodon/reducers/bookmark_categories.ts
deleted file mode 100644
index 81633bbbd4..0000000000
--- a/app/javascript/mastodon/reducers/bookmark_categories.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import type { Reducer } from '@reduxjs/toolkit';
-import { Map as ImmutableMap } from 'immutable';
-
-import {
-  createBookmarkCategory,
-  updateBookmarkCategory,
-} from 'mastodon/actions/bookmark_categories_typed';
-import type { ApiBookmarkCategoryJSON } from 'mastodon/api_types/bookmark_categories';
-import { createBookmarkCategory as createBookmarkCategoryFromJSON } from 'mastodon/models/bookmark_category';
-import type { BookmarkCategory } from 'mastodon/models/bookmark_category';
-
-import {
-  BOOKMARK_CATEGORY_FETCH_SUCCESS,
-  BOOKMARK_CATEGORY_FETCH_FAIL,
-  BOOKMARK_CATEGORIES_FETCH_SUCCESS,
-  BOOKMARK_CATEGORY_DELETE_SUCCESS,
-} from '../actions/bookmark_categories';
-
-const initialState = ImmutableMap<string, BookmarkCategory | null>();
-type State = typeof initialState;
-
-const normalizeBookmarkCategory = (
-  state: State,
-  bookmark_category: ApiBookmarkCategoryJSON,
-) =>
-  state.set(
-    bookmark_category.id,
-    createBookmarkCategoryFromJSON(bookmark_category),
-  );
-
-const normalizeBookmarkCategories = (
-  state: State,
-  bookmark_categories: ApiBookmarkCategoryJSON[],
-) => {
-  bookmark_categories.forEach((bookmark_category) => {
-    state = normalizeBookmarkCategory(state, bookmark_category);
-  });
-
-  return state;
-};
-
-export const bookmarkCategoriesReducer: Reducer<State> = (
-  state = initialState,
-  action,
-) => {
-  if (
-    createBookmarkCategory.fulfilled.match(action) ||
-    updateBookmarkCategory.fulfilled.match(action)
-  ) {
-    return normalizeBookmarkCategory(state, action.payload);
-  } else {
-    switch (action.type) {
-      case BOOKMARK_CATEGORY_FETCH_SUCCESS:
-        return normalizeBookmarkCategory(
-          state,
-          action.bookmarkCategory as ApiBookmarkCategoryJSON,
-        );
-      case BOOKMARK_CATEGORIES_FETCH_SUCCESS:
-        return normalizeBookmarkCategories(
-          state,
-          action.bookmarkCategories as ApiBookmarkCategoryJSON[],
-        );
-      case BOOKMARK_CATEGORY_DELETE_SUCCESS:
-      case BOOKMARK_CATEGORY_FETCH_FAIL:
-        return state.set(action.id as string, null);
-      default:
-        return state;
-    }
-  }
-};
diff --git a/app/javascript/mastodon/reducers/bookmark_category_adder.js b/app/javascript/mastodon/reducers/bookmark_category_adder.js
new file mode 100644
index 0000000000..c47ecb3b40
--- /dev/null
+++ b/app/javascript/mastodon/reducers/bookmark_category_adder.js
@@ -0,0 +1,53 @@
+import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+
+import {
+  BOOKMARK_CATEGORY_ADDER_RESET,
+  BOOKMARK_CATEGORY_ADDER_SETUP,
+  BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_REQUEST,
+  BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_SUCCESS,
+  BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_FAIL,
+  BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS,
+  BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS,
+} from '../actions/bookmark_categories';
+import {
+  UNBOOKMARK_SUCCESS,
+} from '../actions/interactions';
+
+const initialState = ImmutableMap({
+  statusId: null,
+
+  bookmarkCategories: ImmutableMap({
+    items: ImmutableList(),
+    loaded: false,
+    isLoading: false,
+  }),
+});
+
+export default function bookmarkCategoryAdderReducer(state = initialState, action) {
+  switch(action.type) {
+  case BOOKMARK_CATEGORY_ADDER_RESET:
+    return initialState;
+  case BOOKMARK_CATEGORY_ADDER_SETUP:
+    return state.withMutations(map => {
+      map.set('statusId', action.status.get('id'));
+    });
+  case BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_REQUEST:
+    return state.setIn(['bookmarkCategories', 'isLoading'], true);
+  case BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_FAIL:
+    return state.setIn(['bookmarkCategories', 'isLoading'], false);
+  case BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_SUCCESS:
+    return state.update('bookmarkCategories', bookmarkCategories => bookmarkCategories.withMutations(map => {
+      map.set('isLoading', false);
+      map.set('loaded', true);
+      map.set('items', ImmutableList(action.bookmarkCategories.map(item => item.id)));
+    }));
+  case BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS:
+    return state.updateIn(['bookmarkCategories', 'items'], bookmarkCategory => bookmarkCategory.unshift(action.bookmarkCategoryId));
+  case BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS:
+    return state.updateIn(['bookmarkCategories', 'items'], bookmarkCategory => bookmarkCategory.filterNot(item => item === action.bookmarkCategoryId));
+  case UNBOOKMARK_SUCCESS:
+    return action.status.get('id') === state.get('statusId') ? state.setIn(['bookmarkCategories', 'items'], ImmutableList()) : state;
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/bookmark_category_editor.js b/app/javascript/mastodon/reducers/bookmark_category_editor.js
new file mode 100644
index 0000000000..2c1c55c6d1
--- /dev/null
+++ b/app/javascript/mastodon/reducers/bookmark_category_editor.js
@@ -0,0 +1,67 @@
+import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+
+import {
+  BOOKMARK_CATEGORY_CREATE_REQUEST,
+  BOOKMARK_CATEGORY_CREATE_FAIL,
+  BOOKMARK_CATEGORY_CREATE_SUCCESS,
+  BOOKMARK_CATEGORY_UPDATE_REQUEST,
+  BOOKMARK_CATEGORY_UPDATE_FAIL,
+  BOOKMARK_CATEGORY_UPDATE_SUCCESS,
+  BOOKMARK_CATEGORY_EDITOR_RESET,
+  BOOKMARK_CATEGORY_EDITOR_SETUP,
+  BOOKMARK_CATEGORY_EDITOR_TITLE_CHANGE,
+} from '../actions/bookmark_categories';
+
+const initialState = ImmutableMap({
+  bookmarkCategoryId: null,
+  isSubmitting: false,
+  isChanged: false,
+  title: '',
+  isExclusive: false,
+
+  statuses: ImmutableMap({
+    items: ImmutableList(),
+    loaded: false,
+    isLoading: false,
+  }),
+
+  suggestions: ImmutableMap({
+    value: '',
+    items: ImmutableList(),
+  }),
+});
+
+export default function bookmarkCategoryEditorReducer(state = initialState, action) {
+  switch(action.type) {
+  case BOOKMARK_CATEGORY_EDITOR_RESET:
+    return initialState;
+  case BOOKMARK_CATEGORY_EDITOR_SETUP:
+    return state.withMutations(map => {
+      map.set('bookmarkCategoryId', action.bookmarkCategory.get('id'));
+      map.set('title', action.bookmarkCategory.get('title'));
+      map.set('isSubmitting', false);
+    });
+  case BOOKMARK_CATEGORY_EDITOR_TITLE_CHANGE:
+    return state.withMutations(map => {
+      map.set('title', action.value);
+      map.set('isChanged', true);
+    });
+  case BOOKMARK_CATEGORY_CREATE_REQUEST:
+  case BOOKMARK_CATEGORY_UPDATE_REQUEST:
+    return state.withMutations(map => {
+      map.set('isSubmitting', true);
+      map.set('isChanged', false);
+    });
+  case BOOKMARK_CATEGORY_CREATE_FAIL:
+  case BOOKMARK_CATEGORY_UPDATE_FAIL:
+    return state.set('isSubmitting', false);
+  case BOOKMARK_CATEGORY_CREATE_SUCCESS:
+  case BOOKMARK_CATEGORY_UPDATE_SUCCESS:
+    return state.withMutations(map => {
+      map.set('isSubmitting', false);
+      map.set('bookmarkCategoryId', action.bookmarkCategory.id);
+    });
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/circle_adder.js b/app/javascript/mastodon/reducers/circle_adder.js
new file mode 100644
index 0000000000..f09db160e6
--- /dev/null
+++ b/app/javascript/mastodon/reducers/circle_adder.js
@@ -0,0 +1,48 @@
+import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+
+import {
+  CIRCLE_ADDER_RESET,
+  CIRCLE_ADDER_SETUP,
+  CIRCLE_ADDER_CIRCLES_FETCH_REQUEST,
+  CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS,
+  CIRCLE_ADDER_CIRCLES_FETCH_FAIL,
+  CIRCLE_EDITOR_ADD_SUCCESS,
+  CIRCLE_EDITOR_REMOVE_SUCCESS,
+} from '../actions/circles';
+
+const initialState = ImmutableMap({
+  accountId: null,
+
+  circles: ImmutableMap({
+    items: ImmutableList(),
+    loaded: false,
+    isLoading: false,
+  }),
+});
+
+export default function circleAdderReducer(state = initialState, action) {
+  switch(action.type) {
+  case CIRCLE_ADDER_RESET:
+    return initialState;
+  case CIRCLE_ADDER_SETUP:
+    return state.withMutations(map => {
+      map.set('accountId', action.account.get('id'));
+    });
+  case CIRCLE_ADDER_CIRCLES_FETCH_REQUEST:
+    return state.setIn(['circles', 'isLoading'], true);
+  case CIRCLE_ADDER_CIRCLES_FETCH_FAIL:
+    return state.setIn(['circles', 'isLoading'], false);
+  case CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS:
+    return state.update('circles', circles => circles.withMutations(map => {
+      map.set('isLoading', false);
+      map.set('loaded', true);
+      map.set('items', ImmutableList(action.circles.map(item => item.id)));
+    }));
+  case CIRCLE_EDITOR_ADD_SUCCESS:
+    return state.updateIn(['circles', 'items'], circle => circle.unshift(action.circleId));
+  case CIRCLE_EDITOR_REMOVE_SUCCESS:
+    return state.updateIn(['circles', 'items'], circle => circle.filterNot(item => item === action.circleId));
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/circle_editor.js b/app/javascript/mastodon/reducers/circle_editor.js
new file mode 100644
index 0000000000..b991b8d417
--- /dev/null
+++ b/app/javascript/mastodon/reducers/circle_editor.js
@@ -0,0 +1,99 @@
+import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+
+import {
+  CIRCLE_CREATE_REQUEST,
+  CIRCLE_CREATE_FAIL,
+  CIRCLE_CREATE_SUCCESS,
+  CIRCLE_UPDATE_REQUEST,
+  CIRCLE_UPDATE_FAIL,
+  CIRCLE_UPDATE_SUCCESS,
+  CIRCLE_EDITOR_RESET,
+  CIRCLE_EDITOR_SETUP,
+  CIRCLE_EDITOR_TITLE_CHANGE,
+  CIRCLE_ACCOUNTS_FETCH_REQUEST,
+  CIRCLE_ACCOUNTS_FETCH_SUCCESS,
+  CIRCLE_ACCOUNTS_FETCH_FAIL,
+  CIRCLE_EDITOR_SUGGESTIONS_READY,
+  CIRCLE_EDITOR_SUGGESTIONS_CLEAR,
+  CIRCLE_EDITOR_SUGGESTIONS_CHANGE,
+  CIRCLE_EDITOR_ADD_SUCCESS,
+  CIRCLE_EDITOR_REMOVE_SUCCESS,
+} from '../actions/circles';
+
+const initialState = ImmutableMap({
+  circleId: null,
+  isSubmitting: false,
+  isChanged: false,
+  title: '',
+  isExclusive: false,
+
+  accounts: ImmutableMap({
+    items: ImmutableList(),
+    loaded: false,
+    isLoading: false,
+  }),
+
+  suggestions: ImmutableMap({
+    value: '',
+    items: ImmutableList(),
+  }),
+});
+
+export default function circleEditorReducer(state = initialState, action) {
+  switch(action.type) {
+  case CIRCLE_EDITOR_RESET:
+    return initialState;
+  case CIRCLE_EDITOR_SETUP:
+    return state.withMutations(map => {
+      map.set('circleId', action.circle.get('id'));
+      map.set('title', action.circle.get('title'));
+      map.set('isExclusive', action.circle.get('is_exclusive'));
+      map.set('isSubmitting', false);
+    });
+  case CIRCLE_EDITOR_TITLE_CHANGE:
+    return state.withMutations(map => {
+      map.set('title', action.value);
+      map.set('isChanged', true);
+    });
+  case CIRCLE_CREATE_REQUEST:
+  case CIRCLE_UPDATE_REQUEST:
+    return state.withMutations(map => {
+      map.set('isSubmitting', true);
+      map.set('isChanged', false);
+    });
+  case CIRCLE_CREATE_FAIL:
+  case CIRCLE_UPDATE_FAIL:
+    return state.set('isSubmitting', false);
+  case CIRCLE_CREATE_SUCCESS:
+  case CIRCLE_UPDATE_SUCCESS:
+    return state.withMutations(map => {
+      map.set('isSubmitting', false);
+      map.set('circleId', action.circle.id);
+    });
+  case CIRCLE_ACCOUNTS_FETCH_REQUEST:
+    return state.setIn(['accounts', 'isLoading'], true);
+  case CIRCLE_ACCOUNTS_FETCH_FAIL:
+    return state.setIn(['accounts', 'isLoading'], false);
+  case CIRCLE_ACCOUNTS_FETCH_SUCCESS:
+    return state.update('accounts', accounts => accounts.withMutations(map => {
+      map.set('isLoading', false);
+      map.set('loaded', true);
+      map.set('items', ImmutableList(action.accounts.map(item => item.id)));
+    }));
+  case CIRCLE_EDITOR_SUGGESTIONS_CHANGE:
+    return state.setIn(['suggestions', 'value'], action.value);
+  case CIRCLE_EDITOR_SUGGESTIONS_READY:
+    return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id)));
+  case CIRCLE_EDITOR_SUGGESTIONS_CLEAR:
+    return state.update('suggestions', suggestions => suggestions.withMutations(map => {
+      map.set('items', ImmutableList());
+      map.set('value', '');
+    }));
+  case CIRCLE_EDITOR_ADD_SUCCESS:
+    return state.updateIn(['accounts', 'items'], circle => circle.unshift(action.accountId));
+  case CIRCLE_EDITOR_REMOVE_SUCCESS:
+    return state.updateIn(['accounts', 'items'], circle => circle.filterNot(item => item === action.accountId));
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/circles.js b/app/javascript/mastodon/reducers/circles.js
new file mode 100644
index 0000000000..6b27712a49
--- /dev/null
+++ b/app/javascript/mastodon/reducers/circles.js
@@ -0,0 +1,105 @@
+import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
+
+import {
+  CIRCLE_FETCH_SUCCESS,
+  CIRCLE_FETCH_FAIL,
+  CIRCLES_FETCH_SUCCESS,
+  CIRCLE_CREATE_SUCCESS,
+  CIRCLE_UPDATE_SUCCESS,
+  CIRCLE_DELETE_SUCCESS,
+  CIRCLE_STATUSES_FETCH_REQUEST,
+  CIRCLE_STATUSES_FETCH_SUCCESS,
+  CIRCLE_STATUSES_FETCH_FAIL,
+  CIRCLE_STATUSES_EXPAND_REQUEST,
+  CIRCLE_STATUSES_EXPAND_SUCCESS,
+  CIRCLE_STATUSES_EXPAND_FAIL,
+} from '../actions/circles';
+import {
+  COMPOSE_WITH_CIRCLE_SUCCESS,
+} from '../actions/compose';
+
+const initialState = ImmutableMap();
+
+const normalizeCircle = (state, circle) => {
+  const old = state.get(circle.id);
+  if (old === false) {
+    return state;
+  }
+
+  state = state.set(circle.id, fromJS(circle));
+  if (old) {
+    state = state.setIn([circle.id, 'items'], old.get('items'));
+  }
+  return state.setIn([circle.id, 'isLoading'], false).setIn([circle.id, 'isLoaded'], true);
+};
+
+const normalizeCircles = (state, circles) => {
+  circles.forEach(circle => {
+    state = normalizeCircle(state, circle);
+  });
+
+  return state;
+};
+
+const normalizeCircleStatuses = (state, circleId, statuses, next) => {
+  return state.update(circleId, listMap => listMap.withMutations(map => {
+    map.set('next', next);
+    map.set('loaded', true);
+    map.set('isLoading', false);
+    map.set('items', ImmutableOrderedSet(statuses.map(item => item.id)));
+  }));
+};
+
+const appendToCircleStatuses = (state, circleId, statuses, next) => {
+  return appendToCircleStatusesById(state, circleId, statuses.map(item => item.id), next);
+};
+
+const appendToCircleStatusesById = (state, circleId, statuses, next) => {
+  return state.update(circleId, listMap => listMap.withMutations(map => {
+    if (typeof next !== 'undefined') {
+      map.set('next', next);
+    }
+    map.set('isLoading', false);
+    if (map.get('items')) {
+      map.set('items', map.get('items').union(statuses));
+    }
+  }));
+};
+
+const prependToCircleStatusById = (state, circleId, statusId) => {
+  if (!state.get(circleId)) return state;
+
+  return state.updateIn([circleId], circle => circle.withMutations(map => {
+    if (map.get('items')) {
+      map.update('items', list => ImmutableOrderedSet([statusId]).union(list));
+    }
+  }));
+};
+
+export default function circles(state = initialState, action) {
+  switch(action.type) {
+  case CIRCLE_FETCH_SUCCESS:
+  case CIRCLE_CREATE_SUCCESS:
+  case CIRCLE_UPDATE_SUCCESS:
+    return normalizeCircle(state, action.circle);
+  case CIRCLES_FETCH_SUCCESS:
+    return normalizeCircles(state, action.circles);
+  case CIRCLE_DELETE_SUCCESS:
+  case CIRCLE_FETCH_FAIL:
+    return state.set(action.id, false);
+  case CIRCLE_STATUSES_FETCH_REQUEST:
+  case CIRCLE_STATUSES_EXPAND_REQUEST:
+    return state.setIn([action.id, 'isLoading'], true);
+  case CIRCLE_STATUSES_FETCH_FAIL:
+  case CIRCLE_STATUSES_EXPAND_FAIL:
+    return state.setIn([action.id, 'isLoading'], false);
+  case CIRCLE_STATUSES_FETCH_SUCCESS:
+    return normalizeCircleStatuses(state, action.id, action.statuses, action.next);
+  case CIRCLE_STATUSES_EXPAND_SUCCESS:
+    return appendToCircleStatuses(state, action.id, action.statuses, action.next);
+  case COMPOSE_WITH_CIRCLE_SUCCESS:
+    return prependToCircleStatusById(state, action.circleId, action.status.id);
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/circles.ts b/app/javascript/mastodon/reducers/circles.ts
deleted file mode 100644
index e430a6ded3..0000000000
--- a/app/javascript/mastodon/reducers/circles.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Reducer } from '@reduxjs/toolkit';
-import { Map as ImmutableMap } from 'immutable';
-
-import { createCircle, updateCircle } from 'mastodon/actions/circles_typed';
-import type { ApiCircleJSON } from 'mastodon/api_types/circles';
-import { createCircle as createCircleFromJSON } from 'mastodon/models/circle';
-import type { Circle } from 'mastodon/models/circle';
-
-import {
-  CIRCLE_FETCH_SUCCESS,
-  CIRCLE_FETCH_FAIL,
-  CIRCLES_FETCH_SUCCESS,
-  CIRCLE_DELETE_SUCCESS,
-} from '../actions/circles';
-
-const initialState = ImmutableMap<string, Circle | null>();
-type State = typeof initialState;
-
-const normalizeCircle = (state: State, circle: ApiCircleJSON) =>
-  state.set(circle.id, createCircleFromJSON(circle));
-
-const normalizeCircles = (state: State, circles: ApiCircleJSON[]) => {
-  circles.forEach((circle) => {
-    state = normalizeCircle(state, circle);
-  });
-
-  return state;
-};
-
-export const circlesReducer: Reducer<State> = (
-  state = initialState,
-  action,
-) => {
-  if (
-    createCircle.fulfilled.match(action) ||
-    updateCircle.fulfilled.match(action)
-  ) {
-    return normalizeCircle(state, action.payload);
-  } else {
-    switch (action.type) {
-      case CIRCLE_FETCH_SUCCESS:
-        return normalizeCircle(state, action.circle as ApiCircleJSON);
-      case CIRCLES_FETCH_SUCCESS:
-        return normalizeCircles(state, action.circles as ApiCircleJSON[]);
-      case CIRCLE_DELETE_SUCCESS:
-      case CIRCLE_FETCH_FAIL:
-        return state.set(action.id as string, null);
-      default:
-        return state;
-    }
-  }
-};
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 1fd660e48b..a5585ebad7 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -1,6 +1,5 @@
 import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
 
-import { changeUploadCompose } from 'mastodon/actions/compose_typed';
 import { timelineDelete } from 'mastodon/actions/timelines_typed';
 
 import {
@@ -41,12 +40,18 @@ import {
   COMPOSE_EXPIRATION_INSERT,
   COMPOSE_FEATURED_TAG_INSERT,
   COMPOSE_REFERENCE_INSERT,
+  COMPOSE_UPLOAD_CHANGE_REQUEST,
+  COMPOSE_UPLOAD_CHANGE_SUCCESS,
+  COMPOSE_UPLOAD_CHANGE_FAIL,
   COMPOSE_RESET,
   COMPOSE_POLL_ADD,
   COMPOSE_POLL_REMOVE,
   COMPOSE_POLL_OPTION_CHANGE,
   COMPOSE_POLL_SETTINGS_CHANGE,
   COMPOSE_CIRCLE_CHANGE,
+  INIT_MEDIA_EDIT_MODAL,
+  COMPOSE_CHANGE_MEDIA_DESCRIPTION,
+  COMPOSE_CHANGE_MEDIA_FOCUS,
   COMPOSE_CHANGE_MEDIA_ORDER,
   COMPOSE_SET_STATUS,
   COMPOSE_SEARCHABILITY_CHANGE,
@@ -94,6 +99,13 @@ const initialState = ImmutableMap({
   resetFileKey: Math.floor((Math.random() * 0x10000)),
   idempotencyKey: null,
   tagHistory: ImmutableList(),
+  media_modal: ImmutableMap({
+    id: null,
+    description: '',
+    focusX: 0,
+    focusY: 0,
+    dirty: false,
+  }),
   posted_on_this_session: false,
 });
 
@@ -147,7 +159,6 @@ function clearAll(state) {
     map.set('sensitive', state.get('default_sensitive'));
     map.set('language', state.get('default_language'));
     map.update('media_attachments', list => list.clear());
-    map.set('progress', 0);
     map.set('poll', null);
     map.set('idempotencyKey', uuid());
     normalizePrivacy(map);
@@ -164,7 +175,6 @@ function appendMedia(state, media, file) {
     map.update('media_attachments', list => list.push(media.set('unattached', true)));
     map.set('is_uploading', false);
     map.set('is_processing', false);
-    map.set('progress', 0);
     map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
     map.set('idempotencyKey', uuid());
     map.update('pending_media_attachments', n => n - 1);
@@ -398,26 +408,7 @@ const updatePoll = (state, index, value, maxOptions) => state.updateIn(['poll',
   return tmp;
 });
 
-const calculateProgress = (loaded, total) => Math.min(Math.round((loaded / total) * 100), 100);
-
-/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
-export const composeReducer = (state = initialState, action) => {
-  if (changeUploadCompose.fulfilled.match(action)) {
-    return state
-      .set('is_changing_upload', false)
-      .update('media_attachments', list => list.map(item => {
-        if (item.get('id') === action.payload.media.id) {
-          return fromJS(action.payload.media).set('unattached', !action.payload.attached);
-        }
-
-        return item;
-      }));
-  } else if (changeUploadCompose.pending.match(action)) {
-    return state.set('is_changing_upload', true);
-  } else if (changeUploadCompose.rejected.match(action)) {
-    return state.set('is_changing_upload', false);
-  }
-
+export default function compose(state = initialState, action) {
   switch(action.type) {
   case STORE_HYDRATE:
     return hydrate(state, action.state.get('compose'));
@@ -510,13 +501,16 @@ export const composeReducer = (state = initialState, action) => {
     });
   case COMPOSE_SUBMIT_REQUEST:
     return state.set('is_submitting', true);
-
+  case COMPOSE_UPLOAD_CHANGE_REQUEST:
+    return state.set('is_changing_upload', true);
   case COMPOSE_REPLY_CANCEL:
   case COMPOSE_RESET:
   case COMPOSE_SUBMIT_SUCCESS:
     return clearAll(state);
   case COMPOSE_SUBMIT_FAIL:
     return state.set('is_submitting', false);
+  case COMPOSE_UPLOAD_CHANGE_FAIL:
+    return state.set('is_changing_upload', false);
   case COMPOSE_UPLOAD_REQUEST:
     return state.set('is_uploading', true).update('pending_media_attachments', n => n + 1);
   case COMPOSE_UPLOAD_PROCESSING:
@@ -524,19 +518,15 @@ export const composeReducer = (state = initialState, action) => {
   case COMPOSE_UPLOAD_SUCCESS:
     return appendMedia(state, fromJS(action.media), action.file);
   case COMPOSE_UPLOAD_FAIL:
-    return state
-      .set('is_uploading', false)
-      .set('is_processing', false)
-      .set('progress', 0)
-      .update('pending_media_attachments', n => n - 1);
+    return state.set('is_uploading', false).set('is_processing', false).update('pending_media_attachments', n => n - 1);
   case COMPOSE_UPLOAD_UNDO:
     return removeMedia(state, action.media_id);
   case COMPOSE_UPLOAD_PROGRESS:
-    return state.set('progress', calculateProgress(action.loaded, action.total));
+    return state.set('progress', Math.round((action.loaded / action.total) * 100));
   case THUMBNAIL_UPLOAD_REQUEST:
     return state.set('isUploadingThumbnail', true);
   case THUMBNAIL_UPLOAD_PROGRESS:
-    return state.set('thumbnailProgress', calculateProgress(action.loaded, action.total));
+    return state.set('thumbnailProgress', Math.round((action.loaded / action.total) * 100));
   case THUMBNAIL_UPLOAD_FAIL:
     return state.set('isUploadingThumbnail', false);
   case THUMBNAIL_UPLOAD_SUCCESS:
@@ -549,6 +539,19 @@ export const composeReducer = (state = initialState, action) => {
 
         return item;
       }));
+  case INIT_MEDIA_EDIT_MODAL:
+    const media =  state.get('media_attachments').find(item => item.get('id') === action.id);
+    return state.set('media_modal', ImmutableMap({
+      id: action.id,
+      description: media.get('description') || '',
+      focusX: media.getIn(['meta', 'focus', 'x'], 0),
+      focusY: media.getIn(['meta', 'focus', 'y'], 0),
+      dirty: false,
+    }));
+  case COMPOSE_CHANGE_MEDIA_DESCRIPTION:
+    return state.setIn(['media_modal', 'description'], action.description).setIn(['media_modal', 'dirty'], true);
+  case COMPOSE_CHANGE_MEDIA_FOCUS:
+    return state.setIn(['media_modal', 'focusX'], action.focusX).setIn(['media_modal', 'focusY'], action.focusY).setIn(['media_modal', 'dirty'], true);
   case COMPOSE_MENTION:
     return state.withMutations(map => {
       map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' '));
@@ -597,6 +600,17 @@ export const composeReducer = (state = initialState, action) => {
     return insertFeaturedTag(state, action.position, action.data);
   case COMPOSE_REFERENCE_INSERT:
     return insertReference(state, action.url, action.attributeType);
+  case COMPOSE_UPLOAD_CHANGE_SUCCESS:
+    return state
+      .set('is_changing_upload', false)
+      .setIn(['media_modal', 'dirty'], false)
+      .update('media_attachments', list => list.map(item => {
+        if (item.get('id') === action.media.id) {
+          return fromJS(action.media).set('unattached', !action.attached);
+        }
+
+        return item;
+      }));
   case REDRAFT:
     return state.withMutations(map => {
       map.set('text', action.raw_text || unescapeHTML(expandMentions(action.status)));
@@ -624,9 +638,9 @@ export const composeReducer = (state = initialState, action) => {
 
       if (action.status.get('poll')) {
         map.set('poll', ImmutableMap({
-          options: ImmutableList(action.status.get('poll').options.map(x => x.title)),
-          multiple: action.status.get('poll').multiple,
-          expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at),
+          options: action.status.getIn(['poll', 'options']).map(x => x.get('title')),
+          multiple: action.status.getIn(['poll', 'multiple']),
+          expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])),
         }));
       }
     });
@@ -660,9 +674,9 @@ export const composeReducer = (state = initialState, action) => {
 
       if (action.status.get('poll')) {
         map.set('poll', ImmutableMap({
-          options: ImmutableList(action.status.get('poll').options.map(x => x.title)),
-          multiple: action.status.get('poll').multiple,
-          expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at),
+          options: action.status.getIn(['poll', 'options']).map(x => x.get('title')),
+          multiple: action.status.getIn(['poll', 'multiple']),
+          expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])),
         }));
       }
     });
@@ -691,4 +705,4 @@ export const composeReducer = (state = initialState, action) => {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/mastodon/reducers/domain_lists.js b/app/javascript/mastodon/reducers/domain_lists.js
new file mode 100644
index 0000000000..5f63c77f5d
--- /dev/null
+++ b/app/javascript/mastodon/reducers/domain_lists.js
@@ -0,0 +1,26 @@
+import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
+
+import {
+  DOMAIN_BLOCKS_FETCH_SUCCESS,
+  DOMAIN_BLOCKS_EXPAND_SUCCESS,
+  unblockDomainSuccess
+} from '../actions/domain_blocks';
+
+const initialState = ImmutableMap({
+  blocks: ImmutableMap({
+    items: ImmutableOrderedSet(),
+  }),
+});
+
+export default function domainLists(state = initialState, action) {
+  switch(action.type) {
+  case DOMAIN_BLOCKS_FETCH_SUCCESS:
+    return state.setIn(['blocks', 'items'], ImmutableOrderedSet(action.domains)).setIn(['blocks', 'next'], action.next);
+  case DOMAIN_BLOCKS_EXPAND_SUCCESS:
+    return state.updateIn(['blocks', 'items'], set => set.union(action.domains)).setIn(['blocks', 'next'], action.next);
+  case unblockDomainSuccess.type:
+    return state.updateIn(['blocks', 'items'], set => set.delete(action.payload.domain));
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/dropdown_menu.ts b/app/javascript/mastodon/reducers/dropdown_menu.ts
index 0e46f0ee80..59e19bb16d 100644
--- a/app/javascript/mastodon/reducers/dropdown_menu.ts
+++ b/app/javascript/mastodon/reducers/dropdown_menu.ts
@@ -3,15 +3,15 @@ import { createReducer } from '@reduxjs/toolkit';
 import { closeDropdownMenu, openDropdownMenu } from '../actions/dropdown_menu';
 
 interface DropdownMenuState {
-  openId: number | null;
+  openId: string | null;
   keyboard: boolean;
-  scrollKey: string | undefined;
+  scrollKey: string | null;
 }
 
 const initialState: DropdownMenuState = {
   openId: null,
   keyboard: false,
-  scrollKey: undefined,
+  scrollKey: null,
 };
 
 export const dropdownMenuReducer = createReducer(initialState, (builder) => {
@@ -27,7 +27,7 @@ export const dropdownMenuReducer = createReducer(initialState, (builder) => {
     .addCase(closeDropdownMenu, (state, { payload: { id } }) => {
       if (state.openId === id) {
         state.openId = null;
-        state.scrollKey = undefined;
+        state.scrollKey = null;
       }
     });
 });
diff --git a/app/javascript/mastodon/reducers/filters.js b/app/javascript/mastodon/reducers/filters.js
index 28f0c3e6e4..8816f3f81b 100644
--- a/app/javascript/mastodon/reducers/filters.js
+++ b/app/javascript/mastodon/reducers/filters.js
@@ -9,6 +9,7 @@ const normalizeFilter = (state, filter) => {
     title: filter.title,
     context: filter.context,
     filter_action: filter.filter_action,
+    filter_action_ex: filter.filter_action_ex,
     keywords: filter.keywords,
     expires_at: filter.expires_at ? Date.parse(filter.expires_at) : null,
     with_quote: filter.with_quote,
diff --git a/app/javascript/mastodon/reducers/followed_tags.js b/app/javascript/mastodon/reducers/followed_tags.js
new file mode 100644
index 0000000000..afea8e3b35
--- /dev/null
+++ b/app/javascript/mastodon/reducers/followed_tags.js
@@ -0,0 +1,43 @@
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+
+import {
+  FOLLOWED_HASHTAGS_FETCH_REQUEST,
+  FOLLOWED_HASHTAGS_FETCH_SUCCESS,
+  FOLLOWED_HASHTAGS_FETCH_FAIL,
+  FOLLOWED_HASHTAGS_EXPAND_REQUEST,
+  FOLLOWED_HASHTAGS_EXPAND_SUCCESS,
+  FOLLOWED_HASHTAGS_EXPAND_FAIL,
+} from 'mastodon/actions/tags';
+
+const initialState = ImmutableMap({
+  items: ImmutableList(),
+  isLoading: false,
+  next: null,
+});
+
+export default function followed_tags(state = initialState, action) {
+  switch(action.type) {
+  case FOLLOWED_HASHTAGS_FETCH_REQUEST:
+    return state.set('isLoading', true);
+  case FOLLOWED_HASHTAGS_FETCH_SUCCESS:
+    return state.withMutations(map => {
+      map.set('items', fromJS(action.followed_tags));
+      map.set('isLoading', false);
+      map.set('next', action.next);
+    });
+  case FOLLOWED_HASHTAGS_FETCH_FAIL:
+    return state.set('isLoading', false);
+  case FOLLOWED_HASHTAGS_EXPAND_REQUEST:
+    return state.set('isLoading', true);
+  case FOLLOWED_HASHTAGS_EXPAND_SUCCESS:
+    return state.withMutations(map => {
+      map.update('items', set => set.concat(fromJS(action.followed_tags)));
+      map.set('isLoading', false);
+      map.set('next', action.next);
+    });
+  case FOLLOWED_HASHTAGS_EXPAND_FAIL:
+    return state.set('isLoading', false);
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/index.ts b/app/javascript/mastodon/reducers/index.ts
index 617d7a08d9..46cbb48306 100644
--- a/app/javascript/mastodon/reducers/index.ts
+++ b/app/javascript/mastodon/reducers/index.ts
@@ -5,20 +5,30 @@ import { combineReducers } from 'redux-immutable';
 
 import { accountsReducer } from './accounts';
 import accounts_map from './accounts_map';
-import { alertsReducer } from './alerts';
+import alerts from './alerts';
 import announcements from './announcements';
-import { antennasReducer } from './antennas';
-import { bookmarkCategoriesReducer } from './bookmark_categories';
-import { circlesReducer } from './circles';
-import { composeReducer } from './compose';
+import antennaAdder from './antenna_adder';
+import antennaEditor from './antenna_editor';
+import antennas from './antennas';
+import bookmark_categories from './bookmark_categories';
+import bookmarkCategoryAdder from './bookmark_category_adder';
+import bookmarkCategoryEditor from './bookmark_category_editor';
+import circleAdder from './circle_adder';
+import circleEditor from './circle_editor';
+import circles from './circles';
+import compose from './compose';
 import contexts from './contexts';
 import conversations from './conversations';
 import custom_emojis from './custom_emojis';
+import domain_lists from './domain_lists';
 import { dropdownMenuReducer } from './dropdown_menu';
 import filters from './filters';
+import followed_tags from './followed_tags';
 import height_cache from './height_cache';
 import history from './history';
-import { listsReducer } from './lists';
+import listAdder from './list_adder';
+import listEditor from './list_editor';
+import lists from './lists';
 import { markersReducer } from './markers';
 import media_attachments from './media_attachments';
 import meta from './meta';
@@ -28,16 +38,17 @@ import { notificationPolicyReducer } from './notification_policy';
 import { notificationRequestsReducer } from './notification_requests';
 import notifications from './notifications';
 import { pictureInPictureReducer } from './picture_in_picture';
-import { pollsReducer } from './polls';
+import polls from './polls';
 import push_notifications from './push_notifications';
 import reaction_deck from './reaction_deck';
 import { relationshipsReducer } from './relationships';
-import { searchReducer } from './search';
+import search from './search';
 import server from './server';
 import settings from './settings';
 import status_lists from './status_lists';
 import statuses from './statuses';
-import { suggestionsReducer } from './suggestions';
+import suggestions from './suggestions';
+import tags from './tags';
 import timelines from './timelines';
 import trends from './trends';
 import user_lists from './user_lists';
@@ -47,10 +58,11 @@ const reducers = {
   dropdownMenu: dropdownMenuReducer,
   timelines,
   meta,
-  alerts: alertsReducer,
+  alerts,
   loadingBar: loadingBarReducer,
   modal: modalReducer,
   user_lists,
+  domain_lists,
   status_lists,
   accounts: accountsReducer,
   accounts_map,
@@ -60,25 +72,35 @@ const reducers = {
   push_notifications,
   server,
   contexts,
-  compose: composeReducer,
-  search: searchReducer,
+  compose,
+  search,
   media_attachments,
   notifications,
   notificationGroups: notificationGroupsReducer,
   height_cache,
   custom_emojis,
-  lists: listsReducer,
-  antennas: antennasReducer,
-  circles: circlesReducer,
-  bookmark_categories: bookmarkCategoriesReducer,
+  lists,
+  listEditor,
+  listAdder,
+  antennas,
+  antennaEditor,
+  antennaAdder,
+  circles,
+  circleEditor,
+  circleAdder,
+  bookmark_categories,
+  bookmarkCategoryEditor,
+  bookmarkCategoryAdder,
   filters,
   conversations,
-  suggestions: suggestionsReducer,
-  polls: pollsReducer,
+  suggestions,
+  polls,
   trends,
   markers: markersReducer,
   picture_in_picture: pictureInPictureReducer,
   history,
+  tags,
+  followed_tags,
   reaction_deck,
   notificationPolicy: notificationPolicyReducer,
   notificationRequests: notificationRequestsReducer,
diff --git a/app/javascript/mastodon/reducers/list_adder.js b/app/javascript/mastodon/reducers/list_adder.js
new file mode 100644
index 0000000000..d5ac91e9fd
--- /dev/null
+++ b/app/javascript/mastodon/reducers/list_adder.js
@@ -0,0 +1,50 @@
+// Kmyblue tracking marker: copied antenna_adder, circle_adder, bookmark_category_adder
+
+import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+
+import {
+  LIST_ADDER_RESET,
+  LIST_ADDER_SETUP,
+  LIST_ADDER_LISTS_FETCH_REQUEST,
+  LIST_ADDER_LISTS_FETCH_SUCCESS,
+  LIST_ADDER_LISTS_FETCH_FAIL,
+  LIST_EDITOR_ADD_SUCCESS,
+  LIST_EDITOR_REMOVE_SUCCESS,
+} from '../actions/lists';
+
+const initialState = ImmutableMap({
+  accountId: null,
+
+  lists: ImmutableMap({
+    items: ImmutableList(),
+    loaded: false,
+    isLoading: false,
+  }),
+});
+
+export default function listAdderReducer(state = initialState, action) {
+  switch(action.type) {
+  case LIST_ADDER_RESET:
+    return initialState;
+  case LIST_ADDER_SETUP:
+    return state.withMutations(map => {
+      map.set('accountId', action.account.get('id'));
+    });
+  case LIST_ADDER_LISTS_FETCH_REQUEST:
+    return state.setIn(['lists', 'isLoading'], true);
+  case LIST_ADDER_LISTS_FETCH_FAIL:
+    return state.setIn(['lists', 'isLoading'], false);
+  case LIST_ADDER_LISTS_FETCH_SUCCESS:
+    return state.update('lists', lists => lists.withMutations(map => {
+      map.set('isLoading', false);
+      map.set('loaded', true);
+      map.set('items', ImmutableList(action.lists.map(item => item.id)));
+    }));
+  case LIST_EDITOR_ADD_SUCCESS:
+    return state.updateIn(['lists', 'items'], list => list.unshift(action.listId));
+  case LIST_EDITOR_REMOVE_SUCCESS:
+    return state.updateIn(['lists', 'items'], list => list.filterNot(item => item === action.listId));
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/list_editor.js b/app/javascript/mastodon/reducers/list_editor.js
new file mode 100644
index 0000000000..f0bb8886ec
--- /dev/null
+++ b/app/javascript/mastodon/reducers/list_editor.js
@@ -0,0 +1,101 @@
+// Kmyblue tracking marker: copied antenna_editor, circle_editor, bookmark_category_editor
+
+import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+
+import {
+  LIST_CREATE_REQUEST,
+  LIST_CREATE_FAIL,
+  LIST_CREATE_SUCCESS,
+  LIST_UPDATE_REQUEST,
+  LIST_UPDATE_FAIL,
+  LIST_UPDATE_SUCCESS,
+  LIST_EDITOR_RESET,
+  LIST_EDITOR_SETUP,
+  LIST_EDITOR_TITLE_CHANGE,
+  LIST_ACCOUNTS_FETCH_REQUEST,
+  LIST_ACCOUNTS_FETCH_SUCCESS,
+  LIST_ACCOUNTS_FETCH_FAIL,
+  LIST_EDITOR_SUGGESTIONS_READY,
+  LIST_EDITOR_SUGGESTIONS_CLEAR,
+  LIST_EDITOR_SUGGESTIONS_CHANGE,
+  LIST_EDITOR_ADD_SUCCESS,
+  LIST_EDITOR_REMOVE_SUCCESS,
+} from '../actions/lists';
+
+const initialState = ImmutableMap({
+  listId: null,
+  isSubmitting: false,
+  isChanged: false,
+  title: '',
+  isExclusive: false,
+
+  accounts: ImmutableMap({
+    items: ImmutableList(),
+    loaded: false,
+    isLoading: false,
+  }),
+
+  suggestions: ImmutableMap({
+    value: '',
+    items: ImmutableList(),
+  }),
+});
+
+export default function listEditorReducer(state = initialState, action) {
+  switch(action.type) {
+  case LIST_EDITOR_RESET:
+    return initialState;
+  case LIST_EDITOR_SETUP:
+    return state.withMutations(map => {
+      map.set('listId', action.list.get('id'));
+      map.set('title', action.list.get('title'));
+      map.set('isExclusive', action.list.get('is_exclusive'));
+      map.set('isSubmitting', false);
+    });
+  case LIST_EDITOR_TITLE_CHANGE:
+    return state.withMutations(map => {
+      map.set('title', action.value);
+      map.set('isChanged', true);
+    });
+  case LIST_CREATE_REQUEST:
+  case LIST_UPDATE_REQUEST:
+    return state.withMutations(map => {
+      map.set('isSubmitting', true);
+      map.set('isChanged', false);
+    });
+  case LIST_CREATE_FAIL:
+  case LIST_UPDATE_FAIL:
+    return state.set('isSubmitting', false);
+  case LIST_CREATE_SUCCESS:
+  case LIST_UPDATE_SUCCESS:
+    return state.withMutations(map => {
+      map.set('isSubmitting', false);
+      map.set('listId', action.list.id);
+    });
+  case LIST_ACCOUNTS_FETCH_REQUEST:
+    return state.setIn(['accounts', 'isLoading'], true);
+  case LIST_ACCOUNTS_FETCH_FAIL:
+    return state.setIn(['accounts', 'isLoading'], false);
+  case LIST_ACCOUNTS_FETCH_SUCCESS:
+    return state.update('accounts', accounts => accounts.withMutations(map => {
+      map.set('isLoading', false);
+      map.set('loaded', true);
+      map.set('items', ImmutableList(action.accounts.map(item => item.id)));
+    }));
+  case LIST_EDITOR_SUGGESTIONS_CHANGE:
+    return state.setIn(['suggestions', 'value'], action.value);
+  case LIST_EDITOR_SUGGESTIONS_READY:
+    return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id)));
+  case LIST_EDITOR_SUGGESTIONS_CLEAR:
+    return state.update('suggestions', suggestions => suggestions.withMutations(map => {
+      map.set('items', ImmutableList());
+      map.set('value', '');
+    }));
+  case LIST_EDITOR_ADD_SUCCESS:
+    return state.updateIn(['accounts', 'items'], list => list.unshift(action.accountId));
+  case LIST_EDITOR_REMOVE_SUCCESS:
+    return state.updateIn(['accounts', 'items'], list => list.filterNot(item => item === action.accountId));
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/lists.js b/app/javascript/mastodon/reducers/lists.js
new file mode 100644
index 0000000000..e196fe9b70
--- /dev/null
+++ b/app/javascript/mastodon/reducers/lists.js
@@ -0,0 +1,40 @@
+// Kmyblue tracking marker: copied antennas, circles, bookmark_categories
+
+import { Map as ImmutableMap, fromJS } from 'immutable';
+
+import {
+  LIST_FETCH_SUCCESS,
+  LIST_FETCH_FAIL,
+  LISTS_FETCH_SUCCESS,
+  LIST_CREATE_SUCCESS,
+  LIST_UPDATE_SUCCESS,
+  LIST_DELETE_SUCCESS,
+} from '../actions/lists';
+
+const initialState = ImmutableMap();
+
+const normalizeList = (state, list) => state.set(list.id, fromJS(list));
+
+const normalizeLists = (state, lists) => {
+  lists.forEach(list => {
+    state = normalizeList(state, list);
+  });
+
+  return state;
+};
+
+export default function lists(state = initialState, action) {
+  switch(action.type) {
+  case LIST_FETCH_SUCCESS:
+  case LIST_CREATE_SUCCESS:
+  case LIST_UPDATE_SUCCESS:
+    return normalizeList(state, action.list);
+  case LISTS_FETCH_SUCCESS:
+    return normalizeLists(state, action.lists);
+  case LIST_DELETE_SUCCESS:
+  case LIST_FETCH_FAIL:
+    return state.set(action.id, false);
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/lists.ts b/app/javascript/mastodon/reducers/lists.ts
deleted file mode 100644
index 593e717949..0000000000
--- a/app/javascript/mastodon/reducers/lists.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { Reducer } from '@reduxjs/toolkit';
-import { Map as ImmutableMap } from 'immutable';
-
-import { createList, updateList } from 'mastodon/actions/lists_typed';
-import type { ApiListJSON } from 'mastodon/api_types/lists';
-import { createList as createListFromJSON } from 'mastodon/models/list';
-import type { List } from 'mastodon/models/list';
-
-import {
-  LIST_FETCH_SUCCESS,
-  LIST_FETCH_FAIL,
-  LISTS_FETCH_SUCCESS,
-  LIST_DELETE_SUCCESS,
-} from '../actions/lists';
-
-const initialState = ImmutableMap<string, List | null>();
-type State = typeof initialState;
-
-const normalizeList = (state: State, list: ApiListJSON) =>
-  state.set(list.id, createListFromJSON(list));
-
-const normalizeLists = (state: State, lists: ApiListJSON[]) => {
-  lists.forEach((list) => {
-    state = normalizeList(state, list);
-  });
-
-  return state;
-};
-
-export const listsReducer: Reducer<State> = (state = initialState, action) => {
-  if (
-    createList.fulfilled.match(action) ||
-    updateList.fulfilled.match(action)
-  ) {
-    return normalizeList(state, action.payload);
-  } else {
-    switch (action.type) {
-      case LIST_FETCH_SUCCESS:
-        return normalizeList(state, action.list as ApiListJSON);
-      case LISTS_FETCH_SUCCESS:
-        return normalizeLists(state, action.lists as ApiListJSON[]);
-      case LIST_DELETE_SUCCESS:
-      case LIST_FETCH_FAIL:
-        return state.set(action.id as string, null);
-      default:
-        return state;
-    }
-  }
-};
diff --git a/app/javascript/mastodon/reducers/modal.ts b/app/javascript/mastodon/reducers/modal.ts
index e287626ff2..ca85eb8c7f 100644
--- a/app/javascript/mastodon/reducers/modal.ts
+++ b/app/javascript/mastodon/reducers/modal.ts
@@ -3,6 +3,7 @@ import { Record as ImmutableRecord, Stack } from 'immutable';
 
 import { timelineDelete } from 'mastodon/actions/timelines_typed';
 
+import { COMPOSE_UPLOAD_CHANGE_SUCCESS } from '../actions/compose';
 import type { ModalType } from '../actions/modal';
 import { openModal, closeModal } from '../actions/modal';
 
@@ -52,36 +53,12 @@ const pushModal = (
   state: State,
   modalType: ModalType,
   modalProps: ModalProps,
-  previousModalProps?: ModalProps,
 ): State => {
   return state.withMutations((record) => {
     record.set('ignoreFocus', false);
-    record.update('stack', (stack) => {
-      let tmp = stack;
-
-      // With this option, we update the previously opened modal, so that when the
-      // current (new) modal is closed, the previous modal is re-opened with different
-      // props. Specifically, this is useful for the confirmation modal.
-      if (previousModalProps) {
-        const previousModal = tmp.first() as Modal | undefined;
-
-        if (previousModal) {
-          tmp = tmp.shift().unshift(
-            Modal({
-              modalType: previousModal.modalType,
-              modalProps: {
-                ...previousModal.modalProps,
-                ...previousModalProps,
-              },
-            }),
-          );
-        }
-      }
-
-      tmp = tmp.unshift(Modal({ modalType, modalProps }));
-
-      return tmp;
-    });
+    record.update('stack', (stack) =>
+      stack.unshift(Modal({ modalType, modalProps })),
+    );
   });
 };
 
@@ -91,10 +68,11 @@ export const modalReducer: Reducer<State> = (state = initialState, action) => {
       state,
       action.payload.modalType,
       action.payload.modalProps,
-      action.payload.previousModalProps,
     );
   else if (closeModal.match(action)) return popModal(state, action.payload);
   // TODO: type those actions
+  else if (action.type === COMPOSE_UPLOAD_CHANGE_SUCCESS)
+    return popModal(state, { modalType: 'FOCAL_POINT', ignoreFocus: false });
   else if (timelineDelete.match(action))
     return state.update('stack', (stack) =>
       stack.filterNot(
diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js
index bdcf006108..8bed97024c 100644
--- a/app/javascript/mastodon/reducers/notifications.js
+++ b/app/javascript/mastodon/reducers/notifications.js
@@ -1,11 +1,50 @@
-import { fromJS, Map as ImmutableMap } from 'immutable';
+import { fromJS, Map as ImmutableMap, List as ImmutableList } from 'immutable';
+
+import { blockDomainSuccess } from 'mastodon/actions/domain_blocks';
+import { timelineDelete } from 'mastodon/actions/timelines_typed';
 
 import {
+  authorizeFollowRequestSuccess,
+  blockAccountSuccess,
+  muteAccountSuccess,
+  rejectFollowRequestSuccess,
+} from '../actions/accounts';
+import {
+  focusApp,
+  unfocusApp,
+} from '../actions/app';
+import {
+  fetchMarkers,
+} from '../actions/markers';
+import { clearNotifications } from '../actions/notification_groups';
+import {
+  notificationsUpdate,
+  NOTIFICATIONS_EXPAND_SUCCESS,
+  NOTIFICATIONS_EXPAND_REQUEST,
+  NOTIFICATIONS_EXPAND_FAIL,
+  NOTIFICATIONS_FILTER_SET,
+  NOTIFICATIONS_SCROLL_TOP,
+  NOTIFICATIONS_LOAD_PENDING,
+  NOTIFICATIONS_MOUNT,
+  NOTIFICATIONS_UNMOUNT,
+  NOTIFICATIONS_MARK_AS_READ,
   NOTIFICATIONS_SET_BROWSER_SUPPORT,
   NOTIFICATIONS_SET_BROWSER_PERMISSION,
 } from '../actions/notifications';
+import { disconnectTimeline } from '../actions/timelines';
+import { compareId } from '../compare_id';
 
 const initialState = ImmutableMap({
+  pendingItems: ImmutableList(),
+  items: ImmutableList(),
+  hasMore: true,
+  top: false,
+  mounted: 0,
+  unread: 0,
+  lastReadId: '0',
+  readMarkerId: '0',
+  isTabVisible: true,
+  isLoading: 0,
   browserSupport: false,
   browserPermission: 'default',
 });
@@ -23,8 +62,247 @@ export const notificationToMap = notification => ImmutableMap({
   moderation_warning: notification.moderation_warning ? fromJS(notification.moderation_warning) : null,
 });
 
+const normalizeNotification = (state, notification, usePendingItems) => {
+  const top = state.get('top');
+
+  // Under currently unknown conditions, the client may receive duplicates from the server
+  if (state.get('pendingItems').some((item) => item?.get('id') === notification.id) || state.get('items').some((item) => item?.get('id') === notification.id)) {
+    return state;
+  }
+
+  if (usePendingItems || !state.get('pendingItems').isEmpty()) {
+    return state.update('pendingItems', list => list.unshift(notificationToMap(notification))).update('unread', unread => unread + 1);
+  }
+
+  if (shouldCountUnreadNotifications(state)) {
+    state = state.update('unread', unread => unread + 1);
+  } else {
+    state = state.set('lastReadId', notification.id);
+  }
+
+  return state.update('items', list => {
+    if (top && list.size > 40) {
+      list = list.take(20);
+    }
+
+    return list.unshift(notificationToMap(notification));
+  });
+};
+
+const expandNormalizedNotifications = (state, notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) => {
+  // This method is pretty tricky because:
+  // - existing notifications might be out of order
+  // - the existing notifications may have gaps, most often explicitly noted with a `null` item
+  // - ideally, we don't want it to reorder existing items
+  // - `notifications` may include items that are already included
+  // - this function can be called either to fill in a gap, or load newer items
+
+  const lastReadId = state.get('lastReadId');
+  const newItems = ImmutableList(notifications.map(notificationToMap));
+
+  return state.withMutations(mutable => {
+    if (!newItems.isEmpty()) {
+      usePendingItems = isLoadingRecent && (usePendingItems || !mutable.get('pendingItems').isEmpty());
+
+      mutable.update(usePendingItems ? 'pendingItems' : 'items', oldItems => {
+        // If called to poll *new* notifications, we just need to add them on top without duplicates
+        if (isLoadingRecent) {
+          const idsToCheck = oldItems.map(item => item?.get('id')).toSet();
+          const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id')));
+          return insertedItems.concat(oldItems);
+        }
+
+        // If called to expand more (presumably older than any known to the WebUI), we just have to
+        // add them to the bottom without duplicates
+        if (isLoadingMore) {
+          const idsToCheck = oldItems.map(item => item?.get('id')).toSet();
+          const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id')));
+          return oldItems.concat(insertedItems);
+        }
+
+        // Now this gets tricky, as we don't necessarily know for sure where the gap to fill is,
+        // and some items in the timeline may not be properly ordered.
+
+        // However, we know that `newItems.last()` is the oldest item that was requested and that
+        // there is no “hole” between `newItems.last()` and `newItems.first()`.
+
+        // First, find the furthest (if properly sorted, oldest) item in the notifications that is
+        // newer than the oldest fetched one, as it's most likely that it delimits the gap.
+        // Start the gap *after* that item.
+        const lastIndex = oldItems.findLastIndex(item => item !== null && compareId(item.get('id'), newItems.last().get('id')) >= 0) + 1;
+
+        // Then, try to find the furthest (if properly sorted, oldest) item in the notifications that
+        // is newer than the most recent fetched one, as it delimits a section comprised of only
+        // items older or within `newItems` (or that were deleted from the server, so should be removed
+        // anyway).
+        // Stop the gap *after* that item.
+        const firstIndex = oldItems.take(lastIndex).findLastIndex(item => item !== null && compareId(item.get('id'), newItems.first().get('id')) > 0) + 1;
+
+        // At this point:
+        // - no `oldItems` after `firstIndex` is newer than any of the `newItems`
+        // - all `oldItems` after `lastIndex` are older than every of the `newItems`
+        // - it is possible for items in the replaced slice to be older than every `newItems`
+        // - it is possible for items before `firstIndex` to be in the `newItems` range
+        // Therefore:
+        // - to avoid losing items, items from the replaced slice that are older than `newItems`
+        //   should be added in the back.
+        // - to avoid duplicates, `newItems` should be checked the first `firstIndex` items of
+        //   `oldItems`
+        const idsToCheck = oldItems.take(firstIndex).map(item => item?.get('id')).toSet();
+        const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id')));
+        const olderItems = oldItems.slice(firstIndex, lastIndex).filter(item => item !== null && compareId(item.get('id'), newItems.last().get('id')) < 0);
+
+        return oldItems.take(firstIndex).concat(
+          insertedItems,
+          olderItems,
+          oldItems.skip(lastIndex),
+        );
+      });
+    }
+
+    if (!next) {
+      mutable.set('hasMore', false);
+    }
+
+    if (shouldCountUnreadNotifications(state)) {
+      mutable.set('unread', mutable.get('pendingItems').count(item => item !== null) + mutable.get('items').count(item => item && compareId(item.get('id'), lastReadId) > 0));
+    } else {
+      const mostRecent = newItems.find(item => item !== null);
+      if (mostRecent && compareId(lastReadId, mostRecent.get('id')) < 0) {
+        mutable.set('lastReadId', mostRecent.get('id'));
+      }
+    }
+
+    mutable.update('isLoading', (nbLoading) => nbLoading - 1);
+  });
+};
+
+const filterNotifications = (state, accountIds, type) => {
+  const helper = list => list.filterNot(item => item !== null && accountIds.includes(item.get('account')) && (type === undefined || type === item.get('type')));
+  return state.update('items', helper).update('pendingItems', helper);
+};
+
+const clearUnread = (state) => {
+  state = state.set('unread', state.get('pendingItems').size);
+  const lastNotification = state.get('items').find(item => item !== null);
+  return state.set('lastReadId', lastNotification ? lastNotification.get('id') : '0');
+};
+
+const updateTop = (state, top) => {
+  state = state.set('top', top);
+
+  if (!shouldCountUnreadNotifications(state)) {
+    state = clearUnread(state);
+  }
+
+  return state;
+};
+
+const deleteByStatus = (state, statusId) => {
+  const lastReadId = state.get('lastReadId');
+
+  if (shouldCountUnreadNotifications(state)) {
+    const deletedUnread = state.get('items').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0);
+    state = state.update('unread', unread => unread - deletedUnread.size);
+  }
+
+  const helper = list => list.filterNot(item => item !== null && item.get('status') === statusId);
+  const deletedUnread = state.get('pendingItems').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0);
+  state = state.update('unread', unread => unread - deletedUnread.size);
+  return state.update('items', helper).update('pendingItems', helper);
+};
+
+const updateMounted = (state) => {
+  state = state.update('mounted', count => count + 1);
+  if (!shouldCountUnreadNotifications(state, state.get('mounted') === 1)) {
+    state = state.set('readMarkerId', state.get('lastReadId'));
+    state = clearUnread(state);
+  }
+  return state;
+};
+
+const updateVisibility = (state, visibility) => {
+  state = state.set('isTabVisible', visibility);
+  if (!shouldCountUnreadNotifications(state)) {
+    state = state.set('readMarkerId', state.get('lastReadId'));
+    state = clearUnread(state);
+  }
+  return state;
+};
+
+const shouldCountUnreadNotifications = (state, ignoreScroll = false) => {
+  const isTabVisible   = state.get('isTabVisible');
+  const isOnTop        = state.get('top');
+  const isMounted      = state.get('mounted') > 0;
+  const lastReadId     = state.get('lastReadId');
+  const lastItem       = state.get('items').findLast(item => item !== null);
+  const lastItemReached = !state.get('hasMore') || lastReadId === '0' || (lastItem && compareId(lastItem.get('id'), lastReadId) <= 0);
+
+  return !(isTabVisible && (ignoreScroll || isOnTop) && isMounted && lastItemReached);
+};
+
+const recountUnread = (state, last_read_id) => {
+  return state.withMutations(mutable => {
+    if (compareId(last_read_id, mutable.get('lastReadId')) > 0) {
+      mutable.set('lastReadId', last_read_id);
+    }
+
+    if (compareId(last_read_id, mutable.get('readMarkerId')) > 0) {
+      mutable.set('readMarkerId', last_read_id);
+    }
+
+    if (state.get('unread') > 0 || shouldCountUnreadNotifications(state)) {
+      mutable.set('unread', mutable.get('pendingItems').count(item => item !== null) + mutable.get('items').count(item => item && compareId(item.get('id'), last_read_id) > 0));
+    }
+  });
+};
+
 export default function notifications(state = initialState, action) {
   switch(action.type) {
+  case fetchMarkers.fulfilled.type:
+    return action.payload.markers.notifications ? recountUnread(state, action.payload.markers.notifications.last_read_id) : state;
+  case NOTIFICATIONS_MOUNT:
+    return updateMounted(state);
+  case NOTIFICATIONS_UNMOUNT:
+    return state.update('mounted', count => count - 1);
+  case focusApp.type:
+    return updateVisibility(state, true);
+  case unfocusApp.type:
+    return updateVisibility(state, false);
+  case NOTIFICATIONS_LOAD_PENDING:
+    return state.update('items', list => state.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0);
+  case NOTIFICATIONS_EXPAND_REQUEST:
+    return state.update('isLoading', (nbLoading) => nbLoading + 1);
+  case NOTIFICATIONS_EXPAND_FAIL:
+    return state.update('isLoading', (nbLoading) => nbLoading - 1);
+  case NOTIFICATIONS_FILTER_SET:
+    return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', true);
+  case NOTIFICATIONS_SCROLL_TOP:
+    return updateTop(state, action.top);
+  case notificationsUpdate.type:
+    return normalizeNotification(state, action.payload.notification, action.payload.usePendingItems);
+  case NOTIFICATIONS_EXPAND_SUCCESS:
+    return expandNormalizedNotifications(state, action.notifications, action.next, action.isLoadingMore, action.isLoadingRecent, action.usePendingItems);
+  case blockAccountSuccess.type:
+    return filterNotifications(state, [action.payload.relationship.id]);
+  case muteAccountSuccess.type:
+    return action.payload.relationship.muting_notifications ? filterNotifications(state, [action.payload.relationship.id]) : state;
+  case blockDomainSuccess.type:
+    return filterNotifications(state, action.payload.accounts);
+  case authorizeFollowRequestSuccess.type:
+  case rejectFollowRequestSuccess.type:
+    return filterNotifications(state, [action.payload.id], 'follow_request');
+  case clearNotifications.pending.type:
+    return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false);
+  case timelineDelete.type:
+    return deleteByStatus(state, action.payload.statusId);
+  case disconnectTimeline.type:
+    return action.payload.timeline === 'home' ?
+      state.update(action.payload.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) :
+      state;
+  case NOTIFICATIONS_MARK_AS_READ:
+    const lastNotification = state.get('items').find(item => item !== null);
+    return lastNotification ? recountUnread(state, lastNotification.get('id')) : state;
   case NOTIFICATIONS_SET_BROWSER_SUPPORT:
     return state.set('browserSupport', action.value);
   case NOTIFICATIONS_SET_BROWSER_PERMISSION:
diff --git a/app/javascript/mastodon/reducers/polls.js b/app/javascript/mastodon/reducers/polls.js
new file mode 100644
index 0000000000..5e8e775dac
--- /dev/null
+++ b/app/javascript/mastodon/reducers/polls.js
@@ -0,0 +1,45 @@
+import { Map as ImmutableMap, fromJS } from 'immutable';
+
+import { POLLS_IMPORT } from 'mastodon/actions/importer';
+
+import { normalizePollOptionTranslation } from '../actions/importer/normalizer';
+import { STATUS_TRANSLATE_SUCCESS, STATUS_TRANSLATE_UNDO } from '../actions/statuses';
+
+const importPolls = (state, polls) => state.withMutations(map => polls.forEach(poll => map.set(poll.id, fromJS(poll))));
+
+const statusTranslateSuccess = (state, pollTranslation) => {
+  return state.withMutations(map => {
+    if (pollTranslation) {
+      const poll = state.get(pollTranslation.id);
+
+      pollTranslation.options.forEach((item, index) => {
+        map.setIn([pollTranslation.id, 'options', index, 'translation'], fromJS(normalizePollOptionTranslation(item, poll)));
+      });
+    }
+  });
+};
+
+const statusTranslateUndo = (state, id) => {
+  return state.withMutations(map => {
+    const options = map.getIn([id, 'options']);
+
+    if (options) {
+      options.forEach((item, index) => map.deleteIn([id, 'options', index, 'translation']));
+    }
+  });
+};
+
+const initialState = ImmutableMap();
+
+export default function polls(state = initialState, action) {
+  switch(action.type) {
+  case POLLS_IMPORT:
+    return importPolls(state, action.polls);
+  case STATUS_TRANSLATE_SUCCESS:
+    return statusTranslateSuccess(state, action.translation.poll);
+  case STATUS_TRANSLATE_UNDO:
+    return statusTranslateUndo(state, action.pollId);
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/polls.ts b/app/javascript/mastodon/reducers/polls.ts
deleted file mode 100644
index aadf6741c1..0000000000
--- a/app/javascript/mastodon/reducers/polls.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import type { Reducer } from '@reduxjs/toolkit';
-
-import { importPolls } from 'mastodon/actions/importer/polls';
-import { makeEmojiMap } from 'mastodon/models/custom_emoji';
-import { createPollOptionTranslationFromServerJSON } from 'mastodon/models/poll';
-import type { Poll } from 'mastodon/models/poll';
-
-import {
-  STATUS_TRANSLATE_SUCCESS,
-  STATUS_TRANSLATE_UNDO,
-} from '../actions/statuses';
-
-const initialState: Record<string, Poll> = {};
-type PollsState = typeof initialState;
-
-const statusTranslateSuccess = (state: PollsState, pollTranslation?: Poll) => {
-  if (!pollTranslation) return;
-
-  const poll = state[pollTranslation.id];
-
-  if (!poll) return;
-
-  const emojiMap = makeEmojiMap(poll.emojis);
-
-  pollTranslation.options.forEach((item, index) => {
-    const option = poll.options[index];
-    if (!option) return;
-
-    option.translation = createPollOptionTranslationFromServerJSON(
-      item,
-      emojiMap,
-    );
-  });
-};
-
-const statusTranslateUndo = (state: PollsState, id: string) => {
-  state[id]?.options.forEach((option) => {
-    option.translation = null;
-  });
-};
-
-export const pollsReducer: Reducer<PollsState> = (
-  draft = initialState,
-  action,
-) => {
-  if (importPolls.match(action)) {
-    action.payload.polls.forEach((poll) => {
-      draft[poll.id] = poll;
-    });
-  } else if (action.type === STATUS_TRANSLATE_SUCCESS)
-    statusTranslateSuccess(draft, (action.translation as { poll?: Poll }).poll);
-  else if (action.type === STATUS_TRANSLATE_UNDO) {
-    statusTranslateUndo(draft, action.pollId as string);
-  }
-
-  return draft;
-};
diff --git a/app/javascript/mastodon/reducers/push_notifications.js b/app/javascript/mastodon/reducers/push_notifications.js
index 35c2955b85..fa8af0e8cc 100644
--- a/app/javascript/mastodon/reducers/push_notifications.js
+++ b/app/javascript/mastodon/reducers/push_notifications.js
@@ -1,11 +1,11 @@
-import { Map as ImmutableMap } from 'immutable';
+import Immutable from 'immutable';
 
 import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION, SET_ALERTS } from '../actions/push_notifications';
 import { STORE_HYDRATE } from '../actions/store';
 
-const initialState = ImmutableMap({
+const initialState = Immutable.Map({
   subscription: null,
-  alerts: ImmutableMap({
+  alerts: new Immutable.Map({
     follow: false,
     follow_request: false,
     favourite: false,
@@ -24,7 +24,7 @@ export default function push_subscriptions(state = initialState, action) {
 
     if (push_subscription) {
       return state
-        .set('subscription', ImmutableMap({
+        .set('subscription', new Immutable.Map({
           id: push_subscription.get('id'),
           endpoint: push_subscription.get('endpoint'),
         }))
@@ -36,11 +36,11 @@ export default function push_subscriptions(state = initialState, action) {
   }
   case SET_SUBSCRIPTION:
     return state
-      .set('subscription', ImmutableMap({
+      .set('subscription', new Immutable.Map({
         id: action.subscription.id,
         endpoint: action.subscription.endpoint,
       }))
-      .set('alerts', ImmutableMap(action.subscription.alerts))
+      .set('alerts', new Immutable.Map(action.subscription.alerts))
       .set('isSubscribed', true);
   case SET_BROWSER_SUPPORT:
     return state.set('browserSupport', action.value);
diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js
new file mode 100644
index 0000000000..2a6665bfb6
--- /dev/null
+++ b/app/javascript/mastodon/reducers/search.js
@@ -0,0 +1,83 @@
+import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
+
+import {
+  COMPOSE_MENTION,
+  COMPOSE_REPLY,
+  COMPOSE_DIRECT,
+} from '../actions/compose';
+import {
+  SEARCH_CHANGE,
+  SEARCH_CLEAR,
+  SEARCH_FETCH_REQUEST,
+  SEARCH_FETCH_FAIL,
+  SEARCH_FETCH_SUCCESS,
+  SEARCH_SHOW,
+  SEARCH_EXPAND_REQUEST,
+  SEARCH_EXPAND_SUCCESS,
+  SEARCH_EXPAND_FAIL,
+  SEARCH_HISTORY_UPDATE,
+} from '../actions/search';
+
+const initialState = ImmutableMap({
+  value: '',
+  submitted: false,
+  hidden: false,
+  results: ImmutableMap(),
+  isLoading: false,
+  searchTerm: '',
+  type: null,
+  recent: ImmutableOrderedSet(),
+});
+
+export default function search(state = initialState, action) {
+  switch(action.type) {
+  case SEARCH_CHANGE:
+    return state.set('value', action.value);
+  case SEARCH_CLEAR:
+    return state.withMutations(map => {
+      map.set('value', '');
+      map.set('results', ImmutableMap());
+      map.set('submitted', false);
+      map.set('hidden', false);
+      map.set('searchTerm', '');
+      map.set('type', null);
+    });
+  case SEARCH_SHOW:
+    return state.set('hidden', false);
+  case COMPOSE_REPLY:
+  case COMPOSE_MENTION:
+  case COMPOSE_DIRECT:
+    return state.set('hidden', true);
+  case SEARCH_FETCH_REQUEST:
+    return state.withMutations(map => {
+      map.set('results', ImmutableMap());
+      map.set('isLoading', true);
+      map.set('submitted', true);
+      map.set('type', action.searchType);
+    });
+  case SEARCH_FETCH_FAIL:
+  case SEARCH_EXPAND_FAIL:
+    return state.set('isLoading', false);
+  case SEARCH_FETCH_SUCCESS:
+    return state.withMutations(map => {
+      map.set('results', ImmutableMap({
+        accounts: ImmutableOrderedSet(action.results.accounts.map(item => item.id)),
+        statuses: ImmutableOrderedSet(action.results.statuses.map(item => item.id)),
+        hashtags: ImmutableOrderedSet(fromJS(action.results.hashtags)),
+      }));
+
+      map.set('searchTerm', action.searchTerm);
+      map.set('type', action.searchType);
+      map.set('isLoading', false);
+    });
+  case SEARCH_EXPAND_REQUEST:
+    return state.set('type', action.searchType); // .set('isLoading', true); // original Mastodon bug
+  case SEARCH_EXPAND_SUCCESS:
+    const results = action.searchType === 'hashtags' ? ImmutableOrderedSet(fromJS(action.results.hashtags)) : action.results[action.searchType].map(item => item.id);
+    return state.updateIn(['results', action.searchType], list => list.union(results)).set('isLoading', false);
+  case SEARCH_HISTORY_UPDATE:
+    return state.set('recent', ImmutableOrderedSet(fromJS(action.recent)));
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/search.ts b/app/javascript/mastodon/reducers/search.ts
deleted file mode 100644
index 3f6b96fa07..0000000000
--- a/app/javascript/mastodon/reducers/search.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { createReducer, isAnyOf } from '@reduxjs/toolkit';
-
-import type { ApiSearchType } from 'mastodon/api_types/search';
-import type { RecentSearch, SearchResults } from 'mastodon/models/search';
-import { createSearchResults } from 'mastodon/models/search';
-
-import {
-  updateSearchHistory,
-  submitSearch,
-  expandSearch,
-} from '../actions/search';
-
-interface State {
-  recent: RecentSearch[];
-  q: string;
-  type?: ApiSearchType;
-  loading: boolean;
-  results?: SearchResults;
-}
-
-const initialState: State = {
-  recent: [],
-  q: '',
-  type: undefined,
-  loading: false,
-  results: undefined,
-};
-
-export const searchReducer = createReducer(initialState, (builder) => {
-  builder.addCase(submitSearch.fulfilled, (state, action) => {
-    state.q = action.meta.arg.q;
-    state.type = action.meta.arg.type;
-    state.results = createSearchResults(action.payload);
-    state.loading = false;
-  });
-
-  builder.addCase(expandSearch.fulfilled, (state, action) => {
-    const type = action.meta.arg.type;
-    const results = createSearchResults(action.payload);
-
-    state.type = type;
-    state.results = {
-      accounts: state.results
-        ? [...state.results.accounts, ...results.accounts]
-        : results.accounts,
-      statuses: state.results
-        ? [...state.results.statuses, ...results.statuses]
-        : results.statuses,
-      hashtags: state.results
-        ? [...state.results.hashtags, ...results.hashtags]
-        : results.hashtags,
-    };
-    state.loading = false;
-  });
-
-  builder.addCase(updateSearchHistory, (state, action) => {
-    state.recent = action.payload;
-  });
-
-  builder.addMatcher(
-    isAnyOf(expandSearch.pending, submitSearch.pending),
-    (state, action) => {
-      state.type = action.meta.arg.type;
-      state.loading = true;
-    },
-  );
-
-  builder.addMatcher(
-    isAnyOf(expandSearch.rejected, submitSearch.rejected),
-    (state) => {
-      state.loading = false;
-    },
-  );
-});
diff --git a/app/javascript/mastodon/reducers/status_lists.js b/app/javascript/mastodon/reducers/status_lists.js
index cc660af5c3..e9a6117d86 100644
--- a/app/javascript/mastodon/reducers/status_lists.js
+++ b/app/javascript/mastodon/reducers/status_lists.js
@@ -1,13 +1,12 @@
 import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
 
-import { BOOKMARK_CATEGORY_STATUSES_FETCH_SUCCESS, BOOKMARK_CATEGORY_STATUSES_EXPAND_SUCCESS, BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS, BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS } from 'mastodon/actions/bookmark_categories';
-import { CIRCLE_STATUSES_EXPAND_SUCCESS, CIRCLE_STATUSES_FETCH_SUCCESS } from 'mastodon/actions/circles';
-import { COMPOSE_WITH_CIRCLE_SUCCESS } from 'mastodon/actions/compose';
-
 import {
   blockAccountSuccess,
   muteAccountSuccess,
 } from '../actions/accounts';
+import {
+  BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS,
+} from '../actions/bookmark_categories';
 import {
   BOOKMARKED_STATUSES_FETCH_REQUEST,
   BOOKMARKED_STATUSES_FETCH_SUCCESS,
@@ -55,6 +54,7 @@ import {
 } from '../actions/trends';
 
 
+
 const initialState = ImmutableMap({
   favourites: ImmutableMap({
     next: null,
@@ -81,31 +81,9 @@ const initialState = ImmutableMap({
     loaded: false,
     items: ImmutableOrderedSet(),
   }),
-  circle_statuses: ImmutableMap(),
-  bookmark_category_statuses: ImmutableMap(),
 });
 
 const normalizeList = (state, listType, statuses, next) => {
-  if (Array.isArray(listType)) {
-    if (state.getIn(listType)) {
-      return state.updateIn(listType, listMap => {
-        return listMap.withMutations(map => {
-          map.set('next', next);
-          map.set('loaded', true);
-          map.set('isLoading', false);
-          map.set('items', ImmutableOrderedSet(statuses.map(item => item.id)));
-        });
-      });
-    } else {
-      return state.setIn(listType, ImmutableMap({
-        next: next,
-        loaded: true,
-        isLoading: false,
-        items: ImmutableOrderedSet(statuses.map(item => item.id)),
-      }));
-    }
-  }
-
   return state.update(listType, listMap => listMap.withMutations(map => {
     map.set('next', next);
     map.set('loaded', true);
@@ -115,14 +93,6 @@ const normalizeList = (state, listType, statuses, next) => {
 };
 
 const appendToList = (state, listType, statuses, next) => {
-  if (Array.isArray(listType)) {
-    return state.updateIn(listType, listMap => listMap.withMutations(map => {
-      map.set('next', next);
-      map.set('isLoading', false);
-      map.set('items', map.get('items').union(statuses.map(item => item.id)));
-    }));
-  }
-
   return state.update(listType, listMap => listMap.withMutations(map => {
     map.set('next', next);
     map.set('isLoading', false);
@@ -135,16 +105,6 @@ const prependOneToList = (state, listType, status) => {
 };
 
 const prependOneToListById = (state, listType, statusId) => {
-  if (Array.isArray(listType)) {
-    if (!state.getIn(listType)) return state;
-
-    return state.updateIn(listType, item => item.withMutations(map => {
-      if (map.get('items')) {
-        map.update('items', list => ImmutableOrderedSet([statusId]).union(list));
-      }
-    }));
-  }
-
   return state.updateIn([listType, 'items'], (list) => {
     if (list.includes(statusId)) {
       return list;
@@ -158,33 +118,6 @@ const removeOneFromList = (state, listType, status) => {
   return state.updateIn([listType, 'items'], (list) => list.delete(status.get('id')));
 };
 
-const removeOneFromListById = (state, listType, statusId) => {
-  if (Array.isArray(listType)) {
-    if (!state.getIn(listType)) return state;
-
-    return state.updateIn(listType, item => item.withMutations(map => {
-      if (map.get('items')) {
-        map.update('items', list => list.delete(statusId));
-      }
-    }));
-  }
-  
-  return state.update(listType, item => item.withMutations(map => {
-    if (map.get('items')) {
-      map.update('items', list => list.delete(statusId));
-    }
-  }));
-};
-
-const removeOneFromAllBookmarkCategoriesById = (state, statusId) => {
-  let s = state;
-  state.get('bookmark_category_statuses').forEach((category) => {
-    s = s.updateIn(['bookmark_category_statuses', category.get('id'), 'items'], list => list?.delete(statusId));
-  });
-  return s;
-};
-
-/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
 export default function statusLists(state = initialState, action) {
   switch(action.type) {
   case FAVOURITED_STATUSES_FETCH_REQUEST:
@@ -207,16 +140,6 @@ export default function statusLists(state = initialState, action) {
     return normalizeList(state, 'emoji_reactions', action.statuses, action.next);
   case EMOJI_REACTED_STATUSES_EXPAND_SUCCESS:
     return appendToList(state, 'emoji_reactions', action.statuses, action.next);
-  case CIRCLE_STATUSES_FETCH_SUCCESS:
-    return normalizeList(state, ['circle_statuses', action.id], action.statuses, action.next);
-  case CIRCLE_STATUSES_EXPAND_SUCCESS:
-    return appendToList(state, ['circle_statuses', action.id], action.statuses, action.next);
-  case COMPOSE_WITH_CIRCLE_SUCCESS:
-    return prependOneToListById(state, ['circle_statuses', action.circleId], action.statusId);
-  case BOOKMARK_CATEGORY_STATUSES_FETCH_SUCCESS:
-    return normalizeList(state, ['bookmark_category_statuses', action.id], action.statuses, action.next);
-  case BOOKMARK_CATEGORY_STATUSES_EXPAND_SUCCESS:
-    return appendToList(state, ['bookmark_category_statuses', action.id], action.statuses, action.next);
   case BOOKMARKED_STATUSES_FETCH_REQUEST:
   case BOOKMARKED_STATUSES_EXPAND_REQUEST:
     return state.setIn(['bookmarks', 'isLoading'], true);
@@ -248,17 +171,9 @@ export default function statusLists(state = initialState, action) {
   case BOOKMARK_SUCCESS:
     return prependOneToList(state, 'bookmarks', action.status);
   case BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS:
-  {
-    const s = prependOneToListById(state, 'bookmarks', action.statusId);
-    return prependOneToListById(s, ['bookmark_category_statuses', action.id], action.statusId);
-  }
-  case BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS:
-    return removeOneFromListById(state, ['bookmark_category_statuses', action.id], action.statusId);
+    return prependOneToListById(state, 'bookmarks', action.statusId);
   case UNBOOKMARK_SUCCESS:
-  {
-    const s = removeOneFromList(state, 'bookmarks', action.status);
-    return removeOneFromAllBookmarkCategoriesById(s, action.statusId);
-  }
+    return removeOneFromList(state, 'bookmarks', action.status);
   case PINNED_STATUSES_FETCH_SUCCESS:
     return normalizeList(state, 'pins', action.statuses, action.next);
   case PIN_SUCCESS:
diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js
index 977bbe3e3d..e0b4ed82fb 100644
--- a/app/javascript/mastodon/reducers/statuses.js
+++ b/app/javascript/mastodon/reducers/statuses.js
@@ -4,7 +4,8 @@ import { timelineDelete } from 'mastodon/actions/timelines_typed';
 import { me } from 'mastodon/initial_state';
 
 import {
-  BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS,
+  BOOKMARK_CATEGORY_EDITOR_ADD_REQUEST,
+  BOOKMARK_CATEGORY_EDITOR_ADD_FAIL,
 } from '../actions/bookmark_categories';
 import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
 import { normalizeStatusTranslation } from '../actions/importer/normalizer';
@@ -121,8 +122,10 @@ export default function statuses(state = initialState, action) {
     return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], true);
   case BOOKMARK_FAIL:
     return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
-  case BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS:
+  case BOOKMARK_CATEGORY_EDITOR_ADD_REQUEST:
     return state.get(action.statusId) === undefined ? state : state.setIn([action.statusId, 'bookmarked'], true);
+  case BOOKMARK_CATEGORY_EDITOR_ADD_FAIL:
+    return state.get(action.statusId) === undefined ? state : state.setIn([action.statusId, 'bookmarked'], false);
   case UNBOOKMARK_REQUEST:
     return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
   case UNBOOKMARK_FAIL:
diff --git a/app/javascript/mastodon/reducers/suggestions.js b/app/javascript/mastodon/reducers/suggestions.js
new file mode 100644
index 0000000000..5b9d983dea
--- /dev/null
+++ b/app/javascript/mastodon/reducers/suggestions.js
@@ -0,0 +1,40 @@
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+
+import { blockAccountSuccess, muteAccountSuccess } from 'mastodon/actions/accounts';
+import { blockDomainSuccess } from 'mastodon/actions/domain_blocks';
+
+import {
+  SUGGESTIONS_FETCH_REQUEST,
+  SUGGESTIONS_FETCH_SUCCESS,
+  SUGGESTIONS_FETCH_FAIL,
+  SUGGESTIONS_DISMISS,
+} from '../actions/suggestions';
+
+
+const initialState = ImmutableMap({
+  items: ImmutableList(),
+  isLoading: false,
+});
+
+export default function suggestionsReducer(state = initialState, action) {
+  switch(action.type) {
+  case SUGGESTIONS_FETCH_REQUEST:
+    return state.set('isLoading', true);
+  case SUGGESTIONS_FETCH_SUCCESS:
+    return state.withMutations(map => {
+      map.set('items', fromJS(action.suggestions.map(x => ({ ...x, account: x.account.id }))));
+      map.set('isLoading', false);
+    });
+  case SUGGESTIONS_FETCH_FAIL:
+    return state.set('isLoading', false);
+  case SUGGESTIONS_DISMISS:
+    return state.update('items', list => list.filterNot(x => x.get('account') === action.id));
+  case blockAccountSuccess.type:
+  case muteAccountSuccess.type:
+    return state.update('items', list => list.filterNot(x => x.get('account') === action.payload.relationship.id));
+  case blockDomainSuccess.type:
+    return state.update('items', list => list.filterNot(x => action.payload.accounts.includes(x.get('account'))));
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/suggestions.ts b/app/javascript/mastodon/reducers/suggestions.ts
deleted file mode 100644
index b9da933497..0000000000
--- a/app/javascript/mastodon/reducers/suggestions.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { createReducer, isAnyOf } from '@reduxjs/toolkit';
-
-import {
-  blockAccountSuccess,
-  muteAccountSuccess,
-} from 'mastodon/actions/accounts';
-import { blockDomainSuccess } from 'mastodon/actions/domain_blocks';
-import {
-  fetchSuggestions,
-  dismissSuggestion,
-} from 'mastodon/actions/suggestions';
-import { createSuggestion } from 'mastodon/models/suggestion';
-import type { Suggestion } from 'mastodon/models/suggestion';
-
-interface State {
-  items: Suggestion[];
-  isLoading: boolean;
-}
-
-const initialState: State = {
-  items: [],
-  isLoading: false,
-};
-
-export const suggestionsReducer = createReducer(initialState, (builder) => {
-  builder.addCase(fetchSuggestions.pending, (state) => {
-    state.isLoading = true;
-  });
-
-  builder.addCase(fetchSuggestions.fulfilled, (state, action) => {
-    state.items = action.payload.map(createSuggestion);
-    state.isLoading = false;
-  });
-
-  builder.addCase(fetchSuggestions.rejected, (state) => {
-    state.isLoading = false;
-  });
-
-  builder.addCase(dismissSuggestion.pending, (state, action) => {
-    state.items = state.items.filter(
-      (x) => x.account_id !== action.meta.arg.accountId,
-    );
-  });
-
-  builder.addCase(blockDomainSuccess, (state, action) => {
-    state.items = state.items.filter(
-      (x) =>
-        !action.payload.accounts.some((account) => account.id === x.account_id),
-    );
-  });
-
-  builder.addMatcher(
-    isAnyOf(blockAccountSuccess, muteAccountSuccess),
-    (state, action) => {
-      state.items = state.items.filter(
-        (x) => x.account_id !== action.payload.relationship.id,
-      );
-    },
-  );
-});
diff --git a/app/javascript/mastodon/reducers/tags.js b/app/javascript/mastodon/reducers/tags.js
new file mode 100644
index 0000000000..23a1ae82b5
--- /dev/null
+++ b/app/javascript/mastodon/reducers/tags.js
@@ -0,0 +1,26 @@
+import { Map as ImmutableMap, fromJS } from 'immutable';
+
+import {
+  HASHTAG_FETCH_SUCCESS,
+  HASHTAG_FOLLOW_REQUEST,
+  HASHTAG_FOLLOW_FAIL,
+  HASHTAG_UNFOLLOW_REQUEST,
+  HASHTAG_UNFOLLOW_FAIL,
+} from 'mastodon/actions/tags';
+
+const initialState = ImmutableMap();
+
+export default function tags(state = initialState, action) {
+  switch(action.type) {
+  case HASHTAG_FETCH_SUCCESS:
+    return state.set(action.name, fromJS(action.tag));
+  case HASHTAG_FOLLOW_REQUEST:
+  case HASHTAG_UNFOLLOW_FAIL:
+    return state.setIn([action.name, 'following'], true);
+  case HASHTAG_FOLLOW_FAIL:
+  case HASHTAG_UNFOLLOW_REQUEST:
+    return state.setIn([action.name, 'following'], false);
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/selectors/accounts.ts b/app/javascript/mastodon/selectors/accounts.ts
index a33daee867..cee3a87bca 100644
--- a/app/javascript/mastodon/selectors/accounts.ts
+++ b/app/javascript/mastodon/selectors/accounts.ts
@@ -1,7 +1,6 @@
 import { createSelector } from '@reduxjs/toolkit';
 import { Record as ImmutableRecord } from 'immutable';
 
-import { me } from 'mastodon/initial_state';
 import { accountDefaultValues } from 'mastodon/models/account';
 import type { Account, AccountShape } from 'mastodon/models/account';
 import type { Relationship } from 'mastodon/models/relationship';
@@ -46,16 +45,3 @@ export function makeGetAccount() {
     },
   );
 }
-
-export const getAccountHidden = createSelector(
-  [
-    (state: RootState, id: string) => state.accounts.get(id)?.hidden,
-    (state: RootState, id: string) =>
-      state.relationships.get(id)?.following ||
-      state.relationships.get(id)?.requested,
-    (state: RootState, id: string) => id === me,
-  ],
-  (hidden, followingOrRequested, isSelf) => {
-    return hidden && !(isSelf || followingOrRequested);
-  },
-);
diff --git a/app/javascript/mastodon/selectors/antennas.ts b/app/javascript/mastodon/selectors/antennas.ts
deleted file mode 100644
index 7194bf9a19..0000000000
--- a/app/javascript/mastodon/selectors/antennas.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createSelector } from '@reduxjs/toolkit';
-import type { Map as ImmutableMap } from 'immutable';
-
-import type { Antenna } from 'mastodon/models/antenna';
-import type { RootState } from 'mastodon/store';
-
-export const getOrderedAntennas = createSelector(
-  [(state: RootState) => state.antennas],
-  (antennas: ImmutableMap<string, Antenna | null>) =>
-    antennas
-      .toList()
-      .filter((item: Antenna | null) => !!item)
-      .sort((a: Antenna, b: Antenna) => a.title.localeCompare(b.title))
-      .toArray(),
-);
diff --git a/app/javascript/mastodon/selectors/bookmark_categories.ts b/app/javascript/mastodon/selectors/bookmark_categories.ts
deleted file mode 100644
index a3793e33e5..0000000000
--- a/app/javascript/mastodon/selectors/bookmark_categories.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { createSelector } from '@reduxjs/toolkit';
-import type { Map as ImmutableMap } from 'immutable';
-
-import type { BookmarkCategory } from 'mastodon/models/bookmark_category';
-import type { RootState } from 'mastodon/store';
-
-export const getOrderedBookmarkCategories = createSelector(
-  [(state: RootState) => state.bookmark_categories],
-  (bookmark_categories: ImmutableMap<string, BookmarkCategory | null>) =>
-    bookmark_categories
-      .toList()
-      .filter((item: BookmarkCategory | null) => !!item)
-      .sort((a: BookmarkCategory, b: BookmarkCategory) =>
-        a.title.localeCompare(b.title),
-      )
-      .toArray(),
-);
diff --git a/app/javascript/mastodon/selectors/circles.ts b/app/javascript/mastodon/selectors/circles.ts
deleted file mode 100644
index 0959e70ddb..0000000000
--- a/app/javascript/mastodon/selectors/circles.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createSelector } from '@reduxjs/toolkit';
-import type { Map as ImmutableMap } from 'immutable';
-
-import type { Circle } from 'mastodon/models/circle';
-import type { RootState } from 'mastodon/store';
-
-export const getOrderedCircles = createSelector(
-  [(state: RootState) => state.circles],
-  (circles: ImmutableMap<string, Circle | null>) =>
-    circles
-      .toList()
-      .filter((item: Circle | null) => !!item)
-      .sort((a: Circle, b: Circle) => a.title.localeCompare(b.title))
-      .toArray(),
-);
diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js
index a03ee70e3f..9553ee39de 100644
--- a/app/javascript/mastodon/selectors/index.js
+++ b/app/javascript/mastodon/selectors/index.js
@@ -6,7 +6,6 @@ import { me, isHideItem } from '../initial_state';
 import { getFilters } from './filters';
 
 export { makeGetAccount } from "./accounts";
-export { getStatusList, getSubStatusList } from "./statuses";
 
 export const makeGetStatus = () => {
   return createSelector(
@@ -18,10 +17,9 @@ export const makeGetStatus = () => {
       (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
       (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
       getFilters,
-      (_, { contextType }) => ['detailed', 'bookmarks', 'favourites'].includes(contextType),
     ],
 
-    (statusBase, statusReblog, statusQuote, statusReblogQuote, accountBase, accountReblog, filters, warnInsteadOfHide) => {
+    (statusBase, statusReblog, statusQuote, statusReblogQuote, accountBase, accountReblog, filters) => {
       if (!statusBase || statusBase.get('isLoading')) {
         return null;
       }
@@ -38,7 +36,7 @@ export const makeGetStatus = () => {
       }
 
       let filtered = false;
-      let mediaFiltered = false;
+      let filterAction = 'warn';
       if ((accountReblog || accountBase).get('id') !== me && filters) {
         let filterResults = statusReblog?.get('filtered') || statusBase.get('filtered') || ImmutableList();
         const quoteFilterResults = statusQuote?.get('filtered');
@@ -49,18 +47,13 @@ export const makeGetStatus = () => {
           }
         }
 
-        if (!warnInsteadOfHide && filterResults.some((result) => filters.getIn([result.get('filter'), 'filter_action']) === 'hide')) {
+        if (filterResults.some((result) => filters.getIn([result.get('filter'), 'filter_action_ex']) === 'hide')) {
           return null;
         }
-
-        let mediaFilters = filterResults.filter(result => filters.getIn([result.get('filter'), 'filter_action']) === 'blur');
-        if (!mediaFilters.isEmpty()) {
-          mediaFiltered = mediaFilters.map(result => filters.getIn([result.get('filter'), 'title']));
-        }
-
-        filterResults = filterResults.filter(result => filters.has(result.get('filter')) && filters.getIn([result.get('filter'), 'filter_action']) !== 'blur');
+        filterResults = filterResults.filter(result => filters.has(result.get('filter')));
         if (!filterResults.isEmpty()) {
           filtered = filterResults.map(result => filters.getIn([result.get('filter'), 'title']));
+          filterAction = filterResults.some((result) => filters.getIn([result.get('filter'), 'filter_action_ex']) === 'warn') ? 'warn' : 'half_warn';
         }
       }
 
@@ -69,7 +62,8 @@ export const makeGetStatus = () => {
         map.set('quote', statusQuote);
         map.set('account', accountBase);
         map.set('matched_filters', filtered);
-        map.set('matched_media_filters', mediaFiltered);
+        map.set('filter_action', filterAction);
+        map.set('filter_action_ex', filterAction);
       });
     },
   );
@@ -85,6 +79,28 @@ export const makeGetPictureInPicture = () => {
   }));
 };
 
+const ALERT_DEFAULTS = {
+  dismissAfter: 5000,
+  style: false,
+};
+
+const formatIfNeeded = (intl, message, values) => {
+  if (typeof message === 'object') {
+    return intl.formatMessage(message, values);
+  }
+
+  return message;
+};
+
+export const getAlerts = createSelector([state => state.get('alerts'), (_, { intl }) => intl], (alerts, intl) =>
+  alerts.map(item => ({
+    ...ALERT_DEFAULTS,
+    ...item,
+    action: formatIfNeeded(intl, item.action, item.values),
+    title: formatIfNeeded(intl, item.title, item.values),
+    message: formatIfNeeded(intl, item.message, item.values),
+  })).toArray());
+
 export const makeGetNotification = () => createSelector([
   (_, base)             => base,
   (state, _, accountId) => state.getIn(['accounts', accountId]),
@@ -94,3 +110,38 @@ export const makeGetReport = () => createSelector([
   (_, base) => base,
   (state, _, targetAccountId) => state.getIn(['accounts', targetAccountId]),
 ], (base, targetAccount) => base.set('target_account', targetAccount));
+
+export const getAccountGallery = createSelector([
+  (state, id) => state.getIn(['timelines', `account:${id}:media`, 'items'], ImmutableList()),
+  state       => state.get('statuses'),
+  (state, id) => state.getIn(['accounts', id]),
+], (statusIds, statuses, account) => {
+  let medias = ImmutableList();
+
+  statusIds.forEach(statusId => {
+    const status = statuses.get(statusId).set('account', account);
+    medias = medias.concat(status.get('media_attachments').map(media => media.set('status', status)));
+  });
+
+  return medias;
+});
+
+export const getAccountHidden = createSelector([
+  (state, id) => state.getIn(['accounts', id, 'hidden']),
+  (state, id) => state.getIn(['relationships', id, 'following']) || state.getIn(['relationships', id, 'requested']),
+  (state, id) => id === me,
+], (hidden, followingOrRequested, isSelf) => {
+  return hidden && !(isSelf || followingOrRequested);
+});
+
+export const getStatusList = createSelector([
+  (state, type) => state.getIn(['status_lists', type, 'items']),
+], (items) => items.toList());
+
+export const getBookmarkCategoryStatusList = createSelector([
+  (state, bookmarkCategoryId) => state.getIn(['bookmark_categories', bookmarkCategoryId, 'items']),
+], (items) => items ? items.toList() : ImmutableList());
+
+export const getCircleStatusList = createSelector([
+  (state, circleId) => state.getIn(['circles', circleId, 'items']),
+], (items) => items ? items.toList() : ImmutableList());
diff --git a/app/javascript/mastodon/selectors/lists.ts b/app/javascript/mastodon/selectors/lists.ts
deleted file mode 100644
index f93e90ce68..0000000000
--- a/app/javascript/mastodon/selectors/lists.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createSelector } from '@reduxjs/toolkit';
-import type { Map as ImmutableMap } from 'immutable';
-
-import type { List } from 'mastodon/models/list';
-import type { RootState } from 'mastodon/store';
-
-export const getOrderedLists = createSelector(
-  [(state: RootState) => state.lists],
-  (lists: ImmutableMap<string, List | null>) =>
-    lists
-      .toList()
-      .filter((item: List | null) => !!item)
-      .sort((a: List, b: List) => a.title.localeCompare(b.title))
-      .toArray(),
-);
diff --git a/app/javascript/mastodon/selectors/statuses.ts b/app/javascript/mastodon/selectors/statuses.ts
deleted file mode 100644
index d07cc93aec..0000000000
--- a/app/javascript/mastodon/selectors/statuses.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { createSelector } from '@reduxjs/toolkit';
-import type { OrderedSet as ImmutableOrderedSet } from 'immutable';
-import { List as ImmutableList } from 'immutable';
-
-import type { RootState } from 'mastodon/store';
-
-export const getStatusList = createSelector(
-  [
-    (
-      state: RootState,
-      type:
-        | 'favourites'
-        | 'bookmarks'
-        | 'pins'
-        | 'trending'
-        | 'emoji_reactions',
-    ) =>
-      state.status_lists.getIn([type, 'items']) as ImmutableOrderedSet<string>,
-  ],
-  (items) => items.toList(),
-);
-
-export const getSubStatusList = createSelector(
-  [
-    (state: RootState, type: 'bookmark_category' | 'circle', id: string) =>
-      state.status_lists.getIn([
-        `${type}_statuses`,
-        id,
-        'items',
-      ]) as ImmutableOrderedSet<string> | null,
-  ],
-  (items) => (items ? items.toList() : ImmutableList()),
-);
diff --git a/app/javascript/mastodon/service_worker/web_push_locales.js b/app/javascript/mastodon/service_worker/web_push_locales.js
index f3d61e0195..3e39c9a4ed 100644
--- a/app/javascript/mastodon/service_worker/web_push_locales.js
+++ b/app/javascript/mastodon/service_worker/web_push_locales.js
@@ -1,3 +1,6 @@
+/* eslint-disable import/no-commonjs --
+   We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
+
 /* @preval */
 
 const fs   = require('fs');
diff --git a/app/javascript/mastodon/store/middlewares/errors.ts b/app/javascript/mastodon/store/middlewares/errors.ts
index b9efe9f2b4..3ad3844d5b 100644
--- a/app/javascript/mastodon/store/middlewares/errors.ts
+++ b/app/javascript/mastodon/store/middlewares/errors.ts
@@ -12,21 +12,19 @@ import type { AsyncThunkRejectValue } from '../typed_functions';
 const defaultFailSuffix = 'FAIL';
 const isFailedAction = new RegExp(`${defaultFailSuffix}$`, 'g');
 
+interface ActionWithMaybeAlertParams extends Action, AsyncThunkRejectValue {}
+
 interface RejectedAction extends Action {
   payload: AsyncThunkRejectValue;
 }
 
-interface ActionWithMaybeAlertParams extends Action, AsyncThunkRejectValue {
-  payload?: AsyncThunkRejectValue;
-}
-
 function isRejectedActionWithPayload(
   action: unknown,
 ): action is RejectedAction {
   return isAsyncThunkAction(action) && isRejectedWithValue(action);
 }
 
-function isActionWithMaybeAlertParams(
+function isActionWithmaybeAlertParams(
   action: unknown,
 ): action is ActionWithMaybeAlertParams {
   return isAction(action);
@@ -42,12 +40,11 @@ export const errorsMiddleware: Middleware<{}, RootState> =
         showAlertForError(action.payload.error, action.payload.skipNotFound),
       );
     } else if (
-      isActionWithMaybeAlertParams(action) &&
-      !(action.payload?.skipAlert || action.skipAlert) &&
+      isActionWithmaybeAlertParams(action) &&
+      !action.skipAlert &&
       action.type.match(isFailedAction)
     ) {
-      const { error, skipNotFound } = action.payload ?? action;
-      dispatch(showAlertForError(error, skipNotFound));
+      dispatch(showAlertForError(action.error, action.skipNotFound));
     }
 
     return next(action);
diff --git a/app/javascript/mastodon/utils/filters.ts b/app/javascript/mastodon/utils/filters.ts
index 479e1f44ab..5d334fe509 100644
--- a/app/javascript/mastodon/utils/filters.ts
+++ b/app/javascript/mastodon/utils/filters.ts
@@ -7,11 +7,6 @@ export const toServerSideType = (columnType: string) => {
     case 'account':
     case 'explore':
       return columnType;
-    case 'detailed':
-      return 'thread';
-    case 'bookmarks':
-    case 'favourites':
-      return 'home';
     default:
       if (columnType.includes('list:') || columnType.includes('antenna:')) {
         return 'home';
diff --git a/app/javascript/material-icons/400-24px/celebration-fill.svg b/app/javascript/material-icons/400-24px/celebration-fill.svg
deleted file mode 100644
index d715cf2fb3..0000000000
--- a/app/javascript/material-icons/400-24px/celebration-fill.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m80-80 200-560 360 360L80-80Zm502-378-42-42 224-224q32-32 77-32t77 32l24 24-42 42-24-24q-14-14-35-14t-35 14L582-458ZM422-618l-42-42 24-24q14-14 14-34t-14-34l-26-26 42-42 26 26q32 32 32 76t-32 76l-24 24Zm80 80-42-42 144-144q14-14 14-35t-14-35l-64-64 42-42 64 64q32 32 32 77t-32 77L502-538Zm160 160-42-42 64-64q32-32 77-32t77 32l64 64-42 42-64-64q-14-14-35-14t-35 14l-64 64Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/celebration.svg b/app/javascript/material-icons/400-24px/celebration.svg
deleted file mode 100644
index 1d1b19ee72..0000000000
--- a/app/javascript/material-icons/400-24px/celebration.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m80-80 200-560 360 360L80-80Zm132-132 282-100-182-182-100 282Zm370-246-42-42 224-224q32-32 77-32t77 32l24 24-42 42-24-24q-14-14-35-14t-35 14L582-458ZM422-618l-42-42 24-24q14-14 14-34t-14-34l-26-26 42-42 26 26q32 32 32 76t-32 76l-24 24Zm80 80-42-42 144-144q14-14 14-35t-14-35l-64-64 42-42 64 64q32 32 32 77t-32 77L502-538Zm160 160-42-42 64-64q32-32 77-32t77 32l64 64-42 42-64-64q-14-14-35-14t-35 14l-64 64ZM212-212Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/explore-fill.svg b/app/javascript/material-icons/400-24px/explore-fill.svg
index 919ecb580a..febe0a63b4 100644
--- a/app/javascript/material-icons/400-24px/explore-fill.svg
+++ b/app/javascript/material-icons/400-24px/explore-fill.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m300-300 280-80 80-280-280 80-80 280Zm180-120q-25 0-42.5-17.5T420-480q0-25 17.5-42.5T480-540q25 0 42.5 17.5T540-480q0 25-17.5 42.5T480-420Zm0 340q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m260-260 300-140 140-300-300 140-140 300Zm220-180q-17 0-28.5-11.5T440-480q0-17 11.5-28.5T480-520q17 0 28.5 11.5T520-480q0 17-11.5 28.5T480-440Zm0 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/explore.svg b/app/javascript/material-icons/400-24px/explore.svg
index bb8ba1f4c6..547a999421 100644
--- a/app/javascript/material-icons/400-24px/explore.svg
+++ b/app/javascript/material-icons/400-24px/explore.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m300-300 280-80 80-280-280 80-80 280Zm180-120q-25 0-42.5-17.5T420-480q0-25 17.5-42.5T480-540q25 0 42.5 17.5T540-480q0 25-17.5 42.5T480-420Zm0 340q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q133 0 226.5-93.5T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160Zm0-320Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m260-260 300-140 140-300-300 140-140 300Zm220-180q-17 0-28.5-11.5T440-480q0-17 11.5-28.5T480-520q17 0 28.5 11.5T520-480q0 17-11.5 28.5T480-440Zm0 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/extension-fill.svg b/app/javascript/material-icons/400-24px/extension-fill.svg
deleted file mode 100644
index f6e7de8cce..0000000000
--- a/app/javascript/material-icons/400-24px/extension-fill.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M352-120H200q-33 0-56.5-23.5T120-200v-152q48 0 84-30.5t36-77.5q0-47-36-77.5T120-568v-152q0-33 23.5-56.5T200-800h160q0-42 29-71t71-29q42 0 71 29t29 71h160q33 0 56.5 23.5T800-720v160q42 0 71 29t29 71q0 42-29 71t-71 29v160q0 33-23.5 56.5T720-120H568q0-50-31.5-85T460-240q-45 0-76.5 35T352-120Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/extension.svg b/app/javascript/material-icons/400-24px/extension.svg
deleted file mode 100644
index 16909a6307..0000000000
--- a/app/javascript/material-icons/400-24px/extension.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M352-120H200q-33 0-56.5-23.5T120-200v-152q48 0 84-30.5t36-77.5q0-47-36-77.5T120-568v-152q0-33 23.5-56.5T200-800h160q0-42 29-71t71-29q42 0 71 29t29 71h160q33 0 56.5 23.5T800-720v160q42 0 71 29t29 71q0 42-29 71t-71 29v160q0 33-23.5 56.5T720-120H568q0-50-31.5-85T460-240q-45 0-76.5 35T352-120Zm-152-80h85q24-66 77-93t98-27q45 0 98 27t77 93h85v-240h80q8 0 14-6t6-14q0-8-6-14t-14-6h-80v-240H480v-80q0-8-6-14t-14-6q-8 0-14 6t-6 14v80H200v88q54 20 87 67t33 105q0 57-33 104t-87 68v88Zm260-260Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/forward_5-fill.svg b/app/javascript/material-icons/400-24px/forward_5-fill.svg
deleted file mode 100644
index bc0119a640..0000000000
--- a/app/javascript/material-icons/400-24px/forward_5-fill.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-80q-75 0-140.5-28.5t-114-77q-48.5-48.5-77-114T120-440q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-800h6l-62-62 56-58 160 160-160 160-56-58 62-62h-6q-117 0-198.5 81.5T200-440q0 117 81.5 198.5T480-160q117 0 198.5-81.5T760-440h80q0 75-28.5 140.5t-77 114q-48.5 48.5-114 77T480-80ZM380-320v-60h120v-40H380v-140h180v60H440v40h80q17 0 28.5 11.5T560-420v60q0 17-11.5 28.5T520-320H380Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/forward_5.svg b/app/javascript/material-icons/400-24px/forward_5.svg
deleted file mode 100644
index bc0119a640..0000000000
--- a/app/javascript/material-icons/400-24px/forward_5.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-80q-75 0-140.5-28.5t-114-77q-48.5-48.5-77-114T120-440q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-800h6l-62-62 56-58 160 160-160 160-56-58 62-62h-6q-117 0-198.5 81.5T200-440q0 117 81.5 198.5T480-160q117 0 198.5-81.5T760-440h80q0 75-28.5 140.5t-77 114q-48.5 48.5-114 77T480-80ZM380-320v-60h120v-40H380v-140h180v60H440v40h80q17 0 28.5 11.5T560-420v60q0 17-11.5 28.5T520-320H380Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/public-fill.svg b/app/javascript/material-icons/400-24px/public-fill.svg
index 104f26e133..1e9e79de4d 100644
--- a/app/javascript/material-icons/400-24px/public-fill.svg
+++ b/app/javascript/material-icons/400-24px/public-fill.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm-40-82v-78q-33 0-56.5-23.5T360-320v-40L168-552q-3 18-5.5 36t-2.5 36q0 121 79.5 212T440-162Zm276-102q41-45 62.5-100.5T800-480q0-98-54.5-179T600-776v16q0 33-23.5 56.5T520-680h-80v80q0 17-11.5 28.5T400-560h-80v80h240q17 0 28.5 11.5T600-440v120h40q26 0 47 15.5t29 40.5Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm-40-82v-78q-33 0-56.5-23.5T360-320v-40L168-552q-3 18-5.5 36t-2.5 36q0 121 79.5 212T440-162Zm276-102q20-22 36-47.5t26.5-53q10.5-27.5 16-56.5t5.5-59q0-98-54.5-179T600-776v16q0 33-23.5 56.5T520-680h-80v80q0 17-11.5 28.5T400-560h-80v80h240q17 0 28.5 11.5T600-440v120h40q26 0 47 15.5t29 40.5Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/public.svg b/app/javascript/material-icons/400-24px/public.svg
index 104f26e133..1e9e79de4d 100644
--- a/app/javascript/material-icons/400-24px/public.svg
+++ b/app/javascript/material-icons/400-24px/public.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm-40-82v-78q-33 0-56.5-23.5T360-320v-40L168-552q-3 18-5.5 36t-2.5 36q0 121 79.5 212T440-162Zm276-102q41-45 62.5-100.5T800-480q0-98-54.5-179T600-776v16q0 33-23.5 56.5T520-680h-80v80q0 17-11.5 28.5T400-560h-80v80h240q17 0 28.5 11.5T600-440v120h40q26 0 47 15.5t29 40.5Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm-40-82v-78q-33 0-56.5-23.5T360-320v-40L168-552q-3 18-5.5 36t-2.5 36q0 121 79.5 212T440-162Zm276-102q20-22 36-47.5t26.5-53q10.5-27.5 16-56.5t5.5-59q0-98-54.5-179T600-776v16q0 33-23.5 56.5T520-680h-80v80q0 17-11.5 28.5T400-560h-80v80h240q17 0 28.5 11.5T600-440v120h40q26 0 47 15.5t29 40.5Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/question_mark-fill.svg b/app/javascript/material-icons/400-24px/question_mark-fill.svg
deleted file mode 100644
index 9b02086c4d..0000000000
--- a/app/javascript/material-icons/400-24px/question_mark-fill.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M424-320q0-81 14.5-116.5T500-514q41-36 62.5-62.5T584-637q0-41-27.5-68T480-732q-51 0-77.5 31T365-638l-103-44q21-64 77-111t141-47q105 0 161.5 58.5T698-641q0 50-21.5 85.5T609-475q-49 47-59.5 71.5T539-320H424Zm56 240q-33 0-56.5-23.5T400-160q0-33 23.5-56.5T480-240q33 0 56.5 23.5T560-160q0 33-23.5 56.5T480-80Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/question_mark.svg b/app/javascript/material-icons/400-24px/question_mark.svg
deleted file mode 100644
index 9b02086c4d..0000000000
--- a/app/javascript/material-icons/400-24px/question_mark.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M424-320q0-81 14.5-116.5T500-514q41-36 62.5-62.5T584-637q0-41-27.5-68T480-732q-51 0-77.5 31T365-638l-103-44q21-64 77-111t141-47q105 0 161.5 58.5T698-641q0 50-21.5 85.5T609-475q-49 47-59.5 71.5T539-320H424Zm56 240q-33 0-56.5-23.5T400-160q0-33 23.5-56.5T480-240q33 0 56.5 23.5T560-160q0 33-23.5 56.5T480-80Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/replay_5-fill.svg b/app/javascript/material-icons/400-24px/replay_5-fill.svg
deleted file mode 100644
index c0c259829e..0000000000
--- a/app/javascript/material-icons/400-24px/replay_5-fill.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-80q-75 0-140.5-28.5t-114-77q-48.5-48.5-77-114T120-440h80q0 117 81.5 198.5T480-160q117 0 198.5-81.5T760-440q0-117-81.5-198.5T480-720h-6l62 62-56 58-160-160 160-160 56 58-62 62h6q75 0 140.5 28.5t114 77q48.5 48.5 77 114T840-440q0 75-28.5 140.5t-77 114q-48.5 48.5-114 77T480-80ZM380-320v-60h120v-40H380v-140h180v60H440v40h80q17 0 28.5 11.5T560-420v60q0 17-11.5 28.5T520-320H380Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/replay_5.svg b/app/javascript/material-icons/400-24px/replay_5.svg
deleted file mode 100644
index c0c259829e..0000000000
--- a/app/javascript/material-icons/400-24px/replay_5.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-80q-75 0-140.5-28.5t-114-77q-48.5-48.5-77-114T120-440h80q0 117 81.5 198.5T480-160q117 0 198.5-81.5T760-440q0-117-81.5-198.5T480-720h-6l62 62-56 58-160-160 160-160 56 58-62 62h6q75 0 140.5 28.5t114 77q48.5 48.5 77 114T840-440q0 75-28.5 140.5t-77 114q-48.5 48.5-114 77T480-80ZM380-320v-60h120v-40H380v-140h180v60H440v40h80q17 0 28.5 11.5T560-420v60q0 17-11.5 28.5T520-320H380Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/volume_down-fill.svg b/app/javascript/material-icons/400-24px/volume_down-fill.svg
deleted file mode 100644
index 26ec2b09ab..0000000000
--- a/app/javascript/material-icons/400-24px/volume_down-fill.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-360v-240h160l200-200v640L360-360H200Zm440 40v-322q45 21 72.5 65t27.5 97q0 53-27.5 96T640-320Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/volume_down.svg b/app/javascript/material-icons/400-24px/volume_down.svg
deleted file mode 100644
index a3fbc41002..0000000000
--- a/app/javascript/material-icons/400-24px/volume_down.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-360v-240h160l200-200v640L360-360H200Zm440 40v-322q45 21 72.5 65t27.5 97q0 53-27.5 96T640-320ZM480-606l-86 86H280v80h114l86 86v-252ZM380-480Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss
index b328d8ee34..465b748078 100644
--- a/app/javascript/styles/application.scss
+++ b/app/javascript/styles/application.scss
@@ -1,27 +1,24 @@
-@use 'mastodon/functions';
-@use 'mastodon/mixins';
-@use 'mastodon/variables';
-@use 'mastodon/css_variables';
-@use 'fonts/roboto';
-@use 'fonts/roboto-mono';
+@import 'mastodon/mixins';
+@import 'mastodon/variables';
+@import 'fonts/roboto';
+@import 'fonts/roboto-mono';
 
-@use 'mastodon/reset';
-@use 'mastodon/basics';
-@use 'mastodon/branding';
-@use 'mastodon/containers';
-@use 'mastodon/lists';
-@use 'mastodon/widgets';
-@use 'mastodon/forms';
-@use 'mastodon/accounts';
-@use 'mastodon/components';
-@use 'mastodon/polls';
-@use 'mastodon/modal';
-@use 'mastodon/emoji_picker';
-@use 'mastodon/annual_reports';
-@use 'mastodon/about';
-@use 'mastodon/tables';
-@use 'mastodon/admin';
-@use 'mastodon/dashboard';
-@use 'mastodon/rtl';
-@use 'mastodon/accessibility';
-@use 'mastodon/rich_text';
+@import 'mastodon/reset';
+@import 'mastodon/basics';
+@import 'mastodon/branding';
+@import 'mastodon/containers';
+@import 'mastodon/lists';
+@import 'mastodon/widgets';
+@import 'mastodon/forms';
+@import 'mastodon/accounts';
+@import 'mastodon/components';
+@import 'mastodon/polls';
+@import 'mastodon/modal';
+@import 'mastodon/emoji_picker';
+@import 'mastodon/about';
+@import 'mastodon/tables';
+@import 'mastodon/admin';
+@import 'mastodon/dashboard';
+@import 'mastodon/rtl';
+@import 'mastodon/accessibility';
+@import 'mastodon/rich_text';
diff --git a/app/javascript/styles/contrast.scss b/app/javascript/styles/contrast.scss
index 367be051f1..5b43aecbe7 100644
--- a/app/javascript/styles/contrast.scss
+++ b/app/javascript/styles/contrast.scss
@@ -1,3 +1,3 @@
-@use 'contrast/variables';
-@use 'application';
-@use 'contrast/diff';
+@import 'contrast/variables';
+@import 'application';
+@import 'contrast/diff';
diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss
index 8aa05dd8ef..ae607f484a 100644
--- a/app/javascript/styles/contrast/diff.scss
+++ b/app/javascript/styles/contrast/diff.scss
@@ -1,5 +1,3 @@
-@use '../mastodon/variables' as *;
-
 .status__content a,
 .reply-indicator__content a,
 .edit-indicator__content a,
diff --git a/app/javascript/styles/contrast/variables.scss b/app/javascript/styles/contrast/variables.scss
index d63512ce43..2bee5eca74 100644
--- a/app/javascript/styles/contrast/variables.scss
+++ b/app/javascript/styles/contrast/variables.scss
@@ -1,5 +1,3 @@
-@use '../mastodon/functions' as *;
-
 // Dependent colors
 $black: #000000;
 
@@ -16,13 +14,12 @@ $ui-primary-color: $classic-primary-color !default;
 $ui-secondary-color: $classic-secondary-color !default;
 $ui-highlight-color: $classic-highlight-color !default;
 
-@use '../mastodon/variables' with (
-  $darker-text-color: lighten($ui-primary-color, 20%),
-  $dark-text-color: lighten($ui-primary-color, 12%),
-  $secondary-text-color: lighten($ui-secondary-color, 6%),
-  $highlight-text-color: lighten($ui-highlight-color, 10%),
-  $action-button-color: lighten($ui-base-color, 50%),
-  $inverted-text-color: $black,
-  $lighter-text-color: darken($ui-base-color, 6%),
-  $light-text-color: darken($ui-primary-color, 40%)
-);
+$darker-text-color: lighten($ui-primary-color, 20%) !default;
+$dark-text-color: lighten($ui-primary-color, 12%) !default;
+$secondary-text-color: lighten($ui-secondary-color, 6%) !default;
+$highlight-text-color: lighten($ui-highlight-color, 10%) !default;
+$action-button-color: lighten($ui-base-color, 50%);
+
+$inverted-text-color: $black !default;
+$lighter-text-color: darken($ui-base-color, 6%) !default;
+$light-text-color: darken($ui-primary-color, 40%) !default;
diff --git a/app/javascript/styles/full-dark.scss b/app/javascript/styles/full-dark.scss
index 33195d5c6e..105964ba6f 100644
--- a/app/javascript/styles/full-dark.scss
+++ b/app/javascript/styles/full-dark.scss
@@ -1,4 +1,3 @@
-@use 'full-dark/variables';
-@use 'full-dark/css_variables';
-@use 'application';
-@use 'full-dark/diff';
+@import 'full-dark/variables';
+@import 'application';
+@import 'full-dark/diff';
diff --git a/app/javascript/styles/full-dark/css_variables.scss b/app/javascript/styles/full-dark/css_variables.scss
deleted file mode 100644
index 56e63dd23b..0000000000
--- a/app/javascript/styles/full-dark/css_variables.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-@use 'sass:color';
-@use '../mastodon/variables' as *;
-@use 'variables' as *;
-@use '../mastodon/functions' as *;
diff --git a/app/javascript/styles/full-dark/diff.scss b/app/javascript/styles/full-dark/diff.scss
index 727072dc39..9483e7ecb6 100644
--- a/app/javascript/styles/full-dark/diff.scss
+++ b/app/javascript/styles/full-dark/diff.scss
@@ -1,7 +1,3 @@
-@use 'sass:color';
-@use '../mastodon/functions' as *;
-@use '../mastodon/variables' as *;
-
 input[type='text']:not(#cw-spoiler-input),
 input[type='search'],
 input[type='number'],
diff --git a/app/javascript/styles/full-dark/variables.scss b/app/javascript/styles/full-dark/variables.scss
index 1720d716fe..6cedec7df9 100644
--- a/app/javascript/styles/full-dark/variables.scss
+++ b/app/javascript/styles/full-dark/variables.scss
@@ -1,14 +1,11 @@
 $classic-base-color: #282c37; // Midnight Express
 $classic-secondary-color: #d9e1e8; // Pattens Blue
 
-@use '../mastodon/variables' with (
-  // Variables for defaults in UI
-  $simple-background-color: $classic-base-color,
+// Variables for defaults in UI
+$simple-background-color: $classic-base-color !default;
 
-  // Tell UI to use selected colors
-  $ui-base-lighter-color: #969fbc,
+// Tell UI to use selected colors
+$ui-base-lighter-color: #969fbc !default; // Lighter darkest
 
-  // Lighter darkest
-  // For texts on inverted backgrounds
-  $inverted-text-color: $classic-secondary-color
-);
+// For texts on inverted backgrounds
+$inverted-text-color: $classic-secondary-color !default;
diff --git a/app/javascript/styles/mailer.scss b/app/javascript/styles/mailer.scss
index 1e339b4313..f46160889a 100644
--- a/app/javascript/styles/mailer.scss
+++ b/app/javascript/styles/mailer.scss
@@ -1,4 +1,4 @@
-@use 'fonts/inter';
+@import 'fonts/inter';
 
 body {
   accent-color: #6364ff;
@@ -173,9 +173,7 @@ table + p {
 }
 
 .email-prose {
-  p,
-  ul,
-  ol {
+  p {
     color: #17063b;
     font-size: 14px;
     line-height: 20px;
diff --git a/app/javascript/styles/mastodon-light.scss b/app/javascript/styles/mastodon-light.scss
index b530616a3c..756a12d868 100644
--- a/app/javascript/styles/mastodon-light.scss
+++ b/app/javascript/styles/mastodon-light.scss
@@ -1,4 +1,3 @@
-@use 'mastodon-light/variables';
-@use 'mastodon-light/css_variables';
-@use 'application';
-@use 'mastodon-light/diff';
+@import 'mastodon-light/variables';
+@import 'application';
+@import 'mastodon-light/diff';
diff --git a/app/javascript/styles/mastodon-light/css_variables.scss b/app/javascript/styles/mastodon-light/css_variables.scss
deleted file mode 100644
index d9311da1b9..0000000000
--- a/app/javascript/styles/mastodon-light/css_variables.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-@use 'sass:color';
-@use '../mastodon/variables' as *;
-@use 'variables' as *;
-@use '../mastodon/functions' as *;
-
-body {
-  --dropdown-border-color: hsl(240deg, 25%, 88%);
-  --dropdown-background-color: #fff;
-  --modal-border-color: hsl(240deg, 25%, 88%);
-  --modal-background-color: var(--background-color-tint);
-  --background-border-color: hsl(240deg, 25%, 88%);
-  --background-color: #fff;
-  --background-color-tint: rgba(255, 255, 255, 80%);
-  --background-filter: blur(10px);
-  --on-surface-color: #{color.adjust($ui-base-color, $alpha: -0.65)};
-  --rich-text-container-color: rgba(255, 216, 231, 100%);
-  --rich-text-text-color: rgba(114, 47, 83, 100%);
-  --rich-text-decorations-color: rgba(255, 175, 212, 100%);
-  --input-placeholder-color: #{color.adjust($dark-text-color, $alpha: -0.5)};
-  --input-background-color: #{darken($ui-base-color, 10%)};
-}
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index 8ca860a86d..45da56994c 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -1,17 +1,8 @@
 // Notes!
 // Sass color functions, "darken" and "lighten" are automatically replaced.
-@use 'sass:color';
-@use '../mastodon/functions' as *;
-@use '../mastodon/variables' as *;
 
 .simple_form .button.button-tertiary {
   color: $highlight-text-color;
-
-  &:hover,
-  &:focus,
-  &:active {
-    color: $white;
-  }
 }
 
 .status-card__actions button,
@@ -117,6 +108,17 @@
   background: lighten($white, 4%);
 }
 
+// Change the background colors of status__content__spoiler-link
+.reply-indicator__content .status__content__spoiler-link,
+.status__content .status__content__spoiler-link {
+  background: $ui-base-color;
+
+  &:hover,
+  &:focus {
+    background: lighten($ui-base-color, 4%);
+  }
+}
+
 .account-gallery__item a {
   background-color: $ui-base-color;
 }
@@ -130,7 +132,9 @@
 .actions-modal ul li:not(:empty) a:focus button,
 .actions-modal ul li:not(:empty) a:hover,
 .actions-modal ul li:not(:empty) a:hover button,
-.simple_form button:not(.button, .link-button) {
+.simple_form .block-button,
+.simple_form .button,
+.simple_form button {
   color: $white;
 }
 
@@ -143,11 +147,6 @@
   border-top-color: lighten($ui-base-color, 4%);
 }
 
-.dialog-modal__content__preview {
-  background: #fff;
-  border-bottom: 1px solid var(--modal-border-color);
-}
-
 .reactions-bar__item:hover,
 .reactions-bar__item:focus,
 .reactions-bar__item:active {
@@ -155,12 +154,8 @@
 }
 
 .reactions-bar__item.active {
-  background-color: color.mix($white, $ui-highlight-color, 80%);
-  border-color: color.mix(
-    lighten($ui-base-color, 8%),
-    $ui-highlight-color,
-    80%
-  );
+  background-color: mix($white, $ui-highlight-color, 80%);
+  border-color: mix(lighten($ui-base-color, 8%), $ui-highlight-color, 80%);
 }
 
 .media-modal__overlay .picture-in-picture__footer {
@@ -249,7 +244,7 @@
 
 // Change the default colors used on some parts of the profile pages
 .activity-stream-tabs {
-  background: $white;
+  background: $account-background-color;
   border-bottom-color: lighten($ui-base-color, 8%);
 }
 
@@ -291,7 +286,7 @@
   }
 
   .entry {
-    background: $white;
+    background: $account-background-color;
 
     .detailed-status.light,
     .more.light,
@@ -452,35 +447,17 @@
   color: lighten($ui-highlight-color, 8%);
 }
 
+.compose-form .autosuggest-textarea__textarea,
+.compose-form__highlightable,
+.autosuggest-textarea__suggestions,
+.search__input,
+.search__popout,
 .emoji-mart-search input,
 .language-dropdown__dropdown .emoji-mart-search input,
 .poll__option input[type='text'] {
   background: darken($ui-base-color, 10%);
 }
 
-.dropdown-button.warning {
-  border-color: #b3261e;
-  color: #b3261e;
-
-  &.active {
-    background-color: #f9dedc;
-  }
-}
-
-.search__popout__menu__item {
-  &:hover,
-  &:active,
-  &:focus,
-  &.active {
-    color: $white;
-
-    mark,
-    .icon-button {
-      color: $white;
-    }
-  }
-}
-
 .inline-follow-suggestions {
   background-color: rgba($ui-highlight-color, 0.1);
   border-bottom-color: rgba($ui-highlight-color, 0.3);
@@ -548,13 +525,6 @@ a.sparkline {
   }
 }
 
-.notification-group--annual-report {
-  .notification-group__icon,
-  .notification-group__main .link-button {
-    color: var(--indigo-3);
-  }
-}
-
 @supports not selector(::-webkit-scrollbar) {
   html {
     scrollbar-color: rgba($action-button-color, 0.25)
@@ -562,8 +532,6 @@ a.sparkline {
   }
 }
 
-.custom-scrollbars {
-  ::-webkit-scrollbar-thumb {
-    opacity: 0.25;
-  }
+::-webkit-scrollbar-thumb {
+  opacity: 0.25;
 }
diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss
index 5a32e2058e..9a85b3bf69 100644
--- a/app/javascript/styles/mastodon-light/variables.scss
+++ b/app/javascript/styles/mastodon-light/variables.scss
@@ -1,49 +1,82 @@
 @use 'sass:color';
 
-@use '../mastodon/functions' with (
-  $darken-multiplier: 1,
-  $lighten-multiplier: -1
-);
+// Dependent colors
+$black: #000000;
+$white: #ffffff;
 
-$black: #000000; // Black
-$white: #ffffff; // White
-$blurple-500: #6364ff; // Brand purple
+$classic-base-color: hsl(240deg, 16%, 19%);
+$classic-primary-color: hsl(240deg, 29%, 70%);
+$classic-secondary-color: hsl(255deg, 25%, 88%);
+$classic-highlight-color: hsl(240deg, 100%, 69%);
+
+$blurple-600: hsl(252deg, 59%, 51%); // Iris
+$blurple-500: hsl(240deg, 100%, 69%); // Brand purple
+$blurple-300: hsl(237deg, 92%, 75%); // Faded Blue
 $grey-600: hsl(240deg, 8%, 33%); // Trout
 $grey-100: hsl(240deg, 51%, 90%); // Topaz
 
-$classic-base-color: hsl(240deg, 16%, 19%);
-$classic-secondary-color: hsl(255deg, 25%, 88%);
-$classic-highlight-color: $blurple-500;
+$emoji-reaction-color: #dfe5f5 !default;
+$emoji-reaction-selected-color: #9ac1f2 !default;
 
-@use '../mastodon/variables' with (
-  $success-green: color.adjust(
-      hsl(138deg, 32%, 35%),
-      $lightness: 8%,
-      $space: hsl
-    ),
-  $base-overlay-background: $white,
+// Differences
+$success-green: lighten(hsl(138deg, 32%, 35%), 8%);
 
-  $ui-base-color: $classic-secondary-color,
-  $ui-base-lighter-color: hsl(250deg, 24%, 75%),
-  $ui-secondary-color: $classic-base-color,
+$base-overlay-background: $white !default;
+$valid-value-color: $success-green !default;
 
-  $ui-button-secondary-color: $grey-600,
-  $ui-button-secondary-border-color: $grey-600,
-  $ui-button-secondary-focus-color: $white,
-  $ui-button-tertiary-color: $blurple-500,
-  $ui-button-tertiary-border-color: $blurple-500,
+$ui-base-color: $classic-secondary-color !default;
+$ui-base-lighter-color: hsl(250deg, 24%, 75%);
+$ui-primary-color: $classic-primary-color !default;
+$ui-secondary-color: $classic-base-color !default;
+$ui-highlight-color: $classic-highlight-color !default;
 
-  $primary-text-color: $black,
-  $darker-text-color: $classic-base-color,
-  $lighter-text-color: $classic-base-color,
-  $highlight-text-color: $classic-highlight-color,
-  $dark-text-color: hsl(240deg, 16%, 32%),
-  $light-text-color: hsl(240deg, 16%, 32%),
-  $inverted-text-color: $black,
+$ui-button-secondary-color: $grey-600 !default;
+$ui-button-secondary-border-color: $grey-600 !default;
+$ui-button-secondary-focus-color: $white !default;
 
-  $action-button-color: hsl(240deg, 16%, 45%),
-  $emojis-requiring-inversion: 'chains',
+$ui-button-tertiary-color: $blurple-500 !default;
+$ui-button-tertiary-border-color: $blurple-500 !default;
 
-  $emoji-reaction-color: #dfe5f5,
-  $emoji-reaction-selected-color: #9ac1f2
-);
+$primary-text-color: $black !default;
+$darker-text-color: $classic-base-color !default;
+$highlight-text-color: $ui-highlight-color !default;
+$dark-text-color: hsl(240deg, 16%, 32%);
+$action-button-color: hsl(240deg, 16%, 45%);
+
+$inverted-text-color: $black !default;
+$lighter-text-color: $classic-base-color !default;
+$light-text-color: hsl(240deg, 16%, 32%);
+
+// Newly added colors
+$account-background-color: $white !default;
+
+// Invert darkened and lightened colors
+@function darken($color, $amount) {
+  @return hsl(
+    hue($color),
+    color.channel($color, 'saturation', $space: hsl),
+    color.channel($color, 'lightness', $space: hsl) + $amount
+  );
+}
+
+@function lighten($color, $amount) {
+  @return hsl(
+    hue($color),
+    color.channel($color, 'saturation', $space: hsl),
+    color.channel($color, 'lightness', $space: hsl) - $amount
+  );
+}
+
+$emojis-requiring-inversion: 'chains';
+
+body {
+  --dropdown-border-color: hsl(240deg, 25%, 88%);
+  --dropdown-background-color: #fff;
+  --modal-border-color: hsl(240deg, 25%, 88%);
+  --modal-background-color: var(--background-color-tint);
+  --background-border-color: hsl(240deg, 25%, 88%);
+  --background-color: #fff;
+  --background-color-tint: rgba(255, 255, 255, 80%);
+  --background-filter: blur(10px);
+  --on-surface-color: #{transparentize($ui-base-color, 0.65)};
+}
diff --git a/app/javascript/styles/mastodon/_functions.scss b/app/javascript/styles/mastodon/_functions.scss
deleted file mode 100644
index 7190a6233e..0000000000
--- a/app/javascript/styles/mastodon/_functions.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-@use 'sass:color';
-
-$darken-multiplier: -1 !default;
-$lighten-multiplier: 1 !default;
-
-// Invert darkened and lightened colors
-@function darken($color, $amount) {
-  @return color.adjust(
-    $color,
-    $lightness: $amount * $darken-multiplier,
-    $space: hsl
-  );
-}
-
-@function lighten($color, $amount) {
-  @return color.adjust(
-    $color,
-    $lightness: $amount * $lighten-multiplier,
-    $space: hsl
-  );
-}
diff --git a/app/javascript/styles/mastodon/_mixins.scss b/app/javascript/styles/mastodon/_mixins.scss
index b7d9203e3f..c2e4acba4d 100644
--- a/app/javascript/styles/mastodon/_mixins.scss
+++ b/app/javascript/styles/mastodon/_mixins.scss
@@ -1,12 +1,10 @@
-@use 'variables' as *;
-
 @mixin search-input {
   outline: 0;
   box-sizing: border-box;
   width: 100%;
   box-shadow: none;
   font-family: inherit;
-  background: var(--input-background-color);
+  background: $ui-base-color;
   color: $darker-text-color;
   border-radius: 4px;
   border: 1px solid var(--background-border-color);
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index a310e2ffe7..42f1fda0f9 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -1,5 +1,3 @@
-@use 'variables' as *;
-
 $maximum-width: 1235px;
 $fluid-breakpoint: $maximum-width + 20px;
 
@@ -23,6 +21,7 @@ $fluid-breakpoint: $maximum-width + 20px;
 .rules-list {
   font-size: 15px;
   line-height: 22px;
+  color: $primary-text-color;
   counter-reset: list-counter;
 
   li {
@@ -32,41 +31,13 @@ $fluid-breakpoint: $maximum-width + 20px;
     padding-inline-start: 3em;
     font-weight: 500;
     counter-increment: list-counter;
-    min-height: 4ch;
-
-    button {
-      background: transparent;
-      border: 0;
-      padding: 0;
-      margin: 0;
-      text-align: start;
-      font: inherit;
-
-      &:hover,
-      &:focus,
-      &:active {
-        background: transparent;
-      }
-
-      &[aria-expanded='false'] .rules-list__hint {
-        white-space: nowrap;
-        overflow: hidden;
-        text-overflow: ellipsis;
-
-        @supports (-webkit-line-clamp: 2) {
-          display: -webkit-box;
-          -webkit-line-clamp: 2;
-          -webkit-box-orient: vertical;
-          white-space: normal;
-        }
-      }
-    }
 
     &::before {
       content: counter(list-counter);
       position: absolute;
       inset-inline-start: 0;
-      top: 1em;
+      top: 50%;
+      transform: translateY(-50%);
       background: $highlight-text-color;
       color: $ui-base-color;
       border-radius: 50%;
@@ -83,10 +54,6 @@ $fluid-breakpoint: $maximum-width + 20px;
     }
   }
 
-  &__text {
-    color: $primary-text-color;
-  }
-
   &__hint {
     font-size: 14px;
     font-weight: 400;
diff --git a/app/javascript/styles/mastodon/accessibility.scss b/app/javascript/styles/mastodon/accessibility.scss
index 7cd2d4eae3..deaa0afdac 100644
--- a/app/javascript/styles/mastodon/accessibility.scss
+++ b/app/javascript/styles/mastodon/accessibility.scss
@@ -1,4 +1,7 @@
-@use 'variables' as *;
+$emojis-requiring-inversion: 'back' 'copyright' 'curly_loop' 'currency_exchange'
+  'end' 'heavy_check_mark' 'heavy_division_sign' 'heavy_dollar_sign'
+  'heavy_minus_sign' 'heavy_multiplication_x' 'heavy_plus_sign' 'on'
+  'registered' 'soon' 'spider' 'telephone_receiver' 'tm' 'top' 'wavy_dash' !default;
 
 %emoji-color-inversion {
   filter: invert(1);
diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss
index 34d4e840ef..89778b9f3b 100644
--- a/app/javascript/styles/mastodon/accounts.scss
+++ b/app/javascript/styles/mastodon/accounts.scss
@@ -1,6 +1,3 @@
-@use 'variables' as *;
-@use 'functions' as *;
-
 .card {
   & > a {
     display: block;
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index c3035f946f..5c108a2e6c 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -1,6 +1,4 @@
 @use 'sass:math';
-@use 'functions' as *;
-@use 'variables' as *;
 
 $no-columns-breakpoint: 890px;
 $sidebar-width: 300px;
@@ -255,10 +253,6 @@ $content-width: 840px;
         .time-period {
           padding: 0 10px;
         }
-
-        .back-link {
-          margin-bottom: 0;
-        }
       }
 
       h2 small {
@@ -617,6 +611,16 @@ body,
   max-width: 100%;
 }
 
+.simple_form {
+  .actions {
+    margin-top: 15px;
+  }
+
+  .button {
+    font-size: 15px;
+  }
+}
+
 .batch-form-box {
   display: flex;
   flex-wrap: wrap;
@@ -1925,109 +1929,3 @@ a.sparkline {
     }
   }
 }
-
-.status__card {
-  padding: 15px;
-  border-radius: 4px;
-  background: $ui-base-color;
-  font-size: 15px;
-  line-height: 20px;
-  word-wrap: break-word;
-  font-weight: 400;
-  border: 1px solid lighten($ui-base-color, 4%);
-  color: $primary-text-color;
-  box-sizing: border-box;
-  min-height: 100%;
-
-  .status__prepend {
-    padding: 0 0 15px;
-    gap: 4px;
-    align-items: center;
-  }
-
-  .status__content {
-    padding-top: 0;
-
-    summary {
-      display: list-item;
-    }
-  }
-}
-
-.admin {
-  &__terms-of-service {
-    &__container {
-      background: var(--surface-background-color);
-      border-radius: 8px;
-      border: 1px solid var(--background-border-color);
-      overflow: hidden;
-
-      &__header {
-        padding: 16px;
-        font-size: 14px;
-        line-height: 20px;
-        color: $secondary-text-color;
-        display: flex;
-        align-items: center;
-        gap: 12px;
-      }
-
-      &__body {
-        background: var(--background-color);
-        padding: 16px;
-        overflow-y: scroll;
-        height: 30vh;
-      }
-    }
-
-    &__history {
-      & > li {
-        border-bottom: 1px solid var(--background-border-color);
-
-        &:last-child {
-          border-bottom: 0;
-        }
-      }
-
-      &__item {
-        padding: 16px 0;
-        padding-bottom: 8px;
-
-        h5 {
-          font-size: 14px;
-          line-height: 20px;
-          font-weight: 600;
-          margin-bottom: 16px;
-
-          a {
-            color: inherit;
-            text-decoration: none;
-          }
-        }
-      }
-    }
-  }
-}
-
-.dot-indicator {
-  display: inline-flex;
-  align-items: center;
-  gap: 8px;
-  font-weight: 500;
-
-  &__indicator {
-    display: inline-block;
-    width: 8px;
-    height: 8px;
-    border-radius: 50%;
-    background: $dark-text-color;
-  }
-
-  &.success {
-    color: $valid-value-color;
-
-    .dot-indicator__indicator {
-      background-color: $valid-value-color;
-    }
-  }
-}
diff --git a/app/javascript/styles/mastodon/annual_reports.scss b/app/javascript/styles/mastodon/annual_reports.scss
deleted file mode 100644
index 96500a18bb..0000000000
--- a/app/javascript/styles/mastodon/annual_reports.scss
+++ /dev/null
@@ -1,342 +0,0 @@
-@use 'variables' as *;
-
-:root {
-  --indigo-1: #17063b;
-  --indigo-2: #2f0c7a;
-  --indigo-3: #562cfc;
-  --indigo-5: #858afa;
-  --indigo-6: #cccfff;
-  --lime: #baff3b;
-  --goldenrod-2: #ffc954;
-}
-
-.annual-report {
-  flex: 0 0 auto;
-  background: var(--indigo-1);
-  padding: 24px;
-
-  &__header {
-    margin-bottom: 16px;
-
-    h1 {
-      font-size: 25px;
-      font-weight: 600;
-      line-height: 30px;
-      color: var(--lime);
-      margin-bottom: 8px;
-    }
-
-    p {
-      font-size: 16px;
-      font-weight: 600;
-      line-height: 20px;
-      color: var(--indigo-6);
-    }
-  }
-
-  &__bento {
-    display: grid;
-    gap: 8px;
-    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
-    grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto) minmax(
-        0,
-        auto
-      );
-
-    &__box {
-      padding: 16px;
-      border-radius: 8px;
-      background: var(--indigo-2);
-      color: var(--indigo-5);
-    }
-  }
-
-  &__summary {
-    &__most-boosted-post {
-      grid-column: span 2;
-      grid-row: span 2;
-      padding: 0;
-
-      .status__content,
-      .content-warning {
-        color: var(--indigo-6);
-      }
-
-      .detailed-status {
-        border: 0;
-      }
-
-      .content-warning {
-        border: 0;
-        background: var(--indigo-1);
-
-        .link-button {
-          color: var(--indigo-5);
-        }
-      }
-
-      .detailed-status__meta__line {
-        border-bottom-color: var(--indigo-3);
-      }
-
-      .detailed-status__meta {
-        text-overflow: ellipsis;
-        overflow: hidden;
-        white-space: nowrap;
-      }
-
-      .detailed-status__meta,
-      .poll__footer,
-      .poll__link,
-      .detailed-status .logo,
-      .detailed-status__display-name {
-        color: var(--indigo-5);
-      }
-
-      .detailed-status__meta .animated-number,
-      .detailed-status__display-name strong {
-        color: var(--indigo-6);
-      }
-
-      .poll__chart {
-        background-color: var(--indigo-3);
-
-        &.leading {
-          background-color: var(--goldenrod-2);
-        }
-      }
-
-      .status-card,
-      .hashtag-bar {
-        display: none;
-      }
-    }
-
-    &__followers {
-      grid-column: span 1;
-      text-align: center;
-      position: relative;
-      overflow: hidden;
-      padding-block-start: 24px;
-      padding-block-end: 24px;
-
-      --sparkline-gradient-top: rgba(86, 44, 252, 50%);
-      --sparkline-gradient-bottom: rgba(86, 44, 252, 0%);
-
-      &__foreground {
-        width: 100%;
-        height: 100%;
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        justify-content: center;
-        gap: 8px;
-        position: relative;
-        z-index: 1;
-      }
-
-      &__number {
-        font-size: 31px;
-        font-weight: 600;
-        line-height: 37px;
-        color: var(--lime);
-      }
-
-      &__label {
-        font-size: 14px;
-        font-weight: 600;
-        line-height: 17px;
-        color: var(--indigo-6);
-      }
-
-      &__footnote {
-        display: block;
-        font-weight: 400;
-        opacity: 0.5;
-      }
-
-      svg {
-        position: absolute;
-        bottom: 0;
-        inset-inline-end: 0;
-        pointer-events: none;
-        z-index: 0;
-        height: 70%;
-        width: auto;
-
-        path:first-child {
-          fill: url('#gradient') !important;
-          fill-opacity: 1 !important;
-        }
-
-        path:last-child {
-          stroke: var(--indigo-3) !important;
-          fill: none !important;
-        }
-      }
-    }
-
-    &__archetype {
-      grid-column: span 1;
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: center;
-      text-align: center;
-      gap: 8px;
-      padding: 0;
-
-      img {
-        display: block;
-        width: 100%;
-        height: auto;
-        border-radius: 8px;
-      }
-
-      &__label {
-        padding: 16px;
-        padding-bottom: 8px;
-        font-size: 14px;
-        line-height: 17px;
-        font-weight: 600;
-        color: var(--lime);
-      }
-    }
-
-    &__most-used-app {
-      grid-column: span 1;
-      text-align: center;
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: center;
-      gap: 8px;
-      box-sizing: border-box;
-
-      &__label {
-        font-size: 14px;
-        line-height: 17px;
-        font-weight: 600;
-        color: var(--indigo-6);
-      }
-
-      &__icon {
-        font-size: 14px;
-        line-height: 17px;
-        font-weight: 600;
-        color: var(--goldenrod-2);
-      }
-    }
-
-    &__percentile {
-      grid-row: span 2;
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: space-between;
-      text-align: center;
-      text-wrap: balance;
-      padding: 16px 8px;
-
-      &__label {
-        font-size: 14px;
-        line-height: 17px;
-      }
-
-      &__number {
-        font-size: 54px;
-        font-weight: 600;
-        line-height: 73px;
-        color: var(--goldenrod-2);
-      }
-
-      &__footnote {
-        font-size: 11px;
-        line-height: 14px;
-        opacity: 0.5;
-      }
-    }
-
-    &__new-posts {
-      grid-column: span 2;
-      text-align: center;
-      position: relative;
-      overflow: hidden;
-
-      &__label {
-        font-size: 20px;
-        font-weight: 600;
-        line-height: 24px;
-        color: var(--indigo-6);
-        z-index: 1;
-        position: relative;
-      }
-
-      &__number {
-        font-size: 76px;
-        font-weight: 600;
-        line-height: 91px;
-        color: var(--goldenrod-2);
-        z-index: 1;
-        position: relative;
-      }
-
-      svg {
-        position: absolute;
-        inset-inline-start: -7px;
-        top: -4px;
-        z-index: 0;
-      }
-    }
-
-    &__most-used-hashtag {
-      grid-column: span 2;
-      text-align: center;
-      overflow: hidden;
-
-      &__hashtag {
-        font-size: 42px;
-        font-weight: 600;
-        line-height: 58px;
-        color: var(--indigo-6);
-        margin-inline-start: -100%;
-        margin-inline-end: -100%;
-      }
-
-      &__label {
-        font-size: 14px;
-        font-weight: 600;
-        line-height: 17px;
-      }
-    }
-  }
-}
-
-.annual-report-modal {
-  max-width: 600px;
-  background: var(--indigo-1);
-  border-radius: 16px;
-  display: flex;
-  flex-direction: column;
-  overflow-y: auto;
-
-  .loading-indicator .circular-progress {
-    color: var(--lime);
-  }
-
-  @media screen and (max-width: $no-columns-breakpoint) {
-    border-bottom: 0;
-    border-radius: 16px 16px 0 0;
-  }
-}
-
-.notification-group--annual-report {
-  .notification-group__icon {
-    color: var(--lime);
-  }
-
-  .notification-group__main .link-button {
-    font-weight: 500;
-    color: var(--lime);
-  }
-}
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index 7ae8cb2d08..1535a8f058 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -1,6 +1,3 @@
-@use 'variables' as *;
-@use 'functions' as *;
-
 @function hex-color($color) {
   @if type-of($color) == 'color' {
     $color: str-slice(ie-hex-str($color), 4);
@@ -17,12 +14,7 @@ body {
   font-weight: 400;
   color: $primary-text-color;
   text-rendering: optimizelegibility;
-
-  // Disable kerning for Japanese text to preserve monospaced alignment for readability
-  &:not(:lang(ja)) {
-    font-feature-settings: 'kern';
-  }
-
+  font-feature-settings: 'kern';
   text-size-adjust: none;
   -webkit-tap-highlight-color: rgba(0, 0, 0, 0%);
   -webkit-tap-highlight-color: transparent;
@@ -100,7 +92,6 @@ body {
 
     &.with-modals--active {
       overflow-y: hidden;
-      overscroll-behavior: none;
     }
   }
 
@@ -145,7 +136,13 @@ body {
   &.embed {
     margin: 0;
     padding-bottom: 0;
-    overflow: hidden;
+
+    .container {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+    }
   }
 
   &.admin {
@@ -173,7 +170,6 @@ body {
           width: 100%;
           height: auto;
           margin-top: -120px;
-          margin-bottom: -45px;
         }
       }
 
diff --git a/app/javascript/styles/mastodon/branding.scss b/app/javascript/styles/mastodon/branding.scss
index 8e8dd3530b..d1bddc68b0 100644
--- a/app/javascript/styles/mastodon/branding.scss
+++ b/app/javascript/styles/mastodon/branding.scss
@@ -1,5 +1,3 @@
-@use 'variables' as *;
-
 .logo {
   color: $primary-text-color;
 }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 9758ecc62c..fc71ccb58a 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -1,8 +1,3 @@
-@use 'sass:color';
-@use 'variables' as *;
-@use 'functions' as *;
-@use 'mixins' as *;
-
 .app-body {
   -webkit-overflow-scrolling: touch;
   -ms-overflow-style: -ms-autohiding-scrollbar;
@@ -49,38 +44,6 @@
     color: $ui-primary-color;
     cursor: default;
   }
-
-  &:focus-visible {
-    outline: $ui-button-icon-focus-outline;
-  }
-}
-
-.help-button {
-  background: $ui-button-background-color;
-  border: 0;
-  color: $ui-button-color;
-  border-radius: 20px;
-  cursor: pointer;
-  width: 24px;
-  height: 24px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-
-  &:active,
-  &:focus,
-  &:hover {
-    background-color: $ui-button-focus-background-color;
-  }
-
-  &:focus-visible {
-    outline: $ui-button-icon-focus-outline;
-  }
-
-  .icon {
-    width: 14px;
-    height: 14px;
-  }
 }
 
 .button {
@@ -118,14 +81,6 @@
     outline: $ui-button-icon-focus-outline;
   }
 
-  &--compact {
-    font-size: 14px;
-    line-height: normal;
-    font-weight: 700;
-    padding: 5px 12px;
-    border-radius: 4px;
-  }
-
   &--dangerous {
     background-color: var(--error-background-color);
     color: var(--on-error-color);
@@ -149,7 +104,7 @@
 
   &:disabled,
   &.disabled {
-    background-color: $ui-button-disabled-color;
+    background-color: $ui-primary-color;
     cursor: not-allowed;
   }
 
@@ -189,14 +144,14 @@
     &:disabled,
     &.disabled {
       opacity: 0.7;
-      border-color: $ui-button-disabled-color;
-      color: $ui-button-disabled-color;
+      border-color: $ui-primary-color;
+      color: $ui-primary-color;
 
       &:active,
       &:focus,
       &:hover {
-        border-color: $ui-button-disabled-color;
-        color: $ui-button-disabled-color;
+        border-color: $ui-primary-color;
+        color: $ui-primary-color;
       }
     }
   }
@@ -464,10 +419,10 @@ body > [data-popper-placement] {
 
   &__suggestions {
     box-shadow: var(--dropdown-shadow);
-    background: var(--input-background-color);
+    background: $ui-base-color;
     border: 1px solid var(--background-border-color);
     border-radius: 0 0 4px 4px;
-    color: var(--on-input-color);
+    color: $secondary-text-color;
     font-size: 14px;
     padding: 0;
 
@@ -480,7 +435,7 @@ body > [data-popper-placement] {
       font-size: 14px;
       line-height: 20px;
       letter-spacing: 0.25px;
-      color: var(--on-input-color);
+      color: $secondary-text-color;
 
       &:last-child {
         border-radius: 0 0 4px 4px;
@@ -594,7 +549,7 @@ body > [data-popper-placement] {
     transition: border-color 300ms linear;
     min-height: 0;
     position: relative;
-    background: var(--input-background-color);
+    background: $ui-base-color;
     overflow-y: auto;
 
     &.active {
@@ -671,7 +626,7 @@ body > [data-popper-placement] {
     width: 100%;
     margin: 0;
     color: $secondary-text-color;
-    background: var(--input-background-color);
+    background: $ui-base-color;
     font-family: inherit;
     font-size: 14px;
     padding: 12px;
@@ -992,16 +947,6 @@ body > [data-popper-placement] {
     border-color: $ui-highlight-color;
     color: $primary-text-color;
   }
-
-  &.warning {
-    border-color: var(--goldenrod-2);
-    color: var(--goldenrod-2);
-
-    &.active {
-      background-color: var(--goldenrod-2);
-      color: var(--indigo-1);
-    }
-  }
 }
 
 .character-counter {
@@ -1031,7 +976,7 @@ body > [data-popper-placement] {
   p {
     font-size: 15px;
     line-height: 22px;
-    color: $secondary-text-color;
+    color: $darker-text-color;
     margin-bottom: 20px;
 
     strong {
@@ -1142,6 +1087,26 @@ body > [data-popper-placement] {
     color: $highlight-text-color;
   }
 
+  .status__content__spoiler-link {
+    background: $action-button-color;
+
+    &:hover,
+    &:focus {
+      background: lighten($action-button-color, 7%);
+      text-decoration: none;
+    }
+
+    &::-moz-focus-inner {
+      border: 0;
+    }
+
+    &::-moz-focus-inner,
+    &:focus,
+    &:active {
+      outline: 0 !important;
+    }
+  }
+
   .status__content__text {
     display: none;
 
@@ -1400,6 +1365,21 @@ body > [data-popper-placement] {
   color: $dark-text-color;
 }
 
+.status__content__spoiler-link {
+  display: inline-block;
+  border-radius: 2px;
+  background: transparent;
+  border: 0;
+  color: $inverted-text-color;
+  font-weight: 700;
+  font-size: 11px;
+  padding: 0 6px;
+  text-transform: uppercase;
+  line-height: 20px;
+  cursor: pointer;
+  vertical-align: top;
+}
+
 .status__wrapper--filtered {
   color: $dark-text-color;
   border: 0;
@@ -1661,7 +1641,7 @@ body > [data-popper-placement] {
   padding: 0 10px;
 
   .detailed-status__display-name {
-    color: $dark-text-color;
+    color: lighten($inverted-text-color, 16%);
 
     span {
       display: inline;
@@ -1726,8 +1706,7 @@ body > [data-popper-placement] {
 
 .status__wrapper-direct,
 .notification-ungrouped--direct,
-.notification-group--direct,
-.notification-group--annual-report {
+.notification-group--direct {
   background: rgba($ui-highlight-color, 0.05);
 
   &:focus {
@@ -1816,6 +1795,11 @@ body > [data-popper-placement] {
       height: var(--detail-content-emoji-size);
       margin: -1px 0 0;
     }
+
+    .status__content__spoiler-link {
+      line-height: 24px;
+      margin: -1px 0 0;
+    }
   }
 
   .media-gallery,
@@ -1916,22 +1900,18 @@ body > [data-popper-placement] {
 .detailed-status__wrapper-direct {
   .detailed-status,
   .detailed-status__action-bar {
-    background: color.mix($ui-base-color, $ui-highlight-color, 95%);
+    background: mix($ui-base-color, $ui-highlight-color, 95%);
   }
 
   &:focus {
     .detailed-status,
     .detailed-status__action-bar {
-      background: color.mix(
-        lighten($ui-base-color, 4%),
-        $ui-highlight-color,
-        95%
-      );
+      background: mix(lighten($ui-base-color, 4%), $ui-highlight-color, 95%);
     }
   }
 
   .detailed-status__action-bar {
-    border-top-color: color.mix(
+    border-top-color: mix(
       lighten($ui-base-color, 8%),
       $ui-highlight-color,
       95%
@@ -1952,21 +1932,29 @@ body > [data-popper-placement] {
 }
 
 .domain {
-  padding: 16px;
+  padding: 10px;
   border-bottom: 1px solid var(--background-border-color);
-  display: flex;
-  align-items: center;
-  gap: 8px;
 
-  &__domain-name {
+  .domain__domain-name {
     flex: 1 1 auto;
+    display: block;
     color: $primary-text-color;
-    font-size: 15px;
-    line-height: 21px;
+    text-decoration: none;
+    font-size: 14px;
     font-weight: 500;
   }
 }
 
+.domain__wrapper {
+  display: flex;
+}
+
+.domain_buttons {
+  height: 18px;
+  padding: 10px;
+  white-space: nowrap;
+}
+
 .account {
   padding: 16px;
   border-bottom: 1px solid var(--background-border-color);
@@ -2077,8 +2065,6 @@ body > [data-popper-placement] {
         }
 
         &__handle {
-          overflow: hidden;
-          text-overflow: ellipsis;
           user-select: all;
         }
       }
@@ -2372,6 +2358,17 @@ a.account__display-name {
   .status__avatar {
     opacity: 0.5;
   }
+
+  a.status__content__spoiler-link {
+    background: $ui-base-lighter-color;
+    color: $inverted-text-color;
+
+    &:hover,
+    &:focus {
+      background: lighten($ui-base-lighter-color, 7%);
+      text-decoration: none;
+    }
+  }
 }
 
 .notification__report {
@@ -2529,6 +2526,49 @@ a.account__display-name {
   }
 }
 
+.image-loader {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  scrollbar-width: none; /* Firefox */
+  -ms-overflow-style: none; /* IE 10+ */
+
+  * {
+    scrollbar-width: none; /* Firefox */
+    -ms-overflow-style: none; /* IE 10+ */
+  }
+
+  &::-webkit-scrollbar,
+  *::-webkit-scrollbar {
+    width: 0;
+    height: 0;
+    background: transparent; /* Chrome/Safari/Webkit */
+  }
+
+  .image-loader__preview-canvas {
+    max-width: $media-modal-media-max-width;
+    max-height: $media-modal-media-max-height;
+    background: url('../images/void.png') repeat;
+    object-fit: contain;
+  }
+
+  .loading-bar__container {
+    position: relative;
+  }
+
+  .loading-bar {
+    position: absolute;
+  }
+
+  &.image-loader--amorphous .image-loader__preview-canvas {
+    display: none;
+  }
+}
+
 .zoomable-image {
   position: relative;
   width: 100%;
@@ -2536,61 +2576,13 @@ a.account__display-name {
   display: flex;
   align-items: center;
   justify-content: center;
-  scrollbar-width: none;
-  overflow: hidden;
-  user-select: none;
 
   img {
     max-width: $media-modal-media-max-width;
     max-height: $media-modal-media-max-height;
     width: auto;
     height: auto;
-    outline: 1px solid var(--media-outline-color);
-    outline-offset: -1px;
-    border-radius: 8px;
-    touch-action: none;
-  }
-
-  &--zoomed-in {
-    z-index: 9999;
-    cursor: grab;
-
-    img {
-      outline: none;
-      border-radius: 0;
-    }
-  }
-
-  &--dragging {
-    cursor: grabbing;
-  }
-
-  &--error img {
-    visibility: hidden;
-  }
-
-  &__preview {
-    max-width: $media-modal-media-max-width;
-    max-height: $media-modal-media-max-height;
-    position: absolute;
-    z-index: 1;
-    outline: 1px solid var(--media-outline-color);
-    outline-offset: -1px;
-    border-radius: 8px;
-    overflow: hidden;
-
-    canvas {
-      position: absolute;
-      width: 100%;
-      height: 100%;
-      object-fit: cover;
-      z-index: -1;
-    }
-  }
-
-  .loading-indicator {
-    z-index: 2;
-    mix-blend-mode: luminosity;
+    object-fit: contain;
   }
 }
 
@@ -3165,9 +3157,7 @@ $ui-header-logo-wordmark-width: 99px;
 
     .column > .scrollable,
     .tabs-bar__wrapper .column-header,
-    .tabs-bar__wrapper .column-back-button,
-    .explore__search-header,
-    .column-search-header {
+    .tabs-bar__wrapper .column-back-button {
       border-left: 0;
       border-right: 0;
     }
@@ -3210,6 +3200,10 @@ $ui-header-logo-wordmark-width: 99px;
   }
 }
 
+.explore__search-header {
+  display: none;
+}
+
 .explore__suggestions__card {
   padding: 12px 16px;
   gap: 8px;
@@ -3283,6 +3277,10 @@ $ui-header-logo-wordmark-width: 99px;
   .columns-area__panels__pane--compositional {
     display: none;
   }
+
+  .explore__search-header {
+    display: flex;
+  }
 }
 
 .icon-with-badge {
@@ -3342,6 +3340,203 @@ $ui-header-logo-wordmark-width: 99px;
   }
 }
 
+.onboarding__footer {
+  margin-top: 30px;
+  color: $dark-text-color;
+  text-align: center;
+  font-size: 14px;
+
+  .link-button {
+    display: inline-block;
+    color: inherit;
+    font-size: inherit;
+  }
+}
+
+.onboarding__link {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 10px;
+  color: $highlight-text-color;
+  background: lighten($ui-base-color, 4%);
+  border-radius: 8px;
+  padding: 10px 15px;
+  box-sizing: border-box;
+  font-size: 14px;
+  font-weight: 500;
+  height: 56px;
+  text-decoration: none;
+
+  svg {
+    height: 1.5em;
+  }
+
+  &:hover,
+  &:focus,
+  &:active {
+    background: lighten($ui-base-color, 8%);
+  }
+}
+
+.onboarding__illustration {
+  display: block;
+  margin: 0 auto;
+  margin-bottom: 10px;
+  max-height: 200px;
+  width: auto;
+}
+
+.onboarding__lead {
+  font-size: 16px;
+  line-height: 24px;
+  font-weight: 400;
+  color: $darker-text-color;
+  text-align: center;
+  margin-bottom: 30px;
+
+  strong {
+    font-weight: 700;
+    color: $secondary-text-color;
+  }
+}
+
+.onboarding__links {
+  margin-bottom: 30px;
+
+  & > * {
+    margin-bottom: 2px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+}
+
+.onboarding__steps {
+  margin-bottom: 30px;
+
+  &__item {
+    background: lighten($ui-base-color, 4%);
+    border: 0;
+    border-radius: 8px;
+    display: flex;
+    width: 100%;
+    box-sizing: border-box;
+    align-items: center;
+    gap: 10px;
+    padding: 10px;
+    padding-inline-end: 15px;
+    margin-bottom: 2px;
+    text-decoration: none;
+    text-align: start;
+
+    &:hover,
+    &:focus,
+    &:active {
+      background: lighten($ui-base-color, 8%);
+    }
+
+    &__icon {
+      flex: 0 0 auto;
+      border-radius: 50%;
+      display: none;
+      align-items: center;
+      justify-content: center;
+      width: 36px;
+      height: 36px;
+      color: $highlight-text-color;
+      font-size: 1.2rem;
+
+      @media screen and (width >= 600px) {
+        display: flex;
+      }
+    }
+
+    &__progress {
+      flex: 0 0 auto;
+      background: $valid-value-color;
+      border-radius: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 21px;
+      height: 21px;
+      color: $primary-text-color;
+
+      svg {
+        height: 14px;
+        width: auto;
+      }
+    }
+
+    &__go {
+      flex: 0 0 auto;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 21px;
+      height: 21px;
+      color: $highlight-text-color;
+      font-size: 17px;
+
+      svg {
+        height: 1.5em;
+        width: auto;
+      }
+    }
+
+    &__description {
+      flex: 1 1 auto;
+      line-height: 20px;
+
+      h6 {
+        color: $highlight-text-color;
+        font-weight: 500;
+        font-size: 14px;
+      }
+
+      p {
+        color: $darker-text-color;
+        overflow: hidden;
+      }
+    }
+  }
+}
+
+.follow-recommendations {
+  background: darken($ui-base-color, 4%);
+  border-radius: 8px;
+  margin-bottom: 30px;
+
+  .account:last-child {
+    border-bottom: 0;
+  }
+
+  &__empty {
+    text-align: center;
+    color: $darker-text-color;
+    font-weight: 500;
+    padding: 40px;
+  }
+}
+
+.tip-carousel {
+  border: 1px solid transparent;
+  border-radius: 8px;
+  padding: 16px;
+  margin-bottom: 30px;
+
+  &:focus {
+    outline: 0;
+    border-color: $highlight-text-color;
+  }
+
+  .media-modal__pagination {
+    margin-bottom: 0;
+  }
+}
+
 .copy-paste-text {
   background: lighten($ui-base-color, 4%);
   border-radius: 8px;
@@ -3388,10 +3583,9 @@ $ui-header-logo-wordmark-width: 99px;
 .onboarding__profile {
   position: relative;
   margin-bottom: 40px + 20px;
-  margin-top: -20px;
 
   .app-form__avatar-input {
-    border: 2px solid var(--background-color);
+    border: 2px solid $ui-base-color;
     position: absolute;
     inset-inline-start: -2px;
     bottom: -40px;
@@ -3949,6 +4143,29 @@ $ui-header-logo-wordmark-width: 99px;
   }
 }
 
+button.icon-button i.fa-retweet {
+  background-position: 0 0;
+  height: 19px;
+  transition: background-position 0.9s steps(10);
+  transition-duration: 0s;
+  vertical-align: middle;
+  width: 22px;
+
+  &::before {
+    display: none !important;
+  }
+}
+
+button.icon-button.active i.fa-retweet {
+  transition-duration: 0.9s;
+  background-position: 0 100%;
+}
+
+.reduce-motion button.icon-button i.fa-retweet,
+.reduce-motion button.icon-button.active i.fa-retweet {
+  transition: none;
+}
+
 .status-card {
   display: flex;
   align-items: center;
@@ -4208,27 +4425,23 @@ a.status-card {
 }
 
 .load-more {
-  display: flex;
-  align-items: center;
-  justify-content: center;
+  display: block;
   color: $dark-text-color;
   background-color: transparent;
   border: 0;
   font-size: inherit;
+  text-align: center;
   line-height: inherit;
-  width: 100%;
+  margin: 0;
   padding: 15px;
   box-sizing: border-box;
+  width: 100%;
+  clear: both;
   text-decoration: none;
 
   &:hover {
     background: var(--on-surface-color);
   }
-
-  .icon {
-    width: 22px;
-    height: 22px;
-  }
 }
 
 .load-gap {
@@ -4266,7 +4479,10 @@ a.status-card {
 }
 
 .regeneration-indicator {
-  color: $darker-text-color;
+  text-align: center;
+  font-size: 16px;
+  font-weight: 500;
+  color: $dark-text-color;
   border: 1px solid var(--background-border-color);
   border-top: 0;
   cursor: default;
@@ -4278,26 +4494,31 @@ a.status-card {
   padding: 20px;
 
   &__figure {
-    display: block;
-    width: 100%;
-    height: auto;
-    max-width: 350px;
-    margin-top: -50px;
+    &,
+    img {
+      display: block;
+      width: auto;
+      height: 160px;
+      margin: 0;
+    }
+  }
+
+  &--without-header {
+    padding-top: 20px + 48px;
   }
 
   &__label {
-    text-align: center;
-    font-size: 16px;
+    margin-top: 30px;
 
     strong {
-      font-weight: 500;
       display: block;
       margin-bottom: 10px;
-      color: $darker-text-color;
+      color: $dark-text-color;
     }
 
     span {
       font-size: 15px;
+      font-weight: 400;
     }
   }
 }
@@ -4577,7 +4798,6 @@ a.status-card {
   border: 0;
   background: transparent;
   cursor: pointer;
-  text-decoration: none;
 
   .icon {
     width: 13px;
@@ -4609,7 +4829,6 @@ a.status-card {
   justify-content: center;
 }
 
-.load-more .loading-indicator,
 .button .loading-indicator {
   position: static;
   transform: none;
@@ -4621,10 +4840,6 @@ a.status-card {
   }
 }
 
-.load-more .loading-indicator .circular-progress {
-  color: lighten($ui-base-color, 26%);
-}
-
 .circular-progress {
   color: lighten($ui-base-color, 26%);
   animation: 1.4s linear 0s infinite normal none running simple-rotate;
@@ -5108,8 +5323,7 @@ a.status-card {
   color: $dark-text-color;
   text-align: center;
   padding: 20px;
-  font-size: 14px;
-  line-height: 20px;
+  font-size: 15px;
   font-weight: 400;
   cursor: default;
   display: flex;
@@ -5131,17 +5345,6 @@ a.status-card {
   }
 }
 
-.empty-column-indicator {
-  &__arrow {
-    position: absolute;
-    top: 50%;
-    inset-inline-start: 50%;
-    pointer-events: none;
-    transform: translate(100%, -100%) rotate(12deg);
-    transform-origin: center;
-  }
-}
-
 .follow_requests-unlocked_explanation {
   background: var(--surface-background-color);
   border-bottom: 1px solid var(--background-border-color);
@@ -5582,9 +5785,6 @@ a.status-card {
 
     &__results {
       &__item {
-        display: flex;
-        align-items: center;
-        gap: 0.5em;
         cursor: pointer;
         color: $primary-text-color;
         font-size: 14px;
@@ -5637,7 +5837,7 @@ a.status-card {
     inset-inline-start: 0;
     margin-top: -2px;
     width: 100%;
-    background: var(--input-background-color);
+    background: $ui-base-color;
     border: 1px solid var(--background-border-color);
     border-radius: 0 0 4px 4px;
     box-shadow: var(--dropdown-shadow);
@@ -5757,17 +5957,6 @@ a.status-card {
 }
 
 .search__icon {
-  background: transparent;
-  border: 0;
-  padding: 0;
-  position: absolute;
-  top: 12px + 2px;
-  cursor: default;
-  pointer-events: none;
-  margin-inline-start: 16px - 2px;
-  width: 20px;
-  height: 20px;
-
   &::-moz-focus-inner {
     border: 0;
   }
@@ -5779,14 +5968,17 @@ a.status-card {
 
   .icon {
     position: absolute;
-    top: 0;
-    inset-inline-start: 0;
+    top: 12px + 2px;
+    display: inline-block;
     opacity: 0;
     transition: all 100ms linear;
     transition-property: transform, opacity;
     width: 20px;
     height: 20px;
     color: $darker-text-color;
+    cursor: default;
+    pointer-events: none;
+    margin-inline-start: 16px - 2px;
 
     &.active {
       pointer-events: auto;
@@ -5865,7 +6057,7 @@ a.status-card {
 
 .modal-root {
   position: relative;
-  z-index: 9998;
+  z-index: 9999;
 }
 
 .modal-root__overlay {
@@ -5874,8 +6066,7 @@ a.status-card {
   inset-inline-start: 0;
   inset-inline-end: 0;
   bottom: 0;
-  opacity: 0.9;
-  background: $base-overlay-background;
+  background: rgba($base-overlay-background, 0.7);
   transition: background 0.5s;
 }
 
@@ -5896,7 +6087,6 @@ a.status-card {
   z-index: 9999;
   pointer-events: none;
   user-select: none;
-  overscroll-behavior: none;
 }
 
 .modal-root__modal {
@@ -6030,7 +6220,7 @@ a.status-card {
   .picture-in-picture__footer {
     border-radius: 0;
     background: transparent;
-    padding: 16px;
+    padding: 20px 0;
 
     .icon-button {
       color: $white;
@@ -6118,44 +6308,126 @@ a.status-card {
   }
 }
 
-.modal-placeholder {
-  width: 588px;
-  min-height: 478px;
+.onboarding-modal,
+.error-modal,
+.embed-modal {
+  background: $ui-secondary-color;
+  color: $inverted-text-color;
+  border-radius: 8px;
+  overflow: hidden;
+  display: flex;
   flex-direction: column;
-  background: var(--modal-background-color);
-  backdrop-filter: var(--background-filter);
-  border: 1px solid var(--modal-border-color);
-  border-radius: 16px;
+}
 
-  &__error {
-    padding: 24px;
-    display: flex;
-    align-items: center;
+.error-modal__body {
+  height: 80vh;
+  width: 80vw;
+  max-width: 520px;
+  max-height: 420px;
+  position: relative;
+
+  & > div {
+    position: absolute;
+    top: 0;
+    inset-inline-start: 0;
+    width: 100%;
+    height: 100%;
+    box-sizing: border-box;
+    padding: 25px;
     flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    display: flex;
+    opacity: 0;
+    user-select: text;
+  }
+}
 
-    &__image {
-      width: 70%;
-      max-width: 350px;
+.error-modal__body {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  text-align: center;
+}
+
+.onboarding-modal__paginator,
+.error-modal__footer {
+  flex: 0 0 auto;
+  background: darken($ui-secondary-color, 8%);
+  display: flex;
+  padding: 25px;
+
+  & > div {
+    min-width: 33px;
+  }
+
+  .onboarding-modal__nav,
+  .error-modal__nav {
+    color: $lighter-text-color;
+    border: 0;
+    font-size: 14px;
+    font-weight: 500;
+    padding: 10px 25px;
+    line-height: inherit;
+    height: auto;
+    margin: -10px;
+    border-radius: 4px;
+    background-color: transparent;
+
+    &:hover,
+    &:focus,
+    &:active {
+      color: darken($lighter-text-color, 4%);
+      background-color: darken($ui-secondary-color, 16%);
     }
 
-    &__message {
-      text-align: center;
-      text-wrap: balance;
-      font-size: 14px;
-      line-height: 20px;
-      letter-spacing: 0.25px;
+    &.onboarding-modal__done,
+    &.onboarding-modal__next {
+      color: $inverted-text-color;
 
-      &__actions {
-        margin-top: 24px;
-        display: flex;
-        gap: 10px;
-        align-items: center;
-        justify-content: center;
+      &:hover,
+      &:focus,
+      &:active {
+        color: lighten($inverted-text-color, 4%);
       }
     }
   }
 }
 
+.error-modal__footer {
+  justify-content: center;
+}
+
+.display-case {
+  text-align: center;
+  font-size: 15px;
+  margin-bottom: 15px;
+
+  &__label {
+    font-weight: 500;
+    color: $inverted-text-color;
+    margin-bottom: 5px;
+    text-transform: uppercase;
+    font-size: 12px;
+  }
+
+  &__case {
+    background: $ui-base-color;
+    color: $secondary-text-color;
+    font-weight: 500;
+    padding: 10px;
+    border-radius: 4px;
+  }
+}
+
+.onboard-sliders {
+  display: inline-block;
+  max-width: 30px;
+  max-height: auto;
+  margin-inline-start: 10px;
+}
+
 .safety-action-modal {
   width: 600px;
   flex-direction: column;
@@ -6389,14 +6661,12 @@ a.status-card {
   border-radius: 16px;
 
   &__header {
-    box-sizing: border-box;
     border-bottom: 1px solid var(--modal-border-color);
     display: flex;
     align-items: center;
     justify-content: space-between;
     flex-direction: row-reverse;
     padding: 12px 24px;
-    min-height: 61px;
 
     &__title {
       font-size: 16px;
@@ -6418,49 +6688,6 @@ a.status-card {
       gap: 16px;
       padding: 24px;
     }
-
-    &__preview {
-      display: flex;
-      flex-direction: column;
-      gap: 16px;
-      align-items: center;
-      justify-content: center;
-      padding: 24px;
-      background: #000;
-
-      img {
-        display: block;
-      }
-
-      img,
-      .gifv video {
-        outline: 1px solid var(--media-outline-color);
-        outline-offset: -1px;
-        border-radius: 8px;
-      }
-
-      img,
-      .gifv video,
-      .video-player,
-      .audio-player {
-        max-width: 360px;
-        max-height: 45vh;
-      }
-    }
-  }
-
-  &__popout {
-    background: var(--dropdown-background-color);
-    backdrop-filter: var(--background-filter);
-    border: 1px solid var(--dropdown-border-color);
-    box-shadow: var(--dropdown-shadow);
-    max-width: 320px;
-    padding: 16px;
-    border-radius: 8px;
-    z-index: 9999 !important;
-    font-size: 14px;
-    line-height: 20px;
-    color: $darker-text-color;
   }
 
   .copy-paste-text {
@@ -6589,7 +6816,7 @@ a.status-card {
 
     a {
       text-decoration: none;
-      color: $highlight-text-color;
+      color: $inverted-text-color;
       font-weight: 500;
 
       &:hover {
@@ -6622,6 +6849,15 @@ a.status-card {
     color: $primary-text-color;
   }
 
+  .status__content__spoiler-link {
+    color: $primary-text-color;
+    background: $ui-primary-color;
+
+    &:hover {
+      background: lighten($ui-primary-color, 8%);
+    }
+  }
+
   .dialog-option {
     align-items: center;
     gap: 12px;
@@ -6807,6 +7043,62 @@ a.status-card {
   margin-bottom: 29px;
 }
 
+.report-modal__comment {
+  padding: 20px;
+  border-inline-end: 1px solid var(--background-border-color);
+  max-width: 320px;
+
+  p {
+    font-size: 14px;
+    line-height: 20px;
+    margin-bottom: 20px;
+  }
+
+  .setting-text-label {
+    display: block;
+    color: $secondary-text-color;
+    font-size: 14px;
+    font-weight: 500;
+    margin-bottom: 10px;
+  }
+
+  .setting-text {
+    width: 100%;
+    resize: none;
+    min-height: 100px;
+    max-height: 50vh;
+    border: 0;
+
+    @media screen and (height <= 600px) {
+      max-height: 20vh;
+    }
+
+    @media screen and (max-width: $no-columns-breakpoint) {
+      max-height: 20vh;
+    }
+  }
+
+  .setting-toggle {
+    margin-top: 20px;
+    margin-bottom: 24px;
+
+    &__label {
+      color: $inverted-text-color;
+      font-size: 14px;
+    }
+  }
+
+  @media screen and (width <= 480px) {
+    padding: 10px;
+    max-width: 100%;
+    order: 2;
+
+    .setting-toggle {
+      margin-bottom: 4px;
+    }
+  }
+}
+
 .actions-modal {
   max-height: 80vh;
   max-width: 80vw;
@@ -7245,10 +7537,6 @@ a.status-card {
       filter: var(--overlay-icon-shadow);
     }
   }
-
-  &--error img {
-    visibility: hidden;
-  }
 }
 
 .media-gallery__item-thumbnail {
@@ -7325,6 +7613,11 @@ a.status-card {
   outline: 1px solid var(--media-outline-color);
   outline-offset: -1px;
 
+  &.editable {
+    border-radius: 0;
+    height: 100%;
+  }
+
   &.inactive {
     audio,
     .video-player__controls {
@@ -7393,6 +7686,11 @@ a.status-card {
   outline-offset: -1px;
   z-index: 2;
 
+  &.editable {
+    border-radius: 0;
+    height: 100% !important;
+  }
+
   video {
     display: block;
     z-index: -2;
@@ -7403,8 +7701,6 @@ a.status-card {
     height: 100% !important;
     margin: 0;
     aspect-ratio: auto !important;
-    outline: none;
-    border-radius: 0;
 
     video {
       width: 100% !important;
@@ -7436,15 +7732,6 @@ a.status-card {
     }
   }
 
-  .media-gallery__actions {
-    opacity: 0;
-    transition: opacity 0.1s ease;
-
-    &.active {
-      opacity: 1;
-    }
-  }
-
   &.inactive {
     video,
     .video-player__controls {
@@ -7595,7 +7882,7 @@ a.status-card {
       inset-inline-start: 0;
       top: 50%;
       transform: translate(0, -50%);
-      background: $white;
+      background: lighten($ui-highlight-color, 8%);
     }
 
     &__handle {
@@ -7608,7 +7895,7 @@ a.status-card {
       inset-inline-start: 0;
       margin-inline-start: -6px;
       transform: translate(0, -50%);
-      background: $white;
+      background: lighten($ui-highlight-color, 8%);
       box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
       opacity: 0;
 
@@ -7662,7 +7949,7 @@ a.status-card {
       height: 4px;
       border-radius: 4px;
       top: 14px;
-      background: $white;
+      background: lighten($ui-highlight-color, 8%);
     }
 
     &__buffer {
@@ -7678,7 +7965,7 @@ a.status-card {
       height: 12px;
       top: 10px;
       margin-inline-start: -6px;
-      background: $white;
+      background: lighten($ui-highlight-color, 8%);
       box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
 
       .no-reduce-motion & {
@@ -7687,7 +7974,6 @@ a.status-card {
 
       &.active {
         opacity: 1;
-        cursor: grabbing;
       }
     }
 
@@ -7698,28 +7984,6 @@ a.status-card {
     }
   }
 
-  &__hotkey-indicator {
-    position: absolute;
-    top: 50%;
-    inset-inline-start: 50%;
-    transform: translate(-50%, -50%);
-    background: rgba($base-shadow-color, 0.45);
-    backdrop-filter: var(--background-filter);
-    color: $white;
-    border-radius: 8px;
-    padding: 16px 24px;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    gap: 8px;
-
-    &__label {
-      font-size: 15px;
-      font-weight: 500;
-    }
-  }
-
   &.detailed,
   &.fullscreen {
     .video-player__buttons {
@@ -7732,14 +7996,6 @@ a.status-card {
 }
 
 .gifv {
-  position: relative;
-
-  canvas {
-    position: absolute;
-    width: 100%;
-    height: 100%;
-  }
-
   video {
     max-width: 100vw;
     max-height: 80vh;
@@ -7779,8 +8035,7 @@ a.status-card {
     border-radius: 0;
   }
 
-  .load-more,
-  .timeline-hint {
+  .load-more {
     grid-column: span 3;
   }
 }
@@ -7905,11 +8160,14 @@ a.status-card {
 }
 
 .radio-button__input.checked::before {
+  position: absolute;
+  left: 2px;
+  top: 2px;
   content: '';
   display: block;
   border-radius: 50%;
-  width: calc(100% - 4px);
-  height: calc(100% - 4px);
+  width: 12px;
+  height: 12px;
   background: $ui-highlight-color;
 }
 
@@ -8041,6 +8299,92 @@ noscript {
   background: rgba($base-overlay-background, 0.5);
 }
 
+.list-adder,
+.list-editor {
+  backdrop-filter: var(--background-filter);
+  background: var(--modal-background-color);
+  border: 1px solid var(--modal-border-color);
+  flex-direction: column;
+  border-radius: 8px;
+  width: 380px;
+  overflow: hidden;
+
+  @media screen and (width <= 420px) {
+    width: 90%;
+  }
+}
+
+.list-adder {
+  &__lists {
+    height: 50vh;
+    border-radius: 0 0 8px 8px;
+    overflow-y: auto;
+  }
+
+  .list {
+    padding: 10px;
+    border-bottom: 1px solid var(--background-border-color);
+  }
+
+  .list__wrapper {
+    display: flex;
+  }
+
+  .list__display-name {
+    flex: 1 1 auto;
+    overflow: hidden;
+    text-decoration: none;
+    font-size: 16px;
+    padding: 10px;
+    display: flex;
+    align-items: center;
+    gap: 4px;
+  }
+}
+
+.list-editor {
+  h4 {
+    padding: 15px 0;
+    background: lighten($ui-base-color, 13%);
+    font-weight: 500;
+    font-size: 16px;
+    text-align: center;
+    border-radius: 8px 8px 0 0;
+  }
+
+  .drawer__pager {
+    height: 50vh;
+    border: 0;
+  }
+
+  .drawer__inner {
+    &.backdrop {
+      width: calc(100% - 60px);
+      box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
+      border-radius: 0 0 0 8px;
+    }
+  }
+
+  &__accounts {
+    background: unset;
+    overflow-y: auto;
+  }
+
+  .account__display-name {
+    &:hover strong {
+      text-decoration: none;
+    }
+  }
+
+  .account__avatar {
+    cursor: default;
+  }
+
+  .search {
+    margin-bottom: 0;
+  }
+}
+
 .antenna-list-detail {
   font-size: 12px;
   margin-left: 24px;
@@ -8058,6 +8402,7 @@ noscript {
 
 .antenna-setting {
   margin: 8px 16px 32px;
+  overflow: auto;
 
   h2 {
     font-size: 20px;
@@ -8122,14 +8467,11 @@ noscript {
 
   i.fa {
     color: $darker-text-color;
-    width: 24px;
-    height: 24px;
   }
 
   .label {
     flex: 1;
     margin: 0 8px;
-    line-height: 24px;
   }
 }
 
@@ -8173,14 +8515,24 @@ noscript {
 
 .focal-point {
   position: relative;
-  cursor: grab;
+  cursor: move;
   overflow: hidden;
+  height: 100%;
   display: flex;
   justify-content: center;
   align-items: center;
+  background: $base-shadow-color;
 
-  &.dragging {
-    cursor: grabbing;
+  img,
+  video,
+  canvas {
+    display: block;
+    max-height: 80vh;
+    width: 100%;
+    height: auto;
+    margin: 0;
+    object-fit: contain;
+    background: $base-shadow-color;
   }
 
   &__reticle {
@@ -8188,10 +8540,54 @@ noscript {
     width: 100px;
     height: 100px;
     transform: translate(-50%, -50%);
-    border: 2px solid #fff;
+    background: url('../images/reticle.png') no-repeat 0 0;
     border-radius: 50%;
     box-shadow: 0 0 0 9999em rgba($base-shadow-color, 0.35);
-    pointer-events: none;
+  }
+
+  &__overlay {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    top: 0;
+    inset-inline-start: 0;
+  }
+
+  &__preview {
+    position: absolute;
+    bottom: 10px;
+    inset-inline-end: 10px;
+    z-index: 2;
+    cursor: move;
+    transition: opacity 0.1s ease;
+
+    &:hover {
+      opacity: 0.5;
+    }
+
+    strong {
+      color: $primary-text-color;
+      font-size: 14px;
+      font-weight: 500;
+      display: block;
+      margin-bottom: 5px;
+    }
+
+    div {
+      border-radius: 4px;
+      box-shadow: 0 0 14px rgba($base-shadow-color, 0.2);
+    }
+  }
+
+  @media screen and (width <= 480px) {
+    img,
+    video {
+      max-height: 100%;
+    }
+
+    &__preview {
+      display: none;
+    }
   }
 }
 
@@ -8205,7 +8601,6 @@ noscript {
 
   p {
     margin-bottom: 20px;
-    unicode-bidi: plaintext;
 
     &:last-child {
       margin-bottom: 0;
@@ -8309,7 +8704,7 @@ noscript {
       }
 
       .icon-button {
-        border: 1px solid var(--background-border-color);
+        border: 1px solid lighten($ui-base-color, 12%);
         border-radius: 4px;
         box-sizing: content-box;
         padding: 5px;
@@ -8367,8 +8762,6 @@ noscript {
           text-overflow: ellipsis;
 
           span {
-            overflow: hidden;
-            text-overflow: ellipsis;
             user-select: all;
           }
 
@@ -8574,9 +8967,13 @@ noscript {
   &__item {
     display: flex;
     align-items: center;
-    padding: 16px;
+    padding: 15px;
     border-bottom: 1px solid var(--background-border-color);
-    gap: 8px;
+    gap: 15px;
+
+    &:last-child {
+      border-bottom: 0;
+    }
 
     &__name {
       flex: 1 1 auto;
@@ -8683,7 +9080,7 @@ noscript {
   }
 
   &--compact &__item {
-    padding: 12px;
+    padding: 10px;
   }
 }
 
@@ -8848,7 +9245,6 @@ noscript {
   &__item {
     flex-shrink: 0;
     background: lighten($ui-base-color, 12%);
-    color: $darker-text-color;
     border: 0;
     border-radius: 3px;
     margin: 2px;
@@ -8885,6 +9281,7 @@ noscript {
       font-weight: 500;
       text-align: center;
       margin-inline-start: 6px;
+      color: $darker-text-color;
     }
 
     &:hover,
@@ -8893,18 +9290,24 @@ noscript {
       background: lighten($ui-base-color, 16%);
       transition: all 200ms ease-out;
       transition-property: background-color, color;
-      color: lighten($darker-text-color, 4%);
+
+      &__count {
+        color: lighten($darker-text-color, 4%);
+      }
     }
 
     &.active {
       transition: all 100ms ease-in;
       transition-property: background-color, color;
-      background-color: color.mix(
+      background-color: mix(
         lighten($ui-base-color, 12%),
         $ui-highlight-color,
         80%
       );
-      color: lighten($highlight-text-color, 8%);
+
+      .reactions-bar__item__count {
+        color: lighten($highlight-text-color, 8%);
+      }
     }
   }
 
@@ -9092,13 +9495,10 @@ noscript {
 }
 
 .explore__search-header {
+  background: darken($ui-base-color, 4%);
   justify-content: center;
   align-items: center;
-  border: 1px solid var(--background-border-color);
-  border-top: 0;
-  border-bottom: 0;
-  padding: 16px;
-  padding-bottom: 8px;
+  padding: 15px;
 
   .search {
     width: 100%;
@@ -9107,7 +9507,7 @@ noscript {
 
   .search__input {
     border: 1px solid var(--background-border-color);
-    padding: 12px;
+    padding: 10px;
     padding-inline-end: 30px;
   }
 
@@ -9115,21 +9515,13 @@ noscript {
     border: 1px solid var(--background-border-color);
   }
 
-  .search__icon {
-    top: 12px;
-    inset-inline-end: 12px;
+  .search .icon {
+    top: 9px;
+    inset-inline-end: 10px;
     color: $dark-text-color;
   }
 }
 
-.layout-single-column .explore__search-header {
-  display: none;
-
-  @media screen and (max-width: $no-gap-breakpoint - 1px) {
-    display: flex;
-  }
-}
-
 .explore__search-results {
   flex: 1 1 auto;
   display: flex;
@@ -9406,7 +9798,6 @@ noscript {
   }
 
   p {
-    text-align: center;
     font-size: 17px;
     line-height: 22px;
     color: $darker-text-color;
@@ -9482,6 +9873,11 @@ noscript {
       border: 1px solid var(--background-border-color);
     }
 
+    &.focused &__input {
+      border-color: $highlight-text-color;
+      background: lighten($ui-base-color, 4%);
+    }
+
     &.invalid &__input {
       border-color: $error-red;
     }
@@ -9800,7 +10196,6 @@ noscript {
   border: 1px solid $highlight-text-color;
   background: rgba($highlight-text-color, 0.15);
   overflow: hidden;
-  flex-shrink: 0;
 
   &__background-image {
     width: 125%;
@@ -10183,7 +10578,7 @@ noscript {
   position: fixed;
   bottom: 2rem;
   inset-inline-start: 0;
-  z-index: 9999;
+  z-index: 999;
   display: flex;
   flex-direction: column;
   gap: 4px;
@@ -10228,9 +10623,6 @@ noscript {
 }
 
 .notification-bar-action {
-  display: inline-block;
-  border: 0;
-  background: transparent;
   text-transform: uppercase;
   margin-inline-start: 10px;
   cursor: pointer;
@@ -10272,30 +10664,6 @@ noscript {
       line-height: 33px;
       font-weight: 700;
     }
-
-    &__buttons {
-      display: flex;
-      align-items: center;
-      gap: 8px;
-
-      .button {
-        flex-shrink: 1;
-        white-space: nowrap;
-        min-width: 80px;
-      }
-
-      .icon-button {
-        border: 1px solid var(--background-border-color);
-        border-radius: 4px;
-        box-sizing: content-box;
-        padding: 5px;
-
-        .icon {
-          width: 24px;
-          height: 24px;
-        }
-      }
-    }
   }
 }
 
@@ -10555,7 +10923,7 @@ noscript {
 
   &__text {
     flex: 1 1 auto;
-    font-size: 14px;
+    font-style: 14px;
     line-height: 20px;
 
     strong {
@@ -10613,7 +10981,7 @@ noscript {
   &__name {
     flex: 1 1 auto;
     color: $darker-text-color;
-    font-size: 14px;
+    font-style: 14px;
     line-height: 20px;
     overflow: hidden;
     text-overflow: ellipsis;
@@ -10922,7 +11290,12 @@ noscript {
 .compose-form__actions {
   .button {
     display: block; // Otherwise text-ellipsis doesn't work
+    font-size: 14px;
+    line-height: normal;
+    font-weight: 700;
     flex: 1 1 auto;
+    padding: 5px 12px;
+    border-radius: 4px;
   }
 }
 
@@ -11244,97 +11617,3 @@ noscript {
     margin-bottom: 22px;
   }
 }
-
-.lists__item {
-  display: flex;
-  align-items: center;
-  gap: 16px;
-  padding-inline-end: 13px;
-  border-bottom: 1px solid var(--background-border-color);
-
-  &__title {
-    display: flex;
-    align-items: center;
-    gap: 5px;
-    padding: 16px 13px;
-    flex: 1 1 auto;
-    font-size: 16px;
-    line-height: 24px;
-    color: $secondary-text-color;
-    text-decoration: none;
-
-    &:is(a):hover,
-    &:is(a):focus,
-    &:is(a):active {
-      color: $primary-text-color;
-    }
-
-    input {
-      display: block;
-      width: 100%;
-      background: transparent;
-      border: 0;
-      padding: 0;
-      font-family: inherit;
-      font-size: inherit;
-      line-height: inherit;
-      color: inherit;
-
-      &::placeholder {
-        color: var(--input-placeholder-color);
-        opacity: 1;
-      }
-
-      &:focus {
-        outline: 0;
-      }
-    }
-  }
-
-  &__memo {
-    display: block;
-    color: $dark-text-color;
-    font-size: 14px;
-    line-height: 18px;
-  }
-}
-
-.column-search-header {
-  display: flex;
-  gap: 12px;
-  align-items: center;
-  border: 1px solid var(--background-border-color);
-  border-top: 0;
-  border-bottom: 0;
-  padding: 16px;
-  padding-bottom: 8px;
-
-  input {
-    background: var(--input-background-color);
-    border: 1px solid var(--background-border-color);
-    color: var(--on-input-color);
-    padding: 12px;
-    font-size: 16px;
-    line-height: normal;
-    border-radius: 4px;
-    display: block;
-    flex: 1 1 auto;
-
-    &::placeholder {
-      color: var(--input-placeholder-color);
-      opacity: 1;
-    }
-
-    &:focus {
-      outline: 0;
-    }
-  }
-}
-
-.column-footer {
-  padding: 16px;
-}
-
-.lists-scrollable {
-  min-height: 50vh;
-}
diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss
index 7db9ca409d..ac1f862a09 100644
--- a/app/javascript/styles/mastodon/containers.scss
+++ b/app/javascript/styles/mastodon/containers.scss
@@ -1,5 +1,3 @@
-@use 'variables' as *;
-
 .container-alt {
   width: 700px;
   margin: 0 auto;
diff --git a/app/javascript/styles/mastodon/css_variables.scss b/app/javascript/styles/mastodon/css_variables.scss
deleted file mode 100644
index d1a357f730..0000000000
--- a/app/javascript/styles/mastodon/css_variables.scss
+++ /dev/null
@@ -1,39 +0,0 @@
-@use 'sass:color';
-@use 'functions' as *;
-@use 'variables' as *;
-
-:root {
-  --dropdown-border-color: #{lighten($ui-base-color, 4%)};
-  --dropdown-background-color: #{rgba(darken($ui-base-color, 8%), 0.9)};
-  --dropdown-shadow: 0 20px 25px -5px #{rgba($base-shadow-color, 0.25)},
-    0 8px 10px -6px #{rgba($base-shadow-color, 0.25)};
-  --modal-background-color: #{rgba(darken($ui-base-color, 8%), 0.7)};
-  --modal-background-variant-color: #{rgba($ui-base-color, 0.7)};
-  --modal-border-color: #{lighten($ui-base-color, 4%)};
-  --background-border-color: #{lighten($ui-base-color, 4%)};
-  --background-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
-  --background-color: #{darken($ui-base-color, 8%)};
-  --background-color-tint: #{rgba(darken($ui-base-color, 8%), 0.9)};
-  --surface-background-color: #{darken($ui-base-color, 4%)};
-  --surface-variant-background-color: #{$ui-base-color};
-  --surface-variant-active-background-color: #{lighten($ui-base-color, 4%)};
-  --on-surface-color: #{color.adjust($ui-base-color, $alpha: -0.5)};
-  --avatar-border-radius: 8px;
-  --media-outline-color: #{rgba(#fcf8ff, 0.15)};
-  --overlay-icon-shadow: drop-shadow(0 0 8px #{rgba($base-shadow-color, 0.25)});
-  --error-background-color: #{darken($error-red, 16%)};
-  --error-active-background-color: #{darken($error-red, 12%)};
-  --on-error-color: #fff;
-  --rich-text-container-color: rgba(87, 24, 60, 100%);
-  --rich-text-text-color: rgba(255, 175, 212, 100%);
-  --rich-text-decorations-color: rgba(128, 58, 95, 100%);
-  --input-placeholder-color: #{$dark-text-color};
-  --input-background-color: var(--surface-variant-background-color);
-  --on-input-color: #{$secondary-text-color};
-  --content-font-size: 15px;
-  --content-emoji-size: 20px;
-  --content-line-height: 22px;
-  --detail-content-font-size: 19px;
-  --detail-content-emoji-size: 24px;
-  --detail-content-line-height: 24px;
-}
diff --git a/app/javascript/styles/mastodon/dashboard.scss b/app/javascript/styles/mastodon/dashboard.scss
index c99cdc357a..d049b2456c 100644
--- a/app/javascript/styles/mastodon/dashboard.scss
+++ b/app/javascript/styles/mastodon/dashboard.scss
@@ -1,6 +1,3 @@
-@use 'functions' as *;
-@use 'variables' as *;
-
 .dashboard__counters {
   display: flex;
   flex-wrap: wrap;
diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss
index 68f4c87ecd..e883bb4ab5 100644
--- a/app/javascript/styles/mastodon/emoji_picker.scss
+++ b/app/javascript/styles/mastodon/emoji_picker.scss
@@ -1,6 +1,3 @@
-@use 'variables' as *;
-@use 'functions' as *;
-
 .emoji-mart {
   font-size: 13px;
   display: inline-block;
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index f8eaa43a20..b58e015872 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -1,5 +1,4 @@
-@use 'variables' as *;
-@use 'functions' as *;
+$no-columns-breakpoint: 600px;
 
 code {
   font-family: $font-monospace, monospace;
@@ -77,21 +76,6 @@ code {
     margin-bottom: 16px;
     overflow: hidden;
 
-    &:last-child {
-      margin-bottom: 0;
-    }
-
-    &__toolbar {
-      margin-top: 16px;
-      display: flex;
-      align-items: center;
-      gap: 16px;
-
-      .character-counter {
-        flex: 0 0 auto;
-      }
-    }
-
     &.hidden {
       margin: 0;
     }
@@ -353,33 +337,10 @@ code {
     columns: unset;
   }
 
-  .input.datetime .label_input,
-  .input.date .label_input {
-    display: flex;
-    gap: 4px;
-    align-items: center;
-
-    select {
-      display: inline-block;
-      width: auto;
-      flex: 0;
-    }
-  }
-
-  .input.date_of_birth .label_input {
-    display: flex;
-    gap: 8px;
-    align-items: center;
-
-    input {
-      box-sizing: content-box;
-      width: 32px;
-      flex: 0;
-
-      &:last-child {
-        width: 64px;
-      }
-    }
+  .input.datetime .label_input select {
+    display: inline-block;
+    width: auto;
+    flex: 0;
   }
 
   .input.select.select--languages {
@@ -591,7 +552,6 @@ code {
   .actions {
     margin-top: 30px;
     display: flex;
-    gap: 10px;
 
     &.actions--top {
       margin-top: 0;
@@ -600,14 +560,13 @@ code {
   }
 
   .stacked-actions {
-    display: flex;
-    flex-direction: column;
-    gap: 10px;
     margin-top: 30px;
     margin-bottom: 15px;
   }
 
-  .btn {
+  button,
+  .button,
+  .block-button {
     display: block;
     width: 100%;
     border: 0;
@@ -624,6 +583,8 @@ code {
     cursor: pointer;
     font-weight: 500;
     outline: 0;
+    margin-bottom: 10px;
+    margin-inline-end: 10px;
 
     &:last-child {
       margin-inline-end: 0;
@@ -635,9 +596,8 @@ code {
       background-color: $ui-button-focus-background-color;
     }
 
-    &:disabled,
     &:disabled:hover {
-      background-color: $ui-button-disabled-color;
+      background-color: $ui-primary-color;
     }
 
     &.negative {
@@ -651,6 +611,16 @@ code {
     }
   }
 
+  .button.button-tertiary {
+    padding: 9px;
+
+    &:hover,
+    &:focus,
+    &:active {
+      padding: 10px;
+    }
+  }
+
   select {
     appearance: none;
     box-sizing: border-box;
@@ -680,18 +650,6 @@ code {
   }
 
   .label_input {
-    position: relative;
-
-    &__loading-indicator {
-      box-sizing: border-box;
-      position: absolute;
-      top: 0;
-      inset-inline-start: 0;
-      border: 1px solid transparent;
-      padding: 10px 16px;
-      width: 100%;
-    }
-
     &__wrapper {
       position: relative;
     }
@@ -1235,7 +1193,6 @@ code {
   align-items: center;
   padding-bottom: 30px;
   margin-bottom: 30px;
-  color: $white;
 
   li {
     flex: 0 0 auto;
@@ -1318,13 +1275,11 @@ code {
 }
 
 .app-form {
-  padding: 16px;
-
   &__avatar-input,
   &__header-input {
     display: block;
     border-radius: 8px;
-    background: var(--surface-variant-background-color);
+    background: var(--dropdown-background-color);
     position: relative;
     cursor: pointer;
 
@@ -1372,7 +1327,7 @@ code {
     }
 
     &:hover {
-      background-color: var(--surface-variant-active-background-color);
+      background-color: var(--dropdown-border-color);
     }
   }
 
@@ -1435,61 +1390,4 @@ code {
       padding-inline-start: 16px;
     }
   }
-
-  &__link {
-    display: flex;
-    gap: 16px;
-    padding: 8px 0;
-    align-items: center;
-    text-decoration: none;
-    color: $primary-text-color;
-    margin-bottom: 16px;
-
-    &__text {
-      flex: 1 1 auto;
-      font-size: 14px;
-      line-height: 20px;
-      color: $darker-text-color;
-
-      strong {
-        font-weight: 600;
-        display: block;
-        color: $primary-text-color;
-      }
-    }
-  }
-
-  &__memo {
-    color: $dark-text-color;
-    font-size: 14px;
-    margin: 16px 0;
-  }
-}
-
-.avatar-pile {
-  display: flex;
-  align-items: center;
-
-  img {
-    display: block;
-    border-radius: 8px;
-    width: 32px;
-    height: 32px;
-    border: 2px solid var(--background-color);
-    background: var(--surface-background-color);
-    margin-inline-end: -16px;
-    transform: rotate(0);
-
-    &:first-child {
-      transform: rotate(-4deg);
-    }
-
-    &:nth-child(2) {
-      transform: rotate(-2deg);
-    }
-
-    &:last-child {
-      margin-inline-end: 0;
-    }
-  }
 }
diff --git a/app/javascript/styles/mastodon/modal.scss b/app/javascript/styles/mastodon/modal.scss
index 7d060a2681..60e7d62245 100644
--- a/app/javascript/styles/mastodon/modal.scss
+++ b/app/javascript/styles/mastodon/modal.scss
@@ -1,6 +1,3 @@
-@use 'variables' as *;
-@use 'functions' as *;
-
 .modal-layout {
   background: darken($ui-base-color, 4%)
     url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-lighter-color)}33"/></svg>')
diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss
index f49ce3c413..939fca3364 100644
--- a/app/javascript/styles/mastodon/polls.scss
+++ b/app/javascript/styles/mastodon/polls.scss
@@ -1,6 +1,3 @@
-@use 'variables' as *;
-@use 'functions' as *;
-
 .poll {
   margin-top: 16px;
   font-size: 14px;
@@ -41,6 +38,11 @@
       background: darken($ui-primary-color, 5%);
     }
 
+    &::-ms-fill {
+      border-radius: 4px;
+      background: darken($ui-primary-color, 5%);
+    }
+
     &::-webkit-progress-value {
       border-radius: 4px;
       background: darken($ui-primary-color, 5%);
diff --git a/app/javascript/styles/mastodon/reset.scss b/app/javascript/styles/mastodon/reset.scss
index 2dce637a06..fc0305baf3 100644
--- a/app/javascript/styles/mastodon/reset.scss
+++ b/app/javascript/styles/mastodon/reset.scss
@@ -1,5 +1,3 @@
-@use 'variables' as *;
-
 /* http://meyerweb.com/eric/tools/css/reset/
    v2.0 | 20110126
    License: none (public domain)
@@ -61,26 +59,24 @@ table {
   }
 }
 
-.custom-scrollbars {
-  ::-webkit-scrollbar {
-    width: 8px;
-    height: 8px;
-  }
-
-  ::-webkit-scrollbar-thumb {
-    background-color: $action-button-color;
-    border: 2px var(--background-border-color);
-    border-radius: 12px;
-    width: 6px;
-    box-shadow: inset 0 0 0 2px var(--background-border-color);
-  }
-
-  ::-webkit-scrollbar-track {
-    background-color: var(--background-border-color);
-    border-radius: 0px;
-  }
-
-  ::-webkit-scrollbar-corner {
-    background: transparent;
-  }
+::-webkit-scrollbar {
+  width: 8px;
+  height: 8px;
+}
+
+::-webkit-scrollbar-thumb {
+  background-color: $action-button-color;
+  border: 2px var(--background-border-color);
+  border-radius: 12px;
+  width: 6px;
+  box-shadow: inset 0 0 0 2px var(--background-border-color);
+}
+
+::-webkit-scrollbar-track {
+  background-color: var(--background-border-color);
+  border-radius: 0px;
+}
+
+::-webkit-scrollbar-corner {
+  background: transparent;
 }
diff --git a/app/javascript/styles/mastodon/rich_text.scss b/app/javascript/styles/mastodon/rich_text.scss
index 38bfbe3dca..f713a5d10e 100644
--- a/app/javascript/styles/mastodon/rich_text.scss
+++ b/app/javascript/styles/mastodon/rich_text.scss
@@ -3,29 +3,9 @@
 .e-content,
 .edit-indicator__content,
 .reply-indicator__content {
-  code {
-    background: var(--rich-text-container-color);
-    padding: 4px;
-    border-radius: 4px;
-    color: var(--rich-text-text-color);
-    font-size: 0.85em;
-  }
-
-  pre {
-    background: var(--rich-text-container-color);
-    padding: 8px;
-    border-radius: 4px;
-    color: var(--rich-text-text-color);
-
-    code {
-      padding: 0;
-      background: transparent;
-    }
-  }
-
   pre,
   blockquote {
-    margin-bottom: 22px;
+    margin-bottom: 20px;
     white-space: pre-wrap;
     unicode-bidi: plaintext;
 
@@ -61,45 +41,19 @@
   }
 
   blockquote {
-    padding-inline-start: 32px;
-    color: var(--rich-text-text-color);
+    padding-inline-start: 10px;
+    border-inline-start: 3px solid $darker-text-color;
+    color: $darker-text-color;
     white-space: normal;
-    position: relative;
 
-    &::before {
-      display: block;
-      content: '';
-      width: 24px;
-      height: 20px;
-      mask-image: url('../images/quote.svg');
-      background-color: var(--rich-text-decorations-color);
-      position: absolute;
-      inset-inline-start: 0;
-      top: 0;
-    }
-
-    blockquote {
-      margin-top: 4px;
-      border-inline-start: 3px solid var(--rich-text-decorations-color);
-      padding-inline-start: 16px;
-
-      &::before {
-        display: none;
-      }
-    }
-
-    p:last-of-type {
+    p:last-child {
       margin-bottom: 0;
     }
   }
 
   & > ul,
   & > ol {
-    margin-bottom: 22px;
-
-    &:last-child {
-      margin-bottom: 0;
-    }
+    margin-bottom: 20px;
   }
 
   b,
@@ -114,15 +68,7 @@
 
   ul,
   ol {
-    padding-inline-start: 24px;
-
-    li {
-      padding-inline-start: 8px;
-
-      &::marker {
-        text-align: end;
-      }
-    }
+    margin-inline-start: 2em;
 
     p {
       margin: 0;
@@ -130,11 +76,7 @@
   }
 
   ul {
-    list-style-type: '•';
-
-    li::marker {
-      text-align: start;
-    }
+    list-style-type: disc;
   }
 
   ol {
diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss
index 6aa94a97bc..0a05ce7c62 100644
--- a/app/javascript/styles/mastodon/rtl.scss
+++ b/app/javascript/styles/mastodon/rtl.scss
@@ -1,6 +1,3 @@
-@use 'functions' as *;
-@use 'variables' as *;
-
 body.rtl {
   direction: rtl;
 
diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss
index 620518ebf8..cab96e1ac3 100644
--- a/app/javascript/styles/mastodon/tables.scss
+++ b/app/javascript/styles/mastodon/tables.scss
@@ -1,6 +1,3 @@
-@use 'variables' as *;
-@use 'functions' as *;
-
 .table {
   width: 100%;
   max-width: 100%;
@@ -152,14 +149,6 @@ a.table-action-link {
 }
 
 .batch-table {
-  &--no-toolbar {
-    .batch-table__toolbar {
-      position: static;
-      height: 4px;
-      border-bottom: none;
-    }
-  }
-
   &__toolbar,
   &__row {
     display: flex;
@@ -351,12 +340,16 @@ a.table-action-link {
     }
   }
 
-  // Reset the status card to not have borders, background or padding when
-  // inline in the table of statuses
-  .status__card {
-    border: none;
-    background: none;
-    padding: 0;
+  .status__content {
+    padding-top: 0;
+
+    summary {
+      display: list-item;
+    }
+
+    strong {
+      font-weight: 700;
+    }
   }
 
   .nothing-here {
diff --git a/app/javascript/styles/mastodon/_variables.scss b/app/javascript/styles/mastodon/variables.scss
similarity index 71%
rename from app/javascript/styles/mastodon/_variables.scss
rename to app/javascript/styles/mastodon/variables.scss
index ea2d216441..f055e5839f 100644
--- a/app/javascript/styles/mastodon/_variables.scss
+++ b/app/javascript/styles/mastodon/variables.scss
@@ -1,6 +1,3 @@
-@use 'sass:color';
-@use 'functions' as *;
-
 // Commonly used web colors
 $black: #000000; // Black
 $white: #ffffff; // White
@@ -54,11 +51,6 @@ $ui-button-focus-background-color: $blurple-600 !default;
 $ui-button-focus-outline-color: $blurple-400 !default;
 $ui-button-focus-outline: solid 2px $ui-button-focus-outline-color !default;
 
-$ui-button-disabled-color: color.adjust(
-  $ui-button-background-color,
-  $alpha: -0.3
-) !default;
-
 $ui-button-secondary-color: $blurple-500 !default;
 $ui-button-secondary-border-color: $blurple-500 !default;
 $ui-button-secondary-focus-border-color: $blurple-300 !default;
@@ -102,13 +94,37 @@ $media-modal-media-max-height: 80%;
 
 $no-gap-breakpoint: 1175px;
 $mobile-breakpoint: 630px;
-$no-columns-breakpoint: 600px;
 
 $font-sans-serif: 'mastodon-font-sans-serif' !default;
 $font-display: 'mastodon-font-display' !default;
 $font-monospace: 'mastodon-font-monospace' !default;
 
-$emojis-requiring-inversion: 'back' 'copyright' 'curly_loop' 'currency_exchange'
-  'end' 'heavy_check_mark' 'heavy_division_sign' 'heavy_dollar_sign'
-  'heavy_minus_sign' 'heavy_multiplication_x' 'heavy_plus_sign' 'on'
-  'registered' 'soon' 'spider' 'telephone_receiver' 'tm' 'top' 'wavy_dash' !default;
+:root {
+  --dropdown-border-color: #{lighten($ui-base-color, 4%)};
+  --dropdown-background-color: #{rgba(darken($ui-base-color, 8%), 0.9)};
+  --dropdown-shadow: 0 20px 25px -5px #{rgba($base-shadow-color, 0.25)},
+    0 8px 10px -6px #{rgba($base-shadow-color, 0.25)};
+  --modal-background-color: #{rgba(darken($ui-base-color, 8%), 0.7)};
+  --modal-background-variant-color: #{rgba($ui-base-color, 0.7)};
+  --modal-border-color: #{lighten($ui-base-color, 4%)};
+  --background-border-color: #{lighten($ui-base-color, 4%)};
+  --background-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
+  --background-color: #{darken($ui-base-color, 8%)};
+  --background-color-tint: #{rgba(darken($ui-base-color, 8%), 0.9)};
+  --surface-background-color: #{darken($ui-base-color, 4%)};
+  --surface-variant-background-color: #{$ui-base-color};
+  --surface-variant-active-background-color: #{lighten($ui-base-color, 4%)};
+  --on-surface-color: #{transparentize($ui-base-color, 0.5)};
+  --avatar-border-radius: 8px;
+  --content-font-size: 15px;
+  --content-emoji-size: 20px;
+  --content-line-height: 22px;
+  --detail-content-font-size: 19px;
+  --detail-content-emoji-size: 24px;
+  --detail-content-line-height: 24px;
+  --media-outline-color: #{rgba(#fcf8ff, 0.15)};
+  --overlay-icon-shadow: drop-shadow(0 0 8px #{rgba($base-shadow-color, 0.25)});
+  --error-background-color: #{darken($error-red, 16%)};
+  --error-active-background-color: #{darken($error-red, 12%)};
+  --on-error-color: #fff;
+}
diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss
index 8d09c7d583..f467069052 100644
--- a/app/javascript/styles/mastodon/widgets.scss
+++ b/app/javascript/styles/mastodon/widgets.scss
@@ -1,6 +1,3 @@
-@use 'variables' as *;
-@use 'functions' as *;
-
 .directory {
   &__tag {
     box-sizing: border-box;
diff --git a/app/javascript/svg-icons/squiggly_arrow.svg b/app/javascript/svg-icons/squiggly_arrow.svg
deleted file mode 100644
index ae636d7dfd..0000000000
--- a/app/javascript/svg-icons/squiggly_arrow.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-<svg width="109" height="294" viewBox="0 0 109 294" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M1.76026 291.019C0.942479 291.152 0.386871 291.922 0.519274 292.74C0.651676 293.558 1.42195 294.113 2.23973 293.981L1.76026 291.019ZM47.5 148.5L48.5174 147.398L47.5 148.5ZM18.0001 156.5L16.5001 156.5L18.0001 156.5ZM56.4644 187L55.9468 185.592L56.4644 187ZM95.7394 0.194935C95.0187 -0.213447 94.1033 0.0398367 93.6949 0.760613L87.0401 12.5064C86.6318 13.2272 86.885 14.1425 87.6058 14.5509C88.3266 14.9593 89.242 14.706 89.6503 13.9852L95.5657 3.54453L106.006 9.45991C106.727 9.86826 107.643 9.61501 108.051 8.89423C108.459 8.17345 108.206 7.25808 107.485 6.84973L95.7394 0.194935ZM2.23973 293.981C15.8924 291.77 27.1878 282.838 36.0256 270.568C44.8661 258.294 51.3453 242.555 55.3614 226.415C59.3785 210.271 60.9539 193.636 59.9213 179.528C58.8957 165.516 55.2699 153.631 48.5174 147.398L46.4826 149.602C52.3545 155.022 55.9159 165.901 56.9293 179.747C57.9356 193.495 56.4016 209.81 52.4502 225.69C48.4977 241.575 42.1489 256.934 33.5913 268.815C25.0308 280.7 14.3576 288.98 1.76026 291.019L2.23973 293.981ZM48.5174 147.398C41.8156 141.211 33.9683 138.272 27.5716 139.593C24.335 140.262 21.5235 142.02 19.5438 144.91C17.5787 147.779 16.5001 151.661 16.5001 156.5L19.5001 156.5C19.5001 152.089 20.4839 148.846 22.0188 146.606C23.5391 144.386 25.6651 143.051 28.1784 142.531C33.2817 141.478 40.1844 143.789 46.4826 149.602L48.5174 147.398ZM16.5001 156.5C16.5001 166.744 21.6708 176.498 29.2488 182.798C36.8394 189.109 47.0071 192.075 56.982 188.408L55.9468 185.592C47.1899 188.812 38.1254 186.277 31.1668 180.492C24.1956 174.696 19.5001 165.755 19.5001 156.5L16.5001 156.5ZM56.982 188.408C68.8996 184.026 77.8374 172.201 84.4321 156.822C91.0496 141.389 95.442 122.069 98.0809 102.183C103.353 62.4546 101.68 20.0182 96.4457 1.10004L93.5543 1.90001C98.6367 20.2692 100.353 62.2528 95.107 101.788C92.4868 121.533 88.143 140.555 81.6749 155.64C75.184 170.777 66.6866 181.644 55.9468 185.592L56.982 188.408Z" fill="currentColor"/>
-</svg>
diff --git a/app/javascript/types/tesseract.d.ts b/app/javascript/types/tesseract.d.ts
deleted file mode 100644
index f638939b7e..0000000000
--- a/app/javascript/types/tesseract.d.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-declare module 'tesseract.js-core/tesseract-core.wasm.js';
-
-declare module 'tesseract.js/dist/worker.min.js';
diff --git a/app/lib/account_reach_finder.rb b/app/lib/account_reach_finder.rb
index 4bf5c229a5..481e254396 100644
--- a/app/lib/account_reach_finder.rb
+++ b/app/lib/account_reach_finder.rb
@@ -1,16 +1,12 @@
 # frozen_string_literal: true
 
 class AccountReachFinder
-  RECENT_LIMIT = 2_000
-  STATUS_LIMIT = 200
-  STATUS_SINCE = 2.days
-
   def initialize(account)
     @account = account
   end
 
   def inboxes
-    (followers_inboxes + reporters_inboxes + recently_mentioned_inboxes + recently_followed_inboxes + recently_requested_inboxes + relay_inboxes).uniq
+    (followers_inboxes + reporters_inboxes + recently_mentioned_inboxes + relay_inboxes).uniq
   end
 
   private
@@ -24,46 +20,13 @@ class AccountReachFinder
   end
 
   def recently_mentioned_inboxes
-    Account
-      .joins(:mentions)
-      .where(mentions: { status: recent_statuses })
-      .inboxes
-      .take(RECENT_LIMIT)
-  end
+    cutoff_id       = Mastodon::Snowflake.id_at(2.days.ago, with_random: false)
+    recent_statuses = @account.statuses.recent.where(id: cutoff_id...).limit(200)
 
-  def recently_followed_inboxes
-    @account
-      .following
-      .where(follows: { created_at: recent_date_cutoff... })
-      .inboxes
-      .take(RECENT_LIMIT)
-  end
-
-  def recently_requested_inboxes
-    Account
-      .where(id: @account.follow_requests.where({ created_at: recent_date_cutoff... }).select(:target_account_id))
-      .inboxes
-      .take(RECENT_LIMIT)
+    Account.joins(:mentions).where(mentions: { status: recent_statuses }).inboxes.take(2000)
   end
 
   def relay_inboxes
     Relay.enabled.pluck(:inbox_url)
   end
-
-  def oldest_status_id
-    Mastodon::Snowflake
-      .id_at(recent_date_cutoff, with_random: false)
-  end
-
-  def recent_date_cutoff
-    @account.suspended? && @account.suspension_origin_local? ? @account.suspended_at - STATUS_SINCE : STATUS_SINCE.ago
-  end
-
-  def recent_statuses
-    @account
-      .statuses
-      .recent
-      .where(id: oldest_status_id...)
-      .limit(STATUS_LIMIT)
-  end
 end
diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index d726f5e68c..5416d90015 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -20,9 +20,9 @@ class ActivityPub::Activity
   end
 
   class << self
-    def factory(json, account, **)
+    def factory(json, account, **options)
       @json = json
-      klass&.new(json, account, **)
+      klass&.new(json, account, **options)
     end
 
     private
@@ -130,7 +130,12 @@ class ActivityPub::Activity
 
   def first_mentioned_local_account
     audience = (as_array(@json['to']) + as_array(@json['cc'])).map { |x| value_or_id(x) }.uniq
-    ActivityPub::TagManager.instance.uris_to_local_accounts(audience).first
+    local_usernames = audience.select { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }
+                              .map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username) }
+
+    return if local_usernames.empty?
+
+    Account.local.where(username: local_usernames).first
   end
 
   def first_local_follower
diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb
index a6650a0a9e..ceeccd2b3f 100644
--- a/app/lib/activitypub/activity/announce.rb
+++ b/app/lib/activitypub/activity/announce.rb
@@ -5,7 +5,6 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
 
   def perform
     return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity?
-    return reject_payload! if @object.nil?
 
     with_redis_lock("announce:#{value_or_id(@object)}") do
       original_status = status_from_object
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 6cdc21ce75..56176383e1 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -4,8 +4,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   include FormattingHelper
   include NgRuleHelper
 
-  LINK_MEDIA_TYPES = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].freeze
-
   def perform
     @account.schedule_refresh_if_stale!
 
@@ -67,7 +65,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
       @status = Status.create!(@params)
       attach_tags(@status)
       attach_mentions(@status)
-      attach_counts(@status)
     end
 
     resolve_thread(@status)
@@ -246,18 +243,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     end
   end
 
-  def attach_counts(status)
-    likes = @status_parser.favourites_count
-    shares = @status_parser.reblogs_count
-    return if likes.nil? && shares.nil?
-
-    status.status_stat.tap do |status_stat|
-      status_stat.untrusted_reblogs_count = shares unless shares.nil?
-      status_stat.untrusted_favourites_count = likes unless likes.nil?
-      status_stat.save if status_stat.changed?
-    end
-  end
-
   def process_tags
     return if @object['tag'].nil?
 
@@ -293,7 +278,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     return if account.nil?
 
     @mentions << Mention.new(account: account, silent: false)
-  rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
+  rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
     @unresolved_mentions << tag['href']
   end
 
@@ -354,7 +339,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
         media_attachment.download_file!
         media_attachment.download_thumbnail!
         media_attachment.save
-      rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
+      rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
         RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id)
       rescue Seahorse::Client::NetworkingError => e
         Rails.logger.warn "Error storing media attachment: #{e}"
@@ -435,7 +420,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     collection = @object['replies']
     return if collection.blank?
 
-    replies = ActivityPub::FetchRepliesService.new.call(status.account.uri, collection, allow_synchronous_requests: false, request_id: @options[:request_id])
+    replies = ActivityPub::FetchRepliesService.new.call(status, collection, allow_synchronous_requests: false, request_id: @options[:request_id])
     return unless replies.nil?
 
     uri = value_or_id(collection)
@@ -565,7 +550,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   def addresses_local_accounts?
     return true if @options[:delivered_to_account_id]
 
-    ActivityPub::TagManager.instance.uris_to_local_accounts((audience_to + audience_cc).uniq).exists?
+    local_usernames = (audience_to + audience_cc).uniq.select { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }.map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username) }
+
+    return false if local_usernames.empty?
+
+    Account.local.exists?(username: local_usernames)
   end
 
   def tombstone_exists?
@@ -643,6 +632,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     @quote ||= quote_from_tags || @object['quote'] || @object['quoteUrl'] || @object['quoteURL'] || @object['_misskey_quote']
   end
 
+  LINK_MEDIA_TYPES = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].freeze
+
   def quote_from_tags
     return @quote_from_tags if defined?(@quote_from_tags)
 
diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb
index 9122b07483..5b9437eb8d 100644
--- a/app/lib/activitypub/adapter.rb
+++ b/app/lib/activitypub/adapter.rb
@@ -17,7 +17,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
 
     options         = serialization_options(options)
     serialized_hash = serializer.serializable_hash(options.merge(named_contexts: named_contexts, context_extensions: context_extensions))
-    serialized_hash = serialized_hash.slice(*options[:fields]) if options[:fields]
+    serialized_hash = serialized_hash.select { |k, _| options[:fields].include?(k) } if options[:fields]
     serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options)
 
     { '@context': serialized_context(named_contexts, context_extensions) }.merge(serialized_hash)
diff --git a/app/lib/activitypub/case_transform.rb b/app/lib/activitypub/case_transform.rb
index 33e3ef6fac..5f2430600e 100644
--- a/app/lib/activitypub/case_transform.rb
+++ b/app/lib/activitypub/case_transform.rb
@@ -4,7 +4,6 @@ module ActivityPub::CaseTransform
   class << self
     NO_CONVERT_VALUES = %w(
       _misskey_content
-      _misskey_license
       _misskey_quote
     ).freeze
 
diff --git a/app/lib/activitypub/parser/custom_emoji_parser.rb b/app/lib/activitypub/parser/custom_emoji_parser.rb
index fcd36b0395..0c38f55168 100644
--- a/app/lib/activitypub/parser/custom_emoji_parser.rb
+++ b/app/lib/activitypub/parser/custom_emoji_parser.rb
@@ -34,6 +34,6 @@ class ActivityPub::Parser::CustomEmojiParser
   end
 
   def license
-    @json.dig('_misskey_license', 'freeText') || @json['license'] || @json['licence']
+    @json['license'] || @json['licence']
   end
 end
diff --git a/app/lib/activitypub/parser/status_parser.rb b/app/lib/activitypub/parser/status_parser.rb
index 1968f18468..5867f07d86 100644
--- a/app/lib/activitypub/parser/status_parser.rb
+++ b/app/lib/activitypub/parser/status_parser.rb
@@ -5,9 +5,6 @@ class ActivityPub::Parser::StatusParser
 
   NORMALIZED_LOCALE_NAMES = LanguagesHelper::SUPPORTED_LOCALES.keys.index_by(&:downcase).freeze
 
-  SCAN_SEARCHABILITY_RE = /\[searchability:(public|followers|reactors|private)\]/
-  SCAN_SEARCHABILITY_FEDIBIRD_RE = /searchable_by_(all_users|followers_only|reacted_users_only|nobody)/
-
   # @param [Hash] json
   # @param [Hash] options
   # @option options [String] :followers_collection
@@ -127,14 +124,6 @@ class ActivityPub::Parser::StatusParser
     lang.presence && NORMALIZED_LOCALE_NAMES.fetch(lang.downcase.to_sym, lang)
   end
 
-  def favourites_count
-    @object.dig(:likes, :totalItems)
-  end
-
-  def reblogs_count
-    @object.dig(:shares, :totalItems)
-  end
-
   private
 
   def raw_language_code
@@ -185,6 +174,9 @@ class ActivityPub::Parser::StatusParser
     %i(public unlisted).include?(visibility) ? :public : :limited
   end
 
+  SCAN_SEARCHABILITY_RE = /\[searchability:(public|followers|reactors|private)\]/
+  SCAN_SEARCHABILITY_FEDIBIRD_RE = /searchable_by_(all_users|followers_only|reacted_users_only|nobody)/
+
   def default_searchability_from_bio?
     note = @account.note
     return false if note.blank?
diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb
index 3ead162ec3..d773e7e11a 100644
--- a/app/lib/activitypub/tag_manager.rb
+++ b/app/lib/activitypub/tag_manager.rb
@@ -13,7 +13,7 @@ class ActivityPub::TagManager
   }.freeze
 
   def public_collection?(uri)
-    uri == COLLECTIONS[:public] || %w(as:Public Public).include?(uri)
+    uri == COLLECTIONS[:public] || uri == 'as:Public' || uri == 'Public'
   end
 
   def url_for(target)
@@ -98,34 +98,8 @@ class ActivityPub::TagManager
     account_status_shares_url(target.account, target)
   end
 
-  def following_uri_for(target, ...)
-    raise ArgumentError, 'target must be a local account' unless target.local?
-
-    account_following_index_url(target, ...)
-  end
-
-  def followers_uri_for(target, ...)
-    return target.followers_url.presence unless target.local?
-
-    account_followers_url(target, ...)
-  end
-
-  def collection_uri_for(target, ...)
-    raise NotImplementedError unless target.local?
-
-    account_collection_url(target, ...)
-  end
-
-  def inbox_uri_for(target)
-    raise NotImplementedError unless target.local?
-
-    target.instance_actor? ? instance_actor_inbox_url : account_inbox_url(target)
-  end
-
-  def outbox_uri_for(target, ...)
-    raise NotImplementedError unless target.local?
-
-    target.instance_actor? ? instance_actor_outbox_url(...) : account_outbox_url(target, ...)
+  def followers_uri_for(target)
+    target.local? ? account_followers_url(target) : target.followers_url.presence
   end
 
   # Primary audience of a status
@@ -137,9 +111,9 @@ class ActivityPub::TagManager
     when 'public'
       [COLLECTIONS[:public]]
     when 'unlisted', 'public_unlisted', 'private'
-      [followers_uri_for(status.account)]
+      [account_followers_url(status.account)]
     when 'login'
-      [followers_uri_for(status.account), 'as:LoginOnly', 'kmyblue:LoginOnly', 'LoginUser']
+      [account_followers_url(status.account), 'as:LoginOnly', 'kmyblue:LoginOnly', 'LoginUser']
     when 'direct'
       if status.account.silenced?
         # Only notify followers if the account is locally silenced
@@ -182,7 +156,7 @@ class ActivityPub::TagManager
 
     case status.visibility
     when 'public'
-      cc << followers_uri_for(status.account)
+      cc << account_followers_url(status.account)
     when 'unlisted', 'public_unlisted'
       cc << COLLECTIONS[:public]
     end
@@ -242,19 +216,6 @@ class ActivityPub::TagManager
     path_params[param]
   end
 
-  def uris_to_local_accounts(uris)
-    usernames = []
-    ids = []
-
-    uris.each do |uri|
-      param, value = uri_to_local_account_params(uri)
-      usernames << value.downcase if param == :username
-      ids << value if param == :id
-    end
-
-    Account.local.with_username(usernames).or(Account.local.where(id: ids))
-  end
-
   def uri_to_actor(uri)
     uri_to_resource(uri, Account)
   end
@@ -265,7 +226,7 @@ class ActivityPub::TagManager
     if local_uri?(uri)
       case klass.name
       when 'Account'
-        uris_to_local_accounts([uri]).first
+        klass.find_local(uri_to_local_id(uri, :username))
       else
         StatusFinder.new(uri).status
       end
@@ -347,20 +308,4 @@ class ActivityPub::TagManager
       end
     end
   end
-
-  private
-
-  def uri_to_local_account_params(uri)
-    return unless local_uri?(uri)
-
-    path_params = Rails.application.routes.recognize_path(uri)
-
-    # TODO: handle numeric IDs
-    case path_params[:controller]
-    when 'accounts'
-      [:username, path_params[:username]]
-    when 'instance_actors'
-      [:id, -99]
-    end
-  end
 end
diff --git a/app/lib/admin/metrics/dimension/space_usage_dimension.rb b/app/lib/admin/metrics/dimension/space_usage_dimension.rb
index 0d3fd8db33..f1b6dba040 100644
--- a/app/lib/admin/metrics/dimension/space_usage_dimension.rb
+++ b/app/lib/admin/metrics/dimension/space_usage_dimension.rb
@@ -45,6 +45,7 @@ class Admin::Metrics::Dimension::SpaceUsageDimension < Admin::Metrics::Dimension
       PreviewCard.sum(:image_file_size),
       Account.sum(Arel.sql('COALESCE(avatar_file_size, 0) + COALESCE(header_file_size, 0)')),
       Backup.sum(:dump_file_size),
+      Import.sum(:data_file_size),
       SiteUpload.sum(:file_file_size),
     ].sum
 
diff --git a/app/lib/admin/system_check/database_schema_check.rb b/app/lib/admin/system_check/database_schema_check.rb
index a3ef0613ea..c2f01fd55b 100644
--- a/app/lib/admin/system_check/database_schema_check.rb
+++ b/app/lib/admin/system_check/database_schema_check.rb
@@ -6,7 +6,7 @@ class Admin::SystemCheck::DatabaseSchemaCheck < Admin::SystemCheck::BaseCheck
   end
 
   def pass?
-    !ActiveRecord::Base.connection_pool.migration_context.needs_migration?
+    !ActiveRecord::Base.connection.migration_context.needs_migration?
   end
 
   def message
diff --git a/app/lib/admin/system_check/media_privacy_check.rb b/app/lib/admin/system_check/media_privacy_check.rb
index 378a8ce294..2ddc8e8b07 100644
--- a/app/lib/admin/system_check/media_privacy_check.rb
+++ b/app/lib/admin/system_check/media_privacy_check.rb
@@ -76,7 +76,7 @@ class Admin::SystemCheck::MediaPrivacyCheck < Admin::SystemCheck::BaseCheck
 
   def media_attachment
     @media_attachment ||= begin
-      attachment = Account.representative.media_attachments.take
+      attachment = Account.representative.media_attachments.first
       if attachment.present?
         attachment.touch
         attachment
diff --git a/app/lib/annual_report.rb b/app/lib/annual_report.rb
index 275cc4b87d..cf4297f2a4 100644
--- a/app/lib/annual_report.rb
+++ b/app/lib/annual_report.rb
@@ -17,21 +17,11 @@ class AnnualReport
 
   SCHEMA = 1
 
-  def self.table_name_prefix
-    'annual_report_'
-  end
-
   def initialize(account, year)
     @account = account
     @year = year
   end
 
-  def self.prepare(year)
-    SOURCES.each do |klass|
-      klass.prepare(year)
-    end
-  end
-
   def generate
     return if GeneratedAnnualReport.exists?(account: @account, year: @year)
 
diff --git a/app/lib/annual_report/commonly_interacted_with_accounts.rb b/app/lib/annual_report/commonly_interacted_with_accounts.rb
index c2aee44dea..e7482f0d52 100644
--- a/app/lib/annual_report/commonly_interacted_with_accounts.rb
+++ b/app/lib/annual_report/commonly_interacted_with_accounts.rb
@@ -1,14 +1,13 @@
 # frozen_string_literal: true
 
 class AnnualReport::CommonlyInteractedWithAccounts < AnnualReport::Source
-  MINIMUM_INTERACTIONS = 1
   SET_SIZE = 40
 
   def generate
     {
       commonly_interacted_with_accounts: commonly_interacted_with_accounts.map do |(account_id, count)|
                                            {
-                                             account_id: account_id.to_s,
+                                             account_id: account_id,
                                              count: count,
                                            }
                                          end,
@@ -18,10 +17,6 @@ class AnnualReport::CommonlyInteractedWithAccounts < AnnualReport::Source
   private
 
   def commonly_interacted_with_accounts
-    report_statuses.where.not(in_reply_to_account_id: @account.id).group(:in_reply_to_account_id).having(minimum_interaction_count).order(count_all: :desc).limit(SET_SIZE).count
-  end
-
-  def minimum_interaction_count
-    Arel.star.count.gt(MINIMUM_INTERACTIONS)
+    report_statuses.where.not(in_reply_to_account_id: @account.id).group(:in_reply_to_account_id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('in_reply_to_account_id, count(*) AS total'))
   end
 end
diff --git a/app/lib/annual_report/most_reblogged_accounts.rb b/app/lib/annual_report/most_reblogged_accounts.rb
index a23734fce3..39ed3868ea 100644
--- a/app/lib/annual_report/most_reblogged_accounts.rb
+++ b/app/lib/annual_report/most_reblogged_accounts.rb
@@ -1,14 +1,13 @@
 # frozen_string_literal: true
 
 class AnnualReport::MostRebloggedAccounts < AnnualReport::Source
-  MINIMUM_REBLOGS = 1
   SET_SIZE = 10
 
   def generate
     {
       most_reblogged_accounts: most_reblogged_accounts.map do |(account_id, count)|
                                  {
-                                   account_id: account_id.to_s,
+                                   account_id: account_id,
                                    count: count,
                                  }
                                end,
@@ -18,10 +17,6 @@ class AnnualReport::MostRebloggedAccounts < AnnualReport::Source
   private
 
   def most_reblogged_accounts
-    report_statuses.where.not(reblog_of_id: nil).joins(reblog: :account).group(accounts: [:id]).having(minimum_reblog_count).order(count_all: :desc).limit(SET_SIZE).count
-  end
-
-  def minimum_reblog_count
-    Arel.star.count.gt(MINIMUM_REBLOGS)
+    report_statuses.where.not(reblog_of_id: nil).joins(reblog: :account).group('accounts.id').having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('accounts.id, count(*) as total'))
   end
 end
diff --git a/app/lib/annual_report/most_used_apps.rb b/app/lib/annual_report/most_used_apps.rb
index a2e1aca452..fb1ca1d167 100644
--- a/app/lib/annual_report/most_used_apps.rb
+++ b/app/lib/annual_report/most_used_apps.rb
@@ -17,6 +17,6 @@ class AnnualReport::MostUsedApps < AnnualReport::Source
   private
 
   def most_used_apps
-    report_statuses.joins(:application).group(oauth_applications: [:name]).order(count_all: :desc).limit(SET_SIZE).count
+    report_statuses.joins(:application).group('oauth_applications.name').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('oauth_applications.name, count(*) as total'))
   end
 end
diff --git a/app/lib/annual_report/percentiles.rb b/app/lib/annual_report/percentiles.rb
index 2b0305c415..0251cb66ad 100644
--- a/app/lib/annual_report/percentiles.rb
+++ b/app/lib/annual_report/percentiles.rb
@@ -1,37 +1,62 @@
 # frozen_string_literal: true
 
 class AnnualReport::Percentiles < AnnualReport::Source
-  def self.prepare(year)
-    AnnualReport::StatusesPerAccountCount.connection.exec_query(<<~SQL.squish, nil, [year, Mastodon::Snowflake.id_at(DateTime.new(year).beginning_of_year), Mastodon::Snowflake.id_at(DateTime.new(year).end_of_year)])
-      INSERT INTO annual_report_statuses_per_account_counts (year, account_id, statuses_count)
-      SELECT $1, account_id, count(*)
-      FROM statuses
-      WHERE id BETWEEN $2 AND $3
-      AND (local OR uri IS NULL)
-      GROUP BY account_id
-      ON CONFLICT (year, account_id) DO NOTHING
-    SQL
-  end
-
   def generate
     {
       percentiles: {
-        statuses: 100.0 - ((total_with_fewer_statuses / (total_with_any_statuses + 1.0)) * 100),
+        followers: (total_with_fewer_followers / (total_with_any_followers + 1.0)) * 100,
+        statuses: (total_with_fewer_statuses / (total_with_any_statuses + 1.0)) * 100,
       },
     }
   end
 
   private
 
+  def followers_gained
+    @followers_gained ||= @account.passive_relationships.where("date_part('year', follows.created_at) = ?", @year).count
+  end
+
   def statuses_created
     @statuses_created ||= report_statuses.count
   end
 
+  def total_with_fewer_followers
+    @total_with_fewer_followers ||= Follow.find_by_sql([<<~SQL.squish, { year: @year, comparison: followers_gained }]).first.total
+      WITH tmp0 AS (
+        SELECT follows.target_account_id
+        FROM follows
+        INNER JOIN accounts ON accounts.id = follows.target_account_id
+        WHERE date_part('year', follows.created_at) = :year
+          AND accounts.domain IS NULL
+        GROUP BY follows.target_account_id
+        HAVING COUNT(*) < :comparison
+      )
+      SELECT count(*) AS total
+      FROM tmp0
+    SQL
+  end
+
   def total_with_fewer_statuses
-    @total_with_fewer_statuses ||= AnnualReport::StatusesPerAccountCount.where(year: year).where(statuses_count: ...statuses_created).count
+    @total_with_fewer_statuses ||= Status.find_by_sql([<<~SQL.squish, { comparison: statuses_created, min_id: year_as_snowflake_range.first, max_id: year_as_snowflake_range.last }]).first.total
+      WITH tmp0 AS (
+        SELECT statuses.account_id
+        FROM statuses
+        INNER JOIN accounts ON accounts.id = statuses.account_id
+        WHERE statuses.id BETWEEN :min_id AND :max_id
+          AND accounts.domain IS NULL
+        GROUP BY statuses.account_id
+        HAVING count(*) < :comparison
+      )
+      SELECT count(*) AS total
+      FROM tmp0
+    SQL
+  end
+
+  def total_with_any_followers
+    @total_with_any_followers ||= Follow.where("date_part('year', follows.created_at) = ?", @year).joins(:target_account).merge(Account.local).count('distinct follows.target_account_id')
   end
 
   def total_with_any_statuses
-    @total_with_any_statuses ||= AnnualReport::StatusesPerAccountCount.where(year: year).count
+    @total_with_any_statuses ||= Status.where(id: year_as_snowflake_range).joins(:account).merge(Account.local).count('distinct statuses.account_id')
   end
 end
diff --git a/app/lib/annual_report/source.rb b/app/lib/annual_report/source.rb
index 86528731f5..cb9f7b16e3 100644
--- a/app/lib/annual_report/source.rb
+++ b/app/lib/annual_report/source.rb
@@ -8,14 +8,6 @@ class AnnualReport::Source
     @year = year
   end
 
-  def self.prepare(_year)
-    # Use this method if any pre-calculations must be made before individual annual reports are generated
-  end
-
-  def generate
-    raise NotImplementedError
-  end
-
   protected
 
   def report_statuses
diff --git a/app/lib/annual_report/top_hashtags.rb b/app/lib/annual_report/top_hashtags.rb
index 42420a2770..32bd10d698 100644
--- a/app/lib/annual_report/top_hashtags.rb
+++ b/app/lib/annual_report/top_hashtags.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
 class AnnualReport::TopHashtags < AnnualReport::Source
-  MINIMUM_TAGGINGS = 1
   SET_SIZE = 40
 
   def generate
@@ -18,16 +17,6 @@ class AnnualReport::TopHashtags < AnnualReport::Source
   private
 
   def top_hashtags
-    Tag.joins(:statuses).where(statuses: { id: report_statuses.select(:id) }).group(coalesced_tag_names).having(minimum_taggings_count).order(count_all: :desc).limit(SET_SIZE).count
-  end
-
-  def minimum_taggings_count
-    Arel.star.count.gt(MINIMUM_TAGGINGS)
-  end
-
-  def coalesced_tag_names
-    Arel.sql(<<~SQL.squish)
-      COALESCE(tags.display_name, tags.name)
-    SQL
+    Tag.joins(:statuses).where(statuses: { id: report_statuses.select(:id) }).group(:id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('COALESCE(tags.display_name, tags.name), count(*) AS total'))
   end
 end
diff --git a/app/lib/annual_report/top_statuses.rb b/app/lib/annual_report/top_statuses.rb
index 74b129595a..c5abeaa58d 100644
--- a/app/lib/annual_report/top_statuses.rb
+++ b/app/lib/annual_report/top_statuses.rb
@@ -8,9 +8,9 @@ class AnnualReport::TopStatuses < AnnualReport::Source
 
     {
       top_statuses: {
-        by_reblogs: top_reblogs&.to_s,
-        by_favourites: top_favourites&.to_s,
-        by_replies: top_replies&.to_s,
+        by_reblogs: top_reblogs,
+        by_favourites: top_favourites,
+        by_replies: top_replies,
       },
     }
   end
diff --git a/app/lib/antispam.rb b/app/lib/antispam.rb
deleted file mode 100644
index 4ebf192485..0000000000
--- a/app/lib/antispam.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-# frozen_string_literal: true
-
-class Antispam
-  include Redisable
-
-  ACCOUNT_AGE_EXEMPTION = 1.week.freeze
-
-  class DummyStatus < SimpleDelegator
-    def self.model_name
-      Mention.model_name
-    end
-
-    def active_mentions
-      # Don't use the scope but the in-memory array
-      mentions.filter { |mention| !mention.silent? }
-    end
-  end
-
-  class SilentlyDrop < StandardError
-    attr_reader :status
-
-    def initialize(status)
-      super()
-
-      status.created_at = Time.now.utc
-      status.id = Mastodon::Snowflake.id_at(status.created_at)
-      status.in_reply_to_account_id = status.thread&.account_id
-
-      status.delete # Make sure this is not persisted
-
-      @status = DummyStatus.new(status)
-    end
-  end
-
-  def local_preflight_check!(status)
-    return unless spammy_texts.any? { |spammy_text| status.text.include?(spammy_text) }
-    return unless suspicious_reply_or_mention?(status)
-    return unless status.account.created_at >= ACCOUNT_AGE_EXEMPTION.ago
-
-    report_if_needed!(status.account)
-
-    raise SilentlyDrop, status
-  end
-
-  private
-
-  def spammy_texts
-    redis.smembers('antispam:spammy_texts')
-  end
-
-  def suspicious_reply_or_mention?(status)
-    parent = status.thread
-    return true if parent.present? && !Follow.exists?(account_id: parent.account_id, target_account: status.account_id)
-
-    account_ids = status.mentions.map(&:account_id).uniq
-    !Follow.exists?(account_id: account_ids, target_account_id: status.account.id)
-  end
-
-  def report_if_needed!(account)
-    return if Report.unresolved.exists?(account: Account.representative, target_account: account)
-
-    Report.create!(account: Account.representative, target_account: account, category: :spam, comment: 'Account automatically reported for posting a banned URL')
-  end
-end
diff --git a/app/lib/application_extension.rb b/app/lib/application_extension.rb
index d8090d15bc..d7aaeba5bd 100644
--- a/app/lib/application_extension.rb
+++ b/app/lib/application_extension.rb
@@ -3,18 +3,14 @@
 module ApplicationExtension
   extend ActiveSupport::Concern
 
-  APP_NAME_LIMIT = 60
-  APP_REDIRECT_URI_LIMIT = 2_000
-  APP_WEBSITE_LIMIT = 2_000
-
   included do
     include Redisable
 
     has_many :created_users, class_name: 'User', foreign_key: 'created_by_application_id', inverse_of: :created_by_application
 
-    validates :name, length: { maximum: APP_NAME_LIMIT }
-    validates :redirect_uri, length: { maximum: APP_REDIRECT_URI_LIMIT }
-    validates :website, url: true, length: { maximum: APP_WEBSITE_LIMIT }, if: :website?
+    validates :name, length: { maximum: 60 }
+    validates :website, url: true, length: { maximum: 2_000 }, if: :website?
+    validates :redirect_uri, length: { maximum: 2_000 }
 
     # The relationship used between Applications and AccessTokens is using
     # dependent: delete_all, which means the ActiveRecord callback in
diff --git a/app/lib/content_security_policy.rb b/app/lib/content_security_policy.rb
index fc42e2d48b..c764d1856d 100644
--- a/app/lib/content_security_policy.rb
+++ b/app/lib/content_security_policy.rb
@@ -10,7 +10,7 @@ class ContentSecurityPolicy
   end
 
   def media_hosts
-    [assets_host, cdn_host_value, paperclip_root_url].concat(extra_media_hosts).compact
+    [assets_host, cdn_host_value, paperclip_root_url].compact
   end
 
   def sso_host
@@ -31,10 +31,6 @@ class ContentSecurityPolicy
 
   private
 
-  def extra_media_hosts
-    ENV.fetch('EXTRA_MEDIA_HOSTS', '').split(/(?:\s*,\s*|\s+)/)
-  end
-
   def url_from_configured_asset_host
     Rails.configuration.action_controller.asset_host
   end
diff --git a/app/lib/domain_resource.rb b/app/lib/domain_resource.rb
deleted file mode 100644
index 59a29d8797..0000000000
--- a/app/lib/domain_resource.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-class DomainResource
-  attr_reader :domain
-
-  RESOLVE_TIMEOUT = 5
-
-  def initialize(domain)
-    @domain = domain
-  end
-
-  def mx
-    Resolv::DNS.open do |dns|
-      dns.timeouts = RESOLVE_TIMEOUT
-      dns
-        .getresources(domain, Resolv::DNS::Resource::IN::MX)
-        .to_a
-        .map { |mx| mx.exchange.to_s }
-        .compact_blank
-    end
-  end
-end
diff --git a/app/lib/emoji_formatter.rb b/app/lib/emoji_formatter.rb
index 1574d4588d..c0302767ef 100644
--- a/app/lib/emoji_formatter.rb
+++ b/app/lib/emoji_formatter.rb
@@ -24,15 +24,7 @@ class EmojiFormatter
   def to_s
     return html if custom_emojis.empty? || html.blank?
 
-    begin
-      tree = Nokogiri::HTML5.fragment(html)
-    rescue ArgumentError
-      # This can happen if one of the Nokogumbo limits is encountered
-      # Unfortunately, it does not use a more precise error class
-      # nor allows more graceful handling
-      return ''
-    end
-
+    tree = Nokogiri::HTML5.fragment(html)
     tree.xpath('./text()|.//text()[not(ancestor[@class="invisible"])]').to_a.each do |node|
       i                     = -1
       inside_shortname      = false
diff --git a/app/lib/fasp/request.rb b/app/lib/fasp/request.rb
deleted file mode 100644
index 2addbe8502..0000000000
--- a/app/lib/fasp/request.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-
-class Fasp::Request
-  def initialize(provider)
-    @provider = provider
-  end
-
-  def get(path)
-    perform_request(:get, path)
-  end
-
-  def post(path, body: nil)
-    perform_request(:post, path, body:)
-  end
-
-  def delete(path, body: nil)
-    perform_request(:delete, path, body:)
-  end
-
-  private
-
-  def perform_request(verb, path, body: nil)
-    url = @provider.url(path)
-    body = body.present? ? body.to_json : ''
-    headers = request_headers(verb, url, body)
-    response = HTTP.headers(headers).send(verb, url, body:)
-    validate!(response)
-
-    response.parse if response.body.present?
-  end
-
-  def request_headers(verb, url, body = '')
-    result = {
-      'accept' => 'application/json',
-      'content-digest' => content_digest(body),
-    }
-    result.merge(signature_headers(verb, url, result))
-  end
-
-  def content_digest(body)
-    "sha-256=:#{OpenSSL::Digest.base64digest('sha256', body || '')}:"
-  end
-
-  def signature_headers(verb, url, headers)
-    linzer_request = Linzer.new_request(verb, url, {}, headers)
-    message = Linzer::Message.new(linzer_request)
-    key = Linzer.new_ed25519_key(@provider.server_private_key_pem, @provider.remote_identifier)
-    signature = Linzer.sign(key, message, %w(@method @target-uri content-digest))
-    Linzer::Signer.send(:populate_parameters, key, {})
-
-    signature.to_h
-  end
-
-  def validate!(response)
-    content_digest_header = response.headers['content-digest']
-    raise Mastodon::SignatureVerificationError, 'content-digest missing' if content_digest_header.blank?
-    raise Mastodon::SignatureVerificationError, 'content-digest does not match' if content_digest_header != content_digest(response.body)
-
-    signature_input = response.headers['signature-input']&.encode('UTF-8')
-    raise Mastodon::SignatureVerificationError, 'signature-input is missing' if signature_input.blank?
-
-    linzer_response = Linzer.new_response(
-      response.body,
-      response.status,
-      {
-        'content-digest' => content_digest_header,
-        'signature-input' => signature_input,
-        'signature' => response.headers['signature'],
-      }
-    )
-    message = Linzer::Message.new(linzer_response)
-    key = Linzer.new_ed25519_public_key(@provider.provider_public_key_pem)
-    signature = Linzer::Signature.build(message.headers)
-    Linzer.verify(key, message, signature)
-  end
-end
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index 54cb464463..1a558cacbe 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -32,15 +32,6 @@ class FeedManager
     "feed:#{type}:#{id}:#{subtype}"
   end
 
-  # The number of items in the given timeline
-  # @param [Symbol] type
-  # @param [Integer] id
-  # @param [Symbol] subtype
-  # @return [Integer]
-  def timeline_size(type, id, subtype = nil)
-    redis.zcard(key(type, id, subtype))
-  end
-
   # The filter result of the status to a particular feed
   # @param [Symbol] timeline_type
   # @param [Status] status
@@ -51,7 +42,7 @@ class FeedManager
     when :home
       filter_from_home(status, receiver.id, build_crutches(receiver.id, [status]), :home)
     when :list
-      (filter_from_list?(status, receiver) ? :filter : nil) || filter_from_home(status, receiver.account_id, build_crutches(receiver.account_id, [status], list: receiver, stl_home: stl_home), :list, stl_home: stl_home)
+      (filter_from_list?(status, receiver) ? :filter : nil) || filter_from_home(status, receiver.account_id, build_crutches(receiver.account_id, [status], list: receiver), :list, stl_home: stl_home)
     when :mentions
       filter_from_mentions?(status, receiver.id) ? :filter : nil
     when :tags
@@ -661,7 +652,7 @@ class FeedManager
   # @param [Array<Status>] statuses
   # @param [List] list
   # @return [Hash]
-  def build_crutches(receiver_id, statuses, list: nil, stl_home: false)
+  def build_crutches(receiver_id, statuses, list: nil)
     crutches = {}
 
     crutches[:active_mentions] = crutches_active_mentions(statuses)
@@ -685,7 +676,7 @@ class FeedManager
     crutches[:muting]               = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true)
     crutches[:domain_blocking]      = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.flat_map { |s| [s.account.domain, s.reblog&.account&.domain] }.compact).pluck(:domain).index_with(true)
     crutches[:blocked_by]           = Block.where(target_account_id: receiver_id, account_id: statuses.map { |s| [s.account_id, s.reblog&.account_id] }.flatten.compact).pluck(:account_id).index_with(true)
-    crutches[:exclusive_list_users] = crutches_exclusive_list_users(receiver_id, statuses) if list.nil? || stl_home
+    crutches[:exclusive_list_users] = crutches_exclusive_list_users(receiver_id, statuses) if list.blank?
     crutches[:exclusive_antenna_users] = crutches_exclusive_antenna_users(receiver_id, statuses)
 
     crutches
diff --git a/app/lib/hashtag_normalizer.rb b/app/lib/hashtag_normalizer.rb
index 5347271194..49fa6101de 100644
--- a/app/lib/hashtag_normalizer.rb
+++ b/app/lib/hashtag_normalizer.rb
@@ -16,7 +16,7 @@ class HashtagNormalizer
   end
 
   def lowercase(str)
-    str.downcase.to_s
+    str.mb_chars.downcase.to_s
   end
 
   def cjk_width(str)
diff --git a/app/lib/importer/statuses_index_importer.rb b/app/lib/importer/statuses_index_importer.rb
index 8e7da0f387..9ef58b1358 100644
--- a/app/lib/importer/statuses_index_importer.rb
+++ b/app/lib/importer/statuses_index_importer.rb
@@ -80,7 +80,7 @@ class Importer::StatusesIndexImporter < Importer::BaseImporter
   end
 
   def local_votes_scope
-    Poll.joins(:votes).where(votes: { account: Account.local }).select(polls: [:id, :status_id])
+    Poll.joins(:votes).where(votes: { account: Account.local }).select('polls.id, polls.status_id')
   end
 
   def local_statuses_scope
diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb
index a8004f2925..e4e815c38d 100644
--- a/app/lib/link_details_extractor.rb
+++ b/app/lib/link_details_extractor.rb
@@ -46,7 +46,7 @@ class LinkDetailsExtractor
     end
 
     def image
-      obj = first_of_hash(json['image'])
+      obj = first_of_value(json['image'])
 
       return obj['url'] if obj.is_a?(Hash)
 
@@ -85,15 +85,15 @@ class LinkDetailsExtractor
     private
 
     def author
-      first_of_hash(json['author']) || {}
+      first_of_value(json['author']) || {}
     end
 
     def publisher
-      first_of_hash(json['publisher']) || {}
+      first_of_value(json['publisher']) || {}
     end
 
-    def first_of_hash(arr)
-      arr.is_a?(Array) ? arr.flatten.find { |item| item.is_a?(Hash) } : arr
+    def first_of_value(arr)
+      arr.is_a?(Array) ? arr.first : arr
     end
 
     def root_array(root)
@@ -157,7 +157,7 @@ class LinkDetailsExtractor
   end
 
   def title
-    html_entities.decode(structured_data&.headline || opengraph_tag('og:title') || head.at_xpath('title')&.content)&.strip
+    html_entities.decode(structured_data&.headline || opengraph_tag('og:title') || document.xpath('//title').map(&:content).first)&.strip
   end
 
   def description
@@ -205,11 +205,11 @@ class LinkDetailsExtractor
   end
 
   def language
-    valid_locale_or_nil(structured_data&.language || opengraph_tag('og:locale') || document.root.attr('lang'))
+    valid_locale_or_nil(structured_data&.language || opengraph_tag('og:locale') || document.xpath('//html').pick('lang'))
   end
 
   def icon
-    valid_url_or_nil(structured_data&.publisher_icon || link_tag('apple-touch-icon') || link_tag('icon'))
+    valid_url_or_nil(structured_data&.publisher_icon || link_tag('apple-touch-icon') || link_tag('shortcut icon'))
   end
 
   private
@@ -237,20 +237,18 @@ class LinkDetailsExtractor
   end
 
   def link_tag(name)
-    head.at_xpath("//link[nokogiri:link_rel_include(@rel, '#{name}')]", NokogiriHandler)&.attr('href')
+    document.xpath("//link[@rel=\"#{name}\"]").pick('href')
   end
 
   def opengraph_tag(name)
-    head.at_xpath("//meta[nokogiri:casecmp(@property, '#{name}') or nokogiri:casecmp(@name, '#{name}')]", NokogiriHandler)&.attr('content')
+    document.xpath("//meta[@property=\"#{name}\" or @name=\"#{name}\"]").pick('content')
   end
 
   def meta_tag(name)
-    head.at_xpath("//meta[nokogiri:casecmp(@name, '#{name}')]", NokogiriHandler)&.attr('content')
+    document.xpath("//meta[@name=\"#{name}\"]").pick('content')
   end
 
   def structured_data
-    return @structured_data if defined?(@structured_data)
-
     # Some publications have more than one JSON-LD definition on the page,
     # and some of those definitions aren't valid JSON either, so we have
     # to loop through here until we find something that is the right type
@@ -275,10 +273,6 @@ class LinkDetailsExtractor
     @document ||= detect_encoding_and_parse_document
   end
 
-  def head
-    @head ||= document.at_xpath('/html/head')
-  end
-
   def detect_encoding_and_parse_document
     html = nil
     encoding = nil
diff --git a/app/lib/nokogiri_handler.rb b/app/lib/nokogiri_handler.rb
deleted file mode 100644
index 26cf457955..0000000000
--- a/app/lib/nokogiri_handler.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class NokogiriHandler
-  class << self
-    # See "set of space-separated tokens" in the HTML5 spec.
-    WHITE_SPACE = /[ \x09\x0A\x0C\x0D]+/
-
-    def link_rel_include(token_list, token)
-      token_list.to_s.downcase.split(WHITE_SPACE).include?(token.downcase)
-    end
-
-    def casecmp(str1, str2)
-      str1.to_s.casecmp?(str2.to_s)
-    end
-  end
-end
diff --git a/app/lib/oauth_pre_authorization_extension.rb b/app/lib/oauth_pre_authorization_extension.rb
new file mode 100644
index 0000000000..1885e0823d
--- /dev/null
+++ b/app/lib/oauth_pre_authorization_extension.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module OauthPreAuthorizationExtension
+  extend ActiveSupport::Concern
+
+  included do
+    validate :code_challenge_method_s256, error: Doorkeeper::Errors::InvalidCodeChallengeMethod
+  end
+
+  def validate_code_challenge_method_s256
+    code_challenge.blank? || code_challenge_method == 'S256'
+  end
+end
diff --git a/app/lib/plain_text_formatter.rb b/app/lib/plain_text_formatter.rb
index e8ff79806f..f960ba7acc 100644
--- a/app/lib/plain_text_formatter.rb
+++ b/app/lib/plain_text_formatter.rb
@@ -16,15 +16,7 @@ class PlainTextFormatter
     if local?
       text
     else
-      begin
-        node = Nokogiri::HTML5.fragment(insert_newlines)
-      rescue ArgumentError
-        # This can happen if one of the Nokogumbo limits is encountered
-        # Unfortunately, it does not use a more precise error class
-        # nor allows more graceful handling
-        return ''
-      end
-
+      node = Nokogiri::HTML5.fragment(insert_newlines)
       # Elements that are entirely removed with our Sanitize config
       node.xpath('.//iframe|.//math|.//noembed|.//noframes|.//noscript|.//plaintext|.//script|.//style|.//svg|.//xmp').remove
       node.text.chomp
diff --git a/app/lib/request.rb b/app/lib/request.rb
index ad39f928db..03c27c7cea 100644
--- a/app/lib/request.rb
+++ b/app/lib/request.rb
@@ -120,10 +120,16 @@ class Request
     end
 
     begin
+      # If we are using a persistent connection, we have to
+      # read every response to be able to move forward at all.
+      # However, simply calling #to_s or #flush may not be safe,
+      # as the response body, if malicious, could be too big
+      # for our memory. So we use the #body_with_limit method
+      response.body_with_limit if http_client.persistent?
+
       yield response if block_given?
     ensure
-      response.truncated_body if http_client.persistent? && !response.connection.finished_request?
-      http_client.close unless http_client.persistent? && response.connection.finished_request?
+      http_client.close unless http_client.persistent?
     end
   end
 
@@ -333,10 +339,14 @@ class Request
       def check_private_address(address, host)
         addr = IPAddr.new(address.to_s)
 
-        return if Rails.env.development? || Rails.configuration.x.private_address_exceptions.any? { |range| range.include?(addr) }
+        return if Rails.env.development? || private_address_exceptions.any? { |range| range.include?(addr) }
 
         raise Mastodon::PrivateNetworkAddressError, host if PrivateAddressCheck.private_address?(addr)
       end
+
+      def private_address_exceptions
+        @private_address_exceptions = (ENV['ALLOWED_PRIVATE_ADDRESSES'] || '').split(/(?:\s*,\s*|\s+)/).map { |addr| IPAddr.new(addr) }
+      end
     end
   end
 
diff --git a/app/lib/rss/element.rb b/app/lib/rss/element.rb
index 073fad1234..7142fa0396 100644
--- a/app/lib/rss/element.rb
+++ b/app/lib/rss/element.rb
@@ -1,8 +1,8 @@
 # frozen_string_literal: true
 
 class RSS::Element
-  def self.with(*, &block)
-    new(*).tap(&block).to_element
+  def self.with(*args, &block)
+    new(*args).tap(&block).to_element
   end
 
   def create_element(name, content = nil)
diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb
index 56ffd2a28c..deead3717d 100644
--- a/app/lib/status_cache_hydrator.rb
+++ b/app/lib/status_cache_hydrator.rb
@@ -28,7 +28,13 @@ class StatusCacheHydrator
 
   def hydrate_non_reblog_payload(empty_payload, account_id, account)
     empty_payload.tap do |payload|
-      fill_status_payload(payload, @status, account_id, account)
+      payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.id)
+      payload[:reblogged]  = Status.exists?(account_id: account_id, reblog_of_id: @status.id)
+      payload[:muted]      = ConversationMute.exists?(account_id: account_id, conversation_id: @status.conversation_id)
+      payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.id)
+      payload[:pinned]     = StatusPin.exists?(account_id: account_id, status_id: @status.id) if @status.account_id == account_id
+      payload[:filtered]   = mapped_applied_custom_filter(account_id, @status)
+      payload[:emoji_reactions] = @status.emoji_reactions_grouped_by_name(account)
 
       if payload[:poll]
         payload[:poll][:voted] = @status.account_id == account_id
@@ -42,12 +48,19 @@ class StatusCacheHydrator
       payload[:muted]      = false
       payload[:bookmarked] = false
       payload[:pinned]     = false if @status.account_id == account_id
+      payload[:filtered]   = mapped_applied_custom_filter(account_id, @status.reblog)
 
       # If the reblogged status is being delivered to the author who disabled the display of the application
       # used to create the status, we need to hydrate it here too
       payload[:reblog][:application] = payload_reblog_application if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id
 
-      fill_status_payload(payload[:reblog], @status.reblog, account_id, account)
+      payload[:reblog][:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.reblog_of_id)
+      payload[:reblog][:reblogged]  = Status.exists?(account_id: account_id, reblog_of_id: @status.reblog_of_id)
+      payload[:reblog][:muted]      = ConversationMute.exists?(account_id: account_id, conversation_id: @status.reblog.conversation_id)
+      payload[:reblog][:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.reblog_of_id)
+      payload[:reblog][:pinned]     = StatusPin.exists?(account_id: account_id, status_id: @status.reblog_of_id) if @status.reblog.account_id == account_id
+      payload[:reblog][:filtered]   = payload[:filtered]
+      payload[:reblog][:emoji_reactions] = @status.reblog.emoji_reactions_grouped_by_name(account)
 
       if payload[:reblog][:poll]
         if @status.reblog.account_id == account_id
@@ -60,22 +73,11 @@ class StatusCacheHydrator
         end
       end
 
-      payload[:filtered]   = payload[:reblog][:filtered]
       payload[:favourited] = payload[:reblog][:favourited]
       payload[:reblogged]  = payload[:reblog][:reblogged]
     end
   end
 
-  def fill_status_payload(payload, status, account_id, account)
-    payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: status.id)
-    payload[:reblogged]  = Status.exists?(account_id: account_id, reblog_of_id: status.id)
-    payload[:muted]      = ConversationMute.exists?(account_id: account_id, conversation_id: status.conversation_id)
-    payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: status.id)
-    payload[:pinned]     = StatusPin.exists?(account_id: account_id, status_id: status.id) if status.account_id == account_id
-    payload[:filtered]   = mapped_applied_custom_filter(account_id, status)
-    payload[:emoji_reactions] = status.emoji_reactions_grouped_by_name(account)
-  end
-
   def mapped_applied_custom_filter(account_id, status)
     CustomFilter
       .apply_cached_filters(CustomFilter.cached_filters_for(account_id), status, following: following?(account_id))
diff --git a/app/lib/status_reach_finder.rb b/app/lib/status_reach_finder.rb
index 04dd254458..47f8256d21 100644
--- a/app/lib/status_reach_finder.rb
+++ b/app/lib/status_reach_finder.rb
@@ -43,23 +43,19 @@ class StatusReachFinder
 
   def reached_account_inboxes
     reject_domains = @status.limited_visibility? ? banned_domains : banned_domains + friend_domains
-    scope = Account.where(id: reached_account_ids).where.not(domain: reject_domains)
-    inboxes_without_suspended_for(scope)
+    Account.where(id: reached_account_ids).where.not(domain: reject_domains).inboxes
   end
 
   def reached_account_inboxes_for_misskey
-    scope = Account.where(id: reached_account_ids, domain: banned_domains_for_misskey - friend_domains)
-    inboxes_without_suspended_for(scope)
+    Account.where(id: reached_account_ids, domain: banned_domains_for_misskey - friend_domains).inboxes
   end
 
   def reached_account_inboxes_for_friend
-    scope = Account.where(id: reached_account_ids, domain: friend_domains)
-    inboxes_without_suspended_for(scope)
+    Account.where(id: reached_account_ids, domain: friend_domains).inboxes
   end
 
   def reached_account_inboxes_for_sending_domain_block
-    scope = Account.where(id: reached_account_ids, domain: banned_domains_of_status(@status))
-    inboxes_without_suspended_for(scope)
+    Account.where(id: reached_account_ids, domain: banned_domains_of_status(@status)).inboxes
   end
 
   def reached_account_ids
@@ -119,8 +115,13 @@ class StatusReachFinder
   end
 
   def followers_inboxes
-    scope = followers_scope
-    inboxes_without_suspended_for(scope)
+    if @status.in_reply_to_local_account? && distributable?
+      @status.account.followers.or(@status.thread.account.followers.not_domain_blocked_by_account(@status.account)).where.not(domain: banned_domains + friend_domains).inboxes
+    elsif @status.direct_visibility? || @status.limited_visibility?
+      []
+    else
+      @status.account.followers.where.not(domain: banned_domains + friend_domains).inboxes
+    end
   end
 
   def followers_inboxes_for_misskey
@@ -223,19 +224,4 @@ class StatusReachFinder
     from_domain_block = DomainBlock.where(detect_invalid_subscription: true).pluck(:domain)
     (from_info + from_domain_block).uniq
   end
-
-  def followers_scope
-    if @status.in_reply_to_local_account? && distributable?
-      @status.account.followers.or(@status.thread.account.followers.not_domain_blocked_by_account(@status.account)).where.not(domain: banned_domains + friend_domains)
-    elsif @status.direct_visibility? || @status.limited_visibility?
-      Account.none
-    else
-      @status.account.followers.where.not(domain: banned_domains + friend_domains)
-    end
-  end
-
-  def inboxes_without_suspended_for(scope)
-    scope.merge!(Account.without_suspended) unless unsafe?
-    scope.inboxes
-  end
 end
diff --git a/app/lib/suspicious_sign_in_detector.rb b/app/lib/suspicious_sign_in_detector.rb
index 60e5fdad4f..74f49aa558 100644
--- a/app/lib/suspicious_sign_in_detector.rb
+++ b/app/lib/suspicious_sign_in_detector.rb
@@ -19,7 +19,7 @@ class SuspiciousSignInDetector
   end
 
   def previously_seen_ip?(request)
-    @user.ips.contained_by(masked_ip(request)).exists?
+    @user.ips.exists?(['ip <<= ?', masked_ip(request)])
   end
 
   def freshly_signed_up?
diff --git a/app/lib/text_formatter.rb b/app/lib/text_formatter.rb
index 6ff4ea5651..d334babd0e 100644
--- a/app/lib/text_formatter.rb
+++ b/app/lib/text_formatter.rb
@@ -7,7 +7,7 @@ class TextFormatter
 
   URL_PREFIX_REGEX = %r{\A(https?://(www\.)?|xmpp:)}
 
-  DEFAULT_REL = %w(nofollow noopener).freeze
+  DEFAULT_REL = %w(nofollow noopener noreferrer).freeze
 
   DEFAULT_OPTIONS = {
     multiline: true,
@@ -34,26 +34,21 @@ class TextFormatter
   def to_s
     return ''.html_safe if text.blank?
 
-    html = nil
-    MastodonOTELTracer.in_span('TextFormatter#to_s extract_and_rewrite') do
-      html = rewrite do |entity|
-        if entity[:url]
-          link_to_url(entity)
-        elsif entity[:hashtag]
-          link_to_hashtag(entity)
-        elsif entity[:screen_name]
-          link_to_mention(entity)
-        end
+    html = rewrite do |entity|
+      if entity[:url]
+        link_to_url(entity)
+      elsif entity[:hashtag]
+        link_to_hashtag(entity)
+      elsif entity[:screen_name]
+        link_to_mention(entity)
       end
     end
 
-    MastodonOTELTracer.in_span('TextFormatter#to_s simple_format') do
-      # line first letter for blockquote
-      html = markdownify(html.gsub(/^&gt;/, '>')) if markdown?
+    # line first letter for blockquote
+    html = markdownify(html.gsub(/^&gt;/, '>')) if markdown?
 
-      html = simple_format(html, {}, sanitize: false).delete("\n") if !markdown? && multiline?
-      html = html.delete("\n")
-    end
+    html = simple_format(html, {}, sanitize: false).delete("\n") if !markdown? && multiline?
+    html = html.delete("\n")
 
     html.html_safe # rubocop:disable Rails/OutputSafety
   end
@@ -71,12 +66,6 @@ class TextFormatter
       suffix      = url[prefix.length + 30..]
       cutoff      = url[prefix.length..].length > 30
 
-      if suffix && suffix.length == 1 # revert truncation to account for ellipsis
-        display_url += suffix
-        suffix = nil
-        cutoff = false
-      end
-
       tag.a href: url, target: '_blank', rel: rel.join(' '), translate: 'no' do
         tag.span(prefix, class: 'invisible') +
           tag.span(display_url, class: (cutoff ? 'ellipsis' : '')) +
@@ -109,54 +98,48 @@ class TextFormatter
   end
 
   def link_to_url(entity)
-    MastodonOTELTracer.in_span('TextFormatter#link_to_url') do
-      TextFormatter.shortened_link(entity[:url], rel_me: with_rel_me?)
-    end
+    TextFormatter.shortened_link(entity[:url], rel_me: with_rel_me?)
   end
 
   def link_to_hashtag(entity)
-    MastodonOTELTracer.in_span('TextFormatter#link_to_hashtag') do
-      hashtag = entity[:hashtag]
-      url     = tag_url(hashtag)
+    hashtag = entity[:hashtag]
+    url     = tag_url(hashtag)
 
-      <<~HTML.squish
-        <a href="#{h(url)}" class="mention hashtag" rel="tag">#<span>#{h(hashtag)}</span></a>
-      HTML
-    end
+    <<~HTML.squish
+      <a href="#{h(url)}" class="mention hashtag" rel="tag">#<span>#{h(hashtag)}</span></a>
+    HTML
   end
 
   def link_to_mention(entity)
-    MastodonOTELTracer.in_span('TextFormatter#link_to_mention') do
-      username, domain = entity[:screen_name].split('@')
-      domain           = nil if local_domain?(domain)
-      account          = nil
+    username, domain = entity[:screen_name].split('@')
+    domain           = nil if local_domain?(domain)
+    account          = nil
 
-      if preloaded_accounts?
-        same_username_hits = 0
+    if preloaded_accounts?
+      same_username_hits = 0
 
-        preloaded_accounts.each do |other_account|
-          same_username = other_account.username.casecmp(username).zero?
-          same_domain   = other_account.domain.nil? ? domain.nil? : other_account.domain.casecmp(domain)&.zero?
+      preloaded_accounts.each do |other_account|
+        same_username = other_account.username.casecmp(username).zero?
+        same_domain   = other_account.domain.nil? ? domain.nil? : other_account.domain.casecmp(domain)&.zero?
 
-          if same_username && !same_domain
-            same_username_hits += 1
-          elsif same_username && same_domain
-            account = other_account
-          end
+        if same_username && !same_domain
+          same_username_hits += 1
+        elsif same_username && same_domain
+          account = other_account
         end
-      else
-        account = entity_cache.mention(username, domain)
       end
-
-      return "@#{h(entity[:screen_name])}" if account.nil?
-
-      url = ActivityPub::TagManager.instance.url_for(account)
-      display_username = same_username_hits&.positive? || with_domains? ? account.pretty_acct : account.username
-
-      <<~HTML.squish
-        <span class="h-card" translate="no"><a href="#{h(url)}" class="u-url mention">@<span>#{h(display_username)}</span></a></span>
-      HTML
+    else
+      account = entity_cache.mention(username, domain)
     end
+
+    return "@#{h(entity[:screen_name])}" if account.nil?
+
+    url = ActivityPub::TagManager.instance.url_for(account)
+    display_username = same_username_hits&.positive? || with_domains? ? account.pretty_acct : account.username
+
+    <<~HTML.squish
+      <span class="h-card" translate="no"><a href="#{h(url)}" class="u-url mention">@<span>#{h(display_username)}</span></a></span>
+    HTML
   end
 
   def entity_cache
diff --git a/app/lib/translation_service.rb b/app/lib/translation_service.rb
index ee268d7a7c..bfe5de44f8 100644
--- a/app/lib/translation_service.rb
+++ b/app/lib/translation_service.rb
@@ -8,27 +8,17 @@ class TranslationService
   class UnexpectedResponseError < Error; end
 
   def self.configured
-    if configuration.deepl[:api_key].present?
-      TranslationService::DeepL.new(
-        configuration.deepl[:plan],
-        configuration.deepl[:api_key]
-      )
-    elsif configuration.libre_translate[:endpoint].present?
-      TranslationService::LibreTranslate.new(
-        configuration.libre_translate[:endpoint],
-        configuration.libre_translate[:api_key]
-      )
+    if ENV['DEEPL_API_KEY'].present?
+      TranslationService::DeepL.new(ENV.fetch('DEEPL_PLAN', 'free'), ENV['DEEPL_API_KEY'])
+    elsif ENV['LIBRE_TRANSLATE_ENDPOINT'].present?
+      TranslationService::LibreTranslate.new(ENV['LIBRE_TRANSLATE_ENDPOINT'], ENV['LIBRE_TRANSLATE_API_KEY'])
     else
       raise NotConfiguredError
     end
   end
 
   def self.configured?
-    configuration.deepl[:api_key].present? || configuration.libre_translate[:endpoint].present?
-  end
-
-  def self.configuration
-    Rails.configuration.x.translation
+    ENV['DEEPL_API_KEY'].present? || ENV['LIBRE_TRANSLATE_ENDPOINT'].present?
   end
 
   def languages
diff --git a/app/lib/translation_service/deepl.rb b/app/lib/translation_service/deepl.rb
index 7761dbe626..925a1cf172 100644
--- a/app/lib/translation_service/deepl.rb
+++ b/app/lib/translation_service/deepl.rb
@@ -42,8 +42,8 @@ class TranslationService::DeepL < TranslationService
     subtags.join('-')
   end
 
-  def request(verb, path, **)
-    req = Request.new(verb, "#{base_url}#{path}", **)
+  def request(verb, path, **options)
+    req = Request.new(verb, "#{base_url}#{path}", **options)
     req.add_headers(Authorization: "DeepL-Auth-Key #{@api_key}")
     req.perform do |res|
       case res.code
diff --git a/app/lib/translation_service/libre_translate.rb b/app/lib/translation_service/libre_translate.rb
index 0df8590f87..de43d7c88c 100644
--- a/app/lib/translation_service/libre_translate.rb
+++ b/app/lib/translation_service/libre_translate.rb
@@ -27,8 +27,8 @@ class TranslationService::LibreTranslate < TranslationService
 
   private
 
-  def request(verb, path, **)
-    req = Request.new(verb, "#{@base_url}#{path}", allow_local: true, **)
+  def request(verb, path, **options)
+    req = Request.new(verb, "#{@base_url}#{path}", allow_local: true, **options)
     req.add_headers('Content-Type': 'application/json')
     req.perform do |res|
       case res.code
diff --git a/app/lib/vacuum/imports_vacuum.rb b/app/lib/vacuum/imports_vacuum.rb
index 7f101c4506..b67865194f 100644
--- a/app/lib/vacuum/imports_vacuum.rb
+++ b/app/lib/vacuum/imports_vacuum.rb
@@ -9,16 +9,10 @@ class Vacuum::ImportsVacuum
   private
 
   def clean_unconfirmed_imports!
-    BulkImport
-      .confirmation_missed
-      .in_batches
-      .delete_all
+    BulkImport.state_unconfirmed.where(created_at: ..10.minutes.ago).in_batches.delete_all
   end
 
   def clean_old_imports!
-    BulkImport
-      .archival_completed
-      .in_batches
-      .delete_all
+    BulkImport.where(created_at: ..1.week.ago).in_batches.delete_all
   end
 end
diff --git a/app/lib/video_metadata_extractor.rb b/app/lib/video_metadata_extractor.rb
index 33a6264b4f..2155766251 100644
--- a/app/lib/video_metadata_extractor.rb
+++ b/app/lib/video_metadata_extractor.rb
@@ -12,7 +12,7 @@ class VideoMetadataExtractor
   rescue Terrapin::ExitStatusError, Oj::ParseError
     @invalid = true
   rescue Terrapin::CommandNotFoundError
-    raise Paperclip::Errors::CommandNotFoundError, 'Could not run the `ffprobe` command. Please install ffmpeg.' # rubocop:disable I18n/RailsI18n/DecorateString -- This error is not user-facing
+    raise Paperclip::Errors::CommandNotFoundError, 'Could not run the `ffprobe` command. Please install ffmpeg.'
   end
 
   def valid?
@@ -46,9 +46,6 @@ class VideoMetadataExtractor
         # For some video streams the frame_rate reported by `ffprobe` will be 0/0, but for these streams we
         # should use `r_frame_rate` instead. Video screencast generated by Gnome Screencast have this issue.
         @frame_rate ||= @r_frame_rate
-        # If the video has not been re-encoded by ffmpeg, it may contain rotation information,
-        # and we need to simulate applying it to the dimensions
-        @width, @height = @height, @width if video_stream[:side_data_list]&.any? { |x| x[:rotation]&.abs == 90 }
       end
 
       if (audio_stream = audio_streams.first)
diff --git a/app/lib/web_push_request.rb b/app/lib/web_push_request.rb
index 85e8ab6bb5..a43e22480e 100644
--- a/app/lib/web_push_request.rb
+++ b/app/lib/web_push_request.rb
@@ -2,8 +2,7 @@
 
 class WebPushRequest
   SIGNATURE_ALGORITHM = 'p256ecdsa'
-  LEGACY_AUTH_HEADER = 'WebPush'
-  STANDARD_AUTH_HEADER = 'vapid'
+  AUTH_HEADER = 'WebPush'
   PAYLOAD_EXPIRATION = 24.hours
   JWT_ALGORITHM = 'ES256'
   JWT_TYPE = 'JWT'
@@ -11,7 +10,6 @@ class WebPushRequest
   attr_reader :web_push_subscription
 
   delegate(
-    :standard,
     :endpoint,
     :key_auth,
     :key_p256dh,
@@ -26,36 +24,20 @@ class WebPushRequest
     @audience ||= Addressable::URI.parse(endpoint).normalized_site
   end
 
-  def legacy_authorization_header
-    [LEGACY_AUTH_HEADER, encoded_json_web_token].join(' ')
+  def authorization_header
+    [AUTH_HEADER, encoded_json_web_token].join(' ')
   end
 
   def crypto_key_header
     [SIGNATURE_ALGORITHM, vapid_key.public_key_for_push_header].join('=')
   end
 
-  def legacy_encrypt(payload)
-    Webpush::Legacy::Encryption.encrypt(payload, key_p256dh, key_auth)
-  end
-
-  def standard_authorization_header
-    [STANDARD_AUTH_HEADER, standard_vapid_value].join(' ')
-  end
-
-  def standard_encrypt(payload)
+  def encrypt(payload)
     Webpush::Encryption.encrypt(payload, key_p256dh, key_auth)
   end
 
-  def legacy
-    !standard
-  end
-
   private
 
-  def standard_vapid_value
-    "t=#{encoded_json_web_token},k=#{vapid_key.public_key_for_push_header}"
-  end
-
   def encoded_json_web_token
     JWT.encode(
       web_token_payload,
diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb
index 5607aa2a58..f5f40c86aa 100644
--- a/app/mailers/admin_mailer.rb
+++ b/app/mailers/admin_mailer.rb
@@ -56,7 +56,7 @@ class AdminMailer < ApplicationMailer
   end
 
   def new_software_updates
-    @software_updates = SoftwareUpdate.by_version
+    @software_updates = SoftwareUpdate.all.to_a.sort_by(&:gem_version)
 
     locale_for_account(@me) do
       mail subject: default_i18n_subject(instance: @instance)
@@ -64,7 +64,7 @@ class AdminMailer < ApplicationMailer
   end
 
   def new_critical_software_updates
-    @software_updates = SoftwareUpdate.urgent.by_version
+    @software_updates = SoftwareUpdate.where(urgent: true).to_a.sort_by(&:gem_version)
 
     locale_for_account(@me) do
       mail subject: default_i18n_subject(instance: @instance)
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 0eef9b90da..5c9e5c96d9 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -209,25 +209,6 @@ class UserMailer < Devise::Mailer
     end
   end
 
-  def terms_of_service_changed(user, terms_of_service)
-    @resource = user
-    @terms_of_service = terms_of_service
-    @markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, escape_html: true, no_images: true)
-
-    I18n.with_locale(locale) do
-      mail subject: default_i18n_subject
-    end
-  end
-
-  def announcement_published(user, announcement)
-    @resource = user
-    @announcement = announcement
-
-    I18n.with_locale(locale) do
-      mail subject: default_i18n_subject
-    end
-  end
-
   private
 
   def default_devise_subject
diff --git a/app/models/account.rb b/app/models/account.rb
index f3f591d006..7c4009c201 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -69,8 +69,6 @@ class Account < ApplicationRecord
   )
 
   BACKGROUND_REFRESH_INTERVAL = 1.week.freeze
-  REFRESH_DEADLINE = 6.hours
-  STALE_THRESHOLD = 1.day
   DEFAULT_FIELDS_SIZE = 6
   INSTANCE_ACTOR_ID = -99
 
@@ -94,8 +92,6 @@ class Account < ApplicationRecord
   include Account::Interactions
   include Account::Merging
   include Account::Search
-  include Account::Sensitizes
-  include Account::Silences
   include Account::StatusesSearch
   include Account::OtherSettings
   include Account::MasterSettings
@@ -114,33 +110,34 @@ class Account < ApplicationRecord
   validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? }
 
   # Remote user validations, also applies to internal actors
-  validates :username, format: { with: USERNAME_ONLY_RE }, if: -> { (remote? || actor_type_application?) && will_save_change_to_username? }
+  validates :username, format: { with: USERNAME_ONLY_RE }, if: -> { (!local? || actor_type == 'Application') && will_save_change_to_username? }
 
   # Remote user validations
   validates :uri, presence: true, unless: :local?, on: :create
 
   # Local user validations
-  validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: USERNAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_username? && !actor_type_application? }
-  validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && !actor_type_application? }
+  validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: USERNAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' }
+  validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' }
   validates :display_name, length: { maximum: DISPLAY_NAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_display_name? }
   validates :note, note_length: { maximum: NOTE_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_note? }
   validates :fields, length: { maximum: DEFAULT_FIELDS_SIZE }, if: -> { local? && will_save_change_to_fields? }
-  validates_with EmptyProfileFieldNamesValidator, if: -> { local? && will_save_change_to_fields? }
-  with_options on: :create, if: :local? do
-    validates :followers_url, absence: true
-    validates :inbox_url, absence: true
-    validates :shared_inbox_url, absence: true
-    validates :uri, absence: true
+  with_options on: :create do
+    validates :uri, absence: true, if: :local?
+    validates :inbox_url, absence: true, if: :local?
+    validates :shared_inbox_url, absence: true, if: :local?
+    validates :followers_url, absence: true, if: :local?
   end
 
-  validates :domain, exclusion: { in: [''] }
-
   normalizes :username, with: ->(username) { username.squish }
 
   scope :without_internal, -> { where(id: 1...) }
   scope :remote, -> { where.not(domain: nil) }
   scope :local, -> { where(domain: nil) }
   scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) }
+  scope :silenced, -> { where.not(silenced_at: nil) }
+  scope :remote_pending, -> { where(remote_pending: true).where.not(suspended_at: nil) }
+  scope :sensitized, -> { where.not(sensitized_at: nil) }
+  scope :without_silenced, -> { where(silenced_at: nil) }
   scope :without_instance_actor, -> { where.not(id: INSTANCE_ACTOR_ID) }
   scope :recent, -> { reorder(id: :desc) }
   scope :bots, -> { where(actor_type: AUTOMATED_ACTOR_TYPES) }
@@ -160,7 +157,7 @@ class Account < ApplicationRecord
   scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
   scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) }
   scope :dormant, -> { joins(:account_stat).merge(AccountStat.without_recent_activity) }
-  scope :with_username, ->(value) { value.is_a?(Array) ? where(arel_table[:username].lower.in(value.map { |x| x.to_s.downcase })) : where(arel_table[:username].lower.eq(value.to_s.downcase)) }
+  scope :with_username, ->(value) { where arel_table[:username].lower.eq(value.to_s.downcase) }
   scope :with_domain, ->(value) { where arel_table[:domain].lower.eq(value&.to_s&.downcase) }
   scope :without_memorial, -> { where(memorial: false) }
   scope :duplicate_uris, -> { select(:uri, Arel.star.count).group(:uri).having(Arel.star.count.gt(1)) }
@@ -195,10 +192,6 @@ class Account < ApplicationRecord
     domain.nil?
   end
 
-  def remote?
-    !domain.nil?
-  end
-
   def moved?
     moved_to_account_id.present?
   end
@@ -217,10 +210,6 @@ class Account < ApplicationRecord
     self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person'
   end
 
-  def actor_type_application?
-    actor_type == 'Application'
-  end
-
   def group?
     actor_type == 'Group'
   end
@@ -268,19 +257,60 @@ class Account < ApplicationRecord
   end
 
   def possibly_stale?
-    last_webfingered_at.nil? || last_webfingered_at <= STALE_THRESHOLD.ago
+    last_webfingered_at.nil? || last_webfingered_at <= 1.day.ago
   end
 
   def schedule_refresh_if_stale!
     return unless last_webfingered_at.present? && last_webfingered_at <= BACKGROUND_REFRESH_INTERVAL.ago
 
-    AccountRefreshWorker.perform_in(rand(REFRESH_DEADLINE), id)
+    AccountRefreshWorker.perform_in(rand(6.hours.to_i), id)
   end
 
   def refresh!
     ResolveAccountService.new.call(acct) unless local?
   end
 
+  def silenced?
+    silenced_at.present?
+  end
+
+  def silence!(date = Time.now.utc)
+    update!(silenced_at: date)
+  end
+
+  def unsilence!
+    update!(silenced_at: nil)
+  end
+
+  def approve_remote!
+    return unless remote_pending
+
+    update!(remote_pending: false)
+    unsuspend!
+    ActivateRemoteAccountWorker.perform_async(id)
+  end
+
+  def reject_remote!
+    return unless remote_pending
+
+    update!(remote_pending: false, suspension_origin: :local)
+    pending_follow_requests.destroy_all
+    pending_statuses.destroy_all
+    suspend!
+  end
+
+  def sensitized?
+    sensitized_at.present?
+  end
+
+  def sensitize!(date = Time.now.utc)
+    update!(sensitized_at: date)
+  end
+
+  def unsensitize!
+    update!(sensitized_at: nil)
+  end
+
   def memorialize!
     update!(memorial: true)
   end
@@ -350,7 +380,7 @@ class Account < ApplicationRecord
 
     if attributes.is_a?(Hash)
       attributes.each_value do |attr|
-        next if attr[:name].blank? && attr[:value].blank?
+        next if attr[:name].blank?
 
         previous = old_fields.find { |item| item['value'] == attr[:value] }
 
diff --git a/app/models/account/field.rb b/app/models/account/field.rb
index 4b3ccea9c4..bcd89015de 100644
--- a/app/models/account/field.rb
+++ b/app/models/account/field.rb
@@ -73,14 +73,7 @@ class Account::Field < ActiveModelSerializers::Model
   end
 
   def extract_url_from_html
-    begin
-      doc = Nokogiri::HTML5.fragment(value)
-    rescue ArgumentError
-      # This can happen if one of the Nokogumbo limits is encountered
-      # Unfortunately, it does not use a more precise error class
-      # nor allows more graceful handling
-      return
-    end
+    doc = Nokogiri::HTML5.fragment(value)
 
     return if doc.nil?
     return if doc.children.size != 1
diff --git a/app/models/account_alias.rb b/app/models/account_alias.rb
index af156f1497..3b75919afe 100644
--- a/app/models/account_alias.rb
+++ b/app/models/account_alias.rb
@@ -5,11 +5,11 @@
 # Table name: account_aliases
 #
 #  id         :bigint(8)        not null, primary key
+#  account_id :bigint(8)
 #  acct       :string           default(""), not null
 #  uri        :string           default(""), not null
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  account_id :bigint(8)        not null
 #
 
 class AccountAlias < ApplicationRecord
@@ -35,7 +35,7 @@ class AccountAlias < ApplicationRecord
   def set_uri
     target_account = ResolveAccountService.new.call(acct)
     self.uri       = ActivityPub::TagManager.instance.uri_for(target_account) unless target_account.nil?
-  rescue Webfinger::Error, *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::Error
+  rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
     # Validation will take care of it
   end
 
diff --git a/app/models/account_conversation.rb b/app/models/account_conversation.rb
index ccf8f36966..25a75d8a61 100644
--- a/app/models/account_conversation.rb
+++ b/app/models/account_conversation.rb
@@ -5,13 +5,13 @@
 # Table name: account_conversations
 #
 #  id                      :bigint(8)        not null, primary key
-#  lock_version            :integer          default(0), not null
+#  account_id              :bigint(8)
+#  conversation_id         :bigint(8)
 #  participant_account_ids :bigint(8)        default([]), not null, is an Array
 #  status_ids              :bigint(8)        default([]), not null, is an Array
-#  unread                  :boolean          default(FALSE), not null
-#  account_id              :bigint(8)        not null
-#  conversation_id         :bigint(8)        not null
 #  last_status_id          :bigint(8)
+#  lock_version            :integer          default(0), not null
+#  unread                  :boolean          default(FALSE), not null
 #
 
 class AccountConversation < ApplicationRecord
diff --git a/app/models/account_deletion_request.rb b/app/models/account_deletion_request.rb
index c65d2cc217..7d0c346cc2 100644
--- a/app/models/account_deletion_request.rb
+++ b/app/models/account_deletion_request.rb
@@ -5,9 +5,9 @@
 # Table name: account_deletion_requests
 #
 #  id         :bigint(8)        not null, primary key
+#  account_id :bigint(8)
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  account_id :bigint(8)        not null
 #
 class AccountDeletionRequest < ApplicationRecord
   DELAY_TO_DELETION = 30.days.freeze
diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb
index c3a8c49da0..753935d6af 100644
--- a/app/models/account_domain_block.rb
+++ b/app/models/account_domain_block.rb
@@ -5,10 +5,10 @@
 # Table name: account_domain_blocks
 #
 #  id         :bigint(8)        not null, primary key
-#  domain     :string           not null
+#  domain     :string
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  account_id :bigint(8)        not null
+#  account_id :bigint(8)
 #
 
 class AccountDomainBlock < ApplicationRecord
diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb
index fd1e14ac40..763ed3e46f 100644
--- a/app/models/account_filter.rb
+++ b/app/models/account_filter.rb
@@ -61,7 +61,7 @@ class AccountFilter
     when 'email'
       accounts_with_users.merge(User.matches_email(value.to_s.strip))
     when 'ip'
-      valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value).group(users: [:id], accounts: [:id])) : Account.none
+      valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value).group('users.id, accounts.id')) : Account.none
     when 'invited_by'
       invited_by_scope(value)
     when 'order'
diff --git a/app/models/account_migration.rb b/app/models/account_migration.rb
index 7bda388f2a..7a01e250e2 100644
--- a/app/models/account_migration.rb
+++ b/app/models/account_migration.rb
@@ -61,7 +61,7 @@ class AccountMigration < ApplicationRecord
 
   def set_target_account
     self.target_account = ResolveAccountService.new.call(acct, skip_cache: true)
-  rescue Webfinger::Error, *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::Error, Addressable::URI::InvalidURIError
+  rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error, Addressable::URI::InvalidURIError
     # Validation will take care of it
   end
 
diff --git a/app/models/account_note.rb b/app/models/account_note.rb
index f8573d7da4..317e6873fa 100644
--- a/app/models/account_note.rb
+++ b/app/models/account_note.rb
@@ -5,11 +5,11 @@
 # Table name: account_notes
 #
 #  id                :bigint(8)        not null, primary key
+#  account_id        :bigint(8)
+#  target_account_id :bigint(8)
 #  comment           :text             not null
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
-#  account_id        :bigint(8)        not null
-#  target_account_id :bigint(8)        not null
 #
 class AccountNote < ApplicationRecord
   include RelationshipCacheable
diff --git a/app/models/account_pin.rb b/app/models/account_pin.rb
index dc05d3cd25..6c78e8c446 100644
--- a/app/models/account_pin.rb
+++ b/app/models/account_pin.rb
@@ -5,10 +5,10 @@
 # Table name: account_pins
 #
 #  id                :bigint(8)        not null, primary key
+#  account_id        :bigint(8)
+#  target_account_id :bigint(8)
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
-#  account_id        :bigint(8)        not null
-#  target_account_id :bigint(8)        not null
 #
 
 class AccountPin < ApplicationRecord
@@ -23,6 +23,6 @@ class AccountPin < ApplicationRecord
   private
 
   def validate_follow_relationship
-    errors.add(:base, I18n.t('accounts.pin_errors.following')) unless account&.following?(target_account)
+    errors.add(:base, I18n.t('accounts.pin_errors.following')) unless account.following?(target_account)
   end
 end
diff --git a/app/models/account_statuses_cleanup_policy.rb b/app/models/account_statuses_cleanup_policy.rb
index 7e5cfbbe02..73b4bfbb1f 100644
--- a/app/models/account_statuses_cleanup_policy.rb
+++ b/app/models/account_statuses_cleanup_policy.rb
@@ -136,7 +136,7 @@ class AccountStatusesCleanupPolicy < ApplicationRecord
   end
 
   def without_direct_scope
-    Status.not_direct_visibility
+    Status.where.not(visibility: :direct)
   end
 
   def old_enough_scope(max_id = nil)
diff --git a/app/models/account_summary.rb b/app/models/account_summary.rb
index 7522a70193..327c0ef305 100644
--- a/app/models/account_summary.rb
+++ b/app/models/account_summary.rb
@@ -17,6 +17,6 @@ class AccountSummary < ApplicationRecord
   has_many :follow_recommendation_suppressions, primary_key: :account_id, foreign_key: :account_id, inverse_of: false, dependent: nil
 
   scope :safe, -> { where(sensitive: false) }
-  scope :localized, ->(locale) { in_order_of(:language, [locale], filter: false) }
+  scope :localized, ->(locale) { order(Arel::Nodes::Case.new.when(arel_table[:language].eq(locale)).then(1).else(0).desc) }
   scope :filtered, -> { where.missing(:follow_recommendation_suppressions) }
 end
diff --git a/app/models/account_warning.rb b/app/models/account_warning.rb
index 7ce0993767..29d10f0371 100644
--- a/app/models/account_warning.rb
+++ b/app/models/account_warning.rb
@@ -28,7 +28,6 @@ class AccountWarning < ApplicationRecord
     suspend: 4_000,
   }, suffix: :action
 
-  APPEAL_WINDOW = 20.days
   RECENT_PERIOD = 3.months.freeze
 
   normalizes :text, with: ->(text) { text.to_s }, apply_to_nil: true
@@ -51,10 +50,6 @@ class AccountWarning < ApplicationRecord
     overruled_at.present?
   end
 
-  def appeal_eligible?
-    created_at >= APPEAL_WINDOW.ago
-  end
-
   def to_log_human_identifier
     target_account.acct
   end
diff --git a/app/models/admin/action_log.rb b/app/models/admin/action_log.rb
index b6834c4784..49ae679809 100644
--- a/app/models/admin/action_log.rb
+++ b/app/models/admin/action_log.rb
@@ -5,15 +5,15 @@
 # Table name: admin_action_logs
 #
 #  id               :bigint(8)        not null, primary key
+#  account_id       :bigint(8)
 #  action           :string           default(""), not null
-#  human_identifier :string
-#  permalink        :string
-#  route_param      :string
 #  target_type      :string
+#  target_id        :bigint(8)
 #  created_at       :datetime         not null
 #  updated_at       :datetime         not null
-#  account_id       :bigint(8)        not null
-#  target_id        :bigint(8)
+#  human_identifier :string
+#  route_param      :string
+#  permalink        :string
 #
 
 class Admin::ActionLog < ApplicationRecord
diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb
index dff5fad088..b156cd49fe 100644
--- a/app/models/admin/action_log_filter.rb
+++ b/app/models/admin/action_log_filter.rb
@@ -33,7 +33,6 @@ class Admin::ActionLogFilter
     create_domain_block: { target_type: 'DomainBlock', action: 'create' }.freeze,
     create_email_domain_block: { target_type: 'EmailDomainBlock', action: 'create' }.freeze,
     create_ip_block: { target_type: 'IpBlock', action: 'create' }.freeze,
-    create_relay: { target_type: 'Relay', action: 'create' }.freeze,
     create_unavailable_domain: { target_type: 'UnavailableDomain', action: 'create' }.freeze,
     create_user_role: { target_type: 'UserRole', action: 'create' }.freeze,
     create_canonical_email_block: { target_type: 'CanonicalEmailBlock', action: 'create' }.freeze,
@@ -43,7 +42,6 @@ class Admin::ActionLogFilter
     destroy_domain_allow: { target_type: 'DomainAllow', action: 'destroy' }.freeze,
     destroy_domain_block: { target_type: 'DomainBlock', action: 'destroy' }.freeze,
     destroy_ip_block: { target_type: 'IpBlock', action: 'destroy' }.freeze,
-    destroy_relay: { target_type: 'Relay', action: 'destroy' }.freeze,
     destroy_email_domain_block: { target_type: 'EmailDomainBlock', action: 'destroy' }.freeze,
     destroy_instance: { target_type: 'Instance', action: 'destroy' }.freeze,
     destroy_unavailable_domain: { target_type: 'UnavailableDomain', action: 'destroy' }.freeze,
@@ -53,13 +51,10 @@ class Admin::ActionLogFilter
     disable_2fa_user: { target_type: 'User', action: 'disable_2fa' }.freeze,
     disable_custom_emoji: { target_type: 'CustomEmoji', action: 'disable' }.freeze,
     disable_user: { target_type: 'User', action: 'disable' }.freeze,
-    disable_relay: { target_type: 'Relay', action: 'disable' }.freeze,
     enable_custom_emoji: { target_type: 'CustomEmoji', action: 'enable' }.freeze,
     enable_user: { target_type: 'User', action: 'enable' }.freeze,
-    enable_relay: { target_type: 'Relay', action: 'enable' }.freeze,
     memorialize_account: { target_type: 'Account', action: 'memorialize' }.freeze,
     promote_user: { target_type: 'User', action: 'promote' }.freeze,
-    publish_terms_of_service: { target_type: 'TermsOfService', action: 'publish' }.freeze,
     remove_avatar_user: { target_type: 'User', action: 'remove_avatar' }.freeze,
     reopen_report: { target_type: 'Report', action: 'reopen' }.freeze,
     resend_user: { target_type: 'User', action: 'resend' }.freeze,
diff --git a/app/models/admin/status_filter.rb b/app/models/admin/status_filter.rb
index fd4d0ef59d..8d20e4f6ab 100644
--- a/app/models/admin/status_filter.rb
+++ b/app/models/admin/status_filter.rb
@@ -32,7 +32,7 @@ class Admin::StatusFilter
   def scope_for(key, _value)
     case key.to_s
     when 'media'
-      Status.joins(:media_attachments).merge(@account.media_attachments).group(:id).recent
+      Status.joins(:media_attachments).merge(@account.media_attachments).group(:id).reorder('statuses.id desc')
     else
       raise Mastodon::InvalidParameterError, "Unknown filter: #{key}"
     end
diff --git a/app/models/announcement.rb b/app/models/announcement.rb
index ff9c188398..6b3b8ccfc1 100644
--- a/app/models/announcement.rb
+++ b/app/models/announcement.rb
@@ -4,18 +4,17 @@
 #
 # Table name: announcements
 #
-#  id                   :bigint(8)        not null, primary key
-#  all_day              :boolean          default(FALSE), not null
-#  ends_at              :datetime
-#  notification_sent_at :datetime
-#  published            :boolean          default(FALSE), not null
-#  published_at         :datetime
-#  scheduled_at         :datetime
-#  starts_at            :datetime
-#  status_ids           :bigint(8)        is an Array
-#  text                 :text             default(""), not null
-#  created_at           :datetime         not null
-#  updated_at           :datetime         not null
+#  id           :bigint(8)        not null, primary key
+#  text         :text             default(""), not null
+#  published    :boolean          default(FALSE), not null
+#  all_day      :boolean          default(FALSE), not null
+#  scheduled_at :datetime
+#  starts_at    :datetime
+#  ends_at      :datetime
+#  created_at   :datetime         not null
+#  updated_at   :datetime         not null
+#  published_at :datetime
+#  status_ids   :bigint(8)        is an Array
 #
 
 class Announcement < ApplicationRecord
@@ -55,10 +54,6 @@ class Announcement < ApplicationRecord
     update!(published: false, scheduled_at: nil)
   end
 
-  def notification_sent?
-    notification_sent_at.present?
-  end
-
   def mentions
     @mentions ||= Account.from_text(text)
   end
@@ -91,10 +86,6 @@ class Announcement < ApplicationRecord
     end
   end
 
-  def scope_for_notification
-    User.confirmed.joins(:account).merge(Account.without_suspended)
-  end
-
   private
 
   def grouped_ordered_announcement_reactions
diff --git a/app/models/announcement_mute.rb b/app/models/announcement_mute.rb
index 0f34a6a4dc..46fda2f5d6 100644
--- a/app/models/announcement_mute.rb
+++ b/app/models/announcement_mute.rb
@@ -5,10 +5,10 @@
 # Table name: announcement_mutes
 #
 #  id              :bigint(8)        not null, primary key
+#  account_id      :bigint(8)
+#  announcement_id :bigint(8)
 #  created_at      :datetime         not null
 #  updated_at      :datetime         not null
-#  account_id      :bigint(8)        not null
-#  announcement_id :bigint(8)        not null
 #
 
 class AnnouncementMute < ApplicationRecord
diff --git a/app/models/announcement_reaction.rb b/app/models/announcement_reaction.rb
index 46d9fc290f..f953402b7e 100644
--- a/app/models/announcement_reaction.rb
+++ b/app/models/announcement_reaction.rb
@@ -5,12 +5,12 @@
 # Table name: announcement_reactions
 #
 #  id              :bigint(8)        not null, primary key
+#  account_id      :bigint(8)
+#  announcement_id :bigint(8)
 #  name            :string           default(""), not null
+#  custom_emoji_id :bigint(8)
 #  created_at      :datetime         not null
 #  updated_at      :datetime         not null
-#  account_id      :bigint(8)        not null
-#  announcement_id :bigint(8)        not null
-#  custom_emoji_id :bigint(8)
 #
 
 class AnnouncementReaction < ApplicationRecord
diff --git a/app/models/annual_report/statuses_per_account_count.rb b/app/models/annual_report/statuses_per_account_count.rb
deleted file mode 100644
index 05a2f53c9d..0000000000
--- a/app/models/annual_report/statuses_per_account_count.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-# == Schema Information
-#
-# Table name: annual_report_statuses_per_account_counts
-#
-#  id             :bigint(8)        not null, primary key
-#  year           :integer          not null
-#  account_id     :bigint(8)        not null
-#  statuses_count :bigint(8)        not null
-#
-
-class AnnualReport::StatusesPerAccountCount < ApplicationRecord
-  # This table facilitates percentile calculations
-end
diff --git a/app/models/antenna.rb b/app/models/antenna.rb
index 44f9073e17..c64e66bde2 100644
--- a/app/models/antenna.rb
+++ b/app/models/antenna.rb
@@ -5,28 +5,27 @@
 # Table name: antennas
 #
 #  id               :bigint(8)        not null, primary key
-#  any_accounts     :boolean          default(TRUE), not null
-#  any_domains      :boolean          default(TRUE), not null
-#  any_keywords     :boolean          default(TRUE), not null
-#  any_tags         :boolean          default(TRUE), not null
-#  available        :boolean          default(TRUE), not null
-#  exclude_accounts :jsonb
-#  exclude_domains  :jsonb
-#  exclude_keywords :jsonb
-#  exclude_tags     :jsonb
-#  expires_at       :datetime
-#  favourite        :boolean          default(TRUE), not null
-#  ignore_reblog    :boolean          default(FALSE), not null
-#  insert_feeds     :boolean          default(FALSE), not null
-#  keywords         :jsonb
-#  ltl              :boolean          default(FALSE), not null
-#  stl              :boolean          default(FALSE), not null
+#  account_id       :bigint(8)        not null
+#  list_id          :bigint(8)        not null
 #  title            :string           default(""), not null
-#  with_media_only  :boolean          default(FALSE), not null
+#  keywords         :jsonb
+#  exclude_keywords :jsonb
+#  any_domains      :boolean          default(TRUE), not null
+#  any_tags         :boolean          default(TRUE), not null
+#  any_accounts     :boolean          default(TRUE), not null
+#  any_keywords     :boolean          default(TRUE), not null
+#  available        :boolean          default(TRUE), not null
 #  created_at       :datetime         not null
 #  updated_at       :datetime         not null
-#  account_id       :bigint(8)        not null
-#  list_id          :bigint(8)        default(0), not null
+#  expires_at       :datetime
+#  with_media_only  :boolean          default(FALSE), not null
+#  exclude_domains  :jsonb
+#  exclude_accounts :jsonb
+#  exclude_tags     :jsonb
+#  stl              :boolean          default(FALSE), not null
+#  ignore_reblog    :boolean          default(FALSE), not null
+#  insert_feeds     :boolean          default(FALSE), not null
+#  ltl              :boolean          default(FALSE), not null
 #
 class Antenna < ApplicationRecord
   include Expireable
diff --git a/app/models/appeal.rb b/app/models/appeal.rb
index 6a75fec661..fafa75e69d 100644
--- a/app/models/appeal.rb
+++ b/app/models/appeal.rb
@@ -16,6 +16,8 @@
 #  updated_at             :datetime         not null
 #
 class Appeal < ApplicationRecord
+  MAX_STRIKE_AGE = 20.days
+
   TEXT_LENGTH_LIMIT = 2_000
 
   belongs_to :account
@@ -66,6 +68,6 @@ class Appeal < ApplicationRecord
   private
 
   def validate_time_frame
-    errors.add(:base, I18n.t('strikes.errors.too_late')) unless strike.appeal_eligible?
+    errors.add(:base, I18n.t('strikes.errors.too_late')) if strike.created_at < MAX_STRIKE_AGE.ago
   end
 end
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index 24e9d6aeba..299aad6340 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class ApplicationRecord < ActiveRecord::Base
-  primary_abstract_class
+  self.abstract_class = true
 
   include Remotable
 
diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb
index e3e46d7b1c..9b3f4c8a3a 100644
--- a/app/models/bulk_import.rb
+++ b/app/models/bulk_import.rb
@@ -21,9 +21,6 @@
 class BulkImport < ApplicationRecord
   self.inheritance_column = false
 
-  ARCHIVE_PERIOD = 1.week
-  CONFIRM_PERIOD = 10.minutes
-
   belongs_to :account
   has_many :rows, class_name: 'BulkImportRow', inverse_of: :bulk_import, dependent: :delete_all
 
@@ -45,9 +42,6 @@ class BulkImport < ApplicationRecord
 
   validates :type, presence: true
 
-  scope :archival_completed, -> { where(created_at: ..ARCHIVE_PERIOD.ago) }
-  scope :confirmation_missed, -> { state_unconfirmed.where(created_at: ..CONFIRM_PERIOD.ago) }
-
   def self.progress!(bulk_import_id, imported: false)
     # Use `increment_counter` so that the incrementation is done atomically in the database
     BulkImport.increment_counter(:processed_items, bulk_import_id)
diff --git a/app/models/concerns/account/associations.rb b/app/models/concerns/account/associations.rb
index 90171a138d..ec8766f709 100644
--- a/app/models/concerns/account/associations.rb
+++ b/app/models/concerns/account/associations.rb
@@ -4,85 +4,95 @@ module Account::Associations
   extend ActiveSupport::Concern
 
   included do
-    # Core associations
-    with_options dependent: :destroy do
-      # Association where account owns record
-      with_options inverse_of: :account do
-        has_many :account_moderation_notes
-        has_many :account_pins
-        has_many :account_warnings
-        has_many :aliases, class_name: 'AccountAlias'
-        has_many :antenna_accounts
-        has_many :antennas
-        has_many :bookmarks
-        has_many :bookmark_categories
-        has_many :circle_accounts
-        has_many :circles
-        has_many :conversations, class_name: 'AccountConversation'
-        has_many :custom_filters
-        has_many :emoji_reactions
-        has_many :favourites
-        has_many :featured_tags, -> { includes(:tag) }
-        has_many :list_accounts
-        has_many :media_attachments
-        has_many :mentions
-        has_many :migrations, class_name: 'AccountMigration'
-        has_many :ng_rule_histories
-        has_many :notification_permissions
-        has_many :notification_requests
-        has_many :notifications
-        has_many :owned_lists, class_name: 'List'
-        has_many :polls
-        has_many :report_notes
-        has_many :reports
-        has_many :scheduled_statuses
-        has_many :scheduled_expiration_statuses
-        has_many :status_pins
-        has_many :statuses
+    # Local users
+    has_one :user, inverse_of: :account, dependent: :destroy
 
-        has_one :deletion_request, class_name: 'AccountDeletionRequest'
-        has_one :follow_recommendation_suppression
-        has_one :notification_policy
-        has_one :statuses_cleanup_policy, class_name: 'AccountStatusesCleanupPolicy'
-        has_one :user
-      end
+    # Timelines
+    has_many :statuses, inverse_of: :account, dependent: :destroy
+    has_many :favourites, inverse_of: :account, dependent: :destroy
+    has_many :emoji_reactions, inverse_of: :account, dependent: :destroy
+    has_many :bookmarks, inverse_of: :account, dependent: :destroy
+    has_many :bookmark_categories, inverse_of: :account, dependent: :destroy
+    has_many :circles, inverse_of: :account, dependent: :destroy
+    has_many :antennas, inverse_of: :account, dependent: :destroy
+    has_many :mentions, inverse_of: :account, dependent: :destroy
+    has_many :conversations, class_name: 'AccountConversation', dependent: :destroy, inverse_of: :account
+    has_many :scheduled_statuses, inverse_of: :account, dependent: :destroy
+    has_many :scheduled_expiration_statuses, inverse_of: :account, dependent: :destroy
+    has_many :ng_rule_histories, inverse_of: :account, dependent: :destroy
 
-      # Association where account is targeted by record
-      with_options foreign_key: :target_account_id, inverse_of: :target_account do
-        has_many :strikes, class_name: 'AccountWarning'
-        has_many :targeted_moderation_notes, class_name: 'AccountModerationNote'
-        has_many :targeted_reports, class_name: 'Report'
-      end
-    end
+    # Notifications
+    has_many :notifications, inverse_of: :account, dependent: :destroy
+    has_one :notification_policy, inverse_of: :account, dependent: :destroy
+    has_many :notification_permissions, inverse_of: :account, dependent: :destroy
+    has_many :notification_requests, inverse_of: :account, dependent: :destroy
 
-    # Status records pinned by the account
-    has_many :pinned_statuses, -> { reorder(status_pins: { created_at: :desc }) }, through: :status_pins, class_name: 'Status', source: :status
+    # Pinned statuses
+    has_many :status_pins, inverse_of: :account, dependent: :destroy
+    has_many :pinned_statuses, -> { reorder('status_pins.created_at DESC') }, through: :status_pins, class_name: 'Status', source: :status
 
-    # Account records endorsed (pinned) by the account
+    # Endorsements
+    has_many :account_pins, inverse_of: :account, dependent: :destroy
     has_many :endorsed_accounts, through: :account_pins, class_name: 'Account', source: :target_account
 
+    # Media
+    has_many :media_attachments, dependent: :destroy
+    has_many :polls, dependent: :destroy
+
+    # Report relationships
+    has_many :reports, dependent: :destroy, inverse_of: :account
+    has_many :targeted_reports, class_name: 'Report', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
+
+    has_many :report_notes, dependent: :destroy
+    has_many :custom_filters, inverse_of: :account, dependent: :destroy
+    has_many :antenna_accounts, inverse_of: :account, dependent: :destroy
+
+    # Moderation notes
+    has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account
+    has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
+    has_many :account_warnings, dependent: :destroy, inverse_of: :account
+    has_many :strikes, class_name: 'AccountWarning', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
+
     # Remote pendings
     has_many :pending_follow_requests, dependent: :destroy
     has_many :pending_statuses, dependent: :destroy
     has_many :fetchable_pending_statuses, class_name: 'PendingStatus', foreign_key: :fetch_account_id, dependent: :destroy, inverse_of: :fetch_account
 
-    # List records the account has been added to (not owned by the account)
-    has_many :lists, through: :list_accounts
-
-    # Account list items
+    # Antennas (that the account is on, not owned by the account)
+    has_many :antenna_accounts, inverse_of: :account, dependent: :destroy
     has_many :joined_antennas, class_name: 'Antenna', through: :antenna_accounts, source: :antenna
+
+    # Circles (that the account is on, not owned by the account)
+    has_many :circle_accounts, inverse_of: :account, dependent: :destroy
     has_many :joined_circles, class_name: 'Circle', through: :circle_accounts, source: :circle
 
-    # Account record where account has been migrated
+    # Lists (that the account is on, not owned by the account)
+    has_many :list_accounts, inverse_of: :account, dependent: :destroy
+    has_many :lists, through: :list_accounts
+
+    # Lists (owned by the account)
+    has_many :owned_lists, class_name: 'List', dependent: :destroy, inverse_of: :account
+
+    # Account migrations
     belongs_to :moved_to_account, class_name: 'Account', optional: true
+    has_many :migrations, class_name: 'AccountMigration', dependent: :destroy, inverse_of: :account
+    has_many :aliases, class_name: 'AccountAlias', dependent: :destroy, inverse_of: :account
 
-    # Tag records applied to account
+    # Hashtags
     has_and_belongs_to_many :tags # rubocop:disable Rails/HasAndBelongsToMany
+    has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
 
-    # FollowRecommendation for account (surfaced via view)
+    # Account deletion requests
+    has_one :deletion_request, class_name: 'AccountDeletionRequest', inverse_of: :account, dependent: :destroy
+
+    # Follow recommendations
     has_one :follow_recommendation, inverse_of: :account, dependent: nil
+    has_one :follow_recommendation_suppression, inverse_of: :account, dependent: :destroy
 
-    # BulkImport records owned by account
+    # Account statuses cleanup policy
+    has_one :statuses_cleanup_policy, class_name: 'AccountStatusesCleanupPolicy', inverse_of: :account, dependent: :destroy
+
+    # Imports
     has_many :bulk_imports, inverse_of: :account, dependent: :delete_all
   end
 end
diff --git a/app/models/concerns/account/attribution_domains.rb b/app/models/concerns/account/attribution_domains.rb
index 5f4d9afabf..163c2291fe 100644
--- a/app/models/concerns/account/attribution_domains.rb
+++ b/app/models/concerns/account/attribution_domains.rb
@@ -4,9 +4,21 @@ module Account::AttributionDomains
   extend ActiveSupport::Concern
 
   included do
-    normalizes :attribution_domains, with: ->(arr) { arr.filter_map { |str| str.to_s.strip.delete_prefix('http://').delete_prefix('https://').delete_prefix('*.').presence }.uniq }
+    validates :attribution_domains_as_text, domain: { multiline: true }, lines: { maximum: 100 }, if: -> { local? && will_save_change_to_attribution_domains? }
+  end
 
-    validates :attribution_domains, domain: true, length: { maximum: 100 }, if: -> { local? && will_save_change_to_attribution_domains? }
+  def attribution_domains_as_text
+    self[:attribution_domains].join("\n")
+  end
+
+  def attribution_domains_as_text=(str)
+    self[:attribution_domains] = str.split.filter_map do |line|
+      line
+        .strip
+        .delete_prefix('http://')
+        .delete_prefix('https://')
+        .delete_prefix('*.')
+    end
   end
 
   def can_be_attributed_from?(domain)
diff --git a/app/models/concerns/account/avatar.rb b/app/models/concerns/account/avatar.rb
index f991542f7b..5ca8fa862f 100644
--- a/app/models/concerns/account/avatar.rb
+++ b/app/models/concerns/account/avatar.rb
@@ -3,9 +3,9 @@
 module Account::Avatar
   extend ActiveSupport::Concern
 
-  AVATAR_IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/heic', 'image/avif', 'image/heif'].freeze
-  AVATAR_IMAGE_CONVERTIBLE_MIME_TYPES = ['image/heic', 'image/avif', 'image/heif'].freeze
-  AVATAR_LIMIT = Rails.configuration.x.use_vips ? 8.megabytes : 2.megabytes
+  IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
+  LIMIT = 2.megabytes
+
   AVATAR_DIMENSIONS = [400, 400].freeze
   AVATAR_GEOMETRY = [AVATAR_DIMENSIONS.first, AVATAR_DIMENSIONS.last].join('x')
 
@@ -13,9 +13,6 @@ module Account::Avatar
     def avatar_styles(file)
       styles = { original: { geometry: "#{AVATAR_GEOMETRY}#", file_geometry_parser: FastGeometryParser } }
       styles[:static] = { geometry: "#{AVATAR_GEOMETRY}#", format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif'
-
-      styles[:original] = { format: 'jpeg', content_type: 'image/jpeg' }.merge(styles[:original]) if AVATAR_IMAGE_CONVERTIBLE_MIME_TYPES.include?(file.content_type)
-
       styles
     end
 
@@ -24,10 +21,10 @@ module Account::Avatar
 
   included do
     # Avatar upload
-    has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '+profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, processors: [:lazy_thumbnail, :type_corrector]
-    validates_attachment_content_type :avatar, content_type: AVATAR_IMAGE_MIME_TYPES
-    validates_attachment_size :avatar, less_than: AVATAR_LIMIT
-    remotable_attachment :avatar, AVATAR_LIMIT, suppress_errors: false
+    has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '+profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, processors: [:lazy_thumbnail]
+    validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES
+    validates_attachment_size :avatar, less_than: LIMIT
+    remotable_attachment :avatar, LIMIT, suppress_errors: false
   end
 
   def avatar_original_url
diff --git a/app/models/concerns/account/header.rb b/app/models/concerns/account/header.rb
index 662ee7caf7..2a47097fcf 100644
--- a/app/models/concerns/account/header.rb
+++ b/app/models/concerns/account/header.rb
@@ -3,15 +3,16 @@
 module Account::Header
   extend ActiveSupport::Concern
 
-  HEADER_IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
-  HEADER_LIMIT = Rails.configuration.x.use_vips ? 8.megabytes : 2.megabytes
+  IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
+  LIMIT = 2.megabytes
+
   HEADER_DIMENSIONS = [1500, 500].freeze
   HEADER_GEOMETRY = [HEADER_DIMENSIONS.first, HEADER_DIMENSIONS.last].join('x')
-  HEADER_MAX_PIXELS = HEADER_DIMENSIONS.first * HEADER_DIMENSIONS.last
+  MAX_PIXELS = HEADER_DIMENSIONS.first * HEADER_DIMENSIONS.last
 
   class_methods do
     def header_styles(file)
-      styles = { original: { pixels: HEADER_MAX_PIXELS, file_geometry_parser: FastGeometryParser } }
+      styles = { original: { pixels: MAX_PIXELS, file_geometry_parser: FastGeometryParser } }
       styles[:static] = { format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif'
       styles
     end
@@ -22,9 +23,9 @@ module Account::Header
   included do
     # Header upload
     has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '+profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, processors: [:lazy_thumbnail]
-    validates_attachment_content_type :header, content_type: HEADER_IMAGE_MIME_TYPES
-    validates_attachment_size :header, less_than: HEADER_LIMIT
-    remotable_attachment :header, HEADER_LIMIT, suppress_errors: false
+    validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES
+    validates_attachment_size :header, less_than: LIMIT
+    remotable_attachment :header, LIMIT, suppress_errors: false
   end
 
   def header_original_url
diff --git a/app/models/concerns/account/interactions.rb b/app/models/concerns/account/interactions.rb
index 259b51c194..65224145c3 100644
--- a/app/models/concerns/account/interactions.rb
+++ b/app/models/concerns/account/interactions.rb
@@ -80,8 +80,8 @@ module Account::Interactions
       has_many :passive_relationships, foreign_key: 'target_account_id', inverse_of: :target_account
     end
 
-    has_many :following, -> { order(follows: { id: :desc }) }, through: :active_relationships,  source: :target_account
-    has_many :followers, -> { order(follows: { id: :desc }) }, through: :passive_relationships, source: :account
+    has_many :following, -> { order('follows.id desc') }, through: :active_relationships,  source: :target_account
+    has_many :followers, -> { order('follows.id desc') }, through: :passive_relationships, source: :account
 
     with_options class_name: 'SeveredRelationship', dependent: :destroy do
       has_many :severed_relationships, foreign_key: 'local_account_id', inverse_of: :local_account
@@ -99,23 +99,23 @@ module Account::Interactions
       has_many :block_relationships, foreign_key: 'account_id', inverse_of: :account
       has_many :blocked_by_relationships, foreign_key: :target_account_id, inverse_of: :target_account
     end
-    has_many :blocking, -> { order(blocks: { id: :desc }) }, through: :block_relationships, source: :target_account
-    has_many :blocked_by, -> { order(blocks: { id: :desc }) }, through: :blocked_by_relationships, source: :account
+    has_many :blocking, -> { order('blocks.id desc') }, through: :block_relationships, source: :target_account
+    has_many :blocked_by, -> { order('blocks.id desc') }, through: :blocked_by_relationships, source: :account
 
     # Mute relationships
     with_options class_name: 'Mute', dependent: :destroy do
       has_many :mute_relationships, foreign_key: 'account_id', inverse_of: :account
       has_many :muted_by_relationships, foreign_key: :target_account_id, inverse_of: :target_account
     end
-    has_many :muting, -> { order(mutes: { id: :desc }) }, through: :mute_relationships, source: :target_account
-    has_many :muted_by, -> { order(mutes: { id: :desc }) }, through: :muted_by_relationships, source: :account
+    has_many :muting, -> { order('mutes.id desc') }, through: :mute_relationships, source: :target_account
+    has_many :muted_by, -> { order('mutes.id desc') }, through: :muted_by_relationships, source: :account
     has_many :conversation_mutes, dependent: :destroy
     has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy
     has_many :announcement_mutes, dependent: :destroy
   end
 
   def follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false)
-    rel = active_relationships.create_with(show_reblogs: reblogs.nil? || reblogs, notify: notify.nil? ? false : notify, languages: languages, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
+    rel = active_relationships.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, languages: languages, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
                               .find_or_create_by!(target_account: other_account)
 
     rel.show_reblogs = reblogs   unless reblogs.nil?
@@ -128,7 +128,7 @@ module Account::Interactions
   end
 
   def request_follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false)
-    rel = follow_requests.create_with(show_reblogs: reblogs.nil? || reblogs, notify: notify.nil? ? false : notify, uri: uri, languages: languages, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
+    rel = follow_requests.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, uri: uri, languages: languages, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
                          .find_or_create_by!(target_account: other_account)
 
     rel.show_reblogs = reblogs   unless reblogs.nil?
diff --git a/app/models/concerns/account/sensitizes.rb b/app/models/concerns/account/sensitizes.rb
deleted file mode 100644
index 3bb74324a8..0000000000
--- a/app/models/concerns/account/sensitizes.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-module Account::Sensitizes
-  extend ActiveSupport::Concern
-
-  included do
-    scope :sensitized, -> { where.not(sensitized_at: nil) }
-  end
-
-  def sensitized?
-    sensitized_at.present?
-  end
-
-  def sensitize!(date = Time.now.utc)
-    update!(sensitized_at: date)
-  end
-
-  def unsensitize!
-    update!(sensitized_at: nil)
-  end
-end
diff --git a/app/models/concerns/account/silences.rb b/app/models/concerns/account/silences.rb
deleted file mode 100644
index bd785df311..0000000000
--- a/app/models/concerns/account/silences.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-module Account::Silences
-  extend ActiveSupport::Concern
-
-  included do
-    scope :silenced, -> { where.not(silenced_at: nil) }
-    scope :without_silenced, -> { where(silenced_at: nil) }
-  end
-
-  def silenced?
-    silenced_at.present?
-  end
-
-  def silence!(date = Time.now.utc)
-    update!(silenced_at: date)
-  end
-
-  def unsilence!
-    update!(silenced_at: nil)
-  end
-end
diff --git a/app/models/concerns/account/suspensions.rb b/app/models/concerns/account/suspensions.rb
index cdf3c1cb24..c981fb5a29 100644
--- a/app/models/concerns/account/suspensions.rb
+++ b/app/models/concerns/account/suspensions.rb
@@ -6,7 +6,6 @@ module Account::Suspensions
   included do
     scope :suspended, -> { where.not(suspended_at: nil) }
     scope :without_suspended, -> { where(suspended_at: nil) }
-    scope :remote_pending, -> { where(remote_pending: true).where.not(suspended_at: nil) }
   end
 
   def suspended?
@@ -42,21 +41,4 @@ module Account::Suspensions
       destroy_canonical_email_block!
     end
   end
-
-  def approve_remote!
-    return unless remote_pending
-
-    update!(remote_pending: false)
-    unsuspend!
-    ActivateRemoteAccountWorker.perform_async(id)
-  end
-
-  def reject_remote!
-    return unless remote_pending
-
-    update!(remote_pending: false, suspension_origin: :local)
-    pending_follow_requests.destroy_all
-    pending_statuses.destroy_all
-    suspend!
-  end
 end
diff --git a/app/models/concerns/fasp/provider/debug_concern.rb b/app/models/concerns/fasp/provider/debug_concern.rb
deleted file mode 100644
index eee046a17f..0000000000
--- a/app/models/concerns/fasp/provider/debug_concern.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module Fasp::Provider::DebugConcern
-  extend ActiveSupport::Concern
-
-  def perform_debug_call
-    Fasp::Request.new(self)
-                 .post('/debug/v0/callback/logs', body: { hello: 'world' })
-  end
-end
diff --git a/app/models/concerns/inet_container.rb b/app/models/concerns/inet_container.rb
deleted file mode 100644
index da03bcc5d7..0000000000
--- a/app/models/concerns/inet_container.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module InetContainer
-  extend ActiveSupport::Concern
-
-  included do
-    scope :containing, ->(value) { where('ip >>= ?', value) }
-    scope :contained_by, ->(value) { where('ip <<= ?', value) }
-  end
-end
diff --git a/app/models/concerns/notification/groups.rb b/app/models/concerns/notification/groups.rb
deleted file mode 100644
index bea0dbca05..0000000000
--- a/app/models/concerns/notification/groups.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-# frozen_string_literal: true
-
-module Notification::Groups
-  extend ActiveSupport::Concern
-
-  # `set_group_key!` needs to be updated if this list changes
-  GROUPABLE_NOTIFICATION_TYPES = %i(favourite reblog follow emoji_reaction admin.sign_up).freeze
-  MAXIMUM_GROUP_SPAN_HOURS = 12
-
-  included do
-    scope :by_group_key, ->(group_key) { group_key&.start_with?('ungrouped-') ? where(id: group_key.delete_prefix('ungrouped-')) : where(group_key: group_key) }
-  end
-
-  def set_group_key!
-    return if filtered? || GROUPABLE_NOTIFICATION_TYPES.exclude?(type)
-
-    type_prefix = case type
-                  when :favourite, :reblog, :emoji_reaction
-                    [type, target_status&.id].join('-')
-                  when :follow, :'admin.sign_up'
-                    type
-                  else
-                    raise NotImplementedError
-                  end
-    redis_key   = "notif-group/#{account.id}/#{type_prefix}"
-    hour_bucket = activity.created_at.utc.to_i / 1.hour.to_i
-
-    # Reuse previous group if it does not span too large an amount of time
-    previous_bucket = redis.get(redis_key).to_i
-    hour_bucket = previous_bucket if hour_bucket < previous_bucket + MAXIMUM_GROUP_SPAN_HOURS
-
-    # We do not concern ourselves with race conditions since we use hour buckets
-    redis.set(redis_key, hour_bucket, ex: MAXIMUM_GROUP_SPAN_HOURS.hours.to_i)
-
-    self.group_key = "#{type_prefix}-#{hour_bucket}"
-  end
-
-  class_methods do
-    def paginate_groups(limit, pagination_order, grouped_types: nil)
-      raise ArgumentError unless %i(asc desc).include?(pagination_order)
-
-      query = reorder(id: pagination_order)
-
-      # Ideally `:types` would be a bind rather than part of the SQL itself, but that does not
-      # seem to be possible to do with Rails, considering that the expression would occur in
-      # multiple places, including in a `select`
-      group_key_sql = begin
-        if grouped_types.present?
-          # Normalize `grouped_types` so the number of different SQL query shapes remains small, and
-          # the queries can be analyzed in monitoring/telemetry tools
-          grouped_types = (grouped_types.map(&:to_sym) & GROUPABLE_NOTIFICATION_TYPES).sort
-
-          sanitize_sql_array([<<~SQL.squish, { types: grouped_types }])
-            COALESCE(
-              CASE
-                WHEN notifications.type IN (:types) THEN notifications.group_key
-                ELSE NULL
-              END,
-              'ungrouped-' || notifications.id
-            )
-          SQL
-        else
-          "COALESCE(notifications.group_key, 'ungrouped-' || notifications.id)"
-        end
-      end
-
-      unscoped
-        .with_recursive(
-          grouped_notifications: [
-            # Base case: fetching one notification and annotating it with visited groups
-            query
-              .select('notifications.*', "ARRAY[#{group_key_sql}] AS groups")
-              .limit(1),
-            # Recursive case, always yielding at most one annotated notification
-            unscoped
-              .from(
-                [
-                  # Expose the working table as `wt`, but quit early if we've reached the limit
-                  unscoped
-                    .select('id', 'groups')
-                    .from('grouped_notifications')
-                    .where('array_length(grouped_notifications.groups, 1) < :limit', limit: limit)
-                    .arel.as('wt'),
-                  # Recursive query, using `LATERAL` so we can refer to `wt`
-                  query
-                    .where(pagination_order == :desc ? 'notifications.id < wt.id' : 'notifications.id > wt.id')
-                    .where.not("#{group_key_sql} = ANY(wt.groups)")
-                    .limit(1)
-                    .arel.lateral('notifications'),
-                ]
-              )
-              .select('notifications.*', "array_append(wt.groups, #{group_key_sql}) AS groups"),
-          ]
-        )
-        .from('grouped_notifications AS notifications')
-        .order(id: pagination_order)
-        .limit(limit)
-    end
-
-    # This returns notifications from the request page, but with at most one notification per group.
-    # Notifications that have no `group_key` each count as a separate group.
-    def paginate_groups_by_max_id(limit, max_id: nil, since_id: nil, grouped_types: nil)
-      query = reorder(id: :desc)
-      query = query.where(id: ...(max_id.to_i)) if max_id.present?
-      query = query.where(id: (since_id.to_i + 1)...) if since_id.present?
-      query.paginate_groups(limit, :desc, grouped_types: grouped_types)
-    end
-
-    # Differs from :paginate_groups_by_max_id in that it gives the results immediately following min_id,
-    # whereas since_id gives the items with largest id, but with since_id as a cutoff.
-    # Results will be in ascending order by id.
-    def paginate_groups_by_min_id(limit, max_id: nil, min_id: nil, grouped_types: nil)
-      query = reorder(id: :asc)
-      query = query.where(id: (min_id.to_i + 1)...) if min_id.present?
-      query = query.where(id: ...(max_id.to_i)) if max_id.present?
-      query.paginate_groups(limit, :asc, grouped_types: grouped_types)
-    end
-
-    def to_a_grouped_paginated_by_id(limit, options = {})
-      if options[:min_id].present?
-        paginate_groups_by_min_id(limit, min_id: options[:min_id], max_id: options[:max_id], grouped_types: options[:grouped_types]).reverse
-      else
-        paginate_groups_by_max_id(limit, max_id: options[:max_id], since_id: options[:since_id], grouped_types: options[:grouped_types]).to_a
-      end
-    end
-  end
-end
diff --git a/app/models/concerns/ranked_trend.rb b/app/models/concerns/ranked_trend.rb
index e707fe0bad..add36afb0c 100644
--- a/app/models/concerns/ranked_trend.rb
+++ b/app/models/concerns/ranked_trend.rb
@@ -9,10 +9,6 @@ module RankedTrend
   end
 
   class_methods do
-    def locales
-      distinct.pluck(:language)
-    end
-
     def recalculate_ordered_rank
       connection
         .exec_update(<<~SQL.squish)
diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb
index 15133e7bde..8382c91599 100644
--- a/app/models/concerns/remotable.rb
+++ b/app/models/concerns/remotable.rb
@@ -26,7 +26,7 @@ module Remotable
 
             public_send(:"#{attachment_name}=", ResponseWithLimit.new(response, limit))
           end
-        rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS => e
+        rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError => e
           Rails.logger.debug { "Error fetching remote #{attachment_name}: #{e}" }
           public_send(:"#{attachment_name}=", nil) if public_send(:"#{attachment_name}_file_name").present?
           raise e unless suppress_errors
diff --git a/app/models/concerns/status/fetch_replies_concern.rb b/app/models/concerns/status/fetch_replies_concern.rb
deleted file mode 100644
index fd9929aba4..0000000000
--- a/app/models/concerns/status/fetch_replies_concern.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-module Status::FetchRepliesConcern
-  extend ActiveSupport::Concern
-
-  # enable/disable fetching all replies
-  FETCH_REPLIES_ENABLED = ENV['FETCH_REPLIES_ENABLED'] == 'true'
-
-  # debounce fetching all replies to minimize DoS
-  FETCH_REPLIES_COOLDOWN_MINUTES = (ENV['FETCH_REPLIES_COOLDOWN_MINUTES'] || 15).to_i.minutes
-  FETCH_REPLIES_INITIAL_WAIT_MINUTES = (ENV['FETCH_REPLIES_INITIAL_WAIT_MINUTES'] || 5).to_i.minutes
-
-  included do
-    scope :created_recently, -> { where(created_at: FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago..) }
-    scope :not_created_recently, -> { where(created_at: ..FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago) }
-    scope :fetched_recently, -> { where(fetched_replies_at: FETCH_REPLIES_COOLDOWN_MINUTES.ago..) }
-    scope :not_fetched_recently, -> { where(fetched_replies_at: [nil, ..FETCH_REPLIES_COOLDOWN_MINUTES.ago]) }
-
-    scope :should_not_fetch_replies, -> { local.or(created_recently.or(fetched_recently)) }
-    scope :should_fetch_replies, -> { remote.not_created_recently.not_fetched_recently }
-
-    # statuses for which we won't receive update or deletion actions,
-    # and should update when fetching replies
-    # Status from an account which either
-    # a) has only remote followers
-    # b) has local follows that were created after the last update time, or
-    # c) has no known followers
-    scope :unsubscribed, lambda {
-      remote.merge(
-        Status.left_outer_joins(account: :followers).where.not(followers_accounts: { domain: nil })
-              .or(where.not('follows.created_at < statuses.updated_at'))
-              .or(where(follows: { id: nil }))
-      )
-    }
-  end
-
-  def should_fetch_replies?
-    # we aren't brand new, and we haven't fetched replies since the debounce window
-    FETCH_REPLIES_ENABLED && !local? && created_at <= FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago && (
-      fetched_replies_at.nil? || fetched_replies_at <= FETCH_REPLIES_COOLDOWN_MINUTES.ago
-    )
-  end
-end
diff --git a/app/models/concerns/status/safe_reblog_insert.rb b/app/models/concerns/status/safe_reblog_insert.rb
index 48d585ea18..60ddb78e52 100644
--- a/app/models/concerns/status/safe_reblog_insert.rb
+++ b/app/models/concerns/status/safe_reblog_insert.rb
@@ -15,9 +15,7 @@ module Status::SafeReblogInsert
     #
     # The code is kept similar to ActiveRecord::Persistence code and calls it
     # directly when we are not handling a reblog.
-    #
-    # https://github.com/rails/rails/blob/v7.2.1.1/activerecord/lib/active_record/persistence.rb#L238-L263
-    def _insert_record(connection, values, returning)
+    def _insert_record(values, returning)
       return super unless values.is_a?(Hash) && values['reblog_of_id']&.value.present?
 
       primary_key = self.primary_key
@@ -32,19 +30,14 @@ module Status::SafeReblogInsert
 
       # The following line departs from stock ActiveRecord
       # Original code was:
-      # im = Arel::InsertManager.new(arel_table)
+      # im.insert(values.transform_keys { |name| arel_table[name] })
       # Instead, we use a custom builder when a reblog is happening:
       im = _compile_reblog_insert(values)
 
-      with_connection do |_c|
-        connection.insert(
-          im, "#{self} Create", primary_key || false, primary_key_value,
-          returning: returning
-        ).tap do |result|
-          # Since we are using SELECT instead of VALUES, a non-error `nil` return is possible.
-          # For our purposes, it's equivalent to a foreign key constraint violation
-          raise ActiveRecord::InvalidForeignKey, "(reblog_of_id)=(#{values['reblog_of_id'].value}) is not present in table \"statuses\"" if result.nil?
-        end
+      connection.insert(im, "#{self} Create", primary_key || false, primary_key_value, returning: returning).tap do |result|
+        # Since we are using SELECT instead of VALUES, a non-error `nil` return is possible.
+        # For our purposes, it's equivalent to a foreign key constraint violation
+        raise ActiveRecord::InvalidForeignKey, "(reblog_of_id)=(#{values['reblog_of_id'].value}) is not present in table \"statuses\"" if result.nil?
       end
     end
 
diff --git a/app/models/concerns/status/snapshot_concern.rb b/app/models/concerns/status/snapshot_concern.rb
index b0507da5d3..bc469ee2d1 100644
--- a/app/models/concerns/status/snapshot_concern.rb
+++ b/app/models/concerns/status/snapshot_concern.rb
@@ -30,7 +30,7 @@ module Status::SnapshotConcern
     )
   end
 
-  def snapshot!(**)
-    build_snapshot(**).save!
+  def snapshot!(**options)
+    build_snapshot(**options).save!
   end
 end
diff --git a/app/models/concerns/status/visibility.rb b/app/models/concerns/status/visibility.rb
deleted file mode 100644
index 9e812026f6..0000000000
--- a/app/models/concerns/status/visibility.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-# frozen_string_literal: true
-
-module Status::Visibility
-  extend ActiveSupport::Concern
-
-  included do
-    enum :visibility,
-         { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, public_unlisted: 10, login: 11 },
-         suffix: :visibility,
-         validate: true
-    enum :searchability,
-         { public: 0, private: 1, direct: 2, limited: 3, unsupported: 4, public_unlisted: 10 },
-         suffix: :searchability
-    enum :limited_scope,
-         { none: 0, mutual: 1, circle: 2, personal: 3, reply: 4 },
-         suffix: :limited
-
-    scope :unset_searchability, -> { where(searchability: nil, reblog_of_id: nil) }
-    scope :distributable_visibility, -> { where(visibility: %i(public unlisted)) }
-    scope :distributable_visibility_for_anonymous, -> { where(visibility: %i(public public_unlisted unlisted)) }
-    scope :list_eligible_visibility, -> { where(visibility: %i(public unlisted private)) }
-    scope :not_direct_visibility, -> { where.not(visibility: :direct) }
-
-    validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog?
-
-    before_validation :set_visibility, unless: :visibility?
-    before_validation :set_searchability
-  end
-
-  class_methods do
-    def selectable_visibilities
-      selectable_all_visibilities - %w(mutual circle reply direct)
-    end
-
-    def selectable_all_visibilities
-      vs = %w(public public_unlisted login unlisted private mutual circle reply direct)
-      vs -= %w(public_unlisted) unless Setting.enable_public_unlisted_visibility
-      vs -= %w(public) unless Setting.enable_public_visibility
-      vs
-    end
-
-    def selectable_reblog_visibilities
-      %w(unset) + selectable_visibilities
-    end
-
-    def all_visibilities
-      visibilities.keys
-    end
-
-    def selectable_searchabilities
-      ss = searchabilities.keys - %w(unsupported)
-      ss -= %w(public_unlisted) unless Setting.enable_public_unlisted_visibility
-      ss
-    end
-
-    def selectable_searchabilities_for_search
-      searchabilities.keys - %w(public_unlisted unsupported)
-    end
-
-    def all_searchabilities
-      searchabilities.keys - %w(unlisted login unsupported)
-    end
-  end
-
-  def hidden?
-    !distributable?
-  end
-
-  def distributable?
-    public_visibility? || unlisted_visibility? || public_unlisted_visibility?
-  end
-
-  alias sign? distributable?
-
-  private
-
-  def set_visibility
-    self.visibility ||= reblog.visibility if reblog?
-    self.visibility ||= visibility_from_account
-  end
-
-  def visibility_from_account
-    account.locked? ? :private : :public
-  end
-
-  def set_searchability
-    return if searchability.nil?
-
-    self.searchability = if %w(public public_unlisted login unlisted).include?(visibility)
-                           searchability
-                         elsif ['limited', 'direct'].include?(visibility)
-                           searchability == 'limited' ? :limited : :direct
-                         elsif visibility == 'private'
-                           ['public', 'public_unlisted'].include?(searchability) ? :private : searchability
-                         else
-                           :direct
-                         end
-  end
-end
diff --git a/app/models/concerns/user/has_settings.rb b/app/models/concerns/user/has_settings.rb
index 2f8b11dcec..655e589aae 100644
--- a/app/models/concerns/user/has_settings.rb
+++ b/app/models/concerns/user/has_settings.rb
@@ -123,10 +123,6 @@ module User::HasSettings
     settings['web.show_relationships']
   end
 
-  def setting_show_avatar_on_filter
-    settings['web.show_avatar_on_filter']
-  end
-
   def setting_allow_quote
     settings['allow_quote']
   end
@@ -135,10 +131,6 @@ module User::HasSettings
     settings['reject_send_limited_to_suspects']
   end
 
-  def setting_system_scrollbars_ui
-    settings['web.use_system_scrollbars']
-  end
-
   def setting_noindex
     settings['noindex']
   end
@@ -291,10 +283,6 @@ module User::HasSettings
     settings['web.hide_favourite_menu']
   end
 
-  def setting_use_server_css
-    settings['web.use_server_css']
-  end
-
   def setting_use_custom_css
     settings['web.use_custom_css']
   end
diff --git a/app/models/custom_emoji_category.rb b/app/models/custom_emoji_category.rb
index dfcc156080..f480746517 100644
--- a/app/models/custom_emoji_category.rb
+++ b/app/models/custom_emoji_category.rb
@@ -14,6 +14,4 @@ class CustomEmojiCategory < ApplicationRecord
   has_many :emojis, class_name: 'CustomEmoji', foreign_key: 'category_id', inverse_of: :category, dependent: nil
 
   validates :name, presence: true, uniqueness: true
-
-  scope :alphabetic, -> { order(name: :asc) }
 end
diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb
index b67105aade..0e3e64fcf0 100644
--- a/app/models/custom_filter.rb
+++ b/app/models/custom_filter.rb
@@ -5,17 +5,17 @@
 # Table name: custom_filters
 #
 #  id                 :bigint(8)        not null, primary key
-#  action             :integer          default("warn"), not null
-#  context            :string           default([]), not null, is an Array
-#  exclude_follows    :boolean          default(FALSE), not null
-#  exclude_localusers :boolean          default(FALSE), not null
+#  account_id         :bigint(8)
 #  expires_at         :datetime
 #  phrase             :text             default(""), not null
-#  with_profile       :boolean          default(FALSE), not null
-#  with_quote         :boolean          default(TRUE), not null
+#  context            :string           default([]), not null, is an Array
 #  created_at         :datetime         not null
 #  updated_at         :datetime         not null
-#  account_id         :bigint(8)        not null
+#  action             :integer          default("warn"), not null
+#  exclude_follows    :boolean          default(FALSE), not null
+#  exclude_localusers :boolean          default(FALSE), not null
+#  with_quote         :boolean          default(TRUE), not null
+#  with_profile       :boolean          default(FALSE), not null
 #
 
 class CustomFilter < ApplicationRecord
@@ -38,7 +38,7 @@ class CustomFilter < ApplicationRecord
   include Expireable
   include Redisable
 
-  enum :action, { warn: 0, hide: 1, blur: 2 }, suffix: :action, validate: true
+  enum :action, { warn: 0, hide: 1, half_warn: 2 }, suffix: :action
 
   belongs_to :account
   has_many :keywords, class_name: 'CustomFilterKeyword', inverse_of: :custom_filter, dependent: :destroy
diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb
index 51ce4c7e31..a6c6cc045b 100644
--- a/app/models/domain_block.rb
+++ b/app/models/domain_block.rb
@@ -31,7 +31,7 @@ class DomainBlock < ApplicationRecord
   include DomainNormalizable
   include DomainMaterializable
 
-  enum :severity, { silence: 0, suspend: 1, noop: 2 }, validate: true
+  enum :severity, { silence: 0, suspend: 1, noop: 2 }
 
   validates :domain, presence: true, uniqueness: true, domain: true
 
@@ -70,9 +70,7 @@ class DomainBlock < ApplicationRecord
        reject_friend? ? :reject_friend : nil,
        block_trends? ? :block_trends : nil,
        detect_invalid_subscription? ? :detect_invalid_subscription : nil,
-       reject_reports? ? :reject_reports : nil]
-        .reject { |policy| policy == :noop }
-        .compact
+       reject_reports? ? :reject_reports : nil].reject { |policy| policy == :noop || policy.nil? }
     end
   end
 
diff --git a/app/models/email_domain_block.rb b/app/models/email_domain_block.rb
index 583d2e6c1b..f3a86eae8f 100644
--- a/app/models/email_domain_block.rb
+++ b/app/models/email_domain_block.rb
@@ -28,8 +28,6 @@ class EmailDomainBlock < ApplicationRecord
 
   validates :domain, presence: true, uniqueness: true, domain: true
 
-  scope :parents, -> { where(parent_id: nil) }
-
   # Used for adding multiple blocks at once
   attr_accessor :other_domains
 
diff --git a/app/models/export.rb b/app/models/export.rb
index 6ed9f60c7c..2457dcc156 100644
--- a/app/models/export.rb
+++ b/app/models/export.rb
@@ -55,6 +55,42 @@ class Export
     end
   end
 
+  def total_storage
+    account.media_attachments.sum(:file_file_size)
+  end
+
+  def total_statuses
+    account.statuses_count
+  end
+
+  def total_bookmarks
+    account.bookmarks.count
+  end
+
+  def total_follows
+    account.following_count
+  end
+
+  def total_lists
+    account.owned_lists.count
+  end
+
+  def total_followers
+    account.followers_count
+  end
+
+  def total_blocks
+    account.blocking.count
+  end
+
+  def total_mutes
+    account.muting.count
+  end
+
+  def total_domain_blocks
+    account.domain_blocks.count
+  end
+
   private
 
   def to_csv(accounts)
diff --git a/app/models/fasp.rb b/app/models/fasp.rb
deleted file mode 100644
index cb33937715..0000000000
--- a/app/models/fasp.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-module Fasp
-  def self.table_name_prefix
-    'fasp_'
-  end
-end
diff --git a/app/models/fasp/capability.rb b/app/models/fasp/capability.rb
deleted file mode 100644
index eb41571e57..0000000000
--- a/app/models/fasp/capability.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-class Fasp::Capability
-  include ActiveModel::Model
-  include ActiveModel::Attributes
-
-  attribute :id, :string
-  attribute :version, :string
-  attribute :enabled, :boolean, default: false
-end
diff --git a/app/models/fasp/debug_callback.rb b/app/models/fasp/debug_callback.rb
deleted file mode 100644
index 30f5d1c37d..0000000000
--- a/app/models/fasp/debug_callback.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-# == Schema Information
-#
-# Table name: fasp_debug_callbacks
-#
-#  id               :bigint(8)        not null, primary key
-#  ip               :string           not null
-#  request_body     :text             not null
-#  created_at       :datetime         not null
-#  updated_at       :datetime         not null
-#  fasp_provider_id :bigint(8)        not null
-#
-class Fasp::DebugCallback < ApplicationRecord
-  belongs_to :fasp_provider, class_name: 'Fasp::Provider'
-end
diff --git a/app/models/fasp/provider.rb b/app/models/fasp/provider.rb
deleted file mode 100644
index cd1b3008c7..0000000000
--- a/app/models/fasp/provider.rb
+++ /dev/null
@@ -1,141 +0,0 @@
-# frozen_string_literal: true
-
-# == Schema Information
-#
-# Table name: fasp_providers
-#
-#  id                      :bigint(8)        not null, primary key
-#  base_url                :string           not null
-#  capabilities            :jsonb            not null
-#  confirmed               :boolean          default(FALSE), not null
-#  contact_email           :string
-#  fediverse_account       :string
-#  name                    :string           not null
-#  privacy_policy          :jsonb
-#  provider_public_key_pem :string           not null
-#  remote_identifier       :string           not null
-#  server_private_key_pem  :string           not null
-#  sign_in_url             :string
-#  created_at              :datetime         not null
-#  updated_at              :datetime         not null
-#
-class Fasp::Provider < ApplicationRecord
-  include DebugConcern
-
-  has_many :fasp_debug_callbacks, inverse_of: :fasp_provider, class_name: 'Fasp::DebugCallback', dependent: :delete_all
-
-  validates :name, presence: true
-  validates :base_url, presence: true, url: true
-  validates :provider_public_key_pem, presence: true
-  validates :remote_identifier, presence: true
-
-  before_create :create_keypair
-  after_commit :update_remote_capabilities
-
-  def capabilities
-    read_attribute(:capabilities).map do |attributes|
-      Fasp::Capability.new(attributes)
-    end
-  end
-
-  def capabilities_attributes=(attributes)
-    capability_objects = attributes.values.map { |a| Fasp::Capability.new(a) }
-    self[:capabilities] = capability_objects.map(&:attributes)
-  end
-
-  def enabled_capabilities
-    capabilities.select(&:enabled).map(&:id)
-  end
-
-  def capability?(capability_name)
-    return false unless confirmed?
-
-    capabilities.present? && capabilities.any? do |capability|
-      capability.id == capability_name
-    end
-  end
-
-  def capability_enabled?(capability_name)
-    return false unless confirmed?
-
-    capabilities.present? && capabilities.any? do |capability|
-      capability.id == capability_name && capability.enabled
-    end
-  end
-
-  def server_private_key
-    @server_private_key ||= OpenSSL::PKey.read(server_private_key_pem)
-  end
-
-  def server_public_key_base64
-    Base64.strict_encode64(server_private_key.raw_public_key)
-  end
-
-  def provider_public_key_base64=(string)
-    return if string.blank?
-
-    self.provider_public_key_pem =
-      OpenSSL::PKey.new_raw_public_key(
-        'ed25519',
-        Base64.strict_decode64(string)
-      ).public_to_pem
-  end
-
-  def provider_public_key
-    @provider_public_key ||= OpenSSL::PKey.read(provider_public_key_pem)
-  end
-
-  def provider_public_key_raw
-    provider_public_key.raw_public_key
-  end
-
-  def provider_public_key_fingerprint
-    OpenSSL::Digest.base64digest('sha256', provider_public_key_raw)
-  end
-
-  def url(path)
-    base = base_url
-    base = base.chomp('/') if path.start_with?('/')
-    "#{base}#{path}"
-  end
-
-  def update_info!(confirm: false)
-    self.confirmed = true if confirm
-    provider_info = Fasp::Request.new(self).get('/provider_info')
-    assign_attributes(
-      privacy_policy: provider_info['privacyPolicy'],
-      capabilities: provider_info['capabilities'],
-      sign_in_url: provider_info['signInUrl'],
-      contact_email: provider_info['contactEmail'],
-      fediverse_account: provider_info['fediverseAccount']
-    )
-    save!
-  end
-
-  private
-
-  def create_keypair
-    self.server_private_key_pem ||=
-      OpenSSL::PKey.generate_key('ed25519').private_to_pem
-  end
-
-  def update_remote_capabilities
-    return unless saved_change_to_attribute?(:capabilities)
-
-    old, current = saved_change_to_attribute(:capabilities)
-    old ||= []
-    current.each do |capability|
-      update_remote_capability(capability) if capability.key?('enabled') && !old.include?(capability)
-    end
-  end
-
-  def update_remote_capability(capability)
-    version, = capability['version'].split('.')
-    path = "/capabilities/#{capability['id']}/#{version}/activation"
-    if capability['enabled']
-      Fasp::Request.new(self).post(path)
-    else
-      Fasp::Request.new(self).delete(path)
-    end
-  end
-end
diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb
index 964d4e279a..c13cc718d8 100644
--- a/app/models/follow_request.rb
+++ b/app/models/follow_request.rb
@@ -33,15 +33,8 @@ class FollowRequest < ApplicationRecord
 
   def authorize!
     follow = account.follow!(target_account, reblogs: show_reblogs, notify: notify, languages: languages, uri: uri, bypass_limit: true)
-
-    if account.local?
-      ListAccount.where(follow_request: self).update_all(follow_request_id: nil, follow_id: follow.id)
-      MergeWorker.perform_async(target_account.id, account.id, 'home')
-      MergeWorker.push_bulk(List.where(account: account).joins(:list_accounts).where(list_accounts: { account_id: target_account.id }).pluck(:id)) do |list_id|
-        [target_account.id, list_id, 'list']
-      end
-    end
-
+    ListAccount.where(follow_request: self).update_all(follow_request_id: nil, follow_id: follow.id)
+    MergeWorker.perform_async(target_account.id, account.id) if account.local?
     destroy!
   end
 
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index 31ac04b754..6ab5b0bdd5 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -65,7 +65,6 @@ class Form::AdminSettings
     stop_link_preview_domains
     app_icon
     favicon
-    min_age
   ).freeze
 
   INTEGER_KEYS = %i(
@@ -81,7 +80,6 @@ class Form::AdminSettings
     registrations_end_hour
     registrations_secondary_start_hour
     registrations_secondary_end_hour
-    min_age
   ).freeze
 
   BOOLEAN_KEYS = %i(
@@ -118,10 +116,6 @@ class Form::AdminSettings
     favicon
   ).freeze
 
-  DIGEST_KEYS = %i(
-    custom_css
-  ).freeze
-
   OVERRIDEN_SETTINGS = {
     authorized_fetch: :authorized_fetch_mode?,
   }.freeze
@@ -137,12 +131,12 @@ class Form::AdminSettings
   attr_accessor(*KEYS)
 
   validates :registrations_mode, inclusion: { in: %w(open approved none) }, if: -> { defined?(@registrations_mode) }
-  validates :site_contact_username, presence: true, existing_username: true, if: -> { defined?(@site_contact_username) }
+  validates :site_contact_email, :site_contact_username, presence: true, if: -> { defined?(@site_contact_username) || defined?(@site_contact_email) }
+  validates :site_contact_username, existing_username: true, if: -> { defined?(@site_contact_username) }
   validates :bootstrap_timeline_accounts, existing_username: { multiple: true }, if: -> { defined?(@bootstrap_timeline_accounts) }
   validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks) }
   validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks_rationale) }
   validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) }
-  validates :min_age, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@min_age) }
   validates :site_short_description, length: { maximum: DESCRIPTION_LIMIT }, if: -> { defined?(@site_short_description) }
   validates :status_page_url, url: true, allow_blank: true
   validate :validate_site_uploads
@@ -183,8 +177,6 @@ class Form::AdminSettings
     KEYS.each do |key|
       next unless instance_variable_defined?(:"@#{key}")
 
-      cache_digest_value(key) if DIGEST_KEYS.include?(key)
-
       if UPLOAD_KEYS.include?(key)
         public_send(key).save
       else
@@ -196,18 +188,6 @@ class Form::AdminSettings
 
   private
 
-  def cache_digest_value(key)
-    Rails.cache.delete(:"setting_digest_#{key}")
-
-    key_value = instance_variable_get(:"@#{key}")
-    if key_value.present?
-      Rails.cache.write(
-        :"setting_digest_#{key}",
-        Digest::SHA256.hexdigest(key_value)
-      )
-    end
-  end
-
   def typecast_value(key, value)
     if BOOLEAN_KEYS.include?(key)
       value == '1'
diff --git a/app/models/form/redirect.rb b/app/models/form/redirect.rb
index c5b3c1f8f3..3cd5731e6d 100644
--- a/app/models/form/redirect.rb
+++ b/app/models/form/redirect.rb
@@ -32,7 +32,7 @@ class Form::Redirect
 
   def set_target_account
     @target_account = ResolveAccountService.new.call(acct, skip_cache: true)
-  rescue Webfinger::Error, *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::Error, Addressable::URI::InvalidURIError
+  rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error, Addressable::URI::InvalidURIError
     # Validation will take care of it
   end
 
diff --git a/app/models/import.rb b/app/models/import.rb
new file mode 100644
index 0000000000..6b261f8d00
--- /dev/null
+++ b/app/models/import.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: imports
+#
+#  id                :bigint(8)        not null, primary key
+#  type              :integer          not null
+#  approved          :boolean          default(FALSE), not null
+#  created_at        :datetime         not null
+#  updated_at        :datetime         not null
+#  data_file_name    :string
+#  data_content_type :string
+#  data_updated_at   :datetime
+#  account_id        :bigint(8)        not null
+#  overwrite         :boolean          default(FALSE), not null
+#  data_file_size    :integer
+#
+
+# NOTE: This is a deprecated model, only kept to not break ongoing imports
+# on upgrade. See `BulkImport` and `Form::Import` for its replacements.
+
+class Import < ApplicationRecord
+  FILE_TYPES = %w(text/plain text/csv application/csv).freeze
+  MODES = %i(merge overwrite).freeze
+
+  self.inheritance_column = false
+
+  belongs_to :account
+
+  enum :type, { following: 0, blocking: 1, muting: 2, domain_blocking: 3, bookmarks: 4 }
+
+  validates :type, presence: true
+
+  has_attached_file :data
+  validates_attachment_content_type :data, content_type: FILE_TYPES
+  validates_attachment_presence :data
+
+  def mode
+    overwrite? ? :overwrite : :merge
+  end
+
+  def mode=(str)
+    self.overwrite = str.to_sym == :overwrite
+  end
+end
diff --git a/app/models/instance_filter.rb b/app/models/instance_filter.rb
index 7e71640e52..1d94c919f9 100644
--- a/app/models/instance_filter.rb
+++ b/app/models/instance_filter.rb
@@ -28,9 +28,9 @@ class InstanceFilter
   def scope_for(key, value)
     case key.to_s
     when 'limited'
-      Instance.joins(:domain_block).reorder(domain_blocks: { id: :desc })
+      Instance.joins(:domain_block).reorder(Arel.sql('domain_blocks.id desc'))
     when 'allowed'
-      Instance.joins(:domain_allow).reorder(domain_allows: { id: :desc })
+      Instance.joins(:domain_allow).reorder(Arel.sql('domain_allows.id desc'))
     when 'by_domain'
       Instance.matches_domain(value)
     when 'availability'
diff --git a/app/models/invite.rb b/app/models/invite.rb
index 9437ebee60..ea095a3ac1 100644
--- a/app/models/invite.rb
+++ b/app/models/invite.rb
@@ -20,9 +20,6 @@ class Invite < ApplicationRecord
   include Expireable
 
   COMMENT_SIZE_LIMIT = 420
-  ELIGIBLE_CODE_CHARACTERS = [*('a'..'z'), *('A'..'Z'), *('0'..'9')].freeze
-  HOMOGLYPHS = %w(0 1 I l O).freeze
-  VALID_CODE_CHARACTERS = ELIGIBLE_CODE_CHARACTERS - HOMOGLYPHS
 
   belongs_to :user, inverse_of: :invites
   has_many :users, inverse_of: :invite, dependent: nil
@@ -31,7 +28,7 @@ class Invite < ApplicationRecord
 
   validates :comment, length: { maximum: COMMENT_SIZE_LIMIT }
 
-  before_validation :set_code, on: :create
+  before_validation :set_code
 
   def valid_for_use?
     (max_uses.nil? || uses < max_uses) && !expired? && user&.functional?
@@ -41,7 +38,7 @@ class Invite < ApplicationRecord
 
   def set_code
     loop do
-      self.code = VALID_CODE_CHARACTERS.sample(8).join
+      self.code = ([*('a'..'z'), *('A'..'Z'), *('0'..'9')] - %w(0 1 I l O)).sample(8).join
       break if Invite.find_by(code: code).nil?
     end
   end
diff --git a/app/models/ip_block.rb b/app/models/ip_block.rb
index 4c95ac38de..d6242efbf7 100644
--- a/app/models/ip_block.rb
+++ b/app/models/ip_block.rb
@@ -17,14 +17,13 @@ class IpBlock < ApplicationRecord
   CACHE_KEY = 'blocked_ips'
 
   include Expireable
-  include InetContainer
   include Paginable
 
   enum :severity, {
     sign_up_requires_approval: 5000,
     sign_up_block: 5500,
     no_access: 9999,
-  }, prefix: true, validate: true
+  }, prefix: true
 
   validates :ip, :severity, presence: true
   validates :ip, uniqueness: true
@@ -37,14 +36,9 @@ class IpBlock < ApplicationRecord
 
   class << self
     def blocked?(remote_ip)
+      blocked_ips_map = Rails.cache.fetch(CACHE_KEY) { FastIpMap.new(IpBlock.where(severity: :no_access).pluck(:ip)) }
       blocked_ips_map.include?(remote_ip)
     end
-
-    private
-
-    def blocked_ips_map
-      Rails.cache.fetch(CACHE_KEY) { FastIpMap.new(severity_no_access.pluck(:ip)) }
-    end
   end
 
   private
diff --git a/app/models/list.rb b/app/models/list.rb
index a441d065cf..b87e37452e 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -5,14 +5,13 @@
 # Table name: lists
 #
 #  id             :bigint(8)        not null, primary key
-#  exclusive      :boolean          default(FALSE), not null
-#  favourite      :boolean          default(TRUE), not null
-#  notify         :boolean          default(FALSE), not null
-#  replies_policy :integer          default("list"), not null
+#  account_id     :bigint(8)        not null
 #  title          :string           default(""), not null
 #  created_at     :datetime         not null
 #  updated_at     :datetime         not null
-#  account_id     :bigint(8)        not null
+#  replies_policy :integer          default("list"), not null
+#  exclusive      :boolean          default(FALSE), not null
+#  notify         :boolean          default(FALSE), not null
 #
 
 class List < ApplicationRecord
@@ -20,16 +19,16 @@ class List < ApplicationRecord
 
   PER_ACCOUNT_LIMIT = 50
 
-  enum :replies_policy, { list: 0, followed: 1, none: 2 }, prefix: :show, validate: true
+  enum :replies_policy, { list: 0, followed: 1, none: 2 }, prefix: :show
 
   belongs_to :account
 
   has_many :list_accounts, inverse_of: :list, dependent: :destroy
   has_many :accounts, through: :list_accounts
-  has_many :active_accounts, -> { merge(ListAccount.active) }, through: :list_accounts, source: :account
   has_many :antennas, inverse_of: :list, dependent: :destroy
   has_many :list_statuses, inverse_of: :list, dependent: :destroy
   has_many :statuses, through: :list_statuses
+  has_many :active_accounts, -> { merge(ListAccount.active) }, through: :list_accounts, source: :account
 
   validates :title, presence: true
 
@@ -37,14 +36,6 @@ class List < ApplicationRecord
 
   before_destroy :clean_feed_manager
 
-  def favourite!
-    update!(favourite: true)
-  end
-
-  def unfavourite!
-    update!(favourite: false)
-  end
-
   private
 
   def validate_account_lists_limit
diff --git a/app/models/list_account.rb b/app/models/list_account.rb
index 00ecd44c3c..0169319761 100644
--- a/app/models/list_account.rb
+++ b/app/models/list_account.rb
@@ -27,16 +27,17 @@ class ListAccount < ApplicationRecord
   private
 
   def set_follow
-    self.follow = Follow.find_by(account_id: list.account_id, target_account_id: account.id)
-    self.follow_request = FollowRequest.find_by(account_id: list.account_id, target_account_id: account.id) if follow.nil?
+    self.follow = Follow.find_by!(account_id: list.account_id, target_account_id: account.id)
+  rescue ActiveRecord::RecordNotFound
+    self.follow_request = FollowRequest.find_by!(account_id: list.account_id, target_account_id: account.id)
   end
 
   def validate_relationship
-    return if list_owner_account_is_account?
+    return if list.account_id == account_id
 
-    errors.add(:account_id, :must_be_following) if follow_id.nil? && follow_request_id.nil?
-    errors.add(:follow, :invalid) if follow_id.present? && follow.target_account_id != account_id
-    errors.add(:follow_request, :invalid) if follow_request_id.present? && follow_request.target_account_id != account_id
+    errors.add(:account_id, 'follow relationship missing') if follow_id.nil? && follow_request_id.nil?
+    errors.add(:follow, 'mismatched accounts') if follow_id.present? && follow.target_account_id != account_id
+    errors.add(:follow_request, 'mismatched accounts') if follow_request_id.present? && follow_request.target_account_id != account_id
   end
 
   def list_owner_account_is_account?
diff --git a/app/models/marker.rb b/app/models/marker.rb
index bc2d57f56a..a5bd2176a8 100644
--- a/app/models/marker.rb
+++ b/app/models/marker.rb
@@ -5,12 +5,12 @@
 # Table name: markers
 #
 #  id           :bigint(8)        not null, primary key
-#  lock_version :integer          default(0), not null
+#  user_id      :bigint(8)
 #  timeline     :string           default(""), not null
+#  last_read_id :bigint(8)        default(0), not null
+#  lock_version :integer          default(0), not null
 #  created_at   :datetime         not null
 #  updated_at   :datetime         not null
-#  last_read_id :bigint(8)        default(0), not null
-#  user_id      :bigint(8)        not null
 #
 
 class Marker < ApplicationRecord
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 89c74d5a41..49ff740884 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -116,7 +116,7 @@ class MediaAttachment < ApplicationRecord
   VIDEO_PASSTHROUGH_OPTIONS = {
     video_codecs: ['h264'].freeze,
     audio_codecs: ['aac', nil].freeze,
-    colorspaces: ['yuv420p', 'yuvj420p'].freeze,
+    colorspaces: ['yuv420p'].freeze,
     options: {
       format: 'mp4',
       convert_options: {
@@ -425,10 +425,8 @@ class MediaAttachment < ApplicationRecord
 
     @paths_to_cache_bust = MediaAttachment.attachment_definitions.keys.flat_map do |attachment_name|
       attachment = public_send(attachment_name)
-      next if attachment.blank?
-
       styles = DEFAULT_STYLES | attachment.styles.keys
-      styles.map { |style| attachment.url(style) }
+      styles.map { |style| attachment.path(style) }
     end.compact
   rescue => e
     # We really don't want any error here preventing media deletion
diff --git a/app/models/mention.rb b/app/models/mention.rb
index 8b191e8c95..c802cb8faf 100644
--- a/app/models/mention.rb
+++ b/app/models/mention.rb
@@ -20,7 +20,7 @@ class Mention < ApplicationRecord
 
   has_one :notification, as: :activity, dependent: :destroy
 
-  validates :account_id, uniqueness: { scope: :status_id }
+  validates :account, uniqueness: { scope: :status }
 
   scope :active, -> { where(silent: false) }
   scope :silent, -> { where(silent: true) }
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 1714e5fb3b..cdce34c77b 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -19,7 +19,6 @@
 class Notification < ApplicationRecord
   self.inheritance_column = nil
 
-  include Notification::Groups
   include Paginable
   include Redisable
 
@@ -36,6 +35,10 @@ class Notification < ApplicationRecord
     'AccountWarning' => :moderation_warning,
   }.freeze
 
+  # `set_group_key!` needs to be updated if this list changes
+  GROUPABLE_NOTIFICATION_TYPES = %i(favourite reblog follow emoji_reaction).freeze
+  MAXIMUM_GROUP_SPAN_HOURS = 12
+
   # Please update app/javascript/api_types/notification.ts if you change this
   PROPERTIES = {
     mention: {
@@ -80,9 +83,6 @@ class Notification < ApplicationRecord
     moderation_warning: {
       filterable: false,
     }.freeze,
-    annual_report: {
-      filterable: false,
-    }.freeze,
     'admin.sign_up': {
       filterable: false,
     }.freeze,
@@ -124,12 +124,12 @@ class Notification < ApplicationRecord
     belongs_to :report, inverse_of: false
     belongs_to :account_warning, inverse_of: false
     belongs_to :account_relationship_severance_event, inverse_of: false
-    belongs_to :generated_annual_report, inverse_of: false
   end
 
   validates :type, inclusion: { in: TYPES }
 
   scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) }
+  scope :by_group_key, ->(group_key) { group_key&.start_with?('ungrouped-') ? where(id: group_key.delete_prefix('ungrouped-')) : where(group_key: group_key) }
 
   def type
     @type ||= (super || LEGACY_TYPE_CLASS_MAP[activity_type]).to_sym
@@ -156,6 +156,30 @@ class Notification < ApplicationRecord
     end
   end
 
+  def set_group_key!
+    return if filtered? || Notification::GROUPABLE_NOTIFICATION_TYPES.exclude?(type)
+
+    type_prefix = case type
+                  when :favourite, :reblog, :emoji_reaction
+                    [type, target_status&.id].join('-')
+                  when :follow
+                    type
+                  else
+                    raise NotImplementedError
+                  end
+    redis_key   = "notif-group/#{account.id}/#{type_prefix}"
+    hour_bucket = activity.created_at.utc.to_i / 1.hour.to_i
+
+    # Reuse previous group if it does not span too large an amount of time
+    previous_bucket = redis.get(redis_key).to_i
+    hour_bucket = previous_bucket if hour_bucket < previous_bucket + MAXIMUM_GROUP_SPAN_HOURS
+
+    # We do not concern ourselves with race conditions since we use hour buckets
+    redis.set(redis_key, hour_bucket, ex: MAXIMUM_GROUP_SPAN_HOURS.hours.to_i)
+
+    self.group_key = "#{type_prefix}-#{hour_bucket}"
+  end
+
   class << self
     def browserable(types: [], exclude_types: [], from_account_id: nil, include_filtered: false)
       requested_types = if types.empty?
@@ -173,6 +197,94 @@ class Notification < ApplicationRecord
       end
     end
 
+    def paginate_groups(limit, pagination_order, grouped_types: nil)
+      raise ArgumentError unless %i(asc desc).include?(pagination_order)
+
+      query = reorder(id: pagination_order)
+
+      # Ideally `:types` would be a bind rather than part of the SQL itself, but that does not
+      # seem to be possible to do with Rails, considering that the expression would occur in
+      # multiple places, including in a `select`
+      group_key_sql = begin
+        if grouped_types.present?
+          # Normalize `grouped_types` so the number of different SQL query shapes remains small, and
+          # the queries can be analyzed in monitoring/telemetry tools
+          grouped_types = (grouped_types.map(&:to_sym) & GROUPABLE_NOTIFICATION_TYPES).sort
+
+          sanitize_sql_array([<<~SQL.squish, { types: grouped_types }])
+            COALESCE(
+              CASE
+                WHEN notifications.type IN (:types) THEN notifications.group_key
+                ELSE NULL
+              END,
+              'ungrouped-' || notifications.id
+            )
+          SQL
+        else
+          "COALESCE(notifications.group_key, 'ungrouped-' || notifications.id)"
+        end
+      end
+
+      unscoped
+        .with_recursive(
+          grouped_notifications: [
+            # Base case: fetching one notification and annotating it with visited groups
+            query
+              .select('notifications.*', "ARRAY[#{group_key_sql}] AS groups")
+              .limit(1),
+            # Recursive case, always yielding at most one annotated notification
+            unscoped
+              .from(
+                [
+                  # Expose the working table as `wt`, but quit early if we've reached the limit
+                  unscoped
+                    .select('id', 'groups')
+                    .from('grouped_notifications')
+                    .where('array_length(grouped_notifications.groups, 1) < :limit', limit: limit)
+                    .arel.as('wt'),
+                  # Recursive query, using `LATERAL` so we can refer to `wt`
+                  query
+                    .where(pagination_order == :desc ? 'notifications.id < wt.id' : 'notifications.id > wt.id')
+                    .where.not("#{group_key_sql} = ANY(wt.groups)")
+                    .limit(1)
+                    .arel.lateral('notifications'),
+                ]
+              )
+              .select('notifications.*', "array_append(wt.groups, #{group_key_sql}) AS groups"),
+          ]
+        )
+        .from('grouped_notifications AS notifications')
+        .order(id: pagination_order)
+        .limit(limit)
+    end
+
+    # This returns notifications from the request page, but with at most one notification per group.
+    # Notifications that have no `group_key` each count as a separate group.
+    def paginate_groups_by_max_id(limit, max_id: nil, since_id: nil, grouped_types: nil)
+      query = reorder(id: :desc)
+      query = query.where(id: ...(max_id.to_i)) if max_id.present?
+      query = query.where(id: (since_id.to_i + 1)...) if since_id.present?
+      query.paginate_groups(limit, :desc, grouped_types: grouped_types)
+    end
+
+    # Differs from :paginate_groups_by_max_id in that it gives the results immediately following min_id,
+    # whereas since_id gives the items with largest id, but with since_id as a cutoff.
+    # Results will be in ascending order by id.
+    def paginate_groups_by_min_id(limit, max_id: nil, min_id: nil, grouped_types: nil)
+      query = reorder(id: :asc)
+      query = query.where(id: (min_id.to_i + 1)...) if min_id.present?
+      query = query.where(id: ...(max_id.to_i)) if max_id.present?
+      query.paginate_groups(limit, :asc, grouped_types: grouped_types)
+    end
+
+    def to_a_grouped_paginated_by_id(limit, options = {})
+      if options[:min_id].present?
+        paginate_groups_by_min_id(limit, min_id: options[:min_id], max_id: options[:max_id], grouped_types: options[:grouped_types]).reverse
+      else
+        paginate_groups_by_max_id(limit, max_id: options[:max_id], since_id: options[:since_id], grouped_types: options[:grouped_types]).to_a
+      end
+    end
+
     def preload_cache_collection_target_statuses(notifications, &_block)
       notifications.group_by(&:type).each do |type, grouped_notifications|
         associations = TARGET_STATUS_INCLUDES_BY_TYPE[type]
@@ -233,7 +345,7 @@ class Notification < ApplicationRecord
       self.from_account_id = activity&.status&.account_id
     when 'Account'
       self.from_account_id = activity&.id
-    when 'AccountRelationshipSeveranceEvent', 'AccountWarning', 'GeneratedAnnualReport'
+    when 'AccountRelationshipSeveranceEvent', 'AccountWarning'
       # These do not really have an originating account, but this is mandatory
       # in the data model, and the recipient's account will by definition
       # always exist
diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb
index 74b10a6c48..74997f66b7 100644
--- a/app/models/notification_group.rb
+++ b/app/models/notification_group.rb
@@ -20,6 +20,7 @@ class NotificationGroup < ActiveModelSerializers::Model
     group_keys = grouped_notifications.pluck(:group_key)
 
     with_emoji_reaction = grouped_notifications.any? { |notification| notification.type == :emoji_reaction }
+    notifications.any? { |notification| notification.type == :list_status }
 
     groups_data = load_groups_data(notifications.first.account_id, group_keys, pagination_range: pagination_range)
     accounts_map = Account.where(id: groups_data.values.pluck(1).flatten).index_by(&:id)
@@ -66,14 +67,12 @@ class NotificationGroup < ActiveModelSerializers::Model
            :report,
            :account_relationship_severance_event,
            :account_warning,
-           :generated_annual_report,
            to: :notification, prefix: false
 
   def self.convert_emoji_reaction_pair(activity_ids)
     return [] if activity_ids.empty?
 
     EmojiReaction.where(id: activity_ids)
-                 .order(id: :desc)
                  .each_with_object({}) { |e, h| h[e.name] = (h[e.name] || []).push(e) }
                  .to_a
                  .map { |pair| NotificationEmojiReactionGroup.new(emoji_reaction: pair[1].first, sample_accounts: pair[1].take(SAMPLE_ACCOUNTS_SIZE).map(&:account)) }
diff --git a/app/models/poll.rb b/app/models/poll.rb
index 25ce9c3be9..baa0dbe539 100644
--- a/app/models/poll.rb
+++ b/app/models/poll.rb
@@ -5,34 +5,32 @@
 # Table name: polls
 #
 #  id              :bigint(8)        not null, primary key
-#  cached_tallies  :bigint(8)        default([]), not null, is an Array
+#  account_id      :bigint(8)
+#  status_id       :bigint(8)
 #  expires_at      :datetime
-#  hide_totals     :boolean          default(FALSE), not null
-#  last_fetched_at :datetime
-#  lock_version    :integer          default(0), not null
-#  multiple        :boolean          default(FALSE), not null
 #  options         :string           default([]), not null, is an Array
-#  voters_count    :bigint(8)
+#  cached_tallies  :bigint(8)        default([]), not null, is an Array
+#  multiple        :boolean          default(FALSE), not null
+#  hide_totals     :boolean          default(FALSE), not null
 #  votes_count     :bigint(8)        default(0), not null
+#  last_fetched_at :datetime
 #  created_at      :datetime         not null
 #  updated_at      :datetime         not null
-#  account_id      :bigint(8)        not null
-#  status_id       :bigint(8)        not null
+#  lock_version    :integer          default(0), not null
+#  voters_count    :bigint(8)
 #
 
 class Poll < ApplicationRecord
   include Expireable
 
-  MAKE_FETCH_HAPPEN = 1.minute
-
   belongs_to :account
   belongs_to :status
 
   has_many :votes, class_name: 'PollVote', inverse_of: :poll, dependent: :delete_all
 
   with_options class_name: 'Account', source: :account, through: :votes do
-    has_many :voters, -> { group(accounts: [:id]) }
-    has_many :local_voters, -> { group(accounts: [:id]).merge(Account.local) }
+    has_many :voters, -> { group('accounts.id') }
+    has_many :local_voters, -> { group('accounts.id').merge(Account.local) }
   end
 
   has_many :notifications, as: :activity, dependent: :destroy
@@ -42,6 +40,9 @@ class Poll < ApplicationRecord
   validates_with PollOptionsValidator, if: :local?
   validates_with PollExpirationValidator, if: -> { local? && expires_at_changed? }
 
+  scope :attached, -> { where.not(status_id: nil) }
+  scope :unattached, -> { where(status_id: nil) }
+
   before_validation :prepare_options, if: :local?
   before_validation :prepare_votes_count
   before_validation :prepare_cached_tallies
@@ -64,7 +65,11 @@ class Poll < ApplicationRecord
     votes.where(account: account).pluck(:choice)
   end
 
-  delegate :local?, :remote?, to: :account
+  delegate :local?, to: :account
+
+  def remote?
+    !local?
+  end
 
   def emojis
     @emojis ||= CustomEmoji.from_text(options.join(' '), account.domain)
@@ -115,7 +120,7 @@ class Poll < ApplicationRecord
   end
 
   def time_passed_since_last_fetch?
-    last_fetched_at.nil? || last_fetched_at < MAKE_FETCH_HAPPEN.ago
+    last_fetched_at.nil? || last_fetched_at < 1.minute.ago
   end
 
   def show_totals_now?
diff --git a/app/models/poll_vote.rb b/app/models/poll_vote.rb
index 90876ac960..92d74a6dbc 100644
--- a/app/models/poll_vote.rb
+++ b/app/models/poll_vote.rb
@@ -5,12 +5,12 @@
 # Table name: poll_votes
 #
 #  id         :bigint(8)        not null, primary key
+#  account_id :bigint(8)
+#  poll_id    :bigint(8)
 #  choice     :integer          default(0), not null
-#  uri        :string
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  account_id :bigint(8)        not null
-#  poll_id    :bigint(8)        not null
+#  uri        :string
 #
 
 class PollVote < ApplicationRecord
diff --git a/app/models/relay.rb b/app/models/relay.rb
index 813a861c68..f652b4864b 100644
--- a/app/models/relay.rb
+++ b/app/models/relay.rb
@@ -13,7 +13,7 @@
 #
 
 class Relay < ApplicationRecord
-  validates :inbox_url, presence: true, uniqueness: true, url: true # rubocop:disable Rails/UniqueValidationWithoutIndex
+  validates :inbox_url, presence: true, uniqueness: true, url: true, if: :will_save_change_to_inbox_url?
 
   enum :state, { idle: 0, pending: 1, accepted: 2, rejected: 3 }
 
@@ -25,10 +25,6 @@ class Relay < ApplicationRecord
 
   alias enabled? accepted?
 
-  def to_log_human_identifier
-    inbox_url
-  end
-
   def enable!
     activity_id = ActivityPub::TagManager.instance.generate_uri_for(nil)
     payload     = Oj.dump(follow_activity(activity_id))
diff --git a/app/models/remote_follow.rb b/app/models/remote_follow.rb
index f9719b8670..fa0586f57e 100644
--- a/app/models/remote_follow.rb
+++ b/app/models/remote_follow.rb
@@ -66,7 +66,7 @@ class RemoteFollow
 
   def acct_resource
     @acct_resource ||= Webfinger.new("acct:#{acct}").perform
-  rescue Webfinger::Error, *Mastodon::HTTP_CONNECTION_ERRORS
+  rescue Webfinger::Error, HTTP::ConnectionError
     nil
   end
 
diff --git a/app/models/scheduled_status.rb b/app/models/scheduled_status.rb
index 1f4f04268d..27f0cbd280 100644
--- a/app/models/scheduled_status.rb
+++ b/app/models/scheduled_status.rb
@@ -5,9 +5,9 @@
 # Table name: scheduled_statuses
 #
 #  id           :bigint(8)        not null, primary key
-#  params       :jsonb
+#  account_id   :bigint(8)
 #  scheduled_at :datetime
-#  account_id   :bigint(8)        not null
+#  params       :jsonb
 #
 
 class ScheduledStatus < ApplicationRecord
@@ -15,7 +15,6 @@ class ScheduledStatus < ApplicationRecord
 
   TOTAL_LIMIT = 300
   DAILY_LIMIT = 25
-  MINIMUM_OFFSET = 5.minutes.freeze
 
   belongs_to :account, inverse_of: :scheduled_statuses
   has_many :media_attachments, inverse_of: :scheduled_status, dependent: :nullify
@@ -27,7 +26,7 @@ class ScheduledStatus < ApplicationRecord
   private
 
   def validate_future_date
-    errors.add(:scheduled_at, I18n.t('scheduled_statuses.too_soon')) if scheduled_at.present? && scheduled_at <= Time.now.utc + MINIMUM_OFFSET
+    errors.add(:scheduled_at, I18n.t('scheduled_statuses.too_soon')) if scheduled_at.present? && scheduled_at <= Time.now.utc + PostStatusService::MIN_SCHEDULE_OFFSET
   end
 
   def validate_total_limit
diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb
index d99ecf8adb..8b8e533d30 100644
--- a/app/models/session_activation.rb
+++ b/app/models/session_activation.rb
@@ -30,15 +30,13 @@ class SessionActivation < ApplicationRecord
 
   DEFAULT_SCOPES = %w(read write follow).freeze
 
-  scope :latest, -> { order(id: :desc) }
-
   class << self
     def active?(id)
       id && exists?(session_id: id)
     end
 
-    def activate(**)
-      activation = create!(**)
+    def activate(**options)
+      activation = create!(**options)
       purge_old
       activation
     end
@@ -50,7 +48,7 @@ class SessionActivation < ApplicationRecord
     end
 
     def purge_old
-      latest.offset(Rails.configuration.x.max_session_activations).destroy_all
+      order('created_at desc').offset(Rails.configuration.x.max_session_activations).destroy_all
     end
 
     def exclusive(id)
diff --git a/app/models/setting.rb b/app/models/setting.rb
index 12ff32f00a..6af7a98c6d 100644
--- a/app/models/setting.rb
+++ b/app/models/setting.rb
@@ -7,8 +7,10 @@
 #  id         :bigint(8)        not null, primary key
 #  var        :string           not null
 #  value      :text
+#  thing_type :string
 #  created_at :datetime
 #  updated_at :datetime
+#  thing_id   :bigint(8)
 #
 
 # This file is derived from a fork of the `rails-settings-cached` gem available at
@@ -44,10 +46,10 @@ class Setting < ApplicationRecord
   after_commit :rewrite_cache, on: %i(create update)
   after_commit :expire_cache, on: %i(destroy)
 
-  self.ignored_columns += %w(
-    thing_id
-    thing_type
-  )
+  # Settings are server-wide settings only, but they were previously
+  # used for users too. This can be dropped later with a database
+  # migration dropping any scoped setting.
+  default_scope { where(thing_type: nil, thing_id: nil) }
 
   class << self
     # get or set a variable with the variable as the called method
diff --git a/app/models/software_update.rb b/app/models/software_update.rb
index 9f95b0735e..80ea493eb3 100644
--- a/app/models/software_update.rb
+++ b/app/models/software_update.rb
@@ -18,33 +18,19 @@ class SoftwareUpdate < ApplicationRecord
 
   enum :type, { patch: 0, minor: 1, major: 2 }, suffix: :type
 
-  scope :urgent, -> { where(urgent: true) }
-
   def gem_version
     Gem::Version.new(version)
   end
 
-  def outdated?
-    runtime_version >= gem_version
-  end
-
-  def pending?
-    gem_version > runtime_version
-  end
-
   class << self
     def check_enabled?
-      Rails.configuration.x.mastodon.software_update_url.present?
-    end
-
-    def by_version
-      all.sort_by(&:gem_version)
+      ENV['UPDATE_CHECK_URL'] != ''
     end
 
     def pending_to_a
       return [] unless check_enabled?
 
-      all.to_a.filter(&:pending?)
+      all.to_a.filter { |update| update.gem_version > Mastodon::Version.gem_version }
     end
 
     def urgent_pending?
@@ -59,10 +45,4 @@ class SoftwareUpdate < ApplicationRecord
       pending_to_a.any?(&:patch_type?)
     end
   end
-
-  private
-
-  def runtime_version
-    Mastodon::Version.gem_version
-  end
 end
diff --git a/app/models/status.rb b/app/models/status.rb
index 0325802022..c0efaf4fa7 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -31,7 +31,6 @@
 #  markdown                     :boolean          default(FALSE)
 #  limited_scope                :integer
 #  quote_of_id                  :bigint(8)
-#  fetched_replies_at           :datetime
 #
 
 require 'ostruct'
@@ -42,12 +41,10 @@ class Status < ApplicationRecord
   include Paginable
   include RateLimitable
   include Status::DomainBlockConcern
-  include Status::FetchRepliesConcern
   include Status::SafeReblogInsert
   include Status::SearchConcern
   include Status::SnapshotConcern
   include Status::ThreadingConcern
-  include Status::Visibility
   include DtlHelper
 
   MEDIA_ATTACHMENTS_LIMIT = 4
@@ -65,6 +62,10 @@ class Status < ApplicationRecord
   update_index('statuses', :proper)
   update_index('public_statuses', :proper)
 
+  enum :visibility, { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, public_unlisted: 10, login: 11 }, suffix: :visibility, validate: true
+  enum :searchability, { public: 0, private: 1, direct: 2, limited: 3, unsupported: 4, public_unlisted: 10 }, suffix: :searchability
+  enum :limited_scope, { none: 0, mutual: 1, circle: 2, personal: 3, reply: 4 }, suffix: :limited
+
   belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
 
   belongs_to :account, inverse_of: :statuses
@@ -129,6 +130,7 @@ class Status < ApplicationRecord
   validates_with StatusLengthValidator
   validates_with DisallowedHashtagsValidator
   validates :reblog, uniqueness: { scope: :account }, if: :reblog?
+  validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog?
 
   accepts_nested_attributes_for :poll
 
@@ -157,6 +159,10 @@ class Status < ApplicationRecord
   scope :tagged_with_none, lambda { |tag_ids|
     where('NOT EXISTS (SELECT * FROM statuses_tags forbidden WHERE forbidden.status_id = statuses.id AND forbidden.tag_id IN (?))', tag_ids)
   }
+  scope :unset_searchability, -> { where(searchability: nil, reblog_of_id: nil) }
+  scope :distributable_visibility, -> { where(visibility: %i(public public_unlisted login unlisted)) }
+  scope :distributable_visibility_for_anonymous, -> { where(visibility: %i(public public_unlisted unlisted)) }
+  scope :list_eligible_visibility, -> { where(visibility: %i(public public_unlisted login unlisted private)) }
 
   after_create_commit :trigger_create_webhooks
   after_update_commit :trigger_update_webhooks
@@ -169,6 +175,8 @@ class Status < ApplicationRecord
 
   before_validation :prepare_contents, if: :local?
   before_validation :set_reblog
+  before_validation :set_visibility
+  before_validation :set_searchability
   before_validation :set_conversation
   before_validation :set_local
 
@@ -296,6 +304,16 @@ class Status < ApplicationRecord
     PreviewCardsStatus.where(status_id: id).delete_all
   end
 
+  def hidden?
+    !distributable?
+  end
+
+  def distributable?
+    public_visibility? || unlisted_visibility? || public_unlisted_visibility?
+  end
+
+  alias sign? distributable?
+
   def with_media?
     media_attachments.any?
   end
@@ -373,34 +391,12 @@ class Status < ApplicationRecord
     status_stat&.status_referred_by_count || 0
   end
 
-  # Reblogs count received from an external instance
-  def untrusted_reblogs_count
-    status_stat&.untrusted_reblogs_count unless local?
-  end
-
-  # Favourites count received from an external instance
-  def untrusted_favourites_count
-    status_stat&.untrusted_favourites_count unless local?
-  end
-
   def increment_count!(key)
-    if key == :favourites_count && !untrusted_favourites_count.nil?
-      update_status_stat!(favourites_count: favourites_count + 1, untrusted_favourites_count: untrusted_favourites_count + 1)
-    elsif key == :reblogs_count && !untrusted_reblogs_count.nil?
-      update_status_stat!(reblogs_count: reblogs_count + 1, untrusted_reblogs_count: untrusted_reblogs_count + 1)
-    else
-      update_status_stat!(key => public_send(key) + 1)
-    end
+    update_status_stat!(key => public_send(key) + 1)
   end
 
   def decrement_count!(key)
-    if key == :favourites_count && !untrusted_favourites_count.nil?
-      update_status_stat!(favourites_count: [favourites_count - 1, 0].max, untrusted_favourites_count: [untrusted_favourites_count - 1, 0].max)
-    elsif key == :reblogs_count && !untrusted_reblogs_count.nil?
-      update_status_stat!(reblogs_count: [reblogs_count - 1, 0].max, untrusted_reblogs_count: [untrusted_reblogs_count - 1, 0].max)
-    else
-      update_status_stat!(key => [public_send(key) - 1, 0].max)
-    end
+    update_status_stat!(key => [public_send(key) - 1, 0].max)
   end
 
   def emoji_reactions_grouped_by_name(account = nil, **options)
@@ -427,7 +423,7 @@ class Status < ApplicationRecord
           end
 
           emoji_reaction['count'] = emoji_reaction['account_ids'].size
-          public_emoji_reactions << emoji_reaction if emoji_reaction['count'].positive?
+          public_emoji_reactions << emoji_reaction if (emoji_reaction['count']).positive?
         end
 
         public_emoji_reactions
@@ -502,20 +498,53 @@ class Status < ApplicationRecord
   end
 
   class << self
+    def selectable_visibilities
+      selectable_all_visibilities - %w(mutual circle reply direct)
+    end
+
+    def selectable_all_visibilities
+      vs = %w(public public_unlisted login unlisted private mutual circle reply direct)
+      vs -= %w(public_unlisted) unless Setting.enable_public_unlisted_visibility
+      vs -= %w(public) unless Setting.enable_public_visibility
+      vs
+    end
+
+    def selectable_reblog_visibilities
+      %w(unset) + selectable_visibilities
+    end
+
+    def all_visibilities
+      visibilities.keys
+    end
+
+    def selectable_searchabilities
+      ss = searchabilities.keys - %w(unsupported)
+      ss -= %w(public_unlisted) unless Setting.enable_public_unlisted_visibility
+      ss
+    end
+
+    def selectable_searchabilities_for_search
+      searchabilities.keys - %w(public_unlisted unsupported)
+    end
+
+    def all_searchabilities
+      searchabilities.keys - %w(unlisted login unsupported)
+    end
+
     def favourites_map(status_ids, account_id)
-      Favourite.select(:status_id).where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |f, h| h[f.status_id] = true }
+      Favourite.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |f, h| h[f.status_id] = true }
     end
 
     def bookmarks_map(status_ids, account_id)
-      Bookmark.select(:status_id).where(status_id: status_ids).where(account_id: account_id).to_h { |f| [f.status_id, true] }
+      Bookmark.select('status_id').where(status_id: status_ids).where(account_id: account_id).map { |f| [f.status_id, true] }.to_h
     end
 
     def reblogs_map(status_ids, account_id)
-      unscoped.select(:reblog_of_id).where(reblog_of_id: status_ids).where(account_id: account_id).each_with_object({}) { |s, h| h[s.reblog_of_id] = true }
+      unscoped.select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).each_with_object({}) { |s, h| h[s.reblog_of_id] = true }
     end
 
     def mutes_map(conversation_ids, account_id)
-      ConversationMute.select(:conversation_id).where(conversation_id: conversation_ids).where(account_id: account_id).each_with_object({}) { |m, h| h[m.conversation_id] = true }
+      ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).each_with_object({}) { |m, h| h[m.conversation_id] = true }
     end
 
     def blocks_map(account_ids, account_id)
@@ -527,7 +556,7 @@ class Status < ApplicationRecord
     end
 
     def pins_map(status_ids, account_id)
-      StatusPin.select(:status_id).where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true }
+      StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true }
     end
 
     def emoji_reaction_allows_map(status_ids, account_id)
@@ -608,6 +637,25 @@ class Status < ApplicationRecord
     update_column(:poll_id, poll.id) if association(:poll).loaded? && poll.present?
   end
 
+  def set_visibility
+    self.visibility = reblog.visibility if reblog? && visibility.nil?
+    self.visibility = (account.locked? ? :private : :public) if visibility.nil?
+  end
+
+  def set_searchability
+    return if searchability.nil?
+
+    self.searchability = if %w(public public_unlisted login unlisted).include?(visibility)
+                           searchability
+                         elsif visibility == 'limited' || visibility == 'direct'
+                           searchability == 'limited' ? :limited : :direct
+                         elsif visibility == 'private'
+                           searchability == 'public' || searchability == 'public_unlisted' ? :private : searchability
+                         else
+                           :direct
+                         end
+  end
+
   def set_conversation
     self.thread = thread.reblog if thread&.reblog?
 
diff --git a/app/models/status_pin.rb b/app/models/status_pin.rb
index 83711dde42..dae4a5b4e6 100644
--- a/app/models/status_pin.rb
+++ b/app/models/status_pin.rb
@@ -17,17 +17,11 @@ class StatusPin < ApplicationRecord
 
   validates_with StatusPinValidator
 
-  after_destroy :invalidate_cleanup_info, if: %i(account_matches_status_account? account_local?)
-
-  delegate :local?, to: :account, prefix: true
-
-  private
+  after_destroy :invalidate_cleanup_info
 
   def invalidate_cleanup_info
+    return unless status&.account_id == account_id && account.local?
+
     account.statuses_cleanup_policy&.invalidate_last_inspected(status, :unpin)
   end
-
-  def account_matches_status_account?
-    status&.account_id == account_id
-  end
 end
diff --git a/app/models/status_stat.rb b/app/models/status_stat.rb
index a8bb5dbc04..3e5f564fee 100644
--- a/app/models/status_stat.rb
+++ b/app/models/status_stat.rb
@@ -15,17 +15,11 @@
 #  emoji_reactions_count         :integer          default(0), not null
 #  emoji_reaction_accounts_count :integer          default(0), not null
 #  status_referred_by_count      :integer          default(0), not null
-#  untrusted_favourites_count    :bigint(8)
-#  untrusted_reblogs_count       :bigint(8)
 #
 
 class StatusStat < ApplicationRecord
   belongs_to :status, inverse_of: :status_stat
 
-  before_validation :clamp_untrusted_counts
-
-  MAX_UNTRUSTED_COUNT = 100_000_000
-
   def replies_count
     [attributes['replies_count'], 0].max
   end
@@ -53,11 +47,4 @@ class StatusStat < ApplicationRecord
   def status_referred_by_count
     [attributes['status_referred_by_count'] || 0, 0].max
   end
-
-  private
-
-  def clamp_untrusted_counts
-    self.untrusted_favourites_count = untrusted_favourites_count.to_i.clamp(0, MAX_UNTRUSTED_COUNT) if untrusted_favourites_count.present?
-    self.untrusted_reblogs_count = untrusted_reblogs_count.to_i.clamp(0, MAX_UNTRUSTED_COUNT) if untrusted_reblogs_count.present?
-  end
 end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 2fee6bc1ee..a312bc3dbb 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -33,14 +33,13 @@ class Tag < ApplicationRecord
   has_many :followers, through: :passive_relationships, source: :account
 
   has_one :antenna_tag, dependent: :destroy, inverse_of: :tag
-  has_one :trend, class_name: 'TagTrend', inverse_of: :tag, dependent: :destroy
 
   HASHTAG_SEPARATORS = "_\u00B7\u30FB\u200c"
-  HASHTAG_FIRST_SEQUENCE_CHUNK_ONE = "[[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}]".freeze
-  HASHTAG_FIRST_SEQUENCE_CHUNK_TWO = "[[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_]".freeze
-  HASHTAG_FIRST_SEQUENCE = "(#{HASHTAG_FIRST_SEQUENCE_CHUNK_ONE}#{HASHTAG_FIRST_SEQUENCE_CHUNK_TWO})".freeze
+  HASHTAG_FIRST_SEQUENCE_CHUNK_ONE = "[[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}]"
+  HASHTAG_FIRST_SEQUENCE_CHUNK_TWO = "[[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_]"
+  HASHTAG_FIRST_SEQUENCE = "(#{HASHTAG_FIRST_SEQUENCE_CHUNK_ONE}#{HASHTAG_FIRST_SEQUENCE_CHUNK_TWO})"
   HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)'
-  HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}".freeze
+  HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}"
 
   HASHTAG_RE = %r{(?<![=/)\p{Alnum}])#(#{HASHTAG_NAME_PAT})}
   HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i
@@ -64,7 +63,7 @@ class Tag < ApplicationRecord
   scope :recently_used, lambda { |account|
                           joins(:statuses)
                             .where(statuses: { id: account.statuses.select(:id).limit(RECENT_STATUS_LIMIT) })
-                            .group(:id).order(Arel.star.count.desc)
+                            .group(:id).order(Arel.sql('count(*) desc'))
                         }
   scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(Tag.normalize(term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index
 
@@ -130,7 +129,7 @@ class Tag < ApplicationRecord
       query = query.merge(Tag.listable) if options[:exclude_unlistable]
       query = query.merge(matching_name(stripped_term).or(reviewed)) if options[:exclude_unreviewed]
 
-      query.order(Arel.sql('LENGTH(name)').asc, name: :asc)
+      query.order(Arel.sql('length(name) ASC, name ASC'))
            .limit(limit)
            .offset(offset)
     end
@@ -161,11 +160,11 @@ class Tag < ApplicationRecord
   private
 
   def validate_name_change
-    errors.add(:name, I18n.t('tags.does_not_match_previous_name')) unless name_was.casecmp(name).zero?
+    errors.add(:name, I18n.t('tags.does_not_match_previous_name')) unless name_was.mb_chars.casecmp(name.mb_chars).zero?
   end
 
   def validate_display_name_change
-    unless HashtagNormalizer.new.normalize(display_name).casecmp(name).zero?
+    unless HashtagNormalizer.new.normalize(display_name).casecmp(name.mb_chars).zero?
       errors.add(:display_name,
                  I18n.t('tags.does_not_match_previous_name'))
     end
diff --git a/app/models/tag_trend.rb b/app/models/tag_trend.rb
deleted file mode 100644
index 47e8489603..0000000000
--- a/app/models/tag_trend.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-# == Schema Information
-#
-# Table name: tag_trends
-#
-#  id       :bigint(8)        not null, primary key
-#  allowed  :boolean          default(FALSE), not null
-#  language :string           default(""), not null
-#  rank     :integer          default(0), not null
-#  score    :float            default(0.0), not null
-#  tag_id   :bigint(8)        not null
-#
-class TagTrend < ApplicationRecord
-  include RankedTrend
-
-  belongs_to :tag
-
-  scope :allowed, -> { where(allowed: true) }
-  scope :not_allowed, -> { where(allowed: false) }
-end
diff --git a/app/models/terms_of_service.rb b/app/models/terms_of_service.rb
deleted file mode 100644
index 3b69a40a1a..0000000000
--- a/app/models/terms_of_service.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-# == Schema Information
-#
-# Table name: terms_of_services
-#
-#  id                   :bigint(8)        not null, primary key
-#  changelog            :text             default(""), not null
-#  effective_date       :date
-#  notification_sent_at :datetime
-#  published_at         :datetime
-#  text                 :text             default(""), not null
-#  created_at           :datetime         not null
-#  updated_at           :datetime         not null
-#
-class TermsOfService < ApplicationRecord
-  scope :published, -> { where.not(published_at: nil).order(Arel.sql('coalesce(effective_date, published_at) DESC')) }
-  scope :live, -> { published.where('effective_date IS NULL OR effective_date < now()').limit(1) }
-  scope :draft, -> { where(published_at: nil).order(id: :desc).limit(1) }
-
-  validates :text, presence: true
-  validates :changelog, :effective_date, presence: true, if: -> { published? }
-
-  validate :effective_date_cannot_be_in_the_past
-
-  def published?
-    published_at.present?
-  end
-
-  def effective?
-    published? && effective_date&.past?
-  end
-
-  def succeeded_by
-    TermsOfService.published.where(effective_date: (effective_date..)).where.not(id: id).first
-  end
-
-  def notification_sent?
-    notification_sent_at.present?
-  end
-
-  def scope_for_notification
-    User.confirmed.joins(:account).merge(Account.without_suspended).where(created_at: (..published_at))
-  end
-
-  private
-
-  def effective_date_cannot_be_in_the_past
-    return if effective_date.blank?
-
-    min_date = TermsOfService.live.pick(:effective_date) || Time.zone.today
-
-    errors.add(:effective_date, :too_soon, date: min_date) if effective_date < min_date
-  end
-end
diff --git a/app/models/terms_of_service/generator.rb b/app/models/terms_of_service/generator.rb
deleted file mode 100644
index d1c8d413ef..0000000000
--- a/app/models/terms_of_service/generator.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-class TermsOfService::Generator
-  include ActiveModel::Model
-
-  TEMPLATE = Rails.root.join('config', 'templates', 'terms-of-service.md').read
-
-  VARIABLES = %i(
-    admin_email
-    arbitration_address
-    arbitration_website
-    choice_of_law
-    dmca_address
-    dmca_email
-    domain
-    jurisdiction
-    min_age
-  ).freeze
-
-  attr_accessor(*VARIABLES)
-
-  validates(*VARIABLES, presence: true)
-
-  def render
-    format(TEMPLATE, VARIABLES.index_with { |key| public_send(key) })
-  end
-end
diff --git a/app/models/tombstone.rb b/app/models/tombstone.rb
index 12c5cee4c2..bf666c43ac 100644
--- a/app/models/tombstone.rb
+++ b/app/models/tombstone.rb
@@ -5,15 +5,13 @@
 # Table name: tombstones
 #
 #  id           :bigint(8)        not null, primary key
-#  by_moderator :boolean
+#  account_id   :bigint(8)
 #  uri          :string           not null
 #  created_at   :datetime         not null
 #  updated_at   :datetime         not null
-#  account_id   :bigint(8)        not null
+#  by_moderator :boolean
 #
 
 class Tombstone < ApplicationRecord
   belongs_to :account
-
-  validates :uri, presence: true
 end
diff --git a/app/models/trends/base.rb b/app/models/trends/base.rb
index 1e7749e4b1..a189f11f23 100644
--- a/app/models/trends/base.rb
+++ b/app/models/trends/base.rb
@@ -34,7 +34,19 @@ class Trends::Base
   end
 
   def query
-    Trends::Query.new(klass)
+    Trends::Query.new(key_prefix, klass)
+  end
+
+  def score(id, locale: nil)
+    redis.zscore([key_prefix, 'all', locale].compact.join(':'), id) || 0
+  end
+
+  def rank(id, locale: nil)
+    redis.zrevrank([key_prefix, 'allowed', locale].compact.join(':'), id)
+  end
+
+  def currently_trending_ids(allowed, limit)
+    redis.zrevrange(allowed ? "#{key_prefix}:allowed" : "#{key_prefix}:all", 0, limit.positive? ? limit - 1 : limit).map(&:to_i)
   end
 
   protected
@@ -52,9 +64,42 @@ class Trends::Base
     redis.expire(used_key(at_time), 1.day.seconds)
   end
 
+  def score_at_rank(rank)
+    redis.zrevrange("#{key_prefix}:allowed", 0, rank, with_scores: true).last&.last || 0
+  end
+
+  def replace_items(suffix, items)
+    tmp_prefix    = "#{key_prefix}:tmp:#{SecureRandom.alphanumeric(6)}#{suffix}"
+    allowed_items = filter_for_allowed_items(items)
+
+    redis.pipelined do |pipeline|
+      items.each { |item| pipeline.zadd("#{tmp_prefix}:all", item[:score], item[:item].id) }
+      allowed_items.each { |item| pipeline.zadd("#{tmp_prefix}:allowed", item[:score], item[:item].id) }
+
+      rename_set(pipeline, "#{tmp_prefix}:all", "#{key_prefix}:all#{suffix}", items)
+      rename_set(pipeline, "#{tmp_prefix}:allowed", "#{key_prefix}:allowed#{suffix}", allowed_items)
+    end
+  end
+
+  def filter_for_allowed_items(items)
+    raise NotImplementedError
+  end
+
   private
 
   def used_key(at_time)
     "#{key_prefix}:used:#{at_time.beginning_of_day.to_i}"
   end
+
+  def rename_set(pipeline, from_key, to_key, set_items)
+    if set_items.empty?
+      pipeline.del(to_key)
+    else
+      pipeline.rename(from_key, to_key)
+    end
+  end
+
+  def skip_review?
+    Setting.trendable_by_default
+  end
 end
diff --git a/app/models/trends/links.rb b/app/models/trends/links.rb
index c24e069b51..0650c4109d 100644
--- a/app/models/trends/links.rb
+++ b/app/models/trends/links.rb
@@ -14,9 +14,18 @@ class Trends::Links < Trends::Base
   }
 
   class Query < Trends::Query
+    def filtered_for!(account)
+      @account = account
+      self
+    end
+
+    def filtered_for(account)
+      clone.filtered_for!(account)
+    end
+
     def to_arel
       scope = PreviewCard.joins(:trend).reorder(score: :desc)
-      scope = scope.reorder(language_order_clause, score: :desc) if preferred_languages.present?
+      scope = scope.reorder(language_order_clause.desc, score: :desc) if preferred_languages.present?
       scope = scope.merge(PreviewCardTrend.allowed) if @allowed
       scope = scope.offset(@offset) if @offset.present?
       scope = scope.limit(@limit) if @limit.present?
@@ -25,16 +34,23 @@ class Trends::Links < Trends::Base
 
     private
 
-    def trend_class
-      PreviewCardTrend
+    def language_order_clause
+      Arel::Nodes::Case.new.when(PreviewCardTrend.arel_table[:language].in(preferred_languages)).then(1).else(0)
+    end
+
+    def preferred_languages
+      if @account&.chosen_languages.present?
+        @account.chosen_languages
+      else
+        @locale
+      end
     end
   end
 
   def register(status, at_time = Time.now.utc)
     original_status = status.proper
 
-    return unless original_status.public_visibility? &&
-                  status.public_visibility? &&
+    return unless (original_status.public_visibility? && status.public_visibility?) &&
                   !(original_status.account.silenced? || status.account.silenced?) &&
                   !(original_status.spoiler_text? || original_status.sensitive?)
 
@@ -69,7 +85,7 @@ class Trends::Links < Trends::Base
   end
 
   def request_review
-    PreviewCardTrend.locales.flat_map do |language|
+    PreviewCardTrend.pluck('distinct language').flat_map do |language|
       score_at_threshold  = PreviewCardTrend.where(language: language).allowed.by_rank.ranked_below(options[:review_threshold]).first&.score || 0
       preview_card_trends = PreviewCardTrend.where(language: language).not_allowed.joins(:preview_card)
 
diff --git a/app/models/trends/query.rb b/app/models/trends/query.rb
index 670390ae3c..c4edbba6b8 100644
--- a/app/models/trends/query.rb
+++ b/app/models/trends/query.rb
@@ -1,18 +1,19 @@
 # frozen_string_literal: true
 
 class Trends::Query
+  include Redisable
   include Enumerable
 
-  attr_reader :klass, :loaded
+  attr_reader :prefix, :klass, :loaded
 
   alias loaded? loaded
 
-  def initialize(_prefix, klass)
+  def initialize(prefix, klass)
+    @prefix  = prefix
     @klass   = klass
     @records = []
     @loaded  = false
     @allowed = false
-    @account = nil
     @limit   = nil
     @offset  = nil
   end
@@ -26,15 +27,6 @@ class Trends::Query
     clone.allowed!
   end
 
-  def filtered_for!(account)
-    @account = account
-    self
-  end
-
-  def filtered_for(account)
-    clone.filtered_for!(account)
-  end
-
   def in_locale!(value)
     @locale = value
     self
@@ -76,11 +68,22 @@ class Trends::Query
   alias to_a to_ary
 
   def to_arel
-    raise NotImplementedError
+    if ids_for_key.empty?
+      klass.none
+    else
+      scope = klass.joins(sanitized_join_sql).reorder('x.ordering')
+      scope = scope.offset(@offset) if @offset.present?
+      scope = scope.limit(@limit) if @limit.present?
+      scope
+    end
   end
 
   private
 
+  def key
+    [@prefix, @allowed ? 'allowed' : 'all', @locale].compact.join(':')
+  end
+
   def load
     unless loaded?
       @records = perform_queries
@@ -90,25 +93,29 @@ class Trends::Query
     self
   end
 
+  def ids_for_key
+    @ids_for_key ||= redis.zrevrange(key, 0, -1).map(&:to_i)
+  end
+
+  def sanitized_join_sql
+    ActiveRecord::Base.sanitize_sql_array(join_sql_array)
+  end
+
+  def join_sql_array
+    [join_sql_query, ids_for_key]
+  end
+
+  def join_sql_query
+    <<~SQL.squish
+      JOIN unnest(array[?]) WITH ordinality AS x (id, ordering) ON #{klass.table_name}.id = x.id
+    SQL
+  end
+
   def perform_queries
-    to_arel.to_a
+    apply_scopes(to_arel).to_a
   end
 
-  def language_order_clause
-    Arel::Nodes::Case.new.when(language_is_preferred).then(1).else(0).desc
-  end
-
-  def language_is_preferred
-    trend_class
-      .arel_table[:language]
-      .in(preferred_languages)
-  end
-
-  def preferred_languages
-    if @account&.chosen_languages.present?
-      @account.chosen_languages
-    else
-      @locale
-    end
+  def apply_scopes(scope)
+    scope
   end
 end
diff --git a/app/models/trends/statuses.rb b/app/models/trends/statuses.rb
index 14c5acba75..67a2d115bc 100644
--- a/app/models/trends/statuses.rb
+++ b/app/models/trends/statuses.rb
@@ -13,9 +13,18 @@ class Trends::Statuses < Trends::Base
   }
 
   class Query < Trends::Query
+    def filtered_for!(account)
+      @account = account
+      self
+    end
+
+    def filtered_for(account)
+      clone.filtered_for!(account)
+    end
+
     def to_arel
       scope = Status.joins(:trend).reorder(score: :desc)
-      scope = scope.reorder(language_order_clause, score: :desc) if preferred_languages.present?
+      scope = scope.reorder(language_order_clause.desc, score: :desc) if preferred_languages.present?
       scope = scope.merge(StatusTrend.allowed) if @allowed
       scope = scope.not_excluded_by_account(@account).not_domain_blocked_by_account(@account) if @account.present?
       scope = scope.offset(@offset) if @offset.present?
@@ -25,8 +34,16 @@ class Trends::Statuses < Trends::Base
 
     private
 
-    def trend_class
-      StatusTrend
+    def language_order_clause
+      Arel::Nodes::Case.new.when(StatusTrend.arel_table[:language].in(preferred_languages)).then(1).else(0)
+    end
+
+    def preferred_languages
+      if @account&.chosen_languages.present?
+        @account.chosen_languages
+      else
+        @locale
+      end
     end
   end
 
@@ -61,7 +78,7 @@ class Trends::Statuses < Trends::Base
   end
 
   def request_review
-    StatusTrend.locales.flat_map do |language|
+    StatusTrend.pluck('distinct language').flat_map do |language|
       score_at_threshold = StatusTrend.where(language: language, allowed: true).by_rank.ranked_below(options[:review_threshold]).first&.score || 0
       status_trends      = StatusTrend.where(language: language, allowed: false).joins(:status).includes(status: :account)
 
@@ -90,6 +107,7 @@ class Trends::Statuses < Trends::Base
 
   def eligible?(status)
     status.created_at.past? &&
+      (status.searchability.nil? || status.compute_searchability == 'public') &&
       (status.public_visibility? || status.public_unlisted_visibility?) &&
       status.account.discoverable? && !status.account.silenced? && !status.account.sensitized? &&
       status.spoiler_text.blank? && (!status.sensitive? || status.media_attachments.none?) &&
diff --git a/app/models/trends/tag_filter.rb b/app/models/trends/tag_filter.rb
index 02d558ac25..d6f88a9486 100644
--- a/app/models/trends/tag_filter.rb
+++ b/app/models/trends/tag_filter.rb
@@ -6,8 +6,6 @@ class Trends::TagFilter
     status
   ).freeze
 
-  IGNORED_PARAMS = %w(page).freeze
-
   attr_reader :params
 
   def initialize(params)
@@ -15,10 +13,14 @@ class Trends::TagFilter
   end
 
   def results
-    scope = initial_scope
+    scope = if params[:status] == 'pending_review'
+              Tag.unscoped.order(id: :desc)
+            else
+              trending_scope
+            end
 
     params.each do |key, value|
-      next if IGNORED_PARAMS.include?(key.to_s)
+      next if key.to_s == 'page'
 
       scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
     end
@@ -28,24 +30,19 @@ class Trends::TagFilter
 
   private
 
-  def initial_scope
-    Tag.select(Tag.arel_table[Arel.star])
-       .joins(:trend)
-       .eager_load(:trend)
-       .reorder(score: :desc)
-  end
-
   def scope_for(key, value)
     case key.to_s
     when 'status'
       status_scope(value)
-    when 'trending'
-      trending_scope(value)
     else
-      raise Mastodon::InvalidParameterError, "Unknown filter: #{key}"
+      raise "Unknown filter: #{key}"
     end
   end
 
+  def trending_scope
+    Trends.tags.query.to_arel
+  end
+
   def status_scope(value)
     case value.to_s
     when 'approved'
@@ -55,16 +52,7 @@ class Trends::TagFilter
     when 'pending_review'
       Tag.pending_review
     else
-      raise Mastodon::InvalidParameterError, "Unknown status: #{value}"
-    end
-  end
-
-  def trending_scope(value)
-    case value
-    when 'allowed'
-      TagTrend.allowed
-    else
-      TagTrend.all
+      raise "Unknown status: #{value}"
     end
   end
 end
diff --git a/app/models/trends/tags.rb b/app/models/trends/tags.rb
index 819ad508a3..5b00fca7ec 100644
--- a/app/models/trends/tags.rb
+++ b/app/models/trends/tags.rb
@@ -3,8 +3,6 @@
 class Trends::Tags < Trends::Base
   PREFIX = 'trending_tags'
 
-  BATCH_SIZE = 100
-
   self.default_options = {
     threshold: 5,
     review_threshold: 3,
@@ -13,23 +11,6 @@ class Trends::Tags < Trends::Base
     decay_threshold: 1,
   }
 
-  class Query < Trends::Query
-    def to_arel
-      scope = Tag.joins(:trend).reorder(score: :desc)
-      scope = scope.reorder(language_order_clause, score: :desc) if preferred_languages.present?
-      scope = scope.merge(TagTrend.allowed) if @allowed
-      scope = scope.offset(@offset) if @offset.present?
-      scope = scope.limit(@limit) if @limit.present?
-      scope
-    end
-
-    private
-
-    def trend_class
-      TagTrend
-    end
-  end
-
   def register(status, at_time = Time.now.utc)
     return unless !status.reblog? && %i(public public_unlisted login).include?(status.visibility.to_sym) && !status.account.silenced?
     return if !status.account.local? && DomainBlock.block_trends?(status.account.domain)
@@ -44,39 +25,19 @@ class Trends::Tags < Trends::Base
     record_used_id(tag.id, at_time)
   end
 
-  def query
-    Query.new(key_prefix, klass)
-  end
-
   def refresh(at_time = Time.now.utc)
-    # First, recalculate scores for tags that were trending previously. We split the queries
-    # to avoid having to load all of the IDs into Ruby just to send them back into Postgres
-    Tag.where(id: TagTrend.select(:tag_id)).find_in_batches(batch_size: BATCH_SIZE) do |tags|
-      calculate_scores(tags, at_time)
-    end
-
-    # Then, calculate scores for tags that were used today. There are potentially some
-    # duplicate items here that we might process one more time, but that should be fine
-    Tag.where(id: recently_used_ids(at_time)).find_in_batches(batch_size: BATCH_SIZE) do |tags|
-      calculate_scores(tags, at_time)
-    end
-
-    # Now that all trends have up-to-date scores, and all the ones below the threshold have
-    # been removed, we can recalculate their positions
-    TagTrend.recalculate_ordered_rank
+    tags = Tag.where(id: (recently_used_ids(at_time) + currently_trending_ids(false, -1)).uniq)
+    calculate_scores(tags, at_time)
   end
 
   def request_review
-    score_at_threshold = TagTrend.allowed.by_rank.ranked_below(options[:review_threshold]).first&.score || 0
-    tag_trends = TagTrend.not_allowed.includes(:tag)
+    tags = Tag.where(id: currently_trending_ids(false, -1))
 
-    tag_trends.filter_map do |trend|
-      tag = trend.tag
+    tags.filter_map do |tag|
+      next unless would_be_trending?(tag.id) && !tag.trendable? && tag.requires_review_notification?
 
-      if trend.score > score_at_threshold && !tag.trendable? && tag.requires_review_notification?
-        tag.touch(:requested_review_at)
-        tag
-      end
+      tag.touch(:requested_review_at)
+      tag
     end
   end
 
@@ -93,7 +54,9 @@ class Trends::Tags < Trends::Base
   private
 
   def calculate_scores(tags, at_time)
-    items = tags.map do |tag|
+    items = []
+
+    tags.each do |tag|
       expected  = tag.history.get(at_time - 1.day).accounts.to_f
       expected  = 1.0 if expected.zero?
       observed  = tag.history.get(at_time).accounts.to_f
@@ -117,13 +80,19 @@ class Trends::Tags < Trends::Base
 
       decaying_score = max_score * (0.5**((at_time.to_f - max_time.to_f) / options[:max_score_halflife].to_f))
 
-      [decaying_score, tag]
+      next unless decaying_score >= options[:decay_threshold]
+
+      items << { score: decaying_score, item: tag }
     end
 
-    to_insert = items.filter { |(score, _)| score >= options[:decay_threshold] }
-    to_delete = items.filter { |(score, _)| score < options[:decay_threshold] }
+    replace_items('', items)
+  end
 
-    TagTrend.upsert_all(to_insert.map { |(score, tag)| { tag_id: tag.id, score: score, language: '', allowed: tag.trendable? || false } }, unique_by: %w(tag_id language)) if to_insert.any?
-    TagTrend.where(tag_id: to_delete.map { |(_, tag)| tag.id }).delete_all if to_delete.any?
+  def filter_for_allowed_items(items)
+    items.select { |item| item[:item].trendable? }
+  end
+
+  def would_be_trending?(id)
+    score(id) > score_at_rank(options[:review_threshold] - 1)
   end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 7858ab906d..9e0ca283a2 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -5,42 +5,41 @@
 # Table name: users
 #
 #  id                        :bigint(8)        not null, primary key
-#  age_verified_at           :datetime
-#  approved                  :boolean          default(TRUE), not null
-#  chosen_languages          :string           is an Array
-#  confirmation_sent_at      :datetime
+#  email                     :string           default(""), not null
+#  created_at                :datetime         not null
+#  updated_at                :datetime         not null
+#  encrypted_password        :string           default(""), not null
+#  reset_password_token      :string
+#  reset_password_sent_at    :datetime
+#  sign_in_count             :integer          default(0), not null
+#  current_sign_in_at        :datetime
+#  last_sign_in_at           :datetime
 #  confirmation_token        :string
 #  confirmed_at              :datetime
-#  consumed_timestep         :integer
-#  current_sign_in_at        :datetime
-#  disabled                  :boolean          default(FALSE), not null
-#  email                     :string           default(""), not null
+#  confirmation_sent_at      :datetime
+#  unconfirmed_email         :string
+#  locale                    :string
 #  encrypted_otp_secret      :string
 #  encrypted_otp_secret_iv   :string
 #  encrypted_otp_secret_salt :string
-#  encrypted_password        :string           default(""), not null
-#  last_emailed_at           :datetime
-#  last_sign_in_at           :datetime
-#  locale                    :string
-#  otp_backup_codes          :string           is an Array
+#  consumed_timestep         :integer
 #  otp_required_for_login    :boolean          default(FALSE), not null
-#  otp_secret                :string
-#  reset_password_sent_at    :datetime
-#  reset_password_token      :string
-#  settings                  :text
-#  sign_in_count             :integer          default(0), not null
+#  last_emailed_at           :datetime
+#  otp_backup_codes          :string           is an Array
+#  account_id                :bigint(8)        not null
+#  disabled                  :boolean          default(FALSE), not null
+#  invite_id                 :bigint(8)
+#  chosen_languages          :string           is an Array
+#  created_by_application_id :bigint(8)
+#  approved                  :boolean          default(TRUE), not null
 #  sign_in_token             :string
 #  sign_in_token_sent_at     :datetime
-#  sign_up_ip                :inet
-#  time_zone                 :string
-#  unconfirmed_email         :string
-#  created_at                :datetime         not null
-#  updated_at                :datetime         not null
-#  account_id                :bigint(8)        not null
-#  created_by_application_id :bigint(8)
-#  invite_id                 :bigint(8)
-#  role_id                   :bigint(8)
 #  webauthn_id               :string
+#  sign_up_ip                :inet
+#  role_id                   :bigint(8)
+#  settings                  :text
+#  time_zone                 :string
+#  otp_secret                :string
 #
 
 class User < ApplicationRecord
@@ -117,7 +116,6 @@ class User < ApplicationRecord
   validates_with RegistrationFormTimeValidator, on: :create
   validates :website, absence: true, on: :create
   validates :confirm_password, absence: true, on: :create
-  validates :date_of_birth, presence: true, date_of_birth: true, on: :create, if: -> { Setting.min_age.present? }
   validate :validate_role_elevation
 
   scope :account_not_suspended, -> { joins(:account).merge(Account.without_suspended) }
@@ -132,11 +130,10 @@ class User < ApplicationRecord
   scope :signed_in_recently, -> { where(current_sign_in_at: ACTIVE_DURATION.ago..) }
   scope :not_signed_in_recently, -> { where(current_sign_in_at: ...ACTIVE_DURATION.ago) }
   scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }
-  scope :matches_ip, ->(value) { left_joins(:ips).merge(IpBlock.contained_by(value)).group(users: [:id]) }
+  scope :matches_ip, ->(value) { left_joins(:ips).where('user_ips.ip <<= ?', value).group('users.id') }
 
   before_validation :sanitize_role
   before_create :set_approved
-  before_create :set_age_verified_at
   after_commit :send_pending_devise_notifications
   after_create_commit :trigger_webhooks
 
@@ -148,7 +145,7 @@ class User < ApplicationRecord
 
   delegate :can?, to: :role
 
-  attr_reader :invite_code, :date_of_birth
+  attr_reader :invite_code
   attr_writer :external, :bypass_invite_request_check, :current_account
 
   def self.those_who_can(*any_of_privileges)
@@ -165,17 +162,6 @@ class User < ApplicationRecord
     Rails.env.local?
   end
 
-  def date_of_birth=(hash_or_string)
-    @date_of_birth = begin
-      if hash_or_string.is_a?(Hash)
-        day, month, year = hash_or_string.values_at(1, 2, 3)
-        "#{day}.#{month}.#{year}"
-      else
-        hash_or_string
-      end
-    end
-  end
-
   def role
     if role_id.nil?
       UserRole.everyone
@@ -321,15 +307,6 @@ class User < ApplicationRecord
     save!
   end
 
-  def applications_last_used
-    Doorkeeper::AccessToken
-      .where(resource_owner_id: id)
-      .where.not(last_used_at: nil)
-      .group(:application_id)
-      .maximum(:last_used_at)
-      .to_h
-  end
-
   def token_for_app(app)
     return nil if app.nil? || app.owner != self
 
@@ -387,10 +364,10 @@ class User < ApplicationRecord
   end
 
   def revoke_access!
-    Doorkeeper::AccessGrant.by_resource_owner(self).touch_all(:revoked_at)
+    Doorkeeper::AccessGrant.by_resource_owner(self).update_all(revoked_at: Time.now.utc)
 
     Doorkeeper::AccessToken.by_resource_owner(self).in_batches do |batch|
-      batch.touch_all(:revoked_at)
+      batch.update_all(revoked_at: Time.now.utc)
       Web::PushSubscription.where(access_token_id: batch).delete_all
 
       # Revoke each access token for the Streaming API, since `update_all``
@@ -455,8 +432,8 @@ class User < ApplicationRecord
     @pending_devise_notifications ||= []
   end
 
-  def render_and_send_devise_message(notification, *, **)
-    devise_mailer.send(notification, self, *, **).deliver_later
+  def render_and_send_devise_message(notification, *args, **kwargs)
+    devise_mailer.send(notification, self, *args, **kwargs).deliver_later
   end
 
   def set_approved
@@ -469,10 +446,6 @@ class User < ApplicationRecord
     end
   end
 
-  def set_age_verified_at
-    self.age_verified_at = Time.now.utc if Setting.min_age.present?
-  end
-
   def grant_approval_on_confirmation?
     # Re-check approval on confirmation if the server has switched to open registrations
     open_registrations? && !sign_up_from_ip_requires_approval? && !sign_up_email_requires_approval?
@@ -498,7 +471,7 @@ class User < ApplicationRecord
   end
 
   def sign_up_from_ip_requires_approval?
-    sign_up_ip.present? && IpBlock.severity_sign_up_requires_approval.containing(sign_up_ip.to_s).exists?
+    sign_up_ip.present? && IpBlock.severity_sign_up_requires_approval.exists?(['ip >>= ?', sign_up_ip.to_s])
   end
 
   def sign_up_email_requires_approval?
@@ -511,7 +484,13 @@ class User < ApplicationRecord
 
     # Doing this conditionally is not very satisfying, but this is consistent
     # with the MX records validations we do and keeps the specs tractable.
-    records = DomainResource.new(domain).mx unless self.class.skip_mx_check?
+    unless self.class.skip_mx_check?
+      Resolv::DNS.open do |dns|
+        dns.timeouts = 5
+
+        records = dns.getresources(domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }.compact_blank
+      end
+    end
 
     EmailDomainBlock.requires_approval?(records + [domain], attempt_ip: sign_up_ip)
   end
diff --git a/app/models/user_invite_request.rb b/app/models/user_invite_request.rb
index 23b5428d48..9dd6775166 100644
--- a/app/models/user_invite_request.rb
+++ b/app/models/user_invite_request.rb
@@ -5,10 +5,10 @@
 # Table name: user_invite_requests
 #
 #  id         :bigint(8)        not null, primary key
+#  user_id    :bigint(8)
 #  text       :text
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  user_id    :bigint(8)        not null
 #
 
 class UserInviteRequest < ApplicationRecord
diff --git a/app/models/user_ip.rb b/app/models/user_ip.rb
index 25aa81ccd4..a6da2c0740 100644
--- a/app/models/user_ip.rb
+++ b/app/models/user_ip.rb
@@ -11,7 +11,6 @@
 
 class UserIp < ApplicationRecord
   include DatabaseViewRecord
-  include InetContainer
 
   self.primary_key = :user_id
 
diff --git a/app/models/user_role.rb b/app/models/user_role.rb
index 38cbd746fd..405050a385 100644
--- a/app/models/user_role.rb
+++ b/app/models/user_role.rb
@@ -43,9 +43,6 @@ class UserRole < ApplicationRecord
   EVERYONE_ROLE_ID = -99
   NOBODY_POSITION = -1
 
-  POSITION_LIMIT = (2**31) - 1
-  CSS_COLORS = /\A#?(?:[A-F0-9]{3}){1,2}\z/i # CSS-style hex colors
-
   module Flags
     NONE = 0
     ALL  = FLAGS.values.reduce(&:|)
@@ -96,8 +93,7 @@ class UserRole < ApplicationRecord
   attr_writer :current_account
 
   validates :name, presence: true, unless: :everyone?
-  validates :color, format: { with: CSS_COLORS }, if: :color?
-  validates :position, numericality: { in: (-POSITION_LIMIT..POSITION_LIMIT) }
+  validates :color, format: { with: /\A#?(?:[A-F0-9]{3}){1,2}\z/i }, unless: -> { color.blank? }
 
   validate :validate_permissions_elevation
   validate :validate_position_elevation
@@ -107,6 +103,9 @@ class UserRole < ApplicationRecord
   before_validation :set_position
 
   scope :assignable, -> { where.not(id: EVERYONE_ROLE_ID).order(position: :asc) }
+  scope :highlighted, -> { where(highlighted: true) }
+  scope :with_color, -> { where.not(color: [nil, '']) }
+  scope :providing_styles, -> { highlighted.with_color }
 
   has_many :users, inverse_of: :role, foreign_key: 'role_id', dependent: :nullify
 
diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb
index fb7d81a5f4..0a995be81a 100644
--- a/app/models/user_settings.rb
+++ b/app/models/user_settings.rb
@@ -55,11 +55,9 @@ class UserSettings
     setting :use_blurhash, default: true
     setting :use_pending_items, default: false
     setting :use_system_font, default: false
-    setting :use_server_css, default: true
     setting :use_custom_css, default: false
     setting :content_font_size, default: 'medium', in: %w(medium large x_large xx_large)
     setting :bookmark_category_needed, default: false
-    setting :use_system_scrollbars, default: false
     setting :disable_swiping, default: false
     setting :disable_hover_cards, default: false
     setting :delete_modal, default: true
@@ -68,7 +66,6 @@ class UserSettings
     setting :enable_emoji_reaction, default: true
     setting :show_emoji_reaction_on_timeline, default: true
     setting :reblog_modal, default: false
-    setting :missing_alt_text_modal, default: true
     setting :reduce_motion, default: false
     setting :expand_content_warnings, default: false
     setting :display_media, default: 'default', in: %w(default show_all hide_all)
@@ -84,7 +81,6 @@ class UserSettings
     setting :hide_status_reference_unavailable_server, default: false
     setting :hide_favourite_menu, default: false
     setting :hide_emoji_reaction_count, default: false
-    setting :show_avatar_on_filter, default: true
 
     setting_inverse_alias :'web.show_blocking_quote', :'web.hide_blocking_quote'
     setting_inverse_alias :'web.show_emoji_reaction_count', :'web.hide_emoji_reaction_count'
diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb
index 12d843cd09..9d30881bf3 100644
--- a/app/models/web/push_subscription.rb
+++ b/app/models/web/push_subscription.rb
@@ -5,11 +5,10 @@
 # Table name: web_push_subscriptions
 #
 #  id              :bigint(8)        not null, primary key
-#  data            :json
 #  endpoint        :string           not null
-#  key_auth        :string           not null
 #  key_p256dh      :string           not null
-#  standard        :boolean          default(FALSE), not null
+#  key_auth        :string           not null
+#  data            :json
 #  created_at      :datetime         not null
 #  updated_at      :datetime         not null
 #  access_token_id :bigint(8)
@@ -30,8 +29,6 @@ class Web::PushSubscription < ApplicationRecord
 
   delegate :locale, to: :associated_user
 
-  generates_token_for :unsubscribe, expires_in: Web::PushNotificationWorker::TTL
-
   def pushable?(notification)
     policy_allows_notification?(notification) && alert_enabled_for_notification_type?(notification)
   end
diff --git a/app/models/webhook.rb b/app/models/webhook.rb
index f9d6564c92..304b2b1f18 100644
--- a/app/models/webhook.rb
+++ b/app/models/webhook.rb
@@ -53,7 +53,7 @@ class Webhook < ApplicationRecord
   end
 
   def required_permissions
-    events.map { |event| Webhook.permission_for_event(event) }.uniq
+    events.map { |event| Webhook.permission_for_event(event) }
   end
 
   def self.permission_for_event(event)
diff --git a/app/policies/account_warning_policy.rb b/app/policies/account_warning_policy.rb
index 976cae6c9c..4f8df7420e 100644
--- a/app/policies/account_warning_policy.rb
+++ b/app/policies/account_warning_policy.rb
@@ -6,7 +6,7 @@ class AccountWarningPolicy < ApplicationPolicy
   end
 
   def appeal?
-    target? && record.appeal_eligible?
+    target? && record.created_at >= Appeal::MAX_STRIKE_AGE.ago
   end
 
   private
diff --git a/app/policies/admin/fasp/provider_policy.rb b/app/policies/admin/fasp/provider_policy.rb
deleted file mode 100644
index a8088fd37d..0000000000
--- a/app/policies/admin/fasp/provider_policy.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::Fasp::ProviderPolicy < ApplicationPolicy
-  def index?
-    role.can?(:manage_federation)
-  end
-
-  def show?
-    role.can?(:manage_federation)
-  end
-
-  def create?
-    role.can?(:manage_federation)
-  end
-
-  def update?
-    role.can?(:manage_federation)
-  end
-
-  def destroy?
-    role.can?(:manage_federation)
-  end
-end
diff --git a/app/policies/admin/status_policy.rb b/app/policies/admin/status_policy.rb
index d320b98da8..7a42d5bf28 100644
--- a/app/policies/admin/status_policy.rb
+++ b/app/policies/admin/status_policy.rb
@@ -12,7 +12,7 @@ class Admin::StatusPolicy < ApplicationPolicy
   end
 
   def show?
-    role.can?(:manage_reports, :manage_users) && eligible_to_show?
+    role.can?(:manage_reports, :manage_users) && (record.public_visibility? || record.unlisted_visibility? || record.public_unlisted_visibility? || record.reported? || viewable_through_normal_policy?)
   end
 
   def destroy?
@@ -29,10 +29,6 @@ class Admin::StatusPolicy < ApplicationPolicy
 
   private
 
-  def eligible_to_show?
-    record.distributable? || record.login_visibility? || record.reported? || viewable_through_normal_policy?
-  end
-
   def viewable_through_normal_policy?
     StatusPolicy.new(current_account, record, @preloaded_relations).show?
   end
diff --git a/app/policies/announcement_policy.rb b/app/policies/announcement_policy.rb
index 907a3b1a86..b5dc6a18af 100644
--- a/app/policies/announcement_policy.rb
+++ b/app/policies/announcement_policy.rb
@@ -16,8 +16,4 @@ class AnnouncementPolicy < ApplicationPolicy
   def destroy?
     role.can?(:manage_announcements)
   end
-
-  def distribute?
-    record.published? && !record.notification_sent? && role.can?(:manage_settings)
-  end
 end
diff --git a/app/policies/terms_of_service_policy.rb b/app/policies/terms_of_service_policy.rb
deleted file mode 100644
index b4f0c71bc8..0000000000
--- a/app/policies/terms_of_service_policy.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-class TermsOfServicePolicy < ApplicationPolicy
-  def index?
-    role.can?(:manage_settings)
-  end
-
-  def create?
-    role.can?(:manage_settings)
-  end
-
-  def distribute?
-    record.published? && !record.notification_sent? && role.can?(:manage_settings)
-  end
-
-  def update?
-    !record.published? && role.can?(:manage_settings)
-  end
-
-  def destroy?
-    !record.published? && role.can?(:manage_settings)
-  end
-end
diff --git a/app/policies/user_role_policy.rb b/app/policies/user_role_policy.rb
index 44b5589581..6144a0ec4a 100644
--- a/app/policies/user_role_policy.rb
+++ b/app/policies/user_role_policy.rb
@@ -10,16 +10,10 @@ class UserRolePolicy < ApplicationPolicy
   end
 
   def update?
-    role.can?(:manage_roles) && (role.overrides?(record) || self_editing?)
+    role.can?(:manage_roles) && (role.overrides?(record) || role.id == record.id)
   end
 
   def destroy?
-    !record.everyone? && role.can?(:manage_roles) && role.overrides?(record) && !self_editing?
-  end
-
-  private
-
-  def self_editing?
-    role.id == record.id
+    !record.everyone? && role.can?(:manage_roles) && role.overrides?(record) && role.id != record.id
   end
 end
diff --git a/app/presenters/activitypub/misskey_emoji_license_presenter.rb b/app/presenters/activitypub/misskey_emoji_license_presenter.rb
deleted file mode 100644
index db8de36171..0000000000
--- a/app/presenters/activitypub/misskey_emoji_license_presenter.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-class ActivityPub::MisskeyEmojiLicensePresenter < ActiveModelSerializers::Model
-  attributes :free_text
-end
diff --git a/app/presenters/export_summary.rb b/app/presenters/export_summary.rb
deleted file mode 100644
index 8e45aadf67..0000000000
--- a/app/presenters/export_summary.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-# frozen_string_literal: true
-
-class ExportSummary
-  attr_reader :account, :counts
-
-  delegate(
-    :blocking,
-    :bookmarks,
-    :domain_blocks,
-    :owned_lists,
-    :media_attachments,
-    :muting,
-    to: :account,
-    prefix: true
-  )
-
-  def initialize(account)
-    @account = account
-    @counts = populate_counts
-  end
-
-  def total_blocks
-    counts[:blocks].value
-  end
-
-  def total_bookmarks
-    counts[:bookmarks].value
-  end
-
-  def total_domain_blocks
-    counts[:domain_blocks].value
-  end
-
-  def total_followers
-    account.followers_count
-  end
-
-  def total_follows
-    account.following_count
-  end
-
-  def total_lists
-    counts[:owned_lists].value
-  end
-
-  def total_mutes
-    counts[:muting].value
-  end
-
-  def total_statuses
-    account.statuses_count
-  end
-
-  def total_storage
-    counts[:storage].value
-  end
-
-  private
-
-  def populate_counts
-    {
-      blocks: account_blocking.async_count,
-      bookmarks: account_bookmarks.async_count,
-      domain_blocks: account_domain_blocks.async_count,
-      owned_lists: account_owned_lists.async_count,
-      muting: account_muting.async_count,
-      storage: account_media_attachments.async_sum(:file_file_size),
-    }
-  end
-end
diff --git a/app/presenters/language_presenter.rb b/app/presenters/language_presenter.rb
index 717d70fdbc..69ea991d54 100644
--- a/app/presenters/language_presenter.rb
+++ b/app/presenters/language_presenter.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class LanguagePresenter < ActiveModelSerializers::Model
-  attributes :code, :name
+  attributes :code, :name, :native_name
 
   def initialize(code)
     super()
@@ -13,4 +13,8 @@ class LanguagePresenter < ActiveModelSerializers::Model
   def name
     @item[0]
   end
+
+  def native_name
+    @item[1]
+  end
 end
diff --git a/app/presenters/oauth_metadata_presenter.rb b/app/presenters/oauth_metadata_presenter.rb
index f488a62925..1e4d25165c 100644
--- a/app/presenters/oauth_metadata_presenter.rb
+++ b/app/presenters/oauth_metadata_presenter.rb
@@ -26,10 +26,6 @@ class OauthMetadataPresenter < ActiveModelSerializers::Model
     oauth_token_url
   end
 
-  def userinfo_endpoint
-    oauth_userinfo_url
-  end
-
   # As the api_v1_apps route doesn't technically conform to the specification
   # for OAuth 2.0 Dynamic Client Registration defined in RFC 7591 we use a
   # non-standard property for now to indicate the mastodon specific registration
@@ -65,7 +61,7 @@ class OauthMetadataPresenter < ActiveModelSerializers::Model
   end
 
   def code_challenge_methods_supported
-    doorkeeper.pkce_code_challenge_methods_supported
+    %w(S256)
   end
 
   private
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 0c04511ce8..c6e88a64fa 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -44,7 +44,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
   delegate :suspended?, :instance_actor?, to: :object
 
   def id
-    ActivityPub::TagManager.instance.uri_for(object)
+    object.instance_actor? ? instance_actor_url : account_url(object)
   end
 
   def type
@@ -60,27 +60,27 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
   end
 
   def following
-    ActivityPub::TagManager.instance.following_uri_for(object)
+    account_following_index_url(object)
   end
 
   def followers
-    ActivityPub::TagManager.instance.followers_uri_for(object)
+    account_followers_url(object)
   end
 
   def inbox
-    ActivityPub::TagManager.instance.inbox_uri_for(object)
+    object.instance_actor? ? instance_actor_inbox_url : account_inbox_url(object)
   end
 
   def outbox
-    ActivityPub::TagManager.instance.outbox_uri_for(object)
+    object.instance_actor? ? instance_actor_outbox_url : account_outbox_url(object)
   end
 
   def featured
-    ActivityPub::TagManager.instance.collection_uri_for(object, :featured)
+    account_collection_url(object, :featured)
   end
 
   def featured_tags
-    ActivityPub::TagManager.instance.collection_uri_for(object, :tags)
+    account_collection_url(object, :tags)
   end
 
   def endpoints
diff --git a/app/serializers/activitypub/add_serializer.rb b/app/serializers/activitypub/add_serializer.rb
index 640d774272..436b05086f 100644
--- a/app/serializers/activitypub/add_serializer.rb
+++ b/app/serializers/activitypub/add_serializer.rb
@@ -38,6 +38,6 @@ class ActivityPub::AddSerializer < ActivityPub::Serializer
   end
 
   def target
-    ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured)
+    account_collection_url(object.account, :featured)
   end
 end
diff --git a/app/serializers/activitypub/emoji_serializer.rb b/app/serializers/activitypub/emoji_serializer.rb
index 443edd7e53..24c21c3ccf 100644
--- a/app/serializers/activitypub/emoji_serializer.rb
+++ b/app/serializers/activitypub/emoji_serializer.rb
@@ -3,12 +3,11 @@
 class ActivityPub::EmojiSerializer < ActivityPub::Serializer
   include RoutingHelper
 
-  context_extensions :emoji, :license, :keywords, :misskey_license
+  context_extensions :emoji, :license, :keywords
 
   attributes :id, :type, :name, :keywords, :is_sensitive, :updated
 
-  attribute :license, if: :license?
-  has_one :misskey_license, key: :_misskey_license, if: :license?, serializer: ActivityPub::MisskeyEmojiLicenseSerializer
+  attribute :license, if: -> { object.license.present? }
 
   has_one :icon, serializer: ActivityPub::ImageSerializer
 
@@ -35,12 +34,4 @@ class ActivityPub::EmojiSerializer < ActivityPub::Serializer
   def name
     ":#{object.shortcode}:"
   end
-
-  def misskey_license
-    ActivityPub::MisskeyEmojiLicensePresenter.new(free_text: object.license)
-  end
-
-  def license?
-    object.license.present?
-  end
 end
diff --git a/app/serializers/activitypub/misskey_emoji_license_serializer.rb b/app/serializers/activitypub/misskey_emoji_license_serializer.rb
deleted file mode 100644
index fcaf37aff4..0000000000
--- a/app/serializers/activitypub/misskey_emoji_license_serializer.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-class ActivityPub::MisskeyEmojiLicenseSerializer < ActivityPub::Serializer
-  attribute :free_text, key: :freeText
-end
diff --git a/app/serializers/activitypub/remove_serializer.rb b/app/serializers/activitypub/remove_serializer.rb
index 4f78804031..fb224f8a99 100644
--- a/app/serializers/activitypub/remove_serializer.rb
+++ b/app/serializers/activitypub/remove_serializer.rb
@@ -38,6 +38,6 @@ class ActivityPub::RemoveSerializer < ActivityPub::Serializer
   end
 
   def target
-    ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured)
+    account_collection_url(object.account, :featured)
   end
 end
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 7fbee3a626..62a7d5660b 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -21,7 +21,6 @@ class InitialStateSerializer < ActiveModel::Serializer
       store[:me]                = object.current_account.id.to_s
       store[:boost_modal]       = object_account_user.setting_boost_modal
       store[:delete_modal]      = object_account_user.setting_delete_modal
-      store[:missing_alt_text_modal] = object_account_user.settings['web.missing_alt_text_modal']
       store[:auto_play_gif]     = object_account_user.setting_auto_play_gif
       store[:display_media]     = object_account_user.setting_display_media
       store[:expand_spoilers] = object_account_user.setting_expand_spoilers
@@ -49,7 +48,6 @@ class InitialStateSerializer < ActiveModel::Serializer
         object_account_user.setting_show_quote_in_home ? nil : 'quote_in_home',
         object_account_user.setting_show_quote_in_public ? nil : 'quote_in_public',
         object_account_user.setting_show_relationships ? nil : 'relationships',
-        object_account_user.setting_show_avatar_on_filter ? nil : 'avatar_on_filter',
       ].compact
       store[:enabled_visibilities] = enabled_visibilities
       store[:featured_tags] = object.current_account.featured_tags.pluck(:name)
@@ -149,7 +147,6 @@ class InitialStateSerializer < ActiveModel::Serializer
       trends_as_landing_page: Setting.trends_as_landing_page,
       trends_enabled: Setting.trends,
       version: instance_presenter.version,
-      terms_of_service_enabled: TermsOfService.live.exists?,
     }
   end
 
diff --git a/app/serializers/manifest_serializer.rb b/app/serializers/manifest_serializer.rb
index 3496f329f8..cf0164c24a 100644
--- a/app/serializers/manifest_serializer.rb
+++ b/app/serializers/manifest_serializer.rb
@@ -8,8 +8,7 @@ class ManifestSerializer < ActiveModel::Serializer
   attributes :id, :name, :short_name,
              :icons, :theme_color, :background_color,
              :display, :start_url, :scope,
-             :share_target, :shortcuts,
-             :prefer_related_applications, :related_applications
+             :share_target, :shortcuts
 
   def id
     # This is set to `/home` because that was the old value of `start_url` and
@@ -90,28 +89,4 @@ class ManifestSerializer < ActiveModel::Serializer
       },
     ]
   end
-
-  def prefer_related_applications
-    true
-  end
-
-  def related_applications
-    [
-      {
-        platform: 'play',
-        url: 'https://play.google.com/store/apps/details?id=org.joinmastodon.android',
-        id: 'org.joinmastodon.android',
-      },
-      {
-        platform: 'itunes',
-        url: 'https://apps.apple.com/us/app/mastodon-for-iphone/id1571998974',
-        id: 'id1571998974',
-      },
-      {
-        platform: 'f-droid',
-        url: 'https://f-droid.org/en/packages/org.joinmastodon.android/',
-        id: 'org.joinmastodon.android',
-      },
-    ]
-  end
 end
diff --git a/app/serializers/oauth_metadata_serializer.rb b/app/serializers/oauth_metadata_serializer.rb
index 9c5f7365a4..2afb4208fb 100644
--- a/app/serializers/oauth_metadata_serializer.rb
+++ b/app/serializers/oauth_metadata_serializer.rb
@@ -2,7 +2,7 @@
 
 class OauthMetadataSerializer < ActiveModel::Serializer
   attributes :issuer, :authorization_endpoint, :token_endpoint,
-             :revocation_endpoint, :userinfo_endpoint, :scopes_supported,
+             :revocation_endpoint, :scopes_supported,
              :response_types_supported, :response_modes_supported,
              :grant_types_supported, :token_endpoint_auth_methods_supported,
              :code_challenge_methods_supported,
diff --git a/app/serializers/oauth_userinfo_serializer.rb b/app/serializers/oauth_userinfo_serializer.rb
deleted file mode 100644
index e2f37ae02e..0000000000
--- a/app/serializers/oauth_userinfo_serializer.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-class OauthUserinfoSerializer < ActiveModel::Serializer
-  include RoutingHelper
-
-  attributes :iss, :sub, :name, :preferred_username, :profile, :picture
-
-  def iss
-    root_url
-  end
-
-  def sub
-    ActivityPub::TagManager.instance.uri_for(object)
-  end
-
-  def name
-    object.display_name
-  end
-
-  def preferred_username
-    object.username
-  end
-
-  def profile
-    ActivityPub::TagManager.instance.url_for(object)
-  end
-
-  def picture
-    full_asset_url(object.avatar_original_url)
-  end
-end
diff --git a/app/serializers/rest/annual_report_event_serializer.rb b/app/serializers/rest/annual_report_event_serializer.rb
deleted file mode 100644
index 555a596357..0000000000
--- a/app/serializers/rest/annual_report_event_serializer.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-class REST::AnnualReportEventSerializer < ActiveModel::Serializer
-  attributes :year
-
-  def year
-    object.year.to_s
-  end
-end
diff --git a/app/serializers/rest/antenna_serializer.rb b/app/serializers/rest/antenna_serializer.rb
index a3a7d0e599..c0d03c7282 100644
--- a/app/serializers/rest/antenna_serializer.rb
+++ b/app/serializers/rest/antenna_serializer.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class REST::AntennaSerializer < ActiveModel::Serializer
-  attributes :id, :title, :stl, :ltl, :insert_feeds, :with_media_only, :ignore_reblog, :accounts_count, :domains_count, :tags_count, :keywords_count, :favourite
+  attributes :id, :title, :stl, :ltl, :insert_feeds, :with_media_only, :ignore_reblog, :accounts_count, :domains_count, :tags_count, :keywords_count
 
   class ListSerializer < ActiveModel::Serializer
     attributes :id, :title
diff --git a/app/serializers/rest/credential_account_serializer.rb b/app/serializers/rest/credential_account_serializer.rb
index 3ee4c251af..d178438023 100644
--- a/app/serializers/rest/credential_account_serializer.rb
+++ b/app/serializers/rest/credential_account_serializer.rb
@@ -19,7 +19,6 @@ class REST::CredentialAccountSerializer < REST::AccountSerializer
       hide_collections: object.hide_collections,
       discoverable: object.discoverable,
       indexable: object.indexable,
-      attribution_domains: object.attribution_domains,
     }
   end
 
diff --git a/app/serializers/rest/filter_serializer.rb b/app/serializers/rest/filter_serializer.rb
index 578cf16d98..02df25226a 100644
--- a/app/serializers/rest/filter_serializer.rb
+++ b/app/serializers/rest/filter_serializer.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class REST::FilterSerializer < ActiveModel::Serializer
-  attributes :id, :title, :exclude_follows, :exclude_localusers, :with_quote, :with_profile, :context, :expires_at, :filter_action
+  attributes :id, :title, :exclude_follows, :exclude_localusers, :with_quote, :with_profile, :context, :expires_at, :filter_action, :filter_action_ex
   has_many :keywords, serializer: REST::FilterKeywordSerializer, if: :rules_requested?
   has_many :statuses, serializer: REST::FilterStatusSerializer, if: :rules_requested?
 
@@ -12,4 +12,14 @@ class REST::FilterSerializer < ActiveModel::Serializer
   def rules_requested?
     instance_options[:rules_requested]
   end
+
+  def filter_action
+    return :warn if object.half_warn_action?
+
+    object.filter_action
+  end
+
+  def filter_action_ex
+    object.filter_action
+  end
 end
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index b786baedfa..4aee406d4c 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -61,9 +61,6 @@ class REST::InstanceSerializer < ActiveModel::Serializer
       urls: {
         streaming: Rails.configuration.x.streaming_api_base_url,
         status: object.status_page_url,
-        about: about_url,
-        privacy_policy: privacy_policy_url,
-        terms_of_service: TermsOfService.live.exists? ? terms_of_service_url : nil,
       },
 
       vapid: {
@@ -84,13 +81,12 @@ class REST::InstanceSerializer < ActiveModel::Serializer
       },
 
       media_attachments: {
-        description_limit: MediaAttachment::MAX_DESCRIPTION_LENGTH,
-        image_matrix_limit: Attachmentable::MAX_MATRIX_LIMIT,
-        image_size_limit: MediaAttachment::IMAGE_LIMIT,
         supported_mime_types: MediaAttachment.supported_mime_types,
+        image_size_limit: MediaAttachment::IMAGE_LIMIT,
+        image_matrix_limit: Attachmentable::MAX_MATRIX_LIMIT,
+        video_size_limit: MediaAttachment::VIDEO_LIMIT,
         video_frame_rate_limit: MediaAttachment::MAX_VIDEO_FRAME_RATE,
         video_matrix_limit: MediaAttachment::MAX_VIDEO_MATRIX_LIMIT,
-        video_size_limit: MediaAttachment::VIDEO_LIMIT,
       },
 
       polls: {
@@ -131,9 +127,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
       enabled: registrations_enabled?,
       approval_required: Setting.registrations_mode == 'approved' || (Setting.registrations_mode == 'open' && !registrations_in_time?),
       limit_reached: Setting.registrations_mode != 'none' && reach_registrations_limit?,
-      reason_required: Setting.registrations_mode == 'approved' && Setting.require_invite_text,
       message: registrations_enabled? ? nil : registrations_message,
-      min_age: Setting.min_age.presence,
       url: ENV.fetch('SSO_ACCOUNT_SIGN_UP', nil),
     }
   end
diff --git a/app/serializers/rest/list_serializer.rb b/app/serializers/rest/list_serializer.rb
index 826daeb847..2415fe7283 100644
--- a/app/serializers/rest/list_serializer.rb
+++ b/app/serializers/rest/list_serializer.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class REST::ListSerializer < ActiveModel::Serializer
-  attributes :id, :title, :replies_policy, :exclusive, :notify, :favourite
+  attributes :id, :title, :replies_policy, :exclusive, :notify
 
   def id
     object.id.to_s
diff --git a/app/serializers/rest/notification_group_serializer.rb b/app/serializers/rest/notification_group_serializer.rb
index f179f13169..32b7ed25bc 100644
--- a/app/serializers/rest/notification_group_serializer.rb
+++ b/app/serializers/rest/notification_group_serializer.rb
@@ -14,7 +14,6 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
   belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer
   belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer
   has_one :list, if: :list_status_type?, serializer: REST::ListSerializer
-  belongs_to :generated_annual_report, key: :annual_report, if: :annual_report_event?, serializer: REST::AnnualReportEventSerializer
 
   def sample_account_ids
     object.sample_accounts.pluck(:id).map(&:to_s)
@@ -48,10 +47,6 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
     object.type == :moderation_warning
   end
 
-  def annual_report_event?
-    object.type == :annual_report
-  end
-
   def page_min_id
     object.pagination_data[:min_id].to_s
   end
diff --git a/app/serializers/rest/scheduled_status_serializer.rb b/app/serializers/rest/scheduled_status_serializer.rb
index 7c54f39c0d..5d6311b872 100644
--- a/app/serializers/rest/scheduled_status_serializer.rb
+++ b/app/serializers/rest/scheduled_status_serializer.rb
@@ -8,4 +8,8 @@ class REST::ScheduledStatusSerializer < ActiveModel::Serializer
   def id
     object.id.to_s
   end
+
+  def params
+    object.params.without(:application_id)
+  end
 end
diff --git a/app/serializers/rest/status_edit_serializer.rb b/app/serializers/rest/status_edit_serializer.rb
index 8d8923bf14..476e946a14 100644
--- a/app/serializers/rest/status_edit_serializer.rb
+++ b/app/serializers/rest/status_edit_serializer.rb
@@ -5,8 +5,7 @@ class REST::StatusEditSerializer < ActiveModel::Serializer
 
   has_one :account, serializer: REST::AccountSerializer
 
-  attributes :content, :spoiler_text, :sensitive, :created_at
-  attribute :markdown_opt, key: :markdown
+  attributes :content, :spoiler_text, :markdown, :sensitive, :created_at
 
   has_many :ordered_media_attachments, key: :media_attachments, serializer: REST::MediaAttachmentSerializer
   has_many :emojis, serializer: REST::CustomEmojiSlimSerializer
@@ -20,8 +19,4 @@ class REST::StatusEditSerializer < ActiveModel::Serializer
   def poll
     { options: object.poll_options.map { |title| { title: title } } }
   end
-
-  def markdown_opt
-    object.markdown
-  end
 end
diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb
index 21fb3ae399..79f7adcf16 100644
--- a/app/serializers/rest/status_serializer.rb
+++ b/app/serializers/rest/status_serializer.rb
@@ -7,7 +7,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
 
   attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
              :sensitive, :spoiler_text, :visibility, :visibility_ex, :limited_scope, :language,
-             :uri, :url, :replies_count, :reblogs_count, :searchability,
+             :uri, :url, :replies_count, :reblogs_count, :searchability, :markdown,
              :status_reference_ids, :status_references_count, :status_referred_by_count,
              :favourites_count, :emoji_reactions, :emoji_reactions_count, :reactions, :edited_at
 
@@ -19,7 +19,6 @@ class REST::StatusSerializer < ActiveModel::Serializer
   attribute :reactions, if: :reactions?
   attribute :expires_at, if: :will_expire?
   attribute :quote_id, if: :quote?
-  attribute :markdown_opt, key: :markdown
   has_many :filtered, serializer: REST::FilterResultSerializer, if: :current_user?
 
   attribute :content, unless: :source_requested?
@@ -119,10 +118,6 @@ class REST::StatusSerializer < ActiveModel::Serializer
     ActivityPub::TagManager.instance.url_for(object)
   end
 
-  def markdown_opt
-    object.markdown
-  end
-
   def status_reference_ids
     @status_reference_ids = object.reference_objects.pluck(:target_status_id)
   end
@@ -132,11 +127,11 @@ class REST::StatusSerializer < ActiveModel::Serializer
   end
 
   def reblogs_count
-    object.untrusted_reblogs_count || relationships&.attributes_map&.dig(object.id, :reblogs_count) || object.reblogs_count
+    relationships&.attributes_map&.dig(object.id, :reblogs_count) || object.reblogs_count
   end
 
   def favourites_count
-    object.untrusted_favourites_count || relationships&.attributes_map&.dig(object.id, :favourites_count) || object.favourites_count
+    relationships&.attributes_map&.dig(object.id, :favourites_count) || object.favourites_count
   end
 
   def favourited
diff --git a/app/serializers/rest/tag_serializer.rb b/app/serializers/rest/tag_serializer.rb
index a2bcb5fd1f..017b572718 100644
--- a/app/serializers/rest/tag_serializer.rb
+++ b/app/serializers/rest/tag_serializer.rb
@@ -3,14 +3,10 @@
 class REST::TagSerializer < ActiveModel::Serializer
   include RoutingHelper
 
-  attributes :id, :name, :url, :history
+  attributes :name, :url, :history
 
   attribute :following, if: :current_user?
 
-  def id
-    object.id.to_s
-  end
-
   def url
     tag_url(object)
   end
diff --git a/app/serializers/rest/terms_of_service_serializer.rb b/app/serializers/rest/terms_of_service_serializer.rb
deleted file mode 100644
index c6a6f60aa5..0000000000
--- a/app/serializers/rest/terms_of_service_serializer.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-class REST::TermsOfServiceSerializer < ActiveModel::Serializer
-  attributes :effective_date, :effective, :content, :succeeded_by
-
-  def effective_date
-    (object.effective_date || object.published_at).iso8601
-  end
-
-  def effective
-    object.effective?
-  end
-
-  def succeeded_by
-    object.succeeded_by&.effective_date&.iso8601
-  end
-
-  def content
-    markdown.render(object.text.gsub(/%{domain}/, Rails.configuration.x.local_domain))
-  end
-
-  private
-
-  def markdown
-    @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, escape_html: true, no_images: true)
-  end
-end
diff --git a/app/serializers/rest/v1/instance_serializer.rb b/app/serializers/rest/v1/instance_serializer.rb
index 12c7c2d57b..d8c79d7018 100644
--- a/app/serializers/rest/v1/instance_serializer.rb
+++ b/app/serializers/rest/v1/instance_serializer.rb
@@ -50,6 +50,14 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
     { streaming_api: Rails.configuration.x.streaming_api_base_url }
   end
 
+  def usage
+    {
+      users: {
+        active_month: instance_presenter.active_user_count(4),
+      },
+    }
+  end
+
   def configuration
     {
       accounts: {
diff --git a/app/serializers/rest/web_push_subscription_serializer.rb b/app/serializers/rest/web_push_subscription_serializer.rb
index 01825a3bb0..674a2d5a86 100644
--- a/app/serializers/rest/web_push_subscription_serializer.rb
+++ b/app/serializers/rest/web_push_subscription_serializer.rb
@@ -1,12 +1,10 @@
 # frozen_string_literal: true
 
 class REST::WebPushSubscriptionSerializer < ActiveModel::Serializer
-  attributes :id, :endpoint, :standard, :alerts, :server_key, :policy
-
-  delegate :standard, to: :object
+  attributes :id, :endpoint, :alerts, :server_key, :policy
 
   def alerts
-    (object.data&.dig('alerts') || {}).transform_values { |v| ActiveModel::Type::Boolean.new.cast(v) }
+    (object.data&.dig('alerts') || {}).each_with_object({}) { |(k, v), h| h[k] = ActiveModel::Type::Boolean.new.cast(v) }
   end
 
   def server_key
diff --git a/app/serializers/webfinger_serializer.rb b/app/serializers/webfinger_serializer.rb
index 1cb3175c07..b67cd2771a 100644
--- a/app/serializers/webfinger_serializer.rb
+++ b/app/serializers/webfinger_serializer.rb
@@ -13,7 +13,7 @@ class WebfingerSerializer < ActiveModel::Serializer
     if object.instance_actor?
       [instance_actor_url]
     else
-      [short_account_url(object), ActivityPub::TagManager.instance.uri_for(object)]
+      [short_account_url(object), account_url(object)]
     end
   end
 
@@ -43,6 +43,6 @@ class WebfingerSerializer < ActiveModel::Serializer
   end
 
   def self_href
-    ActivityPub::TagManager.instance.uri_for(object)
+    object.instance_actor? ? instance_actor_url : account_url(object)
   end
 end
diff --git a/app/services/activitypub/fetch_all_replies_service.rb b/app/services/activitypub/fetch_all_replies_service.rb
deleted file mode 100644
index 765e5c8ae8..0000000000
--- a/app/services/activitypub/fetch_all_replies_service.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-class ActivityPub::FetchAllRepliesService < ActivityPub::FetchRepliesService
-  include JsonLdHelper
-
-  # Limit of replies to fetch per status
-  MAX_REPLIES = (ENV['FETCH_REPLIES_MAX_SINGLE'] || 500).to_i
-
-  def call(status_uri, collection_or_uri, max_pages: 1, request_id: nil)
-    @status_uri = status_uri
-
-    super
-  end
-
-  private
-
-  def filter_replies(items)
-    # Find all statuses that we *shouldn't* update the replies for, and use that as a filter.
-    # We don't assume that we have the statuses before they're created,
-    # hence the negative filter -
-    # "keep all these uris except the ones we already have"
-    # instead of
-    # "keep all these uris that match some conditions on existing Status objects"
-    #
-    # Typically we assume the number of replies we *shouldn't* fetch is smaller than the
-    # replies we *should* fetch, so we also minimize the number of uris we should load here.
-    uris = items.map { |item| value_or_id(item) }
-
-    # Expand collection to get replies in the DB that were
-    # - not included in the collection,
-    # - that we have locally
-    # - but we have no local followers and thus don't get updates/deletes for
-    parent_id = Status.where(uri: @status_uri).pick(:id)
-    unless parent_id.nil?
-      unsubscribed_replies = Status
-                             .where.not(uri: uris)
-                             .where(in_reply_to_id: parent_id)
-                             .unsubscribed
-                             .pluck(:uri)
-      uris.concat(unsubscribed_replies)
-    end
-
-    dont_update = Status.where(uri: uris).should_not_fetch_replies.pluck(:uri)
-
-    # touch all statuses that already exist and that we're about to update
-    Status.where(uri: uris).should_fetch_replies.touch_all(:fetched_replies_at)
-
-    # Reject all statuses that we already have in the db
-    uris = (uris - dont_update).take(MAX_REPLIES)
-
-    Rails.logger.debug { "FetchAllRepliesService - #{@collection_or_uri}: Fetching filtered statuses: #{uris}" }
-    uris
-  end
-end
diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb
index 25c62f3be6..89c3a1b6c0 100644
--- a/app/services/activitypub/fetch_featured_collection_service.rb
+++ b/app/services/activitypub/fetch_featured_collection_service.rb
@@ -33,7 +33,7 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService
     return collection_or_uri if collection_or_uri.is_a?(Hash)
     return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
 
-    fetch_resource_without_id_validation(collection_or_uri, local_follower, raise_on_error: :temporary)
+    fetch_resource_without_id_validation(collection_or_uri, local_follower, true)
   end
 
   def process_items(items)
diff --git a/app/services/activitypub/fetch_featured_tags_collection_service.rb b/app/services/activitypub/fetch_featured_tags_collection_service.rb
index ec2422a075..a0b3c6036b 100644
--- a/app/services/activitypub/fetch_featured_tags_collection_service.rb
+++ b/app/services/activitypub/fetch_featured_tags_collection_service.rb
@@ -45,7 +45,7 @@ class ActivityPub::FetchFeaturedTagsCollectionService < BaseService
     return collection_or_uri if collection_or_uri.is_a?(Hash)
     return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
 
-    fetch_resource_without_id_validation(collection_or_uri, local_follower, raise_on_error: :temporary)
+    fetch_resource_without_id_validation(collection_or_uri, local_follower, true)
   end
 
   def process_items(items)
diff --git a/app/services/activitypub/fetch_references_service.rb b/app/services/activitypub/fetch_references_service.rb
index 8f3fce5f22..0c71af58fd 100644
--- a/app/services/activitypub/fetch_references_service.rb
+++ b/app/services/activitypub/fetch_references_service.rb
@@ -39,11 +39,11 @@ class ActivityPub::FetchReferencesService < BaseService
     #
     # Therefore, retry with correct signatures if this fails.
     begin
-      fetch_resource_without_id_validation(collection_or_uri, nil)
+      fetch_resource_without_id_validation(collection_or_uri, nil, true)
     rescue Mastodon::UnexpectedResponseError => e
       raise unless e.response && e.response.code == 401 && Addressable::URI.parse(collection_or_uri).query.present?
 
-      fetch_resource_without_id_validation(collection_or_uri, nil, request_options: { with_query_string: true })
+      fetch_resource_without_id_validation(collection_or_uri, nil, true, request_options: { with_query_string: true })
     end
   end
 end
diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb
index 7173746f2d..6f8882378f 100644
--- a/app/services/activitypub/fetch_remote_status_service.rb
+++ b/app/services/activitypub/fetch_remote_status_service.rb
@@ -13,7 +13,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
 
     @request_id = request_id || "#{Time.now.utc.to_i}-status-#{uri}"
     @json = if prefetched_body.nil?
-              fetch_status(uri, true, on_behalf_of)
+              fetch_resource(uri, true, on_behalf_of)
             else
               body_to_json(prefetched_body, compare_id: uri)
             end
@@ -80,20 +80,4 @@ class ActivityPub::FetchRemoteStatusService < BaseService
   def expected_object_type?
     equals_or_includes_any?(@json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
   end
-
-  def fetch_status(uri, id_is_known, on_behalf_of = nil)
-    begin
-      fetch_resource(uri, id_is_known, on_behalf_of, raise_on_error: :all)
-    rescue Mastodon::UnexpectedResponseError => e
-      return unless e.response.code == 404
-
-      # If this is a 404 from a public status from a remote account, delete it
-      existing_status = Status.remote.find_by(uri: uri)
-      if existing_status&.distributable?
-        Rails.logger.debug { "FetchRemoteStatusService - Got 404 for orphaned status with URI #{uri}, deleting" }
-        Tombstone.find_or_create_by(uri: uri, account: existing_status.account)
-        RemoveStatusService.new.call(existing_status, redraft: false)
-      end
-    end
-  end
 end
diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb
index f2e4f45104..46cab6caf9 100644
--- a/app/services/activitypub/fetch_replies_service.rb
+++ b/app/services/activitypub/fetch_replies_service.rb
@@ -3,59 +3,39 @@
 class ActivityPub::FetchRepliesService < BaseService
   include JsonLdHelper
 
-  # Limit of fetched replies
-  MAX_REPLIES = 5
-
-  def call(reference_uri, collection_or_uri, max_pages: 1, allow_synchronous_requests: true, request_id: nil)
-    @reference_uri = reference_uri
+  def call(parent_status, collection_or_uri, allow_synchronous_requests: true, request_id: nil)
+    @account = parent_status.account
     @allow_synchronous_requests = allow_synchronous_requests
 
-    @items, n_pages = collection_items(collection_or_uri, max_pages: max_pages)
+    @items = collection_items(collection_or_uri)
     return if @items.nil?
 
-    @items = filter_replies(@items)
-    FetchReplyWorker.push_bulk(@items) { |reply_uri| [reply_uri, { 'request_id' => request_id }] }
+    FetchReplyWorker.push_bulk(filtered_replies) { |reply_uri| [reply_uri, { 'request_id' => request_id }] }
 
-    [@items, n_pages]
+    @items
   end
 
   private
 
-  def collection_items(collection_or_uri, max_pages: 1)
+  def collection_items(collection_or_uri)
     collection = fetch_collection(collection_or_uri)
     return unless collection.is_a?(Hash)
 
     collection = fetch_collection(collection['first']) if collection['first'].present?
     return unless collection.is_a?(Hash)
 
-    items = []
-    n_pages = 1
-    while collection.is_a?(Hash)
-      items.concat(as_array(collection_page_items(collection)))
-
-      break if items.size >= MAX_REPLIES
-      break if n_pages >= max_pages
-
-      collection = collection['next'].present? ? fetch_collection(collection['next']) : nil
-      n_pages += 1
-    end
-
-    [items, n_pages]
-  end
-
-  def collection_page_items(collection)
     case collection['type']
     when 'Collection', 'CollectionPage'
-      collection['items']
+      as_array(collection['items'])
     when 'OrderedCollection', 'OrderedCollectionPage'
-      collection['orderedItems']
+      as_array(collection['orderedItems'])
     end
   end
 
   def fetch_collection(collection_or_uri)
     return collection_or_uri if collection_or_uri.is_a?(Hash)
     return unless @allow_synchronous_requests
-    return if non_matching_uri_hosts?(@reference_uri, collection_or_uri)
+    return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
 
     # NOTE: For backward compatibility reasons, Mastodon signs outgoing
     # queries incorrectly by default.
@@ -65,19 +45,19 @@ class ActivityPub::FetchRepliesService < BaseService
     #
     # Therefore, retry with correct signatures if this fails.
     begin
-      fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary)
+      fetch_resource_without_id_validation(collection_or_uri, nil, true)
     rescue Mastodon::UnexpectedResponseError => e
       raise unless e.response && e.response.code == 401 && Addressable::URI.parse(collection_or_uri).query.present?
 
-      fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary, request_options: { omit_query_string: false })
+      fetch_resource_without_id_validation(collection_or_uri, nil, true, request_options: { omit_query_string: false })
     end
   end
 
-  def filter_replies(items)
+  def filtered_replies
     # Only fetch replies to the same server as the original status to avoid
     # amplification attacks.
 
     # Also limit to 5 fetched replies to limit potential for DoS.
-    items.map { |item| value_or_id(item) }.reject { |uri| non_matching_uri_hosts?(@reference_uri, uri) }.take(MAX_REPLIES)
+    @items.map { |item| value_or_id(item) }.reject { |uri| non_matching_uri_hosts?(@account.uri, uri) }.take(5)
   end
 end
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index 5c55defbda..c1d54264a6 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -174,13 +174,13 @@ class ActivityPub::ProcessAccountService < BaseService
     begin
       @account.avatar_remote_url = image_url('icon') || '' unless skip_download?
       @account.avatar = nil if @account.avatar_remote_url.blank?
-    rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
+    rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
       RedownloadAvatarWorker.perform_in(rand(30..600).seconds, @account.id)
     end
     begin
       @account.header_remote_url = image_url('image') || '' unless skip_download?
       @account.header = nil if @account.header_remote_url.blank?
-    rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
+    rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
       RedownloadHeaderWorker.perform_in(rand(30..600).seconds, @account.id)
     end
     @account.statuses_count    = outbox_total_items    if outbox_total_items.present?
@@ -421,7 +421,7 @@ class ActivityPub::ProcessAccountService < BaseService
     total_items = collection.is_a?(Hash) && collection['totalItems'].present? && collection['totalItems'].is_a?(Numeric) ? collection['totalItems'] : nil
     has_first_page = collection.is_a?(Hash) && collection['first'].present?
     @collections[type] = [total_items, has_first_page]
-  rescue *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::LengthValidationError
+  rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::LengthValidationError
     @collections[type] = [nil, nil]
   end
 
diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb
index 10224f4d7e..df0d77d0ed 100644
--- a/app/services/activitypub/process_status_update_service.rb
+++ b/app/services/activitypub/process_status_update_service.rb
@@ -53,7 +53,6 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
         update_immediate_attributes!
         update_metadata!
         validate_status_mentions!
-        update_counts!
         create_edits!
       end
 
@@ -74,7 +73,6 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
     with_redis_lock("create:#{@uri}") do
       update_poll!(allow_significant_changes: false)
       queue_poll_notifications!
-      update_counts!
     end
   end
 
@@ -122,7 +120,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
       media_attachment.download_file! if media_attachment.remote_url_previously_changed?
       media_attachment.download_thumbnail! if media_attachment.thumbnail_remote_url_previously_changed?
       media_attachment.save
-    rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
+    rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
       RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id)
     rescue Seahorse::Client::NetworkingError => e
       Rails.logger.warn "Error storing media attachment: #{e}"
@@ -287,7 +285,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
       account ||= ActivityPub::FetchRemoteAccountService.new.call(href, request_id: @request_id)
 
       account&.id
-    rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
+    rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
       # Since previous mentions are about already-known accounts,
       # they don't try to resolve again and won't fall into this case.
       # In other words, this failure case is only for new mentions and won't
@@ -364,19 +362,6 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
     @local_referred_accounts = local_referred_statuses.map(&:account)
   end
 
-  def update_counts!
-    likes = @status_parser.favourites_count
-    shares =  @status_parser.reblogs_count
-    return if likes.nil? && shares.nil?
-
-    @status.status_stat.tap do |status_stat|
-      status_stat.untrusted_reblogs_count = shares unless shares.nil?
-      status_stat.untrusted_favourites_count = likes unless likes.nil?
-
-      status_stat.save if status_stat.changed?
-    end
-  end
-
   def expected_type?
     equals_or_includes_any?(@json['type'], %w(Note Question))
   end
diff --git a/app/services/activitypub/synchronize_followers_service.rb b/app/services/activitypub/synchronize_followers_service.rb
index fd6fd1b899..f51d671a00 100644
--- a/app/services/activitypub/synchronize_followers_service.rb
+++ b/app/services/activitypub/synchronize_followers_service.rb
@@ -4,43 +4,32 @@ class ActivityPub::SynchronizeFollowersService < BaseService
   include JsonLdHelper
   include Payloadable
 
-  MAX_COLLECTION_PAGES = 10
-
   def call(account, partial_collection_url)
     @account = account
-    @expected_followers_ids = []
 
-    return unless process_collection!(partial_collection_url)
+    items = collection_items(partial_collection_url)
+    return if items.nil?
+
+    # There could be unresolved accounts (hence the call to .compact) but this
+    # should never happen in practice, since in almost all cases we keep an
+    # Account record, and should we not do that, we should have sent a Delete.
+    # In any case there is not much we can do if that occurs.
+    @expected_followers = items.filter_map { |uri| ActivityPub::TagManager.instance.uri_to_resource(uri, Account) }
 
     remove_unexpected_local_followers!
+    handle_unexpected_outgoing_follows!
   end
 
   private
 
-  def process_page!(items)
-    page_expected_followers = extract_local_followers(items)
-    @expected_followers_ids.concat(page_expected_followers.pluck(:id))
-
-    handle_unexpected_outgoing_follows!(page_expected_followers)
-  end
-
-  def extract_local_followers(items)
-    # There could be unresolved accounts (hence the call to .filter_map) but this
-    # should never happen in practice, since in almost all cases we keep an
-    # Account record, and should we not do that, we should have sent a Delete.
-    # In any case there is not much we can do if that occurs.
-
-    ActivityPub::TagManager.instance.uris_to_local_accounts(items)
-  end
-
   def remove_unexpected_local_followers!
-    @account.followers.local.where.not(id: @expected_followers_ids).reorder(nil).find_each do |unexpected_follower|
+    @account.followers.local.where.not(id: @expected_followers.map(&:id)).reorder(nil).find_each do |unexpected_follower|
       UnfollowService.new.call(unexpected_follower, @account)
     end
   end
 
-  def handle_unexpected_outgoing_follows!(expected_followers)
-    expected_followers.each do |expected_follower|
+  def handle_unexpected_outgoing_follows!
+    @expected_followers.each do |expected_follower|
       next if expected_follower.following?(@account)
 
       if expected_follower.requested?(@account)
@@ -61,33 +50,18 @@ class ActivityPub::SynchronizeFollowersService < BaseService
     Oj.dump(serialize_payload(follow, ActivityPub::UndoFollowSerializer))
   end
 
-  # Only returns true if the whole collection has been processed
-  def process_collection!(collection_uri, max_pages: MAX_COLLECTION_PAGES)
-    collection = fetch_collection(collection_uri)
-    return false unless collection.is_a?(Hash)
+  def collection_items(collection_or_uri)
+    collection = fetch_collection(collection_or_uri)
+    return unless collection.is_a?(Hash)
 
     collection = fetch_collection(collection['first']) if collection['first'].present?
+    return unless collection.is_a?(Hash)
 
-    while collection.is_a?(Hash)
-      process_page!(as_array(collection_page_items(collection)))
-
-      max_pages -= 1
-
-      return true if collection['next'].blank? # We reached the end of the collection
-      return false if max_pages <= 0 # We reached our pages limit
-
-      collection = fetch_collection(collection['next'])
-    end
-
-    false
-  end
-
-  def collection_page_items(collection)
     case collection['type']
     when 'Collection', 'CollectionPage'
-      collection['items']
+      as_array(collection['items'])
     when 'OrderedCollection', 'OrderedCollectionPage'
-      collection['orderedItems']
+      as_array(collection['orderedItems'])
     end
   end
 
@@ -95,6 +69,6 @@ class ActivityPub::SynchronizeFollowersService < BaseService
     return collection_or_uri if collection_or_uri.is_a?(Hash)
     return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
 
-    fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary)
+    fetch_resource_without_id_validation(collection_or_uri, nil, true)
   end
 end
diff --git a/app/services/add_accounts_to_list_service.rb b/app/services/add_accounts_to_list_service.rb
deleted file mode 100644
index df4e4c8314..0000000000
--- a/app/services/add_accounts_to_list_service.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-class AddAccountsToListService < BaseService
-  def call(list, accounts)
-    @list = list
-    @accounts = accounts
-
-    return if @accounts.empty?
-
-    update_list!
-    merge_into_list!
-  end
-
-  private
-
-  def update_list!
-    ApplicationRecord.transaction do
-      @accounts.each do |account|
-        @list.accounts << account
-      end
-    end
-  end
-
-  def merge_into_list!
-    MergeWorker.push_bulk(merge_account_ids) do |account_id|
-      [account_id, @list.id, 'list']
-    end
-  end
-
-  def merge_account_ids
-    ListAccount.where(list: @list, account: @accounts).where.not(follow_id: nil).pluck(:account_id)
-  end
-end
diff --git a/app/services/app_sign_up_service.rb b/app/services/app_sign_up_service.rb
index a4399efd65..7665880115 100644
--- a/app/services/app_sign_up_service.rb
+++ b/app/services/app_sign_up_service.rb
@@ -41,7 +41,7 @@ class AppSignUpService < BaseService
   end
 
   def user_params
-    @params.slice(:email, :password, :agreement, :locale, :time_zone, :invite_code, :date_of_birth)
+    @params.slice(:email, :password, :agreement, :locale, :time_zone, :invite_code)
   end
 
   def account_params
diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb
index 0bf5a2ecbb..8bc90968b7 100644
--- a/app/services/backup_service.rb
+++ b/app/services/backup_service.rb
@@ -6,8 +6,6 @@ class BackupService < BaseService
   include Payloadable
   include ContextHelper
 
-  CHUNK_SIZE = 1.megabyte
-
   attr_reader :account, :backup
 
   def call(backup)
@@ -183,6 +181,8 @@ class BackupService < BaseService
     ).as_json
   end
 
+  CHUNK_SIZE = 1.megabyte
+
   def download_to_zip(zipfile, attachment, filename)
     adapter = Paperclip.io_adapters.for(attachment)
 
diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb
index d03b889c12..f5c5cb7109 100644
--- a/app/services/batched_remove_status_service.rb
+++ b/app/services/batched_remove_status_service.rb
@@ -101,7 +101,7 @@ class BatchedRemoveStatusService < BaseService
       pipeline.publish(status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', payload)
     end
 
-    status.tags.map { |tag| tag.name.downcase }.each do |hashtag|
+    status.tags.map { |tag| tag.name.mb_chars.downcase }.each do |hashtag|
       pipeline.publish("timeline:hashtag:#{hashtag}", payload)
       pipeline.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local?
     end
diff --git a/app/services/block_domain_service.rb b/app/services/block_domain_service.rb
index 1db15d9ccb..0c5bfaefeb 100644
--- a/app/services/block_domain_service.rb
+++ b/app/services/block_domain_service.rb
@@ -50,14 +50,10 @@ class BlockDomainService < BaseService
   def notify_of_severed_relationships!
     return if @domain_block_event.nil?
 
-    # find_in_batches and perform_bulk both default to batches of 1000
-    @domain_block_event.affected_local_accounts.reorder(nil).find_in_batches do |accounts|
-      notification_jobs_args = accounts.map do |account|
-        event = AccountRelationshipSeveranceEvent.create!(account:, relationship_severance_event: @domain_block_event)
-        [account.id, event.id, 'AccountRelationshipSeveranceEvent', 'severed_relationships']
-      end
-
-      LocalNotificationWorker.perform_bulk(notification_jobs_args)
+    # TODO: check how efficient that query is, also check `push_bulk`/`perform_bulk`
+    @domain_block_event.affected_local_accounts.reorder(nil).find_each do |account|
+      event = AccountRelationshipSeveranceEvent.create!(account: account, relationship_severance_event: @domain_block_event)
+      LocalNotificationWorker.perform_async(account.id, event.id, 'AccountRelationshipSeveranceEvent', 'severed_relationships')
     end
   end
 
diff --git a/app/services/concerns/payloadable.rb b/app/services/concerns/payloadable.rb
index 42ed994a7f..113b389fac 100644
--- a/app/services/concerns/payloadable.rb
+++ b/app/services/concerns/payloadable.rb
@@ -17,9 +17,9 @@ module Payloadable
     always_sign_unsafe = options.delete(:always_sign_unsafe)
     payload     = ActiveModelSerializers::SerializableResource.new(record, options.merge(serializer: serializer, adapter: ActivityPub::Adapter)).as_json
     object      = record.respond_to?(:virtual_object) ? record.virtual_object : record
-    bearcap     = object.is_a?(String) && record.respond_to?(:type) && ['Create', 'Update'].include?(record.type)
+    bearcap     = object.is_a?(String) && record.respond_to?(:type) && (record.type == 'Create' || record.type == 'Update')
 
-    if (object.respond_to?(:sign?) && object.sign? && signer && (always_sign || signing_enabled?)) || bearcap || (signer && always_sign_unsafe)
+    if ((object.respond_to?(:sign?) && object.sign?) && signer && (always_sign || signing_enabled?)) || bearcap || (signer && always_sign_unsafe)
       ActivityPub::LinkedDataSignature.new(payload).sign!(signer, sign_with: sign_with)
     else
       payload
diff --git a/app/services/delete_account_service.rb b/app/services/delete_account_service.rb
index 080bb922af..b8921a6fec 100644
--- a/app/services/delete_account_service.rb
+++ b/app/services/delete_account_service.rb
@@ -65,7 +65,7 @@ class DeleteAccountService < BaseService
     scheduled_expiration_statuses
     status_pins
     tag_follows
-  ).freeze
+  )
 
   ASSOCIATIONS_ON_DESTROY = %w(
     reports
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index 590c7c8e82..f61fb60174 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -177,8 +177,8 @@ class FanOutOnWriteService < BaseService
 
   def broadcast_to_hashtag_streams!
     @status.tags.map(&:name).each do |hashtag|
-      redis.publish("timeline:hashtag:#{hashtag.downcase}", anonymous_payload)
-      redis.publish("timeline:hashtag:#{hashtag.downcase}:local", anonymous_payload) if @status.local? && Setting.enable_local_timeline
+      redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}", anonymous_payload)
+      redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}:local", anonymous_payload) if @status.local? && Setting.enable_local_timeline
     end
   end
 
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index 4f1ff0649a..c7af29d6e0 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -30,7 +30,7 @@ class FetchLinkCardService < BaseService
     end
 
     attach_card if @card&.persisted?
-  rescue *Mastodon::HTTP_CONNECTION_ERRORS, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Encoding::UndefinedConversionError, ActiveRecord::RecordInvalid => e
+  rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Encoding::UndefinedConversionError, ActiveRecord::RecordInvalid => e
     Rails.logger.debug { "Error fetching link #{@original_url}: #{e}" }
     nil
   end
diff --git a/app/services/fetch_resource_service.rb b/app/services/fetch_resource_service.rb
index 3fde78455c..b69015a5e9 100644
--- a/app/services/fetch_resource_service.rb
+++ b/app/services/fetch_resource_service.rb
@@ -12,7 +12,7 @@ class FetchResourceService < BaseService
     return if url.blank?
 
     process(url)
-  rescue *Mastodon::HTTP_CONNECTION_ERRORS, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
+  rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
     Rails.logger.debug { "Error fetching resource #{@url}: #{e}" }
     nil
   end
@@ -74,7 +74,7 @@ class FetchResourceService < BaseService
 
   def process_html(response)
     page      = Nokogiri::HTML5(response.body_with_limit)
-    json_link = page.xpath('//link[nokogiri:link_rel_include(@rel, "alternate")]', NokogiriHandler).find { |link| ACTIVITY_STREAM_LINK_TYPES.include?(link['type']) }
+    json_link = page.xpath('//link[@rel="alternate"]').find { |link| ACTIVITY_STREAM_LINK_TYPES.include?(link['type']) }
 
     process(json_link['href'], terminal: true) unless json_link.nil?
   end
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
index 1ab930e61c..f269916c4d 100644
--- a/app/services/follow_service.rb
+++ b/app/services/follow_service.rb
@@ -84,10 +84,7 @@ class FollowService < BaseService
     follow = @source_account.follow!(@target_account, **follow_options.merge(rate_limit: @options[:with_rate_limit], bypass_limit: @options[:bypass_limit]))
 
     LocalNotificationWorker.perform_async(@target_account.id, follow.id, follow.class.name, 'follow')
-    MergeWorker.perform_async(@target_account.id, @source_account.id, 'home')
-    MergeWorker.push_bulk(List.where(account: @source_account).joins(:list_accounts).where(list_accounts: { account_id: @target_account.id }).pluck(:id)) do |list_id|
-      [@target_account.id, list_id, 'list']
-    end
+    MergeWorker.perform_async(@target_account.id, @source_account.id)
 
     follow
   end
diff --git a/app/services/import_service.rb b/app/services/import_service.rb
new file mode 100644
index 0000000000..6dafb5a0bb
--- /dev/null
+++ b/app/services/import_service.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+require 'csv'
+
+# NOTE: This is a deprecated service, only kept to not break ongoing imports
+# on upgrade. See `BulkImportService` for its replacement.
+
+class ImportService < BaseService
+  ROWS_PROCESSING_LIMIT = 20_000
+
+  def call(import)
+    @import  = import
+    @account = @import.account
+
+    case @import.type
+    when 'following'
+      import_follows!
+    when 'blocking'
+      import_blocks!
+    when 'muting'
+      import_mutes!
+    when 'domain_blocking'
+      import_domain_blocks!
+    when 'bookmarks'
+      import_bookmarks!
+    end
+  end
+
+  private
+
+  def import_follows!
+    parse_import_data!(['Account address'])
+    import_relationships!('follow', 'unfollow', @account.following, ROWS_PROCESSING_LIMIT, reblogs: { header: 'Show boosts', default: true }, notify: { header: 'Notify on new posts', default: false }, languages: { header: 'Languages', default: nil })
+  end
+
+  def import_blocks!
+    parse_import_data!(['Account address'])
+    import_relationships!('block', 'unblock', @account.blocking, ROWS_PROCESSING_LIMIT)
+  end
+
+  def import_mutes!
+    parse_import_data!(['Account address'])
+    import_relationships!('mute', 'unmute', @account.muting, ROWS_PROCESSING_LIMIT, notifications: { header: 'Hide notifications', default: true })
+  end
+
+  def import_domain_blocks!
+    parse_import_data!(['#domain'])
+    items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#domain'].strip }
+
+    if @import.overwrite?
+      presence_hash = items.index_with(true)
+
+      @account.domain_blocks.find_each do |domain_block|
+        if presence_hash[domain_block.domain]
+          items.delete(domain_block.domain)
+        else
+          @account.unblock_domain!(domain_block.domain)
+        end
+      end
+    end
+
+    items.each do |domain|
+      @account.block_domain!(domain)
+    end
+
+    AfterAccountDomainBlockWorker.push_bulk(items) do |domain|
+      [@account.id, domain]
+    end
+  end
+
+  def import_relationships!(action, undo_action, overwrite_scope, limit, extra_fields = {})
+    local_domain_suffix = "@#{Rails.configuration.x.local_domain}"
+    items = @data.take(limit).map { |row| [row['Account address']&.strip&.delete_suffix(local_domain_suffix), extra_fields.to_h { |key, field_settings| [key, row[field_settings[:header]]&.strip || field_settings[:default]] }] }.reject { |(id, _)| id.blank? }
+
+    if @import.overwrite?
+      presence_hash = items.each_with_object({}) { |(id, extra), mapping| mapping[id] = [true, extra] }
+
+      overwrite_scope.reorder(nil).find_each do |target_account|
+        if presence_hash[target_account.acct]
+          items.delete(target_account.acct)
+          extra = presence_hash[target_account.acct][1]
+          Import::RelationshipWorker.perform_async(@account.id, target_account.acct, action, extra.stringify_keys)
+        else
+          Import::RelationshipWorker.perform_async(@account.id, target_account.acct, undo_action)
+        end
+      end
+    end
+
+    head_items = items.uniq { |acct, _| acct.split('@')[1] }
+    tail_items = items - head_items
+
+    Import::RelationshipWorker.push_bulk(head_items + tail_items) do |acct, extra|
+      [@account.id, acct, action, extra.stringify_keys]
+    end
+  end
+
+  def import_bookmarks!
+    parse_import_data!(['#uri'])
+    items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#uri'].strip }
+
+    if @import.overwrite?
+      presence_hash = items.index_with(true)
+
+      @account.bookmarks.find_each do |bookmark|
+        if presence_hash[bookmark.status.uri]
+          items.delete(bookmark.status.uri)
+        else
+          bookmark.destroy!
+        end
+      end
+    end
+
+    statuses = items.filter_map do |uri|
+      status = ActivityPub::TagManager.instance.uri_to_resource(uri, Status)
+      next if status.nil? && ActivityPub::TagManager.instance.local_uri?(uri)
+
+      status || ActivityPub::FetchRemoteStatusService.new.call(uri)
+    rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::UnexpectedResponseError
+      nil
+    rescue => e
+      Rails.logger.warn "Unexpected error when importing bookmark: #{e}"
+      nil
+    end
+
+    account_ids         = statuses.map(&:account_id)
+    preloaded_relations = @account.relations_map(account_ids, skip_blocking_and_muting: true)
+
+    statuses.keep_if { |status| StatusPolicy.new(@account, status, preloaded_relations).show? }
+
+    statuses.each do |status|
+      @account.bookmarks.find_or_create_by!(account: @account, status: status)
+    end
+  end
+
+  def parse_import_data!(default_headers)
+    data = CSV.parse(import_data, headers: true)
+    data = CSV.parse(import_data, headers: default_headers) unless data.headers&.first&.strip&.include?(' ')
+    @data = data.compact_blank
+  end
+
+  def import_data
+    Paperclip.io_adapters.for(@import.data).read.force_encoding(Encoding::UTF_8)
+  end
+end
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index ddc767274e..473205ec57 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -3,7 +3,7 @@
 class NotifyService < BaseService
   include Redisable
 
-  # TODO: the severed_relationships and annual_report types probably warrants email notifications
+  # TODO: the severed_relationships type probably warrants email notifications
   NON_EMAIL_TYPES = %i(
     admin.report
     admin.sign_up
@@ -15,7 +15,6 @@ class NotifyService < BaseService
     list_status
     moderation_warning
     severed_relationships
-    annual_report
   ).freeze
 
   class BaseCondition
@@ -29,7 +28,6 @@ class NotifyService < BaseService
       poll
       update
       account_warning
-      annual_report
     ).freeze
 
     def initialize(notification)
@@ -114,7 +112,7 @@ class NotifyService < BaseService
   class DropCondition < BaseCondition
     def drop?
       blocked   = @recipient.unavailable?
-      blocked ||= from_self? && %i(poll severed_relationships moderation_warning annual_report).exclude?(@notification.type)
+      blocked ||= from_self? && %i(poll severed_relationships moderation_warning).exclude?(@notification.type)
 
       return blocked if message? && from_staff?
 
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 61409f7e71..44ba50ae39 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -6,6 +6,8 @@ class PostStatusService < BaseService
   include DtlHelper
   include NgRuleHelper
 
+  MIN_SCHEDULE_OFFSET = 5.minutes.freeze
+
   class UnexpectedMentionsError < StandardError
     attr_reader :accounts
 
@@ -42,8 +44,6 @@ class PostStatusService < BaseService
     @text        = @options[:text] || ''
     @in_reply_to = @options[:thread]
 
-    @antispam = Antispam.new
-
     return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
 
     validate_status!
@@ -64,8 +64,6 @@ class PostStatusService < BaseService
     end
 
     @status
-  rescue Antispam::SilentlyDrop => e
-    e.status
   end
 
   private
@@ -164,7 +162,6 @@ class PostStatusService < BaseService
     @status.limited_scope = :personal if @status.limited_visibility? && !@status.reply_limited? && !process_mentions_service.mentions?
 
     UpdateStatusExpirationService.new.call(@status)
-    @antispam.local_preflight_check!(@status)
 
     # The following transaction block is needed to wrap the UPDATEs to
     # the media attachments when the status is created
@@ -187,7 +184,6 @@ class PostStatusService < BaseService
 
   def schedule_status!
     status_for_validation = @account.statuses.build(status_attributes)
-    @antispam.local_preflight_check!(status_for_validation)
 
     if status_for_validation.valid?
       # Marking the status as destroyed is necessary to prevent the status from being
@@ -204,8 +200,6 @@ class PostStatusService < BaseService
     else
       raise ActiveRecord::RecordInvalid
     end
-  rescue Antispam::SilentlyDrop
-    @status = @account.scheduled_status.new(scheduled_status_attributes).tap(&:delete)
   end
 
   def postprocess_status!
diff --git a/app/services/precompute_feed_service.rb b/app/services/precompute_feed_service.rb
index a591c90913..86aad50983 100644
--- a/app/services/precompute_feed_service.rb
+++ b/app/services/precompute_feed_service.rb
@@ -3,21 +3,13 @@
 class PrecomputeFeedService < BaseService
   include Redisable
 
-  def call(account, skip_filled_timelines: false)
-    @skip_filled_timelines = skip_filled_timelines
-
-    FeedManager.instance.populate_home(account) unless skip_timeline?(:home, account.id)
+  def call(account)
+    FeedManager.instance.populate_home(account)
 
     account.owned_lists.each do |list|
-      FeedManager.instance.populate_list(list) unless skip_timeline?(:list, list.id)
+      FeedManager.instance.populate_list(list)
     end
   ensure
     redis.del("account:#{account.id}:regeneration")
   end
-
-  private
-
-  def skip_timeline?(type, id)
-    @skip_filled_timelines && FeedManager.instance.timeline_size(type, id) * 2 > FeedManager::MAX_ITEMS
-  end
 end
diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb
index a8d95a53d7..2332d1d8a2 100644
--- a/app/services/process_mentions_service.rb
+++ b/app/services/process_mentions_service.rb
@@ -50,7 +50,7 @@ class ProcessMentionsService < BaseService
       if mention_undeliverable?(mentioned_account)
         begin
           mentioned_account = ResolveAccountService.new.call(Regexp.last_match(1))
-        rescue Webfinger::Error, *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::UnexpectedResponseError
+        rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::UnexpectedResponseError
           mentioned_account = nil
         end
       end
diff --git a/app/services/remove_accounts_from_list_service.rb b/app/services/remove_accounts_from_list_service.rb
deleted file mode 100644
index bd5b7c439e..0000000000
--- a/app/services/remove_accounts_from_list_service.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-class RemoveAccountsFromListService < BaseService
-  def call(list, accounts)
-    @list = list
-    @accounts = accounts
-
-    return if @accounts.empty?
-
-    unmerge_from_list!
-    update_list!
-  end
-
-  private
-
-  def update_list!
-    ListAccount.where(list: @list, account: @accounts).destroy_all
-  end
-
-  def unmerge_from_list!
-    UnmergeWorker.push_bulk(unmerge_account_ids) do |account_id|
-      [account_id, @list.id, 'list']
-    end
-  end
-
-  def unmerge_account_ids
-    ListAccount.where(list: @list, account: @accounts).where.not(follow_id: nil).pluck(:account_id)
-  end
-end
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index 5cc28481a7..eb07152ff8 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -154,8 +154,8 @@ class RemoveStatusService < BaseService
     return if skip_streaming?
 
     @status.tags.map(&:name).each do |hashtag|
-      redis.publish("timeline:hashtag:#{hashtag.downcase}", @payload)
-      redis.publish("timeline:hashtag:#{hashtag.downcase}:local", @payload) if @status.local?
+      redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}", @payload)
+      redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}:local", @payload) if @status.local?
     end
   end
 
diff --git a/app/services/software_update_check_service.rb b/app/services/software_update_check_service.rb
index bafe259cde..8c95ca546e 100644
--- a/app/services/software_update_check_service.rb
+++ b/app/services/software_update_check_service.rb
@@ -12,7 +12,7 @@ class SoftwareUpdateCheckService < BaseService
 
   def clean_outdated_updates!
     SoftwareUpdate.find_each do |software_update|
-      software_update.delete if software_update.outdated?
+      software_update.delete if Mastodon::Version.gem_version >= software_update.gem_version
     rescue ArgumentError
       software_update.delete
     end
@@ -22,12 +22,12 @@ class SoftwareUpdateCheckService < BaseService
     Request.new(:get, "#{api_url}?version=#{version}").add_headers('Accept' => 'application/json', 'User-Agent' => 'Mastodon update checker').perform do |res|
       return Oj.load(res.body_with_limit, mode: :strict) if res.code == 200
     end
-  rescue *Mastodon::HTTP_CONNECTION_ERRORS, Oj::ParseError
+  rescue HTTP::Error, OpenSSL::SSL::SSLError, Oj::ParseError
     nil
   end
 
   def api_url
-    Rails.configuration.x.mastodon.software_update_url
+    ENV.fetch('UPDATE_CHECK_URL', 'https://kmy.blue/update-check')
   end
 
   def version
diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb
index 3934a738f7..8d5446f1a8 100644
--- a/app/services/suspend_account_service.rb
+++ b/app/services/suspend_account_service.rb
@@ -20,7 +20,7 @@ class SuspendAccountService < BaseService
   private
 
   def reject_remote_follows!
-    return if @account.local? || !@account.activitypub? || @account.suspension_origin_remote?
+    return if @account.local? || !@account.activitypub?
 
     # When suspending a remote account, the account obviously doesn't
     # actually become suspended on its origin server, i.e. unlike a
@@ -95,7 +95,7 @@ class SuspendAccountService < BaseService
             end
           end
 
-          CacheBusterWorker.perform_async(attachment.url(style)) if Rails.configuration.x.cache_buster_enabled
+          CacheBusterWorker.perform_async(attachment.path(style)) if Rails.configuration.x.cache_buster_enabled
         end
       end
     end
diff --git a/app/services/unallow_domain_service.rb b/app/services/unallow_domain_service.rb
index bbe9571450..bdc71b1c08 100644
--- a/app/services/unallow_domain_service.rb
+++ b/app/services/unallow_domain_service.rb
@@ -12,7 +12,7 @@ class UnallowDomainService < BaseService
   private
 
   def suspend_accounts!(domain)
-    Account.where(domain: domain).in_batches.touch_all(:suspended_at)
+    Account.where(domain: domain).in_batches.update_all(suspended_at: Time.now.utc)
     AfterUnallowDomainWorker.perform_async(domain)
   end
 end
diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb
index b3f2cd66f6..fe9a7f0d87 100644
--- a/app/services/unfollow_service.rb
+++ b/app/services/unfollow_service.rb
@@ -31,13 +31,7 @@ class UnfollowService < BaseService
 
     create_notification(follow) if !@target_account.local? && @target_account.activitypub?
     create_reject_notification(follow) if @target_account.local? && !@source_account.local? && @source_account.activitypub?
-
-    unless @options[:skip_unmerge]
-      UnmergeWorker.perform_async(@target_account.id, @source_account.id, 'home')
-      UnmergeWorker.push_bulk(List.where(account: @source_account).joins(:list_accounts).where(list_accounts: { account_id: @target_account.id }).pluck(:list_id)) do |list_id|
-        [@target_account.id, list_id, 'list']
-      end
-    end
+    UnmergeWorker.perform_async(@target_account.id, @source_account.id) unless @options[:skip_unmerge]
 
     follow
   end
diff --git a/app/services/unmute_service.rb b/app/services/unmute_service.rb
index 9262961f7d..6aeea358f7 100644
--- a/app/services/unmute_service.rb
+++ b/app/services/unmute_service.rb
@@ -6,12 +6,6 @@ class UnmuteService < BaseService
 
     account.unmute!(target_account)
 
-    if account.following?(target_account)
-      MergeWorker.perform_async(target_account.id, account.id, 'home')
-
-      MergeWorker.push_bulk(List.where(account: account).joins(:list_accounts).where(list_accounts: { account_id: target_account.id }).pluck(:id)) do |list_id|
-        [target_account.id, list_id, 'list']
-      end
-    end
+    MergeWorker.perform_async(target_account.id, account.id) if account.following?(target_account)
   end
 end
diff --git a/app/services/unsuspend_account_service.rb b/app/services/unsuspend_account_service.rb
index 7d3bb806a6..652dd6a845 100644
--- a/app/services/unsuspend_account_service.rb
+++ b/app/services/unsuspend_account_service.rb
@@ -91,7 +91,7 @@ class UnsuspendAccountService < BaseService
             end
           end
 
-          CacheBusterWorker.perform_async(attachment.url(style)) if Rails.configuration.x.cache_buster_enabled
+          CacheBusterWorker.perform_async(attachment.path(style)) if Rails.configuration.x.cache_buster_enabled
         end
       end
     end
diff --git a/app/services/update_status_expiration_service.rb b/app/services/update_status_expiration_service.rb
index 8f0b210c36..2dc533c530 100644
--- a/app/services/update_status_expiration_service.rb
+++ b/app/services/update_status_expiration_service.rb
@@ -27,6 +27,6 @@ class UpdateStatusExpirationService < BaseService
     expired_at = base_time + due
     expired_status = ScheduledExpirationStatus.create!(account: status.account, status: status, scheduled_at: expired_at)
 
-    RemoveExpiredStatusWorker.perform_at(expired_at, expired_status.id) if due < ScheduledStatus::MINIMUM_OFFSET
+    RemoveExpiredStatusWorker.perform_at(expired_at, expired_status.id) if due < PostStatusService::MIN_SCHEDULE_OFFSET
   end
 end
diff --git a/app/services/verify_link_service.rb b/app/services/verify_link_service.rb
index fc3c4cbc28..c4f4191e1f 100644
--- a/app/services/verify_link_service.rb
+++ b/app/services/verify_link_service.rb
@@ -10,7 +10,7 @@ class VerifyLinkService < BaseService
     return unless link_back_present?
 
     field.mark_verified!
-  rescue *Mastodon::HTTP_CONNECTION_ERRORS, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, IPAddr::AddressFamilyError => e
+  rescue OpenSSL::SSL::SSLError, HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, IPAddr::AddressFamilyError => e
     Rails.logger.debug { "Error fetching link #{@url}: #{e}" }
     nil
   end
@@ -26,7 +26,7 @@ class VerifyLinkService < BaseService
   def link_back_present?
     return false if @body.blank?
 
-    links = Nokogiri::HTML5(@body).xpath('(//a|//link)[@rel][nokogiri:link_rel_include(@rel, "me")]', NokogiriHandler)
+    links = Nokogiri::HTML5(@body).css("a[rel~='me'],link[rel~='me']")
 
     if links.any? { |link| link['href']&.downcase == @link_back.downcase }
       true
diff --git a/app/validators/date_of_birth_validator.rb b/app/validators/date_of_birth_validator.rb
deleted file mode 100644
index 79119d2c4c..0000000000
--- a/app/validators/date_of_birth_validator.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-class DateOfBirthValidator < ActiveModel::EachValidator
-  def validate_each(record, attribute, value)
-    record.errors.add(attribute, :below_limit) if value.present? && value.to_date > min_age.ago
-  rescue Date::Error
-    record.errors.add(attribute, :invalid)
-  end
-
-  private
-
-  def min_age
-    Setting.min_age.to_i.years
-  end
-end
diff --git a/app/validators/domain_validator.rb b/app/validators/domain_validator.rb
index 4b2ae82099..718fd190f1 100644
--- a/app/validators/domain_validator.rb
+++ b/app/validators/domain_validator.rb
@@ -4,26 +4,23 @@ class DomainValidator < ActiveModel::EachValidator
   MAX_DOMAIN_LENGTH = 256
   MIN_LABEL_LENGTH = 1
   MAX_LABEL_LENGTH = 63
-  ALLOWED_CHARACTERS_RE = /^[a-z0-9-]+$/i
+  ALLOWED_CHARACTERS_RE = /^[a-z0-9\-]+$/i
 
   def validate_each(record, attribute, value)
     return if value.blank?
 
-    Array.wrap(value).each do |domain|
-      if options[:acct]
-        _, domain = domain.split('@')
-        next if domain.blank?
-      end
+    (options[:multiline] ? value.split : [value]).each do |domain|
+      _, domain = domain.split('@') if options[:acct]
 
-      record.errors.add(attribute, value.is_a?(Enumerable) ? :invalid_domain_on_line : :invalid, value: domain) unless compliant?(domain)
+      next if domain.blank?
+
+      record.errors.add(attribute, options[:multiline] ? :invalid_domain_on_line : :invalid, value: domain) unless compliant?(domain)
     end
   end
 
   private
 
   def compliant?(value)
-    return false if value.blank?
-
     uri = Addressable::URI.new
     uri.host = value
     uri.normalized_host.size < MAX_DOMAIN_LENGTH && uri.normalized_host.split('.').all? { |label| label.size.between?(MIN_LABEL_LENGTH, MAX_LABEL_LENGTH) && label =~ ALLOWED_CHARACTERS_RE }
diff --git a/app/validators/empty_profile_field_names_validator.rb b/app/validators/empty_profile_field_names_validator.rb
deleted file mode 100644
index c979f9f567..0000000000
--- a/app/validators/empty_profile_field_names_validator.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-class EmptyProfileFieldNamesValidator < ActiveModel::Validator
-  def validate(account)
-    return if account.fields.empty?
-
-    account.errors.add(:fields, :fields_with_values_missing_labels) if fields_with_values_missing_names?(account)
-  end
-
-  private
-
-  def fields_with_values_missing_names?(account)
-    account.fields.any? { |field| field.name.blank? && field.value.present? }
-  end
-end
diff --git a/app/validators/lines_validator.rb b/app/validators/lines_validator.rb
new file mode 100644
index 0000000000..27a108bb2c
--- /dev/null
+++ b/app/validators/lines_validator.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class LinesValidator < ActiveModel::EachValidator
+  def validate_each(record, attribute, value)
+    return if value.blank?
+
+    record.errors.add(attribute, :too_many_lines, limit: options[:maximum]) if options[:maximum].present? && value.split.size > options[:maximum]
+  end
+end
diff --git a/app/validators/note_length_validator.rb b/app/validators/note_length_validator.rb
index 1a16bbf2b3..554ad49ce2 100644
--- a/app/validators/note_length_validator.rb
+++ b/app/validators/note_length_validator.rb
@@ -8,7 +8,7 @@ class NoteLengthValidator < ActiveModel::EachValidator
   private
 
   def too_long?(value)
-    countable_text(value).each_grapheme_cluster.size > options[:maximum]
+    countable_text(value).mb_chars.grapheme_length > options[:maximum]
   end
 
   def countable_text(value)
diff --git a/app/validators/poll_expiration_validator.rb b/app/validators/poll_expiration_validator.rb
index 34a627e205..ea8b08e186 100644
--- a/app/validators/poll_expiration_validator.rb
+++ b/app/validators/poll_expiration_validator.rb
@@ -5,12 +5,9 @@ class PollExpirationValidator < ActiveModel::Validator
   MIN_EXPIRATION = 5.minutes.freeze
 
   def validate(poll)
-    # We have a `presence: true` check for this attribute already
-    return if poll.expires_at.nil?
-
     current_time = Time.now.utc
 
-    poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_long')) if poll.expires_at - current_time > MAX_EXPIRATION
-    poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_short')) if (poll.expires_at - current_time).ceil < MIN_EXPIRATION
+    poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_long')) if poll.expires_at.nil? || poll.expires_at - current_time > MAX_EXPIRATION
+    poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_short')) if poll.expires_at.present? && (poll.expires_at - current_time).ceil < MIN_EXPIRATION
   end
 end
diff --git a/app/validators/poll_options_validator.rb b/app/validators/poll_options_validator.rb
index 387471544c..7ce16fe409 100644
--- a/app/validators/poll_options_validator.rb
+++ b/app/validators/poll_options_validator.rb
@@ -7,7 +7,7 @@ class PollOptionsValidator < ActiveModel::Validator
   def validate(poll)
     poll.errors.add(:options, I18n.t('polls.errors.too_few_options')) unless poll.options.size > 1
     poll.errors.add(:options, I18n.t('polls.errors.too_many_options', max: MAX_OPTIONS)) if poll.options.size > MAX_OPTIONS
-    poll.errors.add(:options, I18n.t('polls.errors.over_character_limit', max: MAX_OPTION_CHARS)) if poll.options.any? { |option| option.each_grapheme_cluster.size > MAX_OPTION_CHARS }
+    poll.errors.add(:options, I18n.t('polls.errors.over_character_limit', max: MAX_OPTION_CHARS)) if poll.options.any? { |option| option.mb_chars.grapheme_length > MAX_OPTION_CHARS }
     poll.errors.add(:options, I18n.t('polls.errors.duplicate_options')) unless poll.options.uniq.size == poll.options.size
   end
 end
diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb
index d77a0ac610..dc841ded3e 100644
--- a/app/validators/status_length_validator.rb
+++ b/app/validators/status_length_validator.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class StatusLengthValidator < ActiveModel::Validator
-  MAX_CHARS = (ENV['MAX_CHARS'] || 500).to_i
+  MAX_CHARS = 500
   URL_PLACEHOLDER_CHARS = 23
   URL_PLACEHOLDER = 'x' * 23
 
@@ -18,7 +18,7 @@ class StatusLengthValidator < ActiveModel::Validator
   end
 
   def countable_length(str)
-    str.each_grapheme_cluster.size
+    str.mb_chars.grapheme_length
   end
 
   def combined_text(status)
diff --git a/app/views/.rubocop.yml b/app/views/.rubocop.yml
new file mode 100644
index 0000000000..4e268848c7
--- /dev/null
+++ b/app/views/.rubocop.yml
@@ -0,0 +1,5 @@
+inherit_from: ../../.rubocop.yml
+
+# Disable for the `Rubocop` lints in haml-lint
+Style/IfUnlessModifier:
+  Enabled: false
diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml
index 48380d0ad4..7104b99ac6 100644
--- a/app/views/admin/accounts/index.html.haml
+++ b/app/views/admin/accounts/index.html.haml
@@ -39,7 +39,7 @@
 
   .actions
     %button.button= t('admin.accounts.search')
-    = link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button button--dangerous'
+    = link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative'
 
 %hr.spacer/
 
diff --git a/app/views/admin/announcements/_announcement.html.haml b/app/views/admin/announcements/_announcement.html.haml
index 87ae97cf48..8190f87d2f 100644
--- a/app/views/admin/announcements/_announcement.html.haml
+++ b/app/views/admin/announcements/_announcement.html.haml
@@ -10,8 +10,6 @@
         = l(announcement.created_at)
 
     %div
-      - if can?(:distribute, announcement)
-        = table_link_to 'mail', t('admin.terms_of_service.notify_users'), admin_announcement_preview_path(announcement)
       - if can?(:update, announcement)
         - if announcement.published?
           = table_link_to 'toggle_off', t('admin.announcements.unpublish'), unpublish_admin_announcement_path(announcement), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
diff --git a/app/views/admin/announcements/previews/show.html.haml b/app/views/admin/announcements/previews/show.html.haml
deleted file mode 100644
index 54d5d45ed6..0000000000
--- a/app/views/admin/announcements/previews/show.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-- content_for :page_title do
-  = t('admin.announcements.preview.title')
-
-- content_for :heading_actions do
-  .back-link
-    = link_to admin_announcements_path do
-      = material_symbol 'chevron_left'
-      = t('admin.announcements.back')
-
-.flash-message.info= t('admin.announcements.preview.disclaimer')
-
-%p.lead
-  = t('admin.announcements.preview.explanation_html', count: @user_count, display_count: number_with_delimiter(@user_count))
-
-.prose
-  = linkify(@announcement.text)
-
-%hr.spacer/
-
-.content__heading__actions
-  = link_to t('admin.terms_of_service.preview.send_preview', email: current_user.email), admin_announcement_test_path(@announcement), method: :post, class: 'button button-secondary'
-  = link_to t('admin.terms_of_service.preview.send_to_all', count: @user_count, display_count: number_with_delimiter(@user_count)), admin_announcement_distribution_path(@announcement), method: :post, class: 'button', data: { confirm: t('admin.reports.are_you_sure') }
diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/admin/custom_emojis/index.html.haml
index e027fd2175..2a2ca6aa9d 100644
--- a/app/views/admin/custom_emojis/index.html.haml
+++ b/app/views/admin/custom_emojis/index.html.haml
@@ -35,7 +35,7 @@
 
     .actions
       %button.button= t('admin.accounts.search')
-      = link_to t('admin.accounts.reset'), admin_custom_emojis_path, class: 'button button--dangerous'
+      = link_to t('admin.accounts.reset'), admin_custom_emojis_path, class: 'button negative'
 
 = form_with model: @form, url: batch_admin_custom_emojis_path do |f|
   = hidden_field_tag :page, params[:page] || 1
@@ -72,7 +72,7 @@
             .input.select.optional
               .label_input
                 = f.select :category_id,
-                           options_from_collection_for_select(CustomEmojiCategory.alphabetic.all, 'id', 'name'),
+                           options_from_collection_for_select(CustomEmojiCategory.all, 'id', 'name'),
                            'aria-label': t('admin.custom_emojis.assign_category'),
                            class: 'select optional',
                            prompt: t('admin.custom_emojis.assign_category')
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index c3b7933293..2b4d02fa67 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -16,7 +16,7 @@
   .dashboard__item
     = react_admin_component :counter,
                             end_at: @time_period.last,
-                            href: current_user.can?(:manage_users) ? admin_accounts_path(origin: 'local') : nil,
+                            href: admin_accounts_path(origin: 'local'),
                             label: t('admin.dashboard.new_users'),
                             measure: 'new_users',
                             start_at: @time_period.first
@@ -24,7 +24,7 @@
   .dashboard__item
     = react_admin_component :counter,
                             end_at: @time_period.last,
-                            href: current_user.can?(:manage_users) ? admin_accounts_path(origin: 'local') : nil,
+                            href: admin_accounts_path(origin: 'local'),
                             label: t('admin.dashboard.active_users'),
                             measure: 'active_users',
                             start_at: @time_period.first
@@ -39,7 +39,7 @@
   .dashboard__item
     = react_admin_component :counter,
                             end_at: @time_period.last,
-                            href: current_user.can?(:manage_reports) ? admin_reports_path : nil,
+                            href: admin_reports_path,
                             label: t('admin.dashboard.opened_reports'),
                             measure: 'opened_reports',
                             start_at: @time_period.first
@@ -47,7 +47,7 @@
   .dashboard__item
     = react_admin_component :counter,
                             end_at: @time_period.last,
-                            href: current_user.can?(:manage_reports) ? admin_reports_path(resolved: '1') : nil,
+                            href: admin_reports_path(resolved: '1'),
                             label: t('admin.dashboard.resolved_reports'),
                             measure: 'resolved_reports',
                             start_at: @time_period.first
diff --git a/app/views/admin/domain_blocks/confirm_suspension.html.haml b/app/views/admin/domain_blocks/confirm_suspension.html.haml
index e82026d3a2..4c4f92d710 100644
--- a/app/views/admin/domain_blocks/confirm_suspension.html.haml
+++ b/app/views/admin/domain_blocks/confirm_suspension.html.haml
@@ -19,4 +19,4 @@
 
   .actions
     = link_to t('.cancel'), admin_instances_path, class: 'button button-tertiary'
-    = f.button :submit, t('.confirm'), class: 'button button--dangerous', name: :confirm
+    = f.button :submit, t('.confirm'), class: 'button negative', name: :confirm
diff --git a/app/views/admin/email_domain_blocks/new.html.haml b/app/views/admin/email_domain_blocks/new.html.haml
index 4db8fbe5e5..2dfdca9376 100644
--- a/app/views/admin/email_domain_blocks/new.html.haml
+++ b/app/views/admin/email_domain_blocks/new.html.haml
@@ -30,12 +30,12 @@
             %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
               = f.input_field :other_domains,
                               as: :boolean,
-                              checked_value: record,
+                              checked_value: record.exchange.to_s,
                               include_hidden: false,
                               multiple: true
             .batch-table__row__content.pending-account
               .pending-account__header
-                %samp= record
+                %samp= record.exchange.to_s
                 %br
                 = t('admin.email_domain_blocks.dns.types.mx')
 
diff --git a/app/views/admin/fasp/debug/callbacks/_callback.html.haml b/app/views/admin/fasp/debug/callbacks/_callback.html.haml
deleted file mode 100644
index 6b6d5cfd04..0000000000
--- a/app/views/admin/fasp/debug/callbacks/_callback.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%tr
-  %td= callback.fasp_provider.name
-  %td= callback.fasp_provider.base_url
-  %td= callback.ip
-  %td
-    %time.relative-formatted{ datetime: callback.created_at.iso8601 }
-  %td
-    %code= callback.request_body
-  %td
-    = table_link_to 'close', t('admin.fasp.debug.callbacks.delete'), admin_fasp_debug_callback_path(callback), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
diff --git a/app/views/admin/fasp/debug/callbacks/index.html.haml b/app/views/admin/fasp/debug/callbacks/index.html.haml
deleted file mode 100644
index d83ae95fa5..0000000000
--- a/app/views/admin/fasp/debug/callbacks/index.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-- content_for :page_title do
-  = t('admin.fasp.debug.callbacks.title')
-
-- content_for :heading do
-  %h2= t('admin.fasp.debug.callbacks.title')
-  = render 'admin/fasp/shared/links'
-
-- unless @callbacks.empty?
-  %hr.spacer
-
-  .table-wrapper
-    %table.table
-      %thead
-        %tr
-          %th= t('admin.fasp.providers.name')
-          %th= t('admin.fasp.providers.base_url')
-          %th= t('admin.fasp.debug.callbacks.ip')
-          %th= t('admin.fasp.debug.callbacks.created_at')
-          %th= t('admin.fasp.debug.callbacks.request_body')
-          %th
-      %tbody
-        = render partial: 'callback', collection: @callbacks
diff --git a/app/views/admin/fasp/providers/_provider.html.haml b/app/views/admin/fasp/providers/_provider.html.haml
deleted file mode 100644
index 6184daac7f..0000000000
--- a/app/views/admin/fasp/providers/_provider.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-%tr
-  %td= provider.name
-  %td= provider.base_url
-  %td
-    - if provider.confirmed?
-      = t('admin.fasp.providers.active')
-    - else
-      = t('admin.fasp.providers.registration_requested')
-  %td
-    - if provider.confirmed?
-      = table_link_to 'edit', t('admin.fasp.providers.edit'), edit_admin_fasp_provider_path(provider)
-    - else
-      = table_link_to 'check', t('admin.fasp.providers.finish_registration'), new_admin_fasp_provider_registration_path(provider)
-    - if provider.sign_in_url.present?
-      = table_link_to 'open_in_new', t('admin.fasp.providers.sign_in'), provider.sign_in_url, target: '_blank'
-    - if provider.capability_enabled?('callback')
-      = table_link_to 'repeat', t('admin.fasp.providers.callback'), admin_fasp_provider_debug_calls_path(provider), data: { method: :post }
-
-    = table_link_to 'close', t('admin.fasp.providers.delete'), admin_fasp_provider_path(provider), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
diff --git a/app/views/admin/fasp/providers/edit.html.haml b/app/views/admin/fasp/providers/edit.html.haml
deleted file mode 100644
index f4a799c777..0000000000
--- a/app/views/admin/fasp/providers/edit.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- content_for :page_title do
-  = t('admin.fasp.providers.edit')
-
-= simple_form_for [:admin, @provider] do |f|
-  = render 'shared/error_messages', object: @provider
-
-  %h4= t('admin.fasp.providers.select_capabilities')
-
-  .fields_group
-    = f.fields_for :capabilities do |cf|
-      = cf.input :id, as: :hidden
-      = cf.input :version, as: :hidden
-      = cf.input :enabled, as: :boolean, label: cf.object.id, wrapper: :with_label
-
-  .actions
-    = f.button :button, t('admin.fasp.providers.save'), type: :submit
diff --git a/app/views/admin/fasp/providers/index.html.haml b/app/views/admin/fasp/providers/index.html.haml
deleted file mode 100644
index 209f7e8034..0000000000
--- a/app/views/admin/fasp/providers/index.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-- content_for :page_title do
-  = t('admin.fasp.providers.title')
-
-- content_for :heading do
-  %h2= t('admin.fasp.providers.title')
-  = render 'admin/fasp/shared/links'
-
-- unless @providers.empty?
-  %hr.spacer
-
-  .table-wrapper
-    %table.table#providers
-      %thead
-        %tr
-          %th= t('admin.fasp.providers.name')
-          %th= t('admin.fasp.providers.base_url')
-          %th= t('admin.fasp.providers.status')
-          %th
-      %tbody
-        = render partial: 'provider', collection: @providers
diff --git a/app/views/admin/fasp/registrations/new.html.haml b/app/views/admin/fasp/registrations/new.html.haml
deleted file mode 100644
index 68eb940c09..0000000000
--- a/app/views/admin/fasp/registrations/new.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-- content_for :page_title do
-  = t('admin.fasp.providers.registrations.title')
-
-%p= t('admin.fasp.providers.registrations.description')
-
-%table.table.inline-table
-  %tbody
-    %tr
-      %th= t('admin.fasp.providers.name')
-      %td= @provider.name
-    %tr
-      %th= t('admin.fasp.providers.public_key_fingerprint')
-      %td
-        %code= @provider.provider_public_key_fingerprint
-
-= form_with url: admin_fasp_provider_registration_path(@provider), class: :simple_form do |f|
-  .actions
-    = link_to t('admin.fasp.providers.registrations.reject'), admin_fasp_provider_path(@provider), data: { method: :delete }, class: 'btn negative'
-    = f.button t('admin.fasp.providers.registrations.confirm'), type: :submit, class: 'btn'
diff --git a/app/views/admin/fasp/shared/_links.html.haml b/app/views/admin/fasp/shared/_links.html.haml
deleted file mode 100644
index 0c1d1eb4db..0000000000
--- a/app/views/admin/fasp/shared/_links.html.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-.content__heading__tabs
-  = render_navigation renderer: :links do |primary|
-    :ruby
-      primary.item :providers, safe_join([material_symbol('database'), t('admin.fasp.providers.providers')]), admin_fasp_providers_path
-      primary.item :debug_callbacks, safe_join([material_symbol('repeat'), t('admin.fasp.debug.callbacks.title')]), admin_fasp_debug_callbacks_path
diff --git a/app/views/admin/instances/_dashboard.html.haml b/app/views/admin/instances/_dashboard.html.haml
index 16dcb051a2..ef8500103b 100644
--- a/app/views/admin/instances/_dashboard.html.haml
+++ b/app/views/admin/instances/_dashboard.html.haml
@@ -7,7 +7,7 @@
   .dashboard__item
     = react_admin_component :counter,
                             end_at: period_end_at,
-                            href: current_user.can?(:manage_users) ? admin_accounts_path(origin: 'remote', by_domain: instance_domain) : nil,
+                            href: admin_accounts_path(origin: 'remote', by_domain: instance_domain),
                             label: t('admin.instances.dashboard.instance_accounts_measure'),
                             measure: 'instance_accounts',
                             params: { domain: instance_domain },
@@ -43,7 +43,7 @@
   .dashboard__item
     = react_admin_component :counter,
                             end_at: period_end_at,
-                            href: current_user.can?(:manage_reports) ? admin_reports_path(by_target_domain: instance_domain) : nil,
+                            href: admin_reports_path(by_target_domain: instance_domain),
                             label: t('admin.instances.dashboard.instance_reports_measure'),
                             measure: 'instance_reports',
                             params: { domain: instance_domain },
diff --git a/app/views/admin/instances/index.html.haml b/app/views/admin/instances/index.html.haml
index 79cd5a38f5..b5f084f880 100644
--- a/app/views/admin/instances/index.html.haml
+++ b/app/views/admin/instances/index.html.haml
@@ -42,7 +42,7 @@
 
       .actions
         %button.button= t('admin.accounts.search')
-        = link_to t('admin.accounts.reset'), admin_instances_path, class: 'button button--dangerous'
+        = link_to t('admin.accounts.reset'), admin_instances_path, class: 'button negative'
 
 %hr.spacer/
 
diff --git a/app/views/admin/invites/_invite.html.haml b/app/views/admin/invites/_invite.html.haml
index fcaf5fc616..e3e5d32542 100644
--- a/app/views/admin/invites/_invite.html.haml
+++ b/app/views/admin/invites/_invite.html.haml
@@ -3,7 +3,7 @@
     .input-copy
       .input-copy__wrapper
         = copyable_input value: public_invite_url(invite_code: invite.code)
-      %button.button{ type: :button }= t('generic.copy')
+      %button{ type: :button }= t('generic.copy')
 
   %td
     .name-tag
diff --git a/app/views/admin/relays/index.html.haml b/app/views/admin/relays/index.html.haml
index d9c23f2de7..47f8d6f360 100644
--- a/app/views/admin/relays/index.html.haml
+++ b/app/views/admin/relays/index.html.haml
@@ -3,7 +3,7 @@
 
 .simple_form
   %p.hint= t('admin.relays.description_html')
-  = link_to @relays.empty? ? t('admin.relays.setup') : t('admin.relays.add_new'), new_admin_relay_path, class: 'button button--block'
+  = link_to @relays.empty? ? t('admin.relays.setup') : t('admin.relays.add_new'), new_admin_relay_path, class: 'block-button'
 
 - unless @relays.empty?
   %hr.spacer
diff --git a/app/views/admin/report_notes/_report_note.html.haml b/app/views/admin/report_notes/_report_note.html.haml
index 9c8e267d62..dd60f7eabd 100644
--- a/app/views/admin/report_notes/_report_note.html.haml
+++ b/app/views/admin/report_notes/_report_note.html.haml
@@ -4,7 +4,7 @@
   .report-notes__item__header
     %span.username
       = link_to report_note.account.username, admin_account_path(report_note.account_id)
-    %time.relative-formatted{ datetime: report_note.created_at.iso8601, title: report_note.created_at }
+    %time.relative-formatted{ datetime: report_note.created_at.iso8601 }
       = l report_note.created_at.to_date
 
   .report-notes__item__content
diff --git a/app/views/admin/reports/_comment.html.haml b/app/views/admin/reports/_comment.html.haml
index 2b3af15c49..8c07210af9 100644
--- a/app/views/admin/reports/_comment.html.haml
+++ b/app/views/admin/reports/_comment.html.haml
@@ -18,7 +18,7 @@
           = link_to report.account.username, admin_account_path(report.account_id)
         - else
           = link_to report.account.domain, admin_instance_path(report.account.domain)
-      %time.relative-formatted{ datetime: report.created_at.iso8601, title: report.created_at }
+      %time.relative-formatted{ datetime: report.created_at.iso8601 }
         = l report.created_at.to_date
     .report-notes__item__content
       = simple_format(h(report.comment))
diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml
index c1e47d5b32..f4630ed25a 100644
--- a/app/views/admin/reports/_status.html.haml
+++ b/app/views/admin/reports/_status.html.haml
@@ -2,52 +2,40 @@
   %label.batch-table__row__select.batch-checkbox
     = f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id
   .batch-table__row__content
-    .status__card
-      - if status.reblog?
-        .status__prepend
-          = material_symbol('repeat')
-          = t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(status.proper.account, path: admin_account_status_path(status.proper.account.id, status.proper.id)))
-      - elsif status.reply? && status.in_reply_to_id.present?
-        .status__prepend
-          = material_symbol('reply')
-          = t('admin.statuses.replied_to_html', acct_link: admin_account_inline_link_to(status.in_reply_to_account, path: status.thread.present? ? admin_account_status_path(status.thread.account_id, status.in_reply_to_id) : nil))
-      .status__content><
-        - if status.proper.spoiler_text.blank?
+    .status__content><
+      - if status.proper.spoiler_text.blank?
+        = prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
+      - else
+        %details<
+          %summary><
+            %strong> Content warning: #{prerender_custom_emojis(h(status.proper.spoiler_text), status.proper.emojis)}
           = prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
-        - else
-          %details<
-            %summary><
-              %strong> Content warning: #{prerender_custom_emojis(h(status.proper.spoiler_text), status.proper.emojis)}
-            = prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
 
-      - unless status.proper.ordered_media_attachments.empty?
-        = render partial: 'admin/reports/media_attachments', locals: { status: status.proper }
+    - unless status.proper.ordered_media_attachments.empty?
+      = render partial: 'admin/reports/media_attachments', locals: { status: status.proper }
 
-      .detailed-status__meta
-        - if status.application
-          = status.application.name
-          ·
-
-        = link_to admin_account_status_path(status.account.id, status), class: 'detailed-status__datetime' do
-          %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
-        - if status.edited?
-          ·
-          = link_to t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'formatted')),
-                    admin_account_status_path(status.account_id, status),
-                    class: 'detailed-status__datetime'
-        - if status.discarded?
-          ·
-          %span.negative-hint= t('admin.statuses.deleted')
+    .detailed-status__meta
+      - if status.application
+        = status.application.name
         ·
-
+      = link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do
+        %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
+      - if status.edited?
+        ·
+        = link_to t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'formatted')),
+                  admin_account_status_path(status.account_id, status),
+                  class: 'detailed-status__datetime'
+      - if status.discarded?
+        ·
+        %span.negative-hint= t('admin.statuses.deleted')
+      ·
+      - if status.reblog?
+        = material_symbol('repeat_active')
+        = t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(status.proper.account))
+      - else
         = material_symbol visibility_icon(status)
         = t("statuses.visibilities.#{status.visibility}")
+      - if status.proper.sensitive?
         ·
-
-        = link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__link', rel: 'noopener' do
-          = t('admin.statuses.view_publicly')
-
-        - if status.proper.sensitive?
-          ·
-          = material_symbol('visibility_off')
-          = t('stream_entries.sensitive_content')
+        = material_symbol('visibility_off')
+        = t('stream_entries.sensitive_content')
diff --git a/app/views/admin/reports/actions/preview.html.haml b/app/views/admin/reports/actions/preview.html.haml
index 5a90bf1b8f..79c444453f 100644
--- a/app/views/admin/reports/actions/preview.html.haml
+++ b/app/views/admin/reports/actions/preview.html.haml
@@ -63,7 +63,7 @@
                       = material_symbol 'link'
                       = media_attachment.file_file_name
                 .strike-card__statuses-list__item__meta
-                  = link_to ActivityPub::TagManager.instance.url_for(status), target: '_blank', rel: 'noopener' do
+                  = link_to ActivityPub::TagManager.instance.url_for(status), target: '_blank', rel: 'noopener noreferrer' do
                     %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
                   - unless status.application.nil?
                     ·
diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml
index 6f762d94eb..b476c6ccfa 100644
--- a/app/views/admin/reports/index.html.haml
+++ b/app/views/admin/reports/index.html.haml
@@ -28,7 +28,7 @@
 
     .actions
       %button.button= t('admin.accounts.search')
-      = link_to t('admin.accounts.reset'), admin_reports_path, class: 'button button--dangerous'
+      = link_to t('admin.accounts.reset'), admin_reports_path, class: 'button negative'
 
 - @reports.group_by(&:target_account_id).each_value do |reports|
   - target_account = reports.first.target_account
diff --git a/app/views/admin/roles/_role.html.haml b/app/views/admin/roles/_role.html.haml
index 085bdbd156..636127354b 100644
--- a/app/views/admin/roles/_role.html.haml
+++ b/app/views/admin/roles/_role.html.haml
@@ -1,7 +1,7 @@
 .announcements-list__item
   - if can?(:update, role)
     = link_to edit_admin_role_path(role), class: 'announcements-list__item__title' do
-      %span.user-role
+      %span.user-role{ class: "user-role-#{role.id}" }
         = material_symbol 'group'
 
         - if role.everyone?
@@ -10,7 +10,7 @@
           = role.name
   - else
     %span.announcements-list__item__title
-      %span.user-role
+      %span.user-role{ class: "user-role-#{role.id}" }
         = material_symbol 'group'
 
         - if role.everyone?
diff --git a/app/views/admin/settings/registrations/show.html.haml b/app/views/admin/settings/registrations/show.html.haml
index 4bd384f8b5..fe6a71a4ca 100644
--- a/app/views/admin/settings/registrations/show.html.haml
+++ b/app/views/admin/settings/registrations/show.html.haml
@@ -12,9 +12,6 @@
 
   .flash-message= t('admin.settings.registrations.moderation_recommandation')
 
-  .fields-group
-    = f.input :min_age, as: :string, wrapper: :with_block_label, input_html: { inputmode: 'numeric' }
-
   .fields-row
     .fields-row__column.fields-row__column-6.fields-group
       = f.input :registrations_mode,
diff --git a/app/views/admin/statuses/index.html.haml b/app/views/admin/statuses/index.html.haml
index 57b9fe0e15..4d5d037060 100644
--- a/app/views/admin/statuses/index.html.haml
+++ b/app/views/admin/statuses/index.html.haml
@@ -1,5 +1,7 @@
 - content_for :page_title do
-  = t('admin.statuses.title', name: @account.pretty_acct)
+  = t('admin.statuses.title')
+  \-
+  @#{@account.pretty_acct}
 
 .filters
   .filter-subset
@@ -31,18 +33,11 @@
         = check_box_tag :batch_checkbox_all, nil, false
       .batch-table__toolbar__actions
         - unless @statuses.empty?
-          - if params[:report_id]
-            = f.button safe_join([material_symbol('add'), t('admin.statuses.batch.add_to_report', id: params[:report_id])]),
-                       class: 'table-action-link',
-                       data: { confirm: t('admin.reports.are_you_sure') },
-                       name: :report,
-                       type: :submit
-          - else
-            = f.button safe_join([material_symbol('flag'), t('admin.statuses.batch.report')]),
-                       class: 'table-action-link',
-                       data: { confirm: t('admin.reports.are_you_sure') },
-                       name: :report,
-                       type: :submit
+          = f.button safe_join([material_symbol('flag'), t('admin.statuses.batch.report')]),
+                     class: 'table-action-link',
+                     data: { confirm: t('admin.reports.are_you_sure') },
+                     name: :report,
+                     type: :submit
     .batch-table__body
       - if @statuses.empty?
         = nothing_here 'nothing-here--under-tabs'
diff --git a/app/views/admin/statuses/show.html.haml b/app/views/admin/statuses/show.html.haml
index f5bf98e7de..1322f1dda9 100644
--- a/app/views/admin/statuses/show.html.haml
+++ b/app/views/admin/statuses/show.html.haml
@@ -1,15 +1,8 @@
 - content_for :page_title do
-  = t('admin.statuses.status_title', name: @account.pretty_acct)
+  = t('statuses.title', name: display_name(@account), quote: truncate(@status.spoiler_text.presence || @status.text, length: 50, omission: '…', escape: false))
 
 - content_for :heading_actions do
-  = form_with model: @status_batch_action, url: batch_admin_account_statuses_path(@account.id) do |f|
-    = f.hidden_field :status_ids, { multiple: true, value: @status.id }
-    = f.button safe_join([material_symbol('flag'), t('admin.statuses.batch.report')]),
-               class: 'button',
-               data: { confirm: t('admin.reports.are_you_sure') },
-               name: :report,
-               type: :submit
-  = link_to t('admin.statuses.open'), ActivityPub::TagManager.instance.url_for(@status), class: 'button', target: '_blank', rel: 'noopener'
+  = link_to t('admin.statuses.open'), ActivityPub::TagManager.instance.url_for(@status), class: 'button', target: '_blank', rel: 'noopener noreferrer'
 
 %h3= t('admin.statuses.metadata')
 
@@ -22,7 +15,7 @@
       - if @status.reply?
         %tr
           %th= t('admin.statuses.in_reply_to')
-          %td= admin_account_link_to @status.in_reply_to_account, path: @status.thread.present? ? admin_account_status_path(@status.thread.account_id, @status.in_reply_to_id) : nil
+          %td= admin_account_link_to @status.in_reply_to_account, path: admin_account_status_path(@status.thread.account_id, @status.in_reply_to_id)
       %tr
         %th= t('admin.statuses.application')
         %td= @status.application&.name
@@ -64,56 +57,7 @@
 
 %hr.spacer/
 
-%h3= t('admin.statuses.contents')
-
-.status__card
-  - if @status.reblog?
-    .status__prepend
-      = material_symbol('repeat')
-      = t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(@status.proper.account, path: admin_account_status_path(@status.proper.account.id, @status.proper.id)))
-  - elsif @status.reply? && @status.in_reply_to_id.present?
-    .status__prepend
-      = material_symbol('reply')
-      = t('admin.statuses.replied_to_html', acct_link: admin_account_inline_link_to(@status.in_reply_to_account, path: @status.thread.present? ? admin_account_status_path(@status.thread.account_id, @status.in_reply_to_id) : nil))
-  .status__content><
-    - if @status.proper.spoiler_text.blank?
-      = prerender_custom_emojis(status_content_format(@status.proper), @status.proper.emojis)
-    - else
-      %details<
-        %summary><
-          %strong> Content warning: #{prerender_custom_emojis(h(@status.proper.spoiler_text), @status.proper.emojis)}
-        = prerender_custom_emojis(status_content_format(@status.proper), @status.proper.emojis)
-
-    - unless @status.proper.ordered_media_attachments.empty?
-      = render partial: 'admin/reports/media_attachments', locals: { status: @status.proper }
-
-    .detailed-status__meta
-      - if @status.application
-        = @status.application.name
-        ·
-      %span.detailed-status__datetime
-        %time.formatted{ datetime: @status.created_at.iso8601, title: l(@status.created_at) }= l(@status.created_at)
-      - if @status.edited?
-        ·
-        %span.detailed-status__datetime
-          = t('statuses.edited_at_html', date: content_tag(:time, l(@status.edited_at), datetime: @status.edited_at.iso8601, title: l(@status.edited_at), class: 'formatted'))
-      - if @status.discarded?
-        ·
-        %span.negative-hint= t('admin.statuses.deleted')
-      - unless @status.reblog?
-        ·
-        = material_symbol(visibility_icon(@status))
-        = t("statuses.visibilities.#{@status.visibility}")
-      - if @status.proper.sensitive?
-        ·
-        = material_symbol('visibility_off')
-        = t('stream_entries.sensitive_content')
-
-%hr.spacer/
-
 %h3= t('admin.statuses.history')
-- if @status.edits.empty?
-  %p= t('admin.statuses.no_history')
-- else
-  %ol.history
-    = render partial: 'admin/status_edits/status_edit', collection: batched_ordered_status_edits
+
+%ol.history
+  = render partial: 'admin/status_edits/status_edit', collection: batched_ordered_status_edits
diff --git a/app/views/admin/tags/index.html.haml b/app/views/admin/tags/index.html.haml
index 3d0c844eb0..be699e5ab2 100644
--- a/app/views/admin/tags/index.html.haml
+++ b/app/views/admin/tags/index.html.haml
@@ -25,12 +25,11 @@
 
   .actions
     %button.button= t('admin.tags.search')
-    = link_to t('admin.tags.reset'), admin_tags_path, class: 'button button--dangerous'
+    = link_to t('admin.tags.reset'), admin_tags_path, class: 'button negative'
 
 %hr.spacer/
 
-.batch-table.batch-table--no-toolbar
-  .batch-table__toolbar
+.batch-table
   .batch-table__body
     - if @tags.empty?
       = nothing_here 'nothing-here--under-tabs nothing-here--no-toolbar'
diff --git a/app/views/admin/tags/show.html.haml b/app/views/admin/tags/show.html.haml
index 0c8b2eaec0..462ca312a0 100644
--- a/app/views/admin/tags/show.html.haml
+++ b/app/views/admin/tags/show.html.haml
@@ -6,7 +6,7 @@
     .time-period
       = date_range(@time_period)
 
-  = link_to t('admin.tags.open'), tag_url(@tag), class: 'button', target: '_blank', rel: 'noopener'
+  = link_to t('admin.tags.open'), tag_url(@tag), class: 'button', target: '_blank', rel: 'noopener noreferrer'
 
 - if current_user.can?(:view_dashboard)
   .dashboard
@@ -17,7 +17,7 @@
                               label: t('admin.trends.tags.dashboard.tag_accounts_measure'),
                               measure: 'tag_accounts',
                               params: { id: @tag.id },
-                              rel: 'noopener',
+                              rel: 'noopener noreferrer',
                               start_at: @time_period.first,
                               target: '_blank'
     .dashboard__item
diff --git a/app/views/admin/terms_of_service/_links.html.haml b/app/views/admin/terms_of_service/_links.html.haml
deleted file mode 100644
index aaf0f2c7b7..0000000000
--- a/app/views/admin/terms_of_service/_links.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.content__heading__tabs
-  = render_navigation renderer: :links do |primary|
-    :ruby
-      primary.item :current, safe_join([material_symbol('description'), t('admin.terms_of_service.current')]), admin_terms_of_service_index_path
-      primary.item :draft, safe_join([material_symbol('description'), t('admin.terms_of_service.draft')]), admin_terms_of_service_draft_path
-      primary.item :previous, safe_join([material_symbol('history'), t('admin.terms_of_service.history')]), admin_terms_of_service_history_path
diff --git a/app/views/admin/terms_of_service/drafts/show.html.haml b/app/views/admin/terms_of_service/drafts/show.html.haml
deleted file mode 100644
index e83bb47c6b..0000000000
--- a/app/views/admin/terms_of_service/drafts/show.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-- content_for :page_title do
-  = t('admin.terms_of_service.title')
-
-- content_for :heading do
-  %h2= t('admin.terms_of_service.title')
-  = render partial: 'admin/terms_of_service/links'
-
-= simple_form_for @terms_of_service, url: admin_terms_of_service_draft_path, method: :put do |form|
-  = render 'shared/error_messages', object: @terms_of_service
-
-  .fields-group
-    = form.input :text, wrapper: :with_block_label, input_html: { rows: 8 }
-
-  .fields-group
-    = form.input :changelog, wrapper: :with_block_label, input_html: { rows: 8 }
-
-  .fields-group
-    = form.input :effective_date, wrapper: :with_block_label, as: :date, start_year: Time.zone.today.year
-
-  .actions
-    = form.button :button, t('admin.terms_of_service.save_draft'), type: :submit, name: :action_type, value: :save_draft, class: 'button button-secondary'
-    = form.button :button, t('admin.terms_of_service.publish'), type: :submit, name: :action_type, value: :publish
diff --git a/app/views/admin/terms_of_service/generates/show.html.haml b/app/views/admin/terms_of_service/generates/show.html.haml
deleted file mode 100644
index 9fd06a65da..0000000000
--- a/app/views/admin/terms_of_service/generates/show.html.haml
+++ /dev/null
@@ -1,47 +0,0 @@
-- content_for :page_title do
-  = t('admin.terms_of_service.generates.title')
-
-- content_for :heading_actions do
-  .back-link
-    = link_to admin_terms_of_service_index_path do
-      = material_symbol 'chevron_left'
-      = t('admin.terms_of_service.back')
-
-%p.lead= t('admin.terms_of_service.generates.explanation_html')
-
-%p.lead= t('admin.terms_of_service.generates.chance_to_review_html')
-
-%hr.spacer/
-
-= simple_form_for @generator, url: admin_terms_of_service_generate_path, method: :post do |form|
-  = render 'shared/error_messages', object: @generator
-
-  .fields-group
-    = form.input :domain, wrapper: :with_label
-
-  .fields-group
-    = form.input :min_age, wrapper: :with_label
-
-  .fields-group
-    = form.input :jurisdiction, wrapper: :with_label
-
-  .fields-group
-    = form.input :choice_of_law, wrapper: :with_label
-
-  .fields-group
-    = form.input :admin_email, wrapper: :with_label
-
-  .fields-group
-    = form.input :dmca_email, wrapper: :with_label
-
-  .fields-group
-    = form.input :dmca_address, wrapper: :with_label
-
-  .fields-group
-    = form.input :arbitration_address, wrapper: :with_label
-
-  .fields-group
-    = form.input :arbitration_website, wrapper: :with_label
-
-  .actions
-    = form.button :button, t('admin.terms_of_service.generates.action'), type: :submit
diff --git a/app/views/admin/terms_of_service/histories/show.html.haml b/app/views/admin/terms_of_service/histories/show.html.haml
deleted file mode 100644
index 10921d922f..0000000000
--- a/app/views/admin/terms_of_service/histories/show.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-- content_for :page_title do
-  = t('admin.terms_of_service.history')
-
-- content_for :heading do
-  %h2= t('admin.terms_of_service.title')
-  = render partial: 'admin/terms_of_service/links'
-
-- if @terms_of_service.empty?
-  %p= t('admin.terms_of_service.no_history')
-- else
-  %ol.admin__terms-of-service__history
-    - @terms_of_service.each do |terms_of_service|
-      %li
-        .admin__terms-of-service__history__item
-          %h5
-            - if terms_of_service.effective_date.present?
-              = link_to l(terms_of_service.published_at), terms_of_service_version_path(date: terms_of_service.effective_date)
-            - else
-              = l(terms_of_service.published_at)
-          .prose= markdown(terms_of_service.changelog)
diff --git a/app/views/admin/terms_of_service/index.html.haml b/app/views/admin/terms_of_service/index.html.haml
deleted file mode 100644
index 636851b449..0000000000
--- a/app/views/admin/terms_of_service/index.html.haml
+++ /dev/null
@@ -1,43 +0,0 @@
-- content_for :page_title do
-  = t('admin.terms_of_service.title')
-
-- content_for :heading do
-  %h2= t('admin.terms_of_service.title')
-  = render partial: 'links'
-
-- if @terms_of_service.present?
-  .admin__terms-of-service__container
-    .admin__terms-of-service__container__header
-      .dot-indicator.success
-        .dot-indicator__indicator
-        %span
-          - if @terms_of_service.effective? || @terms_of_service.effective_date.nil?
-            = t('admin.terms_of_service.live')
-          - else
-            = t('admin.terms_of_service.going_live_on_html', date: tag.time(l(@terms_of_service.effective_date), class: 'formatted', date: @terms_of_service.effective_date.iso8601))
-      ·
-      %span
-        = t('admin.terms_of_service.published_on_html', date: tag.time(l(@terms_of_service.published_at.to_date), class: 'formatted', date: @terms_of_service.published_at.to_date.iso8601))
-      ·
-      - if @terms_of_service.notification_sent?
-        %span
-          = t('admin.terms_of_service.notified_on_html', date: tag.time(l(@terms_of_service.notification_sent_at.to_date), class: 'formatted', date: @terms_of_service.notification_sent_at.to_date.iso8601))
-      - else
-        = link_to t('admin.terms_of_service.notify_users'), admin_terms_of_service_preview_path(@terms_of_service), class: 'link-button'
-
-    .admin__terms-of-service__container__body
-      .prose
-        = markdown(@terms_of_service.text)
-
-  %hr.spacer/
-
-  %h3= t('admin.terms_of_service.changelog')
-
-  .prose
-    = markdown(@terms_of_service.changelog)
-- else
-  %p.lead= t('admin.terms_of_service.no_terms_of_service_html')
-
-  .content__heading__actions
-    = link_to t('admin.terms_of_service.create'), admin_terms_of_service_draft_path, class: 'button'
-    = link_to t('admin.terms_of_service.generate'), admin_terms_of_service_generate_path, class: 'button button-secondary'
diff --git a/app/views/admin/terms_of_service/previews/show.html.haml b/app/views/admin/terms_of_service/previews/show.html.haml
deleted file mode 100644
index 48c94cb052..0000000000
--- a/app/views/admin/terms_of_service/previews/show.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-- content_for :page_title do
-  = t('admin.terms_of_service.preview.title')
-
-- content_for :heading_actions do
-  .back-link
-    = link_to admin_terms_of_service_index_path do
-      = material_symbol 'chevron_left'
-      = t('admin.terms_of_service.back')
-
-%p.lead
-  = t('admin.terms_of_service.preview.explanation_html', count: @user_count, display_count: number_with_delimiter(@user_count), date: l(@terms_of_service.published_at.to_date))
-
-.prose
-  = markdown(@terms_of_service.changelog)
-
-%hr.spacer/
-
-.content__heading__actions
-  = link_to t('admin.terms_of_service.preview.send_preview', email: current_user.email), admin_terms_of_service_test_path(@terms_of_service), method: :post, class: 'button button-secondary'
-  = link_to t('admin.terms_of_service.preview.send_to_all', count: @user_count, display_count: number_with_delimiter(@user_count)), admin_terms_of_service_distribution_path(@terms_of_service), method: :post, class: 'button', data: { confirm: t('admin.reports.are_you_sure') }
diff --git a/app/views/admin/trends/statuses/_status.html.haml b/app/views/admin/trends/statuses/_status.html.haml
index aa09803ed5..09547ff036 100644
--- a/app/views/admin/trends/statuses/_status.html.haml
+++ b/app/views/admin/trends/statuses/_status.html.haml
@@ -6,7 +6,7 @@
     .one-liner
       = admin_account_link_to status.account
 
-      = link_to ActivityPub::TagManager.instance.url_for(status), target: '_blank', class: 'emojify', rel: 'noopener' do
+      = link_to ActivityPub::TagManager.instance.url_for(status), target: '_blank', class: 'emojify', rel: 'noopener noreferrer' do
         = one_line_preview(status)
 
         - status.ordered_media_attachments.each do |media_attachment|
diff --git a/app/views/admin/trends/tags/_tag.html.haml b/app/views/admin/trends/tags/_tag.html.haml
index 91a484d46c..b1e714a912 100644
--- a/app/views/admin/trends/tags/_tag.html.haml
+++ b/app/views/admin/trends/tags/_tag.html.haml
@@ -8,12 +8,12 @@
 
       %br/
 
-      = link_to tag_path(tag), target: '_blank', rel: 'noopener' do
+      = link_to tag_path(tag), target: '_blank', rel: 'noopener noreferrer' do
         = t('admin.trends.tags.used_by_over_week', count: tag.history.reduce(0) { |sum, day| sum + day.accounts })
 
-      - if tag.trendable?
+      - if tag.trendable? && (rank = Trends.tags.rank(tag.id))
         ·
-        %abbr{ title: t('admin.trends.tags.current_score', score: tag.trend.score) }= t('admin.trends.tags.trending_rank', rank: tag.trend.rank)
+        %abbr{ title: t('admin.trends.tags.current_score', score: Trends.tags.score(tag.id)) }= t('admin.trends.tags.trending_rank', rank: rank + 1)
 
         - if tag.decaying?
           ·
diff --git a/app/views/admin_mailer/_new_trending_tags.text.erb b/app/views/admin_mailer/_new_trending_tags.text.erb
index c9bd1bc717..f738caaf3d 100644
--- a/app/views/admin_mailer/_new_trending_tags.text.erb
+++ b/app/views/admin_mailer/_new_trending_tags.text.erb
@@ -2,7 +2,7 @@
 
 <% new_trending_tags.each do |tag| %>
 - #<%= tag.display_name %>
-  <%= raw t('admin.trends.tags.usage_comparison', today: tag.history.get(Time.now.utc).accounts, yesterday: tag.history.get(Time.now.utc - 1.day).accounts) %> · <%= t('admin.trends.tags.current_score', score: tag.trend.score.round(2)) %>
+  <%= raw t('admin.trends.tags.usage_comparison', today: tag.history.get(Time.now.utc).accounts, yesterday: tag.history.get(Time.now.utc - 1.day).accounts) %> · <%= t('admin.trends.tags.current_score', score: Trends.tags.score(tag.id).round(2)) %>
 <% end %>
 
 <%= raw t('application_mailer.view')%> <%= admin_trends_tags_url(status: 'pending_review') %>
diff --git a/app/views/antennas/_antenna.html.haml b/app/views/antennas/_antenna.html.haml
new file mode 100644
index 0000000000..2906648cec
--- /dev/null
+++ b/app/views/antennas/_antenna.html.haml
@@ -0,0 +1,77 @@
+.filters-list__item{ class: [(antenna.expired? || !antenna.enabled_config?) && 'expired'] }
+  = link_to edit_antenna_path(antenna), class: 'filters-list__item__title' do
+    = antenna.title
+
+    - if !antenna.enabled_config? && !antenna.stl
+      .expiration{ title: t('antennas.index.disabled') }
+        = t('antennas.index.disabled')
+    - elsif antenna.expires?
+      .expiration{ title: t('antennas.index.expires_on', date: l(antenna.expires_at)) }
+        - if antenna.expired?
+          = t('invites.expired')
+        - else
+          = t('antennas.index.expires_in', distance: distance_of_time_in_words_to_now(antenna.expires_at))
+
+  .listname
+    = antenna.list&.title || '[Insert to Home]'
+
+  .filters-list__item__permissions
+    %ul.permissions-list
+      - unless antenna.stl
+        - unless antenna.antenna_domains.empty?
+          %li.permissions-list__item
+            .permissions-list__item__icon
+              = material_symbol('cloud')
+            .permissions-list__item__text
+              .permissions-list__item__text__title
+                = t('antennas.index.domains', count: antenna.antenna_domains.size)
+              .permissions-list__item__text__type
+                - domains = antenna.antenna_domains.map(&:name)
+                - domains = domains.take(5) + ['…'] if domains.size > 5 # TODO
+                = domains.join(', ')
+        - unless antenna.antenna_accounts.empty?
+          %li.permissions-list__item
+            .permissions-list__item__icon
+              = material_symbol('groups')
+            .permissions-list__item__text
+              .permissions-list__item__text__title
+                = t('antennas.index.accounts', count: antenna.antenna_accounts.size)
+              .permissions-list__item__text__type
+                - accounts = antenna.antenna_accounts.map { |account| account.account.domain ? "@#{account.account.username}@#{account.account.domain}" : "@#{account.account.username}" }
+                - accounts = accounts.take(5) + ['…'] if accounts.size > 5 # TODO
+                = accounts.join(', ')
+        - if antenna.keywords.present?
+          %li.permissions-list__item
+            .permissions-list__item__icon
+              = material_symbol('format_paragraph')
+            .permissions-list__item__text
+              .permissions-list__item__text__title
+                = t('antennas.index.keywords', count: antenna.keywords.size)
+              .permissions-list__item__text__type
+                - keywords = antenna.keywords
+                - keywords = keywords.take(5) + ['…'] if keywords.size > 5 # TODO
+                = keywords.join(', ')
+        - unless antenna.antenna_tags.empty?
+          %li.permissions-list__item
+            .permissions-list__item__icon
+              = material_symbol('tag')
+            .permissions-list__item__text
+              .permissions-list__item__text__title
+                = t('antennas.index.tags', count: antenna.antenna_tags.size)
+              .permissions-list__item__text__type
+                - tags = antenna.antenna_tags.map { |tag| tag.tag.name }
+                - tags = tags.take(5) + ['…'] if tags.size > 5 # TODO
+                = tags.join(', ')
+
+  .announcements-list__item__action-bar
+    .announcements-list__item__meta
+      - if antenna.stl
+        = t('antennas.index.stl')
+      - elsif antenna.enabled_config_raws?
+        = t('antennas.index.contexts', contexts: antenna.context.map { |context| I18n.t("antennas.contexts.#{context}") }.join(', '))
+      - else
+        = t('antennas.errors.empty_contexts')
+
+    %div
+      = table_link_to 'edit', t('antennas.edit.title'), edit_antenna_path(antenna)
+      = table_link_to 'close', t('antennas.index.delete'), antenna_path(antenna), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
diff --git a/app/views/antennas/_antenna_fields.html.haml b/app/views/antennas/_antenna_fields.html.haml
new file mode 100644
index 0000000000..49ec951669
--- /dev/null
+++ b/app/views/antennas/_antenna_fields.html.haml
@@ -0,0 +1,11 @@
+%p= t 'antennas.edit.description'
+%hr.spacer/
+
+.fields-row
+  .fields-row__column.fields-row__column-6.fields-group
+    = f.input :title, as: :string, wrapper: :with_label, hint: false
+  .fields-row__column.fields-row__column-6.fields-group
+    = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: ->(i) { I18n.t("invites.expires_in.#{i}") }, include_blank: I18n.t('invites.expires_in_prompt')
+
+.fields-row
+  = f.input :available, wrapper: :with_label, label: t('antennas.edit.available'), hint: false
diff --git a/app/views/antennas/edit.html.haml b/app/views/antennas/edit.html.haml
new file mode 100644
index 0000000000..ba3e4f368d
--- /dev/null
+++ b/app/views/antennas/edit.html.haml
@@ -0,0 +1,9 @@
+- content_for :page_title do
+  = t('antennas.edit.title')
+
+= simple_form_for @antenna, url: antenna_path(@antenna), method: :put do |f|
+  = render 'shared/error_messages', object: @antenna
+  = render 'antenna_fields', f: f, lists: @lists
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/antennas/index.html.haml b/app/views/antennas/index.html.haml
new file mode 100644
index 0000000000..27a7933051
--- /dev/null
+++ b/app/views/antennas/index.html.haml
@@ -0,0 +1,8 @@
+- content_for :page_title do
+  = t('antennas.index.title')
+
+- if @antennas.empty?
+  .muted-hint.center-text= t 'antennas.index.empty'
+- else
+  .applications-list
+    = render partial: 'antenna', collection: @antennas
diff --git a/app/views/application/_card.html.haml b/app/views/application/_card.html.haml
index c28e18522c..ae74f1dc63 100644
--- a/app/views/application/_card.html.haml
+++ b/app/views/application/_card.html.haml
@@ -2,7 +2,7 @@
 - compact ||= false
 
 .card.h-card
-  = link_to account_url, target: '_blank', rel: 'noopener' do
+  = link_to account_url, target: '_blank', rel: 'noopener noreferrer' do
     - unless compact
       .card__img
         = image_tag account.header.url, alt: ''
diff --git a/app/views/application/mailer/_checklist.html.haml b/app/views/application/mailer/_checklist.html.haml
index 4b460fa26d..91c7c98f26 100644
--- a/app/views/application/mailer/_checklist.html.haml
+++ b/app/views/application/mailer/_checklist.html.haml
@@ -29,8 +29,8 @@
                     %div
                       - if defined?(show_apps_buttons) && show_apps_buttons
                         .email-welcome-apps-btns
-                          = link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-app-store.png'), alt: t('user_mailer.welcome.apps_ios_action'), width: 120, height: 40), app_store_url_ios
-                          = link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-google-play.png'), alt: t('user_mailer.welcome.apps_android_action'), width: 120, height: 40), app_store_url_android
+                          = link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-app-store.png'), alt: t('user_mailer.welcome.apps_ios_action'), width: 120, height: 40), 'https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974'
+                          = link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-google-play.png'), alt: t('user_mailer.welcome.apps_android_action'), width: 120, height: 40), 'https://play.google.com/store/apps/details?id=org.joinmastodon.android'
                       - elsif defined?(button_text) && defined?(button_url) && defined?(checked) && !checked
                         = render 'application/mailer/button', text: button_text, url: button_url, has_arrow: false
                     /[if mso]
diff --git a/app/views/application/mailer/_heading.html.haml b/app/views/application/mailer/_heading.html.haml
index 8b1f3f9ffc..9fc5dc7471 100644
--- a/app/views/application/mailer/_heading.html.haml
+++ b/app/views/application/mailer/_heading.html.haml
@@ -1,13 +1,13 @@
--# locals: (title:, image_url: nil, subtitle: nil)
 %table.email-w-full.email-header-heading-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-header-heading-td
       %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
         %tr
-          - if image_url
+          - if defined?(heading_image_url)
             %td.email-header-heading-img-td
-              = image_tag image_url, alt: '', width: 56, height: 56
+              = image_tag heading_image_url, alt: '', width: 56, height: 56
           %td.email-header-heading-txt-td
-            %h1= title
-            - if subtitle
-              %p= subtitle
+            - if defined?(heading_title)
+              %h1= heading_title
+            - if defined?(heading_subtitle)
+              %p= heading_subtitle
diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml
index ebbef00ff3..a778b94212 100644
--- a/app/views/auth/registrations/new.html.haml
+++ b/app/views/auth/registrations/new.html.haml
@@ -21,22 +21,23 @@
     = f.simple_fields_for :account do |ff|
       = ff.input :username,
                  append: "@#{site_hostname}",
-                 input_html: { autocomplete: 'off', pattern: '[a-zA-Z0-9_]+', maxlength: Account::USERNAME_LENGTH_LIMIT, placeholder: ' ' },
+                 input_html: { 'aria-label': t('simple_form.labels.defaults.username'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: Account::USERNAME_LENGTH_LIMIT },
+                 label: false,
                  required: true,
                  wrapper: :with_label
     = f.input :email,
               hint: false,
-              input_html: { autocomplete: 'username', placeholder: ' ' },
-              required: true,
-              wrapper: :with_label
+              input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'username' },
+              placeholder: t('simple_form.labels.defaults.email'),
+              required: true
     = f.input :password,
               hint: false,
-              input_html: { autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last, placeholder: ' ' },
-              required: true,
-              wrapper: :with_label
+              input_html: { 'aria-label': t('simple_form.labels.defaults.password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last },
+              placeholder: t('simple_form.labels.defaults.password'),
+              required: true
     = f.input :password_confirmation,
               hint: false,
-              input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_password'), autocomplete: 'new-password', maxlength: User.password_length.last },
+              input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_password'), autocomplete: 'new-password' },
               placeholder: t('simple_form.labels.defaults.confirm_password'),
               required: true
     = f.input :confirm_password,
@@ -52,14 +53,6 @@
               required: false,
               wrapper: :with_label
 
-  - if Setting.min_age.present?
-    .fields-group
-      = f.input :date_of_birth,
-                as: :date_of_birth,
-                hint: t('simple_form.hints.user.date_of_birth', age: Setting.min_age.to_i),
-                required: true,
-                wrapper: :with_block_label
-
   - if approved_registrations? && @invite.blank?
     %p.lead= t('auth.sign_up.manual_review', domain: site_hostname)
 
@@ -69,7 +62,6 @@
                                       as: :text,
                                       hint: false,
                                       label: false,
-                                      input_html: { maxlength: UserInviteRequest::TEXT_SIZE_LIMIT },
                                       required: Setting.require_invite_text,
                                       wrapper: :with_block_label
 
@@ -79,8 +71,8 @@
   .fields-group
     = f.input :agreement,
               as: :boolean,
-              label: TermsOfService.live.exists? ? t('auth.user_agreement_html', privacy_policy_path: privacy_policy_path, terms_of_service_path: terms_of_service_path) : t('auth.user_privacy_agreement_html', privacy_policy_path: privacy_policy_path),
-              required: false,
+              label: t('auth.privacy_policy_agreement_html', rules_path: about_more_path, privacy_policy_path: privacy_policy_path),
+              required: true,
               wrapper: :with_label
 
   - if Setting.registration_button_message.present?
@@ -89,3 +81,5 @@
 
   .actions
     = f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit
+
+.form-footer= render 'auth/shared/links'
diff --git a/app/views/auth/registrations/rules.html.haml b/app/views/auth/registrations/rules.html.haml
index 4b0159e862..3a05ed54f0 100644
--- a/app/views/auth/registrations/rules.html.haml
+++ b/app/views/auth/registrations/rules.html.haml
@@ -19,11 +19,12 @@
   %ol.rules-list
     - @rules.each do |rule|
       %li
-        %button{ type: 'button', aria: { expanded: 'false' } }
-          .rules-list__text= rule.text
-          .rules-list__hint= rule.hint
+        .rules-list__text= rule.text
+        .rules-list__hint= rule.hint
 
   .stacked-actions
     - accept_path = @invite_code.present? ? public_invite_url(invite_code: @invite_code, accept: @accept_token) : new_user_registration_path(accept: @accept_token)
     = link_to t('auth.rules.accept'), accept_path, class: 'button'
     = link_to t('auth.rules.back'), root_path, class: 'button button-tertiary'
+
+.form-footer= render 'auth/shared/links'
diff --git a/app/views/auth/sessions/new.html.haml b/app/views/auth/sessions/new.html.haml
index b2b45141ee..fb64bab781 100644
--- a/app/views/auth/sessions/new.html.haml
+++ b/app/views/auth/sessions/new.html.haml
@@ -38,8 +38,6 @@
                   input_html: { 'aria-label': t('auth.disable_custom_css') },
                   label: t('auth.disable_custom_css'),
                   wrapper: :with_label
-    - else
-      = f.hidden_field :disable_css
 
     .actions
       = f.button :button, t('auth.login'), type: :submit
diff --git a/app/views/auth/setup/show.html.haml b/app/views/auth/setup/show.html.haml
index 91654ca214..713f77470d 100644
--- a/app/views/auth/setup/show.html.haml
+++ b/app/views/auth/setup/show.html.haml
@@ -11,17 +11,17 @@
 
   = render 'shared/error_messages', object: @user
 
-  %details
-    %summary.lead
-      %strong= t('auth.setup.link_not_received')
+  %p.lead
+    %strong= t('auth.setup.link_not_received')
+  %p.lead= t('auth.setup.email_below_hint_html')
 
-    %p.lead= t('auth.setup.email_below_hint_html')
+  .fields-group
+    = f.input :email,
+              hint: false,
+              input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'off' },
+              required: true
 
-    .fields-group
-      = f.input :email,
-                hint: false,
-                input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.email') },
-                required: true
+  .actions
+    = f.button :button, t('auth.resend_confirmation'), type: :submit, class: 'button timer-button', disabled: true
 
-    .actions
-      = f.button :button, t('auth.resend_confirmation'), type: :submit, class: 'button timer-button', disabled: true
+.form-footer= render 'auth/shared/links'
diff --git a/app/views/auth/shared/_links.html.haml b/app/views/auth/shared/_links.html.haml
index e204fb9ce6..bd2245e111 100644
--- a/app/views/auth/shared/_links.html.haml
+++ b/app/views/auth/shared/_links.html.haml
@@ -11,7 +11,7 @@
     - if controller_name != 'passwords' && controller_name != 'registrations'
       %li= link_to t('auth.forgot_password'), new_user_password_path
 
-    - if controller_name != 'passwords' && controller_name != 'registrations' && params[:with_options].blank?
+    - if controller_name != 'passwords' && controller_name != 'registrations' && params[:with_options].nil?
       %li= link_to t('auth.with_login_options'), new_user_session_path(with_options: '1')
 
   - if controller_name != 'confirmations' && (!user_signed_in? || !current_user.confirmed? || current_user.unconfirmed_email.present?)
diff --git a/app/views/custom_css/show.css.erb b/app/views/custom_css/show.css.erb
index d4b24b2106..78da809ed6 100644
--- a/app/views/custom_css/show.css.erb
+++ b/app/views/custom_css/show.css.erb
@@ -2,3 +2,9 @@
 <%= raw custom_css_styles %>
 
 <%- end %>
+<%- @user_roles.each do |role| %>
+.user-role-<%= role.id %> {
+  --user-role-accent: <%= role.color %>;
+}
+
+<%- end %>
diff --git a/app/views/disputes/strikes/_card.html.haml b/app/views/disputes/strikes/_card.html.haml
index 33e4099cf9..58965ad600 100644
--- a/app/views/disputes/strikes/_card.html.haml
+++ b/app/views/disputes/strikes/_card.html.haml
@@ -27,7 +27,7 @@
                   = material_symbol 'link'
                   = media_attachment.file_file_name
             .strike-card__statuses-list__item__meta
-              = link_to ActivityPub::TagManager.instance.url_for(status), target: '_blank', rel: 'noopener' do
+              = link_to ActivityPub::TagManager.instance.url_for(status), target: '_blank', rel: 'noopener noreferrer' do
                 %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
               - unless status.application.nil?
                 ·
diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml
index 322d820a2a..150dc06759 100644
--- a/app/views/disputes/strikes/show.html.haml
+++ b/app/views/disputes/strikes/show.html.haml
@@ -66,7 +66,7 @@
       .report-notes__item__header
         %span.username
           = link_to @appeal.account.username, can?(:show, @appeal.account) ? admin_account_path(@appeal.account_id) : short_account_url(@appeal.account)
-        %time.relative-formatted{ datetime: @appeal.created_at.iso8601, title: @appeal.created_at }
+        %time.relative-formatted{ datetime: @appeal.created_at.iso8601 }
           = l @appeal.created_at.to_date
 
       .report-notes__item__content
diff --git a/app/views/filters/_filter_fields.html.haml b/app/views/filters/_filter_fields.html.haml
index 18e9a6580e..171531dc31 100644
--- a/app/views/filters/_filter_fields.html.haml
+++ b/app/views/filters/_filter_fields.html.haml
@@ -26,7 +26,7 @@
 .fields-group
   = f.input :filter_action,
             as: :radio_buttons,
-            collection: %i(warn blur hide),
+            collection: %i(half_warn warn hide),
             hint: t('simple_form.hints.filters.action'),
             include_blank: false,
             label_method: ->(action) { filter_action_label(action) },
diff --git a/app/views/filters/statuses/_status_filter.html.haml b/app/views/filters/statuses/_status_filter.html.haml
index 9f4605a09c..d0d04638d2 100644
--- a/app/views/filters/statuses/_status_filter.html.haml
+++ b/app/views/filters/statuses/_status_filter.html.haml
@@ -19,11 +19,11 @@
         = media_attachment.file_file_name
 
     .detailed-status__meta
-      = link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'name-tag', target: '_blank', rel: 'noopener' do
+      = link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'name-tag', target: '_blank', rel: 'noopener noreferrer' do
         = image_tag(status.account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar')
         .username= status.account.acct
       ·
-      = link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', rel: 'noopener' do
+      = link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do
         %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
       - if status.edited?
         ·
diff --git a/app/views/invites/_invite.html.haml b/app/views/invites/_invite.html.haml
index 88479c4cad..7c94062de4 100644
--- a/app/views/invites/_invite.html.haml
+++ b/app/views/invites/_invite.html.haml
@@ -3,7 +3,7 @@
     .input-copy
       .input-copy__wrapper
         = copyable_input value: public_invite_url(invite_code: invite.code)
-      %button.button{ type: :button }= t('generic.copy')
+      %button{ type: :button }= t('generic.copy')
 
   - if invite.valid_for_use?
     %td
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 678f44d017..8e2d8e383a 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -34,13 +34,10 @@
     = csrf_meta_tags unless skip_csrf_meta_tags?
     %meta{ name: 'style-nonce', content: request.content_security_policy_nonce }
 
-    - if server_css?
-      = custom_stylesheet
-    - else
-      = system_stylesheet
+    = stylesheet_link_tag custom_css_path, skip_pipeline: true, host: root_url, media: 'all'
 
     - if user_custom_css?
-      = user_custom_stylesheet
+      = stylesheet_link_tag user_custom_css_path({ version: user_custom_css_version }), skip_pipeline: true, host: root_url, media: 'all'
 
     = yield :header_tags
 
diff --git a/app/views/mail_subscriptions/show.html.haml b/app/views/mail_subscriptions/show.html.haml
index 78de486457..a09dacc4d3 100644
--- a/app/views/mail_subscriptions/show.html.haml
+++ b/app/views/mail_subscriptions/show.html.haml
@@ -16,5 +16,4 @@
     = form.hidden_field :type,
                         value: params[:type]
     = form.button t('mail_subscriptions.unsubscribe.action'),
-                  type: :submit,
-                  class: 'btn'
+                  type: :submit
diff --git a/app/views/notification_mailer/favourite.html.haml b/app/views/notification_mailer/favourite.html.haml
index ec2a048ba7..62c0a39abe 100644
--- a/app/views/notification_mailer/favourite.html.haml
+++ b/app/views/notification_mailer/favourite.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/favorite.png'),
-           subtitle: t('notification_mailer.favourite.body', name: @account.pretty_acct),
-           title: t('notification_mailer.favourite.title')
+  = render 'application/mailer/heading', heading_title: t('notification_mailer.favourite.title'), heading_subtitle: t('notification_mailer.favourite.body', name: @account.pretty_acct), heading_image_url: frontend_asset_url('images/mailer-new/heading/favorite.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/notification_mailer/follow.html.haml b/app/views/notification_mailer/follow.html.haml
index 4e23f7fae7..8247aa5b44 100644
--- a/app/views/notification_mailer/follow.html.haml
+++ b/app/views/notification_mailer/follow.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/user.png'),
-           subtitle: t('notification_mailer.follow.body', name: @account.pretty_acct),
-           title: t('notification_mailer.follow.title')
+  = render 'application/mailer/heading', heading_title: t('notification_mailer.follow.title'), heading_subtitle: t('notification_mailer.follow.body', name: @account.pretty_acct), heading_image_url: frontend_asset_url('images/mailer-new/heading/user.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/notification_mailer/follow_request.html.haml b/app/views/notification_mailer/follow_request.html.haml
index 13a5cf87e8..9344ef7eb3 100644
--- a/app/views/notification_mailer/follow_request.html.haml
+++ b/app/views/notification_mailer/follow_request.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/follow.png'),
-           subtitle: t('notification_mailer.follow_request.body', name: @account.pretty_acct),
-           title: t('notification_mailer.follow_request.title')
+  = render 'application/mailer/heading', heading_title: t('notification_mailer.follow_request.title'), heading_subtitle: t('notification_mailer.follow_request.body', name: @account.pretty_acct), heading_image_url: frontend_asset_url('images/mailer-new/heading/follow.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/notification_mailer/mention.html.haml b/app/views/notification_mailer/mention.html.haml
index 419d0b76ea..83fa5086a6 100644
--- a/app/views/notification_mailer/mention.html.haml
+++ b/app/views/notification_mailer/mention.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/mention.png'),
-           subtitle: t('notification_mailer.mention.body', name: @status.account.pretty_acct),
-           title: t('notification_mailer.mention.title')
+  = render 'application/mailer/heading', heading_title: t('notification_mailer.mention.title'), heading_subtitle: t('notification_mailer.mention.body', name: @status.account.pretty_acct), heading_image_url: frontend_asset_url('images/mailer-new/heading/mention.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/notification_mailer/reblog.html.haml b/app/views/notification_mailer/reblog.html.haml
index f097211bc5..f91c678cf5 100644
--- a/app/views/notification_mailer/reblog.html.haml
+++ b/app/views/notification_mailer/reblog.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/boost.png'),
-           subtitle: t('notification_mailer.reblog.body', name: @account.pretty_acct),
-           title: t('notification_mailer.reblog.title')
+  = render 'application/mailer/heading', heading_title: t('notification_mailer.reblog.title'), heading_subtitle: t('notification_mailer.reblog.body', name: @account.pretty_acct), heading_image_url: frontend_asset_url('images/mailer-new/heading/boost.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/oauth/authorizations/new.html.haml b/app/views/oauth/authorizations/new.html.haml
index 17228d3cae..ca9d12a676 100644
--- a/app/views/oauth/authorizations/new.html.haml
+++ b/app/views/oauth/authorizations/new.html.haml
@@ -35,8 +35,7 @@
         = form.hidden_field :scope,
                             value: @pre_auth.scope
         = form.button t('doorkeeper.authorizations.buttons.authorize'),
-                      type: :submit,
-                      class: 'btn'
+                      type: :submit
 
       = form_with url: oauth_authorization_path, method: :delete do |form|
         = form.hidden_field :client_id,
@@ -53,4 +52,4 @@
                             value: @pre_auth.scope
         = form.button t('doorkeeper.authorizations.buttons.deny'),
                       type: :submit,
-                      class: 'btn negative'
+                      class: 'negative'
diff --git a/app/views/oauth/authorizations/show.html.haml b/app/views/oauth/authorizations/show.html.haml
index f014a1052f..bdff336368 100644
--- a/app/views/oauth/authorizations/show.html.haml
+++ b/app/views/oauth/authorizations/show.html.haml
@@ -4,4 +4,4 @@
     .input-copy
       .input-copy__wrapper
         = copyable_input value: params[:code], class: 'oauth-code'
-      %button.button{ type: :button }= t('generic.copy')
+      %button{ type: :button }= t('generic.copy')
diff --git a/app/views/oauth/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml
index 3745ed219f..eb544d079b 100644
--- a/app/views/oauth/authorized_applications/index.html.haml
+++ b/app/views/oauth/authorized_applications/index.html.haml
@@ -9,7 +9,7 @@
   - @applications.each do |application|
     .applications-list__item{ id: dom_id(application) }
       - if application.website.present?
-        = link_to application.name, application.website, target: '_blank', rel: 'noopener', class: 'announcements-list__item__title'
+        = link_to application.name, application.website, target: '_blank', rel: 'noopener noreferrer', class: 'announcements-list__item__title'
       - else
         %strong.announcements-list__item__title
           = application.name
diff --git a/app/views/redirects/show.html.haml b/app/views/redirects/show.html.haml
index 984a70624b..aa0db350a8 100644
--- a/app/views/redirects/show.html.haml
+++ b/app/views/redirects/show.html.haml
@@ -11,4 +11,4 @@
   .redirect__message
     %h1= t('redirects.title', instance: site_hostname)
     %p= t('redirects.prompt')
-    %p= link_to @redirect_path, @redirect_path, rel: 'noopener'
+    %p= link_to @redirect_path, @redirect_path, rel: 'noreferrer noopener'
diff --git a/app/views/relationships/show.html.haml b/app/views/relationships/show.html.haml
index b062a4f53d..7478b8c5ce 100644
--- a/app/views/relationships/show.html.haml
+++ b/app/views/relationships/show.html.haml
@@ -42,33 +42,13 @@
       %label.batch-table__toolbar__select.batch-checkbox-all
         = check_box_tag :batch_checkbox_all, nil, false
       .batch-table__toolbar__actions
-        - if followed_by_relationship? && !mutual_relationship?
-          = f.button safe_join([material_symbol('person_add'), t('relationships.follow_selected_followers')]),
-                     class: 'table-action-link',
-                     data: { confirm: t('relationships.confirm_follow_selected_followers') },
-                     name: :follow,
-                     type: :submit
+        = f.button safe_join([material_symbol('person_add'), t('relationships.follow_selected_followers')]), name: :follow, class: 'table-action-link', type: :submit, data: { confirm: t('relationships.confirm_follow_selected_followers') } if followed_by_relationship? && !mutual_relationship?
 
-        - unless followed_by_relationship?
-          = f.button safe_join([material_symbol('person_remove'), t('relationships.remove_selected_follows')]),
-                     class: 'table-action-link',
-                     data: { confirm: t('relationships.confirm_remove_selected_follows') },
-                     name: :unfollow,
-                     type: :submit
+        = f.button safe_join([material_symbol('person_remove'), t('relationships.remove_selected_follows')]), name: :unfollow, class: 'table-action-link', type: :submit, data: { confirm: t('relationships.confirm_remove_selected_follows') } unless followed_by_relationship?
 
-        - unless following_relationship?
-          = f.button safe_join([material_symbol('delete'), t('relationships.remove_selected_followers')]),
-                     class: 'table-action-link',
-                     data: { confirm: t('relationships.confirm_remove_selected_followers') },
-                     name: :remove_from_followers,
-                     type: :submit
+        = f.button safe_join([material_symbol('delete'), t('relationships.remove_selected_followers')]), name: :remove_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('relationships.confirm_remove_selected_followers') } unless following_relationship?
 
-        - if followed_by_relationship?
-          = f.button safe_join([material_symbol('delete'), t('relationships.remove_selected_domains')]),
-                     class: 'table-action-link',
-                     data: { confirm: t('admin.reports.are_you_sure') },
-                     name: :remove_domains_from_followers,
-                     type: :submit
+        = f.button safe_join([material_symbol('delete'), t('relationships.remove_selected_domains')]), name: :remove_domains_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } if followed_by_relationship?
     .batch-table__body
       - if @accounts.empty?
         = nothing_here 'nothing-here--under-tabs'
diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml
index 5a151be73b..273c5a4ba6 100644
--- a/app/views/settings/exports/show.html.haml
+++ b/app/views/settings/exports/show.html.haml
@@ -6,39 +6,39 @@
     %tbody
       %tr
         %th= t('exports.storage')
-        %td= number_to_human_size @export_summary.total_storage
+        %td= number_to_human_size @export.total_storage
         %td
       %tr
         %th= t('accounts.posts_tab_heading')
-        %td= number_with_delimiter @export_summary.total_statuses
+        %td= number_with_delimiter @export.total_statuses
         %td
       %tr
         %th= t('admin.accounts.follows')
-        %td= number_with_delimiter @export_summary.total_follows
+        %td= number_with_delimiter @export.total_follows
         %td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv)
       %tr
         %th= t('exports.lists')
-        %td= number_with_delimiter @export_summary.total_lists
+        %td= number_with_delimiter @export.total_lists
         %td= table_link_to 'download', t('exports.csv'), settings_exports_lists_path(format: :csv)
       %tr
         %th= t('admin.accounts.followers')
-        %td= number_with_delimiter @export_summary.total_followers
+        %td= number_with_delimiter @export.total_followers
         %td
       %tr
         %th= t('exports.mutes')
-        %td= number_with_delimiter @export_summary.total_mutes
+        %td= number_with_delimiter @export.total_mutes
         %td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv)
       %tr
         %th= t('exports.blocks')
-        %td= number_with_delimiter @export_summary.total_blocks
+        %td= number_with_delimiter @export.total_blocks
         %td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv)
       %tr
         %th= t('exports.domain_blocks')
-        %td= number_with_delimiter @export_summary.total_domain_blocks
+        %td= number_with_delimiter @export.total_domain_blocks
         %td= table_link_to 'download', t('exports.csv'), settings_exports_domain_blocks_path(format: :csv)
       %tr
         %th= t('exports.bookmarks')
-        %td= number_with_delimiter @export_summary.total_bookmarks
+        %td= number_with_delimiter @export.total_bookmarks
         %td= table_link_to 'download', t('exports.csv'), settings_exports_bookmarks_path(format: :csv)
 
 %hr.spacer/
diff --git a/app/views/settings/imports/show.html.haml b/app/views/settings/imports/show.html.haml
index dd18ac2168..4d50049d3d 100644
--- a/app/views/settings/imports/show.html.haml
+++ b/app/views/settings/imports/show.html.haml
@@ -5,9 +5,9 @@
   .flash-message.warning= t('imports.mismatched_types_warning')
 
 - if @bulk_import.overwrite?
-  %p.hint= t("imports.overwrite_preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, count: @bulk_import.total_items)
+  %p.hint= t("imports.overwrite_preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, total_items: @bulk_import.total_items)
 - else
-  %p.hint= t("imports.preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, count: @bulk_import.total_items)
+  %p.hint= t("imports.preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, total_items: @bulk_import.total_items)
 
 .simple_form
   .actions
diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml
index 195ad2f76e..131d3bdb12 100644
--- a/app/views/settings/preferences/appearance/show.html.haml
+++ b/app/views/settings/preferences/appearance/show.html.haml
@@ -18,7 +18,6 @@
       = f.input :time_zone,
                 collection: ActiveSupport::TimeZone.all.map { |tz| ["(GMT#{tz.formatted_offset}) #{tz.name}", tz.tzinfo.name] },
                 hint: false,
-                selected: current_user.time_zone || Time.zone.tzinfo.name,
                 wrapper: :with_label
 
   .fields-group
@@ -60,7 +59,6 @@
       = ff.input :'web.disable_swiping', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_disable_swiping')
       = ff.input :'web.disable_hover_cards', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_disable_hover_cards')
       = ff.input :'web.use_system_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_font_ui')
-      = ff.input :'web.use_system_scrollbars', wrapper: :with_label, hint: I18n.t('simple_form.hints.defaults.setting_system_scrollbars_ui'), label: I18n.t('simple_form.labels.defaults.setting_system_scrollbars_ui')
 
     .fields-group
       = ff.input :'web.content_font_size',
@@ -119,7 +117,6 @@
     .fields-group
       = ff.input :'web.reblog_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_boost_modal')
       = ff.input :'web.delete_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_delete_modal')
-      = ff.input :'web.missing_alt_text_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_missing_alt_text_modal')
 
     %h4= t 'appearance.sensitive_content'
 
@@ -142,7 +139,6 @@
 
     .fields-group
       = ff.input :'web.expand_content_warnings', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_expand_spoilers')
-      = ff.input :'web.show_avatar_on_filter', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_show_avatar_on_filter')
 
   .actions
     = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/settings/preferences/custom_css/show.html.haml b/app/views/settings/preferences/custom_css/show.html.haml
index c38ca9b63b..03ee400093 100644
--- a/app/views/settings/preferences/custom_css/show.html.haml
+++ b/app/views/settings/preferences/custom_css/show.html.haml
@@ -8,13 +8,6 @@
   = render 'shared/error_messages', object: current_user
 
   = f.simple_fields_for :settings, current_user.settings do |ff|
-    .fields-group
-      = ff.input :'web.use_server_css',
-                 hint: false,
-                 label: I18n.t('simple_form.labels.defaults.setting_use_server_css'),
-                 kmyblue: true,
-                 wrapper: :with_label
-
     .fields-group
       = ff.input :'web.use_custom_css',
                  hint: false,
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index 3ca798718e..b7c816d5b0 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -41,8 +41,8 @@
     .fields-row__column.fields-row__column-6
       .fields-group
         = f.input :avatar,
-                  hint: t('simple_form.hints.defaults.avatar', dimensions: Account::Avatar::AVATAR_GEOMETRY, size: number_to_human_size(Account::Avatar::AVATAR_LIMIT)),
-                  input_html: { accept: Account::Avatar::AVATAR_IMAGE_MIME_TYPES.join(',') },
+                  hint: t('simple_form.hints.defaults.avatar', dimensions: Account::Avatar::AVATAR_GEOMETRY, size: number_to_human_size(Account::Avatar::LIMIT)),
+                  input_html: { accept: Account::Avatar::IMAGE_MIME_TYPES.join(',') },
                   wrapper: :with_block_label
 
     .fields-row__column.fields-row__column-6
@@ -57,8 +57,8 @@
     .fields-row__column.fields-row__column-6
       .fields-group
         = f.input :header,
-                  hint: t('simple_form.hints.defaults.header', dimensions: Account::Header::HEADER_GEOMETRY, size: number_to_human_size(Account::Header::HEADER_LIMIT)),
-                  input_html: { accept: Account::Header::HEADER_IMAGE_MIME_TYPES.join(',') },
+                  hint: t('simple_form.hints.defaults.header', dimensions: Account::Header::HEADER_GEOMETRY, size: number_to_human_size(Account::Header::LIMIT)),
+                  input_html: { accept: Account::Header::IMAGE_MIME_TYPES.join(',') },
                   wrapper: :with_block_label
 
     .fields-row__column.fields-row__column-6
diff --git a/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml b/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml
index 0ba4ad6893..d069ba12a9 100644
--- a/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml
+++ b/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml
@@ -6,4 +6,4 @@
 
   %hr.spacer/
 
-  = link_to t('otp_authentication.setup'), settings_otp_authentication_path, data: { method: :post }, class: 'button button--block'
+  = link_to t('otp_authentication.setup'), settings_otp_authentication_path, data: { method: :post }, class: 'block-button'
diff --git a/app/views/settings/two_factor_authentication/webauthn_credentials/index.html.haml b/app/views/settings/two_factor_authentication/webauthn_credentials/index.html.haml
index 728e08e2bf..3ea5810207 100644
--- a/app/views/settings/two_factor_authentication/webauthn_credentials/index.html.haml
+++ b/app/views/settings/two_factor_authentication/webauthn_credentials/index.html.haml
@@ -14,4 +14,4 @@
 %hr.spacer/
 
 .simple_form
-  = link_to t('webauthn_credentials.add'), new_settings_webauthn_credential_path, class: 'button button--block'
+  = link_to t('webauthn_credentials.add'), new_settings_webauthn_credential_path, class: 'block-button'
diff --git a/app/views/settings/two_factor_authentication_methods/index.html.haml b/app/views/settings/two_factor_authentication_methods/index.html.haml
index 8088b4423a..8b670283b9 100644
--- a/app/views/settings/two_factor_authentication_methods/index.html.haml
+++ b/app/views/settings/two_factor_authentication_methods/index.html.haml
@@ -38,4 +38,4 @@
 %hr.spacer/
 
 .simple_form
-  = link_to t('two_factor_authentication.generate_recovery_codes'), settings_two_factor_authentication_recovery_codes_path, data: { method: :post }, class: 'button button--block'
+  = link_to t('two_factor_authentication.generate_recovery_codes'), settings_two_factor_authentication_recovery_codes_path, data: { method: :post }, class: 'block-button'
diff --git a/app/views/settings/verifications/show.html.haml b/app/views/settings/verifications/show.html.haml
index 0243e3b806..a93ffcebed 100644
--- a/app/views/settings/verifications/show.html.haml
+++ b/app/views/settings/verifications/show.html.haml
@@ -17,7 +17,7 @@
   .input-copy.lead
     .input-copy__wrapper
       = copyable_input value: link_to('Mastodon', ActivityPub::TagManager.instance.url_for(@account), rel: :me)
-    %button.button{ type: :button }= t('generic.copy')
+    %button{ type: :button }= t('generic.copy')
 
   %p.lead= t('verification.extra_instructions_html')
 
@@ -60,12 +60,12 @@
   .input-copy.lead
     .input-copy__wrapper
       = copyable_input value: tag.meta(name: 'fediverse:creator', content: "@#{@account.local_username_and_domain}")
-    %button.button{ type: :button }= t('generic.copy')
+    %button{ type: :button }= t('generic.copy')
 
   %p.lead= t('author_attribution.then_instructions')
 
   .fields-group
-    = f.input :attribution_domains, as: :text, wrapper: :with_block_label, input_html: { value: @account.attribution_domains.join("\n"), placeholder: "example1.com\nexample2.com\nexample3.com", rows: 4, autocapitalize: 'none', autocorrect: 'off' }
+    = f.input :attribution_domains_as_text, as: :text, wrapper: :with_block_label, input_html: { placeholder: "example1.com\nexample2.com\nexample3.com", rows: 4, autocapitalize: 'none', autocorrect: 'off' }
 
   .actions
     = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/shared/_web_app.html.haml b/app/views/shared/_web_app.html.haml
index 4f9a20a9f4..89d6ee567d 100644
--- a/app/views/shared/_web_app.html.haml
+++ b/app/views/shared/_web_app.html.haml
@@ -1,4 +1,3 @@
-- content_for :body_classes, 'app-body'
 - content_for :header_tags do
   - if user_signed_in?
     = preload_pack_asset 'features/compose.js'
diff --git a/app/views/statuses/show.html.haml b/app/views/statuses/show.html.haml
index 507c2e8615..7751337cb0 100644
--- a/app/views/statuses/show.html.haml
+++ b/app/views/statuses/show.html.haml
@@ -16,8 +16,6 @@
   = opengraph 'og:title', "#{display_name(@account)} (#{acct(@account)})"
   = opengraph 'og:url', short_account_status_url(@account, @status)
   = opengraph 'og:published_time', @status.created_at.iso8601
-  - if @status.language.present?
-    = opengraph 'og:locale', @status.language
   = opengraph 'profile:username', acct(@account)[1..]
 
   = render 'og_description', activity: @status
diff --git a/app/views/system_css/show.css.erb b/app/views/system_css/show.css.erb
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml
index 4d3a3318ee..355a0c3f2e 100644
--- a/app/views/tags/show.html.haml
+++ b/app/views/tags/show.html.haml
@@ -1,6 +1,4 @@
 - content_for :header_tags do
-  %link{ rel: :alternate, type: 'application/rss+xml', href: tag_url(@tag) }/
-  %link{ rel: :alternate, type: 'application/activity+json', href: tag_url(@tag) }/
   %meta{ name: 'robots', content: 'noindex, noai, noimageai' }/
   %meta{ name: 'CCBot', content: 'nofollow' }
   = render partial: 'shared/og'
diff --git a/app/views/terms_of_service/show.html.haml b/app/views/terms_of_service/show.html.haml
deleted file mode 100644
index f6ca023da7..0000000000
--- a/app/views/terms_of_service/show.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-- content_for :page_title, t('terms_of_service.title')
-
-- content_for :header_tags do
-  = render partial: 'shared/og'
-
-= render 'shared/web_app'
diff --git a/app/views/user_mailer/announcement_published.html.haml b/app/views/user_mailer/announcement_published.html.haml
deleted file mode 100644
index 1a879e47c9..0000000000
--- a/app/views/user_mailer/announcement_published.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-= content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/user.png'),
-           title: t('user_mailer.announcement_published.title', domain: site_hostname)
-%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
-  %tr
-    %td.email-body-padding-td
-      %table.email-inner-card-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
-        %tr
-          %td.email-inner-card-td.email-prose
-            %p= t('user_mailer.announcement_published.description', domain: site_hostname)
-            = linkify(@announcement.text)
diff --git a/app/views/user_mailer/announcement_published.text.erb b/app/views/user_mailer/announcement_published.text.erb
deleted file mode 100644
index 94bb4029dc..0000000000
--- a/app/views/user_mailer/announcement_published.text.erb
+++ /dev/null
@@ -1,7 +0,0 @@
-<%= t('user_mailer.announcement_published.title') %>
-
-===
-
-<%= t('user_mailer.announcement_published.description', domain: site_hostname) %>
-
-<%= @announcement.text %>
diff --git a/app/views/user_mailer/appeal_approved.html.haml b/app/views/user_mailer/appeal_approved.html.haml
index 2da3e0d3eb..54e9a94a54 100644
--- a/app/views/user_mailer/appeal_approved.html.haml
+++ b/app/views/user_mailer/appeal_approved.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/appeal-approved.png'),
-           subtitle: t('user_mailer.appeal_approved.subtitle'),
-           title: t('user_mailer.appeal_approved.title')
+  = render 'application/mailer/heading', heading_title: t('user_mailer.appeal_approved.title'), heading_subtitle: t('user_mailer.appeal_approved.subtitle'), heading_image_url: frontend_asset_url('images/mailer-new/heading/appeal-approved.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/appeal_rejected.html.haml b/app/views/user_mailer/appeal_rejected.html.haml
index 6f9282507b..b493712b05 100644
--- a/app/views/user_mailer/appeal_rejected.html.haml
+++ b/app/views/user_mailer/appeal_rejected.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/appeal-rejected.png'),
-           subtitle: t('user_mailer.appeal_rejected.subtitle'),
-           title: t('user_mailer.appeal_rejected.title')
+  = render 'application/mailer/heading', heading_title: t('user_mailer.appeal_rejected.title'), heading_subtitle: t('user_mailer.appeal_rejected.subtitle'), heading_image_url: frontend_asset_url('images/mailer-new/heading/appeal-rejected.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/backup_ready.html.haml b/app/views/user_mailer/backup_ready.html.haml
index c25f0b45dd..99a48e7fdb 100644
--- a/app/views/user_mailer/backup_ready.html.haml
+++ b/app/views/user_mailer/backup_ready.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/archive.png'),
-           subtitle: t('user_mailer.backup_ready.explanation'),
-           title: t('user_mailer.backup_ready.title')
+  = render 'application/mailer/heading', heading_title: t('user_mailer.backup_ready.title'), heading_subtitle: t('user_mailer.backup_ready.explanation'), heading_image_url: frontend_asset_url('images/mailer-new/heading/archive.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/confirmation_instructions.html.haml b/app/views/user_mailer/confirmation_instructions.html.haml
index d3e3e8f930..13e68c722b 100644
--- a/app/views/user_mailer/confirmation_instructions.html.haml
+++ b/app/views/user_mailer/confirmation_instructions.html.haml
@@ -1,7 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/email.png'),
-           title: t('devise.mailer.confirmation_instructions.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.confirmation_instructions.title'), heading_image_url: frontend_asset_url('images/mailer-new/heading/email.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/email_changed.html.haml b/app/views/user_mailer/email_changed.html.haml
index 33e35a314b..71678ad029 100644
--- a/app/views/user_mailer/email_changed.html.haml
+++ b/app/views/user_mailer/email_changed.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/email.png'),
-           subtitle: t('devise.mailer.email_changed.explanation'),
-           title: t('devise.mailer.email_changed.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.email_changed.title'), heading_subtitle: t('devise.mailer.email_changed.explanation'), heading_image_url: frontend_asset_url('images/mailer-new/heading/email.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/failed_2fa.html.haml b/app/views/user_mailer/failed_2fa.html.haml
index edac8ef63b..e1da35ce06 100644
--- a/app/views/user_mailer/failed_2fa.html.haml
+++ b/app/views/user_mailer/failed_2fa.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/login.png'),
-           subtitle: t('user_mailer.failed_2fa.explanation'),
-           title: t('user_mailer.failed_2fa.title')
+  = render 'application/mailer/heading', heading_title: t('user_mailer.failed_2fa.title'), heading_subtitle: t('user_mailer.failed_2fa.explanation'), heading_image_url: frontend_asset_url('images/mailer-new/heading/login.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/password_change.html.haml b/app/views/user_mailer/password_change.html.haml
index 350f5bd996..44c8c0dcaf 100644
--- a/app/views/user_mailer/password_change.html.haml
+++ b/app/views/user_mailer/password_change.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/password.png'),
-           subtitle: t('devise.mailer.password_change.explanation'),
-           title: t('devise.mailer.password_change.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.password_change.title'), heading_subtitle: t('devise.mailer.password_change.explanation'), heading_image_url: frontend_asset_url('images/mailer-new/heading/password.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/reconfirmation_instructions.html.haml b/app/views/user_mailer/reconfirmation_instructions.html.haml
index 6f6954f705..854887a7e0 100644
--- a/app/views/user_mailer/reconfirmation_instructions.html.haml
+++ b/app/views/user_mailer/reconfirmation_instructions.html.haml
@@ -1,7 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/email.png'),
-           title: t('devise.mailer.reconfirmation_instructions.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.reconfirmation_instructions.title'), heading_image_url: frontend_asset_url('images/mailer-new/heading/email.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/reset_password_instructions.html.haml b/app/views/user_mailer/reset_password_instructions.html.haml
index 5a1480867c..a1384fcc27 100644
--- a/app/views/user_mailer/reset_password_instructions.html.haml
+++ b/app/views/user_mailer/reset_password_instructions.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/password.png'),
-           subtitle: t('devise.mailer.reset_password_instructions.explanation'),
-           title: t('devise.mailer.reset_password_instructions.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.reset_password_instructions.title'), heading_subtitle: t('devise.mailer.reset_password_instructions.explanation'), heading_image_url: frontend_asset_url('images/mailer-new/heading/password.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/suspicious_sign_in.html.haml b/app/views/user_mailer/suspicious_sign_in.html.haml
index 890d883c44..deee7a1ce1 100644
--- a/app/views/user_mailer/suspicious_sign_in.html.haml
+++ b/app/views/user_mailer/suspicious_sign_in.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/login.png'),
-           subtitle: t('user_mailer.suspicious_sign_in.explanation'),
-           title: t('user_mailer.suspicious_sign_in.title')
+  = render 'application/mailer/heading', heading_title: t('user_mailer.suspicious_sign_in.title'), heading_subtitle: t('user_mailer.suspicious_sign_in.explanation'), heading_image_url: frontend_asset_url('images/mailer-new/heading/login.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/terms_of_service_changed.html.haml b/app/views/user_mailer/terms_of_service_changed.html.haml
deleted file mode 100644
index 2e34eb4990..0000000000
--- a/app/views/user_mailer/terms_of_service_changed.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-= content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/user.png'),
-           subtitle: t('user_mailer.terms_of_service_changed.subtitle', domain: site_hostname),
-           title: t('user_mailer.terms_of_service_changed.title')
-%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
-  %tr
-    %td.email-body-padding-td
-      %table.email-inner-card-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
-        %tr
-          %td.email-inner-card-td.email-prose
-            %p= t('user_mailer.terms_of_service_changed.description_html', path: terms_of_service_version_url(date: @terms_of_service.effective_date), domain: site_hostname, date: l(@terms_of_service.effective_date || Time.zone.today))
-            %p
-              %strong= t('user_mailer.terms_of_service_changed.changelog')
-            = markdown(@terms_of_service.changelog)
-            %p= t('user_mailer.terms_of_service_changed.agreement', domain: site_hostname)
-            %p= t('user_mailer.terms_of_service_changed.sign_off', domain: site_hostname)
diff --git a/app/views/user_mailer/terms_of_service_changed.text.erb b/app/views/user_mailer/terms_of_service_changed.text.erb
deleted file mode 100644
index ccf332ce89..0000000000
--- a/app/views/user_mailer/terms_of_service_changed.text.erb
+++ /dev/null
@@ -1,14 +0,0 @@
-<%= t('user_mailer.terms_of_service_changed.title') %>
-
-===
-
-<%= t('user_mailer.terms_of_service_changed.description', domain: site_hostname, date: l(@terms_of_service.effective_date || Time.zone.today)) %>
-
-=> <%= terms_of_service_version_url(date: @terms_of_service.effective_date) %>
-
-<%= t('user_mailer.terms_of_service_changed.changelog') %>
-
-<%= @terms_of_service.changelog %>
-<%= t('user_mailer.terms_of_service_changed.agreement', domain: site_hostname) %>
-
-<%= t('user_mailer.terms_of_service_changed.sign_off', domain: site_hostname) %>
diff --git a/app/views/user_mailer/two_factor_disabled.html.haml b/app/views/user_mailer/two_factor_disabled.html.haml
index 4f1559d86c..28f6ca6600 100644
--- a/app/views/user_mailer/two_factor_disabled.html.haml
+++ b/app/views/user_mailer/two_factor_disabled.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/2fa-disabled.png'),
-           subtitle: t('devise.mailer.two_factor_disabled.subtitle'),
-           title: t('devise.mailer.two_factor_disabled.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.two_factor_disabled.title'), heading_subtitle: t('devise.mailer.two_factor_disabled.subtitle'), heading_image_url: frontend_asset_url('images/mailer-new/heading/2fa-disabled.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/two_factor_enabled.html.haml b/app/views/user_mailer/two_factor_enabled.html.haml
index 0ed57ea32b..691dc661a0 100644
--- a/app/views/user_mailer/two_factor_enabled.html.haml
+++ b/app/views/user_mailer/two_factor_enabled.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/2fa-enabled.png'),
-           subtitle: t('devise.mailer.two_factor_enabled.subtitle'),
-           title: t('devise.mailer.two_factor_enabled.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.two_factor_enabled.title'), heading_subtitle: t('devise.mailer.two_factor_enabled.subtitle'), heading_image_url: frontend_asset_url('images/mailer-new/heading/2fa-enabled.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/two_factor_recovery_codes_changed.html.haml b/app/views/user_mailer/two_factor_recovery_codes_changed.html.haml
index cc4c21b83b..2d063e4c76 100644
--- a/app/views/user_mailer/two_factor_recovery_codes_changed.html.haml
+++ b/app/views/user_mailer/two_factor_recovery_codes_changed.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/2fa-recovery.png'),
-           subtitle: t('devise.mailer.two_factor_recovery_codes_changed.subtitle'),
-           title: t('devise.mailer.two_factor_recovery_codes_changed.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.two_factor_recovery_codes_changed.title'), heading_subtitle: t('devise.mailer.two_factor_recovery_codes_changed.subtitle'), heading_image_url: frontend_asset_url('images/mailer-new/heading/2fa-recovery.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/warning.html.haml b/app/views/user_mailer/warning.html.haml
index fe8db974ee..837cde550e 100644
--- a/app/views/user_mailer/warning.html.haml
+++ b/app/views/user_mailer/warning.html.haml
@@ -1,7 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/warning.png'),
-           title: t("user_mailer.warning.title.#{@warning.action}")
+  = render 'application/mailer/heading', heading_title: t("user_mailer.warning.title.#{@warning.action}"), heading_image_url: frontend_asset_url('images/mailer-new/heading/warning.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/webauthn_credential_added.html.haml b/app/views/user_mailer/webauthn_credential_added.html.haml
index 9c5cbdd676..3e16766238 100644
--- a/app/views/user_mailer/webauthn_credential_added.html.haml
+++ b/app/views/user_mailer/webauthn_credential_added.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/key-added.png'),
-           subtitle: t('devise.mailer.webauthn_credential.added.explanation'),
-           title: t('devise.mailer.webauthn_credential.added.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.webauthn_credential.added.title'), heading_subtitle: t('devise.mailer.webauthn_credential.added.explanation'), heading_image_url: frontend_asset_url('images/mailer-new/heading/key-added.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/webauthn_credential_deleted.html.haml b/app/views/user_mailer/webauthn_credential_deleted.html.haml
index decfc02c22..59dcb75d3a 100644
--- a/app/views/user_mailer/webauthn_credential_deleted.html.haml
+++ b/app/views/user_mailer/webauthn_credential_deleted.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/key-deleted.png'),
-           subtitle: t('devise.mailer.webauthn_credential.deleted.explanation'),
-           title: t('devise.mailer.webauthn_credential.deleted.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.webauthn_credential.deleted.title'), heading_subtitle: t('devise.mailer.webauthn_credential.deleted.explanation'), heading_image_url: frontend_asset_url('images/mailer-new/heading/key-deleted.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/webauthn_disabled.html.haml b/app/views/user_mailer/webauthn_disabled.html.haml
index 232c951915..0f7c534bc4 100644
--- a/app/views/user_mailer/webauthn_disabled.html.haml
+++ b/app/views/user_mailer/webauthn_disabled.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/key-disabled.png'),
-           subtitle: t('devise.mailer.webauthn_disabled.explanation'),
-           title: t('devise.mailer.webauthn_disabled.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.webauthn_disabled.title'), heading_subtitle: t('devise.mailer.webauthn_disabled.explanation'), heading_image_url: frontend_asset_url('images/mailer-new/heading/key-disabled.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/webauthn_enabled.html.haml b/app/views/user_mailer/webauthn_enabled.html.haml
index 955a557de4..cd11aa5a40 100644
--- a/app/views/user_mailer/webauthn_enabled.html.haml
+++ b/app/views/user_mailer/webauthn_enabled.html.haml
@@ -1,8 +1,5 @@
 = content_for :heading do
-  = render 'application/mailer/heading',
-           image_url: frontend_asset_url('images/mailer-new/heading/key-enabled.png'),
-           subtitle: t('devise.mailer.webauthn_enabled.explanation'),
-           title: t('devise.mailer.webauthn_enabled.title')
+  = render 'application/mailer/heading', heading_title: t('devise.mailer.webauthn_enabled.title'), heading_subtitle: t('devise.mailer.webauthn_enabled.explanation'), heading_image_url: frontend_asset_url('images/mailer-new/heading/key-enabled.png')
 %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
   %tr
     %td.email-body-padding-td
diff --git a/app/views/user_mailer/welcome.html.haml b/app/views/user_mailer/welcome.html.haml
index c37104da79..efc6cad393 100644
--- a/app/views/user_mailer/welcome.html.haml
+++ b/app/views/user_mailer/welcome.html.haml
@@ -1,9 +1,7 @@
 = content_for :heading do
   .email-desktop-flex
     .email-header-left
-      = render 'application/mailer/heading',
-               subtitle: t('user_mailer.welcome.explanation'),
-               title: t('user_mailer.welcome.title', name: @resource.account.username)
+      = render 'application/mailer/heading', heading_title: t('user_mailer.welcome.title', name: @resource.account.username), heading_subtitle: t('user_mailer.welcome.explanation')
     .email-header-right
       .email-header-card
         %table.email-header-card-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
diff --git a/app/views/user_mailer/welcome.text.erb b/app/views/user_mailer/welcome.text.erb
index 383f436f8e..144d44b842 100644
--- a/app/views/user_mailer/welcome.text.erb
+++ b/app/views/user_mailer/welcome.text.erb
@@ -30,8 +30,8 @@
 
 5. <%= t('user_mailer.welcome.apps_title') %>
    <%= t('user_mailer.welcome.apps_step') %>
-   * iOS: <%= app_store_url_ios %>
-   * Android: <%= app_store_url_android %>
+   * iOS: https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974
+   * Android: https://play.google.com/store/apps/details?id=org.joinmastodon.android
 
 ---
 
diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb
index 7a1440ed15..0c6ca026bb 100644
--- a/app/workers/activitypub/delivery_worker.rb
+++ b/app/workers/activitypub/delivery_worker.rb
@@ -62,7 +62,7 @@ class ActivityPub::DeliveryWorker
     stoplight_wrapper.run do
       request_pool.with(@host) do |http_client|
         build_request(http_client).perform do |response|
-          raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || unsalvageable_authorization_failure?(response)
+          raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response)
 
           @performed = true
         end
@@ -70,10 +70,6 @@ class ActivityPub::DeliveryWorker
     end
   end
 
-  def unsalvageable_authorization_failure?(response)
-    @source_account.permanently_unavailable? && response.code == 401
-  end
-
   def stoplight_wrapper
     Stoplight(@inbox_url)
       .with_threshold(STOPLIGHT_FAILURE_THRESHOLD)
diff --git a/app/workers/activitypub/fetch_all_replies_worker.rb b/app/workers/activitypub/fetch_all_replies_worker.rb
deleted file mode 100644
index 849a06d0fa..0000000000
--- a/app/workers/activitypub/fetch_all_replies_worker.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-# frozen_string_literal: true
-
-# Fetch all replies to a status, querying recursively through
-# ActivityPub replies collections, fetching any statuses that
-# we either don't already have or we haven't checked for new replies
-# in the Status::FETCH_REPLIES_COOLDOWN_MINUTES interval
-class ActivityPub::FetchAllRepliesWorker
-  include Sidekiq::Worker
-  include ExponentialBackoff
-  include JsonLdHelper
-
-  sidekiq_options queue: 'pull', retry: 3
-
-  # Global max replies to fetch per request (all replies, recursively)
-  MAX_REPLIES = (ENV['FETCH_REPLIES_MAX_GLOBAL'] || 1000).to_i
-  MAX_PAGES = (ENV['FETCH_REPLIES_MAX_PAGES'] || 500).to_i
-
-  def perform(root_status_id, options = {})
-    @root_status = Status.remote.find_by(id: root_status_id)
-    return unless @root_status&.should_fetch_replies?
-
-    @root_status.touch(:fetched_replies_at)
-    Rails.logger.debug { "FetchAllRepliesWorker - #{@root_status.uri}: Fetching all replies for status: #{@root_status}" }
-
-    uris_to_fetch, n_pages = get_replies(@root_status.uri, MAX_PAGES, options)
-    return if uris_to_fetch.nil?
-
-    fetched_uris = uris_to_fetch.clone.to_set
-
-    until uris_to_fetch.empty? || fetched_uris.length >= MAX_REPLIES || n_pages >= MAX_PAGES
-      next_reply = uris_to_fetch.pop
-      next if next_reply.nil?
-
-      new_reply_uris, new_n_pages = get_replies(next_reply, MAX_PAGES - n_pages, options)
-      next if new_reply_uris.nil?
-
-      new_reply_uris = new_reply_uris.reject { |uri| fetched_uris.include?(uri) }
-
-      uris_to_fetch.concat(new_reply_uris)
-      fetched_uris = fetched_uris.merge(new_reply_uris)
-      n_pages += new_n_pages
-    end
-
-    Rails.logger.debug { "FetchAllRepliesWorker - #{@root_status.uri}: fetched #{fetched_uris.length} replies" }
-
-    # Workers shouldn't be returning anything, but this is used in tests
-    fetched_uris
-  end
-
-  private
-
-  def get_replies(status_uri, max_pages, options = {})
-    replies_collection_or_uri = get_replies_uri(status_uri)
-    return if replies_collection_or_uri.nil?
-
-    ActivityPub::FetchAllRepliesService.new.call(status_uri, replies_collection_or_uri, max_pages: max_pages, **options.deep_symbolize_keys)
-  end
-
-  def get_replies_uri(parent_status_uri)
-    fetch_resource(parent_status_uri, true)&.fetch('replies', nil)
-  rescue => e
-    Rails.logger.info { "FetchAllRepliesWorker - #{@root_status.uri}: Caught exception while resolving replies URI #{parent_status_uri}: #{e} - #{e.message}" }
-    # Raise if we can't get the collection for top-level status to trigger retry
-    raise e if parent_status_uri == @root_status.uri
-
-    nil
-  end
-end
diff --git a/app/workers/activitypub/fetch_replies_worker.rb b/app/workers/activitypub/fetch_replies_worker.rb
index f9b3dbf171..d72bad7452 100644
--- a/app/workers/activitypub/fetch_replies_worker.rb
+++ b/app/workers/activitypub/fetch_replies_worker.rb
@@ -7,7 +7,7 @@ class ActivityPub::FetchRepliesWorker
   sidekiq_options queue: 'pull', retry: 3
 
   def perform(parent_status_id, replies_uri, options = {})
-    ActivityPub::FetchRepliesService.new.call(Status.find(parent_status_id).account.uri, replies_uri, **options.deep_symbolize_keys)
+    ActivityPub::FetchRepliesService.new.call(Status.find(parent_status_id), replies_uri, **options.deep_symbolize_keys)
   rescue ActiveRecord::RecordNotFound
     true
   end
diff --git a/app/workers/activitypub/update_distribution_worker.rb b/app/workers/activitypub/update_distribution_worker.rb
index 9a418f0f3d..a04ac621f3 100644
--- a/app/workers/activitypub/update_distribution_worker.rb
+++ b/app/workers/activitypub/update_distribution_worker.rb
@@ -1,8 +1,6 @@
 # frozen_string_literal: true
 
 class ActivityPub::UpdateDistributionWorker < ActivityPub::RawDistributionWorker
-  DEBOUNCE_DELAY = 5.seconds
-
   sidekiq_options queue: 'push', lock: :until_executed, lock_ttl: 1.day.to_i
 
   # Distribute an profile update to servers that might have a copy
diff --git a/app/workers/admin/distribute_announcement_notification_worker.rb b/app/workers/admin/distribute_announcement_notification_worker.rb
deleted file mode 100644
index a8b9a0bd94..0000000000
--- a/app/workers/admin/distribute_announcement_notification_worker.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::DistributeAnnouncementNotificationWorker
-  include Sidekiq::Worker
-
-  def perform(announcement_id)
-    announcement = Announcement.find(announcement_id)
-
-    announcement.scope_for_notification.find_each do |user|
-      UserMailer.announcement_published(user, announcement).deliver_later!
-    end
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
-end
diff --git a/app/workers/admin/distribute_terms_of_service_notification_worker.rb b/app/workers/admin/distribute_terms_of_service_notification_worker.rb
deleted file mode 100644
index 7370ee87e8..0000000000
--- a/app/workers/admin/distribute_terms_of_service_notification_worker.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-class Admin::DistributeTermsOfServiceNotificationWorker
-  include Sidekiq::Worker
-
-  def perform(terms_of_service_id)
-    terms_of_service = TermsOfService.find(terms_of_service_id)
-
-    terms_of_service.scope_for_notification.find_each do |user|
-      UserMailer.terms_of_service_changed(user, terms_of_service).deliver_later!
-    end
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
-end
diff --git a/app/workers/import/relationship_worker.rb b/app/workers/import/relationship_worker.rb
new file mode 100644
index 0000000000..2298b095a7
--- /dev/null
+++ b/app/workers/import/relationship_worker.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+# NOTE: This is a deprecated worker, only kept to not break ongoing imports
+# on upgrade. See `Import::RowWorker` for its replacement.
+
+class Import::RelationshipWorker
+  include Sidekiq::Worker
+
+  sidekiq_options queue: 'pull', retry: 8, dead: false
+
+  def perform(account_id, target_account_uri, relationship, options)
+    from_account   = Account.find(account_id)
+    target_domain  = domain(target_account_uri)
+    target_account = stoplight_wrapper(target_domain).run { ResolveAccountService.new.call(target_account_uri, { check_delivery_availability: true }) }
+    options.symbolize_keys!
+
+    return if target_account.nil?
+
+    case relationship
+    when 'follow'
+      begin
+        FollowService.new.call(from_account, target_account, **options)
+      rescue ActiveRecord::RecordInvalid
+        raise if FollowLimitValidator.limit_for_account(from_account) < from_account.following_count
+      end
+    when 'unfollow'
+      UnfollowService.new.call(from_account, target_account)
+    when 'block'
+      BlockService.new.call(from_account, target_account)
+    when 'unblock'
+      UnblockService.new.call(from_account, target_account)
+    when 'mute'
+      MuteService.new.call(from_account, target_account, **options)
+    when 'unmute'
+      UnmuteService.new.call(from_account, target_account)
+    end
+  rescue ActiveRecord::RecordNotFound
+    true
+  end
+
+  def domain(uri)
+    domain = uri.is_a?(Account) ? uri.domain : uri.split('@')[1]
+    TagManager.instance.local_domain?(domain) ? nil : TagManager.instance.normalize_domain(domain)
+  end
+
+  def stoplight_wrapper(domain)
+    if domain.present?
+      Stoplight("source:#{domain}")
+        .with_fallback { nil }
+        .with_threshold(1)
+        .with_cool_off_time(5.minutes.seconds)
+        .with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) }
+    else
+      Stoplight('domain-blank')
+    end
+  end
+end
diff --git a/app/workers/import_worker.rb b/app/workers/import_worker.rb
new file mode 100644
index 0000000000..b6afb972a9
--- /dev/null
+++ b/app/workers/import_worker.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# NOTE: This is a deprecated worker, only kept to not break ongoing imports
+# on upgrade. See `ImportWorker` for its replacement.
+
+class ImportWorker
+  include Sidekiq::Worker
+
+  sidekiq_options queue: 'pull', retry: false
+
+  def perform(import_id)
+    import = Import.find(import_id)
+    ImportService.new.call(import)
+  ensure
+    import&.destroy
+  end
+end
diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb
index d4dcb326bc..8e1614ad21 100644
--- a/app/workers/merge_worker.rb
+++ b/app/workers/merge_worker.rb
@@ -5,42 +5,18 @@ class MergeWorker
   include Redisable
   include DatabaseHelper
 
-  def perform(from_account_id, into_id, type = 'home')
+  def perform(from_account_id, into_account_id)
     with_primary do
       @from_account = Account.find(from_account_id)
-    end
-
-    case type
-    when 'home'
-      merge_into_home!(into_id)
-    when 'list'
-      merge_into_list!(into_id)
-    end
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
-
-  private
-
-  def merge_into_home!(into_account_id)
-    with_primary do
       @into_account = Account.find(into_account_id)
     end
 
     with_read_replica do
       FeedManager.instance.merge_into_home(@from_account, @into_account)
     end
+  rescue ActiveRecord::RecordNotFound
+    true
   ensure
     redis.del("account:#{into_account_id}:regeneration")
   end
-
-  def merge_into_list!(into_list_id)
-    with_primary do
-      @into_list = List.find(into_list_id)
-    end
-
-    with_read_replica do
-      FeedManager.instance.merge_into_list(@from_account, @into_list)
-    end
-  end
 end
diff --git a/app/workers/mute_worker.rb b/app/workers/mute_worker.rb
index ebd401dc20..c74f657cba 100644
--- a/app/workers/mute_worker.rb
+++ b/app/workers/mute_worker.rb
@@ -2,18 +2,9 @@
 
 class MuteWorker
   include Sidekiq::Worker
-  include DatabaseHelper
 
   def perform(account_id, target_account_id)
-    with_primary do
-      @account = Account.find(account_id)
-      @target_account = Account.find(target_account_id)
-    end
-
-    with_read_replica do
-      FeedManager.instance.clear_from_home(@account, @target_account)
-      FeedManager.instance.clear_from_lists(@account, @target_account)
-    end
+    FeedManager.instance.clear_from_home(Account.find(account_id), Account.find(target_account_id))
   rescue ActiveRecord::RecordNotFound
     true
   end
diff --git a/app/workers/poll_expiration_notify_worker.rb b/app/workers/poll_expiration_notify_worker.rb
index fe7647024e..b7a60fab84 100644
--- a/app/workers/poll_expiration_notify_worker.rb
+++ b/app/workers/poll_expiration_notify_worker.rb
@@ -8,7 +8,7 @@ class PollExpirationNotifyWorker
   def perform(poll_id)
     @poll = Poll.find(poll_id)
 
-    return if missing_expiration?
+    return if does_not_expire?
     requeue! && return if not_due_yet?
 
     notify_remote_voters_and_owner! if @poll.local?
@@ -24,7 +24,7 @@ class PollExpirationNotifyWorker
 
   private
 
-  def missing_expiration?
+  def does_not_expire?
     @poll.expires_at.nil?
   end
 
diff --git a/app/workers/publish_scheduled_status_worker.rb b/app/workers/publish_scheduled_status_worker.rb
index 0ec081de91..aa5c4a834a 100644
--- a/app/workers/publish_scheduled_status_worker.rb
+++ b/app/workers/publish_scheduled_status_worker.rb
@@ -9,8 +9,6 @@ class PublishScheduledStatusWorker
     scheduled_status = ScheduledStatus.find(scheduled_status_id)
     scheduled_status.destroy!
 
-    return true if scheduled_status.account.user.disabled?
-
     PostStatusService.new.call(
       scheduled_status.account,
       options_with_objects(scheduled_status.params.with_indifferent_access)
diff --git a/app/workers/refollow_worker.rb b/app/workers/refollow_worker.rb
index 7b26e4a062..4b712d3aae 100644
--- a/app/workers/refollow_worker.rb
+++ b/app/workers/refollow_worker.rb
@@ -21,7 +21,7 @@ class RefollowWorker
       # Schedule re-follow
       begin
         FollowService.new.call(follower, target_account, reblogs: reblogs, notify: notify, languages: languages, bypass_limit: true)
-      rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
+      rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Mastodon::UnexpectedResponseError, HTTP::Error, OpenSSL::SSL::SSLError
         next
       end
     end
diff --git a/app/workers/scheduler/scheduled_statuses_scheduler.rb b/app/workers/scheduler/scheduled_statuses_scheduler.rb
index 1dccaa18b0..14397bfa93 100644
--- a/app/workers/scheduler/scheduled_statuses_scheduler.rb
+++ b/app/workers/scheduler/scheduled_statuses_scheduler.rb
@@ -27,11 +27,11 @@ class Scheduler::ScheduledStatusesScheduler
   end
 
   def due_statuses
-    ScheduledStatus.where(scheduled_at: ..time_due_at)
+    ScheduledStatus.where(scheduled_at: ..Time.now.utc + PostStatusService::MIN_SCHEDULE_OFFSET)
   end
 
   def expired_statuses
-    ScheduledExpirationStatus.where(scheduled_at: ..time_due_at)
+    ScheduledExpirationStatus.where(scheduled_at: ..Time.now.utc + PostStatusService::MIN_SCHEDULE_OFFSET)
   end
 
   def publish_scheduled_announcements!
@@ -41,7 +41,7 @@ class Scheduler::ScheduledStatusesScheduler
   end
 
   def due_announcements
-    Announcement.unpublished.where('scheduled_at IS NOT NULL AND scheduled_at <= ?', time_due_at)
+    Announcement.unpublished.where('scheduled_at IS NOT NULL AND scheduled_at <= ?', Time.now.utc + PostStatusService::MIN_SCHEDULE_OFFSET)
   end
 
   def unpublish_expired_announcements!
@@ -51,8 +51,4 @@ class Scheduler::ScheduledStatusesScheduler
   def expired_announcements
     Announcement.published.where('ends_at IS NOT NULL AND ends_at <= ?', Time.now.utc)
   end
-
-  def time_due_at
-    Time.now.utc + ScheduledStatus::MINIMUM_OFFSET
-  end
 end
diff --git a/app/workers/unfilter_notifications_worker.rb b/app/workers/unfilter_notifications_worker.rb
index cb8a46b8f4..53a35ce12c 100644
--- a/app/workers/unfilter_notifications_worker.rb
+++ b/app/workers/unfilter_notifications_worker.rb
@@ -4,14 +4,25 @@ class UnfilterNotificationsWorker
   include Sidekiq::Worker
   include Redisable
 
-  def perform(account_id, from_account_id)
-    @from_account = Account.find_by(id: from_account_id)
-    @recipient    = Account.find_by(id: account_id)
+  # Earlier versions of the feature passed a `notification_request` ID
+  # If `to_account_id` is passed, the first argument is an account ID
+  # TODO for after 4.3.0: drop the single-argument case
+  def perform(notification_request_or_account_id, from_account_id = nil)
+    if from_account_id.present?
+      @notification_request = nil
+      @from_account = Account.find_by(id: from_account_id)
+      @recipient    = Account.find_by(id: notification_request_or_account_id)
+    else
+      @notification_request = NotificationRequest.find_by(id: notification_request_or_account_id)
+      @from_account = @notification_request&.from_account
+      @recipient    = @notification_request&.account
+    end
 
     return if @from_account.nil? || @recipient.nil?
 
     push_to_conversations!
     unfilter_notifications!
+    remove_request!
     decrement_worker_count!
   end
 
@@ -25,6 +36,10 @@ class UnfilterNotificationsWorker
     filtered_notifications.in_batches.update_all(filtered: false)
   end
 
+  def remove_request!
+    @notification_request&.destroy!
+  end
+
   def filtered_notifications
     Notification.where(account: @recipient, from_account: @from_account, filtered: true)
   end
diff --git a/app/workers/unmerge_worker.rb b/app/workers/unmerge_worker.rb
index e8a3bf9b78..e8ac535dfe 100644
--- a/app/workers/unmerge_worker.rb
+++ b/app/workers/unmerge_worker.rb
@@ -6,40 +6,16 @@ class UnmergeWorker
 
   sidekiq_options queue: 'pull'
 
-  def perform(from_account_id, into_id, type = 'home')
+  def perform(from_account_id, into_account_id)
     with_primary do
       @from_account = Account.find(from_account_id)
-    end
-
-    case type
-    when 'home'
-      unmerge_from_home!(into_id)
-    when 'list'
-      unmerge_from_list!(into_id)
-    end
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
-
-  private
-
-  def unmerge_from_home!(into_account_id)
-    with_primary do
       @into_account = Account.find(into_account_id)
     end
 
     with_read_replica do
       FeedManager.instance.unmerge_from_home(@from_account, @into_account)
     end
-  end
-
-  def unmerge_from_list!(into_list_id)
-    with_primary do
-      @into_list = List.find(into_list_id)
-    end
-
-    with_read_replica do
-      FeedManager.instance.unmerge_from_list(@from_account, @into_list)
-    end
+  rescue ActiveRecord::RecordNotFound
+    true
   end
 end
diff --git a/app/workers/web/push_notification_worker.rb b/app/workers/web/push_notification_worker.rb
index 32279a9e74..e771928ef3 100644
--- a/app/workers/web/push_notification_worker.rb
+++ b/app/workers/web/push_notification_worker.rb
@@ -2,90 +2,54 @@
 
 class Web::PushNotificationWorker
   include Sidekiq::Worker
-  include RoutingHelper
 
   sidekiq_options queue: 'push', retry: 5
 
-  TTL     = 48.hours
+  TTL     = 48.hours.to_s
   URGENCY = 'normal'
 
   def perform(subscription_id, notification_id)
     @subscription = Web::PushSubscription.find(subscription_id)
     @notification = Notification.find(notification_id)
 
-    return if @notification.updated_at < TTL.ago
-
     # Polymorphically associated activity could have been deleted
     # in the meantime, so we have to double-check before proceeding
     return unless @notification.activity.present? && @subscription.pushable?(@notification)
 
-    if web_push_request.legacy
-      perform_legacy_request
-    else
-      perform_standard_request
-    end
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
-
-  private
-
-  def perform_legacy_request
-    payload = web_push_request.legacy_encrypt(push_notification_json)
+    payload = web_push_request.encrypt(push_notification_json)
 
     request_pool.with(web_push_request.audience) do |http_client|
       request = Request.new(:post, web_push_request.endpoint, body: payload.fetch(:ciphertext), http_client: http_client)
 
       request.add_headers(
         'Content-Type' => 'application/octet-stream',
-        'Ttl' => TTL.to_s,
+        'Ttl' => TTL,
         'Urgency' => URGENCY,
         'Content-Encoding' => 'aesgcm',
         'Encryption' => "salt=#{Webpush.encode64(payload.fetch(:salt)).delete('=')}",
         'Crypto-Key' => "dh=#{Webpush.encode64(payload.fetch(:server_public_key)).delete('=')};#{web_push_request.crypto_key_header}",
-        'Authorization' => web_push_request.legacy_authorization_header,
-        'Unsubscribe-URL' => subscription_url
+        'Authorization' => web_push_request.authorization_header
       )
 
-      send(request)
-    end
-  end
+      request.perform do |response|
+        # If the server responds with an error in the 4xx range
+        # that isn't about rate-limiting or timeouts, we can
+        # assume that the subscription is invalid or expired
+        # and must be removed
 
-  def perform_standard_request
-    payload = web_push_request.standard_encrypt(push_notification_json)
-
-    request_pool.with(web_push_request.audience) do |http_client|
-      request = Request.new(:post, web_push_request.endpoint, body: payload, http_client: http_client)
-
-      request.add_headers(
-        'Content-Type' => 'application/octet-stream',
-        'Ttl' => TTL.to_s,
-        'Urgency' => URGENCY,
-        'Content-Encoding' => 'aes128gcm',
-        'Authorization' => web_push_request.standard_authorization_header,
-        'Unsubscribe-URL' => subscription_url,
-        'Content-Length' => payload.length.to_s
-      )
-
-      send(request)
-    end
-  end
-
-  def send(request)
-    request.perform do |response|
-      # If the server responds with an error in the 4xx range
-      # that isn't about rate-limiting or timeouts, we can
-      # assume that the subscription is invalid or expired
-      # and must be removed
-
-      if (400..499).cover?(response.code) && ![408, 429].include?(response.code)
-        @subscription.destroy!
-      elsif !(200...300).cover?(response.code)
-        raise Mastodon::UnexpectedResponseError, response
+        if (400..499).cover?(response.code) && ![408, 429].include?(response.code)
+          @subscription.destroy!
+        elsif !(200...300).cover?(response.code)
+          raise Mastodon::UnexpectedResponseError, response
+        end
       end
     end
+  rescue ActiveRecord::RecordNotFound
+    true
   end
 
+  private
+
   def web_push_request
     @web_push_request || WebPushRequest.new(@subscription)
   end
@@ -108,8 +72,4 @@ class Web::PushNotificationWorker
   def request_pool
     RequestPool.current
   end
-
-  def subscription_url
-    api_web_push_subscription_url(id: @subscription.generate_token_for(:unsubscribe))
-  end
 end
diff --git a/bin/bundler-audit b/bin/bundler-audit
deleted file mode 100755
index 334a73784f..0000000000
--- a/bin/bundler-audit
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-#
-# This file was generated by Bundler.
-#
-# The application 'bundler-audit' is installed as part of a gem, and
-# this file is here to facilitate running it.
-#
-
-ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
-
-bundle_binstub = File.expand_path("bundle", __dir__)
-
-if File.file?(bundle_binstub)
-  if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
-    load(bundle_binstub)
-  else
-    abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
-Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
-  end
-end
-
-require "rubygems"
-require "bundler/setup"
-
-load Gem.bin_path("bundler-audit", "bundler-audit")
diff --git a/bin/haml-lint b/bin/haml-lint
deleted file mode 100755
index bd55739400..0000000000
--- a/bin/haml-lint
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-#
-# This file was generated by Bundler.
-#
-# The application 'haml-lint' is installed as part of a gem, and
-# this file is here to facilitate running it.
-#
-
-ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
-
-bundle_binstub = File.expand_path("bundle", __dir__)
-
-if File.file?(bundle_binstub)
-  if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
-    load(bundle_binstub)
-  else
-    abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
-Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
-  end
-end
-
-require "rubygems"
-require "bundler/setup"
-
-load Gem.bin_path("haml_lint", "haml-lint")
diff --git a/bin/i18n-tasks b/bin/i18n-tasks
deleted file mode 100755
index 22cbc1f1b1..0000000000
--- a/bin/i18n-tasks
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-#
-# This file was generated by Bundler.
-#
-# The application 'i18n-tasks' is installed as part of a gem, and
-# this file is here to facilitate running it.
-#
-
-ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
-
-bundle_binstub = File.expand_path("bundle", __dir__)
-
-if File.file?(bundle_binstub)
-  if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
-    load(bundle_binstub)
-  else
-    abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
-Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
-  end
-end
-
-require "rubygems"
-require "bundler/setup"
-
-load Gem.bin_path("i18n-tasks", "i18n-tasks")
diff --git a/bin/prometheus_exporter b/bin/prometheus_exporter
deleted file mode 100755
index 7e0304de9e..0000000000
--- a/bin/prometheus_exporter
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-#
-# This file was generated by Bundler.
-#
-# The application 'prometheus_exporter' is installed as part of a gem, and
-# this file is here to facilitate running it.
-#
-
-ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
-
-bundle_binstub = File.expand_path("bundle", __dir__)
-
-if File.file?(bundle_binstub)
-  if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
-    load(bundle_binstub)
-  else
-    abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
-Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
-  end
-end
-
-require "rubygems"
-require "bundler/setup"
-
-load Gem.bin_path("prometheus_exporter", "prometheus_exporter")
diff --git a/bin/rspec b/bin/rspec
index cb53ebe5f0..d738b23c0b 100755
--- a/bin/rspec
+++ b/bin/rspec
@@ -1,6 +1,5 @@
 #!/usr/bin/env ruby
 # frozen_string_literal: true
-
 #
 # This file was generated by Bundler.
 #
@@ -8,18 +7,9 @@
 # this file is here to facilitate running it.
 #
 
-ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
-
-bundle_binstub = File.expand_path("bundle", __dir__)
-
-if File.file?(bundle_binstub)
-  if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
-    load(bundle_binstub)
-  else
-    abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
-Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
-  end
-end
+require "pathname"
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
+  Pathname.new(__FILE__).realpath)
 
 require "rubygems"
 require "bundler/setup"
diff --git a/bin/rubocop b/bin/rubocop
index b3801537d4..369a05bedb 100755
--- a/bin/rubocop
+++ b/bin/rubocop
@@ -24,7 +24,4 @@ end
 require "rubygems"
 require "bundler/setup"
 
-# explicit rubocop config increases performance slightly while avoiding config confusion.
-ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__))
-
 load Gem.bin_path("rubocop", "rubocop")
diff --git a/config.ru b/config.ru
index 842bccc340..afd13e2112 100644
--- a/config.ru
+++ b/config.ru
@@ -2,6 +2,5 @@
 
 # This file is used by Rack-based servers to start the application.
 
-require_relative 'config/environment'
-
+require File.expand_path('config/environment', __dir__)
 run Rails.application
diff --git a/config/application.rb b/config/application.rb
index ae960f8b24..0013c78858 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -34,11 +34,10 @@ require_relative '../lib/paperclip/transcoder'
 require_relative '../lib/paperclip/type_corrector'
 require_relative '../lib/paperclip/response_with_limit_adapter'
 require_relative '../lib/terrapin/multi_pipe_extensions'
-require_relative '../lib/mastodon/middleware/public_file_server'
-require_relative '../lib/mastodon/middleware/socket_cleanup'
-require_relative '../lib/mastodon/feature'
 require_relative '../lib/mastodon/snowflake'
 require_relative '../lib/mastodon/version'
+require_relative '../lib/mastodon/rack_middleware'
+require_relative '../lib/public_file_server_middleware'
 require_relative '../lib/devise/strategies/two_factor_ldap_authenticatable'
 require_relative '../lib/devise/strategies/two_factor_pam_authenticatable'
 require_relative '../lib/elasticsearch/client_extensions'
@@ -53,6 +52,8 @@ require_relative '../lib/action_dispatch/remote_ip_extensions'
 require_relative '../lib/stoplight/redis_data_store_extensions'
 require_relative '../lib/active_record/database_tasks_extensions'
 require_relative '../lib/active_record/batches'
+require_relative '../lib/active_record/with_recursive'
+require_relative '../lib/arel/union_parenthesizing'
 require_relative '../lib/simple_navigation/item_extensions'
 
 Bundler.require(:pam_authentication) if ENV['PAM_ENABLED'] == 'true'
@@ -60,7 +61,10 @@ Bundler.require(:pam_authentication) if ENV['PAM_ENABLED'] == 'true'
 module Mastodon
   class Application < Rails::Application
     # Initialize configuration defaults for originally generated Rails version.
-    config.load_defaults 8.0
+    config.load_defaults 7.1
+
+    # Explicitly set the cache format version to align with Rails version
+    config.active_support.cache_format_version = 7.1
 
     # Please, add to the `ignore` list any other `lib` subdirectories that do
     # not contain `.rb` files, or that should not be reloaded or eager loaded.
@@ -88,9 +92,13 @@ module Mastodon
     # We use our own middleware for this
     config.public_file_server.enabled = false
 
-    config.middleware.use Mastodon::Middleware::PublicFileServer if Rails.env.local? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true'
+    config.middleware.use PublicFileServerMiddleware if Rails.env.local? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true'
     config.middleware.use Rack::Attack
-    config.middleware.use Mastodon::Middleware::SocketCleanup
+    config.middleware.use Mastodon::RackMiddleware
+
+    initializer :deprecator do |app|
+      app.deprecators[:mastodon] = ActiveSupport::Deprecation.new('4.3', 'mastodon/mastodon')
+    end
 
     config.before_configuration do
       require 'mastodon/redis_configuration'
@@ -105,21 +113,13 @@ module Mastodon
       end
     end
 
-    config.x.captcha = config_for(:captcha)
-    config.x.mastodon = config_for(:mastodon)
-    config.x.translation = config_for(:translation)
-
-    if ENV.fetch('QUERY_LOG_TAGS_ENABLED', 'false') == 'true'
-      config.active_record.query_log_tags_enabled = ENV.fetch('QUERY_LOG_TAGS_ENABLED', 'false') == 'true'
-      config.active_record.query_log_tags = [:namespaced_controller, :action, :sidekiq_job_class]
-    end
-
     config.to_prepare do
       Doorkeeper::AuthorizationsController.layout 'modal'
       Doorkeeper::AuthorizedApplicationsController.layout 'admin'
       Doorkeeper::Application.include ApplicationExtension
       Doorkeeper::AccessGrant.include AccessGrantExtension
       Doorkeeper::AccessToken.include AccessTokenExtension
+      Doorkeeper::OAuth::PreAuthorization.include OauthPreAuthorizationExtension
       Devise::FailureApp.include AbstractController::Callbacks
       Devise::FailureApp.include Localized
     end
diff --git a/config/captcha.yml b/config/captcha.yml
deleted file mode 100644
index a9f54588c7..0000000000
--- a/config/captcha.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-shared:
-  secret_key: <%= ENV.fetch('HCAPTCHA_SECRET_KEY', nil) %>
-  site_key: <%= ENV.fetch('HCAPTCHA_SITE_KEY', nil) %>
diff --git a/config/database.yml b/config/database.yml
index eba20e849f..2898415d80 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -55,11 +55,10 @@ production:
     prepared_statements: <%= ENV['PREPARED_STATEMENTS'] || 'true' %>
   replica:
     <<: *default
-    database: <%= ENV['REPLICA_DB_NAME'] || ENV['DB_NAME'] || 'mastodon_production' %>
-    username: <%= ENV['REPLICA_DB_USER'] || ENV['DB_USER'] || 'mastodon' %>
+    database: <%= ENV['REPLICA_DB_NAME'] ||ENV['DB_NAME'] || 'mastodon_production' %>
+    username: <%= ENV['REPLICA_DB_USER'] ||ENV['DB_USER'] || 'mastodon' %>
     password: <%= (ENV['REPLICA_DB_PASS'] || ENV['DB_PASS'] || '').to_json %>
-    host: <%= ENV['REPLICA_DB_HOST'] || ENV['DB_HOST'] || 'localhost' %>
-    port: <%= ENV['REPLICA_DB_PORT'] || ENV['DB_PORT'] || 5432 %>
-    prepared_statements: <%= ENV['REPLICA_PREPARED_STATEMENTS'] || ENV['PREPARED_STATEMENTS'] || 'true' %>
+    host: <%= ENV['REPLICA_DB_HOST'] ||ENV['DB_HOST'] || 'localhost' %>
+    port: <%= ENV['REPLICA_DB_PORT'] ||ENV['DB_PORT'] || 5432 %>
+    prepared_statements: <%= ENV['PREPARED_STATEMENTS'] || 'true' %>
     replica: true
-    database_tasks: <%= ENV['REPLICA_DB_TASKS'] || 'true' %>
diff --git a/config/environments/development.rb b/config/environments/development.rb
index bbdd9e2fce..74f0913da2 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -16,7 +16,7 @@ Rails.application.configure do
   # Show full error reports.
   config.consider_all_requests_local = true
 
-  # Enable server timing.
+  # Enable server timing
   config.server_timing = true
 
   # Enable/disable caching. By default caching is disabled.
@@ -51,8 +51,6 @@ Rails.application.configure do
   # Don't care if the mailer can't send.
   config.action_mailer.raise_delivery_errors = false
 
-  # Disable caching for Action Mailer templates even if Action Controller
-  # caching is enabled.
   config.action_mailer.perform_caching = false
 
   # Print deprecation notices to the Rails logger.
@@ -79,6 +77,9 @@ Rails.application.configure do
   # Annotate rendered view with file names.
   # config.action_view.annotate_rendered_view_with_filenames = true
 
+  # Uncomment if you wish to allow Action Cable access from any origin.
+  # config.action_cable.disable_request_forgery_protection = true
+
   config.action_mailer.default_options = { from: 'notifications@localhost' }
 
   # If using a Heroku, Vagrant or generic remote development environment,
@@ -89,11 +90,8 @@ Rails.application.configure do
   # TODO: Remove once devise-two-factor data migration complete
   config.x.otp_secret = ENV.fetch('OTP_SECRET', '1fc2b87989afa6351912abeebe31ffc5c476ead9bf8b3d74cbc4a302c7b69a45b40b1bbef3506ddad73e942e15ed5ca4b402bf9a66423626051104f4b5f05109')
 
-  # Raise error when a before_action's only/except options reference missing actions.
+  # Raise error when a before_action's only/except options reference missing actions
   config.action_controller.raise_on_missing_callback_actions = true
-
-  # Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
-  config.generators.apply_rubocop_autocorrect_after_generate!
 end
 
 Redis.raise_deprecations = true
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 6d4c30cd20..5129b73e41 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -15,21 +15,22 @@ Rails.application.configure do
   config.eager_load = true
 
   # Full error reports are disabled and caching is turned on.
-  config.consider_all_requests_local = false
+  config.consider_all_requests_local       = false
   config.action_controller.perform_caching = true
+  config.action_controller.asset_host      = ENV['CDN_HOST'] if ENV['CDN_HOST'].present?
 
   # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment
   # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files).
   # config.require_master_key = true
 
+  # Compress CSS using a preprocessor.
+  # config.assets.css_compressor = :sass
+
   # Do not fallback to assets pipeline if a precompiled asset is missed.
   config.assets.compile = false
 
-  # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead.
-  # config.public_file_server.enabled = false
-
   # Enable serving of images, stylesheets, and JavaScripts from an asset server.
-  config.asset_host = ENV['CDN_HOST'] if ENV['CDN_HOST'].present?
+  # config.asset_host = "http://assets.example.com"
 
   # Specifies the header that your server uses for sending files.
   config.action_dispatch.x_sendfile_header = ENV['SENDFILE_HEADER'] if ENV['SENDFILE_HEADER'].present?
@@ -39,41 +40,29 @@ Rails.application.configure do
   # Allow to specify public IP of reverse proxy if it's needed
   config.action_dispatch.trusted_proxies = ENV['TRUSTED_PROXY_IP'].split(/(?:\s*,\s*|\s+)/).map { |item| IPAddr.new(item) } if ENV['TRUSTED_PROXY_IP'].present?
 
-  # Assume all access to the app is happening through a SSL-terminating reverse proxy.
-  # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
-  # config.assume_ssl = true
-
   # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
   config.force_ssl = true
-  # Skip http-to-https redirect for the default health check endpoint.
   config.ssl_options = {
     redirect: {
       exclude: ->(request) { request.path.start_with?('/health') || request.headers['Host'].end_with?('.onion') || request.headers['Host'].end_with?('.i2p') },
     },
   }
 
-  # Log to STDOUT by default
-  config.logger = ActiveSupport::Logger.new($stdout)
-                                       .tap  { |logger| logger.formatter = ::Logger::Formatter.new }
-                                       .then { |logger| ActiveSupport::TaggedLogging.new(logger) }
+  # Info include generic and useful information about system operation, but avoids logging too much
+  # information to avoid inadvertent exposure of personally identifiable information (PII). If you
+  # want to log everything, set the level to "debug".
+  config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info').to_sym
 
   # Prepend all log lines with the following tags.
   config.log_tags = [:request_id]
 
-  # "info" includes generic and useful information about system operation, but avoids logging too much
-  # information to avoid inadvertent exposure of personally identifiable information (PII). If you
-  # want to log everything, set the level to "debug".
-  config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info')
-
   # Use a different cache store in production.
   config.cache_store = :redis_cache_store, REDIS_CONFIGURATION.cache
 
   # Use a real queuing backend for Active Job (and separate queues per environment).
-  # config.active_job.queue_adapter = :resque
+  # config.active_job.queue_adapter     = :resque
   # config.active_job.queue_name_prefix = "mastodon_production"
 
-  # Disable caching for Action Mailer templates even if Action Controller
-  # caching is enabled.
   config.action_mailer.perform_caching = false
 
   # Ignore bad email addresses and do not raise email delivery errors.
@@ -81,7 +70,7 @@ Rails.application.configure do
   # config.action_mailer.raise_delivery_errors = false
 
   # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
-  # the I18n.default_locale when a translation cannot be found).
+  # English when a translation cannot be found).
   # This setting would typically be `true` to use the `I18n.default_locale`.
   # Some locales are missing translation entries and would have errors:
   # https://github.com/mastodon/mastodon/pull/24727
@@ -100,8 +89,18 @@ Rails.application.configure do
     { key: controller.signature_key_id } if controller.respond_to?(:signed_request?) && controller.signed_request?
   end
 
+  # Use a different logger for distributed setups.
+  # require "syslog/logger"
+  # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")
+
+  # Log to STDOUT by default
+  config.logger = ActiveSupport::Logger.new($stdout)
+                                       .tap  { |logger| logger.formatter = ::Logger::Formatter.new }
+                                       .then { |logger| ActiveSupport::TaggedLogging.new(logger) }
+
   # Do not dump schema after migrations.
   config.active_record.dump_schema_after_migration = false
+
   config.action_mailer.perform_caching = false
 
   # E-mails
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 5406eac9ec..716bf8d31f 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -19,19 +19,19 @@ Rails.application.configure do
   # loading is working properly before deploying your code.
   config.eager_load = ENV['CI'].present?
 
+  config.assets_digest = false
+
   # Show full error reports and disable caching.
-  config.consider_all_requests_local = true
+  config.consider_all_requests_local       = true
   config.action_controller.perform_caching = false
   config.cache_store = :memory_store
 
-  # Render exception templates for rescuable exceptions and raise for other exceptions.
+  # Raise exceptions instead of rendering exception templates.
   config.action_dispatch.show_exceptions = :rescuable
 
   # Disable request forgery protection in test environment.
   config.action_controller.allow_forgery_protection = false
 
-  # Disable caching for Action Mailer templates even if Action Controller
-  # caching is enabled.
   config.action_mailer.perform_caching = false
 
   config.action_mailer.default_options = { from: 'notifications@localhost' }
@@ -41,10 +41,6 @@ Rails.application.configure do
   # ActionMailer::Base.deliveries array.
   config.action_mailer.delivery_method = :test
 
-  # Unlike controllers, the mailer instance doesn't have any context about the
-  # incoming request so you'll need to provide the :host parameter yourself.
-  config.action_mailer.default_url_options = { host: 'www.example.com' }
-
   # Print deprecation notices to the stderr.
   config.active_support.deprecation = :stderr
 
@@ -62,6 +58,7 @@ Rails.application.configure do
   # Raise exceptions for disallowed deprecations.
   config.active_support.disallowed_deprecation = :raise
 
+  config.i18n.default_locale = :en
   config.i18n.fallbacks = true
 
   # Tell Active Support which deprecation messages to disallow.
@@ -73,7 +70,7 @@ Rails.application.configure do
   # Annotate rendered view with file names.
   # config.action_view.annotate_rendered_view_with_filenames = true
 
-  # Raise error when a before_action's only/except options reference missing actions.
+  # Raise error when a before_action's only/except options reference missing actions
   config.action_controller.raise_on_missing_callback_actions = true
 end
 
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index c9aa65523a..328d5c74db 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -68,8 +68,8 @@ ignore_unused:
   - 'admin_mailer.*.subject'
   - 'user_mailer.*.subject'
   - 'notification_mailer.*'
-  - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*'
-  - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*'
+  - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html'
+  - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html'
   - 'mail_subscriptions.unsubscribe.emails.*'
   - 'preferences.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use
   - 'edit_profile.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use
diff --git a/config/initializers/0_post_deployment_migrations.rb b/config/initializers/0_post_deployment_migrations.rb
index 7af678b456..8e4d63a2e5 100644
--- a/config/initializers/0_post_deployment_migrations.rb
+++ b/config/initializers/0_post_deployment_migrations.rb
@@ -4,4 +4,14 @@
 # before other initializers as Rails may otherwise memoize a list of migrations
 # excluding the post deployment migrations.
 
-Mastodon::Database.add_post_migrate_path_to_rails
+unless ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS']
+  Rails.application.config.paths['db'].each do |db_path|
+    path = Rails.root.join(db_path, 'post_migrate').to_s
+
+    Rails.application.config.paths['db/migrate'] << path
+
+    # Rails memoizes migrations at certain points where it won't read the above
+    # path just yet. As such we must also update the following list of paths.
+    ActiveRecord::Migrator.migrations_paths << path
+  end
+end
diff --git a/config/initializers/allowed_private_addresses.rb b/config/initializers/allowed_private_addresses.rb
deleted file mode 100644
index 65c7af03b4..0000000000
--- a/config/initializers/allowed_private_addresses.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-Rails.application.configure do
-  config.x.private_address_exceptions = (ENV['ALLOWED_PRIVATE_ADDRESSES'] || '').split(/(?:\s*,\s*|\s+)/).map { |addr| IPAddr.new(addr) }
-end
diff --git a/config/initializers/cookie_rotator.rb b/config/initializers/cookie_rotator.rb
new file mode 100644
index 0000000000..ccc2c6b21f
--- /dev/null
+++ b/config/initializers/cookie_rotator.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# TODO: remove this file some time after 4.3.0
+
+Rails.application.config.after_initialize do
+  Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
+    authenticated_encrypted_cookie_salt = Rails.application.config.action_dispatch.authenticated_encrypted_cookie_salt
+    signed_cookie_salt = Rails.application.config.action_dispatch.signed_cookie_salt
+
+    secret_key_base = Rails.application.secret_key_base
+
+    key_generator = ActiveSupport::KeyGenerator.new(
+      secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA1
+    )
+    key_len = ActiveSupport::MessageEncryptor.key_len
+
+    old_encrypted_secret = key_generator.generate_key(authenticated_encrypted_cookie_salt, key_len)
+    old_signed_secret = key_generator.generate_key(signed_cookie_salt)
+
+    cookies.rotate :encrypted, old_encrypted_secret
+    cookies.rotate :signed, old_signed_secret
+  end
+end
diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb
index 476f1fb07a..c530693a3f 100644
--- a/config/initializers/cors.rb
+++ b/config/initializers/cors.rb
@@ -23,7 +23,6 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do
                methods: %i(post put delete get patch options)
       resource '/oauth/token', methods: [:post]
       resource '/oauth/revoke', methods: [:post]
-      resource '/oauth/userinfo', methods: [:get, :post]
     end
   end
 end
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index c81ed2443c..6df6e2525f 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -9,9 +9,16 @@ Doorkeeper.configure do
     current_user || redirect_to(new_user_session_url)
   end
 
-  # Disable Resource Owner Password Credentials Grant Flow
-  resource_owner_from_credentials do
-    nil
+  resource_owner_from_credentials do |_routes|
+    user   = User.authenticate_with_ldap(email: request.params[:username], password: request.params[:password]) if Devise.ldap_authentication
+    user ||= User.authenticate_with_pam(email: request.params[:username], password: request.params[:password]) if Devise.pam_authentication
+
+    if user.nil?
+      user = User.find_by(email: request.params[:username])
+      user = nil unless user&.valid_password?(request.params[:password])
+    end
+
+    user unless user&.otp_required_for_login?
   end
 
   # Doorkeeper provides some administrative interfaces for managing OAuth
@@ -52,9 +59,6 @@ Doorkeeper.configure do
   # Issue access tokens with refresh token (disabled by default)
   # use_refresh_token
 
-  # Proof of Key Code Exchange
-  pkce_code_challenge_methods ['S256']
-
   # Forbids creating/updating applications with arbitrary scopes that are
   # not in configuration, i.e. `default_scopes` or `optional_scopes`.
   # (Disabled by default)
@@ -167,7 +171,7 @@ Doorkeeper.configure do
   #   http://tools.ietf.org/html/rfc6819#section-4.4.3
   #
 
-  grant_flows %w(authorization_code client_credentials)
+  grant_flows %w(authorization_code password client_credentials)
 
   # Under some circumstances you might want to have applications auto-approved,
   # so that the user skips the authorization step.
diff --git a/config/initializers/enable_yjit.rb b/config/initializers/enable_yjit.rb
new file mode 100644
index 0000000000..7b1053ec11
--- /dev/null
+++ b/config/initializers/enable_yjit.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+# Automatically enable YJIT as of Ruby 3.3, as it brings very
+# sizeable performance improvements.
+
+# If you are deploying to a memory constrained environment
+# you may want to delete this file, but otherwise it's free
+# performance.
+if defined?(RubyVM::YJIT.enable)
+  Rails.application.config.after_initialize do
+    RubyVM::YJIT.enable
+  end
+end
diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb
index 497ac1325c..e88b020ff3 100644
--- a/config/initializers/filter_parameter_logging.rb
+++ b/config/initializers/filter_parameter_logging.rb
@@ -6,5 +6,5 @@
 # Use this to limit dissemination of sensitive information.
 # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
 Rails.application.config.filter_parameters += [
-  :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc
+  :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
 ]
diff --git a/config/initializers/httplog.rb b/config/initializers/httplog.rb
index 7a009e84d1..02fcef4120 100644
--- a/config/initializers/httplog.rb
+++ b/config/initializers/httplog.rb
@@ -1,12 +1,7 @@
 # frozen_string_literal: true
 
-# Disable httplog in production unless log_level is `debug`
-if !Rails.env.production? || Rails.configuration.log_level == :debug
-  require 'httplog'
-
-  HttpLog.configure do |config|
-    config.logger = Rails.logger
-    config.color = { color: :yellow }
-    config.compact_log = true
-  end
+HttpLog.configure do |config|
+  config.logger = Rails.logger
+  config.color = { color: :yellow }
+  config.compact_log = true
 end
diff --git a/config/initializers/opentelemetry.rb b/config/initializers/opentelemetry.rb
index 2fb445ecf5..8edce03b90 100644
--- a/config/initializers/opentelemetry.rb
+++ b/config/initializers/opentelemetry.rb
@@ -54,9 +54,6 @@ if ENV.keys.any? { |name| name.match?(/OTEL_.*_ENDPOINT/) }
       'OpenTelemetry::Instrumentation::Sidekiq' => {
         span_naming: :job_class, # Use the job class as the span name, otherwise this is the queue name and not very helpful
       },
-      'OpenTelemetry::Instrumentation::Redis' => {
-        trace_root_spans: false, # don't start traces with Redis spans
-      },
     })
 
     prefix    = ENV.fetch('OTEL_SERVICE_NAME_PREFIX', 'mastodon')
@@ -68,39 +65,7 @@ if ENV.keys.any? { |name| name.match?(/OTEL_.*_ENDPOINT/) }
                         "#{prefix}#{separator}#{$PROGRAM_NAME.split('/').last}"
                       end
     c.service_version = Mastodon::Version.to_s
-
-    if Mastodon::Version.source_commit.present?
-      c.resource = OpenTelemetry::SDK::Resources::Resource.create(
-        'vcs.repository.ref.revision' => Mastodon::Version.source_commit,
-        'vcs.repository.url.full' => Mastodon::Version.source_base_url
-      )
-    end
   end
-
-  # This middleware adds the trace_id and span_id to the Rails logging tags for every requests
-  class TelemetryLoggingMiddleware
-    def initialize(app)
-      @app = app
-    end
-
-    def call(env)
-      span = OpenTelemetry::Trace.current_span
-
-      return @app.call(env) unless span.recording?
-
-      span_id = span.context.hex_span_id
-      trace_id = span.context.hex_trace_id
-
-      Rails.logger.tagged("trace_id=#{trace_id}", "span_id=#{span_id}") do
-        @app.call(env)
-      end
-    end
-  end
-
-  Rails.application.configure do
-    config.middleware.insert_before Rails::Rack::Logger, TelemetryLoggingMiddleware
-  end
-
 end
 
 MastodonOTELTracer = OpenTelemetry.tracer_provider.tracer('mastodon')
diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb
index 6d908fa477..ed16d50a76 100644
--- a/config/initializers/paperclip.rb
+++ b/config/initializers/paperclip.rb
@@ -169,7 +169,7 @@ else
 end
 
 Rails.application.reloader.to_prepare do
-  Paperclip.options[:content_type_mappings] = { csv: %w(text/plain text/csv application/csv) }
+  Paperclip.options[:content_type_mappings] = { csv: Import::FILE_TYPES }
 end
 
 # In some places in the code, we rescue this exception, but we don't always
diff --git a/config/initializers/prometheus_exporter.rb b/config/initializers/prometheus_exporter.rb
deleted file mode 100644
index fdfee59dc8..0000000000
--- a/config/initializers/prometheus_exporter.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-if ENV['MASTODON_PROMETHEUS_EXPORTER_ENABLED'] == 'true'
-  require 'prometheus_exporter'
-  require 'prometheus_exporter/middleware'
-
-  if ENV['MASTODON_PROMETHEUS_EXPORTER_LOCAL'] == 'true'
-    require 'prometheus_exporter/server'
-    require 'prometheus_exporter/client'
-
-    # bind is the address, on which the webserver will listen
-    # port is the port that will provide the /metrics route
-    server = PrometheusExporter::Server::WebServer.new bind: ENV.fetch('MASTODON_PROMETHEUS_EXPORTER_HOST', 'localhost'), port: ENV.fetch('MASTODON_PROMETHEUS_EXPORTER_PORT', '9394').to_i
-    server.start
-
-    # wire up a default local client
-    PrometheusExporter::Client.default = PrometheusExporter::LocalClient.new(collector: server.collector)
-  end
-
-  if ENV['MASTODON_PROMETHEUS_EXPORTER_WEB_DETAILED_METRICS'] == 'true'
-    # Optional, as those metrics might generate extra overhead and be redundant with what OTEL provides
-    # Per-action/controller request stats like HTTP status and timings
-    Rails.application.middleware.unshift PrometheusExporter::Middleware
-  else
-    # Include stripped down version of PrometheusExporter::Middleware that only collects queue time
-    require 'mastodon/middleware/prometheus_queue_time'
-    Rails.application.middleware.unshift Mastodon::Middleware::PrometheusQueueTime, instrument: false
-  end
-end
diff --git a/config/initializers/settings_digests.rb b/config/initializers/settings_digests.rb
deleted file mode 100644
index 2a5d925c70..0000000000
--- a/config/initializers/settings_digests.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-Rails.application.config.to_prepare do
-  custom_css = begin
-    Setting.custom_css
-  rescue ActiveRecord::AdapterError # Running without a database, not migrated, no connection, etc
-    nil
-  end
-
-  if custom_css.present?
-    Rails
-      .cache
-      .write(
-        :setting_digest_custom_css,
-        Digest::SHA256.hexdigest(custom_css)
-      )
-  end
-end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index fca0bec422..5b281c4339 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -22,48 +22,6 @@ Sidekiq.configure_server do |config|
     end
   end
 
-  if ENV['MASTODON_PROMETHEUS_EXPORTER_ENABLED'] == 'true'
-    require 'prometheus_exporter'
-    require 'prometheus_exporter/instrumentation'
-
-    config.on :startup do
-      # Ruby process metrics (memory, GC, etc)
-      PrometheusExporter::Instrumentation::Process.start type: 'sidekiq'
-
-      # Sidekiq process metrics (concurrency, busy, etc)
-      PrometheusExporter::Instrumentation::SidekiqProcess.start
-
-      # ActiveRecord metrics (connection pool usage)
-      PrometheusExporter::Instrumentation::ActiveRecord.start(
-        custom_labels: { type: 'sidekiq' },
-        config_labels: [:database, :host]
-      )
-
-      if ENV['MASTODON_PROMETHEUS_EXPORTER_SIDEKIQ_DETAILED_METRICS'] == 'true'
-        # Optional, as those metrics might generate extra overhead and be redundant with what OTEL provides
-
-        # Per-job metrics
-        config.server_middleware do |chain|
-          chain.add PrometheusExporter::Instrumentation::Sidekiq
-        end
-        config.death_handlers << PrometheusExporter::Instrumentation::Sidekiq.death_handler
-
-        # Per-queue metrics for queues handled by this process (size, latency, etc)
-        # They will be reported by every process handling those queues, so do not sum them up
-        PrometheusExporter::Instrumentation::SidekiqQueue.start
-
-        # Global Sidekiq metrics (size of the global queues, number of jobs, etc)
-        # Will be the same for every Sidekiq process
-        PrometheusExporter::Instrumentation::SidekiqStats.start
-      end
-    end
-
-    at_exit do
-      # Wait for the latest metrics to be reported before shutting down
-      PrometheusExporter::Client.default.stop(wait_timeout_seconds: 10)
-    end
-  end
-
   config.server_middleware do |chain|
     chain.add Mastodon::SidekiqMiddleware
   end
diff --git a/config/initializers/statistics.rb b/config/initializers/statistics.rb
deleted file mode 100644
index 9b35ac96c4..0000000000
--- a/config/initializers/statistics.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-return unless defined?(Rails::Command::StatsCommand)
-
-[
-  %w(AppLibs app/lib),
-  %w(Policies app/policies),
-  %w(Presenters app/presenters),
-  %w(Serializers app/serializers),
-  %w(Services app/services),
-  %w(Validators app/validators),
-  %w(Workers app/workers),
-].each do |name, directory|
-  Rails::CodeStatistics.register_directory(name.titleize, directory)
-end
diff --git a/config/initializers/webauthn.rb b/config/initializers/webauthn.rb
index ad8af3876c..40dfeb8317 100644
--- a/config/initializers/webauthn.rb
+++ b/config/initializers/webauthn.rb
@@ -3,7 +3,7 @@
 WebAuthn.configure do |config|
   # This value needs to match `window.location.origin` evaluated by
   # the User Agent during registration and authentication ceremonies.
-  config.allowed_origins = ["#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}"]
+  config.origin = "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}"
 
   # Relying Party name for display purposes
   config.rp_name = 'Mastodon'
diff --git a/config/locales/activerecord.be.yml b/config/locales/activerecord.be.yml
index f95fbd77d0..b1495c2855 100644
--- a/config/locales/activerecord.be.yml
+++ b/config/locales/activerecord.be.yml
@@ -15,6 +15,8 @@ be:
       user/invite_request:
         text: Прычына
     errors:
+      messages:
+        too_many_lines: перавышана абмежаванне ў %{limit} радкоў
       models:
         account:
           attributes:
@@ -33,11 +35,6 @@ be:
           attributes:
             data:
               malformed: дэфармаваны
-        list_account:
-          attributes:
-            account_id:
-              taken: ужо ў спісе
-          must_be_following: мусіць быць падпісаным уліковым запісам
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.bg.yml b/config/locales/activerecord.bg.yml
index 0b33e953ae..221be3f680 100644
--- a/config/locales/activerecord.bg.yml
+++ b/config/locales/activerecord.bg.yml
@@ -20,11 +20,10 @@ bg:
           invalid: не е действително име на домейн
       messages:
         invalid_domain_on_line: "%{value} не е действително име на домейн"
+        too_many_lines: е над ограничение от %{limit} реда
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: съдържа стойности с липсващи етикети
             username:
               invalid: трябва да е само буквено-цифрено и долни черти
               reserved: е запазено
@@ -40,23 +39,12 @@ bg:
           attributes:
             data:
               malformed: е деформиран
-        list_account:
-          attributes:
-            account_id:
-              taken: е вече в списъка
-          must_be_following: трябва да е последван акаунт
         status:
           attributes:
             reblog:
               taken: от публикациите вече съществуват
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: е твърде скоро и трябва да е по-късно от %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: е под възрастовата граница
             email:
               blocked: използва се забранен доставчик на услуга за е-поща
               unreachable: изглежда не съществува
diff --git a/config/locales/activerecord.ca.yml b/config/locales/activerecord.ca.yml
index f53f7f364a..9fa0f704b0 100644
--- a/config/locales/activerecord.ca.yml
+++ b/config/locales/activerecord.ca.yml
@@ -20,11 +20,10 @@ ca:
           invalid: no és un nom de domini vàlid
       messages:
         invalid_domain_on_line: "%{value} no és un nom de domini vàlid"
+        too_many_lines: sobrepassa el límit de %{limit} línies
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: conté valors sense etiqueta
             username:
               invalid: només pot contenir lletres, números i guions baixos
               reserved: està reservat
@@ -40,23 +39,12 @@ ca:
           attributes:
             data:
               malformed: està mal format
-        list_account:
-          attributes:
-            account_id:
-              taken: ja és a la llista
-          must_be_following: ha de ser un compte que seguiu
         status:
           attributes:
             reblog:
               taken: de la publicació ja existeix
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: és massa aviat, ha de ser després de %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: és inferior a l'edat mínima
             email:
               blocked: utilitza un proveïdor de correu-e no autoritzat
               unreachable: sembla que no existeix
diff --git a/config/locales/activerecord.cs.yml b/config/locales/activerecord.cs.yml
index 38708713d2..fa551e0f6e 100644
--- a/config/locales/activerecord.cs.yml
+++ b/config/locales/activerecord.cs.yml
@@ -20,11 +20,10 @@ cs:
           invalid: není platný název domény
       messages:
         invalid_domain_on_line: "%{value} není platný název domény"
+        too_many_lines: překročil limit %{limit} řádků
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: obsahuje hodnoty s chybějícími popisky
             username:
               invalid: musí obsahovat pouze písmena, číslice a podtržítka
               reserved: je vyhrazeno
@@ -40,23 +39,12 @@ cs:
           attributes:
             data:
               malformed: je chybný
-        list_account:
-          attributes:
-            account_id:
-              taken: již je na seznamu
-          must_be_following: musí být sledovaný účet
         status:
           attributes:
             reblog:
               taken: příspěvku již existuje
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: je příliš brzy, musí být později než %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: je pod věkovou hranicí
             email:
               blocked: používá zakázanou e-mailovou službu
               unreachable: pravděpodobně neexistuje
diff --git a/config/locales/activerecord.cy.yml b/config/locales/activerecord.cy.yml
index c201016be6..0ad257db50 100644
--- a/config/locales/activerecord.cy.yml
+++ b/config/locales/activerecord.cy.yml
@@ -3,7 +3,7 @@ cy:
   activerecord:
     attributes:
       poll:
-        expires_at: Dyddiad cau
+        expires_at: Terfyn amser
         options: Dewisiadau
       user:
         agreement: Cytundeb gwasanaeth
@@ -20,11 +20,10 @@ cy:
           invalid: "- nid yw'n enw parth dilys"
       messages:
         invalid_domain_on_line: Nid yw %{value} yn enw parth dilys
+        too_many_lines: "- dros y terfyn o %{limit} llinell"
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: yn cynnwys gwerthoedd gyda labeli coll
             username:
               invalid: rhaid iddo gynnwys dim ond llythrennau, rhifau a thanlinellau
               reserved: wedi ei neilltuo
@@ -40,23 +39,12 @@ cy:
           attributes:
             data:
               malformed: wedi'i gamffurfio
-        list_account:
-          attributes:
-            account_id:
-              taken: eisoes ar y rhestr
-          must_be_following: rhaid iddo fod yn gyfrif dilyn
         status:
           attributes:
             reblog:
               taken: o'r statws yn bodoli'n barod
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: yn rhy fuan, rhaid iddo fod yn hwyrach na %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: yn iau na'r terfyn oedran
             email:
               blocked: yn defnyddio darparwr e-bost nad yw'n cael ei ganiatáu
               unreachable: nid yw i weld yn bodoli
@@ -65,7 +53,7 @@ cy:
         user_role:
           attributes:
             permissions_as_keys:
-              dangerous: yn cynnwys caniatâd nad ydyn nhw'n ddiogel ar gyfer rôl sail
+              dangerous: yn cynnwys caniatâd nad ydynt yn ddiogel ar gyfer rôl sail
               elevated: yn methu a chynnwys caniatâd nad yw eich rôl cyfredol yn ei gynnwys
               own_role: nid oes modd ei newid gyda'ch rôl cyfredol
             position:
@@ -74,4 +62,4 @@ cy:
         webhook:
           attributes:
             events:
-              invalid_permissions: nid oes modd cynnwys digwyddiadau nad oes gennych yr hawl iddyn nhw
+              invalid_permissions: ni ellir cynnwys digwyddiadau nad oes gennych yr hawl iddynt
diff --git a/config/locales/activerecord.da.yml b/config/locales/activerecord.da.yml
index 7b49c18ca3..35151f477d 100644
--- a/config/locales/activerecord.da.yml
+++ b/config/locales/activerecord.da.yml
@@ -20,11 +20,10 @@ da:
           invalid: er ikke et gyldigt domænenavn
       messages:
         invalid_domain_on_line: "%{value} er ikke et gyldigt domænenavn"
+        too_many_lines: overstiger grænsen på %{limit} linjer
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: indeholder værdier med manglende etiketter
             username:
               invalid: må kun indeholde cifre, bogstaver og understreger
               reserved: er reserveret
@@ -40,23 +39,12 @@ da:
           attributes:
             data:
               malformed: har ugyldigt format
-        list_account:
-          attributes:
-            account_id:
-              taken: er allerede på listen
-          must_be_following: skal være konto, der følges
         status:
           attributes:
             reblog:
               taken: af status findes allerede
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: er for tidligt, skal være efter %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: er under alderskravet
             email:
               blocked: bruger en ikke-tilladt e-mailudbyder
               unreachable: ser ikke ud til at eksistere
diff --git a/config/locales/activerecord.de.yml b/config/locales/activerecord.de.yml
index 4ae7aec5dd..b4bcd660d8 100644
--- a/config/locales/activerecord.de.yml
+++ b/config/locales/activerecord.de.yml
@@ -20,11 +20,10 @@ de:
           invalid: ist kein gültiger Domain-Name
       messages:
         invalid_domain_on_line: "%{value} ist kein gültiger Domain-Name"
+        too_many_lines: übersteigt das Limit von %{limit} Zeilen
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: enthält Werte, bei denen Beschriftungen fehlen
             username:
               invalid: nur Buchstaben, Ziffern und Unterstriche
               reserved: ist bereits vergeben
@@ -40,23 +39,12 @@ de:
           attributes:
             data:
               malformed: ist fehlerhaft
-        list_account:
-          attributes:
-            account_id:
-              taken: befindet sich bereits auf der Liste
-          must_be_following: muss ein gefolgtes Konto sein
         status:
           attributes:
             reblog:
               taken: des Beitrags existiert bereits
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: Datum muss später als %{date} sein
         user:
           attributes:
-            date_of_birth:
-              below_limit: liegt unterhalb der Altersgrenze
             email:
               blocked: verwendet einen unerlaubten E-Mail-Anbieter
               unreachable: scheint nicht zu existieren
diff --git a/config/locales/activerecord.el.yml b/config/locales/activerecord.el.yml
index 58e7a7df3f..a476221616 100644
--- a/config/locales/activerecord.el.yml
+++ b/config/locales/activerecord.el.yml
@@ -20,11 +20,10 @@ el:
           invalid: δεν είναι έγκυρο όνομα τομέα
       messages:
         invalid_domain_on_line: το %{value} δεν είναι έγκυρο όνομα τομέα
+        too_many_lines: υπερβαίνει το όριο των %{limit} γραμμών
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: περιέχει τιμές με ετικέτες που λείπουν
             username:
               invalid: μόνο γράμματα, αριθμοί και κάτω παύλες
               reserved: είναι δεσμευμένο
@@ -40,19 +39,10 @@ el:
           attributes:
             data:
               malformed: δεν είναι έγκυρα
-        list_account:
-          attributes:
-            account_id:
-              taken: είναι ήδη στη λίστα
-          must_be_following: πρέπει να είναι ένας λογαριασμός που ακολουθείς
         status:
           attributes:
             reblog:
               taken: της ανάρτησης υπάρχει ήδη
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: είναι πολύ σύντομα, πρέπει να είναι μετά από %{date}
         user:
           attributes:
             email:
diff --git a/config/locales/activerecord.en-GB.yml b/config/locales/activerecord.en-GB.yml
index 4e7f7b3e20..72edf5e02f 100644
--- a/config/locales/activerecord.en-GB.yml
+++ b/config/locales/activerecord.en-GB.yml
@@ -20,11 +20,10 @@ en-GB:
           invalid: is not a valid domain name
       messages:
         invalid_domain_on_line: "%{value} is not a valid domain name"
+        too_many_lines: is over the limit of %{limit} lines
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contains values with missing labels
             username:
               invalid: must contain only letters, numbers and underscores
               reserved: is reserved
@@ -40,11 +39,6 @@ en-GB:
           attributes:
             data:
               malformed: is malformed
-        list_account:
-          attributes:
-            account_id:
-              taken: is already on the list
-          must_be_following: must be a followed account
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml
index 6940d589ca..e135856036 100644
--- a/config/locales/activerecord.en.yml
+++ b/config/locales/activerecord.en.yml
@@ -20,11 +20,10 @@ en:
           invalid: is not a valid domain name
       messages:
         invalid_domain_on_line: "%{value} is not a valid domain name"
+        too_many_lines: is over the limit of %{limit} lines
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contains values with missing labels
             username:
               invalid: must contain only letters, numbers and underscores
               reserved: is reserved
@@ -40,23 +39,12 @@ en:
           attributes:
             data:
               malformed: is malformed
-        list_account:
-          attributes:
-            account_id:
-              taken: is already on the list
-          must_be_following: must be a followed account
         status:
           attributes:
             reblog:
               taken: of post already exists
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: is too soon, must be later than %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: is below the age limit
             email:
               blocked: uses a disallowed e-mail provider
               unreachable: does not seem to exist
diff --git a/config/locales/activerecord.eo.yml b/config/locales/activerecord.eo.yml
index 57012564be..45149cd117 100644
--- a/config/locales/activerecord.eo.yml
+++ b/config/locales/activerecord.eo.yml
@@ -20,11 +20,10 @@ eo:
           invalid: ne estas valida domajna nomo
       messages:
         invalid_domain_on_line: "%{value} ne estas valida domajna nomo"
+        too_many_lines: superas la limon de %{limit} linioj
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: enhavas valorojn kun mankantaj etikedoj
             username:
               invalid: devas enhavi nur literojn, ciferojn kaj substrekojn
               reserved: rezervita
@@ -40,19 +39,10 @@ eo:
           attributes:
             data:
               malformed: estas misformita
-        list_account:
-          attributes:
-            account_id:
-              taken: jam estas sur la listo
-          must_be_following: devas esti sekvata konto
         status:
           attributes:
             reblog:
               taken: de afiŝo jam ekzistas
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: estas tro frue, devas esti pli malfrue ol %{date}
         user:
           attributes:
             email:
diff --git a/config/locales/activerecord.es-AR.yml b/config/locales/activerecord.es-AR.yml
index 62a409a353..ba4d148c8b 100644
--- a/config/locales/activerecord.es-AR.yml
+++ b/config/locales/activerecord.es-AR.yml
@@ -20,11 +20,10 @@ es-AR:
           invalid: no es un nombre de dominio válido
       messages:
         invalid_domain_on_line: "%{value} no es un nombre de dominio válido"
+        too_many_lines: está por encima del límite de %{limit} líneas
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contiene valores con etiquetas faltantes
             username:
               invalid: sólo letras, números y subguiones ("_")
               reserved: está reservado
@@ -40,23 +39,12 @@ es-AR:
           attributes:
             data:
               malformed: está malformado
-        list_account:
-          attributes:
-            account_id:
-              taken: ya está en la lista
-          must_be_following: debe ser una cuenta seguida
         status:
           attributes:
             reblog:
               taken: del mensaje ya existe
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: es demasiado pronto, debe ser posterior a %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: está por debajo de la edad mínima
             email:
               blocked: usa un proveedor de correo electrónico no permitido
               unreachable: no parece existir
diff --git a/config/locales/activerecord.es-MX.yml b/config/locales/activerecord.es-MX.yml
index 02384f1c71..999ac6859a 100644
--- a/config/locales/activerecord.es-MX.yml
+++ b/config/locales/activerecord.es-MX.yml
@@ -20,11 +20,10 @@ es-MX:
           invalid: no es un nombre de dominio válido
       messages:
         invalid_domain_on_line: "%{value} no es un nombre de dominio válido"
+        too_many_lines: excede el límite de %{limit} líneas
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contiene valores a los que les faltan etiquetas
             username:
               invalid: debe contener sólo letras, números y guiones bajos
               reserved: está reservado
@@ -40,23 +39,12 @@ es-MX:
           attributes:
             data:
               malformed: tiene un formato incorrecto
-        list_account:
-          attributes:
-            account_id:
-              taken: ya está en la lista
-          must_be_following: debe ser una cuenta seguida
         status:
           attributes:
             reblog:
               taken: de la publicación ya existe
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: es demasiado pronto, debe ser posterior al %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: está por debajo del límite de edad
             email:
               blocked: utiliza un proveedor de correo no autorizado
               unreachable: no parece existir
diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml
index 94f29365e9..16e2c66cbe 100644
--- a/config/locales/activerecord.es.yml
+++ b/config/locales/activerecord.es.yml
@@ -20,11 +20,10 @@ es:
           invalid: no es un nombre de dominio válido
       messages:
         invalid_domain_on_line: "%{value} no es un nombre de dominio válido"
+        too_many_lines: excede el límite de %{limit} líneas
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contiene valores con etiquetas que faltan
             username:
               invalid: solo puede contener letras, números y guiones bajos
               reserved: está reservado
@@ -40,23 +39,12 @@ es:
           attributes:
             data:
               malformed: tiene un formato incorrecto
-        list_account:
-          attributes:
-            account_id:
-              taken: ya está en la lista
-          must_be_following: debe ser una cuenta que sigas
         status:
           attributes:
             reblog:
               taken: de la publicación ya existe
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: es demasiado pronto, debe ser posterior a %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: está por debajo de la edad mínima
             email:
               blocked: utiliza un proveedor de correo no autorizado
               unreachable: no parece existir
diff --git a/config/locales/activerecord.et.yml b/config/locales/activerecord.et.yml
index 308a97cb3e..7af2c362db 100644
--- a/config/locales/activerecord.et.yml
+++ b/config/locales/activerecord.et.yml
@@ -20,6 +20,7 @@ et:
           invalid: pole kehtiv domeeninimi
       messages:
         invalid_domain_on_line: "%{value} ei ole kehtiv domeeninimi"
+        too_many_lines: on üle limiidi %{limit} rida
       models:
         account:
           attributes:
@@ -38,11 +39,6 @@ et:
           attributes:
             data:
               malformed: on vigasel kujul
-        list_account:
-          attributes:
-            account_id:
-              taken: on juba loetelus
-          must_be_following: peab olema jälgitav konto
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.eu.yml b/config/locales/activerecord.eu.yml
index 8b4235eb99..e335196da9 100644
--- a/config/locales/activerecord.eu.yml
+++ b/config/locales/activerecord.eu.yml
@@ -20,11 +20,10 @@ eu:
           invalid: ez da domeinu izen baliogarria
       messages:
         invalid_domain_on_line: "%{value} ez da domeinu izen baliogarria"
+        too_many_lines: "%{limit} lerroko muga gainditzen du"
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: falta diren etiketak dituzten balioak ditu
             username:
               invalid: letrak, zenbakiak eta azpimarrak soilik izan behar ditu
               reserved: erreserbatuta dago
@@ -40,11 +39,6 @@ eu:
           attributes:
             data:
               malformed: gaizki eratua dago
-        list_account:
-          attributes:
-            account_id:
-              taken: dagoeneko zerrendan dago
-          must_be_following: jarraitutako kontua izan behar da
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.fa.yml b/config/locales/activerecord.fa.yml
index 5db02e26c7..81e54ed3a9 100644
--- a/config/locales/activerecord.fa.yml
+++ b/config/locales/activerecord.fa.yml
@@ -20,11 +20,10 @@ fa:
           invalid: نام دامنهٔ معتبری نیست
       messages:
         invalid_domain_on_line: "%{value} نام دامنهٔ معتبری نیست"
+        too_many_lines: بیش از کران %{limit} خط است
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: حاوی مقادیری با برچسب های گم شده
             username:
               invalid: تنها حروف، اعداد، و زیرخط
               reserved: محفوظ است
@@ -40,19 +39,10 @@ fa:
           attributes:
             data:
               malformed: بدریخت است
-        list_account:
-          attributes:
-            account_id:
-              taken: در حال حاضر در لیست است
-          must_be_following: باید یک حساب دنبال شده باشد
         status:
           attributes:
             reblog:
               taken: تا از وضعیت‌ها هنوز وجود دارند
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: بیش از حد زود است. باید بعد از %{date} باشد
         user:
           attributes:
             email:
diff --git a/config/locales/activerecord.fi.yml b/config/locales/activerecord.fi.yml
index c731688a1f..b4d91a5f1e 100644
--- a/config/locales/activerecord.fi.yml
+++ b/config/locales/activerecord.fi.yml
@@ -20,11 +20,10 @@ fi:
           invalid: ei ole kelvollinen verkkotunnus
       messages:
         invalid_domain_on_line: "%{value} ei ole kelvollinen verkkotunnus"
+        too_many_lines: ylittää %{limit} rivin rajan
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: sisältää arvoja, joista puuttuu nimike
             username:
               invalid: saa sisältää vain kirjaimia, numeroita ja alaviivoja
               reserved: on varattu
@@ -40,23 +39,12 @@ fi:
           attributes:
             data:
               malformed: on väärin muodostettu
-        list_account:
-          attributes:
-            account_id:
-              taken: on jo listassa
-          must_be_following: on oltava seurattava tili
         status:
           attributes:
             reblog:
               taken: tästä julkaisusta on jo tehty
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: on liian pian, täytyy olla myöhemmin kuin %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: alittaa alaikärajan
             email:
               blocked: käyttää kiellettyä sähköpostipalveluntarjoajaa
               unreachable: ei näytä olevan olemassa
diff --git a/config/locales/activerecord.fil.yml b/config/locales/activerecord.fil.yml
index 5250a30bdb..4084bf2f90 100644
--- a/config/locales/activerecord.fil.yml
+++ b/config/locales/activerecord.fil.yml
@@ -1,9 +1 @@
----
 fil:
-  activerecord:
-    errors:
-      models:
-        account:
-          attributes:
-            fields:
-              fields_with_values_missing_labels: may mga value na walang label
diff --git a/config/locales/activerecord.fo.yml b/config/locales/activerecord.fo.yml
index ce84a1fffd..61b924e5bf 100644
--- a/config/locales/activerecord.fo.yml
+++ b/config/locales/activerecord.fo.yml
@@ -20,11 +20,10 @@ fo:
           invalid: er ikki eitt virkið økisnavn
       messages:
         invalid_domain_on_line: "%{value} er ikki eitt virkið økisnavn"
+        too_many_lines: er longri enn markið á %{limit} reglur
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: inniheldur virði við manglandi spjøldrum
             username:
               invalid: kann bara innihalda bókstavir, tøl og botnstriku
               reserved: er umbiðið
@@ -40,23 +39,12 @@ fo:
           attributes:
             data:
               malformed: er avskeplað
-        list_account:
-          attributes:
-            account_id:
-              taken: er longu á listanum
-          must_be_following: má verða ein konta, sum verður fylgd
         status:
           attributes:
             reblog:
               taken: frá posti sum longu finst
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: tað er ov tíðliga, má vera eftir %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: er niðanfyri aldursmarkið
             email:
               blocked: brúkar ein ikki loyvdan teldopostveitara
               unreachable: tykist ikki at vera til
diff --git a/config/locales/activerecord.fr-CA.yml b/config/locales/activerecord.fr-CA.yml
index a966bb5a8a..8deeeee015 100644
--- a/config/locales/activerecord.fr-CA.yml
+++ b/config/locales/activerecord.fr-CA.yml
@@ -20,11 +20,10 @@ fr-CA:
           invalid: n'est pas un nom de domaine valide
       messages:
         invalid_domain_on_line: "%{value} n'est pas un nom de domaine valide"
+        too_many_lines: dépasse la limite de %{limit} lignes
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contient des valeurs avec des étiquettes manquantes
             username:
               invalid: doit ne contenir que des lettres, des nombres et des tirets bas
               reserved: est réservé
@@ -40,23 +39,12 @@ fr-CA:
           attributes:
             data:
               malformed: est malformé
-        list_account:
-          attributes:
-            account_id:
-              taken: est déjà sur la liste
-          must_be_following: dois être un compte suivi
         status:
           attributes:
             reblog:
               taken: de la publication existe déjà
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: est trop tôt, doit être plus tard que %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: est en dessous de la limite d'âge
             email:
               blocked: utilise un fournisseur de courriel interdit
               unreachable: ne semble pas exister
diff --git a/config/locales/activerecord.fr.yml b/config/locales/activerecord.fr.yml
index ae3ce7f9cb..2f74b16d53 100644
--- a/config/locales/activerecord.fr.yml
+++ b/config/locales/activerecord.fr.yml
@@ -20,11 +20,10 @@ fr:
           invalid: n'est pas un nom de domaine valide
       messages:
         invalid_domain_on_line: "%{value} n'est pas un nom de domaine valide"
+        too_many_lines: dépasse la limite de %{limit} lignes
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contient des valeurs avec des étiquettes manquantes
             username:
               invalid: seulement des lettres, des chiffres et des tirets bas
               reserved: est réservé
@@ -40,23 +39,12 @@ fr:
           attributes:
             data:
               malformed: est mal formé
-        list_account:
-          attributes:
-            account_id:
-              taken: est déjà sur la liste
-          must_be_following: dois être un compte suivi
         status:
           attributes:
             reblog:
               taken: du message existe déjà
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: est trop tôt, doit être plus tard que %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: est en dessous de la limite d'âge
             email:
               blocked: utilise un fournisseur de courriel interdit
               unreachable: ne semble pas exister
diff --git a/config/locales/activerecord.fy.yml b/config/locales/activerecord.fy.yml
index 8885fd43c1..69139eba1d 100644
--- a/config/locales/activerecord.fy.yml
+++ b/config/locales/activerecord.fy.yml
@@ -20,11 +20,10 @@ fy:
           invalid: is in ûnjildige domeinnamme
       messages:
         invalid_domain_on_line: "%{value} is in ûnjildige domeinnamme"
+        too_many_lines: giet oer de limyt fan %{limit} rigels
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: befettet wearden mei ûntbrekkende labels
             username:
               invalid: mei allinnich letters, nûmers en ûnderstreekjes befetsje
               reserved: reservearre
@@ -40,11 +39,6 @@ fy:
           attributes:
             data:
               malformed: hat de ferkearde opmaak
-        list_account:
-          attributes:
-            account_id:
-              taken: stiet al yn de list
-          must_be_following: moat in folge account wêze
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.ga.yml b/config/locales/activerecord.ga.yml
index 853c705663..4f83bc40aa 100644
--- a/config/locales/activerecord.ga.yml
+++ b/config/locales/activerecord.ga.yml
@@ -20,11 +20,10 @@ ga:
           invalid: nach ainm fearainn bailí é
       messages:
         invalid_domain_on_line: Ní ainm fearainn bailí é %{value}
+        too_many_lines: thar an teorainn de %{limit} línte
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: ina bhfuil luachanna a bhfuil lipéid in easnamh orthu
             username:
               invalid: ní mór go mbeadh litreacha, uimhreacha agus pointí béime amháin
               reserved: in áirithe
@@ -40,23 +39,12 @@ ga:
           attributes:
             data:
               malformed: míchumtha
-        list_account:
-          attributes:
-            account_id:
-              taken: cheana féin ar an liosta
-          must_be_following: ní mór cuntas a leanúint
         status:
           attributes:
             reblog:
               taken: den phost cheana féin
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: róluath, caithfidh sé bheith níos déanaí ná %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: faoi ​​bhun na teorann aoise
             email:
               blocked: úsáideann soláthraí ríomhphoist dícheadaithe
               unreachable: ní cosúil go bhfuil sé ann
diff --git a/config/locales/activerecord.gd.yml b/config/locales/activerecord.gd.yml
index bcda4a6a31..26848e2584 100644
--- a/config/locales/activerecord.gd.yml
+++ b/config/locales/activerecord.gd.yml
@@ -20,6 +20,7 @@ gd:
           invalid: "– chan eil seo ’na ainm àrainne dligheach"
       messages:
         invalid_domain_on_line: Chan eil %{value} ’na ainm àrainne dligheach
+        too_many_lines: "– tha seo thar crìoch de %{limit} nan loidhnichean"
       models:
         account:
           attributes:
diff --git a/config/locales/activerecord.gl.yml b/config/locales/activerecord.gl.yml
index f4e6725565..961c96edb4 100644
--- a/config/locales/activerecord.gl.yml
+++ b/config/locales/activerecord.gl.yml
@@ -7,7 +7,7 @@ gl:
         options: Opcións
       user:
         agreement: Acordo do Servizo
-        email: Enderezo de correo
+        email: Enderezo de email
         locale: Locale
         password: Contrasinal
       user/account:
@@ -20,11 +20,10 @@ gl:
           invalid: non é un nome de dominio válido
       messages:
         invalid_domain_on_line: "%{value} non é un nome de dominio válido"
+        too_many_lines: superou o límite de %{limit} liñas
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contén valores aos que lle faltan etiquetas
             username:
               invalid: só letras, números e trazo baixo
               reserved: está reservado
@@ -40,23 +39,12 @@ gl:
           attributes:
             data:
               malformed: ten formato incorrecto
-        list_account:
-          attributes:
-            account_id:
-              taken: xa está na lista
-          must_be_following: ten que ser unha conta que segues
         status:
           attributes:
             reblog:
               taken: do estado xa existe
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: é demasiado axiña, debería ser posterior a %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: é inferior ao límite de idade
             email:
               blocked: utiliza un provedor de email non autorizado
               unreachable: semella que non existe
diff --git a/config/locales/activerecord.he.yml b/config/locales/activerecord.he.yml
index 7dff17493b..1729084a4c 100644
--- a/config/locales/activerecord.he.yml
+++ b/config/locales/activerecord.he.yml
@@ -20,11 +20,10 @@ he:
           invalid: אינו שם מתחם קביל
       messages:
         invalid_domain_on_line: "%{value} אינו שם מתחם קביל"
+        too_many_lines: מעבר למגבלה של %{limit} שורות
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: שדות המכילים ערכים אך חסרים תווית
             username:
               invalid: ספרות, אותיות לטיניות וקו תחתי בלבד
               reserved: שמור
@@ -40,23 +39,12 @@ he:
           attributes:
             data:
               malformed: בתצורה לא תואמת
-        list_account:
-          attributes:
-            account_id:
-              taken: כבר ברשימה
-          must_be_following: חייב להיות חשבון נעקב
         status:
           attributes:
             reblog:
               taken: של ההודעה כבר קיים
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: מוקדם מדי, חייב להיות אחרי %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: מתחת למגבלת הגיל
             email:
               blocked: עושה שימוש בספק דוא"ל אסור
               unreachable: נראה שלא קיים
diff --git a/config/locales/activerecord.hu.yml b/config/locales/activerecord.hu.yml
index cf2f50a9f9..6e376dd678 100644
--- a/config/locales/activerecord.hu.yml
+++ b/config/locales/activerecord.hu.yml
@@ -20,11 +20,10 @@ hu:
           invalid: nem egy érvényes domain név
       messages:
         invalid_domain_on_line: "%{value} nem egy érvényes domain név"
+        too_many_lines: túllépi a(z) %{limit} soros korlátot
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: hiányzó címkékkel rendelkező értékeket tartalmaz
             username:
               invalid: csak betűket, számokat vagy alávonást tartalmazhat
               reserved: foglalt
@@ -40,23 +39,12 @@ hu:
           attributes:
             data:
               malformed: hibás
-        list_account:
-          attributes:
-            account_id:
-              taken: már szerepel a listán
-          must_be_following: követett fióknak kell lennie
         status:
           attributes:
             reblog:
               taken: már létezik ehhez a bejegyzéshez
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: túl korán van, később kellene lennie, mint %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: a korhatár alatt van
             email:
               blocked: egy letiltott email szolgáltatót használ
               unreachable: úgy tűnik, hogy nem létezik
diff --git a/config/locales/activerecord.ia.yml b/config/locales/activerecord.ia.yml
index b4cc349c08..809b8fd582 100644
--- a/config/locales/activerecord.ia.yml
+++ b/config/locales/activerecord.ia.yml
@@ -20,11 +20,10 @@ ia:
           invalid: non es un nomine de dominio valide
       messages:
         invalid_domain_on_line: "%{value} non es un nomine de dominio valide"
+        too_many_lines: il es ultra le limite de %{limit} lineas
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contine valores con etiquettas perdite
             username:
               invalid: debe continer solmente litteras, numeros e lineettas basse
               reserved: es reservate
@@ -40,11 +39,6 @@ ia:
           attributes:
             data:
               malformed: es mal formate
-        list_account:
-          attributes:
-            account_id:
-              taken: jam es in le lista
-          must_be_following: debe esser un conto sequite
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.io.yml b/config/locales/activerecord.io.yml
index 67b59e24fc..2b0d509198 100644
--- a/config/locales/activerecord.io.yml
+++ b/config/locales/activerecord.io.yml
@@ -20,11 +20,10 @@ io:
           invalid: ne esas valida domennomo
       messages:
         invalid_domain_on_line: "%{value} ne esas valida domennomo"
+        too_many_lines: esas plu kam la limito qua esas %{limit} linei
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: enhavas valori kun mankanta etiketi
             username:
               invalid: mustas konsistar nur literi, nombri e sublinei
               reserved: rezervitas
@@ -40,11 +39,6 @@ io:
           attributes:
             data:
               malformed: esas misformaca
-        list_account:
-          attributes:
-            account_id:
-              taken: es ja en la listo
-          must_be_following: mustas esar sequinta konto
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.is.yml b/config/locales/activerecord.is.yml
index cff90a3476..e274cc0a9e 100644
--- a/config/locales/activerecord.is.yml
+++ b/config/locales/activerecord.is.yml
@@ -20,11 +20,10 @@ is:
           invalid: er ekki leyfilegt nafn á léni
       messages:
         invalid_domain_on_line: "%{value} er ekki leyfilegt nafn á léni"
+        too_many_lines: er yfir takmörkum á %{limit} línum
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: inniheldur gildi sem vantar merkingar
             username:
               invalid: má aðeins innihalda bókstafi, tölur og undirstrik
               reserved: er frátekið
@@ -40,23 +39,12 @@ is:
           attributes:
             data:
               malformed: er rangt formað
-        list_account:
-          attributes:
-            account_id:
-              taken: er þegar á listanum
-          must_be_following: verður að vera aðgangur sem fylgst er með
         status:
           attributes:
             reblog:
               taken: af færslum er þegar fyrirliggjandi
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: er of snemmt, verður að vera síðar en %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: er undir aldurstakmörkum
             email:
               blocked: notar óleyfilega tölvupóstþjónustu
               unreachable: virðist ekki vera til
diff --git a/config/locales/activerecord.it.yml b/config/locales/activerecord.it.yml
index 9ff385f26e..3d5be6c258 100644
--- a/config/locales/activerecord.it.yml
+++ b/config/locales/activerecord.it.yml
@@ -20,11 +20,10 @@ it:
           invalid: non è un nome di dominio valido
       messages:
         invalid_domain_on_line: "%{value} non è un nome di dominio valido"
+        too_many_lines: è oltre il limite di %{limit} righe
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contiene valori con label mancanti
             username:
               invalid: deve contenere solo lettere, numeri e trattini bassi
               reserved: è riservato
@@ -40,23 +39,12 @@ it:
           attributes:
             data:
               malformed: è malformato
-        list_account:
-          attributes:
-            account_id:
-              taken: è già nella lista
-          must_be_following: deve essere un account seguito
         status:
           attributes:
             reblog:
               taken: del post esiste già
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: è troppo presto, deve essere successivo alla data %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: è inferiore al limite di età
             email:
               blocked: utilizza un provider di posta elettronica non autorizzato
               unreachable: non sembra esistere
diff --git a/config/locales/activerecord.ja.yml b/config/locales/activerecord.ja.yml
index b52e92a4ec..21be8aa345 100644
--- a/config/locales/activerecord.ja.yml
+++ b/config/locales/activerecord.ja.yml
@@ -20,11 +20,10 @@ ja:
           invalid: 有効なドメイン名ではありません
       messages:
         invalid_domain_on_line: "%{value} は有効なドメイン名ではありません"
+        too_many_lines: "%{limit} 行の制限を超えています。"
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: プロフィール補足情報にラベルが入力されていないものがあります
             username:
               invalid: アルファベット・数字・アンダーバーの組み合わせで入力してください
               reserved: は予約されています
@@ -40,11 +39,6 @@ ja:
           attributes:
             data:
               malformed: は不正です
-        list_account:
-          attributes:
-            account_id:
-              taken: はすでにリストに存在します
-          must_be_following: フォローされているアカウントでなければなりません
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.ko.yml b/config/locales/activerecord.ko.yml
index 3aa991734b..6d437b72b0 100644
--- a/config/locales/activerecord.ko.yml
+++ b/config/locales/activerecord.ko.yml
@@ -20,11 +20,10 @@ ko:
           invalid: 올바른 도메인 네임이 아닙니다
       messages:
         invalid_domain_on_line: "%{value}는 올바른 도메인 네임이 아닙니다"
+        too_many_lines: "%{limit}줄 제한을 초과합니다"
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: 라벨 없는 값들이 존재합니다
             username:
               invalid: 영문자와 숫자, 밑줄만 사용 가능합니다
               reserved: 예약되어 있습니다
@@ -40,23 +39,12 @@ ko:
           attributes:
             data:
               malformed: 데이터가 올바르지 않습니다
-        list_account:
-          attributes:
-            account_id:
-              taken: 이미 목록에 존재합니다
-          must_be_following: 팔로우한 계정이어야 합니다
         status:
           attributes:
             reblog:
               taken: 이미 게시물이 존재합니다
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: 너무 이릅니다. %{date} 이후로 지정해야 합니다
         user:
           attributes:
-            date_of_birth:
-              below_limit: 나이 제한보다 아래입니다
             email:
               blocked: 허용되지 않은 이메일 제공자입니다
               unreachable: 존재하지 않는 것 같습니다
diff --git a/config/locales/activerecord.lt.yml b/config/locales/activerecord.lt.yml
index 1eec2782f4..2e4b54c626 100644
--- a/config/locales/activerecord.lt.yml
+++ b/config/locales/activerecord.lt.yml
@@ -20,11 +20,10 @@ lt:
           invalid: nėra tinkamas domeno vardas.
       messages:
         invalid_domain_on_line: "%{value} nėra tinkamas domeno vardas."
+        too_many_lines: yra daugiau nei %{limit} eilučių ribojimą.
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: turi reikšmių su trūkstamomis etiketėmis.
             username:
               invalid: turi būti tik raidės, skaičiai ir pabraukimai.
               reserved: užimtas.
@@ -40,23 +39,12 @@ lt:
           attributes:
             data:
               malformed: yra netaisyklinga.
-        list_account:
-          attributes:
-            account_id:
-              taken: jau yra sąraše.
-          must_be_following: turi būti sekama paskyra.
         status:
           attributes:
             reblog:
               taken: įrašas jau egzistuoja.
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: yra per anksti, turi būti vėliau nei %{date}.
         user:
           attributes:
-            date_of_birth:
-              below_limit: yra žemiau amžiaus ribos.
             email:
               blocked: naudoja neleidžiamą el. laiško paslaugų teikėją.
               unreachable: neatrodo, kad egzistuoja.
diff --git a/config/locales/activerecord.lv.yml b/config/locales/activerecord.lv.yml
index c7030221a7..b7e2db65e8 100644
--- a/config/locales/activerecord.lv.yml
+++ b/config/locales/activerecord.lv.yml
@@ -20,11 +20,10 @@ lv:
           invalid: nav derīgs domēna nosaukums
       messages:
         invalid_domain_on_line: "%{value} nav derīgs domēna nosaukums"
+        too_many_lines: pārsniedz %{limit} līniju ierobežojumu
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: satur vērtības ar trūkstošām iezīmēm
             username:
               invalid: drīkst saturēt tikai burtus, ciparus un pasvītras
               reserved: ir rezervēts
@@ -40,23 +39,12 @@ lv:
           attributes:
             data:
               malformed: ir nepareizi veidots
-        list_account:
-          attributes:
-            account_id:
-              taken: jau ir sarakstā
-          must_be_following: jābūt kontam, kuram seko
         status:
           attributes:
             reblog:
               taken: ziņai jau pastāv
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: ir pārāk agri, jābūt vēlāk kā %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: ir mazāks par minimālo vecuma ierobežojumu
             email:
               blocked: lieto neatļautu e-pasta pakalpojuma sniedzēju
               unreachable: šķietami neeksistē
diff --git a/config/locales/activerecord.ml.yml b/config/locales/activerecord.ml.yml
index 3c8cf67695..90c5b38ad5 100644
--- a/config/locales/activerecord.ml.yml
+++ b/config/locales/activerecord.ml.yml
@@ -20,6 +20,7 @@ ml:
           invalid: ഇതൊരു തെറ്റിയ മേഖലപേരാണു്
       messages:
         invalid_domain_on_line: "%{value} ഒരു തെറ്റിയ മേഖലപേരാണു്"
+        too_many_lines: ഇതു് %{limit} വരിയതിരിന്റെ മേലെയാണു്
       models:
         account:
           attributes:
diff --git a/config/locales/activerecord.ne.yml b/config/locales/activerecord.ne.yml
index 12795ea20d..f2b7b0ef80 100644
--- a/config/locales/activerecord.ne.yml
+++ b/config/locales/activerecord.ne.yml
@@ -29,7 +29,3 @@ ne:
           attributes:
             website:
               invalid: मान्य URL होइन
-        list_account:
-          attributes:
-            account_id:
-              taken: पहिले नै सूचीमा छ
diff --git a/config/locales/activerecord.nl.yml b/config/locales/activerecord.nl.yml
index b05d6680e7..ee3c8bf260 100644
--- a/config/locales/activerecord.nl.yml
+++ b/config/locales/activerecord.nl.yml
@@ -20,11 +20,10 @@ nl:
           invalid: is een ongeldige domeinnaam
       messages:
         invalid_domain_on_line: "%{value} is een ongeldige domeinnaam"
+        too_many_lines: overschrijdt de limiet van %{limit} regels
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: bevat waarden met ontbrekende labels
             username:
               invalid: alleen letters, nummers en underscores
               reserved: gereserveerd
@@ -40,23 +39,12 @@ nl:
           attributes:
             data:
               malformed: heeft de verkeerde opmaak
-        list_account:
-          attributes:
-            account_id:
-              taken: staat al in de lijst
-          must_be_following: moet een gevolgd account zijn
         status:
           attributes:
             reblog:
               taken: van bericht bestaat al
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: is te vroeg, moet na %{date} zijn
         user:
           attributes:
-            date_of_birth:
-              below_limit: is onder de leeftijdsgrens
             email:
               blocked: gebruikt een niet toegestane e-mailprovider
               unreachable: schijnt niet te bestaan
diff --git a/config/locales/activerecord.nn.yml b/config/locales/activerecord.nn.yml
index f47bafe0b7..a34cc7cf12 100644
--- a/config/locales/activerecord.nn.yml
+++ b/config/locales/activerecord.nn.yml
@@ -20,11 +20,10 @@ nn:
           invalid: er ikkje eit gyldig domenenamn
       messages:
         invalid_domain_on_line: "%{value} er ikkje gyldig i eit domenenamn"
+        too_many_lines: er over grensa på %{limit} liner
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: inneheld verdiar med manglande etikettar
             username:
               invalid: kan berre innehalda bokstavar, tal og understrekar
               reserved: er reservert
@@ -40,11 +39,6 @@ nn:
           attributes:
             data:
               malformed: er feilutforma
-        list_account:
-          attributes:
-            account_id:
-              taken: er allereie på lista
-          must_be_following: må vera ein konto du fylgjer
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.pl.yml b/config/locales/activerecord.pl.yml
index 62edc03c0e..d0e6dda58d 100644
--- a/config/locales/activerecord.pl.yml
+++ b/config/locales/activerecord.pl.yml
@@ -20,11 +20,10 @@ pl:
           invalid: nie jest prawidłową nazwą domeny
       messages:
         invalid_domain_on_line: "%{value} nie jest prawidłową nazwą domeny"
+        too_many_lines: przekracza limit %{limit} linii
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: zawiera wartości z brakującymi etykietami
             username:
               invalid: może składać się tylko z liter, cyfr i podkreślników
               reserved: jest zarezerwowana
@@ -40,11 +39,6 @@ pl:
           attributes:
             data:
               malformed: jest uszkodzona
-        list_account:
-          attributes:
-            account_id:
-              taken: jest już na liście
-          must_be_following: musi być obserwowanym kontem
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.pt-BR.yml b/config/locales/activerecord.pt-BR.yml
index 8b77ed7e83..4d3bb992a5 100644
--- a/config/locales/activerecord.pt-BR.yml
+++ b/config/locales/activerecord.pt-BR.yml
@@ -20,11 +20,10 @@ pt-BR:
           invalid: não é um nome de domínio válido
       messages:
         invalid_domain_on_line: "%{value} não é um nome de domínio válido"
+        too_many_lines: está acima do limite de %{limit} linhas
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contém valores sem rótulos
             username:
               invalid: apenas letras, números e underlines ( "_" )
               reserved: já existe
@@ -39,29 +38,18 @@ pt-BR:
         import:
           attributes:
             data:
-              malformed: está malformado
-        list_account:
-          attributes:
-            account_id:
-              taken: já está na lista
-          must_be_following: deve ser uma conta seguida
+              malformed: Está malformado
         status:
           attributes:
             reblog:
               taken: do toot já existe
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: está muito próxima, deve ser posterior a %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: está abaixo do limite de idade
             email:
               blocked: usa provedor de e-mail não permitido
               unreachable: parece não existir
             role_id:
-              elevated: não pode ser maior que a sua função atual
+              elevated: não pode maior que sua função atual
         user_role:
           attributes:
             permissions_as_keys:
diff --git a/config/locales/activerecord.pt-PT.yml b/config/locales/activerecord.pt-PT.yml
index 397ed492e5..a06fba32c6 100644
--- a/config/locales/activerecord.pt-PT.yml
+++ b/config/locales/activerecord.pt-PT.yml
@@ -20,11 +20,10 @@ pt-PT:
           invalid: não é um nome de domínio válido
       messages:
         invalid_domain_on_line: "%{value} não é um nome de domínio válido"
+        too_many_lines: está acima do limite de %{limit} linhas
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: contém valores com etiquetas em falta
             username:
               invalid: deve conter apenas letras, números e traços inferiores (_)
               reserved: está reservado
@@ -40,23 +39,12 @@ pt-PT:
           attributes:
             data:
               malformed: está malformado
-        list_account:
-          attributes:
-            account_id:
-              taken: já está na lista
-          must_be_following: tem de ser uma conta seguida
         status:
           attributes:
             reblog:
               taken: da publicação já existe
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: é muito cedo, deve ser após %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: está abaixo da idade mínima
             email:
               blocked: usa um fornecedor de e-mail que não é permitido
               unreachable: não parece existir
diff --git a/config/locales/activerecord.ro.yml b/config/locales/activerecord.ro.yml
index 9f21282ca6..1adec0b149 100644
--- a/config/locales/activerecord.ro.yml
+++ b/config/locales/activerecord.ro.yml
@@ -20,6 +20,7 @@ ro:
           invalid: nu este un nume de domeniu valid
       messages:
         invalid_domain_on_line: "%{value} nu este un nume de domeniu valid"
+        too_many_lines: este peste limita de %{limit} linii
       models:
         account:
           attributes:
@@ -38,11 +39,6 @@ ro:
           attributes:
             data:
               malformed: este malformat
-        list_account:
-          attributes:
-            account_id:
-              taken: este deja pe listă
-          must_be_following: trebuie să fie un cont urmărit
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.ru.yml b/config/locales/activerecord.ru.yml
index 08e91e459f..203d8e2c34 100644
--- a/config/locales/activerecord.ru.yml
+++ b/config/locales/activerecord.ru.yml
@@ -3,7 +3,7 @@ ru:
   activerecord:
     attributes:
       poll:
-        expires_at: Срок окончания голосования
+        expires_at: Крайний срок
         options: Варианты
       user:
         agreement: Соглашение с условиями сервиса
@@ -20,11 +20,10 @@ ru:
           invalid: не является действующим доменным именем
       messages:
         invalid_domain_on_line: "%{value} Не является действительным доменным именем"
+        too_many_lines: Превышает предел %{limit} строк
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: содержит значения с отсутствующими ключами
             username:
               invalid: только буквы, цифры и символ подчёркивания
               reserved: зарезервировано
@@ -40,23 +39,12 @@ ru:
           attributes:
             data:
               malformed: неверный формат
-        list_account:
-          attributes:
-            account_id:
-              taken: уже в списке
-          must_be_following: должен быть пользователем, на которого вы подписаны
         status:
           attributes:
             reblog:
               taken: пост уже существует
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: должна быть не ранее %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: ниже возрастного ограничения
             email:
               blocked: использует запрещённого провайдера эл. почты
               unreachable: не существует
diff --git a/config/locales/activerecord.sl.yml b/config/locales/activerecord.sl.yml
index e4c4fe598f..3572182ef3 100644
--- a/config/locales/activerecord.sl.yml
+++ b/config/locales/activerecord.sl.yml
@@ -18,13 +18,9 @@ sl:
       attributes:
         domain:
           invalid: ni veljavno ime domene
-      messages:
-        invalid_domain_on_line: "%{value} ni veljavno ime domene"
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: vsebuje vrednosti, ki niso kategorizirane
             username:
               invalid: samo črke, številke in podčrtaji
               reserved: je rezerviran
@@ -40,23 +36,12 @@ sl:
           attributes:
             data:
               malformed: je napačno oblikovan
-        list_account:
-          attributes:
-            account_id:
-              taken: je že na seznamu
-          must_be_following: mora biti račun, ki mu sledite
         status:
           attributes:
             reblog:
               taken: od objave že obstajajo
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: je prekmalu, naj bo kasneje od %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: ne dosega starostne meje
             email:
               blocked: uporablja nedovoljenega ponudnika e-poštnih storitev
               unreachable: kot kaže ne obstaja
diff --git a/config/locales/activerecord.sq.yml b/config/locales/activerecord.sq.yml
index 2683dd014b..888a17a1c8 100644
--- a/config/locales/activerecord.sq.yml
+++ b/config/locales/activerecord.sq.yml
@@ -20,11 +20,10 @@ sq:
           invalid: s’është emër i vlefshëm përkatësie
       messages:
         invalid_domain_on_line: "%{value} s’është emër i vlefshëm përkatësie"
+        too_many_lines: është tej kufirit prej %{limit} rreshta
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: përmban vlera me etiketa që mungojnë
             username:
               invalid: duhet të përmbajë vetëm shkronja, numra dhe nënvija
               reserved: është i rezervuar
@@ -40,23 +39,12 @@ sq:
           attributes:
             data:
               malformed: janë të keqformuara
-        list_account:
-          attributes:
-            account_id:
-              taken: gjendet tashmë në listë
-          must_be_following: duhet të jetë një llogari e ndjekur
         status:
           attributes:
             reblog:
               taken: e gjendjes ekziston tashmë
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: është shumë herët, duhet të jetë më vonë se %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: është nën kufirin e moshave
             email:
               blocked: përdor një shërbim email të palejuar
               unreachable: s’duket se ekziston
diff --git a/config/locales/activerecord.sv.yml b/config/locales/activerecord.sv.yml
index 74b939fda4..6ac96d9ea9 100644
--- a/config/locales/activerecord.sv.yml
+++ b/config/locales/activerecord.sv.yml
@@ -20,11 +20,10 @@ sv:
           invalid: är inte ett giltigt domännamn
       messages:
         invalid_domain_on_line: "%{value} Är inte ett giltigt domännamn"
+        too_many_lines: överskrider gränsen på %{limit} rader
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: innehåller värden med saknade etiketter
             username:
               invalid: endast bokstäver, siffror och understrykning
               reserved: är reserverat
@@ -40,23 +39,12 @@ sv:
           attributes:
             data:
               malformed: är felformad
-        list_account:
-          attributes:
-            account_id:
-              taken: finns redan i listan
-          must_be_following: måste vara ett följt konto
         status:
           attributes:
             reblog:
               taken: av status finns redan
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: är för tidigt, måste vara senare än %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: är under åldersgränsen
             email:
               blocked: använder en icke tillåten e-postleverantör
               unreachable: verkar inte existera
diff --git a/config/locales/activerecord.th.yml b/config/locales/activerecord.th.yml
index e8a0a37cff..e1021b8afa 100644
--- a/config/locales/activerecord.th.yml
+++ b/config/locales/activerecord.th.yml
@@ -20,6 +20,7 @@ th:
           invalid: ไม่ใช่ชื่อโดเมนที่ถูกต้อง
       messages:
         invalid_domain_on_line: "%{value} ไม่ใช่ชื่อโดเมนที่ถูกต้อง"
+        too_many_lines: เกินขีดจำกัด %{limit} บรรทัด
       models:
         account:
           attributes:
@@ -38,11 +39,6 @@ th:
           attributes:
             data:
               malformed: ผิดรูปแบบ
-        list_account:
-          attributes:
-            account_id:
-              taken: อยู่ในรายการอยู่แล้ว
-          must_be_following: ต้องเป็นบัญชีที่ติดตาม
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.tok.yml b/config/locales/activerecord.tok.yml
index 9a6f9e5875..14a0d3da08 100644
--- a/config/locales/activerecord.tok.yml
+++ b/config/locales/activerecord.tok.yml
@@ -20,6 +20,7 @@ tok:
           invalid: li nimi ilo ike
       messages:
         invalid_domain_on_line: nimi "%{value}" li nimi ilo ike
+        too_many_lines: la %{limit} o mute nanpa wan pi linja sitelen
       models:
         account:
           attributes:
@@ -38,11 +39,6 @@ tok:
           attributes:
             data:
               malformed: li nasin ike
-        list_account:
-          attributes:
-            account_id:
-              taken: li lon kulupu
-          must_be_following: o jan pi kute sina
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.tr.yml b/config/locales/activerecord.tr.yml
index db9317afa2..505289470e 100644
--- a/config/locales/activerecord.tr.yml
+++ b/config/locales/activerecord.tr.yml
@@ -20,11 +20,10 @@ tr:
           invalid: geçerli bir alan adı değil
       messages:
         invalid_domain_on_line: "%{value} geçerli bir alan adı değil"
+        too_many_lines: "%{limit} satır sınırının üzerinde"
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: etiketleri eksik değerler içeriyor
             username:
               invalid: sadece harfler, sayılar ve alt çizgiler
               reserved: kullanılamaz
@@ -40,23 +39,12 @@ tr:
           attributes:
             data:
               malformed: bozulmuştur
-        list_account:
-          attributes:
-            account_id:
-              taken: zaten listede var
-          must_be_following: takip edilen bir hesap olmalı
         status:
           attributes:
             reblog:
               taken: durum zaten var
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: çok erken, %{date} tarihinden sonra olmalıdır
         user:
           attributes:
-            date_of_birth:
-              below_limit: yaş sınırının altında
             email:
               blocked: izin verilmeyen bir e-posta sağlayıcı kullanıyor
               unreachable: mevcut gözükmüyor
diff --git a/config/locales/activerecord.tt.yml b/config/locales/activerecord.tt.yml
index 87c6592b6e..e53c2341e4 100644
--- a/config/locales/activerecord.tt.yml
+++ b/config/locales/activerecord.tt.yml
@@ -5,51 +5,10 @@ tt:
       poll:
         options: Сайлаулар
       user:
-        email: Эл. почта адресы
+        email: Почта адресы
         locale: Тел
         password: Серсүз
       user/account:
         username: Кулланучы исеме
       user/invite_request:
         text: Сәбәп
-    errors:
-      attributes:
-        domain:
-          invalid: бу домен исеме гамәлдә түгел
-      messages:
-        invalid_domain_on_line: "%{value} дөрес домен исеме түгел"
-      models:
-        account:
-          attributes:
-            username:
-              invalid: хәрефләр, цифрлар һәм ассызыклау билгеләре генә ярый
-        admin/webhook:
-          attributes:
-            url:
-              invalid: рөхсәт ителгән URL түгел
-        doorkeeper/application:
-          attributes:
-            website:
-              invalid: рөхсәт ителгән URL түгел
-        import:
-          attributes:
-            data:
-              malformed: формат дөрес түгел
-        list_account:
-          attributes:
-            account_id:
-              taken: инде исемлектә
-        status:
-          attributes:
-            reblog:
-              taken: язма инде бар
-        user:
-          attributes:
-            email:
-              blocked: ярамаган эл. почта провайдерын куллана
-            role_id:
-              elevated: сезнең хәзерге ролегездән югарырак була алмый
-        user_role:
-          attributes:
-            position:
-              elevated: сезнең хәзерге ролегездән югарырак була алмый
diff --git a/config/locales/activerecord.uk.yml b/config/locales/activerecord.uk.yml
index 8b4ba1f671..c9a4c8e1ec 100644
--- a/config/locales/activerecord.uk.yml
+++ b/config/locales/activerecord.uk.yml
@@ -20,11 +20,10 @@ uk:
           invalid: не є дійсним іменем домену
       messages:
         invalid_domain_on_line: "%{value} не є дійсним іменем домену"
+        too_many_lines: перевищує ліміт %{limit} рядків
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: містить значення з відсутніми мітками
             username:
               invalid: має містити лише літери, цифри та підкреслення
               reserved: зарезервовано
@@ -40,19 +39,12 @@ uk:
           attributes:
             data:
               malformed: неправильний
-        list_account:
-          attributes:
-            account_id:
-              taken: вже в списку
-          must_be_following: має бути відстежуваним обліковим записом
         status:
           attributes:
             reblog:
               taken: цього допису вже існує
         user:
           attributes:
-            date_of_birth:
-              below_limit: менше вікової межі
             email:
               blocked: використовує не дозволенного постачальника електронної пошти
               unreachable: не існує
diff --git a/config/locales/activerecord.vi.yml b/config/locales/activerecord.vi.yml
index fe810d94e5..b48510c2e2 100644
--- a/config/locales/activerecord.vi.yml
+++ b/config/locales/activerecord.vi.yml
@@ -20,11 +20,10 @@ vi:
           invalid: không phải là một tên miền hợp lệ
       messages:
         invalid_domain_on_line: "%{value} không phải là một tên miền hợp lệ"
+        too_many_lines: vượt quá giới hạn %{limit} dòng
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: chứa giá trị thiếu nhãn
             username:
               invalid: chỉ chấp nhận ký tự, số và dấu gạch dưới
               reserved: bị cấm sử dụng
@@ -40,23 +39,12 @@ vi:
           attributes:
             data:
               malformed: bị hỏng
-        list_account:
-          attributes:
-            account_id:
-              taken: đã có trong danh sách
-          must_be_following: phải theo dõi từ trước
         status:
           attributes:
             reblog:
               taken: của tút đã tồn tại
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: là quá sớm, cần phải sau %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: dưới độ tuổi tối thiểu
             email:
               blocked: sử dụng dịch vụ email bị cấm
               unreachable: không tồn tại
diff --git a/config/locales/activerecord.zh-CN.yml b/config/locales/activerecord.zh-CN.yml
index af19014cfd..476947ea4d 100644
--- a/config/locales/activerecord.zh-CN.yml
+++ b/config/locales/activerecord.zh-CN.yml
@@ -20,11 +20,10 @@ zh-CN:
           invalid: 不是有效的域名
       messages:
         invalid_domain_on_line: "%{value} 不是有效的域名"
+        too_many_lines: 超出 %{limit} 行的长度限制
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: 包含缺失标签的值
             username:
               invalid: 只能使用字母、数字和下划线
               reserved: 是保留关键字
@@ -40,23 +39,12 @@ zh-CN:
           attributes:
             data:
               malformed: 格式错误
-        list_account:
-          attributes:
-            account_id:
-              taken: 已经被加入到列表了
-          must_be_following: 需要是你关注的账号
         status:
           attributes:
             reblog:
               taken: 已经被转嘟过
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: 日期太近,必须晚于 %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: 低于年龄限制
             email:
               blocked: 使用了被封禁的电子邮件提供商
               unreachable: 似乎不存在
diff --git a/config/locales/activerecord.zh-TW.yml b/config/locales/activerecord.zh-TW.yml
index f8f630ba3c..113d881ae5 100644
--- a/config/locales/activerecord.zh-TW.yml
+++ b/config/locales/activerecord.zh-TW.yml
@@ -20,11 +20,10 @@ zh-TW:
           invalid: 並非一個有效網域
       messages:
         invalid_domain_on_line: "%{value} 並非一個有效網域"
+        too_many_lines: 已超過行數限制 (%{limit} 行)
       models:
         account:
           attributes:
-            fields:
-              fields_with_values_missing_labels: 包含缺少標籤之值
             username:
               invalid: 只能有字母、數字及底線
               reserved: 是保留關鍵字
@@ -40,23 +39,12 @@ zh-TW:
           attributes:
             data:
               malformed: 資料不正確
-        list_account:
-          attributes:
-            account_id:
-              taken: 已於列表中
-          must_be_following: 必須為已跟隨之帳號
         status:
           attributes:
             reblog:
               taken: 嘟文已經存在
-        terms_of_service:
-          attributes:
-            effective_date:
-              too_soon: 太快了,必須晚於 %{date}
         user:
           attributes:
-            date_of_birth:
-              below_limit: 低於年齡要求
             email:
               blocked: 使用不被允許的電子郵件提供商
               unreachable: 該電子郵件地址似乎無法使用
diff --git a/config/locales/an.yml b/config/locales/an.yml
index 6598ea3b90..2db042f1b1 100644
--- a/config/locales/an.yml
+++ b/config/locales/an.yml
@@ -723,6 +723,7 @@ an:
       original_status: Publicación orichinal
       reblogs: Impulsos
       status_changed: Publicación cambiada
+      title: Estau d'as cuentas
       trending: En tendencia
       visibility: Visibilidat
       with_media: Con multimedia
@@ -919,6 +920,7 @@ an:
     migrate_account: Mudar-se a unatra cuenta
     migrate_account_html: Si deseyas reendrezar esta cuenta a unatra distinta, puetz <a href="%{path}">configurar-lo aquí</a>.
     or_log_in_with: U inicia sesión con
+    privacy_policy_agreement_html: He leyiu y accepto la <a href="%{privacy_policy_path}" target="_blank">politica de privacidat</a>
     providers:
       cas: CAS
       saml: SAML
@@ -934,6 +936,7 @@ an:
       preamble_html: Inicia sesión con as tuys credencials <strong>%{domain}</strong>. Si la tuya cuenta se troba en un servidor diferent, no podrás iniciar aquí una sesión.
       title: Iniciar sesión en %{domain}
     sign_up:
+      preamble: Con una cuenta en este servidor de Mastodon, podrás seguir a qualsequier atra persona en o ret, independientment d'o servidor en o qual se trobe.
       title: Creyar cuenta de Mastodon en %{domain}.
     status:
       account_status: Estau d'a cuenta
@@ -1320,6 +1323,7 @@ an:
   scheduled_statuses:
     over_daily_limit: Ha superau lo limite de %{limit} publicacions programadas pa ixe día
     over_total_limit: Ha superau lo limite de %{limit} publicacions programadas
+    too_soon: La calendata programada ha d'estar en o futuro
   sessions:
     activity: Zaguera actividat
     browser: Navegador
diff --git a/config/locales/ar.yml b/config/locales/ar.yml
index 556c47b10b..829855776d 100644
--- a/config/locales/ar.yml
+++ b/config/locales/ar.yml
@@ -834,7 +834,6 @@ ar:
       batch:
         remove_from_report: إزالة من التقرير
         report: إبلاغ
-      contents: المحتوى
       deleted: محذوف
       favourites: المفضلة
       history: تاريخ التعديلات
@@ -848,6 +847,7 @@ ar:
       original_status: المنشور الأصلي
       reblogs: المعاد تدوينها
       status_changed: عُدّل المنشور
+      title: منشورات الحساب
       trending: المتداولة
       visibility: مدى الظهور
       with_media: تحتوي على وسائط
@@ -911,9 +911,6 @@ ar:
       search: البحث
       title: الوسوم
       updated_msg: تم تحديث إعدادات الوسوم بنجاح
-    terms_of_service:
-      save_draft: حفظ المسودة
-      title: شروط الخدمة
     title: الإدارة
     trends:
       allow: السماح
@@ -1122,6 +1119,7 @@ ar:
     migrate_account: الانتقال إلى حساب مختلف
     migrate_account_html: إن كنت ترغب في تحويل هذا الحساب نحو حساب آخَر، يُمكِنُك <a href="%{path}">إعداده هنا</a>.
     or_log_in_with: أو قم بتسجيل الدخول بواسطة
+    privacy_policy_agreement_html: لقد قرأتُ وأوافق على سياسة الخصوصية <a href="%{privacy_policy_path}" target="_blank"></a>
     progress:
       confirm: تأكيد عنوان البريد الإلكتروني
       details: تفاصيلك
@@ -1146,6 +1144,7 @@ ar:
     set_new_password: إدخال كلمة مرور جديدة
     setup:
       email_below_hint_html: قم بفحص مجلد البريد المزعج الخاص بك، أو قم بطلب آخر. يمكنك تصحيح عنوان بريدك الإلكتروني إن كان خاطئا.
+      email_settings_hint_html: انقر على الرابط الذي أرسلناه لك للتحقق من %{email}. سننتظر هنا.
       link_not_received: ألم تحصل على رابط؟
       new_confirmation_instructions_sent: سوف تتلقى رسالة بريد إلكتروني جديدة مع رابط التأكيد في غضون بضع دقائق!
       title: تحقَّق من بريدك الوارِد
@@ -1154,6 +1153,7 @@ ar:
       title: تسجيل الدخول إلى %{domain}
     sign_up:
       manual_review: عمليات التسجيل في %{domain} تمر عبر المراجعة اليدوية من قبل مشرفينا. لمساعدتنا في معالجة إنشاء حسابك، اكتب نَبْذَة عن نفسك ولماذا تريد حسابًا على %{domain}.
+      preamble: مع حساب على خادم ماستدون هذا، ستتمكن من متابعة أي شخص آخر على الشبكة، بغض النظر عن المكان الذي يستضيف فيه حسابهم.
       title: دعنا نجهّز %{domain}.
     status:
       account_status: حالة الحساب
@@ -1394,6 +1394,20 @@ ar:
       merge_long: الإبقاء علي التسجيلات الحالية وإضافة الجديدة
       overwrite: إعادة الكتابة
       overwrite_long: استبدال التسجيلات الحالية بالجديدة
+    overwrite_preambles:
+      blocking_html: أنت على وشك <strong>استبدال قائمة الحظر</strong> بما يصل إلى <strong>%{total_items} من الحسابات</strong> من <strong>%{filename}</strong>.
+      bookmarks_html: أنت على وشك <strong>استبدال إشاراتك المرجعية</strong> بما يصل إلى <strong>%{total_items} من المشاركات</strong> من <strong>%{filename}</strong>.
+      domain_blocking_html: أنت على وشك <strong>إرشاداتك المرجعية</strong> بما يصل إلى <strong>%{total_items} من المشاركات</strong> من <strong>%{filename}</strong>.
+      following_html: أنت على وشك <strong>متابعة</strong> ما يصل إلى <strong>%{total_items} من الحسابات</strong> من <strong>%{filename}</strong> و<strong>التوقف عن متابعة أي شخص آخر</strong>.
+      lists_html: إنّك بصدد <strong>استبدال قوائمك</strong> بمحتوى مِلَفّ <strong>%{filename}</strong>. ما يُقارِب <strong>%{total_items} حسابًا</strong> سوف تُضاف إلى قوائم جديدة.
+      muting_html: أنت على وشك <strong>استبدال قائمة الحسابات الصامتة</strong> بما يصل إلى <strong>%{total_items} من الحسابات</strong> من <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: أنت على وشك <strong>حظر</strong> ما يصل إلى <strong>%{total_items} من الحسابات</strong> من <strong>%{filename}</strong>.
+      bookmarks_html: أنت على وشك إضافة ما يصل إلى <strong>%{total_items} من المشاركات</strong> من <strong>%{filename}</strong> إلى <strong>إشاراتك المرجعية</strong>.
+      domain_blocking_html: أنت على وشك <strong>حظر</strong> ما يصل إلى <strong>%{total_items} من النطاقات</strong> من <strong>%{filename}</strong>.
+      following_html: أنت على وشك <strong>متابعة</strong> ما يصل إلى <strong>%{total_items} من الحسابات</strong> من <strong>%{filename}</strong>.
+      lists_html: أنت على وشك إضافة ما يصل إلى <strong>%{total_items} حسابات</strong> من <strong>%{filename}</strong> إلى <strong>قوائمك</strong>. سيتم إنشاء قوائم جديدة في حالة عدم وجود قائمة للإضافة إليها.
+      muting_html: أنت على وشك <strong>تجاهل</strong> ما يصل إلى <strong>%{total_items} من الحسابات</string> من <string>%{filename}</string>.
     preface: بإمكانك استيراد بيانات قد قُمتَ بتصديرها مِن مثيل خادم آخَر، كقوائم المستخدِمين الذين كنتَ تتابِعهم أو قُمتَ بحظرهم.
     recent_imports: الاستيرادات الحديثة
     states:
@@ -1645,6 +1659,7 @@ ar:
   scheduled_statuses:
     over_daily_limit: لقد تجاوزتَ حد الـ %{limit} منشورات مُبَرمَجة مسموح بها اليوم
     over_total_limit: لقد بلغت حد الـ %{limit} مِن المنشورات المبرمَجة
+    too_soon: يجب أن يكون تاريخ البرمجة في المستقبَل
   self_destruct:
     lead_html: للأسف، سيتم إغلاق <strong>%{domain}</strong> بشكل دائم. إذا كان لديك حساب هناك، لن تكون قادرًا على الاستمرار في استخدامه، غير أنه يمكنك طلب نسخة احتياطية لبياناتك.
     title: سيُغلق هذا الخادم أبوابه
@@ -1817,8 +1832,6 @@ ar:
       too_late: فات الأوان للطعن في هذه العقوبة
   tags:
     does_not_match_previous_name: لا يطابق الإسم السابق
-  terms_of_service:
-    title: شروط الخدمة
   themes:
     contrast: ماستدون (تباين عالٍ)
     default: ماستدون (داكن)
@@ -1879,8 +1892,6 @@ ar:
       further_actions_html: إذا لم يكن هذا أنت، نوصي لك %{action} على الفور وتمكين المصادقة ذات العاملين للحفاظ على أمان حسابك.
       subject: تم النفاذ عبر حسابك من خلال عنوان إيبي جديد
       title: تسجيل دخول جديد
-    terms_of_service_changed:
-      title: تحديث مهم
     warning:
       appeal: تقديم طعن
       appeal_description: إذا كنت تعتقد أن هذا خطأ، يمكنك تقديم طعن إلى فريق %{instance}.
diff --git a/config/locales/ast.yml b/config/locales/ast.yml
index 83d158766b..623ad830a3 100644
--- a/config/locales/ast.yml
+++ b/config/locales/ast.yml
@@ -459,6 +459,7 @@ ast:
     logout: Zarrar la sesión
     migrate_account: Cambéu de cuenta
     migrate_account_html: Si quies redirixir esta cuenta a otra diferente, pues <a href="%{path}">configurar esta opción equí</a>.
+    privacy_policy_agreement_html: Lleí y acepto la <a href="%{privacy_policy_path}" target="_blank">política de privacidá</a>
     progress:
       confirm: Confirmación del corréu electrónicu
       details: Los tos detalles
@@ -477,6 +478,7 @@ ast:
     sign_in:
       preamble_html: Anicia la sesión coles credenciales del dominiu <strong>%{domain}</strong>. Si la to cuenta ta agospiada n'otru sirvidor, nun vas ser a aniciar la sesión equí.
     sign_up:
+      preamble: Con una cuenta nesti sirvidor de Mastodon vas ser a siguir a cualesquier perfil de la rede, independientemente del sirvidor onde s'agospie la so cuenta.
       title: 'Creación d''una cuenta en: %{domain}.'
     status:
       account_status: Estáu de la cuenta
diff --git a/config/locales/be.yml b/config/locales/be.yml
index 539d3baa5f..b595e2cd3b 100644
--- a/config/locales/be.yml
+++ b/config/locales/be.yml
@@ -189,7 +189,6 @@ be:
         create_domain_block: Стварыць даменны блок
         create_email_domain_block: Стварыць даменны блок электроннай пошты
         create_ip_block: Стварыць IP правіла
-        create_relay: Стварыць паўтор
         create_unavailable_domain: Стварыць недаступны Дамен
         create_user_role: Стварыць ролю
         demote_user: Панізіць карыстальніка
@@ -201,22 +200,18 @@ be:
         destroy_email_domain_block: Выдаліць даменны блок электроннай пошты
         destroy_instance: Вычысціць дамен
         destroy_ip_block: Выдаліць IP правіла
-        destroy_relay: Выдаліць паўтор
         destroy_status: Выдаліць допіс
         destroy_unavailable_domain: Выдаліць недаступны дамен
         destroy_user_role: Выдаліць ролю
         disable_2fa_user: Адключыць двухэтапнае спраўджанне
         disable_custom_emoji: Адключыць адвольныя эмодзі
-        disable_relay: Выключыць паўтор
         disable_sign_in_token_auth_user: Адключыць аўтарызацыю праз электронную пошту для карыстальніка
         disable_user: Адключыць карыстальніка
         enable_custom_emoji: Уключыць адвольныя эмодзі
-        enable_relay: Уключыць паўтор
         enable_sign_in_token_auth_user: Уключыць аўтарызацыю праз электронную пошту для карыстальніка
         enable_user: Уключыць карыстальніка
         memorialize_account: Запомніць уліковы запіс
         promote_user: Павысіць правы Карыстальніка
-        publish_terms_of_service: Апублікаваць Умовы выкарыстаньня
         reject_appeal: Адхіліць абскарджанне
         reject_user: Адмовіць карыстальніку
         remove_avatar_user: Выдаліць аватар
@@ -254,7 +249,6 @@ be:
         create_domain_block_html: "%{name} заблакіраваў дамен %{target}"
         create_email_domain_block_html: "%{name} заблакіраваў дамен эл. пошты %{target}"
         create_ip_block_html: "%{name} стварыў правіла для IP %{target}"
-        create_relay_html: "%{name} стварыў(-а) паўтор %{target}"
         create_unavailable_domain_html: "%{name} прыпыніў дастаўку да дамена %{target}"
         create_user_role_html: "%{name} зрабіў ролю %{target}"
         demote_user_html: "%{name} прыбраў карыстальніка %{target}"
@@ -266,22 +260,18 @@ be:
         destroy_email_domain_block_html: "%{name} разблакіраваў дамен эл. пошты %{target}"
         destroy_instance_html: "%{name} цалкам прыбраў дамен %{target}"
         destroy_ip_block_html: "%{name} выдаліў правіла для IP %{target}"
-        destroy_relay_html: "%{name} выдаліў(-ла) паўтор %{target}"
         destroy_status_html: "%{name} выдаліў допіс %{target}"
         destroy_unavailable_domain_html: "%{name} дазволіў працягнуць адпраўку на дамен %{target}"
         destroy_user_role_html: "%{name} выдаліў ролю %{target}"
         disable_2fa_user_html: "%{name} амяніў абавязковую двухфактарную верыфікацыю для карыстальніка %{target}"
         disable_custom_emoji_html: "%{name} заблакіраваў эмодзі %{target}"
-        disable_relay_html: "%{name} выключыў(-ла) паўтор %{target}"
         disable_sign_in_token_auth_user_html: "%{name} адключыў уваход праз эл. пошту для %{target}"
         disable_user_html: "%{name} адключыў уваход для карыстальніка %{target}"
         enable_custom_emoji_html: "%{name} уключыў эмодзі %{target}"
-        enable_relay_html: "%{name} уключыў(-ла) паўтор %{target}"
         enable_sign_in_token_auth_user_html: "%{name} уключыў уваход праз эл. пошту для %{target}"
         enable_user_html: "%{name} уключыў уваход для карыстальніка %{target}"
         memorialize_account_html: Карыстальнік %{name} пераключыў уліковы запіс %{target} у старонку памяці
         promote_user_html: "%{name} павысіў карыстальніка %{target}"
-        publish_terms_of_service_html: "%{name} апублікаваў абнаўленьне ўмоваў абслугоўваньня"
         reject_appeal_html: "%{name} адхіліў запыт на абскарджанне %{target}"
         reject_user_html: "%{name} адхіліў рэгістрацыю з %{target}"
         remove_avatar_user_html: "%{name} выдаліў аватар %{target}"
@@ -852,10 +842,8 @@ be:
       back_to_account: Назад да старонкі ўліковага запісу
       back_to_report: Назад да старонкі справаздачы
       batch:
-        add_to_report: 'Дадаць да скаргі #%{id}'
         remove_from_report: Выдаліць са справаздачы
         report: Справаздача
-      contents: Зьмест
       deleted: Выдалены
       favourites: Упадабаныя
       history: Гісторыя версій
@@ -864,17 +852,13 @@ be:
       media:
         title: Медыя
       metadata: Метаданыя
-      no_history: Гэты пост не рэдагаваўся
       no_status_selected: Ніводная публікацыя не была зменена, бо ніводная не была выбрана
       open: Адкрыць допіс
       original_status: Зыходны допіс
       reblogs: Рэпосты
-      replied_to_html: Адказ карыстальніку %{acct_link}
       status_changed: Допіс зменены
-      status_title: Допіс карыстальніка @%{name}
-      title: Допісы карыстальніка - @%{name}
+      title: Допісы уліковага запісу
       trending: Папулярныя
-      view_publicly: Глядзець публічна
       visibility: Бачнасць
       with_media: З медыя
     strikes:
@@ -951,15 +935,6 @@ be:
       search: Пошук
       title: Хэштэгі
       updated_msg: Налады хэштэгаў паспяхова змененыя
-    terms_of_service:
-      back: Вернуцца да ўмоваў абслугоўваньня
-      changelog: Што зьмянілася
-      create: Выкарыстоўвайце свой уласны
-      current: Цякучы
-      draft: Чарнавік
-      generate: Выкарыстаць шаблон
-      generates:
-        action: Зґенераваць
     title: Адміністрацыя
     trends:
       allow: Дазволіць
@@ -1165,6 +1140,7 @@ be:
     migrate_account: Пераехаць на іншы ўліковы запіс
     migrate_account_html: Калі вы хочаце перанакіраваць гэты ўліковы запіс на іншы, то можаце <a href="%{path}">наладзіць яго тут</a>.
     or_log_in_with: Або ўвайсці з дапамогай
+    privacy_policy_agreement_html: Я азнаёміўся і пагаджаюся з <a href="%{privacy_policy_path}" target="_blank">палітыкай канфідэнцыйнасці</a>
     progress:
       confirm: Пацвердзіць email
       details: Вашы дадзеныя
@@ -1189,6 +1165,7 @@ be:
     set_new_password: Прызначыць новы пароль
     setup:
       email_below_hint_html: Праверце папку са спамам або зрабіце новы запыт. Вы можаце выправіць свой email, калі ён няправільны.
+      email_settings_hint_html: Націсніце на спасылку, якую мы адправілі, каб спраўдзіць %{email}. Мы вас пачакаем тут.
       link_not_received: Не атрымалі спасылку?
       new_confirmation_instructions_sent: Праз некалькі хвілін вы атрымаеце новы ліст на email са спасылкай для пацверджання!
       title: Праверце вашу пошту
@@ -1197,6 +1174,7 @@ be:
       title: Уваход у %{domain}
     sign_up:
       manual_review: Рэгістрацыі на %{domain} праходзяць ручную праверку нашымі мадэратарамі. Каб дапамагчы нам апрацаваць вашу рэгістрацыю, напішыце крыху пра сябе і чаму вы хочаце мець уліковы запіс на %{domain}.
+      preamble: Маючы ўліковы запіс на гэтым серверы Mastodon, вы будзеце мець магчымасць падпісацца на кожнага чалавека ў сетцы, незалежна ад таго, на якім серверы размешчаны ягоны ўліковы запіс.
       title: Наладзьма вас на %{domain}.
     status:
       account_status: Стан уліковага запісу
@@ -1418,6 +1396,20 @@ be:
       merge_long: Захаваць існуючыя запісы і дадаць новыя
       overwrite: Перазапісаць
       overwrite_long: Замяніць бягучыя запісы на новыя
+    overwrite_preambles:
+      blocking_html: Вы збіраецеся <strong>замяніць свой спіс блакіровак</strong> на <strong>%{total_items} уліковых запісаў</strong> з файла <strong>%{filename}</strong>.
+      bookmarks_html: Вы збіраецеся <strong>замяніць свае закладкі</strong> на <strong>%{total_items} допісаў</strong> з файла <strong>%{filename}</strong>.
+      domain_blocking_html: Вы збіраецеся <strong>замяніць свой спіс блакіроўкі даменаў</strong> на <strong>%{total_items} даменаў</strong> з файла <strong>%{filename}</strong>.
+      following_html: Вы збіраецеся <strong>падпісацца</strong> на <strong>%{total_items} уліковых запісаў</strong> з файла <strong>%{filename}</strong> і <strong> адпісацца ад усіх іншых карыстальнікаў</strong>.
+      lists_html: Вы збіраецеся <strong>замяніць свае спісы</strong> змесцівам з <strong>%{filename}</strong>. Да <strong>%{total_items} уліковых запісаў</strong> будуць дададзены ў новыя спісы.
+      muting_html: Вы збіраецеся <strong>замяніць свой спіс ігнараваных уліковых запісаў</strong> на <strong>%{total_items} уліковых запісаў</strong> з файла <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Вы збіраецеся <strong>заблакіраваць</strong> да <strong>%{total_items} уліковых запісаў</strong> з файла<strong>%{filename}</strong>.
+      bookmarks_html: Вы збіраецеся дадаць да <strong>%{total_items} паведамленняў</strong> з файла <strong>%{filename}</strong> у вашы <strong>закладкі</strong>.
+      domain_blocking_html: Вы збіраецеся <strong>заблакіраваць</strong> да <strong>%{total_items} даменаў</strong> з файла <strong>%{filename}</strong>.
+      following_html: Вы збіраецеся <strong>падпісацца </strong> на<strong>%{total_items} уліковых запісаў</strong> з файла <strong>%{filename}</strong>.
+      lists_html: Вы збіраецеся дадаць да <strong>%{total_items} уліковых запісаў</strong> з <strong>%{filename}</strong> у вашы <strong>спісы</strong>. Новыя спісы будуць створаны, калі іх няма.
+      muting_html: Вы збіраецеся <strong>ігнараваць</strong> да <strong>%{total_items} уліковых запісаў</strong> з файла <strong>%{filename}</strong>.
     preface: Вы можаце імпартаваць даныя, экспартаваныя вамі з іншага сервера, напрыклад, спіс людзей, на якіх вы падпісаны або якіх блакуеце.
     recent_imports: Нядаўнія імпарты
     states:
@@ -1675,6 +1667,7 @@ be:
   scheduled_statuses:
     over_daily_limit: Вы перавысілі ліміт ў %{limit} запланаваных на сёння допісаў
     over_total_limit: Вы перавысілі ліміт ў %{limit} запланаваных допісаў
+    too_soon: Запланаваная дата мусіць быць у будучыні
   self_destruct:
     lead_html: На жаль, дамен <strong>%{domain}</strong> зачыняецца назаўсёды. Калі ў вас быў уліковы запіс, вы не зможаце працягваць выкарыстоўваць яго, але вы ўсё яшчэ можаце запытаць рэзервовае капіраванне вашых даных.
     title: Гэты сервер зачыняецца
@@ -1845,8 +1838,6 @@ be:
       too_late: Запозна абскарджваць гэтае папярэджанне
   tags:
     does_not_match_previous_name: не супадае з папярэднім імям
-  terms_of_service:
-    title: Умовы абслугоўваньня
   themes:
     contrast: Mastodon (высокі кантраст)
     default: Mastodon (цёмная)
diff --git a/config/locales/bg.yml b/config/locales/bg.yml
index 7954a7b539..f84e62730a 100644
--- a/config/locales/bg.yml
+++ b/config/locales/bg.yml
@@ -187,7 +187,6 @@ bg:
         create_domain_block: Създаване на блокиране за домейна
         create_email_domain_block: Създаване на блокиране на имейл домейн
         create_ip_block: Създаване на правило за IP
-        create_relay: Създаване на пренасочване
         create_unavailable_domain: Създаване на недостъпен домейн
         create_user_role: Създаване на роля
         demote_user: Понижаване на потребител
@@ -211,7 +210,6 @@ bg:
         enable_user: Активиране на потребител
         memorialize_account: Възпоменаване на акаунта
         promote_user: Повишаване на потребител
-        publish_terms_of_service: Публикуване на условията за ползване
         reject_appeal: Отхвърляне на обжалването
         reject_user: Отхвърляне на потребителя
         remove_avatar_user: Премахване на аватара
@@ -272,7 +270,6 @@ bg:
         enable_user_html: "%{name} включи влизането за потребител %{target}"
         memorialize_account_html: "%{name} превърна акаунта на %{target} във възпоменателна страница"
         promote_user_html: "%{name} повиши потребителя %{target}"
-        publish_terms_of_service_html: "%{name} публикува обновления към условията на услугата"
         reject_appeal_html: "%{name} отхвърли обжалването на решение за модериране от %{target}"
         reject_user_html: "%{name} отхвърли регистрирането от %{target}"
         remove_avatar_user_html: "%{name} премахна аватара на %{target}"
@@ -302,7 +299,6 @@ bg:
       title: Одитен дневник
       unavailable_instance: "(неналично име на домейн)"
     announcements:
-      back: Обратно към оповестяванията
       destroyed_msg: Успешно изтрито оповестяване!
       edit:
         title: Редактиране на оповестянето
@@ -311,9 +307,6 @@ bg:
       new:
         create: Създаване на оповестяване
         title: Ново оповестяване
-      preview:
-        explanation_html: 'Е-писмо ще се изпрати до <strong>%{display_count} потребители</strong>. Следният текст ще се включи в е-писмо:'
-        title: Нагледно известието за оповестяване
       publish: Публикуване
       published_msg: Успешно публикувано оповестяване!
       scheduled_for: Насрочено за %{time}
@@ -472,32 +465,6 @@ bg:
       new:
         title: Внос на блокирания на домейни
       no_file: Няма избран файл
-    fasp:
-      debug:
-        callbacks:
-          created_at: Създадено на
-          delete: Изтриване
-          ip: IP адрес
-          request_body: Тяло на заявката
-          title: Дебъгване на обратните обаждания
-      providers:
-        active: Дейно
-        base_url: Базов URL адрес
-        callback: Обратно обаждане
-        delete: Изтриване
-        edit: Редактиране на доставчик
-        finish_registration: Край на регистрирането
-        name: Име
-        providers: Доставчици
-        public_key_fingerprint: Пръстов отпечатък на публичен ключ
-        registration_requested: Изисква се регистриране
-        registrations:
-          confirm: Потвърждаване
-          reject: Отхвърляне
-        save: Запазване
-        select_capabilities: Избор на възможности
-        sign_in: Вход
-        status: Състояние
     follow_recommendations:
       description_html: "<strong>Препоръките за следване помагат на новите потребители бързо да намерят ново съдържание</strong>. Когато един потребител не е създавал достатъчно връзки, за да формира свои собствени препоръки за следване, тези акаунти ще бъдат препоръчани. Акаунтите ще бъдат генерирани всеки ден на базата на най-голяма скорошна ангажираност и най-голям брой местни последователи за даден език."
       language: За език
@@ -851,10 +818,8 @@ bg:
       back_to_account: Назад към страницата на акаунта
       back_to_report: Назад към страницата на доклада
       batch:
-        add_to_report: 'Добавяне към доклад #%{id}'
         remove_from_report: Премахване от доклада
         report: Докладване
-      contents: Съдържание
       deleted: Изтрито
       favourites: Харесвани
       history: История на версиите
@@ -863,17 +828,13 @@ bg:
       media:
         title: Мултимедия
       metadata: Метаданни
-      no_history: Тази публикация не е била редактирана
       no_status_selected: Няма промяна, тъй като няма избрани публикации
       open: Отваряне на публикация
       original_status: Първообразна публикация
       reblogs: Блогване пак
-      replied_to_html: Отговорено до %{acct_link}
       status_changed: Публикацията променена
-      status_title: Публикация от @%{name}
-      title: Публикации на акаунт - @%{name}
+      title: Публикации на акаунта
       trending: Изгряващи
-      view_publicly: Преглед като публично
       visibility: Видимост
       with_media: С мултимедия
     strikes:
@@ -950,36 +911,6 @@ bg:
       search: Търсене
       title: Хаштагове
       updated_msg: Успешно осъвременени настройки на хаштага
-    terms_of_service:
-      back: Обратно към условията за ползване
-      changelog: Какво е променено
-      create: Употребявайте свой собствен
-      current: Текущо
-      draft: Чернова
-      generate: Употреба на шаблон
-      generates:
-        action: Пораждане
-        chance_to_review_html: "<strong>Пораждани условия на услугата няма да се публикуват самодейно.</strong> Ще имате възможност да видите предварително резултата. Попълнете необходимите подробности, за да продължите."
-        explanation_html: Предоставеният шаблон на условията за услугата е само за осведомителни цели и не трябва да се тълкува като правен съвет, по който и да е въпрос. Посъветвайте се със собствения си правен адвокат относно ситуацията ви и конкретни правни въпроси, които имате.
-        title: Настройка на условията за услугата
-      going_live_on_html: На живо, в сила от %{date}
-      history: История
-      live: На живо
-      no_history: Още няма записани промени в условията на услугата.
-      no_terms_of_service_html: В момента нямате никакви настроени условия за услугата. Условията за услугата са предназначени да осигурят яснота и да ви предпазят от възможни задължения при спорове с потребителите ви.
-      notified_on_html: Потребители, известени на %{date}
-      notify_users: Известете потребителите
-      preview:
-        explanation_html: 'Имейлът ще се изпрати до <strong>%{display_count} потребители</strong>, регистрирали се преди %{date}. Следният текст ще се включи в е-писмо:'
-        send_preview: Изпращане на предварителен преглед до %{email}
-        send_to_all:
-          one: Изпращане на %{display_count} имейл
-          other: Изпращане на %{display_count} имейла
-        title: Предварителен преглед на известие за условията на услугата
-      publish: Публикуване
-      published_on_html: Публикувано на %{date}
-      save_draft: Запазване на чернова
-      title: Условия на услугата
     title: Администрация
     trends:
       allow: Позволяване
@@ -1185,6 +1116,7 @@ bg:
     migrate_account: Преместване в различен акаунт
     migrate_account_html: Ако желаете да пренасочите този акаунт към друг, можете да <a href="%{path}">настроите това тук</a>.
     or_log_in_with: Или влизане с помощта на
+    privacy_policy_agreement_html: Прочетох и има съгласието ми за <a href="%{privacy_policy_path}" target="_blank">политиката за поверителност</a>
     progress:
       confirm: Потвърждаване на имейл
       details: Вашите подробности
@@ -1209,7 +1141,7 @@ bg:
     set_new_password: Задаване на нова парола
     setup:
       email_below_hint_html: Проверете папката си за спам или поискайте друго е-писмо. Може да поправите адреса на имейла си, ако е грешен.
-      email_settings_hint_html: Щракнете на връзката, която изпратихме до %{email}, за да започнете да употребявате Mastodon. Ще изчакаме точно тук.
+      email_settings_hint_html: Щракнете на връзката за потвърждаване, която ви изпратихме до %{email}. Ще ви почакаме тук.
       link_not_received: Не получихте ли връзка?
       new_confirmation_instructions_sent: До няколко минути ще получите друго е-писмо с връзка за потвърждаване!
       title: Проверете входящата си поща
@@ -1218,7 +1150,7 @@ bg:
       title: Влизане в %{domain}
     sign_up:
       manual_review: Регистрирането в %{domain} преминава през ръчен преглед от модераторите ни. Напишете малко за себе си и защо искате акаунт в %{domain}, за да ни помогнете в процеса на регистрацията си.
-      preamble: Имайки акаунт на този съвър в Mastodon, то ще може да последвате всекиго във федивселената, независимо къде се намира акаунтът му.
+      preamble: С акаунт на този съвър в Mastodon ще може да последвате всекиго в мрежата, независимо къде се намира акаунтът му.
       title: Първоначални настройки за %{domain}.
     status:
       account_status: Състояние на акаунта
@@ -1230,8 +1162,6 @@ bg:
       view_strikes: Преглед на предишните предупреждения против акаунта ви
     too_fast: Образецът подаден пребързо, опитайте пак.
     use_security_key: Употреба на ключ за сигурност
-    user_agreement_html: Прочетох и се съгласявам с <a href="%{terms_of_service_path}" target="_blank">условията на услугата</a> и <a href="%{privacy_policy_path}" target="_blank">политиката за поверителност</a>
-    user_privacy_agreement_html: Прочетох и има съгласието ми за <a href="%{privacy_policy_path}" target="_blank">политиката за поверителност</a>
   author_attribution:
     example_title: Примерен текст
     hint_html: Пишете ли новинарски статии или блогове извън Mastodon? Управлявайте как ви приписват авторството, когато са споделени в Mastodon.
@@ -1437,43 +1367,19 @@ bg:
       overwrite: Презаписване
       overwrite_long: Заменя текущите записи с новите
     overwrite_preambles:
-      blocking_html:
-        one: На път сте да <strong>замените списъка си с блокирани</strong> с до <strong>%{count} акаунт</strong> от <strong>%{filename}</strong>.
-        other: На път сте да <strong>замените списъка си с блокирани</strong> с до <strong>%{count} акаунта</strong> от <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: На път сте да <strong>замените списъка си с отметки</strong> с до <strong>%{count} публикация</strong> от <strong>%{filename}</strong>.
-        other: На път сте да <strong>замените списъка си с отметки</strong> с до <strong>%{count} публикации</strong> от <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: На път сте да <strong>замените списъка си с блокирани домейни</strong> с до <strong>%{count} домейн</strong> от <strong>%{filename}</strong>.
-        other: На път сте да <strong>замените списъка си с блокирани домейни</strong> с до <strong>%{count} домейна</strong> от <strong>%{filename}</strong>.
-      following_html:
-        one: На път сте да <strong>последвате</strong> до <strong>%{count} акаунт</strong> от <strong>%{filename}</strong> и <strong>да спрете да следвате някой друг</strong>.
-        other: На път сте да <strong>последвате</strong> до <strong>%{count} акаунта</strong> от <strong>%{filename}</strong> и <strong>да спрете да следвате някой друг</strong>.
-      lists_html:
-        one: На път сте да <strong>замените списъците си</strong> със съдържание от <strong>%{filename}</strong>. До <strong>%{count} акаунт</strong> ще се добави към новите списъци.
-        other: На път сте да <strong>замените списъците си</strong> със съдържание от <strong>%{filename}</strong>. До <strong>%{count} акаунта</strong> ще се добавят към новите списъци.
-      muting_html:
-        one: На път сте да <strong>замените списъка си със заглушен акаунт</strong> с до <strong>%{count} акаунт</strong> от <strong>%{filename}</strong>.
-        other: На път сте да <strong>замените списъка си със заглушени акаунти</strong> с до <strong>%{count} акаунта</strong> от <strong>%{filename}</strong>.
+      blocking_html: На път сте да <strong>замените списъка си с блокирани</strong> с до <strong>%{total_items} акаунта</strong> от <strong>%{filename}</strong>.
+      bookmarks_html: На път сте да <strong>замените списъка си с отметки</strong> с до <strong>%{total_items} публикации</strong> от <strong>%{filename}</strong>.
+      domain_blocking_html: На път сте да <strong>замените списъка си с блокирани домейни</strong> с до <strong>%{total_items} домейна</strong> от <strong>%{filename}</strong>.
+      following_html: На път сте да <strong>последвате</strong> до <strong>%{total_items} акаунта</strong> от <strong>%{filename}</strong> и <strong>да спрете да следвате някой друг</strong>.
+      lists_html: На път сте да <strong>замените списъците си</strong> със съдържание от <strong>%{filename}</strong>. До <strong>%{total_items} акаунта</strong> ще се добавят към новите списъци.
+      muting_html: На път сте да <strong>замените списъка си със заглушени акаунти</strong> с до <strong>%{total_items} акаунта</strong> от <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: На път сте да <strong>блокирате</strong> до <strong>%{count} акаунт</strong> от <strong>%{filename}</strong>.
-        other: На път сте да <strong>блокирате</strong> до <strong>%{count} акаунта</strong> от <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: На път сте да добавите до <strong>%{count} публикация</strong> от <strong>%{filename}</strong> в <strong>отметките</strong> си.
-        other: На път сте да добавите до <strong>%{count} публикации</strong> от <strong>%{filename}</strong> в <strong>отметките</strong> си.
-      domain_blocking_html:
-        one: На път сте да <strong>блокирате</strong> до <strong>%{count} домейн</strong> от <strong>%{filename}</strong>.
-        other: На път сте да <strong>блокирате</strong> до <strong>%{count} домейна</strong> от <strong>%{filename}</strong>.
-      following_html:
-        one: На път сте да <strong>последвате</strong> до <strong>%{count} акаунт</strong> от <strong>%{filename}</strong>.
-        other: На път сте да <strong>последвате</strong> до <strong>%{count} акаунта</strong> от <strong>%{filename}</strong>.
-      lists_html:
-        one: На път сте да добавите до <strong>%{count} акаунт</strong> от <strong>%{filename}</strong> към своите <strong>списъци</strong>. Нови списъци ще се сътворят, ако няма списъци за добавяне.
-        other: На път сте да добавите до <strong>%{count} акаунта</strong> от <strong>%{filename}</strong> към <strong>списъците</strong> си. Нови списъци ще се сътворят, ако няма списъци за добавяне.
-      muting_html:
-        one: На път сте да <strong>заглушите</strong> до <strong>%{count} акаунт</strong> от <strong>%{filename}</strong>.
-        other: На път сте да <strong>заглушите</strong> до <strong>%{count} акаунта</strong> от <strong>%{filename}</strong>.
+      blocking_html: На път сте да <strong>блокирате</strong> до <strong>%{total_items} акаунта</strong> от <strong>%{filename}</strong>.
+      bookmarks_html: На път сте да добавите до <strong>%{total_items} публикации</strong> от <strong>%{filename}</strong> в <strong>отметките</strong> си.
+      domain_blocking_html: На път сте да <strong>блокирате</strong> до <strong>%{total_items} домейна</strong> от <strong>%{filename}</strong>.
+      following_html: На път сте да <strong>последвате</strong> до <strong>%{total_items} акаунта</strong> от <strong>%{filename}</strong>.
+      lists_html: На път сте да добавите до <strong>%{total_items} акаунта</strong> от <strong>%{filename}</strong> към вашите <strong>списъци</strong>. Нови списъци ще се сътворят, ако няма списъци за добавяне.
+      muting_html: На път сте да <strong>заглушите</strong> до <strong>%{total_items} акаунта</strong> от <strong>%{filename}</strong>.
     preface: Можеш да импортираш някои данни, като например всички хора, които следваш или блокираш в акаунта си на тази инстанция, от файлове, създадени чрез експорт в друга инстанция.
     recent_imports: Скорошни внасяния
     states:
@@ -1730,7 +1636,7 @@ bg:
   scheduled_statuses:
     over_daily_limit: Завишили сте ограничението от %{limit} планирани публикации за днес
     over_total_limit: Завишили сте ограничението от %{limit} планирани публикации
-    too_soon: датата трябва да е в бъдеще
+    too_soon: Заплануваната дата трябва да е в бъдеще
   self_destruct:
     lead_html: За жалост, <strong>%{domain}</strong> е трайно затворен. Ако сте имали там акаунт, то няма да може да продължите да го употребявате, но още може да заявите резервно копие на данните си.
     title: Този сървър се затваря
@@ -1893,8 +1799,6 @@ bg:
       too_late: Твърде късно е за обжалване на това предупреждение
   tags:
     does_not_match_previous_name: не съвпада с предишното име
-  terms_of_service:
-    title: Условия на услугата
   themes:
     contrast: Mastodon (висок контраст)
     default: Mastodon (тъмно)
@@ -1926,8 +1830,6 @@ bg:
     recovery_instructions_html: Ако изгубите достъп до телефона си, може да използвате долните кодовете за възстановяване, за да достъп до акаунта си. <strong>Запазете тези кодове на сигурно място</strong>. Например, можете да ги отпечатате и да ги складирате заедно с други важни документи.
     webauthn: Ключове за сигурност
   user_mailer:
-    announcement_published:
-      description: 'Администраторите на %{domain} оповестяват:'
     appeal_approved:
       action: Настройки на акаунта
       explanation: Жалбата, която изпратихте на %{appeal_date}, срещу нарушението за вашия акаунт, направено на %{strike_date}, е приета. Вашият акаунт е отново с добра репутация.
@@ -1957,15 +1859,6 @@ bg:
       further_actions_html: Ако това не сте били вие, препоръчваме да извършите действие %{action} незабавно и да включите двуфакторното удостоверяване, за да запазите сигурността на своя акаунт.
       subject: Вашият акаунт е използвал достъп от нов IP адрес
       title: Нов вход
-    terms_of_service_changed:
-      agreement: Продължавайки употребата на %{domain}, съгласявате се с тези условия. Ако не сте съгласни с осъвременените условия, то може по всяко време да прекратите съгласието си с %{domain}, изтривайки акаунта си.
-      changelog: 'Накратко, ето какво значи това обновяване за вас:'
-      description: 'Получвате това е-писмо, защото сме направили някои промени в условията ни за услугата при %{domain}. Тези новости ще влязат в сила на %{date}. Насърчаваме ви да разгледате изцяло обновените условия тук:'
-      description_html: Получавате това е-писмо, защото правим някои промени по условията ни за услугата при %{domain}. Тези новости ще влязат в сила на <strong>%{date}</strong>. Насърчаваме ви предварително да прегледате <a href="%{path}" target="_blank">изцяло обновените условия тук</a>.
-      sign_off: Отборът на %{domain}
-      subject: Новости в нашите условия за ползване
-      subtitle: Условията на услугата на %{domain} се променят
-      title: Важно обновление
     warning:
       appeal: Подаване на обжалване
       appeal_description: Ако вярвате, че това е грешка, то може да подадете обжалване до щаба на %{instance}.
diff --git a/config/locales/br.yml b/config/locales/br.yml
index e61a43643e..f9fbd34adb 100644
--- a/config/locales/br.yml
+++ b/config/locales/br.yml
@@ -47,7 +47,6 @@ br:
       demote: Argilañ
       disable: Skornañ
       disabled: Skornet
-      display_name: Anv diskouezet
       domain: Domani
       edit: Kemmañ
       email: Postel
@@ -67,7 +66,6 @@ br:
       moderation:
         active: Oberiant
         all: Pep tra
-        disabled: Diweredekaet
         pending: War ober
         silenced: Bevennet
         suspended: Astalet
@@ -79,8 +77,6 @@ br:
       public: Publik
       reject: Nac'hañ
       remove_header: Dilemel an talbenn
-      resend_confirmation:
-        send: Adkas al liamm-kadarnaat
       reset: Adderaouekaat
       reset_password: Adderaouekaat ar ger-tremen
       resubscribe: Adkoumanantiñ
@@ -101,14 +97,7 @@ br:
       web: Web
     action_logs:
       action_types:
-        approve_user: Aprouiñ an implijer
-        change_role_user: Kemmañ perzh an implijer
-        confirm_user: Aprouiñ an implijer
-        create_ip_block: Krouiñ ur reolenn IP
-        create_user_role: Krouiñ ur perzh
-        destroy_ip_block: Dilemel ar reolenn IP
         destroy_status: Dilemel ar c'hannad
-        reset_password_user: Adderaouekaat ar ger-tremen
         update_status: Hizivaat ar c'hannad
       actions:
         destroy_status_html: Dilamet eo bet toud %{target} gant %{name}
@@ -149,15 +138,12 @@ br:
       export: Ezporzhiañ
       import: Enporzhiañ
     domain_blocks:
-      confirm_suspension:
-        cancel: Nullañ
       domain: Domani
       new:
         create: Sevel ur stanker
         severity:
           noop: Hini ebet
           suspend: Astalañ
-      public_comment: Evezhiadenn foran
     email_domain_blocks:
       add_new: Ouzhpenniñ unan nevez
       delete: Dilemel
@@ -169,18 +155,6 @@ br:
         create: Ouzhpenniñ un domani
     export_domain_allows:
       no_file: Restr ebet diuzet
-    fasp:
-      debug:
-        callbacks:
-          delete: Dilemel
-          ip: Chomlec'h IP
-      providers:
-        active: Oberiant
-        delete: Dilemel
-        name: Anv
-        registrations:
-          confirm: Kadarnaat
-        status: Statud
     follow_recommendations:
       status: Statud
       suppressed: Dilamet
@@ -195,7 +169,6 @@ br:
           suspend: Astalañ
         policy: Reolennoù
       dashboard:
-        instance_languages_dimension: Yezhoù pennañ
         instance_statuses_measure: toudoù stoket
       delivery:
         all: Pep tra
@@ -206,9 +179,6 @@ br:
         title: Habaskadur
       purge: Spurjañ
       title: Kevread
-      total_blocked_by_us: Stanket ganeomp
-      total_followed_by_them: Heuliet ganto
-      total_followed_by_us: Heuliet ganeomp
       total_storage: Restroù media stag
     invites:
       filter:
@@ -218,7 +188,6 @@ br:
         title: Sil
       title: Pedadennoù
     ip_blocks:
-      add_new: Krouiñ ur reolenn
       delete: Dilemel
       expires_in:
         '1209600': 2 sizhunvezh
@@ -226,9 +195,6 @@ br:
         '31556952': 1 bloavezh
         '86400': 1 devezh
         '94670856': 3 bloavezh
-      new:
-        title: Krouiñ ur reolenn IP nevez
-      title: Reolennoù IP
     relays:
       delete: Dilemel
       disable: Diweredekaat
@@ -249,7 +215,6 @@ br:
       are_you_sure: Ha sur oc'h?
       comment:
         none: Hini ebet
-      confirm: Kadarnaat
       delete_and_resolve: Dilemel ar c'hannadoù
       forwarded: Treuzkaset
       no_one_assigned: Den ebet
@@ -298,7 +263,7 @@ br:
       original_status: Toud orin
       reblogs: Skignadennoù
       status_changed: Toud kemmet
-      status_title: Embannadenn gant @%{name}
+      title: Toudoù ar gont
       visibility: Gwelusted
       with_media: Gant mediaoù
     strikes:
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index 17efbcb4b6..a0178ad195 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -187,7 +187,6 @@ ca:
         create_domain_block: Crea un bloqueig de domini
         create_email_domain_block: Crea un blocatge del domini del correu electrònic
         create_ip_block: Crear regla IP
-        create_relay: Crea un repetidor
         create_unavailable_domain: Crea un domini no disponible
         create_user_role: Crea Rol
         demote_user: Degrada l'usuari
@@ -199,22 +198,18 @@ ca:
         destroy_email_domain_block: Elimina el blocatge del domini del correu electrònic
         destroy_instance: Purga Domini
         destroy_ip_block: Eliminar regla IP
-        destroy_relay: Esborra el repetidor
         destroy_status: Elimina la publicació
         destroy_unavailable_domain: Esborra domini no disponible
         destroy_user_role: Destrueix Rol
         disable_2fa_user: Desactiva 2FA
         disable_custom_emoji: Desactiva l'emoji personalitzat
-        disable_relay: Desactiva el repetidor
         disable_sign_in_token_auth_user: Desactivar l'autenticació de token per correu-e per a l'usuari
         disable_user: Deshabilita l'usuari
         enable_custom_emoji: Activa l'emoji personalitzat
-        enable_relay: Activa el repetidor
         enable_sign_in_token_auth_user: Activar l'autenticació de token per correu-e per a l'usuari
         enable_user: Activa l'usuari
         memorialize_account: Memoritza el compte
         promote_user: Promou l'usuari
-        publish_terms_of_service: Publicar les condicions de servei
         reject_appeal: Rebutja l'apel·lació
         reject_user: Rebutja l'usuari
         remove_avatar_user: Eliminar avatar
@@ -252,7 +247,6 @@ ca:
         create_domain_block_html: "%{name} ha bloquejat el domini %{target}"
         create_email_domain_block_html: "%{name} ha blocat el domini de correu electrònic %{target}"
         create_ip_block_html: "%{name} ha creat una regla per a l'IP %{target}"
-        create_relay_html: "%{name} ha creat un repetidor %{target}"
         create_unavailable_domain_html: "%{name} ha aturat el lliurament al domini %{target}"
         create_user_role_html: "%{name} ha creat el rol %{target}"
         demote_user_html: "%{name} ha degradat l'usuari %{target}"
@@ -264,22 +258,18 @@ ca:
         destroy_email_domain_block_html: "%{name} ha desblocat el domini de correu electrònic %{target}"
         destroy_instance_html: "%{name} ha purgat el domini %{target}"
         destroy_ip_block_html: "%{name} ha esborrat la regla per a l'IP %{target}"
-        destroy_relay_html: "%{name} ha esborrat el repetidor %{target}"
         destroy_status_html: "%{name} ha eliminat el tut de %{target}"
         destroy_unavailable_domain_html: "%{name} ha représ el lliurament delivery al domini %{target}"
         destroy_user_role_html: "%{name} ha esborrat el rol %{target}"
         disable_2fa_user_html: "%{name} ha desactivat el requisit de dos factors per a l'usuari %{target}"
         disable_custom_emoji_html: "%{name} ha desactivat l'emoji %{target}"
-        disable_relay_html: "%{name} ha desactivat el repetidor %{target}"
         disable_sign_in_token_auth_user_html: "%{name} ha desactivat l'autenticació de token per correu-e per a %{target}"
         disable_user_html: "%{name} ha desactivat l'accés del usuari %{target}"
         enable_custom_emoji_html: "%{name} ha activat l'emoji %{target}"
-        enable_relay_html: "%{name} ha activat el repetidor %{target}"
         enable_sign_in_token_auth_user_html: "%{name} ha activat l'autenticació de token per correu-e per a %{target}"
         enable_user_html: "%{name} ha activat l'accés del usuari %{target}"
         memorialize_account_html: "%{name} ha convertit el compte %{target} en una pàgina de memorial"
         promote_user_html: "%{name} ha promogut l'usuari %{target}"
-        publish_terms_of_service_html: "%{name} ha publicat actualitzacions a les condicions de servei"
         reject_appeal_html: "%{name} ha rebutjat l'apel·lació a la decisió de moderació de %{target}"
         reject_user_html: "%{name} ha rebutjat el registre de %{target}"
         remove_avatar_user_html: "%{name} ha eliminat l'avatar de %{target}"
@@ -309,7 +299,6 @@ ca:
       title: Registre d'auditoria
       unavailable_instance: "(nom de domini no disponible)"
     announcements:
-      back: Torna als anuncis
       destroyed_msg: L’anunci s’ha esborrat amb èxit!
       edit:
         title: Edita l'anunci
@@ -318,9 +307,6 @@ ca:
       new:
         create: Crea un anunci
         title: Nou anunci
-      preview:
-        explanation_html: 'S''enviarà el correu-e a <strong>%{display_count} usuaris</strong>. S''hi inclourà el text següent:'
-        title: Vista prèvia de la notificació d'anunci
       publish: Publica
       published_msg: L’anunci s’ha publicat amb èxit!
       scheduled_for: Programat per a %{time}
@@ -479,26 +465,6 @@ ca:
       new:
         title: Importa dominis bloquejats
       no_file: No s'ha seleccionat cap fitxer
-    fasp:
-      debug:
-        callbacks:
-          created_at: Data de creació
-          delete: Elimina
-          ip: Adreça IP
-          request_body: Solicita el cos
-      providers:
-        active: Actiu
-        base_url: URL base
-        callback: Torna la trucada
-        delete: Elimina
-        edit: Editeu el perfil
-        name: Nom
-        registrations:
-          confirm: Confirma
-          reject: Rebutja
-        save: Desa
-        sign_in: Inicieu la sessió
-        status: Estat
     follow_recommendations:
       description_html: "<strong>Seguir les recomanacions ajuda als nous usuaris a trobar ràpidament contingut interessant</strong>. Quan un usuari no ha interactuat prou amb altres per a formar a qui seguir personalment, aquests comptes li seran recomanats. Es recalculen diàriament a partir d'una barreja de comptes amb els compromisos recents més alts i el nombre més alt de seguidors locals per a un idioma determinat."
       language: Per idioma
@@ -852,10 +818,8 @@ ca:
       back_to_account: Torna a la pàgina del compte
       back_to_report: Torna a la pàgina de l'informe
       batch:
-        add_to_report: 'Afegiu a l''informe #%{id}'
         remove_from_report: Treu de l'informe
         report: Denuncia
-      contents: Continguts
       deleted: Eliminada
       favourites: Favorits
       history: Històric de versions
@@ -864,17 +828,13 @@ ca:
       media:
         title: Contingut multimèdia
       metadata: Metadada
-      no_history: Aquesta publicació no s'ha editat
       no_status_selected: No s’han canviat els estatus perquè cap no ha estat seleccionat
       open: Obre la publicació
       original_status: Publicació original
       reblogs: Impulsos
-      replied_to_html: En resposta a %{acct_link}
       status_changed: Publicació canviada
-      status_title: Publicació de @%{name}
-      title: Publicacions del compte - @%{name}
+      title: Estats del compte
       trending: Tendència
-      view_publicly: Vegeu en públic
       visibility: Visibilitat
       with_media: Amb contingut multimèdia
     strikes:
@@ -951,36 +911,6 @@ ca:
       search: Cerca
       title: Etiquetes
       updated_msg: Ajustaments d'etiquetes actualitzats amb èxit
-    terms_of_service:
-      back: Tornar a les condicions de servei
-      changelog: Què ha canviat
-      create: Feu servir el vostre
-      current: Actual
-      draft: Esborrany
-      generate: Fer servir la plantilla
-      generates:
-        action: Generar
-        chance_to_review_html: "<strong>Les condicions de servei generades no es publicaran automàticament.</strong> Tindreu l'oportunitat de revisar-ne els resultats. Empleneu els detalls necessaris per a procedir."
-        explanation_html: La plantilla de condicions de servei proveïda ho és només a títol informatiu i no s'ha d'interpretar com a consell jurídic en cap cas. Consulteu el vostre propi servei legal sobre la vostra situació i les qüestions legals específiques que tingueu.
-        title: Configuració de les condicions de servei
-      going_live_on_html: En vigor; des de %{date}
-      history: Historial
-      live: En ús
-      no_history: Encara no hi ha enregistrats canvis en les condicions de servei.
-      no_terms_of_service_html: Ara no teniu cap condició de servei configurada. Aquestes tenen per objectiu clarificar drets i obligacions i protegir-vos de responsabilitats potencials en disputes amb els vostres usuaris.
-      notified_on_html: Usuaris notificats el %{date}
-      notify_users: Notifica els usuaris
-      preview:
-        explanation_html: 'Aquest correu-e s''enviarà a <strong>%{display_count} usuaris</strong> que han signat abans de %{date}. S''hi inclourà aquest text:'
-        send_preview: Envia una vista prèvia a %{email}
-        send_to_all:
-          one: Envia %{display_count} correu-e
-          other: Envia %{display_count} correus-e
-        title: Vista prèvia de la notificació de les condicions de servei
-      publish: Publica
-      published_on_html: Publicada el %{date}
-      save_draft: Desa l'esborrany
-      title: Condicions de servei
     title: Administració
     trends:
       allow: Permet
@@ -1188,6 +1118,7 @@ ca:
     migrate_account: Mou a un compte diferent
     migrate_account_html: Si vols redirigir aquest compte a un altre diferent, el pots <a href="%{path}">configurar aquí</a>.
     or_log_in_with: O inicia sessió amb
+    privacy_policy_agreement_html: He llegit i estic d'acord amb la <a href="%{privacy_policy_path}" target="_blank">política de privacitat</a>
     progress:
       confirm: Confirmar email
       details: Els teus detalls
@@ -1212,7 +1143,7 @@ ca:
     set_new_password: Estableix una contrasenya nova
     setup:
       email_below_hint_html: Verifiqueu la carpeta de correu brossa o demaneu-ne un altre. Podeu corregir l'adreça de correu-e si no és correcta.
-      email_settings_hint_html: Feu clic a l'enllaç que us hem enviat a %{email} per a començar a utilitzar Mastodon. Us esperem.
+      email_settings_hint_html: Toca l'enllaç que t'hem enviat per a verificar %{email}. Esperarem aquí mateix.
       link_not_received: No has rebut l'enllaç?
       new_confirmation_instructions_sent: Rebràs un nou correu amb l'enllaç de confirmació en pocs minuts!
       title: Comprova la teva safata d'entrada
@@ -1221,7 +1152,7 @@ ca:
       title: Inicia sessió a %{domain}
     sign_up:
       manual_review: Els registres a %{domain} passen per una revisió manual dels nostres moderadors. Per ajudar-nos a processar el teu registre, escriu-nos quelcom sobre tu i perquè vols un compte a %{domain}.
-      preamble: Amb un compte en aquest servidor de Mastodon podreu seguir qualsevol altra persona al Fedivers, sense importar a quin servidor tingui el compte.
+      preamble: Amb un compte en aquest servidor Mastodon podràs seguir qualsevol altra persona de la xarxa, independentment d'on tingui el compte.
       title: Configurem-te a %{domain}.
     status:
       account_status: Estat del compte
@@ -1233,8 +1164,6 @@ ca:
       view_strikes: Veure accions del passat contra el teu compte
     too_fast: Formulari enviat massa ràpid, torna a provar-ho.
     use_security_key: Usa clau de seguretat
-    user_agreement_html: He llegit i estic d'acord amb les <a href="%{terms_of_service_path}" target="_blank">condicions de servei</a> i la <a href="%{privacy_policy_path}" target="_blank">política de privadesa</a>
-    user_privacy_agreement_html: He llegit i estic d'acord amb la <a href="%{privacy_policy_path}" target="_blank">política de privacitat</a>
   author_attribution:
     example_title: Text d'exemple
     hint_html: Escriviu notícies o un blog fora de Mastodon? Controleu quin crèdit rebeu quan es comparteixen aquí.
@@ -1440,21 +1369,19 @@ ca:
       overwrite: Sobreescriu
       overwrite_long: Reemplaça els registres actuals amb els nous
     overwrite_preambles:
-      blocking_html:
-        one: Esteu a punt de <strong>reemplaçar la vostra llista de blocats</strong> amb fins a <strong>%{count} compte</strong> des de <strong>%{filename}</strong>.
-        other: Esteu a punt de <strong>reemplaçar la vostra llista de blocats</strong> amb fins a <strong>%{count} comptes</strong> des de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Esteu a punt de <strong>reemplaçar els vostres marcadors</strong> amb fins a <strong>%{count} publicació</strong> des de <strong>%{filename}</strong>.
-        other: Esteu a punt de <strong>reemplaçar els vostres marcadors</strong> amb fins a <strong>%{count} publicacions</strong> des de <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Esteu a punt de <strong>reemplaçar la vostra llista de dominis blocats</strong> amb fins a <strong>%{count} domini</strong> des de <strong>%{filename}</strong>.
-        other: Esteu a punt de <strong>reemplaçar la vostra llista de dominis blocats</strong> amb fins a <strong>%{count} dominis</strong> des de <strong>%{filename}</strong>.
-      following_html:
-        one: Esteu a punt de <strong>seguir</strong> fins a <strong>%{count} compte</strong> des de <strong>%{filename}</strong> i <strong>deixar de seguir la resta</strong>.
-        other: Esteu a punt de <strong>seguir</strong> fins a <strong>%{count} comptes</strong> des de <strong>%{filename}</strong> i <strong>deixar de seguir la resta</strong>.
-      lists_html:
-        one: Esteu a punt de <strong>reemplaçar les vostres llistes</strong> amb contactes de <strong>%{filename}</strong>. S'afegirà <strong>%{count} compte</strong> a les noves llistes.
-        other: Esteu a punt de <strong>reemplaçar les vostres llistes</strong> amb contactes de <strong>%{filename}</strong>. S'afegiran fins a <strong>%{count} comptes</strong> a les noves llistes.
+      blocking_html: Ets a punt per a <strong>reemplaçar la teva llista de bloquejos</strong> de fins a <strong>%{total_items} comptes</strong> des de <strong>%{filename}</strong>.
+      bookmarks_html: Ets a punt de <strong>reemplaçar els teus marcadors</strong> de fins a <strong>%{total_items} tuts</strong> des de <strong>%{filename}</strong>.
+      domain_blocking_html: Ets a punt de <strong>reemplaçar la teva llista de dominis bloquejats</strong> de fins a <strong>%{total_items} dominis</strong> des de <strong>%{filename}</strong>.
+      following_html: Ets a punt de <strong>seguir</strong> fins a <strong>%{total_items} comptes</strong> des de <strong>%{filename}</strong> i <strong>deixar de seguir a la resta</strong>.
+      lists_html: Estàs a punt de <strong>reemplaçar les teves llistes</strong> amb contactes de <strong>%{filename}</strong>. S'afegiran fins a <strong>%{total_items} comptes</strong> a les noves llistes.
+      muting_html: Ets a punt de <strong>reemplaçar la teva llista de comptes silenciats</strong> de fins a <strong>%{total_items} comptes</strong> des de <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Ets a punt de <strong>bloquejar</strong> fins a <strong>%{total_items} comptes</strong> des de <strong>%{filename}</strong>.
+      bookmarks_html: Ets a punt d'afegir fins a <strong>%{total_items} tuts</strong> des de <strong>%{filename}</strong> als teus <strong>marcadors</strong>.
+      domain_blocking_html: Ets a punt de <strong>bloquejar</strong> fins a <strong>%{total_items} dominis</strong> des de <strong>%{filename}</strong>.
+      following_html: Ets a punt de <strong>seguir</strong> fins a <strong>%{total_items} comptes</strong> des de <strong>%{filename}</strong>.
+      lists_html: Estàs a punt d'afegir <strong>%{total_items} comptes</strong> de <strong>%{filename}</strong> a les teves <strong>llistes</strong>. Es crearan noves llistes si no hi ha cap llista on afegir-los.
+      muting_html: Ets a punt de <strong>silenciar</strong> fins a <strong>%{total_items} comptes</strong> des de <strong>%{filename}</strong>.
     preface: Pots importar algunes les dades que has exportat des d'un altre servidor, com ara el llistat de les persones que estàs seguint o bloquejant.
     recent_imports: Importacions recents
     states:
@@ -1711,7 +1638,7 @@ ca:
   scheduled_statuses:
     over_daily_limit: Has superat el límit de %{limit} tuts programats per a avui
     over_total_limit: Has superat el límit de %{limit} tuts programats
-    too_soon: la data ha de ser al futur
+    too_soon: La data programada ha de ser futura
   self_destruct:
     lead_html: Lamentablement, <strong>%{domain}</strong> tanca de forma definitiva. Si hi teníeu un compte, no el podreu continuar utilitzant, però podeu demanar una còpia de les vostres dades.
     title: Aquest servidor tancarà
@@ -1874,8 +1801,6 @@ ca:
       too_late: És massa tard per a apel·lar aquesta acció
   tags:
     does_not_match_previous_name: no coincideix amb el nom anterior
-  terms_of_service:
-    title: Condicions de servei
   themes:
     contrast: Mastodon (alt contrast)
     default: Mastodon (fosc)
@@ -1907,10 +1832,6 @@ ca:
     recovery_instructions_html: Si mai perds l'accés al teu telèfon, pots fer servir un dels codis de recuperació a continuació per a recuperar l'accés al teu compte. <strong>Cal mantenir els codis de recuperació en lloc segur</strong>. Per exemple, imprimint-los i guardar-los amb altres documents importants.
     webauthn: Claus de seguretat
   user_mailer:
-    announcement_published:
-      description: 'L''administració de %{domain} fa un anunci:'
-      subject: Anunci de servei
-      title: Anunci de servei de %{domain}
     appeal_approved:
       action: Configuració del compte
       explanation: L'apel·lació a l'acció contra el teu compte del %{strike_date} que has enviat el %{appeal_date} ha estat aprovada. El teu compte torna a estar en bon estat.
@@ -1940,13 +1861,6 @@ ca:
       further_actions_html: Si no has estat tu, %{action} immediatament i activa l'autenticació de dos-factors per a mantenir el teu compte segur.
       subject: S'ha accedit al teu compte des d'una adreça IP nova
       title: Un inici de sessió nou
-    terms_of_service_changed:
-      agreement: En continuar fent servir %{domain} accepteu aquestes condicions. Si no esteu d'acord amb els nous termes, podeu acabar el vostre acord amb %{domain} tot esborrant el vostre compte.
-      changelog: 'En un cop d''ull, això és el que aquest canvi us implica:'
-      sign_off: L'equip de %{domain}
-      subject: Actualitzacions de les condicions de servei
-      subtitle: Les condicions de servei de %{domain} canvien
-      title: Actualització important
     warning:
       appeal: Envia una apel·lació
       appeal_description: Si creus que això és un error, pots enviar una apel·lació a l'equip de %{instance}.
diff --git a/config/locales/ckb.yml b/config/locales/ckb.yml
index 0f2a5ed364..88dadff014 100644
--- a/config/locales/ckb.yml
+++ b/config/locales/ckb.yml
@@ -520,6 +520,7 @@ ckb:
       media:
         title: میدیا
       no_status_selected: هیچ دۆخیک نەگۆڕاوە وەک ئەوەی هیچ بارێک دەستنیشان نەکراوە
+      title: دۆخی ئەژمێر
       with_media: بە میدیا
     tags:
       review: پێداچوونەوەی دۆخ
@@ -863,6 +864,7 @@ ckb:
   scheduled_statuses:
     over_daily_limit: ئێوە لە سنووری ڕیپێدراوی %{limit} توتی ئەو رۆژە،خۆرتر ڕۆیشتوویت
     over_total_limit: تۆ سنووری خشتەکراوی %{limit} ت بەزاندووە
+    too_soon: پێویستە بەرواری خشتەکراو لە داهاتوودا بێت
   sessions:
     activity: دوایین چالاکی
     browser: وێبگەڕ
diff --git a/config/locales/co.yml b/config/locales/co.yml
index 2e45ab9263..dad3e08ffd 100644
--- a/config/locales/co.yml
+++ b/config/locales/co.yml
@@ -459,6 +459,7 @@ co:
       media:
         title: Media
       no_status_selected: I statuti ùn sò micca stati mudificati perchè manc'unu era selezziunatu
+      title: Statuti di u contu
       with_media: Cù media
     system_checks:
       rules_check:
@@ -843,6 +844,7 @@ co:
   scheduled_statuses:
     over_daily_limit: Avete trapassatu a limita di %{limit} statuti pianificati per stu ghjornu
     over_total_limit: Avete trapassatu a limita di %{limit} statuti pianificati
+    too_soon: A data deve esse indè u futuru
   sessions:
     activity: Ultima attività
     browser: Navigatore
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index 37cd0b3bf6..34cedcf89a 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -99,7 +99,7 @@ cs:
         active: Aktivní
         all: Vše
         disabled: Deaktivován
-        pending: Nevyřízeno
+        pending: Čekající
         silenced: Omezeno
         suspended: Pozastavené
         title: Moderování
@@ -193,7 +193,6 @@ cs:
         create_domain_block: Vytvořit blokaci domény
         create_email_domain_block: Vytvořit blok e-mailové domény
         create_ip_block: Vytvořit IP pravidlo
-        create_relay: Vytvořit relay
         create_unavailable_domain: Vytvořit nedostupnou doménu
         create_user_role: Vytvořit roli
         demote_user: Snížit roli uživatele
@@ -205,22 +204,18 @@ cs:
         destroy_email_domain_block: Smazat blokaci e-mailové domény
         destroy_instance: Odmazat doménu
         destroy_ip_block: Smazat IP pravidlo
-        destroy_relay: Smazat relay
         destroy_status: Odstranit Příspěvek
         destroy_unavailable_domain: Smazat nedostupnou doménu
         destroy_user_role: Zničit roli
         disable_2fa_user: Vypnout 2FA
         disable_custom_emoji: Zakázat vlastní emoji
-        disable_relay: Deaktivovat relay
         disable_sign_in_token_auth_user: Zrušit uživatelovo ověřování e-mailovým tokenem
         disable_user: Deaktivovat uživatele
         enable_custom_emoji: Povolit vlastní emoji
-        enable_relay: Aktivovat relay
         enable_sign_in_token_auth_user: Povolit uživatelovo ověřování e-mailovým tokenem
         enable_user: Povolit uživatele
         memorialize_account: Změna na „in memoriam“
         promote_user: Povýšit uživatele
-        publish_terms_of_service: Zveřejnit smluvní podmínky
         reject_appeal: Zamítnout odvolání
         reject_user: Odmítnout uživatele
         remove_avatar_user: Odstranit avatar
@@ -258,7 +253,6 @@ cs:
         create_domain_block_html: Uživatel %{name} zablokoval doménu %{target}
         create_email_domain_block_html: Uživatel %{name} zablokoval e-mailovou doménu %{target}
         create_ip_block_html: Uživatel %{name} vytvořil pravidlo pro IP %{target}
-        create_relay_html: "%{name} vytvořil*a relay %{target}"
         create_unavailable_domain_html: "%{name} zastavil doručování na doménu %{target}"
         create_user_role_html: "%{name} vytvořil %{target} roli"
         demote_user_html: Uživatel %{name} degradoval uživatele %{target}
@@ -270,22 +264,18 @@ cs:
         destroy_email_domain_block_html: "%{name} odblokoval*a e-mailovou doménu %{target}"
         destroy_instance_html: Uživatel %{name} odmazal doménu %{target}
         destroy_ip_block_html: Uživatel %{name} odstranil pravidlo pro IP %{target}
-        destroy_relay_html: "%{name} odstranil*a relay %{target}"
         destroy_status_html: Uživatel %{name} odstranil příspěvek uživatele %{target}
         destroy_unavailable_domain_html: "%{name} obnovil doručování na doménu %{target}"
         destroy_user_role_html: "%{name} odstranil %{target} roli"
         disable_2fa_user_html: Uživatel %{name} vypnul dvoufázové ověřování pro uživatele %{target}
         disable_custom_emoji_html: Uživatel %{name} zakázal emoji %{target}
-        disable_relay_html: "%{name} deaktivoval*a relay %{target}"
         disable_sign_in_token_auth_user_html: "%{name} deaktivoval*a ověřování e-mailovým tokenem pro %{target}"
         disable_user_html: Uživatel %{name} zakázal přihlašování pro uživatele %{target}
         enable_custom_emoji_html: Uživatel %{name} povolil emoji %{target}
-        enable_relay_html: "%{name} aktivoval*a relay %{target}"
         enable_sign_in_token_auth_user_html: "%{name} aktivoval*a ověřování e-mailovým tokenem pro %{target}"
         enable_user_html: Uživatel %{name} povolil přihlašování pro uživatele %{target}
         memorialize_account_html: Uživatel %{name} změnil účet %{target} na „in memoriam“ stránku
         promote_user_html: Uživatel %{name} povýšil uživatele %{target}
-        publish_terms_of_service_html: "%{name} zveřejnil*a aktualizace podmínek služby"
         reject_appeal_html: Uživatel %{name} zamítl odvolání proti rozhodnutí moderátora %{target}
         reject_user_html: "%{name} odmítl registraci od %{target}"
         remove_avatar_user_html: Uživatel %{name} odstranil avatar uživatele %{target}
@@ -315,7 +305,6 @@ cs:
       title: Protokol auditu
       unavailable_instance: "(doména není k dispozici)"
     announcements:
-      back: Zpět na oznámení
       destroyed_msg: Oznámení bylo úspěšně odstraněno!
       edit:
         title: Upravit oznámení
@@ -324,10 +313,6 @@ cs:
       new:
         create: Vytvořit oznámení
         title: Nové oznámení
-      preview:
-        disclaimer: Vzhledem k tomu, že se od nich uživatelé nemohou odhlásit, měla by být e-mailová upozornění omezena na důležitá oznámení, jako je narušení osobních údajů nebo oznámení o uzavření serveru.
-        explanation_html: 'E-mail bude odeslán <strong>%{display_count} uživatelům</strong>. Následující text bude zahrnut do onoho e-mailu:'
-        title: Náhled oznámení
       publish: Zveřejnit
       published_msg: Oznámení bylo úspěšně zveřejněno!
       scheduled_for: Naplánováno na %{time}
@@ -496,36 +481,6 @@ cs:
       new:
         title: Importovat blokované domény
       no_file: Nebyl vybrán žádný soubor
-    fasp:
-      debug:
-        callbacks:
-          created_at: Vytvořeno
-          delete: Odstranit
-          ip: IP adresa
-          request_body: Tělo požadavku
-          title: Ladit callbacky
-      providers:
-        active: Aktivní
-        base_url: Základní URL
-        callback: Callback
-        delete: Smazat
-        edit: Upravit poskytovatele
-        finish_registration: Dokončit registraci
-        name: Jméno
-        providers: Poskytovatelé
-        public_key_fingerprint: Otisk veřejného klíče
-        registration_requested: Registrace požádána
-        registrations:
-          confirm: Potvrdit
-          description: Obdrželi jste registraci od FASP. Odmítněte ji, pokud jste ji neiniciovali. Pokud jste ji iniciovali, pečlivě porovnejte jméno a otisk klíče před jejím potvrzením.
-          reject: Zamítnout
-          title: Potvrdit registraci FASP
-        save: Uložit
-        select_capabilities: Vybrat možnosti
-        sign_in: Přihlásit se
-        status: Stav
-        title: Fediverse Auxiliary Service Providers
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Doporučená sledování pomáhají novým uživatelům rychle najít zajímavý obsah</strong>. Pokud uživatel neinteragoval s ostatními natolik, aby mu byla vytvořena doporučená sledování na míru, jsou použity tyto účty. Jsou přepočítávány na denní bázi ze směsi účtů s největším nedávným zapojením a nejvyšším počtem místních sledovatelů pro daný jazyk."
       language: Pro jazyk
@@ -891,10 +846,8 @@ cs:
       back_to_account: Zpět na stránku účtu
       back_to_report: Zpět na stránku hlášení
       batch:
-        add_to_report: 'Přidat do hlášení #%{id}'
         remove_from_report: Odebrat z hlášení
         report: Nahlásit
-      contents: Obsah
       deleted: Smazáno
       favourites: Oblíbené
       history: Historie verzí
@@ -903,17 +856,13 @@ cs:
       media:
         title: Média
       metadata: Metadata
-      no_history: Tento příspěvek nebyl upraven
       no_status_selected: Nebyly změněny žádné příspěvky, neboť žádné nebyly vybrány
       open: Otevřít příspěvek
       original_status: Původní příspěvek
       reblogs: Boosty
-      replied_to_html: Odpověděl %{acct_link}
       status_changed: Příspěvek změněn
-      status_title: Příspěvek od @%{name}
-      title: Příspěvky na účtu - @%{name}
+      title: Příspěvky účtu
       trending: Populární
-      view_publicly: Zobrazit veřejně
       visibility: Viditelnost
       with_media: S médii
     strikes:
@@ -990,38 +939,6 @@ cs:
       search: Hledat
       title: Hashtagy
       updated_msg: Nastavení hashtagů bylo úspěšně aktualizováno
-    terms_of_service:
-      back: Zpět na podmínky služby
-      changelog: Co se změnilo
-      create: Použít vlastní
-      current: Aktuální
-      draft: Koncept
-      generate: Použít šablonu
-      generates:
-        action: Vygenerovat
-        chance_to_review_html: "<strong>Vygenerované podmínky služby nebudou zveřejněny automaticky.</strong> Výsledky budete mít možnost zkontrolovat. Pro pokračování vyplňte potřebné podrobnosti."
-        explanation_html: Poskytovaná šablona služeb je určena pouze pro informační účely a neměla by být vykládána jako právní poradenství v jakékoli věci. Prosíme, konzultujte Vaši situaci a konkrétní právní otázky s Vaším pravním zástupcem.
-        title: Nastavení podmínek služby
-      going_live_on_html: Publikováno, platné od %{date}
-      history: Historie
-      live: Živě
-      no_history: Zatím nejsou zaznamenány žádné změny podmínek služby.
-      no_terms_of_service_html: Momentálně nemáte nakonfigurovány žádné podmínky služby. Podmínky služby jsou určeny k zajištění jasnosti a ochránit Vás před případnými právními závazky ve sporech s vašimi uživateli.
-      notified_on_html: Uživatelé upozorněni dne %{date}
-      notify_users: Upozornit uživatele
-      preview:
-        explanation_html: 'E-mail bude poslán <strong>%{display_count} uživatelům</strong>, kteří se zaregistrovali před datem %{date}. Následující text bude obsažen v onom e-mailu:'
-        send_preview: Poslat náhled na %{email}
-        send_to_all:
-          few: Poslat %{display_count} emaily
-          many: Poslat %{display_count} emailů
-          one: Poslat %{display_count} email
-          other: Poslat %{display_count} emailů
-        title: Náhled oznámení o podmínkách služby
-      publish: Zveřejnit
-      published_on_html: Zveřejněno %{date}
-      save_draft: Uložit koncept
-      title: Podmínky služby
     title: Administrace
     trends:
       allow: Povolit
@@ -1237,6 +1154,7 @@ cs:
     migrate_account: Přesunout se na jiný účet
     migrate_account_html: Zde můžete <a href="%{path}">nastavit přesměrování tohoto účtu na jiný</a>.
     or_log_in_with: Nebo se přihlaste pomocí
+    privacy_policy_agreement_html: Četl jsem a souhlasím se zásadami <a href="%{privacy_policy_path}" target="_blank">ochrany osobních údajů</a>
     progress:
       confirm: Potvrdit e-mail
       details: Vaše údaje
@@ -1261,7 +1179,7 @@ cs:
     set_new_password: Nastavit nové heslo
     setup:
       email_below_hint_html: Zkontrolujte složku se spamem, nebo požádejte o další. Svou e-mailovou adresu si můžete opravit, pokud je špatně.
-      email_settings_hint_html: Kliknutím na odkaz, který jsme poslali do %{email}, začnete používat Mastodon. Budeme tu čekat.
+      email_settings_hint_html: Klikněte na odkaz, který jsme Vám poslali k ověření %{email}. Budeme zde čekat.
       link_not_received: Nedostali jste odkaz?
       new_confirmation_instructions_sent: Za několik minut obdržíte nový e-mail s potvrzovacím odkazem!
       title: Zkontrolujte doručenou poštu
@@ -1270,7 +1188,7 @@ cs:
       title: Přihlásit se k %{domain}
     sign_up:
       manual_review: Registrace na %{domain} procházejí manuálním hodnocením od našich moderátorů. Abyste nám pomohli zpracovat Vaši registraci, napište trochu o sobě a proč chcete účet na %{domain}.
-      preamble: S účtem na tomto Mastodon serveru budete moci sledovat jakoukoli jinou osobu na fediversu, bez ohledu na to, kde je jejich účet hostován.
+      preamble: S účtem na tomto serveru Mastodon budete moci sledovat jakoukoliv jinou osobu v síti bez ohledu na to, kde je jejich účet hostován.
       title: Pojďme vás nastavit na %{domain}.
     status:
       account_status: Stav účtu
@@ -1282,8 +1200,6 @@ cs:
       view_strikes: Zobrazit minulé prohřešky vašeho účtu
     too_fast: Formulář byl odeslán příliš rychle, zkuste to znovu.
     use_security_key: Použít bezpečnostní klíč
-    user_agreement_html: Přečetl jsem si a souhlasím s <a href="%{terms_of_service_path}" target="_blank">podmínkami služby</a> a <a href="%{privacy_policy_path}" target="_blank">ochranou osobních údajů</a>
-    user_privacy_agreement_html: Četl jsem a souhlasím se zásadami <a href="%{privacy_policy_path}" target="_blank">ochrany osobních údajů</a>
   author_attribution:
     example_title: Ukázkový text
     hint_html: Píšete novinové články nebo blog mimo Mastodon? Kontrolujte, jak Vám bude připisováno autorství, když jsou sdíleny na Mastodonu.
@@ -1503,67 +1419,19 @@ cs:
       overwrite: Přepsat
       overwrite_long: Nahradit aktuální záznamy novými
     overwrite_preambles:
-      blocking_html:
-        few: Chystáte se <strong>nahradit váš seznam bloků</strong> s <strong>%{count} účty</strong> z <strong>%{filename}</strong>.
-        many: Chystáte se <strong>nahradit váš seznam bloků</strong> s <strong>%{count} účty</strong> z <strong>%{filename}</strong>.
-        one: Chystáte se <strong>nahradit váš seznam bloků</strong> s <strong>%{count} účtem</strong> z <strong>%{filename}</strong>.
-        other: Chystáte se <strong>nahradit váš seznam bloků</strong> s <strong>%{count} účty</strong> z <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: Chystáte se <strong>nahradit své záložky</strong> s <strong>%{count} příspěvky</strong> z <strong>%{filename}</strong>.
-        many: Chystáte se <strong>nahradit své záložky</strong> s <strong>%{count} příspěvky</strong> z <strong>%{filename}</strong>.
-        one: Chystáte se <strong>nahradit své záložky</strong> s <strong>%{count} příspěvkem</strong> z <strong>%{filename}</strong>.
-        other: Chystáte se <strong>nahradit své záložky</strong> s <strong>%{count} příspěvky</strong> z <strong>%{filename}</strong>.
-      domain_blocking_html:
-        few: Chystáte se <strong>nahradit Váš seznam zablokovaných domén</strong> s <strong>%{count} stránky</strong> z <strong>%{filename}</strong>.
-        many: Chystáte se <strong>nahradit Váš seznam zablokovaných domén</strong> s <strong>%{count} stránky</strong> z <strong>%{filename}</strong>.
-        one: Chystáte se <strong>nahradit Váš seznam zablokovaných domén</strong> s <strong>%{count} stránkou</strong> z <strong>%{filename}</strong>.
-        other: Chystáte se <strong>nahradit Váš seznam zablokovaných domén</strong> s <strong>%{count} stránky</strong> z <strong>%{filename}</strong>.
-      following_html:
-        few: Chystáte se <strong>sledovat</strong> až <strong>%{count} účty</strong> z <strong>%{filename}</strong> a <strong>přestanete sledovat kohokoliv jiného</strong>.
-        many: Chystáte se <strong>sledovat</strong> až <strong>%{count} účtů</strong> z <strong>%{filename}</strong> a <strong>přestanete sledovat kohokoliv jiného</strong>.
-        one: Chystáte se <strong>sledovat</strong> až <strong>%{count} účet</strong> z <strong>%{filename}</strong> a <strong>přestanete sledovat kohokoliv jiného</strong>.
-        other: Chystáte se <strong>sledovat</strong> až <strong>%{count} účtů</strong> z <strong>%{filename}</strong> a <strong>přestanete sledovat kohokoliv jiného</strong>.
-      lists_html:
-        few: Chystáte se <strong>nahradit své seznamy</strong> obsahem <strong>%{filename}</strong>. Až <strong>%{count} účty</strong> budou přidány do nových seznamů.
-        many: Chystáte se <strong>nahradit své seznamy</strong> obsahem <strong>%{filename}</strong>. Až <strong>%{count} účtů</strong> bude přidáno do nových seznamů.
-        one: Chystáte se <strong>nahradit své seznamy</strong> obsahem <strong>%{filename}</strong>. Až <strong>%{count} účet</strong> bude přidán do nových seznamů.
-        other: Chystáte se <strong>nahradit své seznamy</strong> obsahem <strong>%{filename}</strong>. Až <strong>%{count} účtů</strong> bude přidáno do nových seznamů.
-      muting_html:
-        few: Chystáte se <strong>nahradit svůj seznam ztišených účtů</strong> s <strong>%{count} účty</strong> z <strong>%{filename}</strong>.
-        many: Chystáte se <strong>nahradit svůj seznam ztišených účtů</strong> s <strong>%{count} účty</strong> z <strong>%{filename}</strong>.
-        one: Chystáte se <strong>nahradit svůj seznam ztišených účtů</strong> s <strong>%{count} účtem</strong> z <strong>%{filename}</strong>.
-        other: Chystáte se <strong>nahradit svůj seznam ztišených účtů</strong> s <strong>%{count} účty</strong> z <strong>%{filename}</strong>.
+      blocking_html: Chystáte se <strong>nahradit váš seznam bloků</strong> s <strong>%{total_items} účtami</strong> od <strong>%{filename}</strong>.
+      bookmarks_html: Chystáte se <strong>nahradit své záložky</strong> s <strong>%{total_items} příspěvků</strong> z <strong>%{filename}</strong>.
+      domain_blocking_html: Chystáte se <strong>nahradit seznam stránek</strong> s <strong>%{total_items} stránek</strong> z <strong>%{filename}</strong>.
+      following_html: Chystáte se <strong>sledovat</strong> až <strong>%{total_items} účtů</strong> z <strong>%{filename}</strong> a <strong>přestanete sledovat všechny ostatní</strong>.
+      lists_html: Chystáte se <strong>nahradit své seznamy</strong> obsahem <strong>%{filename}</strong>. Až <strong>%{total_items} účtů</strong> budou přidány do nových seznamů.
+      muting_html: Chystáte se <strong>nahradit seznam skrytých účtů</strong> s <strong>%{total_items} účtami</strong> z <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        few: Chystáte se <strong>zablokovat</strong> až <strong>%{count} účty</strong> z <strong>%{filename}</strong>.
-        many: Chystáte se <strong>zablokovat</strong> až <strong>%{count} účtů</strong> z <strong>%{filename}</strong>.
-        one: Chystáte se <strong>zablokovat</strong> až <strong>%{count} účet</strong> z <strong>%{filename}</strong>.
-        other: Chystáte se <strong>zablokovat</strong> až <strong>%{count} účtů</strong> z <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: Chystáte se přidat až <strong>%{count} příspěvky</strong> z <strong>%{filename}</strong> do vašich <strong>záložek</strong>.
-        many: Chystáte se přidat až <strong>%{count} příspěvků</strong> z <strong>%{filename}</strong> do vašich <strong>záložek</strong>.
-        one: Chystáte se přidat až <strong>%{count} příspěvek</strong> z <strong>%{filename}</strong> do vašich <strong>záložek</strong>.
-        other: Chystáte se přidat až <strong>%{count} příspěvků</strong> z <strong>%{filename}</strong> do vašich <strong>záložek</strong>.
-      domain_blocking_html:
-        few: Chystáte se <strong>zablokovat</strong> až <strong>%{count} domény</strong> z <strong>%{filename}</strong>.
-        many: Chystáte se <strong>zablokovat</strong> až <strong>%{count} domén</strong> z <strong>%{filename}</strong>.
-        one: Chystáte se <strong>zablokovat</strong> až <strong>%{count} doménu</strong> z <strong>%{filename}</strong>.
-        other: Chystáte se <strong>zablokovat</strong> až <strong>%{count} domén</strong> z <strong>%{filename}</strong>.
-      following_html:
-        few: Chystáte se <strong>sledovat</strong> až <strong>%{count} účty</strong> z <strong>%{filename}</strong>.
-        many: Chystáte se <strong>sledovat</strong> až <strong>%{count} účtů</strong> z <strong>%{filename}</strong>.
-        one: Chystáte se <strong>sledovat</strong> až <strong>%{count} účet</strong> z <strong>%{filename}</strong>.
-        other: Chystáte se <strong>sledovat</strong> až <strong>%{count} účtů</strong> z <strong>%{filename}</strong>.
-      lists_html:
-        few: Chystáte se přidat <strong>%{count} účty</strong> z <strong>%{filename}</strong> do vaších <strong>seznamů</strong>. Nové seznamy budou vytvořeny, pokud neexistuje žádný seznam, do kterého by je bylo možné přidat.
-        many: Chystáte se přidat <strong>%{count} účtů</strong> z <strong>%{filename}</strong> do vaších <strong>seznamů</strong>. Nové seznamy budou vytvořeny, pokud neexistuje žádný seznam, do kterého by je bylo možné přidat.
-        one: Chystáte se přidat <strong>%{count} účet</strong> z <strong>%{filename}</strong> do vaších <strong>seznamů</strong>. Nové seznamy budou vytvořeny, pokud neexistuje žádný seznam, do kterého by jej bylo možné přidat.
-        other: Chystáte se přidat <strong>%{count} účtů</strong> z <strong>%{filename}</strong> do vaších <strong>seznamů</strong>. Nové seznamy budou vytvořeny, pokud neexistuje žádný seznam, do kterého by je bylo možné přidat.
-      muting_html:
-        few: Chystáte se <strong>ztišit</strong> až <strong>%{count} účty</strong> z <strong>%{filename}</strong>.
-        many: Chystáte se <strong>ztišit</strong> až <strong>%{count} účtů</strong> z <strong>%{filename}</strong>.
-        one: Chystáte se <strong>ztišit</strong> až <strong>%{count} účet</strong> z <strong>%{filename}</strong>.
-        other: Chystáte se <strong>ztišit</strong> až <strong>%{count} účtů</strong> z <strong>%{filename}</strong>.
+      blocking_html: Chystáte se <strong>blokovat</strong> až <strong>%{total_items} účtů</strong> z <strong>%{filename}</strong>.
+      bookmarks_html: Chystáte se přidat až <strong>%{total_items} příspěvků</strong> z <strong>%{filename}</strong> do vašich <strong>záložek</strong>.
+      domain_blocking_html: Chystáte se <strong>blokovat</strong> až <strong>%{total_items} stránek</strong> z <strong>%{filename}</strong>.
+      following_html: Chystáte se <strong>sledovat</strong> až <strong>%{total_items} účtů</strong> z <strong>%{filename}</strong>.
+      lists_html: Chystáte se přidat <strong>%{total_items} účtů</strong> z <strong>%{filename}</strong> do vaších <strong>seznamů</strong>. Nové seznamy budou vytvořeny, pokud neexistuje žádný seznam, do kterého by bylo možné přidat.
+      muting_html: Chystáte se <strong>skrýt</strong> až <strong>%{total_items} účtů</strong> z <strong>%{filename}</strong>.
     preface: Můžete importovat data, která jste exportovali z jiného serveru, jako například seznam lidí, které sledujete či blokujete.
     recent_imports: Nedávné importy
     states:
@@ -1822,7 +1690,7 @@ cs:
   scheduled_statuses:
     over_daily_limit: Pro dnešek jste překročili limit %{limit} naplánovaných příspěvků
     over_total_limit: Překročili jste limit %{limit} naplánovaných příspěvků
-    too_soon: datum musí být v budoucnu
+    too_soon: Plánované datum musí být v budoucnosti
   self_destruct:
     lead_html: "<strong>%{domain}</strong> bohužel končí nadobro. Pokud jste tam měli účet, nebudete jej moci dále používat, ale stále si můžete vyžádat zálohu vašich dat."
     title: Tento server končí
@@ -1993,8 +1861,6 @@ cs:
       too_late: Na odvolání proti tomuto prohřešku už je pozdě
   tags:
     does_not_match_previous_name: se neshoduje s předchozím názvem
-  terms_of_service:
-    title: Podmínky služby
   themes:
     contrast: Mastodon (vysoký kontrast)
     default: Mastodon (tmavý)
@@ -2026,10 +1892,6 @@ cs:
     recovery_instructions_html: Ztratíte-li někdy přístup ke svému telefonu, můžete k získání přístupu k účtu použít jeden ze záložních kódů. <strong>Uchovejte tyto kódy v bezpečí</strong>. Můžete si je například vytisknout a uložit je mezi jiné důležité dokumenty.
     webauthn: Bezpečnostní klíče
   user_mailer:
-    announcement_published:
-      description: 'Správci %{domain} oznamují:'
-      subject: Oznámení služby
-      title: Oznámení služby %{domain}
     appeal_approved:
       action: Možnosti Účtu
       explanation: Odvolání proti prohřešku vašeho účtu ze dne %{strike_date}, které jste podali %{appeal_date}, bylo schváleno. Váš účet je opět v pořádku.
@@ -2059,15 +1921,6 @@ cs:
       further_actions_html: Pokud jste to nebyli vy, doporučujeme pro zabezpečení vašeho účtu okamžitě %{action} a zapnout dvoufázové ověřování.
       subject: Váš účet byl použit z nové IP adresy
       title: Nové přihlášení
-    terms_of_service_changed:
-      agreement: Pokračováním v používání %{domain} souhlasíte s těmito podmínkami. Pokud nesouhlasíte s aktualizovanými podmínkami, můžete svůj souhlas s %{domain} kdykoliv ukončit odstraněním vašeho účtu.
-      changelog: 'Ve zkratce, zde je to, co tato změna znamená pro vás:'
-      description: 'Tento e-mail jste obdrželi, protože na %{domain} provádíme určité změny našich smluvních podmínek. Tyto aktualizace budou platné od %{date}. Doporučujeme vám zkontrolovat aktualizované podmínky v celém znění zde:'
-      description_html: Tento e-mail jste obdrželi, protože na %{domain} provádíme určité změny našich smluvních podmínek. Tyto aktualizace budou platné od <strong>%{date}</strong>. Doporučujeme vám zkontrolovat <a href="%{path}" target="_blank">aktualizované podmínky v celém znění zde</a>.
-      sign_off: Tým %{domain}
-      subject: Aktualizace našich podmínek služby
-      subtitle: Podmínky služby %{domain} se mění
-      title: Důležitá aktualizace
     warning:
       appeal: Podat odvolání
       appeal_description: Pokud se domníváte, že se jedná o chybu, můžete podat odvolání personálu %{instance}.
diff --git a/config/locales/cy.yml b/config/locales/cy.yml
index edd89fedc6..49958fed36 100644
--- a/config/locales/cy.yml
+++ b/config/locales/cy.yml
@@ -199,7 +199,6 @@ cy:
         create_domain_block: Creu Gwaharddiad Parth
         create_email_domain_block: Creu Rhwystr Parth E-bost
         create_ip_block: Creu rheol IP
-        create_relay: Creu Cyfnewidiad
         create_unavailable_domain: Creu Parth Ddim ar Gael
         create_user_role: Creu Rôl
         demote_user: Diraddio Defnyddiwr
@@ -211,22 +210,18 @@ cy:
         destroy_email_domain_block: Dileu Rhwystr Parth E-bost
         destroy_instance: Clirio Parth
         destroy_ip_block: Dileu rheol IP
-        destroy_relay: Dileu Cyfnewidiad
         destroy_status: Dileu Postiad
         destroy_unavailable_domain: Dileu Parth Ddim ar Gael
         destroy_user_role: Dileu Rôl
         disable_2fa_user: Diffodd 2FA
         disable_custom_emoji: Analluogi Emoji Addasedig
-        disable_relay: Analluogi Cyfnewidiad
         disable_sign_in_token_auth_user: Analluogi Dilysu Tocyn E-bost Defnyddiwr
         disable_user: Analluogi Defnyddiwr
         enable_custom_emoji: Galluogi Emoji Addasedig
-        enable_relay: Galluogi Cyfnewidiad
         enable_sign_in_token_auth_user: Galluogi Dilysu Tocyn E-bost Defnyddiwr
         enable_user: Galluogi Defnyddiwr
         memorialize_account: Cofadeilio Cyfrif
         promote_user: Dyrchafu Defnyddiwr
-        publish_terms_of_service: Cyhoeddi Telerau Gwasanaeth
         reject_appeal: Gwrthod Apêl
         reject_user: Gwrthod Defnyddiwr
         remove_avatar_user: Tynnu Afatar
@@ -264,7 +259,6 @@ cy:
         create_domain_block_html: Mae %{name} wedi rhwystro parth %{target}
         create_email_domain_block_html: Mae %{name} wedi rhwystro parth e-bost %{target}
         create_ip_block_html: Mae %{name} wedi creu rheol ar gyfer IP %{target}
-        create_relay_html: Creodd %{name} gyfnewidiad %{target}
         create_unavailable_domain_html: Mae %{name} wedi stopio danfon i barth %{target}
         create_user_role_html: Mae %{name} wedi creu rôl %{target}
         demote_user_html: Mae %{name} wedi israddio defnyddiwr %{target}
@@ -276,22 +270,18 @@ cy:
         destroy_email_domain_block_html: Mae %{name} wedi dad-rwystro parth e-bost %{target}
         destroy_instance_html: Mae %{name} wedi dileu parth %{target}
         destroy_ip_block_html: Mae %{name} dileu rheol ar gyfer IP %{target}
-        destroy_relay_html: Dileodd %{name} y cyfnewidiad %{target}
         destroy_status_html: Mae %{name} wedi tynnu postiad gan %{target}
         destroy_unavailable_domain_html: Mae %{name} wedi ailddechrau anfon i barth %{target}
         destroy_user_role_html: Mae %{name} wedi dileu rôl %{target}
         disable_2fa_user_html: Mae %{name} wedi analluogi gofyniad dau ffactor ar gyfer defnyddiwr %{target}
         disable_custom_emoji_html: Mae %{name} wedi analluogi emoji %{target}
-        disable_relay_html: Analluogodd %{name} y cyfnewidiad %{target}
         disable_sign_in_token_auth_user_html: Mae %{name} wedi analluogi dilysiad tocyn e-bost %{target}
         disable_user_html: Mae %{name} wedi analluogi mewngofnodi defnyddiwr %{target}
         enable_custom_emoji_html: Mae %{name} wedi analluogi emoji %{target}
-        enable_relay_html: Galluogodd %{name} y cyfnewidiad %{target}
         enable_sign_in_token_auth_user_html: Mae %{name} wedi galluogi dilysiad tocyn e-bost %{target}
         enable_user_html: Mae %{name} wedi galluogi mewngofnodi defnyddiwr %{target}
         memorialize_account_html: Newidiodd %{name} gyfrif %{target} i dudalen memoriam
         promote_user_html: Mae %{name} wedi hyrwyddo defnyddiwr %{target}
-        publish_terms_of_service_html: Mae %{name} wedi cyhoeddi diweddariadau i'r telerau gwasanaeth
         reject_appeal_html: Mae %{name} wedi gwrthod apêl penderfyniad cymedroli %{target}
         reject_user_html: Mae %{name} wedi gwrthod cofrestriad gan %{target}
         remove_avatar_user_html: Mae %{name} wedi tynnu afatar %{target}
@@ -321,7 +311,6 @@ cy:
       title: Cofnod archwilio
       unavailable_instance: "(nid yw enw'r parth ar gael)"
     announcements:
-      back: Nôl i'r cyhoeddiadau
       destroyed_msg: Cyhoeddiad wedi'i ddileu'n llwyddiannus!
       edit:
         title: Golygu cyhoeddiad
@@ -330,9 +319,6 @@ cy:
       new:
         create: Creu cyhoeddiad
         title: Cyhoeddiad newydd
-      preview:
-        explanation_html: 'Bydd yr e-bost yn cael ei anfon at <strong>%{display_count} defnyddiwr</strong> . Bydd y testun canlynol yn cael ei gynnwys yn yr e-bost:'
-        title: Hysbysiad rhagolwg cyhoeddiad
       publish: Cyhoeddi
       published_msg: Cyhoeddiad wedi'i gyhoeddi'n llwyddiannus!
       scheduled_for: Wedi'i amserlenni ar gyfer %{time}
@@ -511,36 +497,6 @@ cy:
       new:
         title: Mewnforio blociau parth
       no_file: Heb ddewis ffeil
-    fasp:
-      debug:
-        callbacks:
-          created_at: Crëwyd am
-          delete: Dileu
-          ip: Cyfeiriad IP
-          request_body: Gofyn am y corff
-          title: Adalwasau Dadfygio
-      providers:
-        active: Gweithredol
-        base_url: URL sylfaen
-        callback: Adalwad
-        delete: Dileu
-        edit: Golygu Darparwr
-        finish_registration: Gorffen cofrestru
-        name: Enw
-        providers: Darparwyr
-        public_key_fingerprint: Ôl bys allwedd cyhoeddus
-        registration_requested: Cais am gofrestru
-        registrations:
-          confirm: Cadarnhau
-          description: Rydych wedi derbyn cofrestriad gan FASP. Gwrthodwch hyn os nad chi ofynnodd amdano. Os taw chi gychwynnodd hyn, cymharwch yr enw ac allwedd yr ôl bys yn ofalus cyn cadarnhau'r cofrestriad.
-          reject: Gwrthod
-          title: Cadarnhau Cofrestriad FASP
-        save: Cadw
-        select_capabilities: Dewis Galluoedd
-        sign_in: Mewngofnodi
-        status: Statws
-        title: Fediverse Auxiliary Service Providers
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Mae dilyn yr argymhellion yn helpu i ddefnyddwyr newydd ddod o hyd i gynnwys diddorol yn gyflym</strong>. Pan nad yw defnyddiwr wedi rhyngweithio digon ag eraill i ffurfio argymhellion dilyn personol, argymhellir y cyfrifon hyn yn lle hynny. Cânt eu hailgyfrifo'n ddyddiol o gymysgedd o gyfrifon gyda'r ymgysylltiadau diweddar uchaf a'r cyfrif dilynwyr lleol uchaf ar gyfer iaith benodol."
       language: Ar gyfer iaith
@@ -918,10 +874,8 @@ cy:
       back_to_account: Nôl i dudalen y cyfrif
       back_to_report: Nôl i dudalen yr adroddiad
       batch:
-        add_to_report: 'Ychwanegu at yr adroddiad #%{id}'
         remove_from_report: Dileu o'r adroddiad
         report: Adroddiad
-      contents: Cynnwys
       deleted: Dilëwyd
       favourites: Ffefrynnau
       history: Hanes fersiynau
@@ -930,17 +884,13 @@ cy:
       media:
         title: Cyfryngau
       metadata: Metadata
-      no_history: Nid yw'r postiad hwn wedi'i olygu
       no_status_selected: Heb newid postiad gan na ddewiswyd dim un
       open: Agor postiad
       original_status: Postiad gwreiddiol
       reblogs: Ailflogiadau
-      replied_to_html: Wedi ymateb i %{acct_link}
       status_changed: Postiad wedi'i newid
-      status_title: Postiad gan @%{name}
-      title: Postiadau cyfrif - @%{name}
+      title: Postiadau cyfrif
       trending: Yn trendio
-      view_publicly: Gweld yn gyhoeddus
       visibility: Gwelededd
       with_media: Gyda chyfryngau
     strikes:
@@ -1017,40 +967,6 @@ cy:
       search: Chwilio
       title: Hashnodau
       updated_msg: Gosodiadau hashnodau wedi'i diweddaru'n llwyddiannus
-    terms_of_service:
-      back: Nôl i delerau gwasanaeth
-      changelog: Beth sydd wedi newid
-      create: Defnyddiwch eich un eich hun
-      current: Cyfredol
-      draft: Drafft
-      generate: Defnyddiwch dempled
-      generates:
-        action: Cynhyrchu
-        chance_to_review_html: "<strong>Ni fydd y telerau gwasanaeth sy'n cael eu cynhyrchu'n cael eu cyhoeddi'n awtomatig.</strong> Bydd cyfle i chi adolygu'r canlyniadau. Cwblhewch y manylion angenrheidiol i symud ymlaen."
-        explanation_html: Mae'r templed telerau gwasanaeth sy'n cael eu darparu at ddibenion gwybodaeth yn unig, ac ni ddylid ei ddehongli fel cyngor cyfreithiol ar unrhyw bwnc. Ymgynghorwch â'ch cyngor cyfreithiol eich hun ar eich sefyllfa ag unrhyw gwestiynau cyfreithiol penodol sydd gennych.
-        title: Telerau Gosod Gwasanaeth
-      going_live_on_html: Byw, effeithiol %{date}
-      history: Hanes
-      live: Byw
-      no_history: Nid oes unrhyw newidiadau i delerau gwasanaeth wedi'u cofnodi eto.
-      no_terms_of_service_html: Nid oes gennych unrhyw delerau gwasanaeth wedi'u ffurfweddu ar hyn o bryd. Bwriad telerau gwasanaeth yw darparu eglurder a'ch amddiffyn rhag rhwymedigaethau posibl mewn anghydfodau gyda'ch defnyddwyr.
-      notified_on_html: Hysbyswyd defnyddwyr ar %{date}
-      notify_users: Hysbysu defnyddwyr
-      preview:
-        explanation_html: 'Bydd yr e-bost yn cael ei anfon at <strong>%{display_count} o ddefnyddwyr</strong> sydd wedi cofrestru cyn %{date}. Bydd y testun canlynol yn cael ei gynnwys yn yr e-bost:'
-        send_preview: Anfon rhagolwg at %{email}
-        send_to_all:
-          few: Anfon %{display_count} e-bost
-          many: Anfon %{display_count} e-bost
-          one: Anfon %{display_count} e-bost
-          other: Anfon %{display_count} e-bost
-          two: Anfon %{display_count} e-bost
-          zero: Anfon %{display_count} e-bost
-        title: Darllenwcy yr hysbysiad telerau gwasanaeth
-      publish: Cyhoeddi
-      published_on_html: Cyhoeddwyd ar %{date}
-      save_draft: Cadw drafft
-      title: Telerau Gwasanaeth
     title: Gweinyddiaeth
     trends:
       allow: Caniatáu
@@ -1274,6 +1190,7 @@ cy:
     migrate_account: Symud i gyfrif gwahanol
     migrate_account_html: Os hoffech chi ailgyfeirio'r cyfrif hwn at un gwahanol, mae modd <a href="%{path}">ei ffurfweddu yma</a>.
     or_log_in_with: Neu mewngofnodwch gyda
+    privacy_policy_agreement_html: Rwyf wedi darllen ac yn cytuno i'r <a href="%{privacy_policy_path}" target="_blank">polisi preifatrwydd</a>
     progress:
       confirm: Cadarnhau'r e-bost
       details: Eich manylion
@@ -1298,7 +1215,7 @@ cy:
     set_new_password: Gosod cyfrinair newydd
     setup:
       email_below_hint_html: Gwiriwch eich ffolder sbam, neu gofynnwch am un arall. Gallwch gywiro eich cyfeiriad e-bost os yw'n anghywir.
-      email_settings_hint_html: Cliciwch y ddolen anfonwyd at %{email} i ddechrau defnyddio Mastodon. Byddwn yn aros yn fan hyn.
+      email_settings_hint_html: Cliciwch ar y ddolen a anfonwyd atoch i wirio %{email}. Byddwn yn aros yma amdanoch.
       link_not_received: Heb gael dolen?
       new_confirmation_instructions_sent: Byddwch yn derbyn e-bost newydd gyda'r ddolen gadarnhau ymhen ychydig funudau!
       title: Gwiriwch eich blwch derbyn
@@ -1307,7 +1224,7 @@ cy:
       title: Mewngofnodi i %{domain}
     sign_up:
       manual_review: Mae cofrestriadau ar %{domain} yn cael eu hadolygu â llaw gan ein cymedrolwyr. Er mwyn ein helpu i brosesu eich cofrestriad, ysgrifennwch ychydig amdanoch chi'ch hun a pham rydych chi eisiau cyfrif ar %{domain}.
-      preamble: Gyda chyfrif ar y gweinydd Mastodon hwn, byddwch chi'n gallu dilyn unrhyw berson arall ar y ffedysawd, waeth ble mae eu cyfrif yn cael ei gynnal.
+      preamble: Gyda chyfrif ar y gweinydd Mastodon hwn, byddwch yn gallu dilyn unrhyw berson arall ar y rhwydwaith, lle bynnag mae eu cyfrif yn cael ei gynnal.
       title: Gadewch i ni eich gosod ar %{domain}.
     status:
       account_status: Statws cyfrif
@@ -1319,8 +1236,6 @@ cy:
       view_strikes: Gweld rybuddion y gorffennol yn erbyn eich cyfrif
     too_fast: Cafodd y ffurflen ei chyflwyno'n rhy gyflym, ceisiwch eto.
     use_security_key: Defnyddiwch allwedd diogelwch
-    user_agreement_html: Rwyf wedi darllen ac yn cytuno i <a href="%{terms_of_service_path}" target="_blank">delerau gwasanaeth</a> a'r <a href="%{privacy_policy_path}" target="_blank">polisi preifatrwydd</a>
-    user_privacy_agreement_html: Rwyf wedi darllen ac yn cytuno i'r <a href="%{privacy_policy_path}" target="_blank">polisi preifatrwydd</a>
   author_attribution:
     example_title: Testun enghreifftiol
     hint_html: Ydych chi'n ysgrifennu erthyglau newyddion neu flog y tu allan i Mastodon? Rheolwch sut y byddwch yn cael eich cydnabod pan fyddan nhw'n cael eu rhannu ar Mastodon.
@@ -1447,7 +1362,7 @@ cy:
   filters:
     contexts:
       account: Proffilau
-      home: Ffrwd gartref
+      home: Cartref a rhestrau
       notifications: Hysbysiadau
       public: Ffrydiau cyhoeddus
       thread: Sgyrsiau
@@ -1554,91 +1469,19 @@ cy:
       overwrite: Trosysgrifio
       overwrite_long: Amnewid y cofnodion cyfredol gyda'r rhai newydd
     overwrite_preambles:
-      blocking_html:
-        few: Rydych ar fin <strong>amnewid eich rhestr rhwystro</strong> gyda hyd at <strong>%{count} o gyfrifon</strong> o <strong>%{filename}</strong>.
-        many: Rydych ar fin <strong>amnewid eich rhestr rhwystro</strong> gyda hyd at <strong>%{count} o gyfrifon</strong> o <strong>%{filename}</strong>.
-        one: Rydych ar fin <strong>amnewid eich rhestr rhwystro</strong> gyda hyd at <strong>%{count} o gyfrifon</strong> o <strong>%{filename}</strong>.
-        other: Rydych ar fin <strong>amnewid eich rhestr rhwystro</strong> gyda hyd at <strong>%{count} o gyfrifon</strong> o <strong>%{filename}</strong>.
-        two: Rydych ar fin <strong>amnewid eich rhestr rhwystro</strong> gyda hyd at <strong>%{count} o gyfrifon</strong> o <strong>%{filename}</strong>.
-        zero: Rydych ar fin <strong>amnewid eich rhestr rhwystro</strong> gyda hyd at <strong>%{count} o gyfrifon</strong> o <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: Rydych ar fin <strong>amnewid eich nodau tudalen</strong> gyda hyd at <strong>%{count} postiadau</strong> gan <strong>%{filename}</strong>.
-        many: Rydych ar fin <strong>amnewid eich nodau tudalen</strong> gyda hyd at <strong>%{count} postiadau</strong> gan <strong>%{filename}</strong>.
-        one: Rydych ar fin <strong>disodli eich nodau tudalen</strong> gyda hyd at <strong>%{count} postiad</strong> o <strong>%{filename}</strong>.
-        other: Rydych ar fin <strong>amnewid eich nodau tudalen</strong> gyda hyd at <strong>%{count} postiadau</strong> gan <strong>%{filename}</strong>.
-        two: Rydych ar fin <strong>amnewid eich nodau tudalen</strong> gyda hyd at <strong>%{count} postiadau</strong> gan <strong>%{filename}</strong>.
-        zero: Rydych ar fin <strong>amnewid eich nodau tudalen</strong> gyda hyd at <strong>%{count} postiadau</strong> gan <strong>%{filename}</strong>.
-      domain_blocking_html:
-        few: Rydych ar fin <strong>amnewid eich rhestr rhwystro parthau </strong> gyda hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong>.
-        many: Rydych ar fin <strong>amnewid eich rhestr rhwystro parthau </strong> gyda hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong>.
-        one: Rydych ar fin <strong>disodli eich rhestr blociau parth</strong> gyda hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong>.
-        other: Rydych ar fin <strong>amnewid eich rhestr rhwystro parthau </strong> gyda hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong>.
-        two: Rydych ar fin <strong>amnewid eich rhestr rhwystro parthau </strong> gyda hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong.
-        zero: Rydych ar fin <strong>amnewid eich rhestr rhwystro parthau </strong> gyda hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong>.
-      following_html:
-        few: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} cyfrif</strong> gan <strong>%{filename}</strong> a <strong>pheidio â dilyn unrhyw un arall</strong>.
-        many: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} cyfrif</strong> gan <strong>%{filename}</strong> a <strong>pheidio â dilyn unrhyw un arall</strong>.
-        one: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} gyfrif</strong> o <strong>%{filename}</strong> a <strong>rhoi'r gorau i ddilyn unrhyw un arall</strong>.
-        other: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} cyfrif</strong> gan <strong>%{filename}</strong> a <strong>pheidio â dilyn unrhyw un arall</strong>.
-        two: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} cyfrif</strong> gan <strong>%{filename}</strong> a <strong>pheidio â dilyn unrhyw un arall</strong>.
-        zero: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} cyfrif</strong> gan <strong>%{filename}</strong> a <strong>pheidio â dilyn unrhyw un arall</strong>.
-      lists_html:
-        few: Rydych ar fin <strong>amnewid eich rhestrau</strong> gyda chynnwys <strong>%{filename}</strong>. Bydd hyd at <strong>%{count} cyfrif</strong> yn cael eu hychwanegu at restrau newydd.
-        many: Rydych ar fin <strong>amnewid eich rhestrau</strong> gyda chynnwys <strong>%{filename}</strong>. Bydd hyd at <strong>%{count} cyfrif</strong> yn cael eu hychwanegu at restrau newydd.
-        one: Rydych ar fin <strong>amnewid eich rhestrau</strong> gyda chynnwys <strong>%{filename}</strong>. Bydd hyd at <strong>%{count} cyfrif</strong> yn cael eu hychwanegu at restrau newydd.
-        other: Rydych ar fin <strong>amnewid eich rhestrau</strong> gyda chynnwys <strong>%{filename}</strong>. Bydd hyd at <strong>%{count} cyfrif</strong> yn cael eu hychwanegu at restrau newydd.
-        two: Rydych ar fin <strong>amnewid eich rhestrau</strong> gyda chynnwys <strong>%{filename}</strong>. Bydd hyd at <strong>%{count} cyfrif</strong> yn cael eu hychwanegu at restrau newydd.
-        zero: Rydych ar fin <strong>amnewid eich rhestrau</strong> gyda chynnwys <strong>%{filename}</strong>. Bydd hyd at <strong>%{count} cyfrif</strong> yn cael eu hychwanegu at restrau newydd.
-      muting_html:
-        few: Rydych ar fin <strong>amnewid eich rhestr o gyfrifon tawel</strong> gyda hyd at <strong>%{count} o gyfrifon</strong> o <strong>%{filename}</strong>.
-        many: Rydych ar fin <strong>amnewid eich rhestr o gyfrifon tawel</strong> gyda hyd at <strong>%{count} o gyfrifon</strong> o <strong>%{filename}</strong>.
-        one: Rydych ar fin <strong>disodli eich rhestr o gyfrifon wedi'u tewi</strong> am hyd at <strong>%{count} gyfrif</strong> o <strong>%{filename}</strong>.
-        other: Rydych ar fin <strong>amnewid eich rhestr o gyfrifon tawel</strong> gyda hyd at <strong>%{count} o gyfrifon</strong> o <strong>%{filename}</strong>.
-        two: Rydych ar fin <strong>amnewid eich rhestr o gyfrifon tawel</strong> gyda hyd at <strong>%{count} o gyfrifon</strong> o <strong>%{filename}</strong>.
-        zero: Rydych ar fin <strong>amnewid eich rhestr o gyfrifon tawel</strong> gyda hyd at <strong>%{count} o gyfrifon</strong> o <strong>%{filename}</strong>.
+      blocking_html: Rydych ar fin <strong>amnewid eich rhestr rhwystro</strong> gyda hyd at <strong>%{total_items} o gyfrifon</strong> o <strong>%{filename}</strong> .
+      bookmarks_html: Rydych ar fin <strong>amnewid eich nodau tudalen</strong> gyda hyd at <strong>%{total_items} o bostiadau</strong> gan <strong>%{filename}</strong> .
+      domain_blocking_html: Rydych ar fin <strong>amnewid eich rhestr rhwystro parth</strong> gyda hyd at <strong>%{total_items} parth</strong> o <strong>%{filename}</strong> .
+      following_html: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{total_items} o gyfrifon</strong> o <strong>%{filename}</strong> a <strong>pheidio a ddilyn unrhyw un arall</strong> .
+      lists_html: Rydych ar fin <strong>amnewid eich rhestrau</strong> gyda chynnwys <strong>%{filename}</strong> . Bydd hyd at <strong>%{total_items} o gyfrifon</strong> yn cael eu hychwanegu at restrau newydd.
+      muting_html: Rydych ar fin <strong>amnewid eich rhestr o gyfrifon tawel</strong> gyda hyd at <strong>%{total_items} o gyfrifon</strong> o <strong>%{filename}</strong> .
     preambles:
-      blocking_html:
-        few: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
-        many: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
-        one: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
-        other: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
-        two: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
-        zero: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: Rydych ar fin ychwanegu hyd at <strong>%{count} pbostiad</strong> o <strong>%{filename}</strong> at eich <strong>nodau tudalen</strong>.
-        many: Rydych ar fin ychwanegu hyd at <strong>%{count} pbostiad</strong> o <strong>%{filename}</strong> at eich <strong>nodau tudalen</strong>.
-        one: Rydych ar fin ychwanegu hyd at <strong>%{count} pbostiad</strong> o <strong>%{filename}</strong> at eich <strong>nodau tudalen</strong>.
-        other: Rydych ar fin ychwanegu hyd at <strong>%{count} pbostiad</strong> o <strong>%{filename}</strong> at eich <strong>nodau tudalen</strong>.
-        two: Rydych ar fin ychwanegu hyd at <strong>%{count} pbostiad</strong> o <strong>%{filename}</strong> at eich <strong>nodau tudalen</strong>.
-        zero: Rydych ar fin ychwanegu hyd at <strong>%{count} pbostiad</strong> o <strong>%{filename}</strong> at eich <strong>nodau tudalen</strong>.
-      domain_blocking_html:
-        few: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong>.
-        many: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong>.
-        one: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong>.
-        other: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong>.
-        two: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong>.
-        zero: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{count} parth</strong> o <strong>%{filename}</strong>.
-      following_html:
-        few: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} cyfrif</strong> gan <strong>%{filename}</strong>.
-        many: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} cyfrif</strong> gan <strong>%{filename}</strong>.
-        one: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} gyfrif</strong> o <strong>%{filename}</strong>.
-        other: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} cyfrif</strong> gan <strong>%{filename}</strong>.
-        two: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} cyfrif</strong> gan <strong>%{filename}</strong>.
-        zero: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{count} cyfrif</strong> gan <strong>%{filename}</strong>.
-      lists_html:
-        few: Rydych ar fin ychwanegu hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong> at eich <strong>rhestrau</strong>. Bydd rhestrau newydd yn cael eu creu os nad oes rhestr i ychwanegu ati.
-        many: Rydych ar fin ychwanegu hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong> at eich <strong>rhestrau</strong>. Bydd rhestrau newydd yn cael eu creu os nad oes rhestr i ychwanegu ati.
-        one: Rydych ar fin ychwanegu hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong> at eich <strong>rhestrau</strong>. Bydd rhestrau newydd yn cael eu creu os nad oes rhestr i ychwanegu ati.
-        other: Rydych ar fin ychwanegu hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong> at eich <strong>rhestrau</strong>. Bydd rhestrau newydd yn cael eu creu os nad oes rhestr i ychwanegu ati.
-        two: Rydych ar fin ychwanegu hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong> at eich <strong>rhestrau</strong>. Bydd rhestrau newydd yn cael eu creu os nad oes rhestr i ychwanegu ati.
-        zero: Rydych ar fin ychwanegu hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong> at eich <strong>rhestrau</strong>. Bydd rhestrau newydd yn cael eu creu os nad oes rhestr i ychwanegu ati.
-      muting_html:
-        few: Rydych ar fin <strong>tewi</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
-        many: Rydych ar fin <strong>tewi</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
-        one: Rydych ar fin <strong>tewi</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
-        other: Rydych ar fin <strong>tewi</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
-        two: Rydych ar fin <strong>tewi</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
-        zero: Rydych ar fin <strong>tewi</strong> hyd at <strong>%{count} cyfrif</strong> o <strong>%{filename}</strong>.
+      blocking_html: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{total_items} o gyfrifon</strong> o <strong>%{filename}</strong> .
+      bookmarks_html: Rydych ar fin ychwanegu hyd at <strong>%{total_items} o bostiadau</strong> o <strong>%{filename}</strong> at eich <strong>nodau tudalen</strong> .
+      domain_blocking_html: Rydych ar fin <strong>rhwystro</strong> hyd at <strong>%{total_items} parth</strong> o <strong>%{filename}</strong> .
+      following_html: Rydych ar fin <strong>dilyn</strong> hyd at <strong>%{total_items} cyfrif</strong> gan <strong>%{filename}</strong> .
+      lists_html: Rydych ar fin ychwanegu hyd at <strong>%{total_items} o gyfrifon</strong> o <strong>%{filename}</strong> at eich <strong>rhestrau</strong> . Bydd rhestrau newydd yn cael eu creu os nad oes rhestr i ychwanegu ati.
+      muting_html: Rydych ar fin <strong>anwybyddu</strong> hyd at <strong>%{total_items} cyfrif</strong> o <strong>%{filename}</strong>.
     preface: Gallwch fewnforio data rydych chi wedi'i allforio o weinydd arall, fel rhestr o'r bobl rydych chi'n eu dilyn neu'n eu blocio.
     recent_imports: Mewnforion diweddar
     states:
@@ -1899,7 +1742,7 @@ cy:
   scheduled_statuses:
     over_daily_limit: Rydych wedi mynd dros y terfyn o %{limit} postiad a drefnwyd ar gyfer heddiw
     over_total_limit: Rydych wedi mynd dros y terfyn o %{limit} postiad a drefnwyd
-    too_soon: rhaid i'r dyddiad fod yn y dyfodol
+    too_soon: Rhaid i'r dyddiad a drefnwyd fod yn y dyfodol
   self_destruct:
     lead_html: Yn anffodus mae <strong>%{domain}</strong> yn cau'n barhaol. Os oedd gennych gyfrif yno, ni fydd modd i chi barhau i'w ddefnyddio, ond mae dal modd gofyn i gael copi wrth gefn o'ch data.
     title: Mae'r gweinydd hwn yn cau
@@ -2078,8 +1921,6 @@ cy:
       too_late: Mae'n rhy hwyr i apelio yn erbyn y rhybudd hwn
   tags:
     does_not_match_previous_name: ddim yn cyfateb i'r enw blaenorol
-  terms_of_service:
-    title: Telerau Gwasanaeth
   themes:
     contrast: Mastodon (Cyferbyniad uchel)
     default: Mastodon (Tywyll)
@@ -2111,10 +1952,6 @@ cy:
     recovery_instructions_html: Os ydych chi'n colli mynediad i'ch ffôn, mae modd i chi ddefnyddio un o'r codau adfer isod i gael mynediad at eich cyfrif. <strong>Cadwch y codau adfer yn breifat</strong>. Er enghraifft, gallwch chi eu argraffu a'u cadw gyda dogfennau eraill pwysig.
     webauthn: Allweddi diogelwch
   user_mailer:
-    announcement_published:
-      description: 'Mae gweinyddwyr %{domain} yn gwneud cyhoeddiad:'
-      subject: Cyhoeddiad gwasanaeth
-      title: Cyhoeddiad gwasanaeth %{domain}
     appeal_approved:
       action: Gosodiadau Cyfrif
       explanation: Mae apêl y rhybudd yn erbyn eich cyfrif ar %{strike_date} a gyflwynwyd gennych ar %{appeal_date} wedi'i chymeradwyo. Mae eich cyfrif unwaith eto yn gadarnhaol.
@@ -2144,15 +1981,6 @@ cy:
       further_actions_html: Os nad chi oedd hwn, rydym yn argymell eich bod yn %{action} ar unwaith ac yn galluogi dilysu dau ffactor i gadw'ch cyfrif yn ddiogel.
       subject: Mae eich cyfrif wedi'i gyrchu o gyfeiriad IP newydd
       title: Mewngofnodiad newydd
-    terms_of_service_changed:
-      agreement: Drwy barhau i ddefnyddio %{domain}, rydych yn cytuno i'r telerau hyn. Os ydych yn anghytuno â'r telerau a ddiweddarwyd, gallwch derfynu eich cytundeb â %{domain} ar unrhyw adeg drwy ddileu eich cyfrif.
-      changelog: 'Yn fyr, dyma beth mae''r diweddariad hwn yn ei olygu i chi:'
-      description: 'Rydych yn derbyn yr e-bost hwn oherwydd ein bod yn gwneud rhai newidiadau i''n telerau gwasanaeth yn %{domain}. Bydd y diweddariadau hyn yn dod i rym ar %{date}. Rydym yn eich annog i adolygu''r telerau diweddaraf yn llawn yma:'
-      description_html: Rydych yn derbyn yr e-bost hwn oherwydd ein bod yn gwneud rhai newidiadau i'n telerau gwasanaeth yn %{domain}. Bydd y diweddariadau hyn yn dod i rym ar <strong>%{date}</strong> . Rydym yn eich annog i adolygu'r <a href="%{path}" target="_blank">telerau diweddaraf yn llawn yma</a> .
-      sign_off: Tîm %{domain}
-      subject: Diweddariadau i'n telerau gwasanaeth
-      subtitle: Mae telerau gwasanaeth %{domain} yn newid
-      title: Diweddariad pwysig
     warning:
       appeal: Cyflwyno apêl
       appeal_description: Os credwch fod hwn yn gamgymeriad, gallwch gyflwyno apêl i staff %{instance}.
diff --git a/config/locales/da.yml b/config/locales/da.yml
index 63a414811e..ec7e2da4fb 100644
--- a/config/locales/da.yml
+++ b/config/locales/da.yml
@@ -187,7 +187,6 @@ da:
         create_domain_block: Opret domæneblokering
         create_email_domain_block: Opret e-maildomæneblokering
         create_ip_block: Opret IP-regel
-        create_relay: Opret Videresendelse
         create_unavailable_domain: Opret Utilgængeligt Domæne
         create_user_role: Opret rolle
         demote_user: Degradér bruger
@@ -199,22 +198,18 @@ da:
         destroy_email_domain_block: Slet e-maildomæneblokering
         destroy_instance: Udrens domæne
         destroy_ip_block: Slet IP-regel
-        destroy_relay: Slet Videresendelse
         destroy_status: Slet indlæg
         destroy_unavailable_domain: Slet Utilgængeligt Domæne
         destroy_user_role: Ødelæg rolle
         disable_2fa_user: Deaktivér 2FA
         disable_custom_emoji: Deaktivér tilpasset emoji
-        disable_relay: Deaktivér Videresendelse
         disable_sign_in_token_auth_user: Deaktivér e-mailtoken godkendelse for bruger
         disable_user: Deaktivér bruger
         enable_custom_emoji: Aktivér tilpasset emoji
-        enable_relay: Aktivér Relay
         enable_sign_in_token_auth_user: Aktivér e-mailtoken godkendelse for bruger
         enable_user: Aktivér bruger
         memorialize_account: Memoralisér konto
         promote_user: Forfrem bruger
-        publish_terms_of_service: Offentliggør Tjenestevilkår
         reject_appeal: Afvis appel
         reject_user: Afvis bruger
         remove_avatar_user: Fjern profilbillede
@@ -252,7 +247,6 @@ da:
         create_domain_block_html: "%{name} blokerede domænet %{target}"
         create_email_domain_block_html: "%{name} blokerede e-maildomænet %{target}"
         create_ip_block_html: "%{name} oprettede en regel for IP %{target}"
-        create_relay_html: "%{name} oprettede videresendelsen %{target}"
         create_unavailable_domain_html: "%{name} stoppede levering til domænet %{target}"
         create_user_role_html: "%{name} oprettede %{target}-rolle"
         demote_user_html: "%{name} degraderede brugeren %{target}"
@@ -264,22 +258,18 @@ da:
         destroy_email_domain_block_html: "%{name} afblokerede e-maildomænet %{target}"
         destroy_instance_html: "%{name} udrensede domænet %{target}"
         destroy_ip_block_html: "%{name} slettede en regel for IP %{target}"
-        destroy_relay_html: "%{name} oprettede videresendelsen %{target}"
         destroy_status_html: "%{name} fjernede indlægget fra %{target}"
         destroy_unavailable_domain_html: "%{name} genoptog levering til domænet %{target}"
         destroy_user_role_html: "%{name} slettede %{target}-rolle"
         disable_2fa_user_html: "%{name} deaktiverede tofaktorkravet for brugeren %{target}"
         disable_custom_emoji_html: "%{name} deaktiverede emojien %{target}"
-        disable_relay_html: "%{name} deaktiverede videresendelsen %{target}"
         disable_sign_in_token_auth_user_html: "%{name} deaktiverede e-mailtoken godkendelsen for %{target}"
         disable_user_html: "%{name} deaktiverede indlogning for brugeren %{target}"
         enable_custom_emoji_html: "%{name} aktiverede emojien %{target}"
-        enable_relay_html: "%{name} aktiverede videresendelsen %{target}"
         enable_sign_in_token_auth_user_html: "%{name} aktiverede e-mailtoken godkendelse for %{target}"
         enable_user_html: "%{name} aktiverede indlogning for brugeren %{target}"
         memorialize_account_html: "%{name} gjorde %{target}s konto til en mindeside"
         promote_user_html: "%{name} forfremmede brugeren %{target}"
-        publish_terms_of_service_html: "%{name} offentliggjorde opdateringer til tjenestevilkårene"
         reject_appeal_html: "%{name} afviste moderationsafgørelsesappellen fra %{target}"
         reject_user_html: "%{name} afviste tilmelding fra %{target}"
         remove_avatar_user_html: "%{name} fjernede %{target}s profilbillede"
@@ -309,7 +299,6 @@ da:
       title: Revisionslog
       unavailable_instance: "(domænenavn utilgængeligt)"
     announcements:
-      back: Retur til annonceringer
       destroyed_msg: Bekendtgørelsen er slettet!
       edit:
         title: Redigér bekendtgørelse
@@ -318,10 +307,6 @@ da:
       new:
         create: Opret bekendtgørelse
         title: Ny bekendtgørelse
-      preview:
-        disclaimer: Da brugere ikke kan fravælge e-mailnotifikationer, bør disse begrænses til vigtige emner som f.eks. personlige databrud eller serverlukninger.
-        explanation_html: 'E-mailen sendes til <strong>%{display_count} brugere</strong>. Flg. tekst medtages i e-mailen:'
-        title: Forhåndsvis annonceringsnotifikation
       publish: Publicér
       published_msg: Bekendtgørelsen er publiceret!
       scheduled_for: Planlagt til %{time}
@@ -480,36 +465,6 @@ da:
       new:
         title: Import af domæneblokeringer
       no_file: Ingen fill udvalgt
-    fasp:
-      debug:
-        callbacks:
-          created_at: Oprettet d.
-          delete: Slet
-          ip: IP-adresse
-          request_body: Anmodningsbrødtekst
-          title: Fejlfindingstilbagekald
-      providers:
-        active: Aktiv
-        base_url: Basis-URL
-        callback: Tilbagekald
-        delete: Slet
-        edit: Redigér udbyder
-        finish_registration: Færdiggør registrering
-        name: Navn
-        providers: Udbydere
-        public_key_fingerprint: Offentlig nøgle-fingeraftryk
-        registration_requested: Registrering kræves
-        registrations:
-          confirm: Bekræft
-          description: Er en registrering er modtaget fra en FASP, så afvis den, hvis man ikke startede pocessen. Startede man processen, tjek omhyggeligt navn og nøglefingeraftryk, før registreringen bekræftes.
-          reject: Afvis
-          title: Bekræft FASP-registrering
-        save: Gem
-        select_capabilities: Vælg kapaciteter
-        sign_in: Log ind
-        status: Status
-        title: Fediverse Auxiliary Service Providers
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Følg-anbefalinger hjælpe nye brugere til hurtigt at finde interessant indhold</strong>. Når en bruger ikke har interageret nok med andre til at generere personlige følg-anbefalinger, anbefales disse konti i stedet. De revurderes dagligt baseret på en blanding af konti med de flest nylige engagementer og fleste lokale følger-antal for et givet sprog."
       language: For sprog
@@ -863,10 +818,8 @@ da:
       back_to_account: Retur til kontoside
       back_to_report: Retur til anmeldelsesside
       batch:
-        add_to_report: 'Føj til rapporten #%{id}'
         remove_from_report: Fjern fra anmeldelse
         report: Anmeldelse
-      contents: Indhold
       deleted: Slettet
       favourites: Favoritter
       history: Versionshistorik
@@ -875,17 +828,13 @@ da:
       media:
         title: Medier
       metadata: Metadata
-      no_history: Dette indlæg er ikke blevet redigeret
       no_status_selected: Ingen indlæg ændret (ingen valgt)
       open: Åbn indlæg
       original_status: Oprindeligt indlæg
       reblogs: Genblogninger
-      replied_to_html: Besvarede %{acct_link}
       status_changed: Indlæg ændret
-      status_title: Indlæg fra @%{name}
-      title: Kontoindlæg - @%{name}
+      title: Kontoindlæg
       trending: Populære
-      view_publicly: Vis offentligt
       visibility: Synlighed
       with_media: Med medier
     strikes:
@@ -962,36 +911,6 @@ da:
       search: Søg
       title: Etiketter
       updated_msg: Hashtag-indstillinger opdateret
-    terms_of_service:
-      back: Tilbage til Tjenestevilkår
-      changelog: Hvad der er ændret
-      create: Brug egne
-      current: Nuværende
-      draft: Udkast
-      generate: Brug skabelon
-      generates:
-        action: Generér
-        chance_to_review_html: "<strong>De genererede Tjenestevilkår offentliggøres ikke automatisk.</strong> Man vil have mulighed for at gennemgå resultaterne. Udfyld venligst de nødvendige oplysninger for at fortsætte."
-        explanation_html: Tjenestevilkår-skabelonen er alene til orientering og bør ikke fortolkes som juridisk rådgivning om noget emne. Kontakt en juridiske rådgiver vedr. den aktuelle situation samt specifikke juridiske spørgsmål, man måtte have.
-        title: Opsætning af Tjenestevilkår
-      going_live_on_html: Ikrafttræden pr. %{date}
-      history: Historik
-      live: Live
-      no_history: Der er endnu ingen registrerede ændringer af vilkårene for tjenesten.
-      no_terms_of_service_html: Der er p.t. ingen opsatte Tjenestevilkår. Tjenestevilkår er beregnet til at give klarhed, så man er beskyttet mod potentielle ansvarspådragelser i tvister med sine brugere.
-      notified_on_html: Brugere underrettet pr. %{date}
-      notify_users: Underrret brugere
-      preview:
-        explanation_html: 'E-mailen sendes til <strong>%{display_count} brugere</strong>, som har tilmeldt sig før %{date}. Følgende tekst medtages i e-mailen:'
-        send_preview: Send forhåndsvisning til %{email}
-        send_to_all:
-          one: Send %{display_count} e-mail
-          other: Send %{display_count} e-mails
-        title: Forhåndsvis Tjenestevilkår-underretning
-      publish: Udgiv
-      published_on_html: Udgivet pr. %{date}
-      save_draft: Gem udkast
-      title: Tjenestevilkår
     title: Administration
     trends:
       allow: Tillad
@@ -1199,6 +1118,7 @@ da:
     migrate_account: Flyt til en anden konto
     migrate_account_html: Ønsker du at omdirigere denne konto til en anden, kan du <a href="%{path}">opsætte dette hér</a>.
     or_log_in_with: Eller log ind med
+    privacy_policy_agreement_html: Jeg accepterer <a href="%{privacy_policy_path}" target="_blank">privatlivspolitikken</a>
     progress:
       confirm: Bekræft e-mail
       details: Dine detaljer
@@ -1223,7 +1143,7 @@ da:
     set_new_password: Opsæt ny adgangskode
     setup:
       email_below_hint_html: Tjek Spam-mappen eller anmod om et nyt link. Om nødvendigt kan e-mailadressen rettes.
-      email_settings_hint_html: Klik på det link, der er sendt til %{email} for at begynde at bruge Mastodon.
+      email_settings_hint_html: Tryk på det tilsendte link for at bekræfte %{email}.
       link_not_received: Intet link modtaget?
       new_confirmation_instructions_sent: Du bør om få minutter modtage en ny e-mail med bekræftelseslinket!
       title: Tjek indbakken
@@ -1232,7 +1152,7 @@ da:
       title: Log ind på %{domain}
     sign_up:
       manual_review: Tilmeldinger på %{domain} undergår manuel moderatorgennemgang. For at hjælpe med behandlingen af tilmeldingen, så skriv en smule om dig selv, samt hvorfor du ønsker en konto på %{domain}.
-      preamble: Man vil med en konto på denne Mastodon-server kunne følge enhver anden bruger i fediverset, uanset hvor vedkommendes konto hostes.
+      preamble: Med en konto på denne Mastodon-server vil man kunne følge enhver anden person på netværket, uanset hvor vedkommendes konto hostes.
       title: Lad os få dig sat op på %{domain}.
     status:
       account_status: Kontostatus
@@ -1244,8 +1164,6 @@ da:
       view_strikes: Se tidligere anmeldelser af din konto
     too_fast: Formularen indsendt for hurtigt, forsøg igen.
     use_security_key: Brug sikkerhedsnøgle
-    user_agreement_html: Jeg accepterer <a href="%{terms_of_service_path}" target="_blank">Tjenestevilkår</a> og <a href="%{privacy_policy_path}" target="_blank">Fortrolighedspolitik</a>
-    user_privacy_agreement_html: Jeg accepterer <a href="%{privacy_policy_path}" target="_blank">fortrolighedspolitikken</a>
   author_attribution:
     example_title: Eksempeltekst
     hint_html: Skriver du nyheder eller blogartikler uden for Mastodon? Styr, hvordan man bliver krediteret, når disse deles på Mastodon.
@@ -1451,43 +1369,19 @@ da:
       overwrite: Overskriv
       overwrite_long: Erstat aktuelle poster med de nye
     overwrite_preambles:
-      blocking_html:
-        one: Man er ved at <strong>erstatte sin sortliste</strong> med <strong>%{count} konto</strong> fra <strong>%{filename}</strong>.
-        other: Man er ved at <strong>erstatte sin sortliste</strong> med optil <strong>%{count} konti</strong> fra <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Man er ved at <strong>erstatte sine bogmærker</strong> med <strong>%{count} post</strong> fra <strong>%{filename}</strong>.
-        other: Man er ved at <strong>erstatte sine bogmærker</strong> med op til <strong>%{count} poster</strong> fra <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Man er ved at <strong>erstatte sin domænesortliste</strong> med <strong>%{count} domæne</strong> fra <strong>%{filename}</strong>.
-        other: Man er ved at <strong>erstatte sin domænesortliste</strong> med op til <strong>%{count} domæner</strong> fra <strong>%{filename}</strong>.
-      following_html:
-        one: Man er ved at <strong>følge</strong> <strong>%{count} konto</strong> fra <strong>%{filename}</strong> og <strong>stoppe med at følge andre</strong>.
-        other: Man er ved at <strong>følge</strong> <strong>%{count} konti</strong> fra <strong>%{filename}</strong> og <strong>stoppe med at følge andre</strong>.
-      lists_html:
-        one: Man er ved at <strong>erstatte sine lister</strong> med indhold fra <strong>%{filename}</strong>. <strong>%{count} konto</strong> føjes til nye lister.
-        other: Man er ved at <strong>erstatte sine lister</strong> med indhold fra <strong>%{filename}</strong>. Op til <strong>%{count} konti</strong> føjes til nye lister.
-      muting_html:
-        one: Man er ved at <strong>sin liste over en tavsgjort konto</strong> med <strong>%{count} konto</strong> fra <strong>%{filename}</strong>.
-        other: Du er ved at <strong>erstatte din liste over skjulte kontoer</strong> med op til <strong>%{count} kontoer</strong> fra <strong>%{filename}</strong>.
+      blocking_html: Du er ved at <strong>erstatte bloklisten</strong> med op til <strong>%{total_items} konti</strong> fra <strong>%{filename}</strong>.
+      bookmarks_html: Du er ved at <strong>erstatte bogmærkelisten</strong> med op til <strong>%{total_items} emner</strong> fra <strong>%{filename}</strong>.
+      domain_blocking_html: Du er ved at <strong>erstatte domæneblokeringslisten</strong> med op til <strong>%{total_items} domæner</strong> fra <strong>%{filename}</strong>.
+      following_html: Du er ved at <strong>følge</strong> op til <strong>%{total_items} konti</strong> fra <strong>%{filename}</strong> og <strong>stoppe med at følge alle andre</strong>.
+      lists_html: Du er ved at <strong>erstatte dine lister</strong> med indholdet af <strong>%{filename}</strong>. Op til <strong>%{total_items} konti</strong> tilføjes nye lister.
+      muting_html: Du er ved at <strong>erstatte din liste over skjulte konti</strong> med op til <strong>%{total_items} konti</strong> fra <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Man er ved at <strong>blokere</strong> <strong>%{count} konto</strong> fra <strong>%{filename}</strong>.
-        other: Man er ved at <strong>blokere</strong> op til <strong>%{count} konti</strong> fra <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Man er ved at tilføje <strong>%{count} post</strong> fra <strong>%{filename}</strong> til sine <strong>bogmærker</strong>.
-        other: Man er ved at tilføje <strong>%{count} poster</strong> fra <strong>%{filename}</strong> til sine <strong>bogmærker</strong>.
-      domain_blocking_html:
-        one: Man er ved at <strong>blokere</strong> <strong>%{count} domæne</strong> fra <strong>%{filename}</strong>.
-        other: Man er ved at <strong>blokere</strong> op til <strong>%{count} domæner</strong> fra <strong>%{filename}</strong>.
-      following_html:
-        one: Man er ved at <strong>følge</strong> <strong>%{count} konto</strong> fra <strong>%{filename}</strong>.
-        other: Man er ved at <strong>følge</strong> op til<strong>%{count} konti</strong> fra <strong>%{filename}</strong>.
-      lists_html:
-        one: Man er ved at tilføje <strong>%{count} konto</strong> fra <strong>%{filename}</strong> til sine <strong>lister</strong>. Nye lister oprettes, hvis der ikke findes nogen liste at tilføje til.
-        other: Man er ved at tilføje <strong>%{count} konti</strong> fra <strong>%{filename}</strong> til sine <strong>lister</strong>. Nye lister oprettes, hvis der ikke findes nogen liste at tilføje til.
-      muting_html:
-        one: Man er ved at <strong>tavsgøre</strong> <strong>%{count} konto</strong> fra <strong>%{filename}</strong>.
-        other: Du er ved at <strong>skjule</strong> op til <strong>%{count} kontoer</strong> fra <strong>%{filename}</strong>.
+      blocking_html: Du er ved at <strong>blokere</strong> op til <strong>%{total_items} konti</strong> fra <strong>%{filename}</strong>.
+      bookmarks_html: Du er ved at føje op til <strong>%{total_items} indlæg</strong> fra <strong>%{filename}</strong> til <strong>bogmærkelisten</strong>.
+      domain_blocking_html: Du er ved at <strong>blokere</strong> op til <strong>%{total_items} domæner</strong> fra <strong>%{filename}</strong>.
+      following_html: Du er ved at <strong>følge</strong> op til <strong>%{total_items} konti</strong> fra <strong>%{filename}</strong>.
+      lists_html: Du er ved at tilføje op til <strong>%{total_items} konti</strong> fra <strong>%{filename}</strong> til dine <strong>lister</strong>. Nye lister oprettes, hvis der ikke er nogen liste at føje konti til.
+      muting_html: Du er ved at <strong>skjule</strong> op til <strong>%{total_items} konti</strong> fra <strong>%{filename}</strong>.
     preface: Du kan importere data, du har eksporteret fra en anden server, såsom en liste over folk du følger eller blokerer.
     recent_imports: Seneste importer
     states:
@@ -1744,7 +1638,7 @@ da:
   scheduled_statuses:
     over_daily_limit: Den daglige grænse på %{limit} planlagte indlæg er nået
     over_total_limit: Grænsen på %{limit} planlagte indlæg er nået
-    too_soon: dato skal være i fremtiden
+    too_soon: Den planlagte dato skal være i fremtiden
   self_destruct:
     lead_html: Desværre lukker <strong>%{domain}</strong> permanent. Har man en konto dér, vil fortsat brug heraf ikke være mulig. Man kan dog stadig anmode om en sikkerhedskopi af sine data.
     title: Denne server er under nedlukning
@@ -1907,8 +1801,6 @@ da:
       too_late: Det er for sent at appellere denne advarsel
   tags:
     does_not_match_previous_name: matcher ikke det foregående navn
-  terms_of_service:
-    title: Tjenestevilkår
   themes:
     contrast: Mastodon (høj kontrast)
     default: Mastodont (mørkt)
@@ -1940,10 +1832,6 @@ da:
     recovery_instructions_html: Mister du nogensinde adgang til din mobil, kan en af gendannelseskoderne nedenfor bruges til at opnå adgang til din konto. <strong>Opbevar disse et sikkert sted</strong>. De kan f.eks. udskrives og gemmes sammen med andre vigtige dokumenter.
     webauthn: Sikkerhedsnøgler
   user_mailer:
-    announcement_published:
-      description: 'Administratorerne på %{domain} udsender en annoncering:'
-      subject: Tjenesteannoncering
-      title: "%{domain}-tjenesteannoncering"
     appeal_approved:
       action: Kontoindstillinger
       explanation: Appellen af kontoadvarslen fra %{strike_date}, indsendt af dig pr. %{appeal_date}, er blevet godkendt. Din kontostatus er igen god.
@@ -1973,15 +1861,6 @@ da:
       further_actions_html: Hvis dette ikke var dig, anbefaler vi, at du %{action} med det samme og aktiverer to-faktor godkendelse for at holde din konto sikker.
       subject: Din konto er blevet tilgået fra en ny IP-adresse
       title: Ny indlogning
-    terms_of_service_changed:
-      agreement: Ved at fortsætte med at bruge %{domain}, accepteres disse vilkår. Kan du ikke acceptere de opdaterede vilkår, kan din aftale med %{domain} til enhver tid opsiges ved at slette din konto.
-      changelog: 'Med ét blik er her, hvad denne opdatering rent praktisk betyder:'
-      description: 'Man modtager denne e-mail, idet vi foretager nogle ændringer i vores Tjenestevilkår på %{domain}. Disse opdateringer træder i kraft pr. %{date}. Man opfordres til at gennemgå de opdaterede vilkår til fulde her:'
-      description_html: Man modtager denne e-mail, idet vi foretager nogle ændringer i vores Tjenestevilkår på %{domain}. Disse opdateringer træder i kraft pr. <strong>%{date}</strong>. Man opfordres til at gennemgå de <a href="%{path}" target="_blank">opdaterede vilkår til fulde her</a>.
-      sign_off: "%{domain}-teamet"
-      subject: Opdatering af Tjenestevilkår
-      subtitle: Tjenestevilkår for %{domain} ændres
-      title: Vigtig opdatering
     warning:
       appeal: Indgiv appel
       appeal_description: Mener du, at dette er en fejl, kan der indgives en appel til %{instance}-personalet.
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 0842ab73d7..0fd1ce35ea 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -187,7 +187,6 @@ de:
         create_domain_block: Domain sperren
         create_email_domain_block: E-Mail-Domain-Sperre erstellen
         create_ip_block: IP-Regel erstellen
-        create_relay: Relay erstellen
         create_unavailable_domain: Nicht verfügbare Domain erstellen
         create_user_role: Rolle erstellen
         demote_user: Benutzer*in herabstufen
@@ -199,22 +198,18 @@ de:
         destroy_email_domain_block: E-Mail-Domain-Sperre entfernen
         destroy_instance: Domain-Daten entfernen
         destroy_ip_block: IP-Regel löschen
-        destroy_relay: Relay löschen
         destroy_status: Beitrag entfernen
         destroy_unavailable_domain: Nicht-verfügbare Domain entfernen
         destroy_user_role: Rolle entfernen
         disable_2fa_user: 2FA deaktivieren
         disable_custom_emoji: Eigenes Emoji deaktivieren
-        disable_relay: Relay deaktivieren
         disable_sign_in_token_auth_user: E-Mail-Token-Authentisierung für dieses Konto deaktivieren
         disable_user: Benutzer*in deaktivieren
         enable_custom_emoji: Eigenes Emoji aktivieren
-        enable_relay: Relay aktivieren
         enable_sign_in_token_auth_user: E-Mail-Token-Authentisierung für dieses Konto aktivieren
         enable_user: Benutzer*in aktivieren
         memorialize_account: Gedenkkonto
         promote_user: Benutzer*in hochstufen
-        publish_terms_of_service: Nutzungsbedingungen veröffentlichen
         reject_appeal: Einspruch ablehnen
         reject_user: Benutzer*in ablehnen
         remove_avatar_user: Profilbild entfernen
@@ -252,7 +247,6 @@ de:
         create_domain_block_html: "%{name} sperrte die Domain %{target}"
         create_email_domain_block_html: "%{name} sperrte die E-Mail-Domain %{target}"
         create_ip_block_html: "%{name} erstellte eine IP-Regel für %{target}"
-        create_relay_html: "%{name} erstellte ein Relay %{target}"
         create_unavailable_domain_html: "%{name} beendete die Zustellung an die Domain %{target}"
         create_user_role_html: "%{name} erstellte die Rolle %{target}"
         demote_user_html: "%{name} stufte %{target} herunter"
@@ -264,22 +258,18 @@ de:
         destroy_email_domain_block_html: "%{name} entsperrte die E-Mail-Domain %{target}"
         destroy_instance_html: "%{name} entfernte die Daten der Domain %{target} von diesem Server"
         destroy_ip_block_html: "%{name} entfernte eine IP-Regel für %{target}"
-        destroy_relay_html: "%{name} löschte das Relay %{target}"
         destroy_status_html: "%{name} entfernte einen Beitrag von %{target}"
         destroy_unavailable_domain_html: "%{name} nahm die Zustellung an die Domain %{target} wieder auf"
         destroy_user_role_html: "%{name} löschte die Rolle %{target}"
         disable_2fa_user_html: "%{name} deaktivierte die Zwei-Faktor-Authentisierung für %{target}"
         disable_custom_emoji_html: "%{name} deaktivierte das Emoji %{target}"
-        disable_relay_html: "%{name} deaktivierte das Relay %{target}"
         disable_sign_in_token_auth_user_html: "%{name} deaktivierte die E-Mail-Token-Authentisierung für %{target}"
         disable_user_html: "%{name} deaktivierte den Zugang für %{target}"
         enable_custom_emoji_html: "%{name} aktivierte das Emoji %{target}"
-        enable_relay_html: "%{name} aktivierte das Relay %{target}"
         enable_sign_in_token_auth_user_html: "%{name} aktivierte die E-Mail-Token-Authentisierung für %{target}"
         enable_user_html: "%{name} aktivierte den Zugang für %{target}"
         memorialize_account_html: "%{name} wandelte das Konto von %{target} in eine Gedenkseite um"
         promote_user_html: "%{name} beförderte %{target}"
-        publish_terms_of_service_html: "%{name} aktualisierte die Nutzungsbedingungen"
         reject_appeal_html: "%{name} hat den Einspruch gegen eine Moderationsentscheidung von %{target} abgelehnt"
         reject_user_html: "%{name} hat die Registrierung von %{target} abgelehnt"
         remove_avatar_user_html: "%{name} entfernte das Profilbild von %{target}"
@@ -309,7 +299,6 @@ de:
       title: Audit-Log
       unavailable_instance: "(Server nicht verfügbar)"
     announcements:
-      back: Zurück zu den Ankündigungen
       destroyed_msg: Ankündigung erfolgreich gelöscht!
       edit:
         title: Ankündigung bearbeiten
@@ -318,10 +307,6 @@ de:
       new:
         create: Ankündigung erstellen
         title: Neue Ankündigung
-      preview:
-        disclaimer: Da sich Profile nicht davon abmelden können, sollten Benachrichtigungen per E-Mail auf wichtige Ankündigungen wie z. B. zu Datenpannen oder Serverabschaltung beschränkt sein.
-        explanation_html: 'Die E-Mail wird an <strong>%{display_count} Nutzer*innen</strong> gesendet. Folgendes wird in der E-Mail enthalten sein:'
-        title: Vorschau der Ankündigung
       publish: Veröffentlichen
       published_msg: Ankündigung erfolgreich veröffentlicht!
       scheduled_for: Geplant für %{time}
@@ -480,33 +465,6 @@ de:
       new:
         title: Domains importieren
       no_file: Keine Datei ausgewählt
-    fasp:
-      debug:
-        callbacks:
-          created_at: Erstellt am
-          delete: Entfernen
-          ip: IP-Adresse
-      providers:
-        active: Aktiv
-        base_url: Basis-URL
-        delete: Entfernen
-        edit: Anbieter bearbeiten
-        finish_registration: Registrierung abschließen
-        name: Name
-        providers: Anbieter
-        public_key_fingerprint: Fingerabdruck des öffentlichen Schlüssels
-        registration_requested: Registrierung angefordert
-        registrations:
-          confirm: Bestätigen
-          description: Sie haben eine Registrierung von einer FASP erhalten. Lehnen Sie ab, wenn Sie dies nicht initiiert haben. Wenn Sie dies initiiert haben, vergleichen Sie Namen und Fingerabdruck vor der Bestätigung der Registrierung.
-          reject: Ablehnen
-          title: FASP-Registrierung bestätigen
-        save: Speichern
-        select_capabilities: Leistungsfähigkeit auswählen
-        sign_in: Anmelden
-        status: Status
-        title: Fediverse Auxiliary Service Providers
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Follower-Empfehlungen helfen neuen Nutzer*innen, interessante Inhalte schnell zu finden</strong>. Wenn ein*e Nutzer*in noch nicht genug mit anderen interagiert hat, um personalisierte Follower-Empfehlungen zu erhalten, werden stattdessen diese Profile vorgeschlagen. Sie werden täglich, basierend auf einer Mischung aus am meisten interagierenden Konten und jenen mit den meisten Followern für eine bestimmte Sprache neu berechnet."
       language: Für Sprache
@@ -751,7 +709,7 @@ de:
         manage_appeals: Einsprüche verwalten
         manage_appeals_description: Erlaubt es Benutzer*innen, Entscheidungen der Moderator*innen zu widersprechen
         manage_blocks: Sperrungen verwalten
-        manage_blocks_description: Erlaubt Nutzer*innen das Sperren von E-Mail-Anbietern und IP-Adressen
+        manage_blocks_description: Erlaubt Nutzer*innen das Sperren von E-Mail-Providern und IP-Adressen
         manage_custom_emojis: Eigene Emojis verwalten
         manage_custom_emojis_description: Erlaubt es Benutzer*innen, eigene Emojis auf dem Server zu verwalten
         manage_federation: Föderation verwalten
@@ -860,10 +818,8 @@ de:
       back_to_account: Zurück zum Konto
       back_to_report: Zurück zur Seite mit den Meldungen
       batch:
-        add_to_report: Der Meldung Nr. %{id} hinzufügen
         remove_from_report: Von der Meldung entfernen
         report: Meldung
-      contents: Inhalte
       deleted: Gelöscht
       favourites: Favoriten
       history: Versionsverlauf
@@ -872,17 +828,13 @@ de:
       media:
         title: Medien
       metadata: Metadaten
-      no_history: Der Beitrag wurde nicht bearbeitet
       no_status_selected: Keine Beiträge wurden geändert, weil keine ausgewählt wurden
       open: Beitrag öffnen
       original_status: Ursprünglicher Beitrag
       reblogs: Geteilte Beiträge
-      replied_to_html: Antwortete %{acct_link}
       status_changed: Beitrag bearbeitet
-      status_title: Beitrag von @%{name}
-      title: Beiträge des Kontos – @%{name}
+      title: Beiträge des Kontos
       trending: Trends
-      view_publicly: Öffentlich anzeigen
       visibility: Sichtbarkeit
       with_media: Mit Medien
     strikes:
@@ -959,36 +911,6 @@ de:
       search: Suchen
       title: Hashtags
       updated_msg: Hashtag-Einstellungen erfolgreich aktualisiert
-    terms_of_service:
-      back: Zurück zu den Nutzungsbedingungen
-      changelog: Was sich geändert hat
-      create: Eigene erstellen
-      current: Aktuell
-      draft: Entwurf
-      generate: Vorlage verwenden
-      generates:
-        action: Erstellen
-        chance_to_review_html: "<strong>Die durch diese Vorlage erstellten Nutzungsbedingungen werden nicht automatisch veröffentlicht.</strong> Du wirst alles noch einmal überprüfen können. Bitte fülle alle Felder mit den notwendigen Informationen aus, um fortzufahren."
-        explanation_html: Die Vorlage für die Nutzungsbedingungen dient ausschließlich zu Informationszwecken und sollte nicht als Rechtsberatung zu einem bestimmten Thema verstanden werden. Bei rechtlichen Fragen, konsultiere bitte deinen Rechtsbeistand.
-        title: Nutzungsbedingungen festlegen
-      going_live_on_html: Aktuell gültig (ab %{date})
-      history: Verlauf
-      live: Aktuell gültig
-      no_history: Es wurden noch keine Änderungen an den Nutzungsbedingungen aufgezeichnet.
-      no_terms_of_service_html: Du hast derzeit keine Nutzungsbedingungen bereitgestellt. Nutzungsbedingungen verschaffen Klarheit und schützen vor möglicher Haftung bei Streitigkeiten mit deinen Nutzer*innen.
-      notified_on_html: Nutzer*innen wurden am %{date} benachrichtigt
-      notify_users: Nutzer*innen benachrichtigen
-      preview:
-        explanation_html: 'Die E-Mail wird an <strong>%{display_count} Nutzer*innen</strong> gesendet, die sich vor dem %{date} registriert haben. Folgendes wird in der E-Mail enthalten sein:'
-        send_preview: Vorschau an %{email} senden
-        send_to_all:
-          one: "%{display_count} E-Mail senden"
-          other: "%{display_count} E-Mails senden"
-        title: Vorschau zu den neuen Nutzungsbedingungen
-      publish: Veröffentlichen
-      published_on_html: Veröffentlicht am %{date}
-      save_draft: Entwurf speichern
-      title: Nutzungsbedingungen
     title: Administration
     trends:
       allow: Erlauben
@@ -1000,9 +922,9 @@ de:
         allow: Link erlauben
         allow_provider: Herausgeber*in erlauben
         confirm_allow: Möchtest du die ausgewählten Links wirklich erlauben?
-        confirm_allow_provider: Möchtest du die ausgewählten Anbieter wirklich erlauben?
+        confirm_allow_provider: Möchtest du die ausgewählten Provider wirklich erlauben?
         confirm_disallow: Möchtest du die ausgewählten Links wirklich verbieten?
-        confirm_disallow_provider: Möchtest du die ausgewählten Anbieter wirklich verbieten?
+        confirm_disallow_provider: Möchtest du die ausgewählten Provider wirklich verbieten?
         description_html: Dies sind Links, die derzeit von zahlreichen Konten geteilt werden und die deinem Server aufgefallen sind. Die Benutzer*innen können darüber herausfinden, was in der Welt vor sich geht. Die Links werden allerdings erst dann öffentlich vorgeschlagen, wenn du die Herausgeber*innen genehmigt hast. Du kannst alternativ aber auch nur einzelne URLs zulassen oder ablehnen.
         disallow: Link verbieten
         disallow_provider: Herausgeber*in verbieten
@@ -1196,6 +1118,7 @@ de:
     migrate_account: Zu einem anderen Konto umziehen
     migrate_account_html: Wenn du dieses Konto auf ein anderes weiterleiten möchtest, kannst du es <a href="%{path}">hier konfigurieren</a>.
     or_log_in_with: Oder anmelden mit
+    privacy_policy_agreement_html: Ich habe die <a href="%{privacy_policy_path}" target="_blank">Datenschutzerklärung</a> gelesen und stimme ihr zu
     progress:
       confirm: E-Mail bestätigen
       details: Deine Daten
@@ -1220,7 +1143,7 @@ de:
     set_new_password: Neues Passwort einrichten
     setup:
       email_below_hint_html: Überprüfe deinen Spam-Ordner oder lass dir den Bestätigungslink erneut zusenden. Falls die angegebene E-Mail-Adresse falsch ist, kannst du sie auch korrigieren.
-      email_settings_hint_html: Klicke auf den Link, den wir an %{email} gesendet haben, um mit Mastodon loszulegen. Wir warten hier solange auf dich.
+      email_settings_hint_html: Klicke auf den Bestätigungslink, den wir an %{email} gesendet haben, um die Adresse zu verifizieren. Wir warten hier solange auf dich.
       link_not_received: Keinen Bestätigungslink erhalten?
       new_confirmation_instructions_sent: In wenigen Minuten wirst du eine neue E-Mail mit dem Bestätigungslink erhalten!
       title: Überprüfe dein E-Mail-Postfach
@@ -1229,7 +1152,7 @@ de:
       title: Bei %{domain} anmelden
     sign_up:
       manual_review: Registrierungen für den Server %{domain} werden manuell durch unsere Moderator*innen überprüft. Um uns dabei zu unterstützen, schreibe etwas über dich und sage uns, weshalb du ein Konto auf %{domain} anlegen möchtest.
-      preamble: Mit einem Konto auf diesem Mastodon-Server kannst du jeder anderen Person im Fediverse folgen, unabhängig davon, wo ihr Konto registriert ist.
+      preamble: Mit einem Konto auf diesem Mastodon-Server kannst du jeder anderen Person im Netzwerk folgen, unabhängig davon, wo ihr Konto registriert ist.
       title: Lass uns dein Konto auf %{domain} einrichten.
     status:
       account_status: Kontostatus
@@ -1241,8 +1164,6 @@ de:
       view_strikes: Vorherige Verstöße deines Kontos ansehen
     too_fast: Formular zu schnell übermittelt. Bitte versuche es erneut.
     use_security_key: Sicherheitsschlüssel verwenden
-    user_agreement_html: Ich stimme den <a href="%{terms_of_service_path}" target="_blank">Nutzungsbedingungen</a> sowie der <a href="%{privacy_policy_path}" target="_blank">Datenschutzerklärung</a> zu
-    user_privacy_agreement_html: Ich stimme der <a href="%{privacy_policy_path}" target="_blank">Datenschutzerklärung</a> zu
   author_attribution:
     example_title: Beispieltext
     hint_html: Schreibst du außerhalb von Mastodon journalistische Artikel oder andere Texte, beispielsweise in einem Blog? Lege hier fest, wann auf dein Profil verwiesen werden soll, wenn Links zu deinen Werken auf Mastodon geteilt werden.
@@ -1448,43 +1369,19 @@ de:
       overwrite: Überschreiben
       overwrite_long: Bestehende Datensätze durch neue ersetzen
     overwrite_preambles:
-      blocking_html:
-        one: Du bist dabei, <strong>deine Liste blockierter Konten</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Konto</strong> zu ersetzen.
-        other: Du bist dabei, <strong>deine Liste blockierter Konten</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Konten</strong> zu ersetzen.
-      bookmarks_html:
-        one: Du bist dabei, <strong>deine Lesezeichen</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Beitrag</strong> zu ersetzen.
-        other: Du bist dabei, <strong>deine Lesezeichen</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Beiträge</strong> zu ersetzen.
-      domain_blocking_html:
-        one: Du bist dabei, <strong>deine Liste blockierter Domains</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Domain</strong> zu ersetzen.
-        other: Du bist dabei, <strong>deine Liste blockierter Domains</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Domains</strong> zu ersetzen.
-      following_html:
-        one: Du bist dabei, bis zu <strong>%{count} Konto</strong> aus <strong>%{filename}</strong> zu <strong>folgen</strong> und <strong>allen anderen zu entfolgen</strong>.
-        other: Du bist dabei, bis zu <strong>%{count} Konten</strong> aus <strong>%{filename}</strong> zu <strong>folgen</strong> und <strong>allen anderen zu entfolgen</strong>.
-      lists_html:
-        one: Du bist dabei, <strong>deine Listen</strong> mit dem Inhalt aus <strong>%{filename}</strong> zu <strong>ersetzen</strong>. Bis zu <strong>%{count} Konto</strong> wird zu neuen Listen hinzugefügt.
-        other: Du bist dabei, <strong>deine Listen</strong> mit dem Inhalt aus <strong>%{filename}</strong> zu <strong>ersetzen</strong>. Bis zu <strong>%{count} Konten</strong> wird zu neuen Listen hinzugefügt.
-      muting_html:
-        one: Du bist dabei, <strong>deine Liste mit einem stummgeschalteten Konto</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Konto</strong> zu ersetzen.
-        other: Du bist dabei, <strong>deine Liste stummgeschalteter Konten</strong> aus <strong>%{filename}</strong> durch bis zu <strong>%{count} Konten</strong> zu ersetzen.
+      blocking_html: Du bist dabei, <strong>deine Liste blockierter Konten</strong> durch bis zu <strong>%{total_items} Konten</strong> aus <strong>%{filename}</strong> zu <strong>ersetzen</strong>.
+      bookmarks_html: Du bist dabei, <strong>deine Lesezeichen</strong> durch bis zu <strong>%{total_items} Beiträge</strong> aus <strong>%{filename}</strong> zu <strong>ersetzen</strong>.
+      domain_blocking_html: Du bist dabei, <strong>deine Liste blockierter Domains</strong> durch bis zu <strong>%{total_items} Domains</strong> aus <strong>%{filename}</strong> zu <strong>ersetzen</strong>.
+      following_html: Du bist dabei, bis zu <strong>%{total_items} Konten</strong> aus <strong>%{filename}</strong> zu folgen und <strong>niemand anderes zu folgen</strong>.
+      lists_html: Du bist dabei, <strong>deine Listen</strong> durch den Inhalt aus <strong>%{filename}</strong> zu <strong>ersetzen</strong>. Es werden bis zu <strong>%{total_items} Konten</strong> zu neuen Listen hinzugefügt.
+      muting_html: Du bist dabei, <strong>deine Liste stummgeschalteter Konten</strong> durch bis zu <strong>%{total_items} Konten</strong> aus <strong>%{filename}</strong> zu ersetzen.
     preambles:
-      blocking_html:
-        one: Du bist dabei, bis zu <strong>%{count} Konto</strong> aus <strong>%{filename}</strong> zu <strong>blockieren</strong>.
-        other: Du bist dabei, bis zu <strong>%{count} Konten</strong> aus <strong>%{filename}</strong> zu <strong>blockieren</strong>.
-      bookmarks_html:
-        one: Du bist dabei, bis zu <strong>%{count} Beitrag</strong> aus <strong>%{filename}</strong> zu deinen <strong>Lesezeichen hinzuzufügen</strong>.
-        other: Du bist dabei, bis zu <strong>%{count} Beiträge</strong> aus <strong>%{filename}</strong> zu deinen <strong>Lesezeichen hinzuzufügen</strong>.
-      domain_blocking_html:
-        one: Du bist dabei, bis zu <strong>%{count} Domain</strong> aus <strong>%{filename}</strong> zu <strong>blockieren</strong>.
-        other: Du bist dabei, bis zu <strong>%{count} Domains</strong> aus <strong>%{filename}</strong> zu <strong>blockieren</strong>.
-      following_html:
-        one: Du bist dabei, bis zu <strong>%{count} Konto</strong> aus <strong>%{filename}</strong> zu <strong>folgen</strong>.
-        other: Du bist dabei, bis zu <strong>%{count} Konten</strong> aus <strong>%{filename}</strong> zu <strong>folgen</strong>.
-      lists_html:
-        one: Du bist dabei, bis zu <strong>%{count} Konto</strong> aus <strong>%{filename}</strong> zu deinen <strong>Listen hinzuzufügen</strong>. Wenn es keine Listen gibt, zu denen etwas hinzugefügt werden kann, dann werden neue Listen erstellt.
-        other: Du bist dabei, bis zu <strong>%{count} Konten</strong> aus <strong>%{filename}</strong> zu deinen <strong>Listen hinzuzufügen</strong>. Wenn es keine Listen gibt, zu denen etwas hinzugefügt werden kann, dann werden neue Listen erstellt.
-      muting_html:
-        one: Du bist dabei, bis zu <strong>%{count} Konto</strong> aus <strong>%{filename} stummzuschalten</strong>.
-        other: Du bist dabei, bis zu <strong>%{count} Konten</strong> aus <strong>%{filename} stummzuschalten</strong>.
+      blocking_html: Du bist dabei, bis zu <strong>%{total_items}%{total_items} Konten</strong> aus <strong>%{filename}</strong> zu <strong>blockieren</strong>.
+      bookmarks_html: Du bist dabei, bis zu <strong>%{total_items} Beiträge</strong> aus <strong>%{filename}</strong> zu deinen <strong>Lesezeichen hinzuzufügen</strong>.
+      domain_blocking_html: Du bist dabei, bis zu <strong>%{total_items} Domains</strong> aus <strong>%{filename}</strong> zu <strong>blockieren</strong>.
+      following_html: Du bist dabei, bis zu <strong>%{total_items} Konten</strong> aus <strong>%{filename}</strong> zu <strong>folgen</strong>.
+      lists_html: Du bist dabei, bis zu <strong>%{total_items} Konten</strong> aus <strong>%{filename}</strong> zu deinen <strong>Listen hinzuzufügen</strong>. Wenn es keine Liste gibt, zu der etwas hinzugefügt werden kann, dann werden neue Listen erstellt.
+      muting_html: Du bist dabei, bis zu <strong>%{total_items} Konten</strong> aus <strong>%{filename} stummzuschalten</strong>.
     preface: Daten, die du von einem Mastodon-Server exportiert hast, kannst du hierher importieren. Das betrifft beispielsweise die Listen von Profilen, denen du folgst oder die du blockiert hast.
     recent_imports: Zuletzt importiert
     states:
@@ -1741,7 +1638,7 @@ de:
   scheduled_statuses:
     over_daily_limit: Du hast das Limit von %{limit} geplanten Beiträgen für heute erreicht
     over_total_limit: Du hast das Limit für geplante Beiträge, das %{limit} beträgt, erreicht
-    too_soon: Datum muss in der Zukunft liegen
+    too_soon: Das geplante Datum muss in der Zukunft liegen
   self_destruct:
     lead_html: Bedauerlicherweise wird <strong>%{domain}</strong> den Betrieb für immer einstellen. Wenn du dort ein Konto angelegt hast, wirst du es nicht weiter verwenden können. Du kannst allerdings eine Sicherung deiner Daten anfordern.
     title: Dieser Server wird den Betrieb einstellen
@@ -1904,8 +1801,6 @@ de:
       too_late: Es ist zu spät, um gegen diese Maßnahme Einspruch zu erheben
   tags:
     does_not_match_previous_name: entspricht nicht dem vorherigen Namen
-  terms_of_service:
-    title: Nutzungsbedingungen
   themes:
     contrast: Mastodon (Hoher Kontrast)
     default: Mastodon (Dunkel)
@@ -1937,10 +1832,6 @@ de:
     recovery_instructions_html: Falls du jemals den Zugang zu deinem Smartphone verlierst, kannst du einen der unten aufgeführten Wiederherstellungscodes verwenden, um wieder Zugang zu deinem Konto zu erhalten. <strong>Bewahre die Wiederherstellungscodes sicher auf</strong>. Du kannst sie zum Beispiel ausdrucken und zusammen mit anderen wichtigen Dokumenten aufbewahren.
     webauthn: Sicherheitsschlüssel
   user_mailer:
-    announcement_published:
-      description: 'Ankündigung der Administrator*innen von %{domain}:'
-      subject: Ankündigung
-      title: Ankündigung von %{domain}
     appeal_approved:
       action: Kontoeinstellungen
       explanation: Der Einspruch gegen deinen Verstoß vom %{strike_date}, den du am %{appeal_date} eingereicht hast, wurde genehmigt. Dein Konto ist wieder in Ordnung.
@@ -1970,15 +1861,6 @@ de:
       further_actions_html: Wenn du das nicht warst, empfehlen wir dir schnellstmöglich, %{action} und die Zwei-Faktor-Authentisierung für dein Konto zu aktivieren, um es abzusichern.
       subject: Es wurde auf dein Konto von einer neuen IP-Adresse zugegriffen
       title: Eine neue Anmeldung
-    terms_of_service_changed:
-      agreement: Wenn du %{domain} weiterhin verwendest, stimmst du den neuen Nutzungsbedingungen automatisch zu. Falls du mit diesen nicht einverstanden bist, kannst du die Vereinbarung mit %{domain} jederzeit widerrufen, indem du dein Konto dort löschst.
-      changelog: 'Hier siehst du, was sich geändert hat:'
-      description: 'Du erhältst diese E-Mail, weil wir einige Änderungen an unseren Nutzungsbedingungen für %{domain} vorgenommen haben. Diese Änderungen gelten ab %{date}. Wir empfehlen, die vollständig aktualisierte Fassung hier zu lesen:'
-      description_html: Du erhältst diese E-Mail, weil wir einige Änderungen an unseren Nutzungsbedingungen für %{domain} vorgenommen haben. Diese Änderungen gelten ab <strong>%{date}</strong>. Wir empfehlen, die <a href="%{path}" target="_blank">vollständig aktualisierte Fassung hier zu lesen</a>.
-      sign_off: Das Team von %{domain}
-      subject: Aktualisierungen unserer Nutzungsbedingungen
-      subtitle: Die Nutzungsbedingungen von %{domain} ändern sich
-      title: Wichtige Mitteilung
     warning:
       appeal: Einspruch erheben
       appeal_description: Wenn du glaubst, dass es sich um einen Fehler handelt, kannst du einen Einspruch an die Administration von %{instance} senden.
diff --git a/config/locales/devise.an.yml b/config/locales/devise.an.yml
index 2d971d706a..4be18845cc 100644
--- a/config/locales/devise.an.yml
+++ b/config/locales/devise.an.yml
@@ -84,6 +84,11 @@ an:
       updated_not_active: La tuya clau s'ha cambiau con exito.
     registrations:
       destroyed: Dica unatra! La tuya cuenta ha estau cancelada con exito. Asperamos veyer-te de nuevo luego.
+      signed_up: Bienveniu! T'has rechistrau correctament.
+      signed_up_but_inactive: T'has rechistrau con exito. Manimenos, no s'ha puesto iniciar sesión perque la tuya cuenta encara no ye activada.
+      signed_up_but_locked: T'has rechistrau con exito. Manimenos, no s'ha puesto iniciar sesión perque la tuya cuenta ye blocada.
+      signed_up_but_pending: Un mensache con un vinclo de confirmación ha estau ninviau a la tuya adreza de correu electronico. Dimpués de fer clic en o vinclo, revisaremos la tuya solicitut. Serás notificau si s'apreba.
+      signed_up_but_unconfirmed: Un mensache con un vinclo de confirmación ha estau ninviau a la tuya adreza de correu electronico. Per favor, sigue lo vinclo pa activar la tuya cuenta. Per favor, compreba la tuya carpeta de correu no deseyau si no recibes dito correu electronico.
       update_needs_confirmation: Has actualizau la tuya cuenta con exito, pero amenestemos verificar la tuya nueva adreza de correu electronico. Per favor, compreba lo tuyo correu electronico y sigue lo vinclo de confirmación pa confirmar la tuya nueva adreza de correu electronico. Per favor, compreba la tuya carpeta de correu no deseyau si no recibes dito correu electronico.
       updated: La tuya cuenta s'ha actualizau con exito.
     sessions:
diff --git a/config/locales/devise.ar.yml b/config/locales/devise.ar.yml
index 7b3f6462aa..e8b6ff7d93 100644
--- a/config/locales/devise.ar.yml
+++ b/config/locales/devise.ar.yml
@@ -94,6 +94,11 @@ ar:
       updated_not_active: تم تغيير كلمة المرور بنجاح.
     registrations:
       destroyed: إلى اللقاء! لقد تم إلغاء حسابك. نتمنى أن نراك مجددا.
+      signed_up: أهلا وسهلا! تم تسجيل دخولك بنجاح.
+      signed_up_but_inactive: لقد تمت عملية إنشاء حسابك بنجاح إلاّ أنه لا يمكننا تسجيل دخولك إلاّ بعد قيامك بتفعيله.
+      signed_up_but_locked: لقد تم تسجيل حسابك بنجاح إلّا أنه لا يمكنك تسجيل الدخول لأن حسابك مجمد.
+      signed_up_but_pending: لقد تم إرسال رسالة تحتوي على رابط للتفعيل إلى عنوان بريدك الإلكتروني. بالضغط على الرابط سوف نقوم بمراجعة طلبك. سنقوم بإشعارك إن حظيت بالموافقة.
+      signed_up_but_unconfirmed: لقد تم إرسال رسالة تحتوي على رابط للتفعيل إلى عنوان بريدك الإلكتروني. بالضغط على الرابط سوف يتم تفعيل حسابك. لذا يُرجى إلقاء نظرة على ملف الرسائل غير المرغوب فيها إنْ لم تَعثُر على الرسالة السالفة الذِكر.
       update_needs_confirmation: لقد قمت بتحديث حسابك بنجاح إلا أنه يجب علينا التأكد من صحة عنوان بريدك الإلكتروني الجديد. يرجى الإطلاع على بريدك و اتباع الرابط الذي تلقيتَه لتأكيد عنوان بريدك الإلكتروني الجديد. إن لم تتلقى تلك الرسالة ، ندعوك إلى تفقُّد مجلد البريد المزعج.
       updated: تم تحديث حسابك بنجاح.
     sessions:
diff --git a/config/locales/devise.ast.yml b/config/locales/devise.ast.yml
index 8c2f2f8e73..c353607043 100644
--- a/config/locales/devise.ast.yml
+++ b/config/locales/devise.ast.yml
@@ -72,6 +72,11 @@ ast:
       updated_not_active: La contraseña camudó correutamente.
     registrations:
       destroyed: "¡Ta llueu! La cuenta anulóse correutamente. Esperamos volver vete pronto."
+      signed_up: "¡Afáyate! Rexistréstite correutamente."
+      signed_up_but_inactive: Rexistréstite correutamente. Por embargu, nun se pudo aniciar la sesión porque la cuenta entá nun s'activó.
+      signed_up_but_locked: Rexistréstite correutamente. Por embargu, nun se pudo aniciar la sesión porque la cuenta ta bloquiada.
+      signed_up_but_pending: Unvióse un mensaxe a la direición de corréu electrónicu que contién un enllaz de confirmación. Darréu de calcar nel enllaz, vamos revisar la solicitú y si s'aprueba, avisámoste.
+      signed_up_but_unconfirmed: Unvióse un mensaxe de confirmación a la to direición de corréu electrónicu. Sigui l'enllaz p'activar la cuenta y revisa la carpeta Puxarra si nun recibiesti esti mensaxe.
       update_needs_confirmation: Anovesti la cuenta correutamente, mas tenemos de verificar la direición de corréu electrónicu nueva. Revisa'l to corréu electrónicu y sigui l'enllaz de confirmación pa confirmar esta direición. Si nun recibiesti'l mensaxe, revisa la carpeta Puxarra.
       updated: La cuenta anovóse correutamente.
     sessions:
diff --git a/config/locales/devise.be.yml b/config/locales/devise.be.yml
index 5f03bf06b8..81f3120a88 100644
--- a/config/locales/devise.be.yml
+++ b/config/locales/devise.be.yml
@@ -94,6 +94,11 @@ be:
       updated_not_active: Ваш пароль быў паспяхова зменены.
     registrations:
       destroyed: Пакуль! Ваш уліковы запіс быў паспяхова выдалены. Мы спадзяваемся хутка ўбачыць вас зноў.
+      signed_up: Вітаем! Вы былі паспяхова зарэгістраваны.
+      signed_up_but_inactive: Вы паспяхова зарэгістраваліся. Аднак, мы не змаглі вас аўтарызаваць, таму што ваш уліковы запіс пакуль што не актывізаваны.
+      signed_up_but_locked: Вы паспяхова зарэгістраваліся. Аднак, мы не змаглі вас аўтарызаваць, таму што ваш уліковы запіс заблакаваны.
+      signed_up_but_pending: Ліст са спасылкай для пацверджання быў высланы на вашу электронную пошту. Пасля таго, як вы націсніце на спасылку, мы разгледзім вашу заяўку. Калі яна будзе ўхвалена, вы будзеце праінфармаваны.
+      signed_up_but_unconfirmed: Ліст са спасылкай для пацверджання быў высланы на вашу электронную пошту. Калі ласка, перайдзіце па спасылцы для актывацыі вашага ўліковага запісу. Праверце вашу тэчку са спамам, калі вы не атрымалі такі ліст.
       update_needs_confirmation: Вы паспяхова абнавілі свой уліковы запіс, аднак, нам неабходна пацвердзіць ваш новы адрас электроннай пошты. Калі ласка, праверце вашу пошту і перайдзіце па спасылцы для пацверджання вашага новага адраса электроннай пошты. Праверце тэчку са спамам, калі вы не атрымалі такі ліст.
       updated: Ваш уліковы запіс быў паспяхова абноўлены.
     sessions:
diff --git a/config/locales/devise.bg.yml b/config/locales/devise.bg.yml
index 5cb41fa616..116939762c 100644
--- a/config/locales/devise.bg.yml
+++ b/config/locales/devise.bg.yml
@@ -94,6 +94,11 @@ bg:
       updated_not_active: Паролата ви беше променена успешно.
     registrations:
       destroyed: Довиждане! Вашият акаунт беше успешно изтрит. Надяваме се скоро да ви видим пак.
+      signed_up: Добре дошли! Успешно се регистрирахте.
+      signed_up_but_inactive: Регистрирахте се успешно. Въпреки това, не може да влезете, тъй като акаунтът ви още не е задействан.
+      signed_up_but_locked: Регистрирахте се успешно. Въпреки това, не можете да влезете, тъй като акаунтът ви е заключен.
+      signed_up_but_pending: Изпратено е съобщение до адреса на имейла ви с връзка за потвърждение. След като щракнете върху линка, ние ще прегледаме заявлението ви. Ще бъдете уведомени при одобрение.
+      signed_up_but_unconfirmed: Е-писмо с връзка за потвърждение е изпратено до имейла ви. Последвайте връзката, за да задействате акаунта си. Проверете папката си за спам, ако не сте получили това е-писмо.
       update_needs_confirmation: Успешно осъвременихте акаунта си, но трябва да потвърдим новия ви адрес на имейл. Проверете имейла си и отворете връзката за потвърждаване на новия имейл адрес. Проверете папката си за спам, ако не сте получили това е-писмо.
       updated: Акаунтът ви е успешно осъвременен.
     sessions:
diff --git a/config/locales/devise.bn.yml b/config/locales/devise.bn.yml
index 897254ce2e..b046289039 100644
--- a/config/locales/devise.bn.yml
+++ b/config/locales/devise.bn.yml
@@ -69,6 +69,11 @@ bn:
       updated_not_active: আপনার পাসওয়ার্ড সফলভাবে পরিবর্তন করা হয়েছে।
     registrations:
       destroyed: টাটা! আপনার অ্যাকাউন্ট সফলভাবে বাতিল করা হয়েছে। আশা করি শীঘ্রই আবার দেখা হবে তোমার সাথে।
+      signed_up: স্বাগতম! আপনার নিবন্ধনটি সঠিকভাবে হয়েছে।
+      signed_up_but_inactive: আপনি সফলভাবে সাইন আপ করেছেন। তবে আপনার অ্যাকাউন্টটি এখনও সক্রিয় না হওয়ার কারণে আমরা আপনাকে সাইন ইন করতে পারি নি।
+      signed_up_but_locked: আপনি সফলভাবে সাইন আপ করেছেন। তবে আপনার অ্যাকাউন্টটি লক থাকায় আমরা আপনাকে সাইন ইন করতে পারিনি।
+      signed_up_but_pending: আপনার ইমেল ঠিকানায় একটি নিশ্চিতকরণ লিঙ্ক সহ একটি বার্তা প্রেরণ করা হয়েছে। আপনি লিঙ্কটি ক্লিক করার পরে, আমরা আপনার আবেদন পর্যালোচনা করব। এটি অনুমোদিত হলে আপনাকে অবহিত করা হবে।
+      signed_up_but_unconfirmed: আপনার ইমেল ঠিকানায় একটি নিশ্চিতকরণ লিঙ্ক সহ একটি বার্তা প্রেরণ করা হয়েছে। আপনার অ্যাকাউন্টটি সক্রিয় করতে লিংকটি অনুসরণ করুন। আপনি এই ইমেলটি না পেলে দয়া করে আপনার স্প্যাম ফোল্ডারটি পরীক্ষা করুন।
       update_needs_confirmation: আপনি আপনার অ্যাকাউন্টটি সফলভাবে আপডেট করেছেন, তবে আমাদের আপনার নতুন ইমেল ঠিকানা যাচাই করা দরকার। আপনার নতুন ইমেল ঠিকানাটি নিশ্চিত করতে দয়া করে আপনার ইমেলটি দেখুন এবং নিশ্চিত লিঙ্কটি অনুসরণ করুন। আপনি এই ইমেলটি না পেয়ে দয়া করে আপনার স্প্যাম ফোল্ডারটি পরীক্ষা করুন।
       updated: আপনার অ্যাকাউন্ট সফলভাবে আপডেট করা হয়েছে।
     sessions:
diff --git a/config/locales/devise.br.yml b/config/locales/devise.br.yml
index a936bcc05f..cb406e5f5e 100644
--- a/config/locales/devise.br.yml
+++ b/config/locales/devise.br.yml
@@ -84,6 +84,11 @@ br:
       updated_not_active: Kemmet eo bet ho ker-tremen ent reizh.
     registrations:
       destroyed: Kenavo! Ho kont a zo bet nullet gant berzh. Emichañs e viot adwelet tuchant.
+      signed_up: Donemat ! Enskrivet oc'h.
+      signed_up_but_inactive: Enskrivet oc'h bet gant berzh. N'omp ket evit anavezadenniñ ac'hanoc'h alatao, rak ho kont n'eo ket aotreet c'hoazh.
+      signed_up_but_locked: Enskrivet oc'h bet gant berzh. N'omp ket evit anavezadenniñ ac'hanoc'h alatao, rak ho kont a zo prennet.
+      signed_up_but_pending: Ur c'hemennad gant ul lec'hienn kadarnaat a zo bet kaset d'ho chomlec'h-postel. Pa vo kliket al lec'hienn ganeoc'h e vo gwiriet hoc'h arload ganeomp. Ur gemennadenn a vo kaset deoc'h hag-eñ eo erbedet ho kont.
+      signed_up_but_unconfirmed: Ur ch'emennad gant ul lec'hienn kadarnaat a zo bet kaset d'ho chomlec'h-postel. Mar plij, heuliit al lec'hienn evit gweredekaat ho kont. Gwiriit ho restr strobel ma ne oa ket resevet ar postel-mañ ganeoc'h.
       update_needs_confirmation: Ho kont a zo bet hizivaet da benn, met ret eo deomp gwiriañ ho chomlec'h-postel nevez. Mar plij, gwiriit ho postelioù ha heuliit al lec'hienn evit kadarnaat ho chomlec'h-postel nevez. Gwiriit ho restr strobel ma ne oa ket resevet ar postel-mañ ganeoc'h.
       updated: Ho kont a zo bet hizivaet da benn.
     sessions:
diff --git a/config/locales/devise.ca.yml b/config/locales/devise.ca.yml
index f228843863..9b4ccccff7 100644
--- a/config/locales/devise.ca.yml
+++ b/config/locales/devise.ca.yml
@@ -96,6 +96,11 @@ ca:
       updated_not_active: La contrasenya s'ha canviat correctament.
     registrations:
       destroyed: Adéu! el compte s'ha cancel·lat amb èxit. Desitgem veure't de nou ben aviat.
+      signed_up: Benvingut! T'has registrat.
+      signed_up_but_inactive: T'has registrat. Tanmateix, no podem iniciar la teva sessió perquè el compte encara no s'ha activat.
+      signed_up_but_locked: T'has registrat. Tanmateix, no podem iniciar la sessió perquè el compte està blocat.
+      signed_up_but_pending: S'ha enviat un missatge amb un enllaç de confirmació a la teva adreça de correu electrònic. Després que hagis fet clic a l'enllaç, revisarem la sol·licitud. Se't notificarà si s'aprova.
+      signed_up_but_unconfirmed: S'ha enviat per correu electrònic un missatge amb un enllaç de confirmació. Fes clic a l'enllaç per a activar el compte. Si us plau, verifica la teva carpeta de correu brossa si no el reps.
       update_needs_confirmation: Has actualitzat el teu compte amb èxit, però necessitem verificar la teva nova adreça de correu. Si us plau comprova el correu i segueixi l'enllaç per confirmar la nova adreça de correu. Si us plau verifica la teva carpeta de correu brossa si no reps aquest correu.
       updated: El teu compte ha estat actualitzat amb èxit.
     sessions:
diff --git a/config/locales/devise.ckb.yml b/config/locales/devise.ckb.yml
index 9a13e6cafa..f33959d76c 100644
--- a/config/locales/devise.ckb.yml
+++ b/config/locales/devise.ckb.yml
@@ -84,6 +84,11 @@ ckb:
       updated_not_active: تێپەڕوشەکەت بە سەرکەوتوویی گۆڕدرا.
     registrations:
       destroyed: خوات لەگەڵ! ئەژمێرەکەت بە سەرکەوتوویی هەڵوەشێنرایەوە. هیوادارین بەزوویی بتبینینەوە.
+      signed_up: بەخێربێیت! تۆ بە سەرکەوتوویی تۆمار کرای.
+      signed_up_but_inactive: تۆ بە سەرکەوتوویی تۆمارکرای. هەرچۆنێک بێت، نەمانتوانی چوونە ژوورەوەت بۆ بکەین لەبەرئەوەی هێشتا هەژمارەکەت کارا نەکراوە.
+      signed_up_but_locked: تۆ بە سەرکەوتوویی تۆمارکرای. هەرچۆنێک بێت، نەمانتوانی چوونە ژوورەوەت بۆ بکەین لەبەرئەوەی هێشتا هەژمارەکەت قوفڵ کراوە.
+      signed_up_but_pending: نامەیەک بە لینکی دووپاتکردنەوە نێردراوە بۆ ناونیشانی ئیمەیڵەکەت. دوای ئەوەی تۆ کرتە لەسەر لینکەکە دەکەیت، ئێمە پێداچوونەوە دەکەین بە بەرنامەکەتدا. ئاگادار دەکرێیت ئەگەر پەسەند کرا.
+      signed_up_but_unconfirmed: نامەیەک بە لینکی دووپاتکردنەوە نێردراوە بۆ ناونیشانی ئیمەیڵەکەت. تکایە دوای لینکەکە بکەوە بۆ کاراکردنی هەژمارەکەت. تکایە بوخچەی سپامەکەت بکەرەوە ئەگەر ئەم ئیمەیڵەت پێنەدرا.
       update_needs_confirmation: تۆ ئەژمێرەکەت بە سەرکەوتوویی نوێکردەوە، بەڵام پێویستە ئیمەیڵە نوێکەت بسەلمێنین. تکایە ئیمەیڵەکەت بپشکنە و دوای بەستەری دڵنیابوونەوە بکەوە بۆ دڵنیابوون لە ناونیشانی ئیمەیڵە نوێکەت. تکایە بوخچەی سپامەکەت بکەرەوە ئەگەر ئەم ئیمەیڵەت پێنەدرا.
       updated: هەژمارەکەت بە سەرکەوتوویی نوێکرایەوە.
     sessions:
diff --git a/config/locales/devise.co.yml b/config/locales/devise.co.yml
index daaed95f6d..d661637875 100644
--- a/config/locales/devise.co.yml
+++ b/config/locales/devise.co.yml
@@ -84,6 +84,11 @@ co:
       updated_not_active: A vostra chjave d’accessu hè stata cambiata.
     registrations:
       destroyed: Avvedeci! U vostru contu hè statu sguassatu. Speremu di vi rivede da prestu.
+      signed_up: Benvinutu! Site cunnettatu·a.
+      signed_up_but_inactive: Site arregistratu·a, mà ùn pudete micca cunnettavi perchè u vostru contu deve esse attivatu.
+      signed_up_but_locked: Site arregistratu·a, mà ùn pudete micca cunnettavi perchè u vostru contu hè chjosu.
+      signed_up_but_pending: Un missaghju cù un ligame di cunfirmazione hè statu mandatu à u vostr'indirizzu e-mail. Dop'à avè cliccatu u ligame, avemu da rivede a vostra dumanda - sarete nutificatu·a s'ella hè appruvata.
+      signed_up_but_unconfirmed: Un missaghju cù un ligame di cunfirmazione hè statu mandatu à u vostru indirizzu e-mail. Aprite stu ligame pè attivà u vostru contu. Pensate à verificà u cartulare di spam s’ellu ùn c’hè nunda.
       update_needs_confirmation: U vostru contu hè statu messu à ghjornu mà duvemu verificà u vostru novu e-mail. Un missaghju cù un ligame di cunfirmazione hè statu mandatu. Pensate à verificà u cartulare di spam s’ellu ùn c’hè nunda.
       updated: U vostru contu hè statu messu à ghjornu.
     sessions:
diff --git a/config/locales/devise.cs.yml b/config/locales/devise.cs.yml
index 42ecc1d53e..4dbc2e08bf 100644
--- a/config/locales/devise.cs.yml
+++ b/config/locales/devise.cs.yml
@@ -94,6 +94,11 @@ cs:
       updated_not_active: Vaše heslo bylo úspěšně změněno.
     registrations:
       destroyed: Sbohem! Váš účet byl úspěšně zrušen. Doufáme, že vás opět brzy uvidíme.
+      signed_up: Vítejte! Vaše registrace proběhla úspěšně.
+      signed_up_but_inactive: Vaše registrace proběhla úspěšně. Nemohli jsme vás však přihlásit, protože váš účet ještě není aktivován.
+      signed_up_but_locked: Vaše registrace proběhla úspěšně. Nemohli jsme vás však přihlásit, protože váš účet je uzamčen.
+      signed_up_but_pending: Na vaši e-mailovou adresu byla poslána zpráva s odkazem pro potvrzení. Poté, co na odkaz kliknete, vaši žádost posoudíme. Pokud bude schválena, budeme vás informovat.
+      signed_up_but_unconfirmed: Na vaši e-mailovou adresu byla poslána zpráva s odkazem pro potvrzení. Pro aktivaci vašeho účtu prosím klepněte na v něm uvedený odkaz. Pokud tento e-mail neobdržíte, podívejte se prosím také do složky „spam“.
       update_needs_confirmation: Váš účet byl úspěšně aktualizován, ale je potřeba ověřit vaši novou e-mailovou adresu. Pokud tento e-mail neobdržíte, podívejte se prosím také do složky „spam“.
       updated: Váš účet byl úspěšně aktualizován.
     sessions:
diff --git a/config/locales/devise.cy.yml b/config/locales/devise.cy.yml
index da383f70ae..b41e12f85d 100644
--- a/config/locales/devise.cy.yml
+++ b/config/locales/devise.cy.yml
@@ -94,6 +94,11 @@ cy:
       updated_not_active: Mae eich cyfrinair wedi ei newid yn llwyddiannus.
     registrations:
       destroyed: Hwyl fawr! Mae eich cyfrif wedi ei chanslo'n llwyddiannus. Gobeithiwn eich gweld chi eto'n fuan.
+      signed_up: Croeso! Rydych wedi cofrestru'n llwyddiannus.
+      signed_up_but_inactive: Yr ydych wedi cofrestru'n llwyddiannus. Fodd bynnag, ni allwn eich mewngofnodi achos nid yw eich cyfrif wedi ei hagor eto.
+      signed_up_but_locked: Rydych chi wedi cofrestru'n llwyddiannus. Fodd bynnag, ni allem eich mewngofnodi oherwydd bod eich cyfrif wedi'i gloi.
+      signed_up_but_pending: Mae neges gyda dolen cadarnhau wedi'i hanfon i'ch cyfeiriad e-bost. Ar ôl i chi glicio ar y ddolen, byddwn yn adolygu eich cais. Byddwch yn cael gwybod os caiff ei gymeradwyo.
+      signed_up_but_unconfirmed: Mae neges gyda dolen cadarnhau wedi ei anfon i'ch cyfeiriad e-bost. Dilynwch y ddolen er mwyn agor eich cyfrif. Edrychwch yn eich ffolder sbam os na dderbynioch chi'r e-bost hwn, os gwelwch yn dda.
       update_needs_confirmation: Rydych wedi diweddaru eich cyfrif yn llwyddiannus, ond mae angen i ni wirio'ch cyfeiriad e-bost newydd. Edrychwch ar eich e-byst a dilynwch y ddolen gadarnhau er mwyn cadarnhau eich cyfeiriad e-bost newydd. Edrychwch ar eich ffolder sbam os na dderbynioch chi yr e-bost hwn.
       updated: Mae eich cyfrif wedi ei ddiweddaru yn llwyddiannus.
     sessions:
diff --git a/config/locales/devise.da.yml b/config/locales/devise.da.yml
index d32cbbfd64..c472242ba7 100644
--- a/config/locales/devise.da.yml
+++ b/config/locales/devise.da.yml
@@ -94,6 +94,11 @@ da:
       updated_not_active: Din adgangskode er skiftet.
     registrations:
       destroyed: Farvel! Din konto er nu annulleret. Vi håber snart at se dig igen.
+      signed_up: Velkommen! Du er nu tilmeldt.
+      signed_up_but_inactive: Du har nu oprettet dig. Da din konto endnu ikke er aktiveret, kan du dog ikke logge ind med det samme.
+      signed_up_but_locked: Du har nu oprettet dig. Da din konto er låst, kan du ikke logge ind med det samme.
+      signed_up_but_pending: Et bekræftelseslink er e-mailet til dig. Når du har klikket på linket, gennemgår vi din ansøgning, og du får besked, hvis den godkendes.
+      signed_up_but_unconfirmed: Et bekræftelseslink er e-mailet til dig. Følg linket for at aktivere din konto. Tjek spammappen, hvis e-mailen ikke dukker op i indbakken.
       update_needs_confirmation: Du har opdateret din konto. Din nye e-mailadresse skal nu bekræftes. Til dette formål er du blevet e-mailet et bekræftelseslink, så følg dette for at bekræfte den nye e-mailadresse. Ser du ikke e-mailen i din indbakke snarest, så tjek Spam-mappen.
       updated: Din konto er nu opdateret.
     sessions:
diff --git a/config/locales/devise.de.yml b/config/locales/devise.de.yml
index 706fdfbee1..f22f834e61 100644
--- a/config/locales/devise.de.yml
+++ b/config/locales/devise.de.yml
@@ -94,6 +94,11 @@ de:
       updated_not_active: Dein Passwort wurde erfolgreich geändert.
     registrations:
       destroyed: Tschüss! Dein Konto wurde erfolgreich gelöscht. Wir hoffen, dich bald wiederzusehen.
+      signed_up: Herzlich willkommen! Du hast dich erfolgreich registriert.
+      signed_up_but_inactive: Du hast dich erfolgreich registriert. Allerdings ist dein Konto noch nicht aktiviert und du kannst dich daher noch nicht anmelden.
+      signed_up_but_locked: Du hast dich erfolgreich registriert. Allerdings ist dein Konto gesperrt und du kannst dich daher nicht anmelden.
+      signed_up_but_pending: Eine Nachricht mit einem Bestätigungslink wurde an deine E-Mail-Adresse gesendet. Nachdem du diesen Link angeklickt hast, werden wir deine Bewerbung überprüfen. Sobald sie genehmigt wurde, wirst du benachrichtigt.
+      signed_up_but_unconfirmed: Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Konto noch nicht bestätigt ist. Du erhältst in Kürze eine E-Mail. Darin wird erklärt, wie du dein Konto freischalten kannst.
       update_needs_confirmation: Deine Daten wurden aktualisiert, aber du musst deine neue E-Mail-Adresse bestätigen. Du erhältst in wenigen Minuten eine E-Mail. Darin ist erklärt, wie du die Änderung deiner E-Mail-Adresse abschließen kannst.
       updated: Dein Konto wurde erfolgreich aktualisiert.
     sessions:
diff --git a/config/locales/devise.el.yml b/config/locales/devise.el.yml
index 3d3d4f87d9..209dfe5bdf 100644
--- a/config/locales/devise.el.yml
+++ b/config/locales/devise.el.yml
@@ -94,6 +94,11 @@ el:
       updated_not_active: Το συνθηματικό σου άλλαξε.
     registrations:
       destroyed: Αντίο! Ο λογαριασμός σου ακυρώθηκε με επιτυχία. Ελπίζουμε να σε ξαναδούμε σύντομα.
+      signed_up: Καλώς ήρθες! Εγγράφηκες με επιτυχία.
+      signed_up_but_inactive: Εγγράφηκες με επιτυχία. Όμως δε μπορέσαμε να σε συνδέσουμε γιατί ο λογαριασμός σου δεν έχει ενεργοποιηθεί ακόμα.
+      signed_up_but_locked: Εγγράφηκες με επιτυχία. Όμως δε μπορέσαμε να σε συνδέσουμε γιατί ο λογαριασμός σου είναι κλειδωμένος.
+      signed_up_but_pending: Στάλθηκε στο email σου μήνυμα με ένα σύνδεσμο επιβεβαίωσης. Μόλις τον ακολουθήσεις θα ελέγξουμε την αίτηση σου. Θα ειδοποιήσεις εάν γίνει δεκτή.
+      signed_up_but_unconfirmed: Σου στείλαμε ένα μήνυμα με σύνδεσμο επιβεβαίωσης στη διεύθυνση email σου. Παρακαλούμε ακολούθησε το σύνδεσμο για να ενεργοποιήσεις το λογαριασμό σου. Παρακαλούμε έλεγξε το φάκελο με τα ανεπιθύμητα μηνύματα σου αν δεν το λάβεις.
       update_needs_confirmation: Ενημέρωσες το λογαριασμό σου με επιτυχία αλλά χρειαζόμαστε να επιβεβαιώσουμε τη νέα διεύθυνση email σου. Παρακαλούμε έλεγξε τα email σου και ακολούθησε το σύνδεσμο για να την επιβεβαιώσεις. Παρακαλούμε έλεγξε το φάκελο με τα ανεπιθύμητα μηνύματα σου αν δεν το λάβεις.
       updated: Ο λογαριασμός σου επιβεβαιώθηκε με επιτυχία.
     sessions:
diff --git a/config/locales/devise.en-GB.yml b/config/locales/devise.en-GB.yml
index 1127735ca0..3e8a534e1f 100644
--- a/config/locales/devise.en-GB.yml
+++ b/config/locales/devise.en-GB.yml
@@ -94,6 +94,11 @@ en-GB:
       updated_not_active: Your password has been changed successfully.
     registrations:
       destroyed: Bye! Your account has been successfully cancelled. We hope to see you again soon.
+      signed_up: Welcome! You have signed up successfully.
+      signed_up_but_inactive: You have signed up successfully. However, we could not log you in because your account is not yet activated.
+      signed_up_but_locked: You have signed up successfully. However, we could not log you in because your account is locked.
+      signed_up_but_pending: A message with a confirmation link has been sent to your email address. After you click the link, we will review your application. You will be notified if it is approved.
+      signed_up_but_unconfirmed: A message with a confirmation link has been sent to your email address. Please follow the link to activate your account. Please check your spam folder if you didn't receive this email.
       update_needs_confirmation: You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address. Please check your spam folder if you didn't receive this email.
       updated: Your account has been updated successfully.
     sessions:
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index dd7b0dcc92..63a59229fe 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -95,6 +95,11 @@ en:
     registrations:
       destroyed: Bye! Your account has been successfully cancelled. We hope to see you again soon.
       sign_up_failed_because_reach_limit: You cannot create account because reaching limit.
+      signed_up: Welcome! You have signed up successfully.
+      signed_up_but_inactive: You have signed up successfully. However, we could not sign you in because your account is not yet activated.
+      signed_up_but_locked: You have signed up successfully. However, we could not sign you in because your account is locked.
+      signed_up_but_pending: A message with a confirmation link has been sent to your email address. After you click the link, we will review your application. You will be notified if it is approved.
+      signed_up_but_unconfirmed: A message with a confirmation link has been sent to your email address. Please follow the link to activate your account. Please check your spam folder if you didn't receive this email.
       update_needs_confirmation: You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address. Please check your spam folder if you didn't receive this email.
       updated: Your account has been updated successfully.
     sessions:
diff --git a/config/locales/devise.eo.yml b/config/locales/devise.eo.yml
index 063b8707e0..754fa01550 100644
--- a/config/locales/devise.eo.yml
+++ b/config/locales/devise.eo.yml
@@ -94,6 +94,11 @@ eo:
       updated_not_active: Via pasvorto estis sukcese ŝanĝita.
     registrations:
       destroyed: Ĝis! Via konto estis sukcese forigita. Ni esperas revidi vin baldaŭ.
+      signed_up: Bonvenon! Vi sukcese registriĝis.
+      signed_up_but_inactive: Vi sukcese registriĝis. Tamen, ni ne povis ensalutigi vin, ĉar via konto ankoraŭ ne estas konfirmita.
+      signed_up_but_locked: Vi sukcese registriĝis. Tamen, ni ne povis ensalutigi vin, ĉar via konto estas ŝlosita.
+      signed_up_but_pending: Mesaĝo kun konfirma ligilo estis sendita al via retpoŝta adreso. Post kiam vi alklakis la ligilon, ni revizios vian kandidatiĝon. Vi estos sciigita se ĝi estas aprobita.
+      signed_up_but_unconfirmed: Retmesaĝo kun konfirma ligilo estis sendita al via retadreso. Bonvolu sekvi la ligilon por aktivigi vian konton. Bonvolu kontroli vian spamujon, se vi ne ricevis ĉi tiun retmesaĝon.
       update_needs_confirmation: Vi sukcese ĝisdatigis vian konton, sed ni bezonas kontroli vian novan retadreson. Bonvolu kontroli viajn retmesaĝojn kaj sekvi la konfirman ligilon por konfirmi vian novan retadreson. Bonvolu kontroli vian spamujon, se vi ne ricevis ĉi tiun retmesaĝon.
       updated: Via konto estis sukcese ĝisdatigita.
     sessions:
diff --git a/config/locales/devise.es-AR.yml b/config/locales/devise.es-AR.yml
index f71b8473ec..6249294597 100644
--- a/config/locales/devise.es-AR.yml
+++ b/config/locales/devise.es-AR.yml
@@ -94,6 +94,11 @@ es-AR:
       updated_not_active: Se cambió exitosamente tu contraseña.
     registrations:
       destroyed: "¡Chauchas! Se canceló exitosamente tu cuenta. Esperamos verte pronto de nuevo."
+      signed_up: "¡Bienvenido! Te registraste exitosamente."
+      signed_up_but_inactive: Te registraste exitosamente. Sin embargo, no podés iniciar sesión porque tu cuenta todavía no está activada.
+      signed_up_but_locked: Te registraste exitosamente. Sin embargo, no podés iniciar sesión porque tu cuenta está bloqueada.
+      signed_up_but_pending: Se envió un correo electrónico a tu dirección de correo con un enlace de confirmación. Después que hagás clic en ese enlace, revisaremos tu pedido. Si sos aprobado, serás notificado.
+      signed_up_but_unconfirmed: Se envió un correo electrónico a tu dirección de correo con un enlace de confirmación. Por favor, seguí ese enlace para activar tu cuenta. Si pasa el tiempo y no recibiste ningún mensaje, por favor, revisá tu carpeta de correo basura / no deseado / spam.
       update_needs_confirmation: Actualizaste tu cuenta exitosamente. Sin embargo, necesitamos verificar tu nueva dirección de correo electrónico. Por favor, revisá tu correo electrónico y seguí el enlace de confirmación. Si pasa el tiempo y no recibiste ningún mensaje, por favor, revisá tu carpeta de correo basura / no deseado / spam.
       updated: Se actualizó exitosamente tu cuenta.
     sessions:
diff --git a/config/locales/devise.es-MX.yml b/config/locales/devise.es-MX.yml
index 2e8ddc2e40..ac90412e85 100644
--- a/config/locales/devise.es-MX.yml
+++ b/config/locales/devise.es-MX.yml
@@ -94,6 +94,11 @@ es-MX:
       updated_not_active: Su contraseña ha sido cambiada con éxito.
     registrations:
       destroyed: "¡Adios! Su cuenta ha sido cancelada con éxito. Esperamos verle pronto de nuevo."
+      signed_up: "¡Bienvenido! Se ha registrado con éxito."
+      signed_up_but_inactive: Se ha registrado con éxito. Sin embargo, no podemos identificarle porque su cuenta no ha sido activada todavía.
+      signed_up_but_locked: Se ha registrado con éxito. Sin embargo, no podemos identificarle porque su cuenta está bloqueada.
+      signed_up_but_pending: Un mensaje con un enlace de confirmacion ha sido enviado a su direccion de email. Luego de clickear el link revisaremos su aplicacion. Seras notificado si es aprovada.
+      signed_up_but_unconfirmed: Un mensaje con un enlace de confirmación ha sido enviado a su correo electrónico. Por favor siga el enlace para activar su cuenta.
       update_needs_confirmation: Ha actualizado su cuenta con éxito, pero necesitamos verificar su nueva dirección de correo. Por favor compruebe su correo y siga el enlace para confirmar su nueva dirección de correo.
       updated: su cuenta ha sido actualizada con éxito.
     sessions:
diff --git a/config/locales/devise.es.yml b/config/locales/devise.es.yml
index ddfc1ba678..0fc329e0b6 100644
--- a/config/locales/devise.es.yml
+++ b/config/locales/devise.es.yml
@@ -94,6 +94,11 @@ es:
       updated_not_active: Tu contraseña se ha cambiado con éxito.
     registrations:
       destroyed: "¡Hasta otra! Tu cuenta ha sido cancelada con éxito. Esperamos verte de nuevo pronto."
+      signed_up: "¡Bienvenido! Te has registrado correctamente."
+      signed_up_but_inactive: Te has registrado con éxito. Sin embargo, no se ha podido iniciar sesión porque tu cuenta aún no está activada.
+      signed_up_but_locked: Te has registrado con éxito. Sin embargo, no se ha podido iniciar sesión porque tu cuenta está bloqueada.
+      signed_up_but_pending: Un mensaje con un enlace de confirmación ha sido enviado a tu dirección de correo electrónico. Después de hacer clic en el enlace, revisaremos tu solicitud. Serás notificado si se aprueba.
+      signed_up_but_unconfirmed: Un mensaje con un enlace de confirmación ha sido enviado a tu dirección de correo electrónico. Por favor, sigue el enlace para activar tu cuenta. Por favor, comprueba tu carpeta de correo no deseado si no recibes dicho correo electrónico.
       update_needs_confirmation: Has actualizado tu cuenta con éxito, pero necesitamos verificar tu nueva dirección de correo electrónico. Por favor, comprueba tu correo electrónico y sigue el enlace de confirmación para confirmar tu nueva dirección de correo electrónico. Por favor, comprueba tu carpeta de correo no deseado si no recibes dicho correo electrónico.
       updated: Tu cuenta se ha actualizado con éxito.
     sessions:
diff --git a/config/locales/devise.et.yml b/config/locales/devise.et.yml
index f09ad35b39..76fbf619cc 100644
--- a/config/locales/devise.et.yml
+++ b/config/locales/devise.et.yml
@@ -94,6 +94,11 @@ et:
       updated_not_active: Sinu salasõna muutmine õnnestus.
     registrations:
       destroyed: Nägemist! Sinu konto sulgemine õnnestus. Me loodame sind varsti taas näha.
+      signed_up: Tere tulemast! Sinu konto loomine õnnestus.
+      signed_up_but_inactive: Sinu konto loodi edukalt, kuid me ei saanud sind sisse logida, kuna konto pole veel aktiveeritud.
+      signed_up_but_locked: Sinu konto loodi edukalt, kuid me ei saanud sind sisse logida, kuna konto on lukustatud.
+      signed_up_but_pending: Kiri kinnituslingiga saadeti sinu e-postile. Pärast seda, kui oled vajutanud kinnituslingile, vaatame me taotluse üle. Saad teavituse, kui taotlus on vastu võetud.
+      signed_up_but_unconfirmed: Kiri kinnituslingiga saadeti sinu e-postile. Palun järgi linki, et oma konto aktiveerida. Palun kontrolli rämpspostikausta, kui selline kiri ei saabunud.
       update_needs_confirmation: Konto uuendamine õnnestus, kuid e-postiaadress tuleb veel kinnitada. Palun kontrolli oma e-posti ning järgi kirjas olevat kinnituslinki, et e-postiaadress kinnitada. Palun kontrolli rämpspostikausta, kui selline kiri ei saabunud.
       updated: Konto uuendamine õnnestus.
     sessions:
diff --git a/config/locales/devise.eu.yml b/config/locales/devise.eu.yml
index 4b3bbea8c6..3e675659fe 100644
--- a/config/locales/devise.eu.yml
+++ b/config/locales/devise.eu.yml
@@ -94,6 +94,11 @@ eu:
       updated_not_active: Zure pasahitza ongi aldatu da.
     registrations:
       destroyed: Agur! Zure kontua ongi ezeztatu da. Ea laster berriro ikusten garen.
+      signed_up: Ongi etorri! Ongi hasi duzu saioa.
+      signed_up_but_inactive: Ongi eman duzu izena. Hala ere, ezin duzu saioa hasi zure kontua oraindik ez dagoelako aktibatuta.
+      signed_up_but_locked: Ongi eman duzu izena. Hala ere, ezin duzu saioa hasi zure kontua giltzapetuta dagoelako.
+      signed_up_but_pending: Berrespen esteka bat duen mezu bat bidali dizugu zure eposta helbidera. Behin esteka sakatzen duzula, zure eskaera berrikusiko da. Onartzen bada, jakinarazpena jasoko duzu.
+      signed_up_but_unconfirmed: Baieztapen esteka bat duen eposta bat bidali dizugu. Jarraitu esteka zure kontua aktibatzeko. Begiratu zure spam karpetan ez baduzu mezua jaso.
       update_needs_confirmation: Zure kontua ongi eguneratu duzu, baina zure eposta helbide berria egiaztatu behar dugu. Berrespen esteka bat duen mezua bidali dizugu, sartu estekan zure eposta helbide berria berresteko. Begiratu zure spam karpetan ez baduzu mezua jaso.
       updated: Zure kontua ongi eguneratu da.
     sessions:
diff --git a/config/locales/devise.fa.yml b/config/locales/devise.fa.yml
index 71cd7699fe..c441e346a2 100644
--- a/config/locales/devise.fa.yml
+++ b/config/locales/devise.fa.yml
@@ -16,13 +16,13 @@ fa:
       pending: حساب شما همچنان در دست بررسی است.
       timeout: مهلت این ورود شما به سر رسید. برای ادامه، دوباره وارد شوید.
       unauthenticated: برای ادامه باید وارد شوید یا ثبت نام کنید.
-      unconfirmed: پیش از ادامه باید نشانی رایانامه‌تان را تأیید کنید.
+      unconfirmed: برای ادامه باید نشانی ایمیل خود را تأیید کنید.
     mailer:
       confirmation_instructions:
-        action: تأیید نشانی رایانامه
+        action: تأیید نشانی ایمیل
         action_with_app: پذیرش و بازگشت به %{app}
-        explanation: حسابی با این نشانی رایانامه روی %{host} ساخته‌اید. یک کلیک با فعّال کردنش فاصله دارید. اگر شما نبودید از این رایانامه چشم بپوشید.
-        explanation_when_pending: درخواستی برای دعوت به %{host} با این نشانی رایانامه داده‌اید. درخواستتان را پس از تأیید نشانی رایانامه‌تان بررسی خواهیم کرد. می‌توانید برای تغییر جزییات یا حذف حسابتان وارد شوید؛ ولی تا پیش از تأیید حساب نمی‌توانید به بیش‌تر قابلیت‌ها دسترسی داشته باشید. داده‌هایتان در صورت رد شدن درخواست برداشته خواهد شد و نیازی به اقدامی از سوی شما نیست. اگر شما نبودید از این رایانامه چشم بپوشید.
+        explanation: شما با این نشانی ایمیل حسابی در %{host} باز کرده‌اید. با یک کلیک می‌توانید این حساب را فعال کنید. اگر شما چنین کاری نکردید، لطفاً این ایمیل را نادیده بگیرید.
+        explanation_when_pending: شما با این نشانی ایمیل برای %{host} درخواست دعوت‌نامه داده‌اید. اگر ایمیل خود را تأیید کنید، ما درخواست شما را بررسی خواهیم کرد. تا وقتی بررسی تمام نشده، شما نمی‌توانید به حساب خود وارد شوید. اگر درخواست شما رد شود، ما اطلاعاتی را که از شما داریم پاک خواهیم کرد پس نیازی به کاری از سمت شما نخواهد بود. اگر شما چنین درخواستی نداده‌اید، لطفاً این ایمیل را نادیده بگیرید.
         extra_html: لطفاً همچنین <a href="%{terms_path}">قوانین کارساز</a> و <a href="%{policy_path}">شرایط خدمتمان</a> را بررسی کنید.
         subject: 'ماستودون: دستورالعمل تأیید برای %{instance}'
         title: تأیید نشانی رایانامه
@@ -87,22 +87,27 @@ fa:
       failure: تآیید هویتتان از %{kind} نتوانست انجام شود چرا که «%{reason}».
       success: تأیید هویت از حساب %{kind} با موفقیت انجام شد.
     passwords:
-      no_token: بدون آمدن از رایانامهٔ بازنشانی گذرواژه نمی‌توان به این صفحه دسترسی داشت. اگر از چنین رایانامه‌ای آمده‌اید لطفاً از استفادهٔ نشانی کامل مطمئن شوید.
-      send_instructions: اگر نشانی رایانامه‌تان در پایگاه داده‌مان باشد تا چند دقیقهٔ دیگر پیوند بازیابی گذرواژه‌ای در آن خواهید گرفت. بررسی شاخهٔ هرزنامه در صورت نگرفتن این رایانامه.
-      send_paranoid_instructions: اگر نشانی رایانامه‌تان در پایگاه داده‌مان باشد تا چند دقیقهٔ دیگر پیوند بازیابی گذرواژه‌ای در آن خواهید گرفت. بررسی شاخهٔ هرزنامه در صورت نگرفتن این رایانامه.
+      no_token: این صفحه را تنها از راه یک ایمیل بازنشانی گذرواژه می‌شود دید. اگر از چنین ایمیلی می‌آیید، لطفاً مطمئن شوید که نشانی موجود در ایمیل را کامل به کار برده‌اید.
+      send_instructions: اگر ایمیل شما در پایگاه دادهٔ ما موجود باشد، تا دقایقی دیگر یک ایمیل بازیابی گذرواژه دریافت خواهید کرد. اگر این ایمیل نیامد، لطفاً پوشهٔ هرزنامه‌هایتان را بررسی کنید.
+      send_paranoid_instructions: اگر ایمیل شما در پایگاه دادهٔ ما موجود باشد، تا دقایقی دیگر یک ایمیل بازیابی گذرواژه دریافت خواهید کرد. اگر این ایمیل نیامد، لطفاً پوشهٔ هرزنامه‌هایتان را بررسی کنید.
       updated: گذرواژه شما با موفقیت تغییر کرد. شما الان وارد سیستم هستید.
       updated_not_active: گذرواژه شما با موفقیت تغییر کرد.
     registrations:
       destroyed: بدرود! حساب شما با موفقیت لغو شد. امیدواریم دوباره شما را ببینیم.
-      update_needs_confirmation: حسابتان را با موفّقیت به‌روز کردید؛ ولی نشانی رایانامهٔ جدیدتان باید تأیید شود. بررسی رایانامه و پیروی از پیوند تأییدیه برای تأیید نشانی رایانهٔ جدیدتان. بررسی شاخهٔ هرزنامه در صورت نگرفتن این رایانامه.
+      signed_up: خوش آمدید! شما با موفقیت ثبت نام کردید.
+      signed_up_but_inactive: خوش آمدید! با موفقیت ثبت نام کردید. ولی هنوز وارد نشده‌اید؛ چرا که حسابتان هنوز فعال نشده است.
+      signed_up_but_locked: با موفّقیت ثبت‌نام کرده‌اید. با این حال نمی‌توان واردتان کرد؛ چرا که حسابتان قفل است.
+      signed_up_but_pending: پیامی با پیوند تأیید به نشانی رایانامه‌تان فرستاده شده. پس از زدن پیوند درخواستتان را بازبینی خواهیم کرد. در صورت پذیرش آگاه خواهید شد.
+      signed_up_but_unconfirmed: پیامی با پیوند تأیید به نشانی رایانامه‌تان فرستاده شده. لطفاً برای فعّال کردن حسابتان پیوند را بزنید. اگر این رایانامه را نگرفته‌اید شاخهٔ هرزنامه‌ها را بررسی کنید.
+      update_needs_confirmation: حسابتان را با موفّقیت به‌روز کردید؛‌ ولی باید نشانی رایانامهٔ جدیتان را تأیید کنیم. لطفاً رایانامه‌تان را بررسی کرده و برای تأیید نشانی رایانهٔ جدیدتان پیوند را بزنید. اگر این رایانامه را نگرفته‌اید شاخهٔ هرزنامه‌ها را بررسی کنید.
       updated: حسابتان با موفّقیت به‌روز شد.
     sessions:
       already_signed_out: با موفّقیت خارج شدید.
       signed_in: با موفّقیت وارد شدید.
       signed_out: با موفّقیت خارج شدید.
     unlocks:
-      send_instructions: تا دقایقی دیگر رایانامه‌ای با دستورالعمل قفل‌گشایی حسابتان دریافت خواهید کرد. بررسی شاخهٔ هرزنامه در صورت نگرفتن این رایانامه.
-      send_paranoid_instructions: اگر حسابتان وجود داشته باشد تا دقایقی دیگر رایانامه‌ای با دستورالعمل قفل‌گشاییش دریافت خواهید کرد. بررسی شاخهٔ هرزنامه در صورت نگرفتن این رایانامه.
+      send_instructions: تا دقایقی دیگر رایانامه‌ای با دستورالعمل قفل‌گشایی حسابتان دریافت خواهید کرد. اگر این رایانامه را نگرفتید، لطفاً پوشهٔ هرزنامه‌هایتان را بررسی کنید.
+      send_paranoid_instructions: اگر حسابتان وجود داشته باشد تا دقایقی دیگر رایانامه‌ای با دستورالعمل قفل‌گشاییش دریافت خواهید کرد. اگر این رایانامه را نگرفتید، لطفاً پوشهٔ هرزنامه‌هایتان را بررسی کنید.
       unlocked: حسابتان با موفّقیت قفل‌گشایی شد. لطفاً برای ادامه وارد شوید.
   errors:
     messages:
diff --git a/config/locales/devise.fi.yml b/config/locales/devise.fi.yml
index d230da1f54..61d58981d8 100644
--- a/config/locales/devise.fi.yml
+++ b/config/locales/devise.fi.yml
@@ -94,6 +94,11 @@ fi:
       updated_not_active: Salasanan vaihto onnistui.
     registrations:
       destroyed: Tilisi on poistettu. Näkemiin ja tervetuloa uudelleen.
+      signed_up: Tervetuloa! Rekisteröityminen onnistui.
+      signed_up_but_inactive: Rekisteröityminen onnistui. Emme kuitenkaan voi kirjata sinua sisään, sillä käyttäjätiliäsi ei ole vielä aktivoitu.
+      signed_up_but_locked: Rekisteröityminen onnistui. Emme kuitenkaan voi kirjata sinua sisään, sillä käyttäjätilisi on lukittuna.
+      signed_up_but_pending: Sähköpostiosoitteeseesi on lähetetty vahvistuslinkki. Sen avattuasi tarkistamme hakemuksesi, ja ilmoitamme hyväksynnästä.
+      signed_up_but_unconfirmed: Sähköpostiosoitteeseesi on lähetetty vahvistuslinkki. Aktivoi käyttäjätilisi seuraamalla linkkiä. Jos sitä ei kuulu, tarkista roskapostikansiosi.
       update_needs_confirmation: Tilin päivitys onnistui. Uusi sähköpostiosoite on kuitenkin vahvistettava. Tarkista saapuneet viestisi, ja vahvista uusi sähköpostiosoitteesi vahvistuslinkkiä seuraten. Jos viestiä ei kuulu, tarkista myös roskapostikansiosi.
       updated: Käyttäjätilisi tietojen päivittäminen onnistui.
     sessions:
diff --git a/config/locales/devise.fil.yml b/config/locales/devise.fil.yml
index cc88d26023..ad9a917cdf 100644
--- a/config/locales/devise.fil.yml
+++ b/config/locales/devise.fil.yml
@@ -94,6 +94,11 @@ fil:
       updated_not_active: Matagumpay na napalitan ang password mo.
     registrations:
       destroyed: Paalam! Matagumpay na nakansela ang account mo. Sana ay magkita ulit tayo sa hinaharap.
+      signed_up: Maligayang pagdating! Nakapag-sign up ka na.
+      signed_up_but_inactive: Nakapag-sign up ka na. Pero ka namin ma-sign in dahil hindi pa naa-activate ang account mo.
+      signed_up_but_locked: Nakapag-sign up ka na. Pero ka namin ma-sign in dahil naka-lock ang account mo.
+      signed_up_but_pending: Naipadala na sa email address mo ang mensaheng may confirmation link. Pagka-click ng link, ire-review namin ang application. Aabisuhan ka kapag naaprobahan ito.
+      signed_up_but_unconfirmed: Naipadala na sa email address mo ang mensaheng may confirmation link. Pakisundan ang link para ma-activate ang account mo. Pakitingnan ang spam folder mo kung hindi mo natanggap ang email na ito.
       update_needs_confirmation: Na-update mo na ang account mo, pero kailangan naming beripikahin ang bago mong email address. Pakitingnan ang email mo at sundan ang confirm link para makumpirma ang bagong email address mo. Pakitingnan ang spam folder mo kung hindi mo natanggap ang email na ito.
       updated: Matagumpay na na-update ang account mo.
     sessions:
diff --git a/config/locales/devise.fo.yml b/config/locales/devise.fo.yml
index f9783f30cc..30f83ba0da 100644
--- a/config/locales/devise.fo.yml
+++ b/config/locales/devise.fo.yml
@@ -94,6 +94,11 @@ fo:
       updated_not_active: Loyniorðið er broytt.
     registrations:
       destroyed: Farvæl! Konta tín er gjørd óvirkin. Vit vóna at síggja teg aftur skjótt.
+      signed_up: Vælkomin! Tú er nú tilmeldað/ur.
+      signed_up_but_inactive: Tú er tilmeldað/ur. Vit kunnu tó ikki rita teg inn, tí konta tín er ikki gjørd virkin enn.
+      signed_up_but_locked: Tú er tilmeldað/ur. Vit kunnu tó ikki rita teg inn, tí konta tín er læst.
+      signed_up_but_pending: Eini boð við einum váttanarleinki er send til teldupoststaðin hjá tær. Tá tú hevur klikt á leinkið, so fara vit at eftirkanna umsókn tína. Tú fær boð, um hon verður játtað.
+      signed_up_but_unconfirmed: Eini boð við einum váttanarleinki eru send til teldupoststaðin hjá tær. Vinarliga fylg leinkinum fyri at gera kontu tína virkna. Vinarliga eftirkanna mappuna við ruskposti, um tú ikki móttók teldupostin.
       update_needs_confirmation: Tað eydnaðist tær at dagføra kontuna, men tað er neyðugt at vátta nýggja teldupoststaðin hjá tær. Vinarliga kanna teldupostin hjá tær og fylg váttanarleinkinum at vátta nýggja teldupostin. Vinarliga kanna mappuna við ruskposti, um tú ikki móttók teldubrævið.
       updated: Tað eydnaðist at dagføra kontu tína.
     sessions:
diff --git a/config/locales/devise.fr-CA.yml b/config/locales/devise.fr-CA.yml
index fbe549743c..69ee177e33 100644
--- a/config/locales/devise.fr-CA.yml
+++ b/config/locales/devise.fr-CA.yml
@@ -94,6 +94,11 @@ fr-CA:
       updated_not_active: Votre mot de passe a été modifié avec succès.
     registrations:
       destroyed: Au revoir! Votre compte a été supprimé avec succès. Nous espérons vous revoir bientôt.
+      signed_up: Bienvenue! Vous êtes connecté·e.
+      signed_up_but_inactive: Vous êtes bien enregistré·e. Vous ne pouvez cependant pas vous connecter car votre compte n’est pas encore activé.
+      signed_up_but_locked: Vous êtes bien enregistré·e. Vous ne pouvez cependant pas vous connecter car votre compte est verrouillé.
+      signed_up_but_pending: Un message avec un lien de confirmation a été envoyé à votre adresse courriel. Après avoir cliqué sur le lien, nous examinerons votre demande. Vous serez informé·e si elle a été approuvée.
+      signed_up_but_unconfirmed: Un message contenant un lien de confirmation a été envoyé à votre adresse courriel. Ouvrez ce lien pour activer votre compte. Veuillez vérifier votre dossier de pourriels si vous n'avez pas reçu le courriel.
       update_needs_confirmation: Votre compte a été mis à jour avec succès, mais nous devons vérifier votre nouvelle adresse courriel. Merci de vérifier vos courriels et de cliquer sur le lien de confirmation pour valider votre nouvelle adresse. Veuillez vérifier votre dossier de pourriels si vous ne recevez pas le courriel.
       updated: Votre compte a été modifié avec succès.
     sessions:
diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml
index 10c49b22e6..631a2eee6f 100644
--- a/config/locales/devise.fr.yml
+++ b/config/locales/devise.fr.yml
@@ -94,6 +94,11 @@ fr:
       updated_not_active: Votre mot de passe a été modifié avec succès.
     registrations:
       destroyed: Au revoir ! Votre compte a été supprimé avec succès. Nous espérons vous revoir bientôt.
+      signed_up: Bienvenue ! Vous êtes connecté.
+      signed_up_but_inactive: Vous êtes bien enregistré·e. Vous ne pouvez cependant pas vous connecter car votre compte n’est pas encore activé.
+      signed_up_but_locked: Vous êtes bien enregistré·e. Vous ne pouvez cependant pas vous connecter car votre compte est verrouillé.
+      signed_up_but_pending: Un message avec un lien de confirmation a été envoyé à votre adresse courriel. Après avoir cliqué sur le lien, nous examinerons votre demande. Vous serez informé·e si elle a été approuvée.
+      signed_up_but_unconfirmed: Un message contenant un lien de confirmation a été envoyé à votre adresse de courriel. Ouvrez ce lien pour activer votre compte. Veuillez vérifier votre dossier d'indésirables si vous ne recevez pas le courriel.
       update_needs_confirmation: Votre compte a bien été mis à jour, mais nous devons vérifier votre nouvelle adresse de courriel. Merci de vérifier vos courriels et de cliquer sur le lien de confirmation pour finaliser la validation de votre nouvelle adresse. Si vous n'avez pas reçu le courriel, vérifiez votre dossier d’indésirables.
       updated: Votre compte a été modifié avec succès.
     sessions:
diff --git a/config/locales/devise.fy.yml b/config/locales/devise.fy.yml
index 318d9beff1..c8a04a7405 100644
--- a/config/locales/devise.fy.yml
+++ b/config/locales/devise.fy.yml
@@ -96,6 +96,11 @@ fy:
       updated_not_active: Jo wachtwurd is mei sukses wizige.
     registrations:
       destroyed: Jo account is mei sukses fuortsmiten. Miskien oant sjen.
+      signed_up: Jo binne registrearre.
+      signed_up_but_inactive: Jo binne registrearre. Jo koene allinnich net automatysk oanmeld wurde, omdat jo account noch net aktivearre is.
+      signed_up_but_locked: Jo binne registrearre. Jo koene allinnich net automatysk oanmeld wurde, omdat jo account beskoattele is.
+      signed_up_but_pending: Der is in berjocht mei in befêstigingskeppeling nei jo e-mailadres ferstjoerd. Neidat jo op dizze keppeling klikt hawwe nimme wy jo oanfraach yn behanneling. Jo wurde op de hichte steld wannear’t dizze goedkard wurdt.
+      signed_up_but_unconfirmed: Jo ûntfange fia in e-mailberjocht ynstruksjes hoe’t jo jo account aktivearje kinne. Sjoch tusken jo net-winske wannear’t neat ûntfongen waard.
       update_needs_confirmation: Jo hawwe jo e-mailadres mei sukses wizige, mar wy moatte jo nije e-mailadres noch befêstigje. Kontrolearje jo Postfek YN en klik op de keppeling yn it e-mailberjocht om jo e-mailadres te befêstigjen. Sjoch tusken jo net-winske wannear’t neat ûntfongen waard.
       updated: Jo accountgegevens binne bewarre.
     sessions:
diff --git a/config/locales/devise.ga.yml b/config/locales/devise.ga.yml
index 5d03c2a8e6..ca51bb9467 100644
--- a/config/locales/devise.ga.yml
+++ b/config/locales/devise.ga.yml
@@ -94,6 +94,11 @@ ga:
       updated_not_active: Athraíodh do phasfhocal go rathúil.
     registrations:
       destroyed: Slán! Cealaíodh do chuntas go rathúil. Tá súil againn tú a fheiceáil arís go luath.
+      signed_up: Fáilte romhat! D'éirigh leat síniú suas.
+      signed_up_but_inactive: D'éirigh leat síniú suas. Mar sin féin, níorbh fhéidir linn tú a shíniú isteach toisc nach bhfuil do chuntas gníomhachtaithe fós.
+      signed_up_but_locked: D'éirigh leat síniú suas. Mar sin féin, níorbh fhéidir linn tú a shíniú isteach toisc go bhfuil do chuntas glasáilte.
+      signed_up_but_pending: Tá teachtaireacht le nasc deimhnithe seolta chuig do sheoladh ríomhphoist. Tar éis duit an nasc a chliceáil, déanfaimid athbhreithniú ar d’iarratas. Cuirfear in iúl duit má cheadaítear é.
+      signed_up_but_unconfirmed: Tá teachtaireacht le nasc deimhnithe seolta chuig do sheoladh ríomhphoist. Lean an nasc chun do chuntas a ghníomhachtú. Seiceáil d'fhillteán turscair mura bhfuair tú an ríomhphost seo.
       update_needs_confirmation: D'éirigh leat do chuntas a nuashonrú, ach caithfimid do sheoladh ríomhphoist nua a fhíorú. Seiceáil do ríomhphost agus lean an nasc dearbhaithe chun do sheoladh ríomhphoist nua a dhearbhú. Seiceáil d'fhillteán turscair mura bhfuair tú an ríomhphost seo.
       updated: D'éirigh le do chuntas a nuashonrú.
     sessions:
diff --git a/config/locales/devise.gd.yml b/config/locales/devise.gd.yml
index d110bc799b..ca9dcc4969 100644
--- a/config/locales/devise.gd.yml
+++ b/config/locales/devise.gd.yml
@@ -94,6 +94,11 @@ gd:
       updated_not_active: Chaidh am facal-faire agad atharrachadh.
     registrations:
       destroyed: Soraidh slàn leat! Chaidh crìoch a chur air a’ chunntas agad. Tha sinn an dòchas nach bi thu fada gun tilleadh.
+      signed_up: Fàilte ort! Tha thu air clàradh leinn.
+      signed_up_but_inactive: Tha thu air clàradh leinn. Gidheadh, chan urrainn dhuinn do clàradh a-steach air sgàth ’s nach deach an cunntas agad a ghnìomhachadh fhathast.
+      signed_up_but_locked: Tha thu air clàradh leinn. Gidheadh, chan urrainn dhuinn do clàradh a-steach air sgàth ’s gu bheil an cunntas agad glaiste.
+      signed_up_but_pending: Chaidh teachdaireachd le ceangal dearbhaidh a chur dhan t-seòladh puist-d agad. Nuair a bhios tu air briogadh air a’ cheangal, nì sinn lèirmheas air d’ iarrtas. Leigidh sinn fios dhut ma thèid aontachadh ris.
+      signed_up_but_unconfirmed: Chaidh teachdaireachd le ceangal dearbhaidh a chur dhan t-seòladh puist-d agad. Lean air a’ cheangal ud a ghnìomhachadh a’ chunntais agad. Thoir sùil air pasgan an spama agad mura faigh thu am post-d seo.
       update_needs_confirmation: Chaidh an cunntas agad ùrachadh ach feumaidh sinn an seòladh puist-d ùr agad a dhearbhadh. Thoir sùil air a’ phost-d agad agus lean air a’ cheangal dearbhaidh a dhearbhadh an t-seòlaidh puist-d ùir agad. Thoir sùil air pasgan an spama agad mura faigh thu am post-d seo.
       updated: Chaidh an cunntas agad ùrachadh.
     sessions:
diff --git a/config/locales/devise.gl.yml b/config/locales/devise.gl.yml
index 6bbec181b8..00b1824808 100644
--- a/config/locales/devise.gl.yml
+++ b/config/locales/devise.gl.yml
@@ -2,8 +2,8 @@
 gl:
   devise:
     confirmations:
-      confirmed: Confirmouse correctamente o teu enderezo de correo.
-      send_instructions: Nuns minutos vas recibir un correo coas instrucións para confirmar o teu enderezo de correo. Por favor, comproba o cartafol de spam se non recibiches o correo.
+      confirmed: O teu enderezo de email foi confirmado.
+      send_instructions: Vas recibir un email coas instrucións para confirmar o teu enderezo de email dentro dalgúns minutos. Por favor, comproba o cartafol de spam se non recibiches o correo.
       send_paranoid_instructions: Se o teu enderezo de email xa existira na nosa base de datos, vas recibir un correo coas instrucións de confirmación dentro dalgúns minutos. Por favor, comproba o cartafol de spam se non recibiches o correo.
     failure:
       already_authenticated: Xa estás conectada.
@@ -16,31 +16,31 @@ gl:
       pending: A túa conta aínda está baixo revisión.
       timeout: A túa sesión caducou. Accede outra vez para continuar.
       unauthenticated: Precisas iniciar sesión ou rexistrarte antes de continuar.
-      unconfirmed: Tes que confirmar o teu enderezo de correo antes de continuar.
+      unconfirmed: Tes que confirmar o teu enderezo de email antes de continuar.
     mailer:
       confirmation_instructions:
-        action: Verificar o enderezo de correo
+        action: Verificar o enderezo de email
         action_with_app: Confirmar e volver a %{app}
-        explanation: Creaches unha conta en %{host} con este enderezo de correo. Estás a un clic de activala. Se non foches ti quen fixo esta solicitude, por favor ignora esta mensaxe.
-        explanation_when_pending: Solicitaches un convite para %{host} con este enderezo de correo. Logo confirmes o teu enderezo de correo, imos revisar a túa inscrición. Podes iniciar sesión para mudar os teus datos ou eliminar a túa conta, mais non poderás aceder á meirande parte das funcións até que a túa conta sexa aprobada. Se a túa inscrición for rexeitada, os teus datos serán eliminados, polo que non será necesaria calquera acción adicional da túa parte. Se non solicitaches este convite, por favor, ignora este correo.
+        explanation: Creaches unha conta en %{host} con este enderezo de email. Estás a un clic de activala. Se non foches ti o que fixeches este rexisto, por favor ignora esta mensaxe.
+        explanation_when_pending: Solicitaches un convite para %{host} con este enderezo de email. Logo de que confirmes o teu enderezo de email, imos revisar a túa inscrición. Podes iniciar sesión para mudar os teus datos ou eliminar a túa conta, mais non poderás aceder á meirande parte das funcións até que a túa conta sexa aprobada. Se a túa inscrición for rexeitada, os teus datos serán eliminados, polo que non será necesaria calquera acción adicional da túa parte. Se non solicitaches este convite, por favor, ignora este correo.
         extra_html: Por favor, le <a href="%{terms_path}">as regras do servidor</a> e os <a href="%{policy_path}">nosos termos do servizo</a>.
         subject: 'Mastodon: Instrucións de confirmación para %{instance}'
-        title: Verificar o enderezo de correo
+        title: Verificar o enderezo de email
       email_changed:
         explanation: 'O email asociado á túa conta será mudado a:'
         extra: Se non mudaches o teu email é posíbel que alguén teña conseguido acceder á túa conta. Por favor muda o teu contrasinal de xeito imediato ou entra en contacto cun administrador do servidor se non podes acceder a túa conta.
         subject: 'Mastodon: Email mudado'
-        title: Novo enderezo de correo
+        title: Novo enderezo de email
       password_change:
         explanation: O contrasinal da túa conta foi mudado.
         extra: Se non mudaches o teu contrasinal, é posíbel que alguén teña conseguido acceder á túa conta. Por favor muda o teu contrasinal de xeito imediato ou entra en contato cun administrador do servidor se non podes acceder a túa conta.
         subject: 'Mastodon: Contrasinal mudado'
         title: Contrasinal mudado
       reconfirmation_instructions:
-        explanation: Confirma o teu novo enderezo para mudar o correo.
-        extra: Se esta mudanza non foi iniciada por ti, por favor ignora este correo. O enderezo de correo para a túa conta do Mastodon non mudará mentres non accedas á ligazón de enriba.
+        explanation: Confirma o teu novo enderezo para mudar o email.
+        extra: Se esta mudanza non foi comezada por ti, por favor ignora este email. O enderezo de email para a túa conta do Mastodon non mudará mentres non accedas á ligazón de enriba.
         subject: 'Mastodon: Confirmar email para %{instance}'
-        title: Verificar o enderezo de correo
+        title: Verificar o enderezo de email
       reset_password_instructions:
         action: Mudar contrasinal
         explanation: Solicitaches un novo contrasinal para a túa conta.
@@ -87,14 +87,19 @@ gl:
       failure: Non foi posible autenticarte desde %{kind} debido a "%{reason}".
       success: Autenticado con éxito na conta %{kind}.
     passwords:
-      no_token: Non podes acceder a esta páxina se non vés a través da ligazón enviada por correo para o cambio do contrasinal. Se empregaches esa ligazón para chegar aquí, por favor verifica que o enderezo URL actual é o mesmo do que foi enviado no correo.
-      send_instructions: Se o teu enderezo de correo existe na nosa base de datos, vas recibir un email coas instrucións para mudar o contrasinal dentro duns minutos. Por favor, comproba o teu cartafol de correo lixo (spam) se ves que non recibiches o correo.
-      send_paranoid_instructions: Se o teu enderezo de correo existe na nosa base de datos, vas recibir un email coas instrucións para mudar o contrasinal dentro duns minutos. Por favor, comproba o teu cartafol de correo lixo (spam) se ves que non recibiches o correo.
+      no_token: Non podes acceder a esta páxina se non vés a través da ligazón enviada por email para o mudado do teu contrasinal. Se empregaches esa ligazón para chegar aquí, por favor verifica que o enderezo URL actual é o mesmo do que foi enviado no email.
+      send_instructions: Se o teu enderezo de email existe na nosa base de datos, vas recibir un email coas instrucións para mudar o contrasinal dentro duns minutos. Por favor, comproba o teu cartafol de correo lixo (spam) se ves que non recibiches o email.
+      send_paranoid_instructions: Se o teu enderezo de email existe na nosa base de datos, vas recibir unha ligazón para recuperar o contrasinal dentro duns minutos. Por favor, comproba o teu cartafol de correo lixo (spam) se ves que non recibiches o email.
       updated: O teu contrasinal foi mudado. Estás xa autenticado na túa conta.
       updated_not_active: O teu contrasinal foi mudado de xeito correcto.
     registrations:
       destroyed: Adeus! A túa conta foi cancelada de xeito correcto. Agardamos verte de novo.
-      update_needs_confirmation: Actualizaches a túa conta de xeito correcto, pero precisamos verificar o teu novo enderezo de correo. Por favor, revisa o teu correo e segue a ligazón para confirmar o teu novo enderezo de correo. Comproba o cartafol de correo lixo (spam) se ves que non recibiches o correo.
+      signed_up: Benvido! Rexistrácheste de xeito correcto.
+      signed_up_but_inactive: A túa conta foi rexistada. Porén aínda non está activada.
+      signed_up_but_locked: A túa conta foi rexistada. Porén está bloqueada.
+      signed_up_but_pending: Acabamos de enviar unha mensaxe ao teu email cunha ligazón de confirmación. Após premer na ligazón, revisaremos a túa solicitude. Recibirás unha notificación se a túa conta é aprobada.
+      signed_up_but_unconfirmed: Enviouse unha mensaxe cunha ligazón de confirmación ao teu email. Por favor, preme nesa ligazón para activar a túa conta. Comproba o teu cartafol de correo lixo (spam) se ves que non recibiches o correo.
+      update_needs_confirmation: Actualizaches a túa conta de xeito correcto, pero precisamos verificar o teu novo enderezo de email. Por favor, revisa o teu email e segue a ligazón para confirmar o teu novo enderezo de email. Comproba o teu cartafol de correo lixo (spam) se ves que non recibiches o correo.
       updated: A túa conta foi actualizada de xeito correcto.
     sessions:
       already_signed_out: Pechouse a sesión de xeito correcto.
diff --git a/config/locales/devise.he.yml b/config/locales/devise.he.yml
index 91eb75f9d1..02e307ae1c 100644
--- a/config/locales/devise.he.yml
+++ b/config/locales/devise.he.yml
@@ -94,6 +94,11 @@ he:
       updated_not_active: סיסמתך שונתה בהצלחה.
     registrations:
       destroyed: בייוש! חשבונך בוטל בהצלחה. אנחנו מקווים לראות אותך שוב בקרוב.
+      signed_up: ברוכים הבאים! נרשמת בהצלחה.
+      signed_up_but_inactive: נרשמת בהצלחה. למרות זאת לא הצליחה הכניסה לחשבון מאחר וחשבונך עוד לא הופעל.
+      signed_up_but_locked: נרשמת בהצלחה. למרות זאת לא הצליחה הכניסה לחשבון מאחר וחשבונך נעול.
+      signed_up_but_pending: מסר עם קישורית אישור נשלח לכתובת הדוא"ל שלך. אחרי לחיצה על הקישורית, נבחן את בקשתך. הודעה תמסר אם בקשתך תתקבל.
+      signed_up_but_unconfirmed: דוא"ל עם קישורית לאימות נשלך לכתובתך. נא לעקוב אחר הקישורית על מנת להפעיל את החשבון. יש לבדוק את תיבת הספאם ליתר בטחון אם ההודעה לא הגיעה תוך דקות ספורות.
       update_needs_confirmation: עדכת את חשבונך בהצלחה, אך יש צורך לאמת את כתובת הדוא"ל החדשה שלך. נא לבדוק בחשבון הדוא"ל לקבלת קישורית אימות על מנת לאמת את הכתובת החדשה. יש לבדוק את תיבת הספאם ליתר בטחון אם ההודעה לא הגיעה תוך דקות ספורות.
       updated: חשבונך עודכן בהצלחה.
     sessions:
diff --git a/config/locales/devise.hr.yml b/config/locales/devise.hr.yml
index b7ecbbb90b..89db8922e4 100644
--- a/config/locales/devise.hr.yml
+++ b/config/locales/devise.hr.yml
@@ -71,6 +71,10 @@ hr:
       updated_not_active: Vaša lozinka uspješno je promijenjena.
     registrations:
       destroyed: Zbogom! Vaš je račun uspješno otkazan. Nadamo se da ćemo Vas uskoro ponovno vidjeti.
+      signed_up: Dobro došli! Uspješno ste se prijavili.
+      signed_up_but_inactive: Uspješno ste se registrirali. No, ne možemo Vas prijaviti jer Vaš račun još nije aktiviran.
+      signed_up_but_locked: Uspješno ste se registrirali. No, ne možemo Vas prijaviti jer je Vaš račun zaključan.
+      signed_up_but_unconfirmed: Poruka s poveznicom za potvrđivanje poslana je na Vašu adresu e-pošte. Molimo slijedite poveznicu za aktivaciju Vašeg računa. Molimo provjerite mapu neželjene pošte, ako niste primili ovu e-poštu.
       update_needs_confirmation: Vaš račun uspješno je ažuriran, ali moramo potvrditi Vašu novu adresu e-pošte. Molimo provjerite Vašu e-poštu i slijedite poveznicu za potvrđivanje Vaše nove adrese e-pošte. Molimo provjerite mapu neželjene pošte, ako niste primili ovu e-poštu.
       updated: Vaš je račun uspješno ažuriran.
     sessions:
diff --git a/config/locales/devise.hu.yml b/config/locales/devise.hu.yml
index 5ea81bef0d..d8958d5fb0 100644
--- a/config/locales/devise.hu.yml
+++ b/config/locales/devise.hu.yml
@@ -94,6 +94,11 @@ hu:
       updated_not_active: A jelszavad sikeresen megváltoztatásra került.
     registrations:
       destroyed: Viszlát! A fiókodat sikeresen töröltük. Reméljük hamarosan viszontláthatunk.
+      signed_up: Üdvözlünk! Sikeresen regisztráltál.
+      signed_up_but_inactive: Sikeresen regisztráltál. Ennek ellenére nem tudunk beléptetni, ugyanis a fiókodat még nem aktiválták.
+      signed_up_but_locked: Sikeresen regisztráltál. Ennek ellenére nem tudunk beléptetni, ugyanis a fiókod le van zárva.
+      signed_up_but_pending: Egy megerősítési hivatkozással ellátott üzenetet kiküldtünk az e-mail címedre. Ha kattintasz a hivatkozásra, átnézzük a kérelmedet. Értesítünk, ha jóváhagytuk.
+      signed_up_but_unconfirmed: Egy megerősítési hivatkozással ellátott üzenetet kiküldtünk az e-mail címedre. Kérjük használd a hivatkozást a fiókod aktiválásához. Ellenőrizd a spam mappádat, ha nem kaptad meg ezt a levelet.
       update_needs_confirmation: Sikeresen frissítetted a fiókodat, de szükségünk van az e-mail címed megerősítésére. Kérlek ellenőrizd az e-mailedet és kövesd a levélben szereplő megerősítési linket az e-mail címed megerősítéséhez. Ellenőrizd a levélszemét mappád, ha nem kaptál volna ilyen levelet.
       updated: A fiókod sikeresen frissítésre került.
     sessions:
diff --git a/config/locales/devise.hy.yml b/config/locales/devise.hy.yml
index 25b6641ce6..ee23163f51 100644
--- a/config/locales/devise.hy.yml
+++ b/config/locales/devise.hy.yml
@@ -84,6 +84,11 @@ hy:
       updated_not_active: Գաղտնաբառդ փոփոխուեց յաջողութեամբ։
     registrations:
       destroyed: Ցը՜․ Քո հաշիւը յաջողութեամբ չեղարկուեց։ Յոյս ունենք քեզ կրկին տեսնել։
+      signed_up: Ողջո՜յն։ Բարեյաջող գրանցուեցիր։
+      signed_up_but_inactive: Բարեյաջող գրանցուեցիր։ Սակայն, դեռ չես կարող մուտք գործել, քանի որ հաշիւդ դեռ ակտիւ չէ։
+      signed_up_but_locked: Բարեյաջող գրանցուեցիր։ Սակայն, դեռ չես կարող մուտք գործել, քանի որ հաշիւդ փակ է։
+      signed_up_but_pending: Հաղորդագրութիւնը՝ հաստատման յղումով ուղարկուել է քո էլ․ փոստին։ Յղմանը կտտացնելուց յետոյ մենք կը վերանայենք քո դիմումը։ Հաստատումից յետոյ քեզ կը տեղեկացնենք։
+      signed_up_but_unconfirmed: Հաղորդագրութիւնը՝ հաստատման յղումով ուղարկուել է քո էլ․ փոստին։ Խնդրում ենք հետեւիր յղմանը հաշիւդ ակտիւացնելու համար։ Խնդրում ենք, ստուգիր սպամ պանակը, եթէ չստանաս իմակ։
       update_needs_confirmation: Բարեյաջող թարմացրիր հաշիւդ, բայց մենք պէտք է հաստատենք քո էլ․ հասցէն։ Խնդրում ենք, ստուգիր փոստդ եւ հետեւիր հաստատման յղմանը՝ նոր էլ․ հասցէդ հաստատելու համար։ Ստուգիր սպամ պանակը, իմակ չստանալու դէպքում։
       updated: Հաշիւդ բարեյաջող թարմացուեց։
     sessions:
diff --git a/config/locales/devise.ia.yml b/config/locales/devise.ia.yml
index eb580dde5d..8e073c9efb 100644
--- a/config/locales/devise.ia.yml
+++ b/config/locales/devise.ia.yml
@@ -94,6 +94,11 @@ ia:
       updated_not_active: Tu contrasigno ha essite cambiate.
     registrations:
       destroyed: A revider! Tu conto ha essite cancellate. Nos spera vider te de novo tosto.
+      signed_up: Benvenite! Tu te ha inscribite con successo.
+      signed_up_but_inactive: Tu te ha inscribite con successo. Nonobstante, nos non poteva aperir tu session perque tu conto non es ancora activate.
+      signed_up_but_locked: Tu te ha inscribite con successo. Nonobstante, nos non poteva aperir tu session perque tu conto es serrate.
+      signed_up_but_pending: Un message con un ligamine de confirmation ha essite inviate a tu adresse de email. Post que tu clicca sur le ligamine, nos revidera tu demanda. Tu essera notificate si illo es approbate.
+      signed_up_but_unconfirmed: Un message con un ligamine de confirmation ha essite inviate a tu adresse de e-mail. Per favor seque le ligamine pro activar tu conto. Consulta tu dossier de spam si tu non recipe iste e-mail.
       update_needs_confirmation: Tu ha actualisate tu conto con successo, ma nos debe verificar tu nove adresse de e-mail. Accede a tu e-mail e seque le ligamine de confirmation pro confirmar tu nove adresse de e-mail. Consulta tu dossier de spam si tu non recipe iste e-mail.
       updated: Tu conto ha essite actualisate con successo.
     sessions:
diff --git a/config/locales/devise.id.yml b/config/locales/devise.id.yml
index 7f87dea880..c0dede95aa 100644
--- a/config/locales/devise.id.yml
+++ b/config/locales/devise.id.yml
@@ -84,6 +84,11 @@ id:
       updated_not_active: Kata sandi anda berhasil diubah.
     registrations:
       destroyed: Selamat tinggal! Akun anda berhasil dibatalkan. Kami harap berjumpa lagi dengan anda dilain waktu.
+      signed_up: Selamat datang! Pendaftaran anda berhasil.
+      signed_up_but_inactive: Anda berhasil melakukan pendaftaran. Tetapi kami tidak dapat memasukkan anda karena akun anda belum diaktifkan.
+      signed_up_but_locked: Anda berhasil melakukan pendaftaran. Tetapi kami tidak dapat memasukkan anda karena akun anda dikunci.
+      signed_up_but_pending: Pesan dengan tautan konfirmasi telah dikirim ke alamat email Anda. Setelah Anda mengklik tautan, kami akan meninjau lamaran Anda. Anda akan diberitahu jika diterima.
+      signed_up_but_unconfirmed: Sebuah pesan berisi link konfirmasi telah dikirim ke alamat email anda. Silakan ikuti link tersebut untuk mengaktifkan akun anda.
       update_needs_confirmation: Akun anda telah berhasil diubah, tetapi kami harus memverifikasi alamat email anda yang baru. Mohon cek email anda dan ikuti link untuk mengkonfirmasi alamat email anda yang baru.
       updated: Akun anda berhasil diubah.
     sessions:
diff --git a/config/locales/devise.ie.yml b/config/locales/devise.ie.yml
index 116fd3b5e0..9c82bd4529 100644
--- a/config/locales/devise.ie.yml
+++ b/config/locales/devise.ie.yml
@@ -94,6 +94,11 @@ ie:
       updated_not_active: Tui passa-parol ha esset changeat successosimen.
     registrations:
       destroyed: Adío! Tui conto ha esset anullat con successe. Noi espera revider te pos ne long.
+      signed_up: Benevenit! Tu ha successat registrar te.
+      signed_up_but_inactive: Tu ha registrat te con successe, támen noi ne posset far te intrar pro que tui conto ancor ne ha esset activat.
+      signed_up_but_locked: Tu ha registrat te con successe, támen noi ne posset far te intrar pro que tui conto es serrat.
+      signed_up_but_pending: Un missage con un ligament de confirmation ha esset inviat a tui adresse electronic. Pos har cliccat sur li ligament, noi va inspecter tui aplication. Tu va reciver un notification si it es aprobat.
+      signed_up_but_unconfirmed: Un missage con un ligament de confirmation ha esset inviat a tui adresse electronic. Ples sequer li ligament por activar tui conto, e confirmar tui spamiere si tu ne ha recivet li e-posta.
       update_needs_confirmation: Tu ha actualisat tui conto con successe, ma noi deve verificar tui nov adresse electronic. Ples confirmar tui e-postas e sequer li ligament de confirmation por confirmar li nov adresse, e inspecter tui spamiere si tu ne ha recivet li e-posta.
       updated: Tui conto ha esset actualisat successosimen.
     sessions:
diff --git a/config/locales/devise.io.yml b/config/locales/devise.io.yml
index 8a86cf18b0..49637579e4 100644
--- a/config/locales/devise.io.yml
+++ b/config/locales/devise.io.yml
@@ -21,19 +21,19 @@ io:
       confirmation_instructions:
         action: Verifikez retpostadreso
         action_with_app: Konfirmez e retrovenez a %{app}
-        explanation: Vu kreis konto sur %{host} per ca retpostadreso. Se vu ne agis lu, ignorez ca retposto.
+        explanation: Vu kreis konto che %{host} per ca retpostadreso. Vu povas facile aktivigar lu. Se vu ne agis lu, ignorez ca retposto.
         explanation_when_pending: Vu aplikis por ganar invito a %{host} per ca retpostkonto. Pos vu konfirmas vua retpostkonto, ni kontrolos vua apliko. Vu povas enirar por chanjar vua detali o efacar vua konto, ma vu ne povas acesar maxim de funcioni til vua konto aprobesas. Se vua apliko refuzesas, vua informi efacesos, do plusa ago ne bezonesos de vu. Se vu ne agis lu, ignorez ca retposto.
         extra_html: Anke videz <a href="%{terms_path}">reguli di la servilo</a> e <a href="%{policy_path}">nia servokondicioni</a>.
         subject: Instrucioni por konfirmar %{instance}
         title: Verifikez retpostadreso
       email_changed:
         explanation: 'Retpostadreso di vua konto chanjesas a:'
-        extra: Se vu ne chanjesis vua retpostadreso, eble ulu adiris vua konto.
+        extra: Se vu ne chanjesis vua retpostadreso, nulu posible acesis vua konto. Chanjez vua pasvorto quik o kontaktez serviladministratero se vu ne povas enirar vua konto.
         subject: 'Mastodon: Retpostadreso chanjesis'
         title: Nova retpostadreso
       password_change:
         explanation: La pasvorto di vua konto chanjesis.
-        extra: Se vu ne chanjesis vua pasvorto, eble ulu adiris vua konto.
+        extra: Se vu ne chanjesis vua pasvorto, nulu posible acesis vua konto. Chanjez vua pasvorto quik o kontaktez serviladministratero se vu ne povas enirar vua konto.
         subject: Tua pasvorto chanjesis senprobleme.
         title: Pasvorto chanjesis
       reconfirmation_instructions:
@@ -49,14 +49,14 @@ io:
         title: Richanjo di pasvorto
       two_factor_disabled:
         explanation: Eniro esas nun posibla per nur retpostoadreso e pasvorto.
-        subject: 'Mastodon: Dufaktora yurizo desebligesis'
-        subtitle: Dufaktora yurizo por vua konto desebligesis.
-        title: 2FA desebligesis
+        subject: 'Mastodon: 2-faktorverifiko deaktivigesis'
+        subtitle: 2-faktora verifiko por vua konto desaktivigesis.
+        title: 2FA deaktivigesis
       two_factor_enabled:
         explanation: Ficho facesis da parigita softwaro TOTP bezonesos por eniro.
-        subject: 'Mastodon: Dufaktora yurizo ebligesis'
-        subtitle: Dufaktora yurizo ebligesis por vua konto.
-        title: 2FA ebligesis
+        subject: 'Mastodon: 2-faktorverifiko aktivigesis'
+        subtitle: 2-faktora verifiko aktivigesis por vua konto.
+        title: 2FA aktivigesis
       two_factor_recovery_codes_changed:
         explanation: Antea rigankodexi devalidesis e novo facesis.
         subject: 'Mastodon: 2-faktorrigankodexi rifacesis'
@@ -66,23 +66,23 @@ io:
         subject: Instructioni por riacendar la konto
       webauthn_credential:
         added:
-          explanation: Ca sekuresklefo adjuntesis ad vua konto
+          explanation: Ca sekurklefo insertesis a vua konto
           subject: 'Mastodon: Nova sekurklefo'
-          title: Nova sekuresklefo adjuntesis
+          title: Nova sekurklefo insertesis
         deleted:
           explanation: Ca sekurklefo efacesis de vua konto
           subject: 'Mastodon: Sekurklefo efacesis'
           title: 1 de vua sekurklefi efacesis
       webauthn_disabled:
-        explanation: Yurizo kun sekuresklefi desebligesis por vua konto.
+        explanation: Verifiko per sekuresklefi desaktivigesis por vua konto.
         extra: Eniro esas nun posibla per nur ficho qua facesis da parigita softwaro TOTP.
-        subject: 'Mastodon: Yurizo per sekuresklefi desebligesis'
-        title: Sekuresklefi desebligesis
+        subject: 'Mastodon: Verifiko per sekurklefi deaktivigesis'
+        title: Sekurklefi deaktivigesis
       webauthn_enabled:
-        explanation: Sekuresklefyurizo ebligesis por vua konto.
+        explanation: Sekuresklefoa verifiko aktivigesis por vua konto.
         extra: Vua sekuresklefo povas nun uzesar por eniro.
-        subject: 'Mastodon: Sekuresklefyurizo ebligesis'
-        title: Sekuresklefi ebligesis
+        subject: 'Mastodon: Sekurklefverifiko aktivigesis'
+        title: Sekurklefi aktivigesis
     omniauth_callbacks:
       failure: 'Ni ne povis autentikigar tu per %{kind}: ''%{reason}''.'
       success: Autentikigita senprobleme per %{kind}.
@@ -94,6 +94,11 @@ io:
       updated_not_active: Tua pasvorto redaktesis senprobleme.
     registrations:
       destroyed: Til! Tua konto efacesis senprobleme. Ni esperas rividar tu balde.
+      signed_up: Bonveno! Tu membreskis senprobleme.
+      signed_up_but_inactive: Tu bone membreskis, ma tu ankore ne povas enirar pro ke tua konto ne konfirmesis.
+      signed_up_but_locked: Tu bone membreskis, ma tu ne povas enirar pro ke tua konto extingesis.
+      signed_up_but_pending: Mesajo kun konfirmoligilo sendesis a vua retpostadreso. Pos vu kliktas la ligilo, ni kontrolos vua apliko. Vu notifikesos se ol aprobesas.
+      signed_up_but_unconfirmed: Retpost-mesajo kun tua ligilo por konfirmar tua konto sendesis a tua retpost-adreso. Voluntez uzar ta ligilo por konfirmar tua konto.
       update_needs_confirmation: Tu vone aktualigis tua konto, ma ni bezonas kontrolar tua nova retpost-adreso. Voluntez kontrolar tua retpost-mesaji ed uzar la ligilo por konfirmar tua nova retpost-adreso.
       updated: Tua konto aktualigesis senprobleme.
     sessions:
diff --git a/config/locales/devise.is.yml b/config/locales/devise.is.yml
index a22c6d468f..a045bdd80c 100644
--- a/config/locales/devise.is.yml
+++ b/config/locales/devise.is.yml
@@ -94,6 +94,11 @@ is:
       updated_not_active: Það tókst að breyta lykilorðinu þínu.
     registrations:
       destroyed: Bless! Hætt hefur verið við notandaaðganginn þinn. Við vonumst samt eftir að sjá þig fljótt aftur.
+      signed_up: Velkomin/n! Það tókst að nýskrá þig.
+      signed_up_but_inactive: Þér hefur tekist að nýskrá þig. Hinsvegar gátum við ekki skráð þig inn því notandaaðgangurinn þinn hefur ekki enn verið virkjaður.
+      signed_up_but_locked: Þér hefur tekist að nýskrá þig. Hinsvegar gátum við ekki skráð þig inn því notandaaðgangurinn þinn er læstur.
+      signed_up_but_pending: Skilaboð með staðfestingartengli hafa verið send á tölvupóstfangið þitt. Þegar þú smellir á þennan tengil munum við yfirfara og staðfesta umsóknina þína. Þú færð svo að vita hvort hún verður samþykkt.
+      signed_up_but_unconfirmed: Skilaboð með staðfestingartengli hafa verið send á tölvupóstfangið þitt. Smelltu á þennan tengil til að virkja notandaaðganginn þinn. Skoðaðu í ruslpóstmöppuna þína ef þú færð ekki þessi skilaboð.
       update_needs_confirmation: Þú uppfærðir notandaaðganginn þinn, en við þurfum að sannreyna nýja tölvupóstfangið þitt. Skoðaðu tölvupóstinn þinn og fylgdu tenglinum sem þangað á að berast til að staðfesta tölvupóstfangið þitt. Skoðaðu í ruslpóstmöppuna þína ef þú færð ekki þennan tölvupóst.
       updated: Það tókst að uppfæra notandaaðganginn þinn.
     sessions:
diff --git a/config/locales/devise.it.yml b/config/locales/devise.it.yml
index 8497b369be..8aaea3c15b 100644
--- a/config/locales/devise.it.yml
+++ b/config/locales/devise.it.yml
@@ -94,6 +94,11 @@ it:
       updated_not_active: La tua password è stata modificata correttamente.
     registrations:
       destroyed: Addio! Il tuo profilo è stato eliminato correttamente. Speriamo di rivederti presto.
+      signed_up: Benvenuto! Ti sei registrato con successo.
+      signed_up_but_inactive: Ti sei registrato con successo. Tuttavia, non siamo riusciti a farti accedere perché il tuo profilo non è ancora stato attivato.
+      signed_up_but_locked: Ti sei registrato con successo. Tuttavia, non siamo riusciti a farti accedere perché il tuo profilo è bloccato.
+      signed_up_but_pending: Un messaggio con un link di conferma è stato inviato al tuo indirizzo email. Una volta che avrai cliccato il link, revisioneremo la tua richiesta. Sarai notificato, se verrà approvata.
+      signed_up_but_unconfirmed: Un messaggio con un link di conferma è stato inviato al tuo indirizzo email. Sei pregato di seguire il link per attivare il tuo profilo. Sei pregato di controllare la tua cartella dello spam, se non hai ricevuto quest'email.
       update_needs_confirmation: Hai aggiornato correttamente il tuo profilo, ma dobbiamo verificare il tuo nuovo indirizzo email. Sei pregato di controllare la tua email e di seguire il link di conferma, per confermare il tuo nuovo indirizzo email. Sei pregato di controllare la tua cartella dello spam, se non hai ricevuto quest'email.
       updated: Il tuo profilo è stato aggiornato con successo.
     sessions:
diff --git a/config/locales/devise.ja.yml b/config/locales/devise.ja.yml
index 5836e4e776..40a42de587 100644
--- a/config/locales/devise.ja.yml
+++ b/config/locales/devise.ja.yml
@@ -95,6 +95,11 @@ ja:
     registrations:
       destroyed: アカウントの作成はキャンセルされました。またのご利用をお待ちしています。
       sign_up_failed_because_reach_limit: 制限に到達しているため、アカウントを作成できません。
+      signed_up: アカウントの作成が完了しました。Mastodonへようこそ。
+      signed_up_but_inactive: アカウントの作成が完了しました。しかし、アカウントが有効化されていないためログインできませんでした。
+      signed_up_but_locked: アカウントの作成が完了しました。しかし、アカウントがロックされているためログインできませんでした。
+      signed_up_but_pending: メールアドレスの確認用のリンクが入力したメールアドレスに送信されました。リンクをクリックした後、あなたの申請を審査します。承認されると通知されます。
+      signed_up_but_unconfirmed: メールアドレスの確認用のリンクが入力したメールアドレスに送信されました。メール内のリンクをクリックしてアカウントを有効化してください。
       update_needs_confirmation: アカウント情報の更新に成功しました。しかし、メールアドレスの確認が必要です。送信されたメール内のリンクをクリックしてメールアドレスを確認してください。
       updated: アカウント情報の更新に成功しました。
     sessions:
diff --git a/config/locales/devise.ka.yml b/config/locales/devise.ka.yml
index abce3639d4..3267eb22eb 100644
--- a/config/locales/devise.ka.yml
+++ b/config/locales/devise.ka.yml
@@ -56,6 +56,10 @@ ka:
       updated_not_active: თქვენი პაროლი წარმატებით შეიცვალა.
     registrations:
       destroyed: ნახვამდის! თქვენი ანგარიში წარმატებით გაუქმდა. იმედი გვაქვს ისევ შევხვდებით.
+      signed_up: გამარჯობა! თქვენ წარმატებით დარეგისტრირდით.
+      signed_up_but_inactive: თქვენ წარმატებით დარეგისტრირდით. თუმცა, ავტორიზაცია ვერ შედგა, თქვენი ანგარიში ჯერ არაა გააქტიურებული.
+      signed_up_but_locked: თქვენ წარმატებით დარეგისტრირდით. თუმცა, აცტორიზაცია ვერ შედგა, თქვენი ანგარიში ჩაკეტილია.
+      signed_up_but_unconfirmed: წერილი დამოწმების ბმულით თქვენს ელ-ფოსტაზე გამოგზავნილია. გთხოვთ გაჰყევით ბმულს, რათა გაააქტიუროთ ანგარიში. გთხოვთ შეხედოთ თქვენი სპამის ფოლდერს თუ არ მიიღებთ ამ წერილს.
       update_needs_confirmation: თქვენი ანგარიში წარმატებით განახლდა, მაგრამ გვესაჭიროება თქვენი ელ-ფოსტის მისამართის დამოწმება. შეამოწმეთ ელ-ფოსტა და დასამოწმებლად გადადით მიღებულ ბმულზე. გთხოვთ შეხედოთ თქვენი სპამის ფოლდერს თუ არ მიიღებთ ამ წერილს.
       updated: თქვენი ანგარიში წარმატებით განახლდა.
     sessions:
diff --git a/config/locales/devise.kab.yml b/config/locales/devise.kab.yml
index 1628643bd1..95969fdfd0 100644
--- a/config/locales/devise.kab.yml
+++ b/config/locales/devise.kab.yml
@@ -94,6 +94,11 @@ kab:
       updated_not_active: Awal-ik uffir yettwabeddel mebla ugur.
     registrations:
       destroyed: Ar timlilit! Amiḍan-ik yettwakkes mebla ugur. Nessaram ad k-nwali tikelt-nniḍen.
+      signed_up: Anṣuf! Aqla-k tkecmeḍ.
+      signed_up_but_inactive: Tjerdeḍ akken iwata. maca ur tezmireḍ ara ad teqneḍ akka tura acku amiḍan-ik·im mazal ur yermid ara.
+      signed_up_but_locked: Tjerdeḍ akken iwata. maca ur tezmireḍ ara ad teqneḍ akka tura acku amiḍan-ik·im ittwawḥel.
+      signed_up_but_pending: Aseɣwen n usentem ittwazen ɣer tensa-inen·inem n imayl. ticki tsenndeḍ ɣef useɣwen, ad nsenqed asnas-ik·im. ad ak·am-n-yaweḍ telɣut ma yella ittwaqbel.
+      signed_up_but_unconfirmed: Aseɣwen n usentem ittwazen ɣer tensa-inen·inem n imayl. Ttxil ḍfer aseɣwen i usentem n umiḍan-ik·im. Ttxil wali akaram n ispamen ma ur teṭṭifeḍ ara imayl-agi.
       update_needs_confirmation: Tleqmeḍ akken iwata amiḍan-ik·im, maca nesra ad nsenqed tansa-ik·im imayl tamaynut. Ttxil-k·m senqed imayl-k·m sakin ḍfer aseɣwen i usentem n n tansa imayl tamaynut. Ttxil senqed akaram n spam ma yella ur tufiḍ ara imayl-nni.
       updated: Amiḍan-ik·im yettwalqem akken iwata.
     sessions:
diff --git a/config/locales/devise.kk.yml b/config/locales/devise.kk.yml
index 61e89f6245..06fad30aec 100644
--- a/config/locales/devise.kk.yml
+++ b/config/locales/devise.kk.yml
@@ -69,6 +69,11 @@ kk:
       updated_not_active: Құпиясөзіңіз сәтті өзгертілді.
     registrations:
       destroyed: Сау! Аккаунтыңыз тоқтатылды. Қайтадан ораласыз деп сенеміз.
+      signed_up: Қош келдіңіз! Тіркелу сәтті өтті.
+      signed_up_but_inactive: Тіркелу сәтті аяқталды. Дегенмен, аккаунтыңыз әлі белсендірілмегендіктен, сізге сайтқа кіру мүмкін болмайды.
+      signed_up_but_locked: Тіркелу сәтті аяқталды. Дегенмен, аккаунтыңыз құлыпталғандықтан, сізге сайтқа кіру мүмкін болмайды.
+      signed_up_but_pending: Электрондық пошта мекенжайыңызға растау сілтемесі бар хабарлама жіберілді. Сілтемені басқаннан кейін біз сіздің өтінішіңізді қарастырамыз. Егер ол мақұлданса, сізге хабарланады.
+      signed_up_but_unconfirmed: Растау сілтемесі бар хат электрондық поштаыңызға жіберілді. Аккаунтыңызды белсендіру үшін сілтеме бойынша өтіңіз. Бұл хат келмесе, спам құтысын тексеріңіз.
       update_needs_confirmation: Аккаунтыыызды сәтті жаңарттыңыз, бірақ жаңа электрондық поштаны тексеру қажет. Электрондық поштаңызды тексеріп, жаңа электрондық пошта мекенжайыңызды растаңыз. Бұл электрондық поштаны алмасаңыз, спам қалтаңызды тексеріңіз.
       updated: Аккаунтыңыз сәтті жаңартылды.
     sessions:
diff --git a/config/locales/devise.ko.yml b/config/locales/devise.ko.yml
index b57ef9684c..7ba5ffb9c8 100644
--- a/config/locales/devise.ko.yml
+++ b/config/locales/devise.ko.yml
@@ -94,6 +94,11 @@ ko:
       updated_not_active: 암호를 잘 변경하였습니다.
     registrations:
       destroyed: 안녕히 가세요! 계정이 성공적으로 제거되었습니다. 다시 만나기를 희망합니다.
+      signed_up: 안녕하세요! 성공적으로 가입했습니다.
+      signed_up_but_inactive: 성공적으로 가입했습니다. 하지만 계정이 활성화되지 않았기 때문에 아직 로그인할 수 없습니다.
+      signed_up_but_locked: 성공적으로 가입했습니다. 하지만 계정이 잠겨있기 때문에 아직 로그인할 수 없습니다.
+      signed_up_but_pending: 확인 링크를 포함한 메일이 발송 되었습니다. 링크를 클릭한 이후, 우리가 당신의 신청양식을 검토합니다. 승인이 되면 알림을 발송합니다.
+      signed_up_but_unconfirmed: 확인 링크를 포함한 메일이 발송 되었습니다. 링크를 클릭해 계정을 활성화 하세요. 메일을 받지 못 하신 경우 스팸 폴더를 확인해 주세요.
       update_needs_confirmation: 계정 정보를 성공적으로 업데이트했으며, 새 이메일 주소에 대한 확인이 필요합니다. 이메일로 전달된 링크를 따라 새 이메일을 확인하세요. 메일을 받지 못하셨다면 스팸 폴더를 확인해 주세요.
       updated: 계정 정보가 성공적으로 업데이트 되었습니다.
     sessions:
diff --git a/config/locales/devise.ku.yml b/config/locales/devise.ku.yml
index 4072b1ae94..33a868a8a5 100644
--- a/config/locales/devise.ku.yml
+++ b/config/locales/devise.ku.yml
@@ -88,6 +88,11 @@ ku:
       updated_not_active: Borînpeyva te bi serkeftî hate guhertin.
     registrations:
       destroyed: Xatirê te! Ajimêra te bi serkeftî hate pûçkirin. Em hêvî dikin ku tu di nêzîk de te dîsa bibînin.
+      signed_up: Bi xêr hatî! Te bi serkeftî tomarkirin kir.
+      signed_up_but_inactive: Te bi serkeftî tomarkirin kir. lê piştî ku tu çalak bikî em dikarin ku tu têketinê bikî.
+      signed_up_but_locked: Te bi serkeftî tomarkirin kir. lê ajimêra te girtiye ber wê tu nikarî ku têketinê bikî.
+      signed_up_but_pending: Peyamek bi girêdana pejirandinê ji navnîşana e-nameya te re hate şandin. Piştî ku tu girêdanê bitikînî, em ê serlêdana te binirxînin. Ku were pejirandin tu yê bê agahdarkirin.
+      signed_up_but_unconfirmed: Peyamek bi girêdana pejirandinê ji navnîşana e-nameya te re hate şandin. Ji kerema xwe girêdanê bişopîne da ku tu ajimêra xwe çalak bikî. Ji kerema xwe ku te ev e-name nestand peldanka nexwestî (spam) ya xwe kontrol bike.
       update_needs_confirmation: Te ajimêra xwe bi awayekî serkeftî rojane kir, lê pêdiviya me bi kontrolkirina navnîşana e -nameya te ya nû heye. Ji kerema xwe e -nameya xwe kontrol bike û girêdana piştrastkirinê bişopîne da ku navnîşana e -nameya xwe ya nû piştrast bikî. Ji kerema xwe ku te ev e-name nestand peldanka nexwestî (spam) ya xwe kontrol bike.
       updated: Ajimêra te bi awayekî serkeftî hate rojanekirin.
     sessions:
diff --git a/config/locales/devise.la.yml b/config/locales/devise.la.yml
index 1ca297cf6a..e6d3cb1f78 100644
--- a/config/locales/devise.la.yml
+++ b/config/locales/devise.la.yml
@@ -5,3 +5,8 @@ la:
       send_instructions: Sī adresa tua epistularis in nostra basi datōrum exstat, vinculum ad recuperandam clavem adresa tua epistulari adferētur pauca momenta post. Sī autem hanc epistulam nōn recēpistī, rogāmus ut scrūtināriōnem spurcāriī tuī faciās.
     registrations:
       destroyed: Vale! Ratio tua succēssu cancellāta est. Spērāmus tē mox iterum vidēre.
+      signed_up: Bene vēnistī! Tē adscrīpsistī fēlīciter.
+      signed_up_but_inactive: Tē adscrīpsistī fēlīciter. Tamen, tibi nōn licuit intrāre, quia ratiō tua adhūc nōn est ācta.
+      signed_up_but_locked: Tē adscrīpsistī fēlīciter. Tamen, tibi nōn licuit intrāre, quia ratiō tua clāusa est.
+      signed_up_but_pending: Nūntius cum nexū cōnfirmātiōnis ad tuam īnscrīptiōnem ēlectronicam missus est. Posteā, cum nexum premseris, rogātiōnem tuam recēnseāmus. Sī probāta erit, certior fēceris.
+      signed_up_but_unconfirmed: Nūntius cum nexū cōnfirmātiōnis ad tuam īnscrīptiōnem ēlectronicam missus est. Quaesō, nexum sequere ut ratiōnem tuam āctīvēs. Quaesō, scrīnium spammae īnspice sī hunc nūntium nōn accēpistī.
diff --git a/config/locales/devise.lad.yml b/config/locales/devise.lad.yml
index 45e4750450..7d447140f4 100644
--- a/config/locales/devise.lad.yml
+++ b/config/locales/devise.lad.yml
@@ -94,6 +94,11 @@ lad:
       updated_not_active: Tu kod se tiene trokado kon reushita.
     registrations:
       destroyed: Tu kuento a sido efasado kon reushita. Asperamos verte de muevo pronto.
+      signed_up: Bienvenido! Te tienes enrejistrado djustamente.
+      signed_up_but_inactive: Te tienes enrejistrado kon reushita. Entanto, no se pudio inisyar sesyon porke tu kuento ainda no esta aktivado.
+      signed_up_but_locked: Te tienes enrejistrado kon reushita. Entanto, no pudites konektarte kon tu kuento porke tu kuento esta blokado.
+      signed_up_but_pending: Un mesaj kon un atadijo de konfirmasyon a sido enviado a tu adreso de posta elektronika. Dempues de klikar en el atadijo, revizaremos tu solisitud. Seras avizado si se acheta.
+      signed_up_but_unconfirmed: Un mesaj kon un atadijo de konfirmasyon a sido enviado a tu adreso de posta elektronika. Por favor, sigue el atadijo para aktivar tu kuento. Por favor, komprova tu kuti de posta spam si no risives akeya posta elektronika.
       update_needs_confirmation: Tienes aktualizado tu kuento kon reushita, pero kale verifikar tu muevo adreso de posta elektronika. Por favor, komprova tu posta elektronika i sige el atadijo de konfirmasyon para konfirmar tu muevo adreso de posta elektronika. Por favor, komprova tu kuti de posta spam si no risives akeya posta elektronika.
       updated: Tu kuento se aktualizo kon reushita.
     sessions:
diff --git a/config/locales/devise.lt.yml b/config/locales/devise.lt.yml
index 1eb5692494..4c54c50e4f 100644
--- a/config/locales/devise.lt.yml
+++ b/config/locales/devise.lt.yml
@@ -94,6 +94,11 @@ lt:
       updated_not_active: Tavo slaptažodis buvo sėkmingai pakeistas.
     registrations:
       destroyed: Iki pasimatymo! Tavo paskyra sėkmingai atšaukta. Tikimės, kad netrukus vėl pamatysime tave.
+      signed_up: Sveiki atvykę! Sėkmingai užsiregistravote.
+      signed_up_but_inactive: Sėkmingai užsiregistravote. Tačiau negalėjome jūsų prijungti, nes jūsų paskyra dar nėra aktyvuota.
+      signed_up_but_locked: Sėkmingai užsiregistravote. Tačiau negalėjome jūsų prijungti, nes jūsų paskyra yra užrakinta.
+      signed_up_but_pending: Į jūsų el. pašto adresą buvo išsiųstas laiškas su patvirtinimo nuoroda. Paspaudę nuorodą, peržiūrėsime jūsų paraišką. Jums bus pranešta, jei ji bus patvirtinta.
+      signed_up_but_unconfirmed: Į jūsų el. pašto adresą buvo išsiųstas laiškas su patvirtinimo nuoroda. Sekite nuorodą, kad aktyvuotumėte savo paskyrą. Jei negavote šio el. laiško, patikrinkite brukalo (kartais vadinamo šlamšto) aplanką.
       update_needs_confirmation: Sėkmingai atnaujinai savo paskyrą, bet mums reikia patvirtinti naująjį el. pašto adresą. Patikrink savo el. paštą ir sek patvirtinimo nuorodą, kad patvirtintum savo naują el. pašto adresą. Jei negavai šio el. laiško, patikrink šlamšto aplanką.
       updated: Tavo paskyra buvo sėkmingai atnaujinta.
     sessions:
diff --git a/config/locales/devise.lv.yml b/config/locales/devise.lv.yml
index e60f8fa62e..2cae3fa048 100644
--- a/config/locales/devise.lv.yml
+++ b/config/locales/devise.lv.yml
@@ -2,9 +2,9 @@
 lv:
   devise:
     confirmations:
-      confirmed: Tava e-pasta adrese tika sekmīgi apstiprināta.
-      send_instructions: Pēc dažām minūtēm saņemsi e-pasta ziņojum ar norādēm, kā apstiprināt savu e-pasta adresi. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
-      send_paranoid_instructions: Ja Tava e-pasta adrese ir mūsu datubāzē, pēc dažām minūtēm saņemsi e-pasta ziņojumu ar norādēm, kā apstiprināt savu e-pasta adresi. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
+      confirmed: Tava e-pasta adrese ir veiksmīgi apstiprināta.
+      send_instructions: Pēc dažām minūtēm saņemsi e-pastu ar norādījumiem, kā apstiprināt savu e-pasta adresi. Lūdzu, pārbaudi spama mapi, ja neesi saņēmis šo e-pastu.
+      send_paranoid_instructions: Ja tava e-pasta adrese ir mūsu datu bāzē, pēc dažām minūtēm saņemsi e-pastu ar norādījumiem, kā apstiprināt savu e-pasta adresi. Lūdzu, pārbaudi spama mapi, ja neesi saņēmis šo e-pastu.
     failure:
       already_authenticated: Tu jau esi pieteicies.
       inactive: Tavs konts vēl nav aktivizēts.
@@ -58,10 +58,10 @@ lv:
         subtitle: Tavam kontam tika iespējota divpakāpju autentifikācija.
         title: 2FA iespējota
       two_factor_recovery_codes_changed:
-        explanation: Iepriekšējie atkopes kodi ir padarīt nederīgi, un ir izveidoti jauni.
-        subject: 'Mastodon: atkārtoti izveidoti divpakāpju atkopes kodi'
-        subtitle: Iepriekšējie atkopes kodi tika padarīti par nederīgiem, un tika izveidoti jauni.
-        title: 2FA atkopes kodi nomainīti
+        explanation: Iepriekšējie atkopšanas kodi ir atzīti par nederīgiem un ģenerēti jauni.
+        subject: 'Mastodon: Divfaktoru atkopšanas kodi pārģenerēti'
+        subtitle: Iepriekšējie atkopšanas kodi tika padarīti par nederīgiem, un tika izveidoti jauni.
+        title: 2FA atkopšanas kodi mainīti
       unlock_instructions:
         subject: 'Mastodon: Norādījumi atbloķēšanai'
       webauthn_credential:
@@ -85,25 +85,30 @@ lv:
         title: Drošības atslēgas iespējotas
     omniauth_callbacks:
       failure: Nevarēja autentificēt tevi no %{kind}, jo "%{reason}".
-      success: Sekmīgi autentificēts no %{kind} konta.
+      success: Veiksmīgi autentificēts no %{kind} konta.
     passwords:
       no_token: Tu nevari piekļūt šai lapai, ja neesi saņēmis paroles atiestatīšanas e-pasta ziņojumu. Ja ienāci no paroles atiestatīšanas e-pasta, lūdzu, pārliecinies, vai izmanto visu norādīto URL.
-      send_instructions: Ja Tava e-pasta adrese ir mūsu datubāzē, pēc dažām minūtēm savā e-pasta adresē saņemsi paroles atkopes saiti. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
-      send_paranoid_instructions: Ja Tava e-pasta adrese ir mūsu datubāzē, pēc dažām minūtēm savā e-pasta adresē saņemsi paroles atkopes saiti. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
-      updated: Tava parole tika sekmīgi nomainīta. Tagad esi pieteicies.
-      updated_not_active: Tava parole tika sekmīgi nomainīta.
+      send_instructions: Ja tava e-pasta adrese ir mūsu datu bāzē, pēc dažām minūtēm uz savu e-pasta adresi saņemsi paroles atkopšanas saiti. Lūdzu, pārbaudi spama mapi, ja neesi saņēmis šo e-pastu.
+      send_paranoid_instructions: Ja tava e-pasta adrese ir mūsu datu bāzē, pēc dažām minūtēm uz savu e-pasta adresi saņemsi paroles atkopšanas saiti. Lūdzu, pārbaudi spama mapi, ja neesi saņēmis šo e-pastu.
+      updated: Tava parole tika veiksmīgi nomainīta. Tagad esi pieteicies.
+      updated_not_active: Tava parole ir veiksmīgi nomainīta.
     registrations:
-      destroyed: Visu labu! Tavs konts ir sekmīgi atcelts. Mēs ceram Tevi drīz atkal redzēt.
-      update_needs_confirmation: Tu sekmīgi atjaunināji savu kontu, taču mums ir jāapliecina Tava jaunā e-pasta adrese. Lūgums pārbaudīt savu e-pastu un sekot apstiprinājuma saitei, lai apstiprinātu savu jauno e-pasta adresi. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
-      updated: Tavs konts tika sekmīgi atjaunināts.
+      destroyed: Visu labu! Tavs konts ir veiksmīgi atcelts. Mēs ceram tevi drīz atkal redzēt.
+      signed_up: Laipni lūdzam! Reģistrēšanās bija sekmīga.
+      signed_up_but_inactive: Reģistrēšanās bija sekmīga. Tomēr mēs nevarējām Tevi pieteikt, jo Tavs konts vēl nav aktivēts.
+      signed_up_but_locked: Reģistrēšanās bija sekmīga. Tomēr mēs nevarējām Tevi pieteikt, jo Tavs konts ir slēgts.
+      signed_up_but_pending: Ziņojums ar apstiprinājuma saiti tika nosūtīts uz Tavu e-pasta adresi. Pēc tam, kad atvērsi saiti, mēs izskatīsim Tavu pieteikumu. Tev tiks paziņots, ja tas tiks apstiprināts.
+      signed_up_but_unconfirmed: Ziņojums ar apstiprinājuma saiti tika nosūtīts uz Tavu e-pasta adresi. Lūgums sekot saitei, lai aktivētu savu kontu. Lūgums pārbaudīt savu mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
+      update_needs_confirmation: Tu veiksmīgi atjaunināji savu kontu, taču mums ir jāverificē teva jaunā e-pasta adrese. Lūdzu, pārbaudi savu e-pastu un seko apstiprinājuma saitei, lai apstiprinātu savu jauno e-pasta adresi. Lūdzu, pārbaudi spama mapi, ja neesi saņēmis šo e-pastu.
+      updated: Tavs konts ir veiksmīgi atjaunināts.
     sessions:
-      already_signed_out: Sekmīgi izrakstījies.
-      signed_in: Sekmīgi pierakstījies.
-      signed_out: Sekmīgi izrakstījies.
+      already_signed_out: Veiksmīgi izrakstījies.
+      signed_in: Veiksmīgi pieteicies.
+      signed_out: Veiksmīgi izrakstījies.
     unlocks:
-      send_instructions: Pēc dažām minūtēm Tu saņemsi e-pasta ziņojumu ar norādēm, kā atslēgt savu kontu. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
-      send_paranoid_instructions: Ja Tavs konts pastāv, dažu minūšu laikā saņemsi e-pasta ziņojumu ar norādēm, kā to atslēgt. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
-      unlocked: Konts tika sekmīgi atslēgts. Lūgums pieteikties, lai turpinātu.
+      send_instructions: Pēc dažām minūtēm tu saņemsi e-pastu ar norādījumiem, kā atbloķēt savu kontu. Lūdzu, pārbaudi spama mapi, ja neesi saņēmis šo e-pastu.
+      send_paranoid_instructions: Ja tavs konts eksistē, dažu minūšu laikā tu saņemsi e-pastu ar norādījumiem, kā to atbloķēt. Lūdzu, pārbaudi spama mapi, ja neesi saņēmis šo e-pastu.
+      unlocked: Konts tika veiksmīgi atbloķēts. Lūgums pieteikties, lai turpinātu.
   errors:
     messages:
       already_confirmed: jau tika apstiprināts, lūgums mēģināt pieteikties
diff --git a/config/locales/devise.ms.yml b/config/locales/devise.ms.yml
index 90d59798cb..da18d1896e 100644
--- a/config/locales/devise.ms.yml
+++ b/config/locales/devise.ms.yml
@@ -84,6 +84,11 @@ ms:
       updated_not_active: Kata laluan anda telah berjaya ditukar.
     registrations:
       destroyed: Selamat tinggal! Akaun anda telah berjaya dibatalkan. Kami harap berjumpa lagi.
+      signed_up: Selamat datang! Anda telah berjaya mendaftar.
+      signed_up_but_inactive: Anda telah berjaya mendaftar. Namun, kami tidak dapat mendaftar masuk anda kerana akaun anda belum diaktifkan.
+      signed_up_but_locked: Anda telah berjaya mendaftar. Walau bagaimanapun, kami tidak dapat melog masuk anda kerana akaun anda dikunci.
+      signed_up_but_pending: Mesej dengan pautan pengesahan telah dihantar ke alamat e-mel anda. Selepas anda mengklik pautan, kami akan menyemak permohonan anda. Anda akan dimaklumkan jika ia diluluskan.
+      signed_up_but_unconfirmed: Mesej dengan pautan pengesahan telah dihantar ke alamat e-mel anda. Sila ikuti pautan untuk mengaktifkan akaun anda. Sila semak folder spam anda jika anda tidak menerima e-mel ini.
       update_needs_confirmation: Anda berjaya mengemas kini akaun anda, tetapi kami perlu mengesahkan alamat e-mel baharu anda. Sila semak e-mel anda dan ikuti pautan pengesahan untuk mengesahkan alamat e-mel baharu anda. Sila semak folder spam anda jika anda tidak menerima e-mel ini.
       updated: Akaun anda telah berjaya dikemas kini.
     sessions:
diff --git a/config/locales/devise.my.yml b/config/locales/devise.my.yml
index 4eff1ddd6a..126ecd7404 100644
--- a/config/locales/devise.my.yml
+++ b/config/locales/devise.my.yml
@@ -84,6 +84,11 @@ my:
       updated_not_active: သင့်စကားဝှက်ကို အောင်မြင်စွာ ပြောင်းလဲပြီးပါပြီ။
     registrations:
       destroyed: တာ့တာ၊ သင့်အကောင့်ကို ပယ်ဖျက်လိုက်ပါပြီ။ မကြာခင် ပြန်တွေ့နိုင်ရန်မျှော်လင့်မိပါသည်။
+      signed_up: ကြိုဆိုပါသည်။ သင်သည် အောင်မြင်စွာ စာရင်းသွင်းပြီးပါပြီ။
+      signed_up_but_inactive: သင်သည် အောင်မြင်စွာ အကောင့်ဖွင့်ပြီးပါပြီ။ သို့သော်လည်း သင့်အကောင့် အတည်မပြုရသေးသောကြောင့် ဝင်၍မရနိုင်ပါ။
+      signed_up_but_locked: သင်သည် အောင်မြင်စွာ အကောင့်ဖွင့်ပြီးပါပြီ။ သို့သော်လည်း သင့်အကောင့်လော့ခ်ချထားသောကြောင့် လက်မှတ်ထိုးဝင်၍မရနိုင်ပါ။
+      signed_up_but_pending: အတည်ပြုချက်လင့်ခ်ပါရှိသော စာတိုတစ်စောင်ကို သင့်အီးမေးလ်လိပ်စာသို့ ပေးပို့လိုက်ပါပြီ။ လင့်ခ်ကို နှိပ်ပြီးနောက် သင့်အက်ပလီကေးရှင်းကို ကျွန်ုပ်တို့ ပြန်လည်စစ်ဆေးပါမည်။ အတည်ပြုပြီးပါက သင့်ထံ အကြောင်းကြားပါမည်။
+      signed_up_but_unconfirmed: အကောင့်ဖွင့်ရန်အတွက် လင့်ခ်တစ်ခုကို ထောက်ခံချက်အီးမေးလ််နှင့်အတူပို့ပေးလိုက်ပါပြီ။ သင့်အကောင့်ဖွင့်နိုင်ရန် လင့်ခ်ကိုဝင်ရောက်ပေးပါ။ အီးမေးလ်မတွေ့ပါက စပမ်းဖိုင်တွဲထဲတွင်စစ်ကြည့်ပါ။
       update_needs_confirmation: သင့်အကောင့်ကို မွမ်းမံပြင်ဆင်ထားသော်လည်း သင့်အီးမေးလ်လိပ်စာအသစ်ကို အတည်ပြုရန် လိုအပ်ပါသည်။ ကျေးဇူးပြု၍ သင့်အီးမေးလ်ကို စစ်ဆေးပြီး သင့်အီးမေးလ်လိပ်စာအသစ်ကို အတည်ပြုရန်အတွက် အတည်ပြုလင့်ခ်အား နှိပ်ပါ။ ဤအီးမေးလ်ကို မရရှိပါက သင်၏ Spam ဖိုင်ကို စစ်ဆေးပါ။
       updated: သင့်အကောင့်အားအောင်မြင်စွာ ပြင်ဆင်ပြီးပါပြီ။
     sessions:
diff --git a/config/locales/devise.nan.yml b/config/locales/devise.nan.yml
index 6f47b4f652..dce568bfcc 100644
--- a/config/locales/devise.nan.yml
+++ b/config/locales/devise.nan.yml
@@ -13,3 +13,9 @@ nan:
         title: 2FA啟用ah
       two_factor_recovery_codes_changed:
         title: 2FA驗證碼改ah
+    registrations:
+      signed_up: 歡迎!Lí成功註冊ah。
+      signed_up_but_inactive: Lí成功註冊ah。M̄-kú,你ê口座iáu-buē啟用,guán bē-tàng hōo lí登入。
+      signed_up_but_locked: Lí成功註冊ah。M̄-kú,你ê口座hőng鎖定,guán bē-tàng hōo lí登入。
+      signed_up_but_pending: 有確認連結ê訊息,已經寄kàu lí ê電子phue箱ah。Tshi̍h連結了後,guán ē審查你ê申請。Nā核准就通知lí。
+      signed_up_but_unconfirmed: 有確認連結ê訊息,已經寄kàu lí ê電子phue箱ah。請跟tuè連結來啟用口座。Nā無收著,請檢查lí ê pùn-sò批資料giap-á。
diff --git a/config/locales/devise.nl.yml b/config/locales/devise.nl.yml
index a80b5a7bfa..662401a57a 100644
--- a/config/locales/devise.nl.yml
+++ b/config/locales/devise.nl.yml
@@ -94,6 +94,11 @@ nl:
       updated_not_active: Jouw wachtwoord is gewijzigd.
     registrations:
       destroyed: Jouw account is succesvol verwijderd. Wellicht tot ziens.
+      signed_up: Je bent geregistreerd.
+      signed_up_but_inactive: Je bent geregistreerd. Je kon alleen niet automatisch ingelogd worden omdat jouw account nog niet geactiveerd is.
+      signed_up_but_locked: Je bent ingeschreven. Je kon alleen niet automatisch ingelogd worden omdat jouw account is opgeschort.
+      signed_up_but_pending: Er is een bericht met een bevestigingslink naar jouw e-mailadres verzonden. Nadat je op deze link hebt geklikt nemen we jouw aanvraag in behandeling. Je wordt op de hoogte gesteld wanneer deze wordt goedgekeurd.
+      signed_up_but_unconfirmed: Je ontvangt via e-mail instructies hoe je jouw account kunt activeren. Kijk tussen je spam wanneer niks werd ontvangen.
       update_needs_confirmation: Je hebt je e-mailadres succesvol gewijzigd, maar we moeten je nieuwe mailadres nog bevestigen. Controleer jouw e-mail en klik op de link in de mail om jouw e-mailadres te bevestigen. Kijk tussen je spam wanneer niks werd ontvangen.
       updated: Jouw accountgegevens zijn opgeslagen.
     sessions:
diff --git a/config/locales/devise.nn.yml b/config/locales/devise.nn.yml
index cdf358ce83..e9e8e1f6e0 100644
--- a/config/locales/devise.nn.yml
+++ b/config/locales/devise.nn.yml
@@ -94,6 +94,11 @@ nn:
       updated_not_active: Passordet ditt er endra.
     registrations:
       destroyed: Ha det! Kontoen din er sletta. Me vonar å sjå deg igjen snart.
+      signed_up: Velkomen! No er du registrert.
+      signed_up_but_inactive: Du har registrert deg, men me kunne ikkje logga deg inn fordi kontoen din er ikkje aktivert enno.
+      signed_up_but_locked: Du har registrert deg, men me kunne ikkje logga deg inn fordi kontoen din er låst.
+      signed_up_but_pending: Ei melding med ei stadfestingslenke har vorte sendt til epostadressa di. Når du klikkar på lenka skal me sjå gjennom søknaden din. Du får ei melding om den vert godkjend.
+      signed_up_but_unconfirmed: Ei melding med ei lenke for å stadfeste kontoen har vorte sendt til epostadressa di. Klikk på lenka for å aktivere kontoen. Sjekk søppelpostmappa dersom du ikkje har fått eposten.
       update_needs_confirmation: Du har oppdatert kontoen din, men me må stadfesta den nye epostadressa. Sjekk innboksen og fylg lenka for å stadfeste adressa di. Sjekk søppelpostmappa dersom du ikkje har fått denne eposten.
       updated: Kontoen har vorte oppdatert.
     sessions:
diff --git a/config/locales/devise.no.yml b/config/locales/devise.no.yml
index b92e7e8aa3..2cd433c6e0 100644
--- a/config/locales/devise.no.yml
+++ b/config/locales/devise.no.yml
@@ -94,6 +94,11 @@
       updated_not_active: Passordet ditt er endret.
     registrations:
       destroyed: Ha det! Kontoen din er avsluttet. Vi håper å se deg igjen snart.
+      signed_up: Velkommen! Registreringen var vellykket.
+      signed_up_but_inactive: Registreringen var vellykket. Vi kunne dessverre ikke logge deg inn fordi kontoen din ennå ikke har blitt aktivert.
+      signed_up_but_locked: Registreringen var vellykket. Vi kunne dessverre ikke logge deg inn fordi kontoen din har blitt låst.
+      signed_up_but_pending: En melding med en bekreftelseslenke er sendt til din e-postadresse. Etter at du har klikket på lenken, vil vi gjennomgå søknaden din. Du vil bli varslet hvis den blir godkjent.
+      signed_up_but_unconfirmed: En melding med en bekreftelseslenke har blitt sendt til din e-postadresse. Klikk på lenken i e-posten for å aktivere kontoen din. Sjekk spam-mappen din hvis du ikke mottok e-posten.
       update_needs_confirmation: Du har oppdatert kontoen din, men vi må bekrefte din nye e-postadresse. Sjekk e-posten din og følg bekreftelseslenken for å bekrefte din nye e-postadresse. Sjekk spam-mappen din hvis du ikke mottok e-posten.
       updated: Kontoen din ble oppdatert.
     sessions:
diff --git a/config/locales/devise.oc.yml b/config/locales/devise.oc.yml
index a039975ef5..2640777e00 100644
--- a/config/locales/devise.oc.yml
+++ b/config/locales/devise.oc.yml
@@ -84,6 +84,11 @@ oc:
       updated_not_active: Vòstre senhal es ben estat cambiat.
     registrations:
       destroyed: Adieu-siatz ! Vòstra inscripcion es estada anullada amb succès. Esperem vos tornar veire lèu.
+      signed_up: La benvenguda ! Sètz ben marcat al malhum.
+      signed_up_but_inactive: Sètz ben marcat. Pasmens, avèm pas pogut vos connectar perque vòstre compte es pas encara validat.
+      signed_up_but_locked: Sètz ben marcat. Pasmens, avèm pas pogut vos connectar perque vòstre compte es pas encara blocat.
+      signed_up_but_pending: Un messatge amb un ligam de confirmacion es estat enviat a vòstra adreça electronica. Aprèp aver clicat lo ligam, revisarem vòstra demanda. Seretz avisat se’s aprovada.
+      signed_up_but_unconfirmed: Un messatge amb un ligam de confirmacion es estat enviat a vòstra adreça de corrièl. Clicatz sul ligam per activar vòstre compte. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
       update_needs_confirmation: Avètz ben mes a jorn vòstre compte, mai nos cal verificar vòstra nòva adreça de corrièl. Mercés de verificar vòstres messatges e clicar sul ligam de confirmacion per confirmar vòstra nòva adreça de corrièl. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
       updated: Vòstre compte es estat mes a jorn amb succès.
     sessions:
diff --git a/config/locales/devise.pl.yml b/config/locales/devise.pl.yml
index 093ea6961d..f34fd04633 100644
--- a/config/locales/devise.pl.yml
+++ b/config/locales/devise.pl.yml
@@ -94,6 +94,11 @@ pl:
       updated_not_active: Twoje hasło zostało zmienione.
     registrations:
       destroyed: Do zobaczenia! Twoje konto zostało zawieszone. Mamy jednak nadzieję, że do nas wrócisz.
+      signed_up: Witamy! Twoje konto zostało utworzone.
+      signed_up_but_inactive: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto nie zostało jeszcze aktywowane.
+      signed_up_but_locked: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto jest zablokowane.
+      signed_up_but_pending: Na Twój adres e-mail została wysłana wiadomosć z odnośnikiem potwierdzającym. Po kliknięciu w odnośnik, przejrzymy Twoje podanie. Zostaniesz poinformowany(-a), gdy zostanie ono przyjęte.
+      signed_up_but_unconfirmed: Na Twój adres e-mail została wysłana wiadomosć z odnośnikiem potwierdzającym. Kliknij w odnośnik, aby aktywować konto. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem.
       update_needs_confirmation: Konto zostało zaktualizowane, musimy jednak zweryfikować Twój nowy adres e-mail. Została na niego wysłana wiadomość z odnośnikiem potwierdzającym. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem.
       updated: Konto zostało zaktualizowane.
     sessions:
diff --git a/config/locales/devise.pt-BR.yml b/config/locales/devise.pt-BR.yml
index aa1190dfd0..8f504362de 100644
--- a/config/locales/devise.pt-BR.yml
+++ b/config/locales/devise.pt-BR.yml
@@ -14,7 +14,7 @@ pt-BR:
       not_found_in_database: "%{authentication_keys} ou senha inválida."
       omniauth_user_creation_failure: Erro ao criar uma conta para esta identidade.
       pending: Sua conta está sendo revisada.
-      timeout: Sua sessão expirou. Por favor, entre novamente para continuar.
+      timeout: Sua sessão expirou. Faça ‘login’ novamente para continuar.
       unauthenticated: Você precisa entrar ou criar uma conta antes de continuar.
       unconfirmed: Você precisa confirmar o seu endereço de e-mail antes de continuar.
     mailer:
@@ -48,12 +48,12 @@ pt-BR:
         subject: 'Mastodon: Instruções para alterar senha'
         title: Redefinir senha
       two_factor_disabled:
-        explanation: Agora você pode entrar usando apenas seu e-mail e senha.
+        explanation: O ‘login’ agora é possível usando apenas o endereço eletrônico e senha.
         subject: 'Mastodon: Autenticação de dois fatores desativada'
         subtitle: A autenticação de dois fatores foi desativada.
         title: 2FA desativada
       two_factor_enabled:
-        explanation: Um código de autenticação de dois fatores será necessário para entrar em sua conta.
+        explanation: Será necessário um código gerado pelo aplicativo de autenticação TOTP para fazer login.
         subject: 'Mastodon: Autenticação de dois fatores ativada'
         subtitle: A autenticação de dois fatores foi ativada para sua conta.
         title: 2FA ativada
@@ -75,11 +75,11 @@ pt-BR:
           title: Uma das suas chaves de segurança foi excluída
       webauthn_disabled:
         explanation: A autenticação por chaves de segurança foi desativada para sua conta.
-        extra: Agora você pode entrar usando apenas o código de autenticação de dois fatores.
+        extra: Agora você pode fazer login usando apenas o código gerado pelo aplicativo de autenticação TOTP.
         subject: 'Mastodon: Autenticação por chaves de segurança desativada'
         title: Chaves de segurança desativadas
       webauthn_enabled:
-        explanation: A autenticação por chave de segurança foi ativada.
+        explanation: A autenticação por chave de segurança foi ativada para sua conta.
         extra: Sua chave de segurança agora pode ser usada para ‘login’.
         subject: 'Mastodon: Autenticação por chaves de segurança ativada'
         title: Chaves de segurança ativadas
@@ -94,6 +94,11 @@ pt-BR:
       updated_not_active: Sua senha foi alterada com sucesso.
     registrations:
       destroyed: Adeus! Sua conta foi cancelada. Talvez um dia possamos nos ver de novo.
+      signed_up: Boas vindas! Conta criada.
+      signed_up_but_inactive: Conta criada! Agora você deve confirmá-la.
+      signed_up_but_locked: Você se cadastrou com sucesso. Contudo, não pudemos entrar na sua conta porque ela está bloqueada.
+      signed_up_but_pending: Um e-mail com um link de confirmação foi enviado para o seu endereço de e-mail. Após você entrar no link, revisaremos sua solicitação e você será notificado caso seja aprovado.
+      signed_up_but_unconfirmed: Um e-mail com instruções para confirmar o seu endereço de e-mail foi enviado. Verifique sua caixa de spam caso ainda não o tenha recebido.
       update_needs_confirmation: Você alterou seu endereço de e-mail ou sua senha, porém é preciso confirmar a alteração. Entre no link que foi enviado para o seu novo endereço de e-mail e verifique sua caixa de spam caso ainda não o tenha recebido.
       updated: Sua conta foi alterada com sucesso.
     sessions:
diff --git a/config/locales/devise.pt-PT.yml b/config/locales/devise.pt-PT.yml
index 9660c3edb0..5a1defba3a 100644
--- a/config/locales/devise.pt-PT.yml
+++ b/config/locales/devise.pt-PT.yml
@@ -94,6 +94,11 @@ pt-PT:
       updated_not_active: A tua palavra-passe foi alterada.
     registrations:
       destroyed: Adeus! A tua conta foi cancelada. Esperamos ver-te em breve.
+      signed_up: Bem-vindo! A sua conta foi registada com sucesso.
+      signed_up_but_inactive: Registou-se com sucesso. No entanto, não foi possível iniciar sessão porque a sua conta ainda não está ativada.
+      signed_up_but_locked: Registou-se com sucesso. No entanto, não foi possível iniciar sessão porque a sua conta está bloqueada.
+      signed_up_but_pending: Foi enviada uma mensagem com uma hiperligação de confirmação para o seu endereço de e-mail. Depois de clicar na hiperligação, analisaremos a sua candidatura. Será notificado se for aprovado.
+      signed_up_but_unconfirmed: Foi enviada para o seu endereço de e-mail uma mensagem com uma hiperligação de confirmação. Siga a hiperligação para ativar a sua conta. Verifique a sua pasta de spam se não recebeu esta mensagem de e-mail.
       update_needs_confirmation: Atualizaste a tua conta com sucesso, mas temos de verificar o teu novo endereço de e-mail. Verifica o teu e-mail e segue a hiperligação de confirmação para confirmares o teu novo endereço de e-mail. Verifica a tua pasta de spam se não recebeste esta mensagem de correio eletrónico.
       updated: A tua conta foi corretamente atualizada.
     sessions:
diff --git a/config/locales/devise.ro.yml b/config/locales/devise.ro.yml
index 1548dc36f3..f570f77e1d 100644
--- a/config/locales/devise.ro.yml
+++ b/config/locales/devise.ro.yml
@@ -94,6 +94,11 @@ ro:
       updated_not_active: Parola dvs. a fost schimbată cu succes.
     registrations:
       destroyed: La revedere! Contul dvs. a fost anulat cu succes. Sperăm să vă vedem din nou în curând.
+      signed_up: Bine ați venit! V-ați înregistrat cu succes.
+      signed_up_but_inactive: V-ați înregistrat cu succes. Cu toate acestea, nu vă putem conecta deoarece contul dvs. nu este încă activat.
+      signed_up_but_locked: V-ați înregistrat cu succes. Cu toate acestea, nu vă putem conecta deoarece contul dvs. este blocat.
+      signed_up_but_pending: Un mesaj cu un link de confirmare a fost trimis la adresa dvs. de e-mail. După ce faceți clic pe link, vă vom revizui cererea. Veți fi notificat dacă este aprobat.
+      signed_up_but_unconfirmed: Un mesaj cu un link de confirmare a fost trimis la adresa dvs. de e-mail. Vă rugăm să urmați link-ul pentru a vă activa contul. Vă rugăm să verificați folderul spam dacă nu ați primit acest e-mail.
       update_needs_confirmation: V-ați actualizat contul cu succes, dar trebuie să verificăm noua dvs. adresă de e-mail. Vă rugăm să verificați adresa de e-mail și să urmați link-ul de confirmare pentru a confirma noua dvs. adresă de e-mail. Vă rugăm să verificați dosarul spam dacă nu ați primit acest e-mail.
       updated: Contul dvs. a fost actualizat cu succes.
     sessions:
diff --git a/config/locales/devise.ru.yml b/config/locales/devise.ru.yml
index 853381801a..9e7ace6433 100644
--- a/config/locales/devise.ru.yml
+++ b/config/locales/devise.ru.yml
@@ -94,6 +94,11 @@ ru:
       updated_not_active: Ваш пароль был успешно изменен.
     registrations:
       destroyed: До свидания! Ваша учётная запись была успешно удалена. Мы надеемся скоро увидеть вас снова.
+      signed_up: Добро пожаловать! Вы успешно зарегистрировались.
+      signed_up_but_inactive: Вы успешно зарегистрировались. Тем не менее, мы не можем авторизовать вас, поскольку ваша учётная запись еще не активирована.
+      signed_up_but_locked: Вы успешно зарегистрировались. Тем не менее, мы не можем авторизовать вас, поскольку ваша учётная запись заблокирована.
+      signed_up_but_pending: На ваш e-mail адрес было отправлено письмо с ссылкой для подтверждения. После перехода по ней, мы начнём рассматривать вашу заявку. В случае подтверждения, мы вас оповестим.
+      signed_up_but_unconfirmed: Сообщение со ссылкой для подтверждения было выслано на ваш адрес e-mail. Пожалуйста, пройдите по ссылке для активации вашей учётной записи.
       update_needs_confirmation: Данные учётной записи обновлены, но нам необходимо подтвердить ваш новый e-mail адрес. Проверьте почту и перейдите по ссылке из письма. Если оно не приходит, проверьте папку «спам».
       updated: Ваша учётная запись успешно обновлена.
     sessions:
diff --git a/config/locales/devise.sc.yml b/config/locales/devise.sc.yml
index 6926a85c42..dde8fd229e 100644
--- a/config/locales/devise.sc.yml
+++ b/config/locales/devise.sc.yml
@@ -84,6 +84,11 @@ sc:
       updated_not_active: Sa crae tua est istada mudada.
     registrations:
       destroyed: A si bìdere! Su contu tuo est istadu cantzelladu. Isperamus de ti torrare a bìdere chitzi.
+      signed_up: Registratzione curreta. Ti donamus sa benebènnida.
+      signed_up_but_inactive: Registratzione curreta. Mancari de aici, si no ti faghimus intrare est ca su contu tuo no est ancora ativu.
+      signed_up_but_locked: Registratzione curreta. Mancari de aici, si no ti faghimus intrare est ca su contu tuo est blocadu.
+      signed_up_but_pending: Unu ligàmene de cunfirma est istadu imbiadu a s'indiritzu tuo de posta eletrònica. A pustis chi incarcas in su ligàmene, amus a revisionare sa dimanda tua. Si aprovada, t'at a arribare una notìfica.
+      signed_up_but_unconfirmed: Unu ligàmene de cunfirma est istadu imbiadu a s'indiritzu tuo de posta eletrònica. Sighi su ligàmene pro ativare su contu tuo. Controlla sa cartella de s'àliga si no as retzidu custu messàgiu de posta eletrònica.
       update_needs_confirmation: Su contu tuo est istadu atualizadu, ma depimus verificare s'indiritzu tuo de posta eletrònica nou. Controlla sa posta eletrònica e sighi su ligàmene pro cunfirmare s'indiritzu nou de posta eletrònica. Controlla sa cartella de s'àliga si no as retzidu custu messàgiu de posta eletrònica.
       updated: Su contu tuo est istadu atualizadu.
     sessions:
diff --git a/config/locales/devise.sco.yml b/config/locales/devise.sco.yml
index 991101f778..1db6bbe303 100644
--- a/config/locales/devise.sco.yml
+++ b/config/locales/devise.sco.yml
@@ -84,6 +84,11 @@ sco:
       updated_not_active: Yer passwird haes been chynged successfully.
     registrations:
       destroyed: Cheerio! Yer accoont haes been successfully shut doon. We howp fir tae see ye again suin.
+      signed_up: Hullo! Ye'v signed up successfully.
+      signed_up_but_inactive: Ye'v signed up successfully. Hou an iver, we cuidnae sign ye in acause yer accoont isnae activatit yit.
+      signed_up_but_locked: Ye'v signed up successfully. Hou an iver, we cuidnae sign ye in acause yer accoont is snibbed.
+      signed_up_but_pending: A email wi a confirmation link haes been sent tae yer email address. Efter ye chap on the link, we'll luik ower yer application. Ye'll be sent a note gin it's approved.
+      signed_up_but_unconfirmed: A message wi a confirmation link haes been sent tae yer email address. Please follae the link fir tae activate yer accoont. Please check yer mince folder if ye didnae get this email.
       update_needs_confirmation: Ye updatit yer accoont successfully, but we need tae verify yer new email address. Please tak a deek at yer email an follae the confirm link fir tae confirm yer new email address. Please tak a deek at yer mince folder if ye didnae get this email.
       updated: Yer accoont haes been updatit successfully.
     sessions:
diff --git a/config/locales/devise.si.yml b/config/locales/devise.si.yml
index 354b6fc977..d0bfa719bc 100644
--- a/config/locales/devise.si.yml
+++ b/config/locales/devise.si.yml
@@ -77,6 +77,11 @@ si:
       updated_not_active: ඔබගේ මුරපදය සාර්ථකව වෙනස් කර ඇත.
     registrations:
       destroyed: ආයුබෝවන්! ඔබගේ ගිණුම සාර්ථකව අවලංගු කර ඇත. ඉක්මනින්ම ඔබව නැවත හමුවීමට අපි බලාපොරොත්තු වෙමු.
+      signed_up: සාදරයෙන් පිළිගනිමු! ඔබ සාර්ථකව ලියාපදිංචි වී ඇත.
+      signed_up_but_inactive: ඔබ සාර්ථකව ලියාපදිංචි වී ඇත. කෙසේ වෙතත්, ඔබගේ ගිණුම තවමත් සක්‍රිය කර නොමැති නිසා අපට ඔබව පුරනය වීමට නොහැකි විය.
+      signed_up_but_locked: ඔබ සාර්ථකව ලියාපදිංචි වී ඇත. කෙසේ වෙතත්, ඔබගේ ගිණුම අගුලු දමා ඇති නිසා අපට ඔබව පුරනය කිරීමට නොහැකි විය.
+      signed_up_but_pending: තහවුරු කිරීමේ සබැඳියක් සහිත පණිවිඩයක් ඔබගේ විද්‍යුත් තැපැල් ලිපිනයට යවා ඇත. ඔබ සබැඳිය ක්ලික් කළ පසු, අපි ඔබගේ අයදුම්පත සමාලෝචනය කරන්නෙමු. එය අනුමත වුවහොත් ඔබට දැනුම් දෙනු ලැබේ.
+      signed_up_but_unconfirmed: තහවුරු කිරීමේ සබැඳියක් සහිත පණිවිඩයක් ඔබගේ විද්‍යුත් තැපැල් ලිපිනයට යවා ඇත. ඔබගේ ගිණුම සක්‍රිය කිරීමට කරුණාකර සබැඳිය අනුගමනය කරන්න. ඔබට මෙම විද්‍යුත් තැපෑල නොලැබුනේ නම් කරුණාකර ඔබගේ අයාචිත තැපැල් ෆෝල්ඩරය පරීක්ෂා කරන්න.
       update_needs_confirmation: ඔබගේ ගිණුම සාර්ථකව යාවත්කාලීන වුවද අපට නව වි-තැපැල් ලිපිනය සත්‍යාපනය කර ගැනීමට වුවමනා කෙරේ. කරුණාකර ඔබගේ වි-තැපෑල පරීක්‍ෂා කර ඊට අදාළ සබැඳිය අනුගමනය කර ඔබගේ නව වි-තැපැල් ලිපිනය තහවුරු කරන්න. ඔබට මෙම වි-තැපෑල නොලැබුණේ නම් කරුණාකර අයාචිත තැපැල් බහාලුම බලන්න.
       updated: ඔබගේ ගිණුම සාර්ථකව යාවත්කාලීන කර ඇත.
     sessions:
diff --git a/config/locales/devise.sk.yml b/config/locales/devise.sk.yml
index 08e93456b0..b36416b317 100644
--- a/config/locales/devise.sk.yml
+++ b/config/locales/devise.sk.yml
@@ -94,6 +94,11 @@ sk:
       updated_not_active: Vaše heslo bolo úspešne zmenené.
     registrations:
       destroyed: Dovidenia! Váš účet bol úspešne zrušený. Dúfame ale, že vás tu opäť niekedy uvidíme.
+      signed_up: Vitajte! Vaša registrácia bola úspešná.
+      signed_up_but_inactive: Registrácia bola úspešná. Avšak váš účet ešte nebol aktivovaný, takže sa nemôžete prihlásiť.
+      signed_up_but_locked: Registrácia bola úspešná. Avšak váš účet je zamknutý, takže sa nemôžete prihlásiť.
+      signed_up_but_pending: Na váš e-mail bola odoslaná správa s odkazom na overenie. Po kliknutí na odkaz vašu prihlášku skontrolujeme. O jej schválení vás budeme informovať.
+      signed_up_but_unconfirmed: Na váš e-mail bola odoslaná správa s odkazom na overenie. Svoj účet aktivujte kliknutím na odkaz. Ak ste e-mail nedostali, skontrolujte svoj priečinok pre spam.
       update_needs_confirmation: Váš účet bol úspešne zmenený, ale ešte potrebujeme overiť vašu novú e-mailovú adresu. Overíte ju kliknutím na potvrdzovací odkaz zaslaný na váš e-mail. Ak ste e-mail nedostali, skontrolujte svoj priečinok pre spam.
       updated: Váš účet bol úspešne aktualizovaný.
     sessions:
diff --git a/config/locales/devise.sl.yml b/config/locales/devise.sl.yml
index 8e708f4ebf..0eb9b6330a 100644
--- a/config/locales/devise.sl.yml
+++ b/config/locales/devise.sl.yml
@@ -94,6 +94,11 @@ sl:
       updated_not_active: Vaše geslo je bilo uspešno spremenjeno.
     registrations:
       destroyed: Srečno! Vaš račun je bil uspešno preklican. Upamo, da vas bomo kmalu spet videli.
+      signed_up: Dobrodošli! Uspešno ste se vpisali.
+      signed_up_but_inactive: Uspešno ste se vpisali. Vendar vas nismo mogli prijaviti, ker vaš račun še ni aktiviran.
+      signed_up_but_locked: Uspešno ste se vpisali. Vendar vas nismo mogli prijaviti, ker je vaš račun zaklenjen.
+      signed_up_but_pending: Na vaš e-poštni naslov je bilo poslano sporočilo s povezavo za potrditev. Ko kliknete povezavo, bomo pregledali vašo prijavo. Obveščeni boste, če bo odobrena.
+      signed_up_but_unconfirmed: Na vaš e-poštni naslov je bilo poslano sporočilo s povezavo za potrditev. Sledite povezavi, da aktivirate svoj račun. Če niste prejeli te e-pošte, preverite mapo z neželeno pošto.
       update_needs_confirmation: Uspešno ste posodobili račun, vendar moramo potrditi vaš novi e-poštni naslov. Preverite svojo e-pošto in sledite povezavi za potrditev, da potrdite nov e-poštni naslov. Če niste prejeli te e-pošte, preverite mapo z neželeno pošto.
       updated: Vaš račun je bil uspešno posodobljen.
     sessions:
diff --git a/config/locales/devise.sq.yml b/config/locales/devise.sq.yml
index 69ac7bd26d..76dd493245 100644
--- a/config/locales/devise.sq.yml
+++ b/config/locales/devise.sq.yml
@@ -94,6 +94,11 @@ sq:
       updated_not_active: Fjalëkalimi juaj u ndryshua me sukses.
     registrations:
       destroyed: Shëndet! Llogaria juaj u fshi me sukses. Shpresojmë t’ju rishohim së shpejti.
+      signed_up: Mirë se vini! U regjistruat me sukses.
+      signed_up_but_inactive: U regjistruat me sukses. Megjithatë, s’u bë dot hyrja juaj, ngaqë llogaria juaj s’është aktivizuar ende.
+      signed_up_but_locked: U regjistruat me sukses. Megjithatë, s’u bë dot hyrja juaj, ngaqë llogaria juaj është kyçur.
+      signed_up_but_pending: Te adresa juaj email është dërguar një mesazh me një lidhje ripohimi. Pasi të klikoni mbi lidhjen, do të shqyrtojmë aplikimin tuaj. Nëse miratohet, do të njoftoheni.
+      signed_up_but_unconfirmed: Te adresa juaj email u dërgua një mesazh me një lidhje ripohimi. Ju lutemi, që të aktivizoni llogarinë tuaj, ndiqni lidhjen. Ju lutemi, kontrolloni dosjen e mesazheve të padëshiruar, nëse nuk e morët këtë email.
       update_needs_confirmation: E përditësuat me sukses llogarinë tuaj, por na duhet të verifikojmë adresën tuaj të re email. Ju lutemi, që të ripohoni adresën tuaj të re email, kontrolloni email-in tuaj dhe ndiqni lidhjen. Ju lutemi, kontrolloni dosjen e mesazheve të padëshiruar, nëse nuk e morët këtë email.
       updated: Llogaria juaj u përditësua me sukses.
     sessions:
diff --git a/config/locales/devise.sr-Latn.yml b/config/locales/devise.sr-Latn.yml
index 00e8ac4538..3947b2d84f 100644
--- a/config/locales/devise.sr-Latn.yml
+++ b/config/locales/devise.sr-Latn.yml
@@ -94,6 +94,11 @@ sr-Latn:
       updated_not_active: Vaša lozinka nije uspešno promenjena.
     registrations:
       destroyed: Ćao! Vaš nalog je uspešno obrisan. Nadamo se da ćete se uskoro vratiti.
+      signed_up: Dobro došli! Uspešno ste se registrovali.
+      signed_up_but_inactive: Uspešno ste se registrovali. Nažalost ne možete se prijaviti zato što Vaš nalog još nije aktiviran.
+      signed_up_but_locked: Uspešno ste se registrovali. Nažalost ne možete se prijaviti zato što je Vaš nalog zaključan.
+      signed_up_but_pending: Na vaš imejl poslata je poruka sa vezom za potvrdu. Nakon što kliknete na vezu, pregledaćemo vašu prijavu. Bićete obavešteni ako bude odobreno.
+      signed_up_but_unconfirmed: Poruka za potvrdu Vašeg naloga je poslata na Vašu imejl adresu. Kliknite na vezu u imejlu da potvrdite svoj nalog. Molimo proverite i spam fasciklu ako niste primili poruku.
       update_needs_confirmation: Uspešno ste ažurirali svoj nalog, ali moramo da verifikujemo vašu novu adresu e-pošte. Proverite svoju e-poštu i pratite vezu za potvrdu da biste potvrdili novu adresu e-pošte. Proverite svoju fasciklu neželjene pošte ako niste primili ovu e-poštu.
       updated: Vaš nalog je uspešno ažuriran.
     sessions:
diff --git a/config/locales/devise.sr.yml b/config/locales/devise.sr.yml
index a7e855fa68..a4c08dfaf0 100644
--- a/config/locales/devise.sr.yml
+++ b/config/locales/devise.sr.yml
@@ -94,6 +94,11 @@ sr:
       updated_not_active: Ваша лозинка није успешно промењена.
     registrations:
       destroyed: Ћао! Ваш налог је успешно обрисан. Надамо се да ћете се ускоро вратити.
+      signed_up: Добро дошли! Успешно сте се регистровали.
+      signed_up_but_inactive: Успешно сте се регистровали. Нажалост не можете се пријавити зато што Ваш налог још није активиран.
+      signed_up_but_locked: Успешно сте се регистровали. Нажалост не можете се пријавити зато што је Ваш налог закључан.
+      signed_up_but_pending: На ваш имејл послата је порука са везом за потврду. Након што кликнете на везу, прегледаћемо вашу пријаву. Бићете обавештени ако буде одобрено.
+      signed_up_but_unconfirmed: Порука за потврду Вашег налога је послата на Вашу имејл адресу. Кликните на везу у имејлу да потврдите свој налог. Молимо проверите и спам фасциклу ако нисте примили поруку.
       update_needs_confirmation: Успешно сте ажурирали свој налог, али морамо да верификујемо вашу нову адресу е-поште. Проверите своју е-пошту и пратите везу за потврду да бисте потврдили нову адресу е-поште. Проверите своју фасциклу нежељене поште ако нисте примили ову е-пошту.
       updated: Ваш налог је успешно ажуриран.
     sessions:
diff --git a/config/locales/devise.sv.yml b/config/locales/devise.sv.yml
index 1808cfee5e..1e14a7de52 100644
--- a/config/locales/devise.sv.yml
+++ b/config/locales/devise.sv.yml
@@ -94,6 +94,11 @@ sv:
       updated_not_active: Ditt lösenord har ändrats. Du är nu inloggad.
     registrations:
       destroyed: Adjö! Ditt konto har blivit nedstängt. Vi hoppas att vi snart ses igen.
+      signed_up: Välkommen! Du har nu registrerat dig.
+      signed_up_but_inactive: Du har nu registrerat dig. Vi kunde dock inte logga in dig eftersom ditt konto ännu inte är aktiverat.
+      signed_up_but_locked: Du har nu registrerat dig. Vi kunde dock inte logga in eftersom ditt konto är låst.
+      signed_up_but_pending: Ett meddelande med en bekräftelselänk har skickats till din e-postadress. När du klickar på länken kommer vi att granska din ansökan. Du kommer att meddelas om den godkänns.
+      signed_up_but_unconfirmed: Ett meddelande med en bekräftelselänk har skickats till din e-postadress. Vänligen följ länken för att aktivera ditt konto. Kontrollera din skräppostmapp om du inte fick det här e-postmeddelandet.
       update_needs_confirmation: Du har uppdaterat ditt konto, men vi måste verifiera din nya e-postadress. Vänligen kolla din e-post och följ bekräfta-länken för att bekräfta din nya e-postadress. Kontrollera din skräppost om du inte fick e-postmeddelandet.
       updated: Ditt konto har uppdaterats utan problem.
     sessions:
diff --git a/config/locales/devise.ta.yml b/config/locales/devise.ta.yml
index 76fb45c7c1..a0e6187ec4 100644
--- a/config/locales/devise.ta.yml
+++ b/config/locales/devise.ta.yml
@@ -69,6 +69,11 @@ ta:
       updated_not_active: உங்கள் கடவுச்சொல் வெற்றிகரமாக மாற்றப்பட்டது.
     registrations:
       destroyed: நன்றி! தங்கள் கணக்கு வெற்றிகரமாக ரத்து செய்யப்பட்டது. தங்கள் வருகையை மீண்டும் எதிர்நோக்கியிருக்கிறோம்.
+      signed_up: வருக! நீங்கள் வெற்றிகரமாகப் பதிவுசெய்துவிட்டீர்கள்.
+      signed_up_but_inactive: நீங்கள் வெற்றிகரமாகப் பதிவுசெய்துவிட்டீர்கள். ஆனால், உங்கள் கணக்கு இன்னும் செயல்படுத்தப்படாததால், உங்களை உள்நுழைக்க இயலவில்லை.
+      signed_up_but_locked: நீங்கள் வெற்றிகரமாகப் பதிவுசெய்துவிட்டீர்கள். ஆனால், உங்கள் கணக்கு பூட்டப்பட்டுள்ளதால், உங்களை உள்நுழைக்க இயலவில்லை.
+      signed_up_but_pending: உங்கள் மின்னஞ்சல் முகவரிக்கு ஒரு உறுதிபடுத்தும் சுட்டி அனுப்பப்பட்டுள்ளது. அந்த சுட்டியை நீங்கள் கிளிக் செய்தவுடன் உங்களின் விண்ணப்பத்தை நாங்கள் பரிசீலிப்போம். விண்ணப்பம் அங்கீகரிக்கப்பட்டால் உங்களுக்குத் தெரிவிக்கப்படும்.
+      signed_up_but_unconfirmed: உங்கள் மின்னஞ்சல் முகவரிக்கு ஒரு உறுதிபடுத்தும் சுட்டி அனுப்பப்பட்டுள்ளது. உங்கள் கணக்கை செயல்படுத்த அந்த சுட்டியை தயவுசெய்து கிளிக் செய்யவும். மின்னஞ்சலை நீங்கள் பெறவில்லை எனில், தயைகூர்ந்து ஸ்பேம் பெட்டியில் பார்க்கவும்.
       update_needs_confirmation: உங்கள் கணக்கு வெற்றிகரமாகத் திருத்தப்பட்டது. ஆனால், உங்கள் புதிய மின்னஞ்சல் முகவரியை நாங்கள் உறுதிசெய்ய வேண்டும். உங்கள் மின்னஞ்சல் முகவரிக்கு அனுப்பப்பட்டுள்ள உறுதிபடுத்தும் சுட்டியை தயவுசெய்து கிளிக் செய்யவும். மின்னஞ்சலை நீங்கள் பெறவில்லை எனில், தயைகூர்ந்து ஸ்பேம் பெட்டியில் பார்க்கவும்.
       updated: உங்கள் கணக்கு வெற்றிகரமாகத் திருத்தப்பட்டது.
     sessions:
diff --git a/config/locales/devise.th.yml b/config/locales/devise.th.yml
index 4909b50129..50e2e4b29e 100644
--- a/config/locales/devise.th.yml
+++ b/config/locales/devise.th.yml
@@ -94,6 +94,11 @@ th:
       updated_not_active: เปลี่ยนรหัสผ่านของคุณสำเร็จ
     registrations:
       destroyed: ลาก่อน! ยกเลิกบัญชีของคุณสำเร็จ เราหวังว่าจะได้พบคุณอีกในเร็ว ๆ นี้
+      signed_up: ยินดีต้อนรับ! คุณได้ลงทะเบียนสำเร็จ
+      signed_up_but_inactive: คุณได้ลงทะเบียนสำเร็จ อย่างไรก็ตาม เราไม่สามารถลงชื่อคุณเข้าได้เนื่องจากยังไม่ได้เปิดใช้งานบัญชีของคุณ
+      signed_up_but_locked: คุณได้ลงทะเบียนสำเร็จ อย่างไรก็ตาม เราไม่สามารถลงชื่อคุณเข้าได้เนื่องจากมีการล็อคบัญชีของคุณอยู่
+      signed_up_but_pending: ส่งข้อความพร้อมลิงก์การยืนยันไปยังที่อยู่อีเมลของคุณแล้ว หลังจากคุณคลิกลิงก์ เราจะตรวจทานใบสมัครของคุณ คุณจะได้รับการแจ้งเตือนหากมีการอนุมัติใบสมัคร
+      signed_up_but_unconfirmed: ส่งข้อความพร้อมลิงก์การยืนยันไปยังที่อยู่อีเมลของคุณแล้ว โปรดไปตามลิงก์เพื่อเปิดใช้งานบัญชีของคุณ โปรดตรวจสอบโฟลเดอร์สแปมของคุณหากคุณไม่ได้รับอีเมลนี้
       update_needs_confirmation: คุณได้อัปเดตบัญชีของคุณสำเร็จ แต่เราจำเป็นต้องยืนยันที่อยู่อีเมลใหม่ของคุณ โปรดตรวจสอบอีเมลของคุณและไปตามลิงก์การยืนยันเพื่อยืนยันที่อยู่อีเมลใหม่ของคุณ โปรดตรวจสอบโฟลเดอร์สแปมของคุณหากคุณไม่ได้รับอีเมลนี้
       updated: อัปเดตบัญชีของคุณสำเร็จ
     sessions:
diff --git a/config/locales/devise.tr.yml b/config/locales/devise.tr.yml
index 57162e8055..e709d3fff1 100644
--- a/config/locales/devise.tr.yml
+++ b/config/locales/devise.tr.yml
@@ -94,6 +94,11 @@ tr:
       updated_not_active: Parolanız başarıyla değiştirildi.
     registrations:
       destroyed: Görüşürüz! hesabın başarıyla iptal edildi. Umarız seni sonra tekrar görürüz.
+      signed_up: Hoş geldiniz! Başarılı bir şekilde oturum açtınız.
+      signed_up_but_inactive: Başarıyla kaydoldun. Ancak, seni içeri alamıyoruz çünkü hesabın henüz aktif değil.
+      signed_up_but_locked: Başarıyla kaydoldun. Ancak, seni içeri alamıyoruz çünkü hesabın kilitli.
+      signed_up_but_pending: Onay bağlantısına sahip bir mesaj e-posta adresinize gönderildi. Bağlantıyı tıkladıktan sonra başvurunuzu inceleyeceğiz. Onaylanması durumunda size bilgi verilecektir.
+      signed_up_but_unconfirmed: Onay bağlantısına sahip bir mesaj e-posta adresinize gönderildi. Lütfen hesabınızı etkinleştirmek için bağlantıyı takip edin. Bu e-postayı almadıysanız, lütfen spam klasörünüzü kontrol edin.
       update_needs_confirmation: Hesabınızı başarıyla güncellediniz, ancak yeni e-posta adresinizi doğrulamamız gerekiyor. Lütfen e-postanızı kontrol edin ve yeni e-posta adresinizi onaylamak için onay bağlantısını izleyin. Bu e-postayı almadıysanız, lütfen spam klasörünüzü kontrol edin.
       updated: Hesabınız başarıyla güncellendi.
     sessions:
diff --git a/config/locales/devise.tt.yml b/config/locales/devise.tt.yml
index 608d7dbcc9..8757bcc033 100644
--- a/config/locales/devise.tt.yml
+++ b/config/locales/devise.tt.yml
@@ -3,19 +3,7 @@ tt:
   devise:
     confirmations:
       confirmed: Сезнең э. почта адресыгыз уңышлы расланган.
-    failure:
-      already_authenticated: Сез кердегез инде.
-      inactive: Сезнең аккаунтыгыз әле активламаган.
-      invalid: "%{authentication_keys} яки серсүз дөрес кертелмәгән."
-      locked: Сезнең хисапъязмагыз блокланган.
-      not_found_in_database: "%{authentication_keys} яки серсүз дөрес кертелмәгән."
     mailer:
-      confirmation_instructions:
-        action: Email адресын расла
-        action_with_app: Расла һәм %{app} эченә кайт
-        title: Email адресын раслагыз
-      email_changed:
-        explanation: 'Сезнең аккаунтыгызның email адресы моңа үзгәртеләчәк:'
       reset_password_instructions:
         action: Серсүзне үзгәртү
         title: Серсүзне алыштыру
@@ -28,6 +16,7 @@ tt:
       updated: Серсүзегез уңышлы үзгәртелде. Сез хәзер кердегез.
       updated_not_active: Серсүзегез уңышлы үзгәртелде.
     registrations:
+      signed_up: Рәхим итегез! Сез уңышлы теркәлдегез.
       updated: Хисабыгыз уңышлы яңартылды.
     sessions:
       already_signed_out: Уңышлы чыккансыз.
diff --git a/config/locales/devise.uk.yml b/config/locales/devise.uk.yml
index 7ede7a96c2..65e89a274f 100644
--- a/config/locales/devise.uk.yml
+++ b/config/locales/devise.uk.yml
@@ -94,6 +94,11 @@ uk:
       updated_not_active: Ваш пароль було успішно змінено.
     registrations:
       destroyed: До побачення! Ваш обліковий запис було успішно видалено. Сподіваємось, Ви скоро повернетеся.
+      signed_up: Ласкаво просимо! Ви були успішно зареєстровані.
+      signed_up_but_inactive: Ви були успішно зареєстровані, але ми не можемо авторизувати вас, оскільки ваш обліковий запис ще не активовано.
+      signed_up_but_locked: Ви були успішно зареєстровані, але ми не можемо авторизувати вас, оскільки ваш обліковий запис заблокований.
+      signed_up_but_pending: Лист з посиланням для підтвердження було надіслано на вашу електронну пошту. Коли ви перейдете за цим посиланням, ми розглянемо ваш запит. Вас буде проінформовано, якщо запит буде схвалено.
+      signed_up_but_unconfirmed: Повідомлення з посиланням на підтвердження будо відправлено на вашу адресу електронної пошти. Будь ласка, перейдіть за посиланням, щоб активувати ваш обліковий запис. Якщо ви не отримали цього листа, перевірте теку зі спамом у вашій скринці.
       update_needs_confirmation: Ваш обліковий запис оновлено, але необхідно підтвердити нову адресу електронної пошти. Будь ласка, перевірте свою електронну скриньку і перейдіть за посиланням "Підтвердити", шоб завершити оновлення адреси електронної пошти. Якщо ви не отримали цього листа, перевірте теку зі спамом у вашій скринці.
       updated: Ваш обліковий запис було успішно оновлено.
     sessions:
diff --git a/config/locales/devise.vi.yml b/config/locales/devise.vi.yml
index 28414fa485..6ae78f83cc 100644
--- a/config/locales/devise.vi.yml
+++ b/config/locales/devise.vi.yml
@@ -94,6 +94,11 @@ vi:
       updated_not_active: Mật khẩu của bạn đã được thay đổi thành công.
     registrations:
       destroyed: Tạm biệt! Tài khoản của bạn đã bị hủy. Hi vọng chúng tôi sẽ sớm gặp lại bạn.
+      signed_up: Chúc mừng! Bạn đã đăng ký thành công.
+      signed_up_but_inactive: Bạn đã đăng ký thành công. Tuy nhiên, bạn cần phải kích hoạt tài khoản mới có thể đăng nhập.
+      signed_up_but_locked: Bạn đã đăng ký thành công. Tuy nhiên, chúng tôi không thể đăng nhập cho bạn vì tài khoản của bạn bị khóa.
+      signed_up_but_pending: Một email xác minh đã được gửi đến địa chỉ email của bạn. Sau khi bạn nhấp vào liên kết, chúng tôi sẽ xem xét đơn đăng ký của bạn và thông báo nếu đơn được chấp thuận.
+      signed_up_but_unconfirmed: Một email xác minh đã được gửi đến địa chỉ email của bạn. Hãy nhấp vào liên kết trong email để kích hoạt tài khoản của bạn. Nếu không thấy, hãy kiểm tra mục thư rác.
       update_needs_confirmation: Bạn đã cập nhật tài khoản thành công, nhưng chúng tôi cần xác minh địa chỉ email mới của bạn. Vui lòng kiểm tra email và nhấp vào liên kết xác minh. Nếu bạn không thấy email, hãy kiểm tra trong thư rác.
       updated: Tài khoản của bạn đã được cập nhật thành công.
     sessions:
diff --git a/config/locales/devise.zh-CN.yml b/config/locales/devise.zh-CN.yml
index c96c4afdcc..5c14d3c571 100644
--- a/config/locales/devise.zh-CN.yml
+++ b/config/locales/devise.zh-CN.yml
@@ -94,6 +94,11 @@ zh-CN:
       updated_not_active: 你的密码已修改成功。
     registrations:
       destroyed: 再见!你的账号已成功注销。我们希望很快可以再见到你。
+      signed_up: 欢迎!你已成功注册。
+      signed_up_but_inactive: 你已成功注册,但我们无法让你登录,因为你的账号还没有激活。
+      signed_up_but_locked: 你已成功注册,但由于你的账户已被锁定,我们无法让你登录。
+      signed_up_but_pending: 一条带有确认链接的邮件已经发送到你的邮箱地址。在你点击该链接后,我们将会审核你的申请。如果申请被批准,你将收到通知。
+      signed_up_but_unconfirmed: 一封带有确认链接的邮件已经发送至你的邮箱,请点击邮件中的链接以激活你的账号。如果没有,请检查你的垃圾邮件。
       update_needs_confirmation: 账号信息更新成功,但我们需要验证你的新邮箱地址,请点击邮件中的链接以确认。如果没有收到邮件,请检查你的垃圾邮件文件夹。
       updated: 账号资料更新成功。
     sessions:
diff --git a/config/locales/devise.zh-HK.yml b/config/locales/devise.zh-HK.yml
index b9c5529d9f..a2620a8e4a 100644
--- a/config/locales/devise.zh-HK.yml
+++ b/config/locales/devise.zh-HK.yml
@@ -94,6 +94,11 @@ zh-HK:
       updated_not_active: 你的密碼已經更新。
     registrations:
       destroyed: 再見!你的帳號已被取消,希望日後能再見到你。
+      signed_up: 歡迎你!你已經成功登記。
+      signed_up_but_inactive: 你的登記已經成功,可是由於你的帳號還未被啟用,暫時還不能讓你登入。
+      signed_up_but_locked: 你的登記已經成功,可是由於你的帳號已被鎖定,我們無法讓你登入。
+      signed_up_but_pending: 確認連結已發送到你的電郵地址。在你點擊連結後,我們會審核你的申請。一旦通過審核,你將會收到進一步通知。
+      signed_up_but_unconfirmed: 一條確認連結已經電郵到你的郵址。請使用讓連結啟用你的帳號。
       update_needs_confirmation: 你的帳號已經更新,但我們需要確認你的電郵地址。請打開你的郵箱,使用確認電郵的連結來確認的地郵址。如果未能在收件箱找相關電郵指示,請檢查垃圾郵件箱。
       updated: 你的帳號已經成功更新。
     sessions:
diff --git a/config/locales/devise.zh-TW.yml b/config/locales/devise.zh-TW.yml
index ca01b16591..fdbde86995 100644
--- a/config/locales/devise.zh-TW.yml
+++ b/config/locales/devise.zh-TW.yml
@@ -94,6 +94,11 @@ zh-TW:
       updated_not_active: 您的密碼已成功變更。
     registrations:
       destroyed: 再見!您的帳號已成功取消,期待再相逢。
+      signed_up: 歡迎!您已成功註冊。
+      signed_up_but_inactive: 您已註冊成功,但由於您的帳號尚未啟用,我們暫時無法讓您登入。
+      signed_up_but_locked: 您已註冊成功,但由於您的帳號已被鎖定,我們無法讓您登入。
+      signed_up_but_pending: 包含驗證連結的訊息已寄到您的電子郵件信箱。按下此連結後我們將審核您的申請。核准後將通知您。
+      signed_up_but_unconfirmed: 包含驗證連結的訊息已寄至您的電子郵件信箱。請前往連結以啟用帳號。若未收到請檢查垃圾郵件資料夾。
       update_needs_confirmation: 已成功更新您的帳號,但仍需驗證您的新電子郵件地址。請檢查電子郵件信箱並前往確認連結來確認新電子郵件地址。若未收到請檢查垃圾郵件資料夾。
       updated: 您的帳號已成功更新。
     sessions:
diff --git a/config/locales/doorkeeper.io.yml b/config/locales/doorkeeper.io.yml
index 94efbfc9d7..0384d968be 100644
--- a/config/locales/doorkeeper.io.yml
+++ b/config/locales/doorkeeper.io.yml
@@ -60,7 +60,6 @@ io:
       error:
         title: Eroro eventis
       new:
-        prompt_html: "%{client_name} volas permiso por adirar vua konto."
         review_permissions: Kontrolez permisi
         title: Yurizo bezonesas
       show:
@@ -83,7 +82,6 @@ io:
         access_denied: Moyenproprietanto o yurizservilo refuzis la demando.
         credential_flow_not_configured: Moyenproprietantpasvortidentesesofluo faliis pro ke Doorkeeper.configure.resource_owner_from_credentials ne ajustesis.
         invalid_client: Klientpermiso falias pro nesavita kliento, neinkluzita klientpermiso o nesuportita permismetodo.
-        invalid_code_challenge_method: La kodexchalenjmetodo mustas esar S256.
         invalid_grant: Provizita yurizo esis nevalida, expiris, deaprobesis, ne parigas uzita ridirektoligilo dum yurizdemando o facesis a altra kliento.
         invalid_redirect_uri: La inkluzita ridirektoligilo esas nevalida.
         invalid_request:
@@ -125,7 +123,7 @@ io:
         admin/reports: Administro di raporti
         all: Kompleta aceso a vua Mastodon-konto
         blocks: Restriktita
-        bookmarks: Lektosigni
+        bookmarks: Libromarki
         conversations: Konversi
         crypto: Intersequanta chifro
         favourites: Favoriziti
@@ -133,10 +131,9 @@ io:
         follow: Sequati, silencigati e blokusati
         follows: Sequati
         lists: Listi
-        media: Audvidajaddonaji
+        media: Mediatachaji
         mutes: Silencigati
         notifications: Avizi
-        profile: Vua Mastodon-profilo
         push: Pulsavizi
         reports: Raporti
         search: Trovez
@@ -151,28 +148,17 @@ io:
     scopes:
       admin:read: lektez omna informi di la servilo
       admin:read:accounts: lektez privata informo di omna konti
-      admin:read:canonical_email_blocks: lektar trublema informo di omna retpostoblokusi
-      admin:read:domain_allows: lektar trublema informo di omna domenpermisi
-      admin:read:domain_blocks: lektar trublema informo di omna domenblokusi
-      admin:read:email_domain_blocks: lektar trublema informo di omna retpostodomenblokusi
-      admin:read:ip_blocks: lektar trublema informo di omna IP-blokusi
       admin:read:reports: lektez privata informo di omna raporti e raportizita konti
-      admin:write: redaktar omna informi di la servilo
+      admin:write: modifikez omna informi di la servilo
       admin:write:accounts: jerez konti
-      admin:write:canonical_email_blocks: agar jeri ad retpostoblokusi
-      admin:write:domain_allows: agar jeri ad domenpermisi
-      admin:write:domain_blocks: agar jeri ad domenblokusi
-      admin:write:email_domain_blocks: agar jeri ad retpostodomenblokusi
-      admin:write:ip_blocks: agar jeri ad IP-blokusi
       admin:write:reports: jerez raporti
       crypto: uzas intersequanta chifro
       follow: follow, block, unblock and unfollow accounts
-      profile: lektar nur profilinformo di vua konto
       push: ganez vua pulsavizi
       read: read your account's data
       read:accounts: videz kontinformo
       read:blocks: videz restrikti
-      read:bookmarks: vidar vua lektosigni
+      read:bookmarks: videz vua libromarki
       read:favourites: videz vua favoriziti
       read:filters: videz vua filtrili
       read:follows: videz vua sequinti
@@ -183,15 +169,15 @@ io:
       read:search: trovez por vu
       read:statuses: videz omna posti
       write: post on your behalf
-      write:accounts: redaktar vua porfilo
+      write:accounts: modifikez vua porfilo
       write:blocks: restriktez konti e domeni
-      write:bookmarks: lektosignar afishi
+      write:bookmarks: libromarkez posti
       write:conversations: silencigez e efacez konversi
       write:favourites: favorizita posti
       write:filters: kreez filtrili
       write:follows: sequez personi
       write:lists: kreez listi
-      write:media: adkargar audvidajdoseri
+      write:media: adchargez mediifaili
       write:mutes: silencigez personi e konversi
       write:notifications: efacez vua avizi
       write:reports: raportizez altra omni
diff --git a/config/locales/doorkeeper.kab.yml b/config/locales/doorkeeper.kab.yml
index c65bada409..fa9e1c540a 100644
--- a/config/locales/doorkeeper.kab.yml
+++ b/config/locales/doorkeeper.kab.yml
@@ -111,9 +111,9 @@ kab:
         lists: Tibdarin
         media: Imeddayen n umidya
         mutes: Yeggugem
-        notifications: Ilɣa
+        notifications: Alɣuten
         profile: Amaɣnu-k Mastodon
-        push: Ilɣa yettudemmren
+        push: Alɣuten yettudemmren
         reports: Ineqqisen
         search: Nadi
         statuses: Tisuffaɣ
@@ -127,7 +127,7 @@ kab:
       admin:read: ad iɣeṛ akk isefka ɣef uqeddac
       admin:write: ad iẓreg akk isefka ɣef uqeddac
       follow: ad ibeddel assaɣen n umiḍan
-      push: ad iṭṭef-d ilɣa-k·m yettwademren
+      push: ad iṭṭef-d alɣuten-ik·im yettwademren
       read: ad iɣeṛ akk isefka n umiḍan-ik·im
       read:accounts: ẓer isallen n yimiḍanen
       read:blocks: ẓer imiḍanen i tesḥebseḍ
@@ -136,7 +136,7 @@ kab:
       read:follows: ẓer imeḍfaṛen-ik
       read:lists: ẓer tibdarin-ik·im
       read:mutes: ẓer wid i tesgugmeḍ
-      read:notifications: ad iẓer ilɣa-inek·inem
+      read:notifications: ad iẓer alɣuten-inek·inem
       read:reports: ẓer ineqqisen-ik·im
       read:search: anadi deg umkan-ik·im
       read:statuses: ad iẓer meṛṛa tisuffaɣ
@@ -148,4 +148,4 @@ kab:
       write:follows: ḍfeṛ imdanen
       write:lists: ad yesnulfu tibdarin
       write:media: ad yessali ifuyla n umidya
-      write:notifications: sfeḍ ilɣa-k·m
+      write:notifications: sfeḍ alɣuten-ik·im
diff --git a/config/locales/doorkeeper.lv.yml b/config/locales/doorkeeper.lv.yml
index 15c1e7692a..af892d79fa 100644
--- a/config/locales/doorkeeper.lv.yml
+++ b/config/locales/doorkeeper.lv.yml
@@ -150,13 +150,13 @@ lv:
         title: OAuth nepieciešama autorizācija
     scopes:
       admin:read: lasīt visus datus uz servera
-      admin:read:accounts: lasīt jūtīgu informāciju no visiem kontiem
-      admin:read:canonical_email_blocks: lasīt jūtīgu informāciju par visiem kanoniskajiem e-pasta blokiem
-      admin:read:domain_allows: lasīt jūtīgu informāciju par visiem atļautajiem domēniem
-      admin:read:domain_blocks: lasīt jūtīgu informāciju par visiem domēna blokiem
-      admin:read:email_domain_blocks: lasīt jūtīgu informāciju par visiem e-pasta domēna blokiem
-      admin:read:ip_blocks: lasīt jūtīgu informāciju par visiem IP blokiem
-      admin:read:reports: lasīt jūtīgu informāciju no visiem pārskatiem un kontiem, par kuriem ziņots
+      admin:read:accounts: lasīt sensitīvu informāciju no visiem kontiem
+      admin:read:canonical_email_blocks: lasīt sensitīvu informāciju par visiem kanoniskajiem e-pasta blokiem
+      admin:read:domain_allows: lasīt visu domēnu sensitīvo informāciju, ko atļauj
+      admin:read:domain_blocks: lasīt sensitīvu informāciju par visiem domēna blokiem
+      admin:read:email_domain_blocks: lasīt sensitīvu informāciju par visiem e-pasta domēna blokiem
+      admin:read:ip_blocks: lasīt sensitīvu informāciju par visiem IP blokiem
+      admin:read:reports: lasīt sensitīvu informāciju no visiem pārskatiem un kontiem, par kuriem ziņots
       admin:write: modificēt visus datus uz servera
       admin:write:accounts: veikt satura pārraudzības darbības kontos
       admin:write:canonical_email_blocks: veikt satura pārraudzības darbības kanoniskajos e-pasta blokos
diff --git a/config/locales/doorkeeper.ms.yml b/config/locales/doorkeeper.ms.yml
index aadce76efd..f89def7b85 100644
--- a/config/locales/doorkeeper.ms.yml
+++ b/config/locales/doorkeeper.ms.yml
@@ -128,11 +128,11 @@ ms:
         crypto: Penyulitan hujung ke hujung
         favourites: Sukaan
         filters: Penapis
-        follow: Ikutan, Redaman dan Sekatan
+        follow: Ikut, Senyap dan Blok
         follows: Ikutan
         lists: Senarai
         media: Lampiran media
-        mutes: Redaman
+        mutes: Senyapkan
         notifications: Pemberitahuan
         push: Pemberitahuan segera
         reports: Laporan
@@ -173,7 +173,7 @@ ms:
       read:filters: lihat penapis anda
       read:follows: lihat senarai yang anda ikuti
       read:lists: lihat senarai anda
-      read:mutes: lihat redamanku
+      read:mutes: lihat senarai yang anda senyapkan
       read:notifications: lihat notifikasi anda
       read:reports: lihat laporan anda
       read:search: cari bagi pihak anda
@@ -182,13 +182,13 @@ ms:
       write:accounts: ubaisuai profail anda
       write:blocks: domain dan akaun blok
       write:bookmarks: menandabuku hantaran
-      write:conversations: redamkan dan padamkan perbualan
+      write:conversations: senyapkan dan padamkan perbualan
       write:favourites: hantaran disukai
       write:filters: cipta penapis
       write:follows: ikut orang
       write:lists: cipta senarai
       write:media: memuat naik fail media
-      write:mutes: redamkan orang dan perbualan
+      write:mutes: membisukan orang dan perbualan
       write:notifications: kosongkan pemberitahuan anda
       write:reports: melaporkan orang lain
       write:statuses: terbitkan hantaran
diff --git a/config/locales/doorkeeper.pt-BR.yml b/config/locales/doorkeeper.pt-BR.yml
index a92819bf68..85bf5d60c0 100644
--- a/config/locales/doorkeeper.pt-BR.yml
+++ b/config/locales/doorkeeper.pt-BR.yml
@@ -61,7 +61,7 @@ pt-BR:
         title: Ocorreu um erro
       new:
         prompt_html: "%{client_name} gostaria de permissão para acessar sua conta. <strong>Aprove esta solicitação apenas se você reconhecer e confiar nesta fonte.</strong>"
-        review_permissions: Revisar permissões
+        review_permissions: Rever permissões
         title: Autorização necessária
       show:
         title: Copie este código de autorização e cole no aplicativo.
@@ -130,7 +130,7 @@ pt-BR:
         crypto: Criptografia de ponta a ponta
         favourites: Favoritos
         filters: Filtros
-        follow: Seguidos, Silenciados e Bloqueados
+        follow: Seguimentos, Silenciamentos e Bloqueios
         follows: Seguidos
         lists: Listas
         media: Mídias anexadas
@@ -165,7 +165,7 @@ pt-BR:
       admin:write:email_domain_blocks: executar ações de moderação em domínios de e-mail bloqueados
       admin:write:ip_blocks: executar ações de moderação em IPs bloqueados
       admin:write:reports: executar ações de moderação em denúncias
-      crypto: usar criptografia de ponta a ponta
+      crypto: usar criptografia de ponta-a-ponta
       follow: alterar o relacionamento das contas
       profile: ler somente as informações do perfil da sua conta
       push: receber notificações push
diff --git a/config/locales/doorkeeper.sl.yml b/config/locales/doorkeeper.sl.yml
index 8b28d1532a..3f36c73756 100644
--- a/config/locales/doorkeeper.sl.yml
+++ b/config/locales/doorkeeper.sl.yml
@@ -60,7 +60,6 @@ sl:
       error:
         title: Prišlo je do napake
       new:
-        prompt_html: "%{client_name} želi dostopati do vašega računa. <strong>To prošnjo odobrite le, če tega odjemalca prepoznate in mu zaupate.</strong>"
         review_permissions: Preglej dovoljenja
         title: Potrebna je odobritev
       show:
@@ -83,7 +82,6 @@ sl:
         access_denied: Lastnik virov ali odobritveni strežnik je zavrnil zahtevo.
         credential_flow_not_configured: Pretok geselskih pooblastil lastnika virov ni uspel, ker Doorkeeper.configure.resource_owner_from_credentials ni nastavljen.
         invalid_client: Odobritev odjemalca ni uspela zaradi neznanega odjemalca, zaradi nevključitve odobritve odjemalca ali zaradi nepodprte metode odobritve.
-        invalid_code_challenge_method: Metoda za kodo mora biti S256, čistopis ni podprt.
         invalid_grant: Predložena odobritev je neveljavna, je potekla, je preklicana, se ne ujema z URI-jem za preusmeritev uporabljenim v zahtevi za odobritev, ali pa je bila izdana drugemu odjemalcu.
         invalid_redirect_uri: URI za preusmeritev ni veljaven.
         invalid_request:
diff --git a/config/locales/el.yml b/config/locales/el.yml
index d2f4fbba01..a842f78fb2 100644
--- a/config/locales/el.yml
+++ b/config/locales/el.yml
@@ -187,7 +187,6 @@ el:
         create_domain_block: Δημιουργία Αποκλεισμού Τομέα
         create_email_domain_block: Δημιουργία Αποκλεισμού Τομέα Email
         create_ip_block: Δημιουργία κανόνα IP
-        create_relay: Δημιουργία Relay
         create_unavailable_domain: Δημιουργία Μη Διαθέσιμου Τομέα
         create_user_role: Δημιουργία Ρόλου
         demote_user: Υποβιβασμός Χρήστη
@@ -199,22 +198,18 @@ el:
         destroy_email_domain_block: Διαγραφή Αποκλεισμού Τομέα Email
         destroy_instance: Εκκαθάριση Τομέα
         destroy_ip_block: Διαγραφή κανόνα IP
-        destroy_relay: Διαγραφή Relay
         destroy_status: Διαγραφή Ανάρτησης
         destroy_unavailable_domain: Διαγραφή Μη Διαθέσιμου Τομέα
         destroy_user_role: Καταστροφή Ρόλου
         disable_2fa_user: Απενεργοποίηση 2FA
         disable_custom_emoji: Απενεργοποίηση Προσαρμοσμένων Emoji
-        disable_relay: Απενεργοποίηση Relay
         disable_sign_in_token_auth_user: Απενεργοποίηση Ελέγχου Ταυτότητας Διακριτικού Email για Χρήστη
         disable_user: Απενεργοποίηση Χρήστη
         enable_custom_emoji: Ενεργοποίηση Προσαρμοσμένων Emoji
-        enable_relay: Ενεργοποίηση Relay
         enable_sign_in_token_auth_user: Ενεργοποίηση Ελέγχου Ταυτότητας Διακριτικού Email για Χρήστη
         enable_user: Ενεργοποίηση Χρήστη
         memorialize_account: Μετατροπή Λογαριασμού σε Εις Μνήμην
         promote_user: Προαγωγή Χρήστη
-        publish_terms_of_service: Δημοσίευση Όρων Χρήσης
         reject_appeal: Απόρριψη Έφεσης
         reject_user: Απόρριψη Χρήστη
         remove_avatar_user: Αφαίρεση Άβαταρ
@@ -252,7 +247,6 @@ el:
         create_domain_block_html: Ο/Η %{name} απέκλεισε τον τομέα %{target}
         create_email_domain_block_html: Ο χρήστης %{name} απέκλεισε τον τομέα email %{target}
         create_ip_block_html: Ο/Η %{name} δημιούργησε κανόνα για την IP %{target}
-        create_relay_html: Ο χρήστης %{name} δημιούργησε ένα relay %{target}
         create_unavailable_domain_html: Ο/Η %{name} σταμάτησε να τροφοδοτεί τον τομέα %{target}
         create_user_role_html: Ο/Η %{name} δημιούργησε ρόλο %{target}
         demote_user_html: Ο/Η %{name} υποβίβασε τον χρήστη %{target}
@@ -264,22 +258,18 @@ el:
         destroy_email_domain_block_html: Ο χρήστης %{name} έκανε άρση αποκλεισμού του τομέα email %{target}
         destroy_instance_html: Ο/Η %{name} εκκαθάρισε τον τομέα %{target}
         destroy_ip_block_html: Ο/Η %{name} διέγραψε τον κανόνα για την IP %{target}
-        destroy_relay_html: Ο χρήστης %{name} διέγραψε το relay %{target}
         destroy_status_html: Ο/Η %{name} αφαίρεσε την ανάρτηση του/της %{target}
         destroy_unavailable_domain_html: Ο/Η %{name} ξανάρχισε να τροφοδοτεί το domain %{target}
         destroy_user_role_html: Ο/Η %{name} διέγραψε τον ρόλο του %{target}
         disable_2fa_user_html: Ο/Η %{name} απενεργοποίησε την απαίτηση για ταυτοποίηση δύο παραγόντων για τον χρήστη %{target}
         disable_custom_emoji_html: Ο/Η %{name} απενεργοποίησε το emoji %{target}
-        disable_relay_html: Ο χρήστης %{name} απενεργοποίησε το relay %{target}
         disable_sign_in_token_auth_user_html: Ο χρήστης %{name} απενεργοποίησε την ταυτοποίηση χαρακτηριστικού μέσω e-mail για %{target}
         disable_user_html: Ο/Η %{name} απενεργοποίησε τη σύνδεση για τον χρήστη %{target}
         enable_custom_emoji_html: Ο/Η %{name} ενεργοποίησε το emoji %{target}
-        enable_relay_html: Ο χρήστης %{name} ενεργοποίησε το relay %{target}
         enable_sign_in_token_auth_user_html: Ο χρήστης %{name} ενεργοποίησε την πιστοποίηση αναγνωριστικού email του %{target}
         enable_user_html: Ο/Η %{name} ενεργοποίησε τη σύνδεση για τον χρήστη %{target}
         memorialize_account_html: O/H %{name} μετέτρεψε τον λογαριασμό του %{target} σε σελίδα εις μνήμην
         promote_user_html: Ο/Η %{name} προβίβασε το χρήστη %{target}
-        publish_terms_of_service_html: Ο χρήστης %{name} δημοσίευσε ενημερώσεις για τους όρους της υπηρεσίας
         reject_appeal_html: Ο/Η %{name} απέρριψε την ένσταση της απόφασης των συντονιστών από %{target}
         reject_user_html: ο/η %{name} απέρριψε την εγγραφή από %{target}
         remove_avatar_user_html: ο/η %{name} αφαίρεσε το άβαταρ του/της %{target}
@@ -309,7 +299,6 @@ el:
       title: Αρχείο ελέγχου
       unavailable_instance: "(μη διαθέσιμο όνομα τομέα)"
     announcements:
-      back: Επιστροφή στις ανακοινώσεις
       destroyed_msg: Επιτυχής διαγραφή ανακοίνωσης!
       edit:
         title: Ενημέρωση ανακοίνωσης
@@ -318,9 +307,6 @@ el:
       new:
         create: Δημιουργία ανακοίνωσης
         title: Νέα ανακοίνωση
-      preview:
-        explanation_html: 'Το email θα αποσταλεί σε <strong>%{display_count} χρήστες</strong>. Το ακόλουθο κείμενο θα συμπεριληφθεί στο e-mail:'
-        title: Προεπισκόπηση ειδοποίησης ανακοίνωσης
       publish: Δημοσίευση
       published_msg: Επιτυχής δημοσίευση ανακοίνωσης!
       scheduled_for: Προγραμματισμένη για %{time}
@@ -479,34 +465,6 @@ el:
       new:
         title: Εισαγωγή αποκλεισμένων τομέων
       no_file: Δεν επιλέχθηκε αρχείο
-    fasp:
-      debug:
-        callbacks:
-          created_at: Δημιουργήθηκε στις
-          delete: Διαγραφή
-          ip: Διεύθυνση IP
-          request_body: Σώμα αιτήματος
-          title: Κλήσεις Αποσφαλμάτωσης
-      providers:
-        active: Ενεργό
-        base_url: URL βάσης
-        callback: Επανάκληση
-        delete: Διαγραφή
-        edit: Επεξεργασία Παρόχου
-        finish_registration: Ολοκλήρωση εγγραφής
-        name: Όνομα
-        providers: Πάροχοι
-        public_key_fingerprint: Αποτύπωμα δημόσιου κλειδιού
-        registration_requested: Η εγγραφή ζητήθηκε
-        registrations:
-          confirm: Επιβεβαίωση
-          description: Έλαβες μια εγγραφή από ένα FASP. Απέρριψέ την αν δεν την άρχισες εσύ. Αν το άρχισες εσύ, σύγκρινε προσεκτικά το όνομα και το κλειδί αποτύπωμα πριν από την επιβεβαίωση της εγγραφής.
-          reject: Απόρριψη
-          title: Επιβεβαίωση Εγγραφής FASP
-        save: Αποθήκευση
-        select_capabilities: Επέλεξε Δυνατότητες
-        sign_in: Σύνδεση
-        status: Κατάσταση
     follow_recommendations:
       description_html: "<strong>Ακολουθώντας συστάσεις βοηθάει τους νέους χρήστες να βρουν γρήγορα ενδιαφέρον περιεχόμενο</strong>. Όταν ένας χρήστης δεν έχει αλληλεπιδράσει με άλλους αρκετά για να διαμορφώσει εξατομικευμένες συστάσεις, συνιστώνται αυτοί οι λογαριασμοί. Υπολογίζονται εκ νέου σε καθημερινή βάση από ένα σύνολο λογαριασμών με τις υψηλότερες πρόσφατες αλληλεπιδράσεις και μεγαλύτερο αριθμό τοπικών ακόλουθων για μια δεδομένη γλώσσα."
       language: Για τη γλώσσα
@@ -860,10 +818,8 @@ el:
       back_to_account: Επιστροφή στη σελίδα λογαριασμού
       back_to_report: Πίσω στη σελίδα αναφοράς
       batch:
-        add_to_report: 'Προσθήκη στην αναφορά #%{id}'
         remove_from_report: Αφαίρεση από την αναφορά
         report: Αναφορά
-      contents: Περιεχόμενα
       deleted: Διαγεγραμμένα
       favourites: Αγαπημένα
       history: Ιστορικό εκδόσεων
@@ -872,17 +828,13 @@ el:
       media:
         title: Πολυμέσα
       metadata: Μεταδεδομένα
-      no_history: Αυτή η ανάρτηση δεν έχει επεξεργαστεί
       no_status_selected: Καμία δημοσίευση δεν άλλαξε αφού καμία δεν ήταν επιλεγμένη
       open: Άνοιγμα ανάρτησης
       original_status: Αρχική ανάρτηση
       reblogs: Αναδημοσιεύσεις
-      replied_to_html: Απάντησε στον χρήστη %{acct_link}
       status_changed: Η ανάρτηση άλλαξε
-      status_title: Ανάρτηση από @%{name}
-      title: Αναρτήσεις λογαριασμού - @%{name}
+      title: Καταστάσεις λογαριασμού
       trending: Τάσεις
-      view_publicly: Προβολή δημόσια
       visibility: Ορατότητα
       with_media: Με πολυμέσα
     strikes:
@@ -959,35 +911,6 @@ el:
       search: Αναζήτηση
       title: Ετικέτες
       updated_msg: Οι ρυθμίσεις των ετικετών ενημερώθηκαν επιτυχώς
-    terms_of_service:
-      back: Πίσω στους όρους παροχής υπηρεσιών
-      changelog: Τι άλλαξε
-      create: Χρησιμοποιήστε τους δικούς σου
-      current: Τρέχουσα
-      draft: Πρόχειρο
-      generate: Χρήση προτύπου
-      generates:
-        action: Δημιουργία
-        chance_to_review_html: "<strong>Οι παραγόμενοι όροι υπηρεσίας δε θα δημοσιεύονται αυτόματα.</strong> Θα έχεις την ευκαιρία να εξετάσεις το αποτέλεσμα. Παρακαλούμε συμπλήρωσε τις απαιτούμενες πληροφορίες για να συνεχίσεις."
-        explanation_html: Το πρότυπο όρων υπηρεσίας που παρέχονται είναι μόνο για ενημερωτικούς σκοπούς και δε θα πρέπει να ερμηνεύονται ως νομικές συμβουλές για οποιοδήποτε θέμα. Παρακαλούμε συμβουλέψου τον νομικό σου σύμβουλο σχετικά με την περίπτωσή σου και τις συγκεκριμένες νομικές ερωτήσεις που έχεις.
-        title: Ρύθμιση Όρων Παροχής Υπηρεσιών
-      history: Ιστορικό
-      live: Ενεργό
-      no_history: Δεν υπάρχουν ακόμα καταγεγραμμένες αλλαγές στους όρους παροχής υπηρεσιών.
-      no_terms_of_service_html: Δεν έχετε ρυθμίσει τους όρους της υπηρεσίας. Οι όροι της υπηρεσίας αποσκοπούν στην παροχή σαφήνειας και την προστασία σου από πιθανές υποχρεώσεις σε διαφορές με τους χρήστες σου.
-      notified_on_html: Οι χρήστες ειδοποιήθηκαν στις %{date}
-      notify_users: Ειδοποίηση χρηστών
-      preview:
-        explanation_html: 'Το email θα σταλεί σε <strong>%{display_count} χρήστες</strong> που έχουν εγγραφεί πριν από τις %{date}. Το ακόλουθο κείμενο θα συμπεριληφθεί στο e-mail:'
-        send_preview: Αποστολή προεπισκόπησης στο %{email}
-        send_to_all:
-          one: Αποστολή %{display_count} email
-          other: Αποστολή %{display_count} emails
-        title: Προεπισκόπηση ειδοποίησης όρων υπηρεσίας
-      publish: Δημοσίευση
-      published_on_html: Δημοσιεύτηκε στις %{date}
-      save_draft: Αποθήκευση προχείρου
-      title: Όροι Παροχής Υπηρεσιών
     title: Διαχείριση
     trends:
       allow: Επιτρέπεται
@@ -1195,6 +1118,7 @@ el:
     migrate_account: Μεταφορά σε διαφορετικό λογαριασμό
     migrate_account_html: Αν θέλεις να ανακατευθύνεις αυτό τον λογαριασμό σε έναν διαφορετικό, μπορείς να το <a href="%{path}">διαμορφώσεις εδώ</a>.
     or_log_in_with: Ή συνδέσου με
+    privacy_policy_agreement_html: Έχω διαβάσει και συμφωνώ με την <a href="%{privacy_policy_path}" target="_blank">πολιτική απορρήτου</a>
     progress:
       confirm: Επιβεβαίωση email
       details: Τα στοιχεία σας
@@ -1219,7 +1143,7 @@ el:
     set_new_password: Ορισμός νέου συνθηματικού
     setup:
       email_below_hint_html: Έλεγξε τον φάκελο ανεπιθύμητης αλληλογραφίας ή ζήτα καινούργιο. Μπορείς να διορθώσεις τη διεύθυνση email σου αν είναι λάθος.
-      email_settings_hint_html: Κάνε κλικ στο σύνδεσμο που στείλαμε στο %{email} για να αρχίσεις να χρησιμοποιείς το Mastodon. Θα περιμένουμε εδώ.
+      email_settings_hint_html: Πάτησε το σύνδεσμο που σου στείλαμε για να επαληθεύσεις το %{email}. Θα σε περιμένουμε εδώ.
       link_not_received: Δεν έλαβες τον σύνδεσμο;
       new_confirmation_instructions_sent: Θα λάβεις ένα νέο email με το σύνδεσμο επιβεβαίωσης σε λίγα λεπτά!
       title: Ελέγξτε τα εισερχόμενά σας
@@ -1228,7 +1152,7 @@ el:
       title: Συνδεθείτε στο %{domain}
     sign_up:
       manual_review: Οι εγγραφές στο %{domain} περνούν από χειροκίνητη αξιολόγηση από τους συντονιστές μας. Για να μας βοηθήσεις να επεξεργαστούμε την εγγραφή σου, γράψε λίγα λόγια για τον εαυτό σου και γιατί θέλεις έναν λογαριασμό στο %{domain}.
-      preamble: Με έναν λογαριασμό σ' αυτόν τον διακομιστή Mastodon, θα μπορείς να ακολουθήσεις οποιοδήποτε άλλο άτομο στο δίκτυο, ανεξάρτητα από το πού φιλοξενείται ο λογαριασμός του.
+      preamble: Με έναν λογαριασμό σ' αυτόν τον διακομιστή Mastodon, θα μπορείτε να ακολουθήσετε οποιοδήποτε άλλο άτομο στο δίκτυο, ανεξάρτητα από το πού φιλοξενείται ο λογαριασμός του.
       title: Ας ξεκινήσουμε τις ρυθμίσεις στο %{domain}.
     status:
       account_status: Κατάσταση λογαριασμού
@@ -1240,8 +1164,6 @@ el:
       view_strikes: Προβολή προηγούμενων ποινών εναντίον του λογαριασμού σας
     too_fast: Η φόρμα υποβλήθηκε πολύ γρήγορα, προσπαθήστε ξανά.
     use_security_key: Χρήση κλειδιού ασφαλείας
-    user_agreement_html: Έχω διαβάσει με τους <a href="%{terms_of_service_path}" target="_blank">όρους παροχής υπηρεσιών</a> και την <a href="%{privacy_policy_path}" target="_blank">πολιτική απορρήτου</a>
-    user_privacy_agreement_html: Έχω διαβάσει και συμφωνώ με την πολιτική απορρήτου <a href="%{privacy_policy_path}" target="_blank"></a>
   author_attribution:
     example_title: Δείγμα κειμένου
     hint_html: Γράφεις ειδήσεις ή blog άρθρα εκτός του Mastodon; Έλεγξε πώς μπορείς να πάρεις τα εύσημα όταν μοιράζονται στο Mastodon.
@@ -1447,43 +1369,19 @@ el:
       overwrite: Αντικατάσταση
       overwrite_long: Αντικατάσταση των υπαρχόντων εγγράφων με τις καινούργιες
     overwrite_preambles:
-      blocking_html:
-        one: Πρόκειται να <strong>αντικαταστήσεις τη λίστα αποκλεισμών</strong> με έως και <strong>%{count} λογαριασμό</strong> από το <strong>%{filename}</strong>.
-        other: Πρόκειται να <strong>αντικαταστήσεις τη λίστα αποκλεισμών</strong> με έως και <strong>%{count} λογαριασμούς</strong> από το <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Πρόκειται να <strong>αντικαταστήσεις τους σελιδοδείκτες σου</strong> με έως και <strong>%{count} ανάρτηση</strong> από το <strong>%{filename}</strong>.
-        other: Πρόκειται να <strong>αντικαταστήσεις τους σελιδοδείκτες σου</strong> με έως και <strong>%{count} αναρτήσεις</strong> από το <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Πρόκειται να <strong>αντικαταστήσεις τη λίστα αποκλεισμών τομέων</strong> με έως και <strong>%{count} τομέα</strong> από το <strong>%{filename}</strong>.
-        other: Πρόκειται να <strong>αντικαταστήσεις τη λίστα αποκλεισμών τομέων</strong> με έως και <strong>%{count} τομείς</strong> από το <strong>%{filename}</strong>.
-      following_html:
-        one: Πρόκειται να <strong>ακολουθήσεις</strong> μέχρι <strong>%{count} λογαριασμό</strong> από το <strong>%{filename}</strong> και <strong>να σταματήσεις να ακολουθείς οποιονδήποτε άλλο</strong>.
-        other: Πρόκειται να <strong>ακολουθήσεις</strong> μέχρι <strong>%{count} λογαριασμούς</strong> από το <strong>%{filename}</strong> και <strong>να σταματήσεις να ακολουθείς οποιονδήποτε άλλο</strong>.
-      lists_html:
-        one: Πρόκειται να <strong>αντικαταστήσεις τις λίστες σου</strong> με περιεχόμενο του <strong>%{filename}</strong>. Μέχρι <strong>%{count} λογαριασμός</strong> θα προστεθεί σε νέες λίστες.
-        other: Πρόκειται να <strong>αντικαταστήσεις τις λίστες σου</strong> με περιεχόμενο του <strong>%{filename}</strong>. Μέχρι <strong>%{count} λογαριασμοί</strong> θα προστεθούν σε νέες λίστες.
-      muting_html:
-        one: Πρόκειται να <strong>αντικαταστήσεις τη λίστα λογαριασμών σε σίγαση</strong> με έως και <strong>%{count} λογαριασμό</strong> από το <strong>%{filename}</strong>.
-        other: Πρόκειται να <strong>αντικαταστήσεις τη λίστα λογαριασμών σε σίγαση</strong> με έως και <strong>%{count} λογαριασμούς</strong> από το <strong>%{filename}</strong>.
+      blocking_html: Πρόκειται να <strong>αντικαταστήσεις τη λίστα αποκλεισμών</strong> με έως και <strong>%{total_items} λογαριασμούς</strong> από το <strong>%{filename}</strong>.
+      bookmarks_html: Πρόκειται να <strong>αντικαταστήσεις τους σελιδοδείκτες σου</strong> με έως και <strong>%{total_items} αναρτήσεις</strong> από το <strong>%{filename}</strong>.
+      domain_blocking_html: Πρόκειται να <strong>αντικαταστήσεις τη λίστα αποκλεισμών τομέων</strong> με έως και <strong>%{total_items} τομείς</strong> από το <strong>%{filename}</strong>.
+      following_html: Πρόκειται να <strong>ακολουθήσεις</strong> μέχρι <strong>%{total_items} λογαριασμούς</strong> από το <strong>%{filename}</strong> και <strong>να σταματήσεις να ακολουθείς οποιονδήποτε άλλο</strong>.
+      lists_html: Πρόκειται να <strong>αντικαταστήσεις τις λίστες σου</strong> με περιεχόμενο του <strong>%{filename}</strong>. Μέχρι <strong>%{total_items} λογαριασμοί</strong> θα προστεθούν σε νέες λίστες.
+      muting_html: Πρόκειται να <strong>αντικαταστήσεις τη λίστα λογαριασμών σε σίγαση</strong> με έως και <strong>%{total_items} λογαριασμούς</strong> από το <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Πρόκειται να <strong>αποκλείσεις</strong> έως και <strong>%{count} λογαριασμό</strong> από το <strong>%{filename}</strong>.
-        other: Πρόκειται να <strong>αποκλείσεις</strong> έως και <strong>%{count} λογαριασμούς</strong> από το <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Πρόκειται να προσθέσεις μέχρι και <strong>%{count} ανάρτηση</strong> από το <strong>%{filename}</strong> στους <strong>σελιδοδείκτες</strong> σου.
-        other: Πρόκειται να προσθέσεις μέχρι και <strong>%{count} αναρτήσεις</strong> από το <strong>%{filename}</strong> στους <strong>σελιδοδείκτες</strong> σου.
-      domain_blocking_html:
-        one: Πρόκειται να <strong>αποκλείσεις</strong> έως και <strong>%{count} τόμεα</strong> από το <strong>%{filename}</strong>.
-        other: Πρόκειται να <strong>αποκλείσεις</strong> έως και <strong>%{count} τομείς</strong> από το <strong>%{filename}</strong>.
-      following_html:
-        one: Πρόκειται να <strong>ακολουθήσεις</strong> έως και <strong>%{count} λογαριασμό</strong> από το <strong>%{filename}</strong>.
-        other: Πρόκειται να <strong>ακολουθήσεις</strong> έως και <strong>%{count} λογαριασμούς</strong> από το <strong>%{filename}</strong>.
-      lists_html:
-        one: Πρόκειται να προσθέσεις μέχρι και <strong>%{count} λογαριασμό</strong> από το <strong>%{filename}</strong> στις <strong>λίστες</strong> σου. Θα δημιουργηθούν νέες λίστες αν δεν υπάρχει λίστα για προσθήκη.
-        other: Πρόκειται να προσθέσεις μέχρι και <strong>%{count} λογαριασμούς</strong> από το <strong>%{filename}</strong> στις <strong>λίστες</strong> σου. Θα δημιουργηθούν νέες λίστες αν δεν υπάρχει λίστα για προσθήκη.
-      muting_html:
-        one: Πρόκειται να <strong>κάνεις σίγαση</strong> σε έως και <strong>%{count} λογαριασμό</strong> από το <strong>%{filename}</strong>.
-        other: Πρόκειται να <strong>κάνεις σίγαση</strong> σε έως και <strong>%{count} λογαριασμούς</strong> από το <strong>%{filename}</strong>.
+      blocking_html: Πρόκειται να <strong>αποκλείσεις</strong> έως και <strong>%{total_items} λογαριασμούς</strong> από το <strong>%{filename}</strong>.
+      bookmarks_html: Πρόκειται να προσθέσεις μέχρι και <strong>%{total_items} αναρτήσεις</strong> από το <strong>%{filename}</strong> στους <strong>σελιδοδείκτες</strong> σου.
+      domain_blocking_html: Πρόκειται να <strong>αποκλείσεις</strong> έως και <strong>%{total_items} τομείς</strong> από το <strong>%{filename}</strong>.
+      following_html: Πρόκειται να <strong>ακολουθήσεις</strong> έως και <strong>%{total_items} λογαριασμούς</strong> από το <strong>%{filename}</strong>.
+      lists_html: Πρόκειται να προσθέσεις μέχρι και <strong>%{total_items} λογαριασμούς</strong> από το <strong>%{filename}</strong> στις <strong>λίστες</strong> σου. Θα δημιουργηθούν νέες λίστες αν δεν υπάρχει λίστα για προσθήκη.
+      muting_html: Πρόκειται να <strong>κάνεις σίγαση</strong> σε έως και <strong>%{total_items} λογαριασμούς</strong> από το <strong>%{filename}</strong>.
     preface: Μπορείς να εισάγεις τα δεδομένα που έχεις εξάγει από άλλο διακομιστή, όπως τη λίστα των ατόμων που ακολουθείς ή έχεις αποκλείσει.
     recent_imports: Πρόσφατες Εισαγωγές
     states:
@@ -1740,7 +1638,7 @@ el:
   scheduled_statuses:
     over_daily_limit: Έχεις υπερβεί το όριο των %{limit} προγραμματισμένων αναρτήσεων για εκείνη τη μέρα
     over_total_limit: Έχεις υπερβεί το όριο των %{limit} προγραμματισμένων αναρτήσεων
-    too_soon: η ημερομηνία πρέπει να είναι στο μέλλον
+    too_soon: Η προγραμματισμένη ημερομηνία πρέπει να είναι στο μέλλον
   self_destruct:
     lead_html: Δυστυχώς, το <strong>%{domain}</strong> κλείνει οριστικά. Αν είχατε λογαριασμό εκεί, δεν θα μπορείτε να συνεχίσετε τη χρήση του, αλλά μπορείτε ακόμα να ζητήσετε ένα αντίγραφο ασφαλείας των δεδομένων σας.
     title: Αυτός ο διακομιστής κλείνει οριστικά
@@ -1903,8 +1801,6 @@ el:
       too_late: Είναι πολύ αργά για να κάνεις έφεση σε αυτό το παράπτωμα
   tags:
     does_not_match_previous_name: δεν ταιριάζει με το προηγούμενο όνομα
-  terms_of_service:
-    title: Όροι Παροχής Υπηρεσιών
   themes:
     contrast: Mastodon (Υψηλή αντίθεση)
     default: Mastodon (Σκοτεινό)
@@ -1965,13 +1861,6 @@ el:
       further_actions_html: Αν δεν ήσουν εσύ, σας συνιστούμε να %{action} αμέσως και να ενεργοποιήσεις τον έλεγχο ταυτοποίησης δύο παραγόντων για να διατηρήσεις τον λογαριασμό σου ασφαλή.
       subject: Ο λογαριασμός σου έχει συνδεθεί από μια νέα διεύθυνση IP
       title: Μια νέα σύνδεση
-    terms_of_service_changed:
-      agreement: Συνεχίζοντας να χρησιμοποιείς το %{domain}, συμφωνείς με αυτούς τους όρους. Αν διαφωνείς με τους ενημερωμένους όρους, μπορείς να τερματίσεις τη συμφωνία σου με το %{domain} ανά πάσα στιγμή διαγράφοντας τον λογαριασμό σου.
-      changelog: 'Με μια ματιά, αυτό σημαίνει αυτή η ενημέρωση για σένα:'
-      sign_off: Η ομάδα του %{domain}
-      subject: Ενημερώσεις στους όρους παροχής υπηρεσιών μας
-      subtitle: Οι όροι παροχής υπηρεσιών του %{domain} αλλάζουν
-      title: Σημαντική ενημέρωση
     warning:
       appeal: Υποβολή έφεσης
       appeal_description: Αν πιστεύεις ότι έγινε λάθος, μπορείς να υποβάλεις μια αίτηση στο προσωπικό του %{instance}.
diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml
index 92934f50f0..42eae026fb 100644
--- a/config/locales/en-GB.yml
+++ b/config/locales/en-GB.yml
@@ -187,7 +187,6 @@ en-GB:
         create_domain_block: Create Domain Block
         create_email_domain_block: Create Email Domain Block
         create_ip_block: Create IP rule
-        create_relay: Create Relay
         create_unavailable_domain: Create Unavailable Domain
         create_user_role: Create Role
         demote_user: Demote User
@@ -199,22 +198,18 @@ en-GB:
         destroy_email_domain_block: Delete Email Domain Block
         destroy_instance: Purge Domain
         destroy_ip_block: Delete IP rule
-        destroy_relay: Delete Relay
         destroy_status: Delete Post
         destroy_unavailable_domain: Delete Unavailable Domain
         destroy_user_role: Destroy Role
         disable_2fa_user: Disable 2FA
         disable_custom_emoji: Disable Custom Emoji
-        disable_relay: Disable Relay
         disable_sign_in_token_auth_user: Disable Email Token Authentication for User
         disable_user: Disable User
         enable_custom_emoji: Enable Custom Emoji
-        enable_relay: Enable Relay
         enable_sign_in_token_auth_user: Enable Email Token Authentication for User
         enable_user: Enable User
         memorialize_account: Memorialise Account
         promote_user: Promote User
-        publish_terms_of_service: Publish Terms of Service
         reject_appeal: Reject Appeal
         reject_user: Reject User
         remove_avatar_user: Remove Avatar
@@ -252,7 +247,6 @@ en-GB:
         create_domain_block_html: "%{name} blocked domain %{target}"
         create_email_domain_block_html: "%{name} blocked email domain %{target}"
         create_ip_block_html: "%{name} created rule for IP %{target}"
-        create_relay_html: "%{name} created a relay %{target}"
         create_unavailable_domain_html: "%{name} stopped delivery to domain %{target}"
         create_user_role_html: "%{name} created %{target} role"
         demote_user_html: "%{name} demoted user %{target}"
@@ -264,22 +258,18 @@ en-GB:
         destroy_email_domain_block_html: "%{name} unblocked email domain %{target}"
         destroy_instance_html: "%{name} purged domain %{target}"
         destroy_ip_block_html: "%{name} deleted rule for IP %{target}"
-        destroy_relay_html: "%{name} deleted the relay %{target}"
         destroy_status_html: "%{name} removed post by %{target}"
         destroy_unavailable_domain_html: "%{name} stopped delivery to domain %{target}"
         destroy_user_role_html: "%{name} deleted %{target} role"
         disable_2fa_user_html: "%{name} disabled two factor requirement for user %{target}"
         disable_custom_emoji_html: "%{name} disabled emoji %{target}"
-        disable_relay_html: "%{name} disabled the relay %{target}"
         disable_sign_in_token_auth_user_html: "%{name} disabled email token authentication for %{target}"
         disable_user_html: "%{name} disabled login for user %{target}"
         enable_custom_emoji_html: "%{name} enabled emoji %{target}"
-        enable_relay_html: "%{name} enabled the relay %{target}"
         enable_sign_in_token_auth_user_html: "%{name} enabled email token authentication for %{target}"
         enable_user_html: "%{name} enabled login for user %{target}"
         memorialize_account_html: "%{name} turned %{target}'s account into a memoriam page"
         promote_user_html: "%{name} promoted user %{target}"
-        publish_terms_of_service_html: "%{name} published updates to the terms of service"
         reject_appeal_html: "%{name} rejected moderation decision appeal from %{target}"
         reject_user_html: "%{name} rejected sign-up from %{target}"
         remove_avatar_user_html: "%{name} removed %{target}'s avatar"
@@ -828,10 +818,8 @@ en-GB:
       back_to_account: Back to account page
       back_to_report: Back to report page
       batch:
-        add_to_report: 'Add to report #%{id}'
         remove_from_report: Remove from report
         report: Report
-      contents: Contents
       deleted: Deleted
       favourites: Favourites
       history: Version history
@@ -840,17 +828,13 @@ en-GB:
       media:
         title: Media
       metadata: Metadata
-      no_history: This post hasn't been edited
       no_status_selected: No posts were changed as none were selected
       open: Open post
       original_status: Original post
       reblogs: Reblogs
-      replied_to_html: Replied to %{acct_link}
       status_changed: Post changed
-      status_title: Post by @%{name}
-      title: Account posts - @%{name}
+      title: Account posts
       trending: Trending
-      view_publicly: View publicly
       visibility: Visibility
       with_media: With media
     strikes:
@@ -927,35 +911,6 @@ en-GB:
       search: Search
       title: Hashtags
       updated_msg: Hashtag settings updated successfully
-    terms_of_service:
-      back: Back to terms of service
-      changelog: What's changed
-      create: Use your own
-      current: Current
-      draft: Draft
-      generate: Use template
-      generates:
-        action: Generate
-        chance_to_review_html: "<strong>The generated terms of service will not be published automatically.</strong> You will have a chance to review the results. Please fill in the necessary details to proceed."
-        explanation_html: The terms of service template provided is for informational purposes only, and should not be construed as legal advice on any subject matter. Please consult with your own legal counsel on your situation and specific legal questions you have.
-        title: Terms of Service Setup
-      history: History
-      live: Live
-      no_history: There are no recorded changes of the terms of service yet.
-      no_terms_of_service_html: You don't currently have any terms of service configured. Terms of service are meant to provide clarity and protect you from potential liabilities in disputes with your users.
-      notified_on_html: Users notified on %{date}
-      notify_users: Notify users
-      preview:
-        explanation_html: 'The email will be sent to <strong>%{display_count} users</strong> who have signed up before %{date}. The following text will be included in the e-mail:'
-        send_preview: Send preview to %{email}
-        send_to_all:
-          one: Send %{display_count} email
-          other: Send %{display_count} emails
-        title: Preview terms of service notification
-      publish: Publish
-      published_on_html: Published on %{date}
-      save_draft: Save draft
-      title: Terms of Service
     title: Administration
     trends:
       allow: Allow
@@ -1163,6 +1118,7 @@ en-GB:
     migrate_account: Move to a different account
     migrate_account_html: If you wish to redirect this account to a different one, you can <a href="%{path}">configure it here</a>.
     or_log_in_with: Or log in with
+    privacy_policy_agreement_html: I have read and agree to the <a href="%{privacy_policy_path}" target="_blank">privacy policy</a>
     progress:
       confirm: Confirm email
       details: Your details
@@ -1187,7 +1143,7 @@ en-GB:
     set_new_password: Set new password
     setup:
       email_below_hint_html: Check your spam folder, or request another one. You can correct your email address if it's wrong.
-      email_settings_hint_html: Click the link we sent to %{email} to begin using Mastodon. We'll wait right here.
+      email_settings_hint_html: Click the link we sent you to verify %{email}. We'll wait right here.
       link_not_received: Didn't get a link?
       new_confirmation_instructions_sent: You will receive a new email with the confirmation link in a few minutes!
       title: Check your inbox
@@ -1196,7 +1152,7 @@ en-GB:
       title: Log in to %{domain}
     sign_up:
       manual_review: Sign-ups on %{domain} go through manual review by our moderators. To help us process your registration, write a bit about yourself and why you want an account on %{domain}.
-      preamble: With an account on this Mastodon server, you'll be able to follow any other person on the Fediverse, regardless of where their account is hosted.
+      preamble: With an account on this Mastodon server, you'll be able to follow any other person on the network, regardless of where their account is hosted.
       title: Let's get you set up on %{domain}.
     status:
       account_status: Account status
@@ -1208,8 +1164,6 @@ en-GB:
       view_strikes: View past strikes against your account
     too_fast: Form submitted too fast, try again.
     use_security_key: Use security key
-    user_agreement_html: I have read and agree to the <a href="%{terms_of_service_path}" target="_blank">terms of service</a> and <a href="%{privacy_policy_path}" target="_blank">privacy policy</a>
-    user_privacy_agreement_html: I have read and agree to the <a href="%{privacy_policy_path}" target="_blank">privacy policy</a>
   author_attribution:
     example_title: Sample text
     hint_html: Are you writing news or blog articles outside of Mastodon? Control how you get credited when they are shared on Mastodon.
@@ -1415,43 +1369,19 @@ en-GB:
       overwrite: Overwrite
       overwrite_long: Replace current records with the new ones
     overwrite_preambles:
-      blocking_html:
-        one: You are about to <strong>replace your block list</strong> with up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>replace your block list</strong> with up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: You are about to <strong>replace your bookmarks</strong> with up to <strong>%{count} post</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>replace your bookmarks</strong> with up to <strong>%{count} posts</strong> from <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>.
-      following_html:
-        one: You are about to <strong>follow</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>.
-        other: You are about to <strong>follow</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>.
-      lists_html:
-        one: You are about to <strong>replace your lists</strong> with contents of <strong>%{filename}</strong>. Up to <strong>%{count} account</strong> will be added to new lists.
-        other: You are about to <strong>replace your lists</strong> with contents of <strong>%{filename}</strong>. Up to <strong>%{count} accounts</strong> will be added to new lists.
-      muting_html:
-        one: You are about to <strong>replace your list of muted account</strong> with up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>replace your list of muted accounts</strong> with up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
+      blocking_html: You are about to <strong>replace your block list</strong> with up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
+      bookmarks_html: You are about to <strong>replace your bookmarks</strong> with up to <strong>%{total_items} posts</strong> from <strong>%{filename}</strong>.
+      domain_blocking_html: You are about to <strong>replace your domain block list</strong> with up to <strong>%{total_items} domains</strong> from <strong>%{filename}</strong>.
+      following_html: You are about to <strong>follow</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>.
+      lists_html: You are about to <strong>replace your lists</strong> with contents of <strong>%{filename}</strong>. Up to <strong>%{total_items} accounts</strong> will be added to new lists.
+      muting_html: You are about to <strong>replace your list of muted accounts</strong> with up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: You are about to <strong>block</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>block</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: You are about to add up to <strong>%{count} post</strong> from <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
-        other: You are about to add up to <strong>%{count} posts</strong> from <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
-      domain_blocking_html:
-        one: You are about to <strong>block</strong> up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>block</strong> up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>.
-      following_html:
-        one: You are about to <strong>follow</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>follow</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
-      lists_html:
-        one: You are about to add up to <strong>%{count} account</strong> from <strong>%{filename}</strong> to your <strong>lists</strong>. New lists will be created if there is no list to add to.
-        other: You are about to add up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong> to your <strong>lists</strong>. New lists will be created if there is no list to add to.
-      muting_html:
-        one: You are about to <strong>mute</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>mute</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
+      blocking_html: You are about to <strong>block</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
+      bookmarks_html: You are about to add up to <strong>%{total_items} posts</strong> from <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
+      domain_blocking_html: You are about to <strong>block</strong> up to <strong>%{total_items} domains</strong> from <strong>%{filename}</strong>.
+      following_html: You are about to <strong>follow</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
+      lists_html: You are about to add up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong> to your <strong>lists</strong>. New lists will be created if there is no list to add to.
+      muting_html: You are about to <strong>mute</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
     preface: You can import data that you have exported from another server, such as a list of the people you are following or blocking.
     recent_imports: Recent imports
     states:
@@ -1708,7 +1638,7 @@ en-GB:
   scheduled_statuses:
     over_daily_limit: You have exceeded the limit of %{limit} scheduled posts for today
     over_total_limit: You have exceeded the limit of %{limit} scheduled posts
-    too_soon: date must be in the future
+    too_soon: The scheduled date must be in the future
   self_destruct:
     lead_html: Unfortunately, <strong>%{domain}</strong> is permanently closing down. If you had an account there, you will not be able to continue using it, but you can still request a backup of your data.
     title: This server is closing down
@@ -1871,8 +1801,6 @@ en-GB:
       too_late: It is too late to appeal this strike
   tags:
     does_not_match_previous_name: does not match the previous name
-  terms_of_service:
-    title: Terms of Service
   themes:
     contrast: Mastodon (High contrast)
     default: Mastodon (Dark)
@@ -1933,13 +1861,6 @@ en-GB:
       further_actions_html: If this wasn't you, we recommend that you %{action} immediately and enable two-factor authentication to keep your account secure.
       subject: Your account has been accessed from a new IP address
       title: A new login
-    terms_of_service_changed:
-      agreement: By continuing to use %{domain}, you are agreeing to these terms. If you disagree with the updated terms, you may terminate your agreement with %{domain} at any time by deleting your account.
-      changelog: 'At a glance, here is what this update means for you:'
-      sign_off: The %{domain} team
-      subject: Updates to our terms of service
-      subtitle: The terms of service of %{domain} are changing
-      title: Important update
     warning:
       appeal: Submit an appeal
       appeal_description: If you believe this is an error, you can submit an appeal to the staff of %{instance}.
diff --git a/config/locales/en.yml b/config/locales/en.yml
index dca6a2d9f8..6d042c747e 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -193,7 +193,6 @@ en:
         create_domain_block: Create Domain Block
         create_email_domain_block: Create Email Domain Block
         create_ip_block: Create IP rule
-        create_relay: Create Relay
         create_unavailable_domain: Create Unavailable Domain
         create_user_role: Create Role
         demote_user: Demote User
@@ -205,24 +204,20 @@ en:
         destroy_email_domain_block: Delete Email Domain Block
         destroy_instance: Purge Domain
         destroy_ip_block: Delete IP rule
-        destroy_relay: Delete Relay
         destroy_status: Delete Post
         destroy_unavailable_domain: Delete Unavailable Domain
         destroy_user_role: Destroy Role
         disable_2fa_user: Disable 2FA
         disable_custom_emoji: Disable Custom Emoji
-        disable_relay: Disable Relay
         disable_sign_in_token_auth_user: Disable Email Token Authentication for User
         disable_user: Disable User
         enable_custom_emoji: Enable Custom Emoji
-        enable_relay: Enable Relay
         enable_sign_in_token_auth_user: Enable Email Token Authentication for User
         enable_user: Enable User
         force_cw_status: Add CW to Post
         force_sensitive_status: Add Sensitive Flag to Post
         memorialize_account: Memorialize Account
         promote_user: Promote User
-        publish_terms_of_service: Publish Terms of Service
         reject_appeal: Reject Appeal
         reject_remote_account: Reject Remote Account
         reject_user: Reject User
@@ -262,7 +257,6 @@ en:
         create_domain_block_html: "%{name} blocked domain %{target}"
         create_email_domain_block_html: "%{name} blocked email domain %{target}"
         create_ip_block_html: "%{name} created rule for IP %{target}"
-        create_relay_html: "%{name} created a relay %{target}"
         create_unavailable_domain_html: "%{name} stopped delivery to domain %{target}"
         create_user_role_html: "%{name} created %{target} role"
         demote_user_html: "%{name} demoted user %{target}"
@@ -274,24 +268,20 @@ en:
         destroy_email_domain_block_html: "%{name} unblocked email domain %{target}"
         destroy_instance_html: "%{name} purged domain %{target}"
         destroy_ip_block_html: "%{name} deleted rule for IP %{target}"
-        destroy_relay_html: "%{name} deleted the relay %{target}"
         destroy_status_html: "%{name} removed post by %{target}"
         destroy_unavailable_domain_html: "%{name} resumed delivery to domain %{target}"
         destroy_user_role_html: "%{name} deleted %{target} role"
         disable_2fa_user_html: "%{name} disabled two factor requirement for user %{target}"
         disable_custom_emoji_html: "%{name} disabled emoji %{target}"
-        disable_relay_html: "%{name} disabled the relay %{target}"
         disable_sign_in_token_auth_user_html: "%{name} disabled email token authentication for %{target}"
         disable_user_html: "%{name} disabled login for user %{target}"
         enable_custom_emoji_html: "%{name} enabled emoji %{target}"
-        enable_relay_html: "%{name} enabled the relay %{target}"
         enable_sign_in_token_auth_user_html: "%{name} enabled email token authentication for %{target}"
         enable_user_html: "%{name} enabled login for user %{target}"
         force_cw_status_html: "%{name} turned post by %{target} cw"
         force_sensitive_status_html: "%{name} turned post by %{target} sensitive"
         memorialize_account_html: "%{name} turned %{target}'s account into a memoriam page"
         promote_user_html: "%{name} promoted user %{target}"
-        publish_terms_of_service_html: "%{name} published updates to the terms of service"
         reject_appeal_html: "%{name} rejected moderation decision appeal from %{target}"
         reject_remote_account_html: "%{name} rejected %{target} join on this server"
         reject_user_html: "%{name} rejected sign-up from %{target}"
@@ -325,7 +315,6 @@ en:
       title: Audit log
       unavailable_instance: "(domain name unavailable)"
     announcements:
-      back: Back to announcements
       destroyed_msg: Announcement successfully deleted!
       edit:
         title: Edit announcement
@@ -334,10 +323,6 @@ en:
       new:
         create: Create announcement
         title: New announcement
-      preview:
-        disclaimer: As users cannot opt out of them, email notifications should be limited to important announcements such as personal data breach or server closure notifications.
-        explanation_html: 'The email will be sent to <strong>%{display_count} users</strong>. The following text will be included in the e-mail:'
-        title: Preview announcement notification
       publish: Publish
       published_msg: Announcement successfully published!
       scheduled_for: Scheduled for %{time}
@@ -530,36 +515,6 @@ en:
       new:
         title: Import domain blocks
       no_file: No file selected
-    fasp:
-      debug:
-        callbacks:
-          created_at: Created at
-          delete: Delete
-          ip: IP address
-          request_body: Request body
-          title: Debug Callbacks
-      providers:
-        active: Active
-        base_url: Base URL
-        callback: Callback
-        delete: Delete
-        edit: Edit Provider
-        finish_registration: Finish registration
-        name: Name
-        providers: Providers
-        public_key_fingerprint: Public key fingerprint
-        registration_requested: Registration requested
-        registrations:
-          confirm: Confirm
-          description: You received a registration from a FASP. Reject it if you did not initiate this. If you initiated this, carefully compare name and key fingerprint before confirming the registration.
-          reject: Reject
-          title: Confirm FASP Registration
-        save: Save
-        select_capabilities: Select Capabilities
-        sign_in: Sign In
-        status: Status
-        title: Fediverse Auxiliary Service Providers
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Follow recommendations help new users quickly find interesting content</strong>. When a user has not interacted with others enough to form personalized follow recommendations, these accounts are recommended instead. They are re-calculated on a daily basis from a mix of accounts with the highest recent engagements and highest local follower counts for a given language."
       language: For language
@@ -1122,10 +1077,8 @@ en:
       back_to_account: Back to account page
       back_to_report: Back to report page
       batch:
-        add_to_report: 'Add to report #%{id}'
         remove_from_report: Remove from report
         report: Report
-      contents: Contents
       deleted: Deleted
       favourites: Favorites
       force_cw: Force CW
@@ -1136,20 +1089,16 @@ en:
       media:
         title: Media
       metadata: Metadata
-      no_history: This post hasn't been edited
       no_status_selected: No posts were changed as none were selected
       open: Open post
       original_status: Original post
       reblogs: Reblogs
-      remove: Remove
+      remove: Remove post
       remove_history: Remove edit history
       remove_media: Remove medias
-      replied_to_html: Replied to %{acct_link}
       status_changed: Post changed
-      status_title: Post by @%{name}
-      title: Account posts - @%{name}
+      title: Account posts
       trending: Trending
-      view_publicly: View publicly
       visibility: Visibility
       with_media: With media
     strikes:
@@ -1226,36 +1175,6 @@ en:
       search: Search
       title: Hashtags
       updated_msg: Hashtag settings updated successfully
-    terms_of_service:
-      back: Back to terms of service
-      changelog: What's changed
-      create: Use your own
-      current: Current
-      draft: Draft
-      generate: Use template
-      generates:
-        action: Generate
-        chance_to_review_html: "<strong>The generated terms of service will not be published automatically.</strong> You will have a chance to review the results. Please fill in the necessary details to proceed."
-        explanation_html: The terms of service template provided is for informational purposes only, and should not be construed as legal advice on any subject matter. Please consult with your own legal counsel on your situation and specific legal questions you have.
-        title: Terms of Service Setup
-      going_live_on_html: Live, effective %{date}
-      history: History
-      live: Live
-      no_history: There are no recorded changes of the terms of service yet.
-      no_terms_of_service_html: You don't currently have any terms of service configured. Terms of service are meant to provide clarity and protect you from potential liabilities in disputes with your users.
-      notified_on_html: Users notified on %{date}
-      notify_users: Notify users
-      preview:
-        explanation_html: 'The email will be sent to <strong>%{display_count} users</strong> who have signed up before %{date}. The following text will be included in the e-mail:'
-        send_preview: Send preview to %{email}
-        send_to_all:
-          one: Send %{display_count} email
-          other: Send %{display_count} emails
-        title: Preview terms of service notification
-      publish: Publish
-      published_on_html: Published on %{date}
-      save_draft: Save draft
-      title: Terms of Service
     title: Administration
     trends:
       allow: Allow
@@ -1409,11 +1328,21 @@ en:
     hint_html: If you want to move from another account to this one, here you can create an alias, which is required before you can proceed with moving followers from the old account to this one. This action by itself is <strong>harmless and reversible</strong>. <strong>The account migration is initiated from the old account</strong>.
     remove: Unlink alias
   antennas:
+    contexts:
+      account: Accounts
+      domain: Domains
+      keyword: Keywords
+      tag: Tags
+    edit:
+      available: Available
+      description: Antenna is for all public and local public posts recognized by the server, from all accounts that have not refused to subscribe. Detected posts will be added to the specified list.
+      title: Edit antenna
     errors:
       duplicate_account: Duplicate account
       duplicate_domain: Duplicate domain
       duplicate_keyword: Duplicate keyword
       duplicate_tag: Duplicate tag
+      empty_contexts: No contexts! You must set any context filters
       invalid_list_owner: This list is not yours
       limit:
         accounts: 登録できるアカウント数の上限に達しています
@@ -1424,6 +1353,23 @@ en:
       over_ltl_limit: You have exceeded the limit of %{limit} ltl antennas
       over_stl_limit: You have exceeded the limit of %{limit} stl antennas
       too_short_keyword: Keyword is too short
+    index:
+      accounts:
+        other: "%{count} accounts"
+      contexts: Antennas in %{contexts}
+      delete: Delete
+      disabled: Disabled
+      domains:
+        other: "%{count} domains"
+      empty: You have no antennas.
+      expires_in: Expires in %{distance}
+      expires_on: Expires on %{date}
+      keywords:
+        other: "%{count} keywords"
+      stl: This antenna is in STL mode, ignoring reject-subscription settings.
+      tags:
+        other: "%{count} tags"
+      title: Antennas
   appearance:
     advanced_web_interface: Advanced web interface
     advanced_web_interface_hint: 'If you want to make use of your entire screen width, the advanced web interface allows you to configure many different columns to see as much information at the same time as you want: Home, notifications, federated timeline, any number of lists and hashtags.'
@@ -1491,6 +1437,7 @@ en:
     migrate_account: Move to a different account
     migrate_account_html: If you wish to redirect this account to a different one, you can <a href="%{path}">configure it here</a>.
     or_log_in_with: Or log in with
+    privacy_policy_agreement_html: I have read and agree to the <a href="%{privacy_policy_path}" target="_blank">privacy policy</a>
     progress:
       confirm: Confirm email
       details: Your details
@@ -1518,7 +1465,7 @@ en:
     set_new_password: Set new password
     setup:
       email_below_hint_html: Check your spam folder, or request another one. You can correct your email address if it's wrong.
-      email_settings_hint_html: Click the link we sent to %{email} to begin using Mastodon. We'll wait right here.
+      email_settings_hint_html: Click the link we sent you to verify %{email}. We'll wait right here.
       link_not_received: Didn't get a link?
       new_confirmation_instructions_sent: You will receive a new email with the confirmation link in a few minutes!
       title: Check your inbox
@@ -1527,7 +1474,7 @@ en:
       title: Login to %{domain}
     sign_up:
       manual_review: Sign-ups on %{domain} go through manual review by our moderators. To help us process your registration, write a bit about yourself and why you want an account on %{domain}.
-      preamble: With an account on this Mastodon server, you'll be able to follow any other person on the fediverse, regardless of where their account is hosted.
+      preamble: With an account on this Mastodon server, you'll be able to follow any other person on the network, regardless of where their account is hosted.
       title: Let's get you set up on %{domain}.
     status:
       account_status: Account status
@@ -1539,8 +1486,6 @@ en:
       view_strikes: View past strikes against your account
     too_fast: Form submitted too fast, try again.
     use_security_key: Use security key
-    user_agreement_html: I have read and agree to the <a href="%{terms_of_service_path}" target="_blank">terms of service</a> and <a href="%{privacy_policy_path}" target="_blank">privacy policy</a>
-    user_privacy_agreement_html: I have read and agree to the <a href="%{privacy_policy_path}" target="_blank">privacy policy</a>
     with_login_options: Will you disable your custom css?
   author_attribution:
     example_title: Sample text
@@ -1754,43 +1699,19 @@ en:
       overwrite: Overwrite
       overwrite_long: Replace current records with the new ones
     overwrite_preambles:
-      blocking_html:
-        one: You are about to <strong>replace your block list</strong> with up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>replace your block list</strong> with up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: You are about to <strong>replace your bookmarks</strong> with up to <strong>%{count} post</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>replace your bookmarks</strong> with up to <strong>%{count} posts</strong> from <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>.
-      following_html:
-        one: You are about to <strong>follow</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>.
-        other: You are about to <strong>follow</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>.
-      lists_html:
-        one: You are about to <strong>replace your lists</strong> with contents of <strong>%{filename}</strong>. Up to <strong>%{count} account</strong> will be added to new lists.
-        other: You are about to <strong>replace your lists</strong> with contents of <strong>%{filename}</strong>. Up to <strong>%{count} accounts</strong> will be added to new lists.
-      muting_html:
-        one: You are about to <strong>replace your list of muted account</strong> with up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>replace your list of muted accounts</strong> with up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
+      blocking_html: You are about to <strong>replace your block list</strong> with up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
+      bookmarks_html: You are about to <strong>replace your bookmarks</strong> with up to <strong>%{total_items} posts</strong> from <strong>%{filename}</strong>.
+      domain_blocking_html: You are about to <strong>replace your domain block list</strong> with up to <strong>%{total_items} domains</strong> from <strong>%{filename}</strong>.
+      following_html: You are about to <strong>follow</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>.
+      lists_html: You are about to <strong>replace your lists</strong> with contents of <strong>%{filename}</strong>. Up to <strong>%{total_items} accounts</strong> will be added to new lists.
+      muting_html: You are about to <strong>replace your list of muted accounts</strong> with up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: You are about to <strong>block</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>block</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: You are about to add up to <strong>%{count} post</strong> from <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
-        other: You are about to add up to <strong>%{count} posts</strong> from <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
-      domain_blocking_html:
-        one: You are about to <strong>block</strong> up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>block</strong> up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>.
-      following_html:
-        one: You are about to <strong>follow</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>follow</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
-      lists_html:
-        one: You are about to add up to <strong>%{count} account</strong> from <strong>%{filename}</strong> to your <strong>lists</strong>. New lists will be created if there is no list to add to.
-        other: You are about to add up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong> to your <strong>lists</strong>. New lists will be created if there is no list to add to.
-      muting_html:
-        one: You are about to <strong>mute</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
-        other: You are about to <strong>mute</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
+      blocking_html: You are about to <strong>block</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
+      bookmarks_html: You are about to add up to <strong>%{total_items} posts</strong> from <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
+      domain_blocking_html: You are about to <strong>block</strong> up to <strong>%{total_items} domains</strong> from <strong>%{filename}</strong>.
+      following_html: You are about to <strong>follow</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
+      lists_html: You are about to add up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong> to your <strong>lists</strong>. New lists will be created if there is no list to add to.
+      muting_html: You are about to <strong>mute</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
     preface: You can import data that you have exported from another server, such as a list of the people you are following or blocking.
     recent_imports: Recent imports
     states:
@@ -2078,7 +1999,7 @@ en:
   scheduled_statuses:
     over_daily_limit: You have exceeded the limit of %{limit} scheduled posts for today
     over_total_limit: You have exceeded the limit of %{limit} scheduled posts
-    too_soon: date must be in the future
+    too_soon: The scheduled date must be in the future
   self_destruct:
     lead_html: Unfortunately, <strong>%{domain}</strong> is permanently closing down. If you had an account there, you will not be able to continue using it, but you can still request a backup of your data.
     title: This server is closing down
@@ -2276,8 +2197,6 @@ en:
       too_late: It is too late to appeal this strike
   tags:
     does_not_match_previous_name: does not match the previous name
-  terms_of_service:
-    title: Terms of Service
   themes:
     contrast: Mastodon (High contrast)
     default: Mastodon (Dark)
@@ -2310,10 +2229,6 @@ en:
     recovery_instructions_html: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. <strong>Keep the recovery codes safe</strong>. For example, you may print them and store them with other important documents.
     webauthn: Security keys
   user_mailer:
-    announcement_published:
-      description: 'The administrators of %{domain} are making an announcement:'
-      subject: Service announcement
-      title: "%{domain} service announcement"
     appeal_approved:
       action: Account Settings
       explanation: The appeal of the strike against your account on %{strike_date} that you submitted on %{appeal_date} has been approved. Your account is once again in good standing.
@@ -2343,15 +2258,6 @@ en:
       further_actions_html: If this wasn't you, we recommend that you %{action} immediately and enable two-factor authentication to keep your account secure.
       subject: Your account has been accessed from a new IP address
       title: A new sign-in
-    terms_of_service_changed:
-      agreement: By continuing to use %{domain}, you are agreeing to these terms. If you disagree with the updated terms, you may terminate your agreement with %{domain} at any time by deleting your account.
-      changelog: 'At a glance, here is what this update means for you:'
-      description: 'You are receiving this e-mail because we''re making some changes to our terms of service at %{domain}. These updates will take effect on %{date}. We encourage you to review the updated terms in full here:'
-      description_html: You are receiving this e-mail because we're making some changes to our terms of service at %{domain}. These updates will take effect on <strong>%{date}</strong>. We encourage you to review the <a href="%{path}" target="_blank">updated terms in full here</a>.
-      sign_off: The %{domain} team
-      subject: Updates to our terms of service
-      subtitle: The terms of service of %{domain} are changing
-      title: Important update
     warning:
       appeal: Submit an appeal
       appeal_description: If you believe this is an error, you can submit an appeal to the staff of %{instance}.
diff --git a/config/locales/eo.yml b/config/locales/eo.yml
index a514d332a8..35e96fe1c2 100644
--- a/config/locales/eo.yml
+++ b/config/locales/eo.yml
@@ -187,7 +187,6 @@ eo:
         create_domain_block: Krei Blokadon De Domajno
         create_email_domain_block: Krei retpoŝtan domajnan blokon
         create_ip_block: Krei IP-regulon
-        create_relay: Krei Relajson
         create_unavailable_domain: Krei Nehaveblan Domajnon
         create_user_role: Krei Rolon
         demote_user: Malpromocii Uzanton
@@ -199,22 +198,18 @@ eo:
         destroy_email_domain_block: Forigi retpoŝtan domajnan blokon
         destroy_instance: Forigi Domajnon
         destroy_ip_block: Forigi IP-regulon
-        destroy_relay: Forigi Relajson
         destroy_status: Forigi Afiŝon
         destroy_unavailable_domain: Forigi Nehaveblan Domajnon
         destroy_user_role: Detrui Rolon
         disable_2fa_user: Malebligi 2FA
         disable_custom_emoji: Malŝalti Proprajn Bildosignojn
-        disable_relay: Malebligi Relajson
         disable_sign_in_token_auth_user: Malebligi aŭtentigon per retpoŝta ĵetono por la uzanto
         disable_user: Neebligi la uzanton
         enable_custom_emoji: Ebligi Propran Emoĝion
-        enable_relay: Ebligi Relajson
         enable_sign_in_token_auth_user: Ebligi aŭtentigon per retpoŝta ĵetono por la uzanto
         enable_user: Ebligi uzanton
         memorialize_account: Memorigu Konton
         promote_user: Promocii Uzanton
-        publish_terms_of_service: Publikigi kondiĉojn de uzado
         reject_appeal: Malaprobi Apelacion
         reject_user: Malakcepti Uzanton
         remove_avatar_user: Forigi la profilbildon
@@ -252,7 +247,6 @@ eo:
         create_domain_block_html: "%{name} blokis domajnon %{target}"
         create_email_domain_block_html: "%{name} blokis retpoŝtan domajnon %{target}"
         create_ip_block_html: "%{name} kreis regulon por IP %{target}"
-        create_relay_html: "%{name} kreis relajson %{target}"
         create_unavailable_domain_html: "%{name} ĉesis sendon al domajno %{target}"
         create_user_role_html: "%{name} kreis rolon de %{target}"
         demote_user_html: "%{name} degradis uzanton %{target}"
@@ -264,22 +258,18 @@ eo:
         destroy_email_domain_block_html: "%{name} malblokis retpoŝtan domajnon %{target}"
         destroy_instance_html: "%{name} forigis domajnon %{target}"
         destroy_ip_block_html: "%{name} forigis regulon por IP %{target}"
-        destroy_relay_html: "%{name} forigis la relajson %{target}"
         destroy_status_html: "%{name} forigis mesaĝojn de %{target}"
         destroy_unavailable_domain_html: "%{name} restartigis sendon al domajno %{target}"
         destroy_user_role_html: "%{name} forigis rolon de %{target}"
         disable_2fa_user_html: "%{name} malebligis dufaktoran aŭtentigon por uzanto %{target}"
         disable_custom_emoji_html: "%{name} malebligis la bildosignon %{target}"
-        disable_relay_html: "%{name} malebligis la relajson %{target}"
         disable_sign_in_token_auth_user_html: "%{name} malebligis la aŭtentigon de retpoŝta ĵetono por %{target}"
         disable_user_html: "%{name} malebligis ensaluton por uzanto %{target}"
         enable_custom_emoji_html: "%{name} ebligis la bildosignon %{target}"
-        enable_relay_html: "%{name} ebligis la relajson %{target}"
         enable_sign_in_token_auth_user_html: "%{name} ebligis la aŭtentigon de retpoŝta ĵetono por %{target}"
         enable_user_html: "%{name} ebligis ensaluton por uzanto %{target}"
         memorialize_account_html: "%{name} ŝanĝis la konton de %{target} al memora paĝo"
         promote_user_html: "%{name} plirangigis uzanton %{target}"
-        publish_terms_of_service_html: "%{name} publikigis ĝisdatigojn de la kondiĉoj de la servo"
         reject_appeal_html: "%{name} malakceptis apelacion kontraŭ moderiga decido de %{target}"
         reject_user_html: "%{name} malakceptis registriĝon de %{target}"
         remove_avatar_user_html: "%{name} forigis la profilbildon de %{target}"
@@ -309,7 +299,6 @@ eo:
       title: Ĵurnalo de revizo
       unavailable_instance: "(domajna nomo nedisponebla)"
     announcements:
-      back: Reen al anoncoj
       destroyed_msg: La anonco sukcese forigita!
       edit:
         title: Redakti anoncon
@@ -318,9 +307,6 @@ eo:
       new:
         create: Krei anoncon
         title: Nova anonco
-      preview:
-        explanation_html: 'La retpoŝto estos sendita al <strong>%{display_count} uzantoj</strong>. La jena teksto estos inkluzivita en la retmesaĝon:'
-        title: Antaŭrigardu sciigan anoncon
       publish: Publikigi
       published_msg: Anonco sukcese publikigita!
       scheduled_for: Planigota je %{time}
@@ -479,16 +465,6 @@ eo:
       new:
         title: Importi domajnblokojn
       no_file: Neniu dosiero elektita
-    fasp:
-      debug:
-        callbacks:
-          delete: Forigi
-      providers:
-        delete: Forigi
-        name: Nomo
-        registrations:
-          confirm: Konfirmi
-        sign_in: Ensaluti
     follow_recommendations:
       description_html: "<strong>Sekvorekomendoj helpi novajn uzantojn rapide trovi interesa enhavo</strong>. Ili rekalkulitas ĉiutage lau interagoj kaj sekvantokvantoj."
       language: Por la lingvo
@@ -842,10 +818,8 @@ eo:
       back_to_account: Reveni al konta paĝo
       back_to_report: Reveni al raportpaĝo
       batch:
-        add_to_report: 'Aldoni al raporto #%{id}'
         remove_from_report: Forigi de raporto
         report: Raporti
-      contents: Enhavoj
       deleted: Forigita
       favourites: Stelumoj
       history: Versia historio
@@ -854,17 +828,13 @@ eo:
       media:
         title: Aŭdovidaĵoj
       metadata: Metadatumoj
-      no_history: Ĉi tiu afiŝo ne estis redaktita
       no_status_selected: Neniu afiŝo estis ŝanĝita ĉar neniu estis elektita
       open: Malfermi afiŝojn
       original_status: Originala afiŝo
       reblogs: Reblogaĵoj
-      replied_to_html: Respondis al %{acct_link}
       status_changed: Afiŝo ŝanĝiĝis
-      status_title: Afiŝo de @%{name}
-      title: Afiŝoj de konto - @%{name}
+      title: Afiŝoj de la konto
       trending: Popularaĵoj
-      view_publicly: Vidu publike
       visibility: Videbleco
       with_media: Kun aŭdovidaĵoj
     strikes:
@@ -941,36 +911,6 @@ eo:
       search: Serĉi
       title: Kradvortoj
       updated_msg: Kradvorto agordoj ĝisdatigis sukcese
-    terms_of_service:
-      back: Reen al kondiĉoj de uzado
-      changelog: Kio ŝanĝiĝis
-      create: Uzu viajn proprajn
-      current: Nuna
-      draft: Malneto
-      generate: Uzi ŝablonon
-      generates:
-        action: Generi
-        chance_to_review_html: "<strong>La generataj kondiĉoj de uzado ne aŭtomate publikiĝos.</strong> Estos oportuni por vi kontroli la rezultojn. Bonvole entajpu la necesajn detalojn por daŭrigi."
-        explanation_html: La modelo por la kondiĉoj de la servo disponeblas sole por informi. Ĝi nepre ne estas leĝa konsilo pri iu ajn temo. Bonvole konsultu vian propran leĝan konsilanton pri via situacio kaj iuj leĝaj neklarecoj.
-        title: Agordo de kondiĉoj de uzado
-      going_live_on_html: Efektiviĝos %{date}
-      history: Historio
-      live: Antaŭmontro
-      no_history: Ankoraŭ ne estas registritaj ŝanĝoj de la kondiĉoj de la servo.
-      no_terms_of_service_html: Vi nuntempe ne havas iujn ajn kondiĉojn de la servo agordita. La kondiĉoj de la servo celas doni klarecon kaj protekti vin kontraŭ eblaj respondecoj en disputoj kun viaj uzantoj.
-      notified_on_html: Uzantojn sciigis je %{date}
-      notify_users: Informu uzantojn
-      preview:
-        explanation_html: 'La retmesaĝo estos alsendata al <strong>%{display_count} uzantoj</strong>, kiuj kreis konton antaŭ %{date}. La sekvonta teksto inkluziviĝos en la retmesaĝo:'
-        send_preview: Sendu antaŭrigardon al %{email}
-        send_to_all:
-          one: Sendi %{display_count} retpoŝton
-          other: Sendi %{display_count} retpoŝtojn
-        title: Antaŭmontri sciigon pri la kondiĉoj de la servo
-      publish: Publikigi
-      published_on_html: Publikigita je %{date}
-      save_draft: Konservi malneton
-      title: Kondiĉoj de uzado
     title: Administrado
     trends:
       allow: Permesi
@@ -1178,6 +1118,7 @@ eo:
     migrate_account: Movi al alia konto
     migrate_account_html: Se vi deziras alidirekti ĉi tiun konton al alia, vi povas <a href="%{path}">agordi ĝin ĉi tie</a>.
     or_log_in_with: Aŭ saluti per
+    privacy_policy_agreement_html: Mi legis kaj konsentis pri <a href="%{privacy_policy_path}" target="_blank">privatpolitiko</a>
     progress:
       confirm: Konfirmi retadreson
       details: Viaj detaloj
@@ -1202,7 +1143,7 @@ eo:
     set_new_password: Elekti novan pasvorton
     setup:
       email_below_hint_html: Kontrolu vian spam-dosierujon aŭ petu novan. Se necese, vi povas korekti vian retadreson.
-      email_settings_hint_html: Alklaku la ligilon, kiun ni sendis al %{email} por komenci uzi Mastodon. Ni atendos ĝuste ĉi tie.
+      email_settings_hint_html: Enklaku la ligilon, ke ni sendis al vi por kontroli %{email}. Ni estos tien.
       link_not_received: Ĉu vi ne ricevis ligilon?
       new_confirmation_instructions_sent: Vi ricevos novan retpoŝton kun la konfirma ligilo post kelkaj minutoj!
       title: Kontrolu vian retpoŝta enirkesto
@@ -1211,7 +1152,7 @@ eo:
       title: Saluti en %{domain}
     sign_up:
       manual_review: Enskriboj en %{domain} havas manan superrigardon, farita de niaj moderistoj. Por helpi nin por procezi vian enskribon, skribu ion pri vi mem, kaj kial vi volas konton en %{domain}.
-      preamble: Per konto ĉe ĉi tiu Mastodon-servilo, vi povos sekvi ajnan alian personon ĉe la fediverso, sendepende de kie ilia konto estas gastigita.
+      preamble: Per konto ĉe ĉi tiu Mastodon-servilo, vi povas sekvi ajn personojn en la reto.
       title: Ni pretigu vin ĉe %{domain}.
     status:
       account_status: Statuso de la konto
@@ -1223,8 +1164,6 @@ eo:
       view_strikes: Vidi antauaj admonoj kontra via konto
     too_fast: Formularo sendita tro rapide, klopodu denove.
     use_security_key: Uzi sekurecan ŝlosilon
-    user_agreement_html: Mi legis kaj konsentas pri la <a href="%{terms_of_service_path}" target="_blank">servokondiĉoj</a> kaj <a href="%{privacy_policy_path}" target="_blank">privateca politiko</a>
-    user_privacy_agreement_html: Mi legis kaj konsentis pri <a href="%{privacy_policy_path}" target="_blank">privatpolitiko</a>
   author_attribution:
     example_title: Ekzempla teksto
     hint_html: Ĉu vi skribas novaĵojn aŭ blogartikolojn ekster Mastodon? Kontrolu kiel vi estas kreditita kiam ili estas kunhavataj ĉe Mastodon.
@@ -1432,43 +1371,19 @@ eo:
       overwrite: Anstataŭigi
       overwrite_long: Anstataŭigi la nunajn registrojn per la novaj
     overwrite_preambles:
-      blocking_html:
-        one: Vi tuj <strong>anstataŭas vian blokliston</strong>, kun ĝis <strong>%{count} kontoj</strong> de <strong>%{filename}</strong>.
-        other: Vi tuj <strong>anstataŭas vian blokliston</strong> ĝis <strong>%{count} kontoj</strong> de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Vi tuj <strong>anstataŭas viajn legosignojn</strong> kun ĝis <strong>%{count} afiŝoj</strong> de <strong>%{filename}</strong>.
-        other: Vi tuj <strong>anstataŭas viajn legosignojn</strong> kun ĝis <strong>%{count} afiŝoj</strong> de <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Vi tuj <strong>anstataŭas vian domajnan blokliston</strong> kun ĝis <strong>%{count} domajnoj</strong> de <strong>%{filename}</strong>.
-        other: Vi tuj <strong>anstataŭas vian domajnan blokliston</strong> kun ĝis <strong>%{count} domajnoj</strong> de <strong>%{filename}</strong>.
-      following_html:
-        one: Vi tuj <strong>sekvos</strong> ĝis <strong>%{count} kontoj</strong> de <strong>%{filename}</strong> kaj <strong>ĉesos sekvi iun alian</strong>.
-        other: Vi tuj <strong>sekvos</strong> ĝis <strong>%{count} kontoj</strong> de <strong>%{filename}</strong> kaj <strong>ĉesos sekvi iun alian</strong>.
-      lists_html:
-        one: Vi tuj <strong>anstataŭos viajn listojn</strong> kun enhavo de <strong>%{filename}</strong>. Ĝis <strong>%{count} kontoj</strong> estos aldonitaj al novaj listoj.
-        other: Vi tuj <strong>anstataŭos viajn listojn</strong> kun enhavo de <strong>%{filename}</strong>. Ĝis <strong>%{count} kontoj</strong> estos aldonitaj al novaj listoj.
-      muting_html:
-        one: Vi tuj <strong>anstataŭas viajn listojn de silentigitaj kontoj</strong> kun ĝis<strong>%{count} kontoj</strong> de <strong>%{filename}</strong>.
-        other: Vi tuj <strong>anstataŭas viajn listojn de silentigitaj kontoj</strong> kun ĝis<strong>%{count} kontoj</strong> de <strong>%{filename}</strong>.
+      blocking_html: Vi estas <strong>anstataŭonta vian blokliston</strong> per ĝis <strong>%{total_items} kontoj</strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Vi estas <strong>anstataŭonta viajn legosignojn</strong> per ĝis <strong>%{total_items} afiŝoj</strong> de <strong>%{filename}</strong>.
+      domain_blocking_html: Vi estas <strong>anstataŭonta vian domajnan blokliston</strong> per ĝis <strong>%{total_items} domajnoj</strong> de <strong>%{filename}</strong>.
+      following_html: Vi estas <strong>sekvonta</strong> ĝis <strong>%{total_items} kontoj</strong> de <strong>%{filename}</strong> kaj <strong>ĉesos sekvi iun alian</strong>.
+      lists_html: Vi estas <strong>anstataŭonta viajn listojn</strong> per enhavo de <strong>%{filename}</strong>. Ĝis <strong>%{total_items} kontoj</strong> estos aldonitaj al novaj listoj.
+      muting_html: Vi estas <strong>anstataŭonta viajn listojn de silentigitaj kontoj</strong> per ĝis<strong>%{total_items} kontoj</strong> de <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Vi tuj <strong>blokas</strong> ĝis <strong>%{count} kontoj</strong> de <strong>%{filename}</strong>.
-        other: Vi tuj <strong>blokas</strong> ĝis <strong>%{count} kontoj</strong> de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Vi tuj aldonas ĝis <strong>%{count} afiŝojn</strong> de <strong>%{filename}</strong> al viaj <strong>legosignoj</strong>.
-        other: Vi tuj aldonas ĝis <strong>%{count} afiŝojn</strong> de <strong>%{filename}</strong> al viaj <strong>legosignoj</strong>.
-      domain_blocking_html:
-        one: Vi tuj <strong>blokas</strong> ĝis <strong>%{count} domajnoj</strong> de <strong>%{filename}</strong>.
-        other: Vi tuj <strong>blokas</strong> ĝis <strong>%{count} domajnoj</strong> de <strong>%{filename}</strong>.
-      following_html:
-        one: Vi tuj <strong>sekvas</strong> ĝis <strong>%{count} kontoj</strong> de <strong>%{filename}</strong>.
-        other: Vi tuj <strong>sekvas</strong> ĝis <strong>%{count} kontoj</strong> de <strong>%{filename}</strong>.
-      lists_html:
-        one: Vi tuj aldonas ĝis <strong>%{count} kontojn</strong> de <strong>%{filename}</strong> al viaj <strong>listoj</strong>. Novaj listoj estos kreitaj se ne estas listo por aldoni.
-        other: Vi tuj aldonas ĝis <strong>%{count} kontojn</strong> de <strong>%{filename}</strong> al viaj <strong>listoj</strong>. Novaj listoj estos kreitaj se ne estas listo por aldoni.
-      muting_html:
-        one: Vi tuj<strong>silentas</strong> ĝis <strong>%{count} kontoj</strong> de <strong>%{filename}</strong>.
-        other: Vi tuj<strong>silentas</strong> ĝis <strong>%{count} kontoj</strong> de <strong>%{filename}</strong>.
+      blocking_html: Vi estas <strong>blokonta</strong> ĝis <strong>%{total_items} kontoj</strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Vi estas aldononta ĝis <strong>%{total_items} afiŝojn</strong> de <strong>%{filename}</strong> al viaj <strong>legosignoj</strong>.
+      domain_blocking_html: Vi estas <strong>blokonta</strong> ĝis <strong>%{total_items} domajnoj</strong> de <strong>%{filename}</strong>.
+      following_html: Vi estas <strong>sekvonta</strong> ĝis <strong>%{total_items} kontoj</strong> de <strong>%{filename}</strong>.
+      lists_html: Vi estas aldononta ĝis <strong>%{total_items} kontojn</strong> de <strong>%{filename}</strong> al viaj <strong>listoj</strong>. Novaj listoj estos kreitaj se ne estas listo por aldoni.
+      muting_html: Vi estas <strong>silentonta</strong> ĝis <strong>%{total_items} kontoj</strong> de <strong>%{filename}</strong>.
     preface: Vi povas importi datumojn, kiujn vi eksportis el alia servilo, kiel liston de homoj, kiujn vi sekvas aŭ blokas.
     recent_imports: Lastatempaj importoj
     states:
@@ -1725,7 +1640,7 @@ eo:
   scheduled_statuses:
     over_daily_limit: Vi transpasis la limigon al %{limit} samtage planitaj mesaĝoj
     over_total_limit: Vi transpasis la limigon al %{limit} planitaj mesaĝoj
-    too_soon: dato devas esti en la futuro
+    too_soon: La planita dato devas esti en la estonteco
   self_destruct:
     lead_html: Bedaŭrinde, <strong>%{domain}</strong> konstante fermiĝas. Se vi havis konton tie, vi ne povos daŭrigi uzi ĝin, sed vi ankoraŭ povas peti sekurkopion de viaj datumoj.
     title: Ĉi tiu servilo fermiĝas
@@ -1888,8 +1803,6 @@ eo:
       too_late: Estas tro malfrua por apelacii ĉi tiun admonon
   tags:
     does_not_match_previous_name: ne kongruas kun la antaŭa nomo
-  terms_of_service:
-    title: Kondiĉoj de uzado
   themes:
     contrast: Mastodon (Forta kontrasto)
     default: Mastodon (Malhela)
@@ -1921,10 +1834,6 @@ eo:
     recovery_instructions_html: Se vi perdas aliron al via telefono, vi povas uzi unu el la subaj realiraj kodoj por rehavi aliron al via konto. <strong>Konservu realirajn kodojn sekure</strong>. Ekzemple, vi povas printi ilin kaj konservi ilin kun aliaj gravaj dokumentoj.
     webauthn: Sekurecaj ŝlosiloj
   user_mailer:
-    announcement_published:
-      description: 'La administrantoj de %{domain} faras anoncon:'
-      subject: Serva anonco
-      title: "%{domain} serva anonco"
     appeal_approved:
       action: Konto-agordoj
       explanation: La apelacio de la admono kontra via konto je %{strike_date} pri sendodato %{appeal_date} aprobitas.
@@ -1954,15 +1863,6 @@ eo:
       further_actions_html: Se ne estas vi, ni rekomendas ke vi %{action} tuj por sekurigi vian konton.
       subject: Via konto estas alirita de nova IP-adreso
       title: Nova saluto
-    terms_of_service_changed:
-      agreement: Se vi daŭrige uzos %{domain}, vi aŭtomate interkonsentos pri ĉi tiuj kondiĉoj. Se vi malkonsentas pri la novaj kondiĉoj, vi ĉiutempe rajtas nuligi la interkonsenton kun %{domain} per forigi vian konton.
-      changelog: 'Facile dirite, la ŝanĝoj estas la jenaj:'
-      description: 'Vi ricevas ĉi tiun retmesaĝon ĉar ni faras iujn ŝanĝojn al niaj servokondiĉoj ĉe %{domain}. Ĉi tiuj ĝisdatigoj efektiviĝos %{date}. Ni instigas vin revizii la ĝisdatigitajn terminojn plene ĉi tie:'
-      description_html: Vi ricevas ĉi tiun retmesaĝon ĉar ni faras iujn ŝanĝojn al niaj servokondiĉoj ĉe %{domain}. Ĉi tiuj ĝisdatigoj efektiviĝos <strong>%{date}</strong> Ni instigas vin revizii la <a href="%{path}" target="_blank">ĝisdatigitajn terminojn plene ĉi tie</a>.
-      sign_off: La teamo de %{domain}
-      subject: Ĝisdatigoj al niaj kondiĉoj de servo
-      subtitle: La kondiĉoj de la servo de %{domain} ŝanĝiĝas
-      title: Grava ĝisdatigo
     warning:
       appeal: Sendi apelacion
       appeal_description: Se vi pensas ke ĉi tio estas eraro, vi povas sendi apelacion al la teamo de %{instance}.
diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml
index ab3d307fd8..b3fe023417 100644
--- a/config/locales/es-AR.yml
+++ b/config/locales/es-AR.yml
@@ -187,7 +187,6 @@ es-AR:
         create_domain_block: Crear bloqueo de dominio
         create_email_domain_block: Crear bloqueo de dominio de correo electrónico
         create_ip_block: Crear regla de dirección IP
-        create_relay: Crear relé
         create_unavailable_domain: Crear dominio no disponible
         create_user_role: Crear rol
         demote_user: Descender usuario
@@ -199,22 +198,18 @@ es-AR:
         destroy_email_domain_block: Eliminar bloqueo de dominio de correo electrónico
         destroy_instance: Purgar dominio
         destroy_ip_block: Eliminar regla de dirección IP
-        destroy_relay: Eliminar relé
         destroy_status: Eliminar mensaje
         destroy_unavailable_domain: Eliminar dominio no disponible
         destroy_user_role: Destruir rol
         disable_2fa_user: Deshabilitar 2FA
         disable_custom_emoji: Deshabilitar emoji personalizado
-        disable_relay: Deshabilitar relé
         disable_sign_in_token_auth_user: Deshabilitar autenticación de token por correo electrónico para el usuario
         disable_user: Deshabilitar usuario
         enable_custom_emoji: Habilitar emoji personalizado
-        enable_relay: Habilitar relé
         enable_sign_in_token_auth_user: Habilitar autenticación de token por correo electrónico para el usuario
         enable_user: Habilitar usuario
         memorialize_account: Convertir en cuenta conmemorativa
         promote_user: Promover usuario
-        publish_terms_of_service: Publicar términos del servicio
         reject_appeal: Rechazar apelación
         reject_user: Rechazar usuario
         remove_avatar_user: Quitar avatar
@@ -252,7 +247,6 @@ es-AR:
         create_domain_block_html: "%{name} bloqueó el dominio %{target}"
         create_email_domain_block_html: "%{name} bloqueó el dominio de correo electrónico %{target}"
         create_ip_block_html: "%{name} creó la regla para la dirección IP %{target}"
-        create_relay_html: "%{name} creó el relé %{target}"
         create_unavailable_domain_html: "%{name} detuvo la entrega al dominio %{target}"
         create_user_role_html: "%{name} creó el rol %{target}"
         demote_user_html: "%{name} bajó de nivel al usuario %{target}"
@@ -264,22 +258,18 @@ es-AR:
         destroy_email_domain_block_html: "%{name} desbloqueó el dominio de correo electrónico %{target}"
         destroy_instance_html: "%{name} purgó el dominio %{target}"
         destroy_ip_block_html: "%{name} eliminó la regla para la dirección IP %{target}"
-        destroy_relay_html: "%{name} eliminó el relé %{target}"
         destroy_status_html: "%{name} eliminó el mensaje de %{target}"
         destroy_unavailable_domain_html: "%{name} reanudó la entrega al dominio %{target}"
         destroy_user_role_html: "%{name} eliminó el rol %{target}"
         disable_2fa_user_html: "%{name} deshabilitó el requerimiento de dos factores para el usuario %{target}"
         disable_custom_emoji_html: "%{name} deshabilitó el emoji %{target}"
-        disable_relay_html: "%{name} deshabilitó el relé %{target}"
         disable_sign_in_token_auth_user_html: "%{name} deshabilitó la autenticación de token por correo electrónico para %{target}"
         disable_user_html: "%{name} deshabilitó el inicio de sesión para el usuario %{target}"
         enable_custom_emoji_html: "%{name} habilitó el emoji %{target}"
-        enable_relay_html: "%{name} eliminó el relé %{target}"
         enable_sign_in_token_auth_user_html: "%{name} habilitó la autenticación de token por correo electrónico para %{target}"
         enable_user_html: "%{name} habilitó el inicio de sesión para el usuario %{target}"
         memorialize_account_html: "%{name} convirtió la cuenta de %{target} en una cuenta conmemorativa"
         promote_user_html: "%{name} promovió al usuario %{target}"
-        publish_terms_of_service_html: "%{name} publicó actualizaciones de los términos del servicio"
         reject_appeal_html: "%{name} rechazó la solicitud de moderación de %{target}"
         reject_user_html: "%{name} rechazó el registro de %{target}"
         remove_avatar_user_html: "%{name} quitó el avatar de %{target}"
@@ -309,7 +299,6 @@ es-AR:
       title: Registro de auditoría
       unavailable_instance: "[nombre de dominio no disponible]"
     announcements:
-      back: Volver a los anuncios
       destroyed_msg: "¡Anuncio eliminado exitosamente!"
       edit:
         title: Editar anuncio
@@ -318,10 +307,6 @@ es-AR:
       new:
         create: Crear anuncio
         title: Nuevo anuncio
-      preview:
-        disclaimer: Como los usuarios no pueden excluirse de ellas, las notificaciones por correo electrónico deberían limitarse a anuncios importantes como la violación de datos personales o las notificaciones de cierre del servidor.
-        explanation_html: 'El correo electrónico se enviará a <strong>%{display_count} usuarios</strong>. En el correo electrónico se incluirá el siguiente texto:'
-        title: Previsualizar la notificación del anuncio
       publish: Publicar
       published_msg: "¡Anuncio publicado exitosamente!"
       scheduled_for: Programado para %{time}
@@ -480,36 +465,6 @@ es-AR:
       new:
         title: Importar bloques de dominio
       no_file: No hay ningún archivo seleccionado
-    fasp:
-      debug:
-        callbacks:
-          created_at: 'Fecha de creación:'
-          delete: Eliminar
-          ip: Dirección IP
-          request_body: Cuerpo de la solicitud
-          title: Depurar llamadas
-      providers:
-        active: Activo
-        base_url: Dirección web base
-        callback: Depuraciones
-        delete: Eliminar
-        edit: Editar proveedor
-        finish_registration: Finalizar registro
-        name: Nombre
-        providers: Proveedores
-        public_key_fingerprint: Huella de clave pública
-        registration_requested: Registro solicitado
-        registrations:
-          confirm: Confirmar
-          description: Recibiste un registro de un FASP (Fediverse Auxiliary Service Providers; Proveedores de Servicio Auxiliares del Fediverso). Rechazalo si no iniciaste esto. Si, en cambio, iniciaste esto, compará cuidadosamente el nombre y la huella de la clave antes de confirmar el registro.
-          reject: Rechazar
-          title: Confirmar registro FASP
-        save: Guardar
-        select_capabilities: Seleccionar capacidades
-        sign_in: Iniciar sesión
-        status: Estado
-        title: Proveedores de Servicios Auxiliares del Fediverso
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Las recomendaciones de cuentas para seguir ayudan a los nuevos usuarios a encontrar rápidamente contenido interesante</strong>. Cuando un usuario no ha interactuado con otros lo suficiente como para formar recomendaciones personalizadas de seguimiento, se recomiendan estas cuentas, en su lugar. Se recalculan diariamente a partir de una mezcla de cuentas con las interacciones más recientes y el mayor número de seguidores para un idioma determinado."
       language: Por idioma
@@ -863,10 +818,8 @@ es-AR:
       back_to_account: Volver a la página de la cuenta
       back_to_report: Volver a la página de la denuncia
       batch:
-        add_to_report: Agregar a la denuncia N°%{id}
         remove_from_report: Quitar de la denuncia
         report: Denunciar
-      contents: Contenido
       deleted: Eliminado
       favourites: Favoritos
       history: Historial de versiones
@@ -875,17 +828,13 @@ es-AR:
       media:
         title: Medios
       metadata: Metadatos
-      no_history: Este mensaje no ha sido editado
       no_status_selected: No se cambió ningún mensaje, ya que ninguno fue seleccionado
       open: Abrir mensaje
       original_status: Mensaje original
       reblogs: Adhesiones
-      replied_to_html: Respondido a %{acct_link}
       status_changed: Mensaje cambiado
-      status_title: Mensaje por @%{name}
-      title: Mensajes de cuenta - @%{name}
+      title: Mensajes de la cuenta
       trending: En tendencia
-      view_publicly: Ver públicamente
       visibility: Visibilidad
       with_media: Con medios
     strikes:
@@ -962,36 +911,6 @@ es-AR:
       search: Buscar
       title: Etiquetas
       updated_msg: La configuración de la etiqueta se actualizó exitosamente
-    terms_of_service:
-      back: Volver a los términos del servicio
-      changelog: Qué cambió
-      create: Usá los tuyos
-      current: Actual
-      draft: Borrador
-      generate: Usar plantilla
-      generates:
-        action: Generar
-        chance_to_review_html: "<strong>Los términos del servicio generados no se publicarán automáticamente.</strong> Vas a tener la oportunidad de revisar el resultado. Por favor, rellená los detalles necesarios para continuar."
-        explanation_html: La plantilla de términos de servicio ofrecida es únicamente para propósito informativo, y no debería ser considerada asesoramiento legal sobre ningún tema. Por favor, consultá con tu propio asesor legal sobre tu situación y las cuestiones legales específicas que tengás.
-        title: Configuración de los términos del servicio
-      going_live_on_html: Actual, efectivo desde %{date}
-      history: Historial
-      live: En vivo
-      no_history: Aún no se han registrado cambios en los términos del servicio.
-      no_terms_of_service_html: Actualmente, no tenés configurado ningún término del servicio. Los términos del servicio están pensados para proporcionar claridad y protegerte de posibles responsabilidades en disputas con tus usuarios.
-      notified_on_html: Usuarios notificados el %{date}
-      notify_users: Notificar usuarios
-      preview:
-        explanation_html: 'El correo electrónico se enviará a <strong>%{display_count} usuarios</strong> que se registraron antes del %{date}. El siguiente texto se incluirá en el correo:'
-        send_preview: Enviar previsualización a %{email}
-        send_to_all:
-          one: Enviar %{display_count} correo electrónico
-          other: Enviar %{display_count} correos electrónicos
-        title: Previsualizar la notificación de los términos del servicio
-      publish: Publicar
-      published_on_html: Publicado el %{date}
-      save_draft: Guardar borrador
-      title: Términos del servicio
     title: Administración
     trends:
       allow: Permitir
@@ -1199,6 +1118,7 @@ es-AR:
     migrate_account: Mudarse a otra cuenta
     migrate_account_html: Si querés redireccionar esta cuenta a otra distinta, podés <a href="%{path}">configurar eso acá</a>.
     or_log_in_with: O iniciar sesión con
+    privacy_policy_agreement_html: Leí y acepto la <a href="%{privacy_policy_path}" target="_blank">política de privacidad</a>
     progress:
       confirm: Confirmar correo electrónico
       details: Tus detalles
@@ -1223,7 +1143,7 @@ es-AR:
     set_new_password: Establecer nueva contraseña
     setup:
       email_below_hint_html: Revisá tu carpeta de correo no deseado / spam, o solicitá otro enlace de confirmación. Podés corregir tu dirección de correo electrónico si está mal.
-      email_settings_hint_html: Hacé clic en el enlace que enviamos a %{email} para comenzar a usar Mastodon. Estaremos esperando aquí mismo.
+      email_settings_hint_html: Hacé clic en el enlace que te enviamos para verificar %{email}. Te esperamos por acá.
       link_not_received: "¿No recibiste un enlace?"
       new_confirmation_instructions_sent: "¡Recibirás un nuevo correo electrónico con el enlace de confirmación en unos minutos!"
       title: Revisá tu bandeja de entrada
@@ -1232,7 +1152,7 @@ es-AR:
       title: Iniciar sesión en %{domain}
     sign_up:
       manual_review: Los registros en %{domain} pasan por la revisión manual de nuestros moderadores. Para ayudarnos a procesar tu registro, escribinos un poco sobre vos y contanos por qué querés una cuenta en %{domain}.
-      preamble: Con una cuenta en este servidor de Mastodon, podrás seguir a cualquier otra cuenta en el Fediverso, independientemente de dónde esté alojada su cuenta.
+      preamble: Con una cuenta en este servidor de Mastodon, podrás seguir a cualquier otra cuenta en la red, independientemente de en qué servidor esté alojada esa cuenta.
       title: Dejá que te preparemos en %{domain}.
     status:
       account_status: Estado de la cuenta
@@ -1244,8 +1164,6 @@ es-AR:
       view_strikes: Ver incumplimientos pasados contra tu cuenta
     too_fast: Formulario enviado demasiado rápido, probá de nuevo.
     use_security_key: Usar la llave de seguridad
-    user_agreement_html: Leí y acepto los <a href="%{terms_of_service_path}" target="_blank">términos del servicio</a> y la <a href="%{privacy_policy_path}" target="_blank">política de privacidad</a>
-    user_privacy_agreement_html: Leí y acepto la <a href="%{privacy_policy_path}" target="_blank">política de privacidad</a>
   author_attribution:
     example_title: Texto de ejemplo
     hint_html: "¿Escribís artículos de noticias o de blog fuera de Mastodon? Controlá cómo se te acredita cuando se comparten en Mastodon."
@@ -1451,43 +1369,19 @@ es-AR:
       overwrite: Sobrescribir
       overwrite_long: Reemplazar registros actuales con los nuevos
     overwrite_preambles:
-      blocking_html:
-        one: Estás a punto de <strong>reemplazar tu lista de bloqueos</strong> por hasta <strong>%{count} cuenta</strong> provenientes de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tu lista de bloqueos</strong> por hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Estás a punto de <strong>reemplazar tus marcadores</strong> por hasta <strong>%{count} mensaje</strong> provenientes de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tus marcadores</strong> por hasta <strong>%{count} mensajes</strong> provenientes de <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Estás a punto de <strong>reemplazar tu lista de bloqueos de dominio</strong> por hasta <strong>%{count} dominio</strong> provenientes de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tu lista de bloqueos de dominio</strong> por hasta <strong>%{count} dominios</strong> provenientes de <strong>%{filename}</strong>.
-      following_html:
-        one: Estás a punto de <strong>seguir</strong> hasta <strong>%{count} cuenta</strong> provenientes de <strong>%{filename}</strong> y <strong>dejar de seguir a cualquier otra cuenta</strong>.
-        other: Estás a punto de <strong>seguir</strong> hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong> y <strong>dejar de seguir a cualquier otra cuenta</strong>.
-      lists_html:
-        one: Estás a punto de <strong>reemplazar tus listas</strong> con el contenido de <strong>%{filename}</strong>. Se agregarán hasta <strong>%{count} cuenta</strong> a listas nuevas.
-        other: Estás a punto de <strong>reemplazar tus listas</strong> con el contenido de <strong>%{filename}</strong>. Se agregarán hasta <strong>%{count} cuentas</strong> a listas nuevas.
-      muting_html:
-        one: Estás a punto de <strong>reemplazar tu lista de cuentas silenciadas</strong> con hasta <strong>%{count} cuenta</strong> provenientes de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tu lista de cuentas silenciadas</strong> con hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong>.
+      blocking_html: Estás a punto de <strong>reemplazar tu lista de bloqueos</strong> por hasta <strong>%{total_items} cuentas</strong> provenientes de <strong>%{filename}</strong>.
+      bookmarks_html: Estás a punto de <strong>reemplazar tus marcadores</strong> por hasta <strong>%{total_items} mensajes</strong> provenientes de <strong>%{filename}</strong>.
+      domain_blocking_html: Estás a punto de <strong>reemplazar tu lista de bloqueos de dominio</strong> por hasta <strong>%{total_items} dominios</strong> provenientes de <strong>%{filename}</strong>.
+      following_html: Estás a punto de <strong>seguir</strong> hasta <strong>%{total_items} cuentas</strong> provenientes de <strong>%{filename}</strong> y <strong>dejar de seguir a cualquier otra cuenta</strong>.
+      lists_html: Estás a punto de <strong>reemplazar tus listas</strong> con el contenido de <strong>%{filename}</strong>. Se agregarán hasta <strong>%{total_items} cuentas</strong> a listas nuevas.
+      muting_html: Estás a punto de <strong>reemplazar tu lista de cuentas silenciadas</strong> con hasta <strong>%{total_items} cuentas</strong> provenientes de <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Estás a punto de <strong>bloquear</strong> hasta <strong>%{count} cuenta</strong> provenientes de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>bloquear</strong> hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Está a punto de agregar hasta <strong>%{count} mensaje</strong> provenientes de <strong>%{filename}</strong> a tus <strong>marcadores</strong>.
-        other: Está a punto de agregar hasta <strong>%{count} mensajes</strong> provenientes de <strong>%{filename}</strong> a tus <strong>marcadores</strong>.
-      domain_blocking_html:
-        one: Estás a punto de <strong>bloquear</strong> hasta <strong>%{count} dominio</strong> provenientes de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>bloquear</strong> hasta <strong>%{count} dominios</strong> provenientes de <strong>%{filename}</strong>.
-      following_html:
-        one: Estás a punto de <strong>seguir</strong> hasta <strong>%{count} cuenta</strong> provenientes de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>seguir</strong> hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong>.
-      lists_html:
-        one: Estás a punto de agregar hasta <strong>%{count} cuenta</strong> desde <strong>%{filename}</strong> a tus <strong>listas</strong>. Se crearán nuevas listas si no hay lista a cuál agregar.
-        other: Estás a punto de agregar hasta <strong>%{count} cuentas</strong> desde <strong>%{filename}</strong> a tus <strong>listas</strong>. Se crearán nuevas listas si no hay lista a cuál agregar.
-      muting_html:
-        one: Estás a punto de <strong>silenciar</strong> hasta <strong>%{count} cuenta</strong> provenientes de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>silenciar</strong> hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong>.
+      blocking_html: Estás a punto de <strong>bloquear</strong> hasta <strong>%{total_items} cuentas</strong> provenientes de <strong>%{filename}</strong>.
+      bookmarks_html: Está a punto de agregar hasta <strong>%{total_items} mensajes</strong> provenientes de <strong>%{filename}</strong> a tus <strong>marcadores</strong>.
+      domain_blocking_html: Estás a punto de <strong>bloquear</strong> hasta <strong>%{total_items} dominios</strong> provenientes de <strong>%{filename}</strong>.
+      following_html: Estás a punto de <strong>seguir</strong> hasta <strong>cuentas%{total_items}</strong> provenientes de <strong>%{filename}</strong>.
+      lists_html: Estás a punto de agregar hasta <strong>%{total_items} cuentas</strong> desde <strong>%{filename}</strong> a tus <strong>listas</strong>. Se crearán nuevas listas si no hay lista a cual agregar.
+      muting_html: Estás a punto de <strong>silenciar</strong> hasta <strong>%{total_items} cuentas</strong> provenientes de <strong>%{filename}</strong>.
     preface: Podés importar ciertos datos que exportaste desde otro servidor, como una lista de las cuentas que estás siguiendo o bloqueando.
     recent_imports: Importaciones recientes
     states:
@@ -1744,7 +1638,7 @@ es-AR:
   scheduled_statuses:
     over_daily_limit: Superaste el límite de %{limit} mensajes programados para ese día
     over_total_limit: Superaste el límite de %{limit} mensajes programados
-    too_soon: la fecha debe ser en el futuro
+    too_soon: La fecha programada debe estar en el futuro
   self_destruct:
     lead_html: Desafortunadamente, <strong>%{domain}</strong> va a cerrar permanentemente. Si tenías una cuenta ahí, no podrás continuar usándola, pero aún podés solicitar una copia de tus datos.
     title: Este servidor está cerrando
@@ -1907,8 +1801,6 @@ es-AR:
       too_late: Es demasiado tarde para apelar este incumplimiento
   tags:
     does_not_match_previous_name: no coincide con el nombre anterior
-  terms_of_service:
-    title: Términos del servicio
   themes:
     contrast: Alto contraste
     default: Oscuro
@@ -1940,10 +1832,6 @@ es-AR:
     recovery_instructions_html: Si alguna vez perdés el acceso a tu aplicación de 2FA, podés usar uno de los siguientes códigos de recuperación para recuperar el acceso a tu cuenta. <strong>Mantenelos a salvo</strong>. Por ejemplo, podés imprimirlos y guardarlos con otros documentos importantes.
     webauthn: Llaves de seguridad
   user_mailer:
-    announcement_published:
-      description: 'Los administradores de %{domain} están haciendo un anuncio:'
-      subject: Anuncio del servicio
-      title: Anuncio del servicio %{domain}
     appeal_approved:
       action: Configuración de la cuenta
       explanation: La apelación del incumplimiento contra tu cuenta del %{strike_date} que enviaste el %{appeal_date} fue aprobada. Tu cuenta se encuentra de nuevo en buen estado.
@@ -1973,15 +1861,6 @@ es-AR:
       further_actions_html: Si no fuiste vos, te recomendamos que %{action} inmediatamente y habilités la autenticación de dos factores para mantener tu cuenta segura.
       subject: Se accedió a tu cuenta desde una nueva dirección IP
       title: Un nuevo inicio de sesión
-    terms_of_service_changed:
-      agreement: Si seguís usando %{domain}, aceptás estos términos. Si no estás de acuerdo con los términos actualizados, podés cancelar tu acuerdo con %{domain} en cualquier momento, eliminando tu cuenta.
-      changelog: 'A modo de pantallazo general, esto es lo que esta actualización significa para vos:'
-      description: 'Estás recibiendo este correo electrónico porque estamos haciendo algunos cambios en nuestros términos de servicio en %{domain}. Estas actualizaciones serán efectivas el %{date}. Te alentamos a revisar los términos actualizados en su totalidad aquí:'
-      description_html: Estás recibiendo este correo electrónico porque estamos haciendo algunos cambios en nuestros términos de servicio en %{domain}. Estas actualizaciones serán efectivas el <strong>%{date}</strong>. Te alentamos a revisar los <a href="%{path}" target="_blank"> términos actualizados en su totalidad aquí</a>.
-      sign_off: El equipo de %{domain}
-      subject: Actualizaciones en nuestros términos del servicio
-      subtitle: Los términos del servicio de %{domain} están cambiando
-      title: Actualización importante
     warning:
       appeal: Enviar una apelación
       appeal_description: Si creés que esto es un error, podés enviar una apelación al equipo de %{instance}.
diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml
index 3032b663a1..5ed74a1c4b 100644
--- a/config/locales/es-MX.yml
+++ b/config/locales/es-MX.yml
@@ -187,7 +187,6 @@ es-MX:
         create_domain_block: Crear Bloqueo de Dominio
         create_email_domain_block: Crear bloqueo de dominio de correo electrónico
         create_ip_block: Crear regla IP
-        create_relay: Crear Relé
         create_unavailable_domain: Crear Dominio No Disponible
         create_user_role: Crear rol
         demote_user: Degradar Usuario
@@ -199,22 +198,18 @@ es-MX:
         destroy_email_domain_block: Eliminar bloqueo de dominio de correo electrónico
         destroy_instance: Purgar dominio
         destroy_ip_block: Eliminar regla IP
-        destroy_relay: Eliminar Relé
         destroy_status: Eliminar Publicación
         destroy_unavailable_domain: Eliminar Dominio No Disponible
         destroy_user_role: Destruir Rol
         disable_2fa_user: Deshabilitar 2FA
         disable_custom_emoji: Deshabilitar Emoji Personalizado
-        disable_relay: Desactivar Relé
         disable_sign_in_token_auth_user: Deshabilitar la autenticación por token de correo electrónico para el usuario
         disable_user: Deshabilitar Usuario
         enable_custom_emoji: Habilitar Emoji Personalizado
-        enable_relay: Activar Relé
         enable_sign_in_token_auth_user: Habilitar la autenticación por token de correo electrónico para el usuario
         enable_user: Habilitar Usuario
         memorialize_account: Transformar en Cuenta Conmemorativa
         promote_user: Promover Usuario
-        publish_terms_of_service: Publicar condiciones del servicio
         reject_appeal: Rechazar apelación
         reject_user: Rechazar Usuario
         remove_avatar_user: Eliminar Avatar
@@ -252,7 +247,6 @@ es-MX:
         create_domain_block_html: "%{name} bloqueó el dominio %{target}"
         create_email_domain_block_html: "%{name} bloqueó el dominio de correo electrónico %{target}"
         create_ip_block_html: "%{name} creó una regla para la IP %{target}"
-        create_relay_html: "%{name} creó un relé %{target}"
         create_unavailable_domain_html: "%{name} detuvo las entregas al dominio %{target}"
         create_user_role_html: "%{name} creó el rol %{target}"
         demote_user_html: "%{name} degradó al usuario %{target}"
@@ -264,22 +258,18 @@ es-MX:
         destroy_email_domain_block_html: "%{name} desbloqueó el dominio de correo electrónico %{target}"
         destroy_instance_html: "%{name} purgó el dominio %{target}"
         destroy_ip_block_html: "%{name} eliminó una regla para la IP %{target}"
-        destroy_relay_html: "%{name} eliminó el relé %{target}"
         destroy_status_html: "%{name} eliminó la publicación por %{target}"
         destroy_unavailable_domain_html: "%{name} reanudó las entregas al dominio %{target}"
         destroy_user_role_html: "%{name} eliminó el rol %{target}"
         disable_2fa_user_html: "%{name} desactivó el requisito de dos factores para el usuario %{target}"
         disable_custom_emoji_html: "%{name} desactivó el emoji %{target}"
-        disable_relay_html: "%{name} desactivó el relé %{target}"
         disable_sign_in_token_auth_user_html: "%{name} desactivó la autenticación por token de correo electrónico para %{target}"
         disable_user_html: "%{name} deshabilitó el inicio de sesión para el usuario %{target}"
         enable_custom_emoji_html: "%{name} activó el emoji %{target}"
-        enable_relay_html: "%{name} activó el relé %{target}"
         enable_sign_in_token_auth_user_html: "%{name} activó autenticación por token de correo electrónico para %{target}"
         enable_user_html: "%{name} habilitó el inicio de sesión para el usuario %{target}"
         memorialize_account_html: "%{name} convirtió la cuenta de %{target} en una página in memoriam"
         promote_user_html: "%{name} promoción al usuario %{target}"
-        publish_terms_of_service_html: "%{name} publicó actualizaciones de las condiciones del servicio"
         reject_appeal_html: "%{name} rechazó la solicitud de moderación de %{target}"
         reject_user_html: "%{name} rechazó el registro de %{target}"
         remove_avatar_user_html: "%{name} eliminó el avatar de %{target}"
@@ -309,7 +299,6 @@ es-MX:
       title: Log de auditoría
       unavailable_instance: "(nombre de dominio no disponible)"
     announcements:
-      back: Volver a la sección de anuncios
       destroyed_msg: "¡Anuncio eliminado con éxito!"
       edit:
         title: Editar anuncio
@@ -318,10 +307,6 @@ es-MX:
       new:
         create: Crear anuncio
         title: Nuevo anuncio
-      preview:
-        disclaimer: Como los usuarios no pueden optar por no recibirlas, las notificaciones por correo electrónico deben limitarse a anuncios importantes, como la violación de datos personales o las notificaciones de cierre de servidores.
-        explanation_html: 'El correo electrónico se enviará a <strong>%{display_count} usuarios</strong>. En el correo electrónico se incluirá el siguiente texto:'
-        title: Vista previa de la notificación del anuncio
       publish: Publicar
       published_msg: "¡Anuncio publicado con éxito!"
       scheduled_for: Programado para %{time}
@@ -480,36 +465,6 @@ es-MX:
       new:
         title: Importar bloqueos de dominio
       no_file: No hay archivo seleccionado
-    fasp:
-      debug:
-        callbacks:
-          created_at: Creado el
-          delete: Eliminar
-          ip: Dirección IP
-          request_body: Cuerpo de la solicitud
-          title: Depurar devoluciones de llamada
-      providers:
-        active: Activo
-        base_url: URL base
-        callback: Devolución de llamada
-        delete: Eliminar
-        edit: Editar proveedor
-        finish_registration: Terminar el registro
-        name: Nombre
-        providers: Proveedores
-        public_key_fingerprint: Huella digital de clave pública
-        registration_requested: Se solicitó el registro
-        registrations:
-          confirm: Confirmar
-          description: Has recibido un registro de un FASP. Recházalo si no lo iniciaste tú. Si lo comenzaste tú, compara cuidadosamente el nombre y la huella de la clave antes de confirmar el registro.
-          reject: Rechazar
-          title: Confirmar registro FASP
-        save: Guardar
-        select_capabilities: Selecciona las capacidades
-        sign_in: Iniciar sesión
-        status: Estado
-        title: Fediverse Auxiliary Service Providers (Proveedores de Servicio Auxiliares del Fediverso)
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Las recomendaciones de cuentas ayudan a los nuevos usuarios a encontrar rápidamente contenido interesante</strong>. Cuando un usuario no ha interactuado con otros lo suficiente como para suscitar recomendaciones personalizadas de cuentas a las que seguir, en su lugar se le recomiendan estas cuentas. Se recalculan diariamente a partir de una mezcla de cuentas con el mayor número de interacciones recientes y con el mayor número de seguidores locales con un idioma determinado."
       language: Para el idioma
@@ -863,10 +818,8 @@ es-MX:
       back_to_account: Volver a la cuenta
       back_to_report: Volver a la página de reporte
       batch:
-        add_to_report: 'Añadir al informe #%{id}'
         remove_from_report: Eliminar del reporte
         report: Reportar
-      contents: Contenidos
       deleted: Eliminado
       favourites: Favoritos
       history: Historial de versiones
@@ -875,17 +828,13 @@ es-MX:
       media:
         title: Multimedia
       metadata: Metadatos
-      no_history: Esta publicación no ha sido editada
       no_status_selected: No se cambió ninguna publicación al no seleccionar ninguna
       open: Abrir publicación
       original_status: Publicación original
       reblogs: Impulsos
-      replied_to_html: En respuesta a %{acct_link}
       status_changed: Publicación cambiada
-      status_title: Publicado por %{name}
-      title: Publicaciones de la cuenta - @%{name}
+      title: Estado de las cuentas
       trending: En tendencia
-      view_publicly: Ver públicamente
       visibility: Visibilidad
       with_media: Con multimedia
     strikes:
@@ -962,36 +911,6 @@ es-MX:
       search: Buscar
       title: Etiquetas
       updated_msg: Etiquetas actualizadas exitosamente
-    terms_of_service:
-      back: Regresar a las condiciones del servicio
-      changelog: Lo que ha cambiado
-      create: Usa los tuyos
-      current: Actual
-      draft: Borrador
-      generate: Usar plantilla
-      generates:
-        action: Generar
-        chance_to_review_html: "<strong>Las condiciones de servicio generadas no se publicarán automáticamente.</strong> Tendrás la oportunidad de revisar los resultados. Por favor, rellena los datos necesarios para continuar."
-        explanation_html: La plantilla de condiciones de servicio que se proporciona tiene únicamente fines informativos y no debe interpretarse como asesoramiento jurídico sobre ningún tema. Por favor, consulte con su propio asesor legal sobre su situación y las cuestiones legales específicas que tenga.
-        title: Configuración de las condiciones del servicio
-      going_live_on_html: En vigor desde %{date}
-      history: Historial
-      live: Actual
-      no_history: No se han registrado cambios en las condiciones del servicio hasta el momento.
-      no_terms_of_service_html: Actualmente, no tiene configuradas condiciones del servicio. Las condiciones del servicio están diseñadas para proporcionar claridad y protegerte de posibles responsabilidades en disputas con tus usuarios.
-      notified_on_html: Usuarios notificados el %{date}
-      notify_users: Notificar usuarios
-      preview:
-        explanation_html: 'El correo electrónico se enviará a <strong>%{display_count} usuarios</strong> que se hayan registrado antes de %{date}. En el correo electrónico se incluirá el siguiente texto:'
-        send_preview: Enviar vista previa a %{email}
-        send_to_all:
-          one: Enviar %{display_count} correo electrónico
-          other: Enviar %{display_count} correos electrónicos
-        title: Vista previa de la notificación de las condiciones del servicio
-      publish: Publicar
-      published_on_html: Publicado el %{date}
-      save_draft: Guardar borrador
-      title: Condiciones del servicio
     title: Administración
     trends:
       allow: Permitir
@@ -1199,6 +1118,7 @@ es-MX:
     migrate_account: Mudarse a otra cuenta
     migrate_account_html: Si deseas redireccionar esta cuenta a otra distinta, puedes <a href="%{path}">configurarlo aquí</a>.
     or_log_in_with: O inicia sesión con
+    privacy_policy_agreement_html: He leído y acepto la <a href="%{privacy_policy_path}" target="_blank">política de privacidad</a>
     progress:
       confirm: Confirmar dirección de correo
       details: Tus detalles
@@ -1223,7 +1143,7 @@ es-MX:
     set_new_password: Establecer nueva contraseña
     setup:
       email_below_hint_html: Comprueba tu carpeta de correo no deseado o solicita otro enlace de confirmación. Puedes corregir tu dirección de correo electrónico si está mal.
-      email_settings_hint_html: Haz clic en el enlace que te enviamos a %{email} para comenzar a usar Mastodon. Te esperamos aquí.
+      email_settings_hint_html: Pulsa el enlace que te hemos enviado para verificar %{email}. Esperaremos aquí mismo.
       link_not_received: "¿No recibiste un enlace?"
       new_confirmation_instructions_sent: "¡Recibirás un nuevo correo electrónico con el enlace de confirmación en unos minutos!"
       title: Revisa tu bandeja de entrada
@@ -1232,7 +1152,7 @@ es-MX:
       title: Registrate en %{domain}
     sign_up:
       manual_review: Los registros en %{domain} pasan por la revisión manual de nuestros moderadores. Para ayudarnos a procesar tu registro, escribe un poco sobre ti mismo y por qué quieres una cuenta en %{domain}.
-      preamble: Al tener una cuenta en este servidor de Mastodon, tendrás la oportunidad de seguir a cualquier persona en el fediverso, sin importar en qué plataforma esté alojada su cuenta.
+      preamble: Con una cuenta en este servidor de Mastodon, podrás seguir a cualquier otra persona en la red, independientemente del servidor en el que se encuentre.
       title: Crear cuenta de Mastodon en %{domain}.
     status:
       account_status: Estado de la cuenta
@@ -1244,8 +1164,6 @@ es-MX:
       view_strikes: Ver amonestaciones pasadas contra tu cuenta
     too_fast: Formulario enviado demasiado rápido, inténtelo de nuevo.
     use_security_key: Usar la clave de seguridad
-    user_agreement_html: He leído y acepto las <a href="%{terms_of_service_path}" target="_blank">condiciones del servicio</a> y la <a href="%{privacy_policy_path}" target="_blank">política de privacidad</a>
-    user_privacy_agreement_html: Leí y acepto la <a href="%{privacy_policy_path}" target="_blank">política de privacidad</a>
   author_attribution:
     example_title: Texto de ejemplo
     hint_html: "¿Estás escribiendo artículos de noticias o blogs fuera de Mastodon? Controla cómo te acreditan cuando se comparten en Mastodon."
@@ -1451,43 +1369,19 @@ es-MX:
       overwrite: Sobrescribir
       overwrite_long: Reemplazar registros actuales con los nuevos
     overwrite_preambles:
-      blocking_html:
-        one: Estás a punto de <strong>reemplazar tu lista de bloqueos</strong> por <strong>%{count} cuenta</strong> proveniente de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tu lista de bloqueos</strong> por hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Estás a punto de <strong>reemplazar tus marcadores</strong> por <strong>%{count} publicación</strong> proveniente de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tus marcadores</strong> por hasta <strong>%{count} publicaciones</strong> provenientes de <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Estás a punto de <strong>reemplazar tu lista de dominios bloqueados</strong> con <strong>%{count} dominio</strong> de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tu lista de dominios bloqueados</strong> con hasta <strong>%{count} dominios</strong> de <strong>%{filename}</strong>.
-      following_html:
-        one: Estás a punto de <strong>seguir</strong> a <strong>%{count} cuenta</strong> de <strong>%{filename}</strong> y <strong>dejar de seguir a cualquier otra</strong>.
-        other: Estás a punto de <strong>seguir</strong> hasta <strong>%{count} cuentas</strong> de <strong>%{filename}</strong> y <strong>dejar de seguir cualquier otra cuenta</strong>.
-      lists_html:
-        one: Estás a punto de <strong>reemplazar tus listas</strong> con contenidos de <strong>%{filename}</strong>. Hasta <strong>%{count} cuenta</strong> será agregada a nuevas listas.
-        other: Estás a punto de <strong>reemplazar tus listas</strong> con contenidos de <strong>%{filename}</strong>. Hasta <strong>%{count} cuentas</strong> serán agregadas a nuevas listas.
-      muting_html:
-        one: Estás a punto de <strong>reemplazar tu lista de cuentas silenciadas</strong> con <strong>%{count} cuenta</strong> de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tu lista de cuentas silenciadas</strong> con hasta <strong>%{count} cuentas</strong> de <strong>%{filename}</strong>.
+      blocking_html: Estás a punto de <strong>reemplazar tu lista de bloqueos</strong> por hasta <strong>%{total_items} cuentas</strong> provenientes de <strong>%{filename}</strong>.
+      bookmarks_html: Estás a punto de <strong>reemplazar tus marcadores</strong> por hasta <strong>%{total_items} publicaciones</strong> provenientes de <strong>%{filename}</strong>.
+      domain_blocking_html: Estás a punto de <strong>reemplazar tu lista de bloqueos de dominio</strong> por hasta <strong>%{total_items} dominios</strong> provenientes de <strong>%{filename}</strong>.
+      following_html: Estás a punto de <strong>seguir</strong> hasta <strong>%{total_items} cuentas</strong> provenientes de <strong>%{filename}</strong> y <strong>dejar de seguir a cualquier otra cuenta</strong>.
+      lists_html: Estás a punto de <strong>reemplazar tus listas</strong> con contenidos de <strong>%{filename}</strong>. <strong>%{total_items} cuentas</strong> se añadirán a listas nuevas.
+      muting_html: Estás a punto de <strong>reemplazar tu lista de cuentas silenciadas</strong> con hasta <strong>%{total_items} cuentas</strong> proveninetes de <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Estás a punto de <strong>bloquear</strong> a <strong>%{count} cuenta</strong> de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>bloquear</strong> hasta <strong>%{count} cuentas</strong> de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Estás a punto de agregar <strong>%{count} publicación</strong> de <strong>%{filename}</strong> a tus <strong>marcadores</strong>.
-        other: Estás a punto de agregar <strong>%{count} publicaciones</strong> de <strong>%{filename}</strong> a tus <strong>marcadores</strong>.
-      domain_blocking_html:
-        one: Estás a punto de <strong>bloquear</strong> <strong>%{count} dominio</strong> de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>bloquear</strong> hasta <strong>%{count} dominios</strong> de <strong>%{filename}</strong>.
-      following_html:
-        one: Estás a punto de <strong>seguir</strong> a <strong>%{count} cuenta</strong> de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>seguir</strong> hasta <strong>%{count} cuentas</strong> de <strong>%{filename}</strong>.
-      lists_html:
-        one: Estás a punto de agregar <strong>%{count} cuenta</strong> de <strong>%{filename}</strong> a tus <strong>listas</strong>. Se crearán nuevas listas si no hay ninguna lista a la que agregar.
-        other: Estás a punto de agregar <strong>%{count} cuentas</strong> de <strong>%{filename}</strong> a tus <strong>listas</strong>. Se crearán nuevas listas si no hay ninguna lista a la que agregar.
-      muting_html:
-        one: Estás a punto de <strong>silenciar</strong> a <strong>%{count} cuenta</strong> de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>silenciar</strong> hasta <strong>%{count} cuentas</strong> de <strong>%{filename}</strong>.
+      blocking_html: Estás a punto de <strong>bloquear</strong> hasta <strong>%{total_items} cuentas</strong> proveninetes de <strong>%{filename}</strong>.
+      bookmarks_html: Estás a punto de añadir hasta <strong>%{total_items} publicaciones</strong> proveninetes de <strong>%{filename}</strong> a tus <strong>marcadores</strong>.
+      domain_blocking_html: Estás a punto de <strong>bloquear</strong> hasta <strong>%{total_items} dominios</strong> provenientes de <strong>%{filename}</strong>.
+      following_html: Estás a punto de <strong>seguir</strong> hasta <strong>cuentas%{total_items}</strong> provenientes de <strong>%{filename}</strong>.
+      lists_html: Estás a punto de añadir <strong>%{total_items} cuentas</strong> desde <strong>%{filename}</strong> a tus <strong>listas</strong>. Se crearán nuevas listas si no hay listas para añadirlas.
+      muting_html: Estás a punto de <strong>silenciar</strong> hasta <strong>%{total_items} cuentas</strong> provenientes de <strong>%{filename}</strong>.
     preface: Puedes importar ciertos datos, como todas las personas que estás siguiendo o bloqueando en tu cuenta en esta instancia, desde archivos exportados de otra instancia.
     recent_imports: Importaciones recientes
     states:
@@ -1744,7 +1638,7 @@ es-MX:
   scheduled_statuses:
     over_daily_limit: Has superado el límite de %{limit} publicaciones programadas para ese día
     over_total_limit: Has superado el límite de %{limit} publicaciones programadas
-    too_soon: la fecha debe ser en el futuro
+    too_soon: La fecha programada debe estar en el futuro
   self_destruct:
     lead_html: Desafortunadamente, <strong>%{domain}</strong> está cerrando de manera permanente. Si tenías una cuenta ahí, no puedes continuar utilizándolo, pero puedes solicitar un respaldo de tus datos.
     title: Este servidor está cerrando
@@ -1907,8 +1801,6 @@ es-MX:
       too_late: Es demasiado tarde para apelar esta amonestación
   tags:
     does_not_match_previous_name: no coincide con el nombre anterior
-  terms_of_service:
-    title: Condiciones del servicio
   themes:
     contrast: Alto contraste
     default: Mastodon
@@ -1940,10 +1832,6 @@ es-MX:
     recovery_instructions_html: Si pierdes acceso a tu teléfono, puedes usar uno de los siguientes códigos de recuperación para obtener acceso a tu cuenta. <strong>Mantenlos a salvo</strong>. Por ejemplo, puedes imprimirlos y guardarlos con otros documentos importantes.
     webauthn: Claves de seguridad
   user_mailer:
-    announcement_published:
-      description: 'Los administradores de %{domain} están haciendo un anuncio:'
-      subject: Anuncio
-      title: Anuncio de %{domain}
     appeal_approved:
       action: Ajustes de la cuenta
       explanation: La apelación de la amonestación contra tu cuenta del %{strike_date} que enviaste el %{appeal_date} fue aprobada. Tu cuenta se encuentra de nuevo en buen estado.
@@ -1973,15 +1861,6 @@ es-MX:
       further_actions_html: Si no fuiste tú, te recomendamos que %{action} inmediatamente y habilites la autenticación de dos factores para mantener tu cuenta segura.
       subject: Tu cuenta ha sido accedida desde una nueva dirección IP
       title: Un nuevo inicio de sesión
-    terms_of_service_changed:
-      agreement: Al seguir usando %{domain}, aceptas estas condiciones. Si no estás de acuerdo con las condiciones actualizadas, puedes cancelar tu acuerdo con %{domain} en cualquier momento eliminando tu cuenta.
-      changelog: 'En pocas palabras, esto es lo que esta actualización implica para ti:'
-      description: 'Estás recibiendo este correo electrónico porque estamos haciendo algunos cambios en nuestros términos de servicio en %{domain}. Estas actualizaciones entrarán en vigor en %{date}. Te animamos a revisar los términos actualizados en su totalidad aquí:'
-      description_html: Estás recibiendo este correo electrónico porque estamos haciendo algunos cambios en nuestros términos de servicio en %{domain}. Estas actualizaciones entrarán en vigor en <strong>%{date}</strong>. Te animamos a revisar los <a href="%{path}" target="_blank"> términos actualizados en su totalidad aquí</a>.
-      sign_off: El equipo de %{domain}
-      subject: Actualizaciones en nuestras condiciones del servicio
-      subtitle: Las condiciones del servicio de %{domain} han cambiado
-      title: Actualización importante
     warning:
       appeal: Enviar una apelación
       appeal_description: Si crees que esto es un error, puedes enviar una apelación al equipo de %{instance}.
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 9e0feb9d18..75ec81430d 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -2,8 +2,8 @@
 es:
   about:
     about_mastodon_html: 'La red social del futuro: ¡Sin anuncios, sin vigilancia corporativa, diseño ético, y descentralización! ¡Sé dueño de tu información con Mastodon!'
-    contact_missing: No establecido
-    contact_unavailable: N/D
+    contact_missing: No especificado
+    contact_unavailable: No disponible
     hosted_on: Mastodon alojado en %{domain}
     title: Acerca de
   accounts:
@@ -11,7 +11,7 @@ es:
       one: Seguidor
       other: Seguidores
     following: Siguiendo
-    instance_actor_flash: Esta cuenta es un actor virtual utilizado para representar al propio servidor y no a ningún usuario individual. Se utiliza con fines de federación y no debe suspenderse.
+    instance_actor_flash: Esta cuenta es un actor virtual utilizado para representar al servidor en sí mismo y no a ningún usuario individual. Se utiliza para propósitos de la federación y no se debe suspender.
     last_active: última conexión
     link_verified_on: La propiedad de este vínculo fue verificada el %{date}
     nothing_here: "¡No hay nada aquí!"
@@ -40,7 +40,7 @@ es:
       avatar: Avatar
       by_domain: Dominio
       change_email:
-        changed_msg: "¡Correo electrónico cambiado con éxito!"
+        changed_msg: "¡Email cambiado con éxito!"
         current_email: Correo electrónico actual
         label: Cambiar el correo electrónico
         new_email: Nuevo correo electrónico
@@ -64,7 +64,7 @@ es:
       disable_sign_in_token_auth: Deshabilitar la autenticación por token de correo electrónico
       disable_two_factor_authentication: Desactivar autenticación de dos factores
       disabled: Deshabilitada
-      display_name: Nombre para mostrar
+      display_name: Nombre
       domain: Dominio
       edit: Editar
       email: Correo electrónico
@@ -102,7 +102,7 @@ es:
       moderation_notes: Notas de moderación
       most_recent_activity: Actividad más reciente
       most_recent_ip: IP más reciente
-      no_account_selected: No se ha modificado ninguna cuenta porque no se ha seleccionado ninguna
+      no_account_selected: Ninguna cuenta se cambió, ya que ninguna fue seleccionada
       no_limits_imposed: Sin límites impuestos
       no_role_assigned: Ningún rol asignado
       not_subscribed: No se está suscrito
@@ -187,7 +187,6 @@ es:
         create_domain_block: Crear Bloqueo de Dominio
         create_email_domain_block: Crear Bloqueo de Dominio de Correo Electrónico
         create_ip_block: Crear regla IP
-        create_relay: Crear Relé
         create_unavailable_domain: Crear Dominio No Disponible
         create_user_role: Crear Rol
         demote_user: Degradar Usuario
@@ -199,22 +198,18 @@ es:
         destroy_email_domain_block: Eliminar Bloqueo de Dominio de Correo Electrónico
         destroy_instance: Purgar Dominio
         destroy_ip_block: Eliminar regla IP
-        destroy_relay: Eliminar Relé
         destroy_status: Eliminar Publicación
         destroy_unavailable_domain: Eliminar Dominio No Disponible
         destroy_user_role: Destruir Rol
         disable_2fa_user: Deshabilitar 2FA
         disable_custom_emoji: Deshabilitar Emoji Personalizado
-        disable_relay: Desactivar Relé
         disable_sign_in_token_auth_user: Deshabilitar la Autenticación por Token de Correo Electrónico para el Usuario
         disable_user: Deshabilitar Usuario
         enable_custom_emoji: Habilitar Emoji Personalizado
-        enable_relay: Activar Relé
         enable_sign_in_token_auth_user: Habilitar la Autenticación por Token de Correo Electrónico para el Usuario
         enable_user: Habilitar Usuario
         memorialize_account: Transformar en Cuenta Conmemorativa
         promote_user: Promover Usuario
-        publish_terms_of_service: Publicar términos del servicio
         reject_appeal: Rechazar Apelación
         reject_user: Rechazar Usuario
         remove_avatar_user: Eliminar Avatar
@@ -252,7 +247,6 @@ es:
         create_domain_block_html: "%{name} bloqueó el dominio %{target}"
         create_email_domain_block_html: "%{name} bloqueó el dominio de correo electrónico %{target}"
         create_ip_block_html: "%{name} creó una regla para la IP %{target}"
-        create_relay_html: "%{name} creó un relé %{target}"
         create_unavailable_domain_html: "%{name} detuvo las entregas al dominio %{target}"
         create_user_role_html: "%{name} creó el rol %{target}"
         demote_user_html: "%{name} degradó al usuario %{target}"
@@ -264,22 +258,18 @@ es:
         destroy_email_domain_block_html: "%{name} desbloqueó el dominio de correo electrónico %{target}"
         destroy_instance_html: "%{name} purgó el dominio %{target}"
         destroy_ip_block_html: "%{name} eliminó una regla para la IP %{target}"
-        destroy_relay_html: "%{name} eliminó el relé %{target}"
         destroy_status_html: "%{name} eliminó la publicación de %{target}"
         destroy_unavailable_domain_html: "%{name} reanudó las entregas al dominio %{target}"
         destroy_user_role_html: "%{name} eliminó el rol %{target}"
         disable_2fa_user_html: "%{name} desactivó el requisito de dos factores para el usuario %{target}"
         disable_custom_emoji_html: "%{name} desactivó el emoji %{target}"
-        disable_relay_html: "%{name} desactivó el relé %{target}"
         disable_sign_in_token_auth_user_html: "%{name} ha deshabilitado la autenticación por token de correo electrónico para %{target}"
         disable_user_html: "%{name} deshabilitó el inicio de sesión para el usuario %{target}"
         enable_custom_emoji_html: "%{name} activó el emoji %{target}"
-        enable_relay_html: "%{name} activó el relé %{target}"
         enable_sign_in_token_auth_user_html: "%{name} ha habilitado la autenticación por token de correo electrónico para %{target}"
         enable_user_html: "%{name} habilitó el inicio de sesión para el usuario %{target}"
         memorialize_account_html: "%{name} convirtió la cuenta de %{target} en una página in memoriam"
         promote_user_html: "%{name} promoción al usuario %{target}"
-        publish_terms_of_service_html: "%{name} ha publicado actualizaciones de los términos del servicio"
         reject_appeal_html: "%{name} rechazó la solicitud de moderación de %{target}"
         reject_user_html: "%{name} rechazó el registro de %{target}"
         remove_avatar_user_html: "%{name} eliminó el avatar de %{target}"
@@ -309,7 +299,6 @@ es:
       title: Registro de auditoría
       unavailable_instance: "(nombre de dominio no disponible)"
     announcements:
-      back: Volver a la sección de anuncios
       destroyed_msg: "¡Anuncio eliminado con éxito!"
       edit:
         title: Editar anuncio
@@ -318,10 +307,6 @@ es:
       new:
         create: Crear anuncio
         title: Nuevo anuncio
-      preview:
-        disclaimer: Como los usuarios no pueden optar por no recibirlas, las notificaciones por correo electrónico deben limitarse a anuncios importantes, como la violación de datos personales o las notificaciones de cierre de servidores.
-        explanation_html: 'El correo electrónico se enviará a <strong>%{display_count} usuarios</strong>. En el correo electrónico se incluirá el siguiente texto:'
-        title: Vista previa de la notificación del anuncio
       publish: Publicar
       published_msg: "¡Anuncio publicado con éxito!"
       scheduled_for: Programado para %{time}
@@ -480,36 +465,6 @@ es:
       new:
         title: Importar bloqueos de dominio
       no_file: Ningún archivo seleccionado
-    fasp:
-      debug:
-        callbacks:
-          created_at: Creado el
-          delete: Eliminar
-          ip: Dirección IP
-          request_body: Cuerpo de la solicitud
-          title: Depurar devoluciones de llamada
-      providers:
-        active: Activo
-        base_url: URL base
-        callback: Devolución de llamada
-        delete: Eliminar
-        edit: Editar proveedor
-        finish_registration: Terminar el registro
-        name: Nombre
-        providers: Proveedores
-        public_key_fingerprint: Huella digital de clave pública
-        registration_requested: Se solicitó el registro
-        registrations:
-          confirm: Confirmar
-          description: Has recibido un registro de un FASP. Recházalo si no lo iniciaste tú. Si lo iniciaste tú, compara cuidadosamente el nombre y la huella de la clave antes de confirmar el registro.
-          reject: Rechazar
-          title: Confirmar registro FASP
-        save: Guardar
-        select_capabilities: Selecciona las capacidades
-        sign_in: Iniciar sesión
-        status: Estado
-        title: Fediverse Auxiliary Service Providers (Proveedores de Servicio Auxiliares del Fediverso)
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Las recomendaciones de cuentas ayudan a los nuevos usuarios a encontrar rápidamente contenido interesante</strong>. Cuando un usuario no ha interactuado con otros lo suficiente como para suscitar recomendaciones personalizadas de cuentas a las que seguir, en su lugar se le recomiendan estas cuentas. Se recalculan diariamente a partir de una mezcla de cuentas con el mayor número de interacciones recientes y con el mayor número de seguidores locales con un idioma determinado."
       language: Para el idioma
@@ -863,10 +818,8 @@ es:
       back_to_account: Volver a la cuenta
       back_to_report: Volver a la página del reporte
       batch:
-        add_to_report: 'Añadir al informe #%{id}'
         remove_from_report: Eliminar del reporte
         report: Reporte
-      contents: Contenidos
       deleted: Eliminado
       favourites: Favoritos
       history: Historial de versiones
@@ -875,17 +828,13 @@ es:
       media:
         title: Multimedia
       metadata: Metadatos
-      no_history: Esta publicación no ha sido editada
       no_status_selected: No se cambió ninguna publicación al no seleccionar ninguna
       open: Abrir publicación
       original_status: Publicación original
       reblogs: Impulsos
-      replied_to_html: Respondió a %{acct_link}
       status_changed: Publicación cambiada
-      status_title: Publicación de @%{name}
-      title: Publicaciones de la cuenta - @%{name}
+      title: Publicaciones de la cuenta
       trending: En tendencia
-      view_publicly: Ver públicamente
       visibility: Visibilidad
       with_media: Con multimedia
     strikes:
@@ -962,36 +911,6 @@ es:
       search: Buscar
       title: Etiquetas
       updated_msg: La configuración de etiquetas se actualizó correctamente
-    terms_of_service:
-      back: Volver a los términos del servicio
-      changelog: Qué ha cambiado
-      create: Usa los tuyos
-      current: Actual
-      draft: Borrador
-      generate: Usar plantilla
-      generates:
-        action: Generar
-        chance_to_review_html: "<strong>Los términos del servicio generados no se publicarán automáticamente..</strong> Tendrás la oportunidad de revisar el resultado. Por favor, rellena los detalles necesarios para continuar."
-        explanation_html: La plantilla de términos de servicio ofrecida es únicamente para propósito informativo, y no debería ser considerada asesoramiento legal sobre ningún tema. Por favor, consulta con tu propio consejo legal sobre tu situación y las cuestiones legales específicas que tengas.
-        title: Configuración de términos del servicio
-      going_live_on_html: En vigor desde %{date}
-      history: Historial
-      live: En vivo
-      no_history: Aún no se han registrado cambios en los términos del servicio.
-      no_terms_of_service_html: Actualmente no tienes configurado ningún término del servicio. Los términos del servicio están pensados para proporcionar claridad y protegerte de posibles responsabilidades en disputas con tus usuarios.
-      notified_on_html: Usuarios notificados el %{date}
-      notify_users: Notificar usuarios
-      preview:
-        explanation_html: 'El correo se enviará a <strong>%{display_count} usuarios</strong> que se registraron antes de %{date}. El siguiente texto se incluirá en el correo:'
-        send_preview: Enviar vista previa a %{email}
-        send_to_all:
-          one: Enviar %{display_count} correo electrónico
-          other: Enviar %{display_count} correos electrónicos
-        title: Vista previa de la notificación de términos del servicios
-      publish: Publicar
-      published_on_html: Publicado el %{date}
-      save_draft: Guardar borrador
-      title: Términos del Servicio
     title: Administración
     trends:
       allow: Permitir
@@ -1199,6 +1118,7 @@ es:
     migrate_account: Mudarse a otra cuenta
     migrate_account_html: Si deseas redireccionar esta cuenta a otra distinta, puedes <a href="%{path}">configurarlo aquí</a>.
     or_log_in_with: O inicia sesión con
+    privacy_policy_agreement_html: He leído y acepto la <a href="%{privacy_policy_path}" target="_blank">política de privacidad</a>
     progress:
       confirm: Confirmar dirección de correo
       details: Tus detalles
@@ -1223,7 +1143,7 @@ es:
     set_new_password: Establecer nueva contraseña
     setup:
       email_below_hint_html: Comprueba tu carpeta de correo no deseado o solicita otro enlace de confirmación. Puedes corregir tu dirección de correo electrónico si está mal.
-      email_settings_hint_html: Haz clic en el enlace que enviamos a %{email} para comenzar a usar Mastodon. Estaremos esperando aquí mismo.
+      email_settings_hint_html: Haz clic en el enlace que te hemos enviado para verificar %{email}. Te esperamos aquí.
       link_not_received: "¿No recibiste un enlace?"
       new_confirmation_instructions_sent: "¡Recibirás un nuevo correo electrónico con el enlace de confirmación en unos minutos!"
       title: Revisa tu bandeja de entrada
@@ -1232,7 +1152,7 @@ es:
       title: Iniciar sesión en %{domain}
     sign_up:
       manual_review: Los registros en %{domain} pasan por la revisión manual de nuestros moderadores. Para ayudarnos a procesar tu registro, escribe un poco sobre ti mismo y por qué quieres una cuenta en %{domain}.
-      preamble: Con una cuenta en este servidor de Mastodon, podrás seguir a cualquier otra persona en el fediverso, independientemente de dónde esté alojada su cuenta.
+      preamble: Con una cuenta en este servidor de Mastodon, podrás seguir a cualquier otra persona en la red, independientemente del servidor en el que se encuentre.
       title: Crear cuenta de Mastodon en %{domain}.
     status:
       account_status: Estado de la cuenta
@@ -1244,8 +1164,6 @@ es:
       view_strikes: Ver amonestaciones pasadas contra tu cuenta
     too_fast: Formulario enviado demasiado rápido, inténtelo de nuevo.
     use_security_key: Usar la clave de seguridad
-    user_agreement_html: He leído y acepto los <a href="%{terms_of_service_path}" target="_blank">términos del servicio</a> y la <a href="%{privacy_policy_path}" target="_blank">política de privacidad</a>
-    user_privacy_agreement_html: He leído y acepto la <a href="%{privacy_policy_path}" target="_blank">política de privacidad</a>
   author_attribution:
     example_title: Texto de ejemplo
     hint_html: "¿Escribes noticias o artículos de blog fuera de Mastodon? Controla cómo se te acredita cuando se comparten en Mastodon."
@@ -1451,43 +1369,19 @@ es:
       overwrite: Sobrescribir
       overwrite_long: Reemplazar registros actuales con los nuevos
     overwrite_preambles:
-      blocking_html:
-        one: Estás a punto de <strong>reemplazar tu lista de bloqueos</strong> por <strong>%{count} cuenta</strong> proveniente de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tu lista de bloqueos</strong> por hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Estás a punto de <strong>reemplazar tus marcadores</strong> por <strong>%{count} publicación</strong> proveniente de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tus marcadores</strong> por hasta <strong>%{count} publicaciones</strong> provenientes de <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Estás a punto de <strong>reemplazar tu lista de bloqueos de dominio</strong> por <strong>%{count} dominio</strong> proveniente de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tu lista de bloqueos de dominio</strong> por hasta <strong>%{count} dominios</strong> provenientes de <strong>%{filename}</strong>.
-      following_html:
-        one: Estás a punto de <strong>seguir</strong> a <strong>%{count} cuenta</strong> proveniente de <strong>%{filename}</strong> y <strong>dejar de seguir a cualquier otra cuenta</strong>.
-        other: Estás a punto de <strong>seguir</strong> hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong> y <strong>dejar de seguir a cualquier otra cuenta</strong>.
-      lists_html:
-        one: Estás a punto de <strong>reemplazar tus listas</strong> con el contenido de <strong>%{filename}</strong>. Se añadirá <strong>%{count} cuenta</strong> a una nueva lista.
-        other: Estás a punto de <strong>reemplazar tus listas</strong> con el contenido de <strong>%{filename}</strong>. Se añadirán <strong>%{count} cuentas</strong> a nuevas listas.
-      muting_html:
-        one: Estás a punto de <strong>reemplazar tu lista de cuentas silenciadas</strong> con <strong>%{count} cuenta</strong> proveninete de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>reemplazar tu lista de cuentas silenciadas</strong> con hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong>.
+      blocking_html: Estás a punto de <strong>reemplazar tu lista de bloqueos</strong> por hasta <strong>%{total_items} cuentas</strong> provenientes de <strong>%{filename}</strong>.
+      bookmarks_html: Estás a punto de <strong>reemplazar tus marcadores</strong> por hasta <strong>%{total_items} publicaciones</strong> provenientes de <strong>%{filename}</strong>.
+      domain_blocking_html: Estás a punto de <strong>reemplazar tu lista de bloqueos de dominio</strong> por hasta <strong>%{total_items} dominios</strong> provenientes de <strong>%{filename}</strong>.
+      following_html: Estás a punto de <strong>seguir</strong> hasta <strong>%{total_items} cuentas</strong> provenientes de <strong>%{filename}</strong> y <strong>dejar de seguir a cualquier otra cuenta</strong>.
+      lists_html: Estás a punto de <strong>reemplazar tus listas</strong> con contenidos de <strong>%{filename}</strong>. Se añadirán <strong>%{total_items} cuentas</strong> a nuevas listas.
+      muting_html: Estás a punto de <strong>reemplazar tu lista de cuentas silenciadas</strong> con hasta <strong>%{total_items} cuentas</strong> proveninetes de <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Estás a punto de <strong>bloquear</strong> a <strong>%{count} cuenta</strong> proveninete de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>bloquear</strong> hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Está a punto de añadir <strong>%{count} publicación</strong> proveniente de <strong>%{filename}</strong> a tus <strong>marcadores</strong>.
-        other: Está a punto de añadir hasta <strong>%{count} publicaciones</strong> provenientes de <strong>%{filename}</strong> a tus <strong>marcadores</strong>.
-      domain_blocking_html:
-        one: Estás a punto de <strong>bloquear</strong> <strong>%{count} dominio</strong> proveniente de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>bloquear</strong> hasta <strong>%{count} dominios</strong> provenientes de <strong>%{filename}</strong>.
-      following_html:
-        one: Estás a punto de <strong>seguir</strong> a <strong>%{count} cuenta</strong> proveniente de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>seguir</strong> hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong>.
-      lists_html:
-        one: Estás a punto de añadir <strong>%{count} cuenta</strong> desde <strong>%{filename}</strong> a tus <strong>listas</strong>. Se creará una nueva listas si no hay listas donde añadirla.
-        other: Estás a punto de añadir <strong>%{count} cuentas</strong> desde <strong>%{filename}</strong> a tus <strong>listas</strong>. Se crearán nuevas listas si no hay listas donde añadirlas.
-      muting_html:
-        one: Estás a punto de <strong>silenciar</strong> a <strong>%{count} cuenta</strong> proveniente de <strong>%{filename}</strong>.
-        other: Estás a punto de <strong>silenciar</strong> hasta <strong>%{count} cuentas</strong> provenientes de <strong>%{filename}</strong>.
+      blocking_html: Estás a punto de <strong>bloquear</strong> hasta <strong>%{total_items} cuentas</strong> proveninetes de <strong>%{filename}</strong>.
+      bookmarks_html: Está a punto de añadir hasta <strong>%{total_items} publicaciones</strong> proveninetes de <strong>%{filename}</strong> a tus <strong>marcadores</strong>.
+      domain_blocking_html: Estás a punto de <strong>bloquear</strong> hasta <strong>%{total_items} dominios</strong> provenientes de <strong>%{filename}</strong>.
+      following_html: Estás a punto de <strong>seguir</strong> hasta <strong>cuentas%{total_items}</strong> provenientes de <strong>%{filename}</strong>.
+      lists_html: Estás a punto de añadir <strong>%{total_items} cuentas</strong> desde <strong>%{filename}</strong> a tus <strong>listas</strong>. Se crearán nuevas listas si no hay listas para añadirlas.
+      muting_html: Estás a punto de <strong>silenciar</strong> hasta <strong>%{total_items} cuentas</strong> provenientes de <strong>%{filename}</strong>.
     preface: Puedes importar ciertos datos, como todas las personas que estás siguiendo o bloqueando en tu cuenta en esta instancia, desde archivos exportados de otra instancia.
     recent_imports: Importaciones recientes
     states:
@@ -1744,7 +1638,7 @@ es:
   scheduled_statuses:
     over_daily_limit: Ha superado el límite de %{limit} publicaciones programadas para ese día
     over_total_limit: Ha superado el límite de %{limit} publicaciones programadas
-    too_soon: la fecha debe ser en el futuro
+    too_soon: La fecha programada debe estar en el futuro
   self_destruct:
     lead_html: Desafortunadamente, <strong>%{domain}</strong> va a cerrar permanentemente. Si tenías una cuenta allí, no podrás continuar usándola, pero aún puedes solicitar una copia de tus datos.
     title: Este servidor está cerrando
@@ -1907,8 +1801,6 @@ es:
       too_late: Es demasiado tarde para apelar esta amonestación
   tags:
     does_not_match_previous_name: no coincide con el nombre anterior
-  terms_of_service:
-    title: Términos del Servicio
   themes:
     contrast: Mastodon (alto contraste)
     default: Mastodon (oscuro)
@@ -1940,10 +1832,6 @@ es:
     recovery_instructions_html: Si pierdes acceso a tu teléfono, puedes usar uno de los siguientes códigos de recuperación para obtener acceso a tu cuenta. <strong>Mantenlos a salvo</strong>. Por ejemplo, puedes imprimirlos y guardarlos con otros documentos importantes.
     webauthn: Claves de seguridad
   user_mailer:
-    announcement_published:
-      description: 'Los administradores de %{domain} están haciendo un anuncio:'
-      subject: Anuncio
-      title: Anuncio de %{domain}
     appeal_approved:
       action: Ajustes de la cuenta
       explanation: La apelación de la amonestación contra tu cuenta del %{strike_date} que enviaste el %{appeal_date} fue aprobada. Tu cuenta se encuentra de nuevo en buen estado.
@@ -1973,15 +1861,6 @@ es:
       further_actions_html: Si no fuiste tú, te recomendamos que %{action} inmediatamente y habilites la autenticación de dos factores para mantener tu cuenta segura.
       subject: Tu cuenta ha sido accedida desde una nueva dirección IP
       title: Un nuevo inicio de sesión
-    terms_of_service_changed:
-      agreement: Al seguir usando %{domain}, aceptas estos términos. Si no estás de acuerdo con los términos actualizados, puedes cancelar tu acuerdo con %{domain} en cualquier momento eliminando tu cuenta.
-      changelog: 'En resumen, esto es lo que esta actualización significa para ti:'
-      description: 'Estás recibiendo este correo electrónico porque estamos haciendo algunos cambios en nuestros términos de servicio en %{domain}. Estas actualizaciones entrarán en vigor en %{date}. Te animamos a revisar los términos actualizados en su totalidad aquí:'
-      description_html: Estás recibiendo este correo electrónico porque estamos haciendo algunos cambios en nuestros términos de servicio en %{domain}. Estas actualizaciones entrarán en vigor en <strong>%{date}</strong>. Te animamos a revisar los <a href="%{path}" target="_blank"> términos actualizados en su totalidad aquí</a>.
-      sign_off: El equipo de %{domain}
-      subject: Actualizaciones en nuestros términos del servicio
-      subtitle: Los términos del servicio de %{domain} están cambiando
-      title: Actualización importante
     warning:
       appeal: Enviar una apelación
       appeal_description: Si crees que esto es un error, puedes enviar una apelación al personal de %{instance}.
diff --git a/config/locales/et.yml b/config/locales/et.yml
index 8339ed087f..19c7f8c799 100644
--- a/config/locales/et.yml
+++ b/config/locales/et.yml
@@ -828,17 +828,13 @@ et:
       media:
         title: Meedia
       metadata: Metaandmed
-      no_history: Seda postitust pole muudetud
       no_status_selected: Ühtegi postitust ei muudetud, sest ühtegi polnud valitud
       open: Ava postitus
       original_status: Algne postitus
       reblogs: Jagamised
-      replied_to_html: Vastatud %{acct_link}
       status_changed: Muudetud postitus
-      status_title: Postitajaks @%{name}
-      title: Postitus kontolt - @%{name}
+      title: Konto postitused
       trending: Populaarne
-      view_publicly: Nähtav avalikult
       visibility: Nähtavus
       with_media: Meediaga
     strikes:
@@ -915,19 +911,6 @@ et:
       search: Otsi
       title: Märksõnad
       updated_msg: Sildi sätted edukalt uuendatud
-    terms_of_service:
-      back: Tagasi teenuse tingimustesse
-      changelog: Mis on muutunud
-      create: Kasuta enda oma
-      current: Praegune
-      draft: Mustand
-      generate: Kasuta malli
-      generates:
-        action: Genereeri
-        chance_to_review_html: "<strong>Loodud teenusetingimusi ei avaldata automaatselt.</strong> Sul on võimalus tulemused üle vaadata. Palun täida vajalikud andmed, et jätkata."
-        explanation_html: Esitatud teenusetingimuste näidis on mõeldud ainult teavitamise eesmärgil ja seda ei tohiks tõlgendada kui juriidilist nõuannet mis tahes küsimuses. Palun konsulteeri olukorra ja konkreetsete juriidiliste küsimuste osas oma õigusnõustajaga.
-        title: Teenuse tingimuste seadistamine
-      history: Ajalugu
     title: Administreerimine
     trends:
       allow: Luba
@@ -1135,6 +1118,7 @@ et:
     migrate_account: Teisele kontole ära kolimine
     migrate_account_html: Kui soovid konto siit ära kolida, <a href="%{path}">saad seda teha siin</a>.
     or_log_in_with: Või logi sisse koos
+    privacy_policy_agreement_html: Olen tutvunud <a href="%{privacy_policy_path}" target="_blank">isikuandmete kaitse põhimõtetega</a> ja nõustun nendega
     progress:
       confirm: E-posti kinnitamine
       details: Sinu üksikasjad
@@ -1159,7 +1143,7 @@ et:
     set_new_password: Uue salasõna määramine
     setup:
       email_below_hint_html: Kontrolli rämpsposti kausta või taotle uut. Saad oma e-posti aadressi parandada, kui see on vale.
-      email_settings_hint_html: Klõpsa aadressile %{email} saadetud linki, et alustada Mastodoni kasutamist. Me oleme ootel.
+      email_settings_hint_html: Klõpsa linki, mis saadeti sulle, et kinnitada %{email}. Seni me ootame.
       link_not_received: Kas ei saanud linki?
       new_confirmation_instructions_sent: Saad mõne minuti pärast uue kinnituslingiga e-kirja!
       title: Kontrolli sisendkasti
@@ -1168,7 +1152,7 @@ et:
       title: Logi sisse kohta %{domain}
     sign_up:
       manual_review: Liitumised kohas %{domain} vaadatakse meie moderaatorite poolt käsitsi läbi. Aitamaks meil sinu taotlust läbi vaadata, kirjuta palun natuke endast ja miks soovid kontot kohas %{domain}.
-      preamble: Selle Mastodoni serveri kontoga saad jälgida mistahes teist isikut fediversumis, sõltumata sellest, kus ta konto on majutatud.
+      preamble: Selle kontoga saad jälgida ja suhelda kõigi teiste kasutajatega erinevates Mastodoni serverites.
       title: Loo konto serverisse  %{domain}.
     status:
       account_status: Konto olek
@@ -1385,43 +1369,19 @@ et:
       overwrite: Kirjuta üle
       overwrite_long: Vaheta praegused andmed uute vastu
     overwrite_preambles:
-      blocking_html:
-        one: Oled <strong>asendamas oma blokeeringute loetelu</strong> faili <strong>%{filename}</strong> sisuga, milles on kuni <strong>%{count} konto</strong>.
-        other: Oled <strong>asendamas oma blokeeringute loetelu</strong> faili <strong>%{filename}</strong> sisuga, milles on kuni <strong>%{count} kontot</strong>.
-      bookmarks_html:
-        one: Oled <strong>asendamas oma järjehoidjad</strong> faili <strong>%{filename}</strong> sisuga, milles on kuni <strong>%{count} postitus</strong>.
-        other: Oled <strong>asendamas oma järjehoidjad</strong> faili <strong>%{filename}</strong> sisuga, milles on kuni <strong>%{count} postitust</strong>.
-      domain_blocking_html:
-        one: Oled <strong>asendamas oma domeenide blokeeringu loetelu</strong> faili <strong>%{filename}</strong> sisuga, milles on kuni <strong>%{count} domeen</strong>.
-        other: Oled <strong>asendamas oma domeenide blokeeringu loetelu</strong> faili <strong>%{filename}</strong> sisuga, milles on kuni <strong>%{count} domeen</strong>.
-      following_html:
-        one: Oled <strong>jälgima hakkamas</strong> kuni <strong>%{count} kontot</strong> failist <strong>%{filename}</strong> ja <strong>lõpetad kõigi teiste jälgimise</strong>.
-        other: Oled <strong>jälgima hakkamas</strong> kuni <strong>%{count} kontot</strong> failist <strong>%{filename}</strong> ja <strong>lõpetad kõigi teiste jälgimise</strong>.
-      lists_html:
-        one: Oled <strong>asendamas oma loetelusid</strong> faili <strong>%{filename}</strong> sisuga. Uutesse loeteludesse lisatakse kuni <strong>%{count} konto</strong>.
-        other: Oled <strong>asendamas oma loetelusid</strong> faili <strong>%{filename}</strong> sisuga. Uutesse loeteludesse lisatakse kuni <strong>%{count} kontot</strong>.
-      muting_html:
-        one: Oled <strong>asendamas oma vaigistatud kontode loetelu</strong> faili <strong>%{filename}</strong> sisuga, milles on kuni <strong>%{count} konto</strong>.
-        other: Oled <strong>asendamas oma vaigistatud kontode loetelu</strong> faili <strong>%{filename}</strong> sisuga, milles on kuni <strong>%{count} kontot</strong>.
+      blocking_html: Oled <strong>asendamas oma blokeeringute loetelu</strong> kuni <strong>%{total_items} kontoga </strong> kohast <strong>%{filename}</strong>.
+      bookmarks_html: Oled <strong>asendamas oma järjehoidjaid</strong> kuni <strong>%{total_items} postitusega </strong> kohast <strong>%{filename}</strong>.
+      domain_blocking_html: Oled <strong>asendamas oma domeenide blokeeringute loetelu </strong> kuni <strong>%{total_items} domeeniga </strong> kohast <strong>%{filename}</strong>.
+      following_html: Oled <strong>jälgima hakkamas</strong> kuni <strong>%{total_items} kontot</strong> kohast <strong>%{filename}</strong> ja <strong>lõpetad kõigi teiste jälgimise</strong>.
+      lists_html: Oled <strong>asendamas oma loetelusid</strong> faili <strong>%{filename}</strong> sisuga. Uutesse loeteludesse lisatakse kuni <strong>%{total_items} kontot</strong>.
+      muting_html: Oled <strong>asendamas oma vaigistatud kontode loetelu</strong> kuni <strong>%{total_items} kontoga </strong> kohast <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Oled <strong>blokeerimas</strong> kuni <strong>%{count} konto</strong> failist <strong>%{filename}</strong>.
-        other: Oled <strong>blokeerimas</strong> kuni <strong>%{count} kontot</strong> failist <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Oled lisamas kuni <strong>%{count} postituse</strong> failist <strong>%{filename}</strong> oma <strong>järjehoidjatesse</strong>.
-        other: Oled lisamas kuni <strong>%{count} postitust</strong> failist <strong>%{filename}</strong> oma <strong>järjehoidjatesse</strong>.
-      domain_blocking_html:
-        one: Oled <strong>blokeerimas</strong> kuni <strong>%{count} domeeni</strong> failist <strong>%{filename}</strong>.
-        other: Oled <strong>blokeerimas</strong> kuni <strong>%{count} domeeni</strong> failist <strong>%{filename}</strong>.
-      following_html:
-        one: Oled <strong>jälgima hakkamas</strong> kuni <strong>%{count} konto</strong> failist <strong>%{filename}</strong>.
-        other: Oled <strong>jälgima hakkamas</strong> kuni <strong>%{count} kontot</strong> failist <strong>%{filename}</strong>.
-      lists_html:
-        one: Oled lisamas oma <strong>loeteludesse</strong> failist <strong>%{filename}</strong> kuni <strong>%{count} konto</strong>. Kui pole loetelusi, kuhu lisada, luuakse uued loetelud.
-        other: Oled lisamas oma <strong>loeteludesse</strong> failist <strong>%{filename}</strong> kuni <strong>%{count} kontot</strong>. Kui pole loetelusi, kuhu lisada, luuakse uued loetelud.
-      muting_html:
-        one: Oled <strong>vaigistamas</strong> kuni <strong>%{count} konto</strong> failist <strong>%{filename}</strong>.
-        other: Oled <strong>vaigistamas</strong> kuni <strong>%{count} kontot</strong> failist <strong>%{filename}</strong>.
+      blocking_html: Oled <strong>blokeerimas</strong> kuni <strong>%{total_items} kontoga </strong> kohast <strong>%{filename}</strong>.
+      bookmarks_html: Oled lisamas kuni <strong>%{total_items} postitust</strong> kohast <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
+      domain_blocking_html: Oled <strong>blokeerimas</strong> kuni <strong>%{total_items} domeeni</strong> kohast <strong>%{filename}</strong>.
+      following_html: Oled <strong>jälgima hakkamas</strong> kuni <strong>%{total_items} kontot</strong> kohast <strong>%{filename}</strong>.
+      lists_html: Oled lisamas oma <strong>loeteludesse</strong> failist <strong>%{filename}</strong> kuni <strong>%{total_items} kontot</strong>. Kui pole loetelusi, kuhu lisada, luuakse uued loetelud.
+      muting_html: Oled <strong>vaigistamas</strong> kuni <strong>%{total_items} kontot</strong> kohast <strong>%{filename}</strong>.
     preface: Saad importida mistahes andmeid, mis on eksporditud teisest serverist. Näiteks nimekirja inimestest, keda jälgid ja keda blokeerid.
     recent_imports: Viimati imporditud
     states:
@@ -1678,6 +1638,7 @@ et:
   scheduled_statuses:
     over_daily_limit: Lubatud ajastatud postituste arv %{limit} päevas on tänaseks ületatud
     over_total_limit: Oled jõudnud ajastatud postituste lubatud maksimumarvuni %{limit}
+    too_soon: Ajastatud kuupäev peab olema tukevikus
   self_destruct:
     lead_html: Kahjuks suletakse <strong>%{domain}</strong> lõplikult. Kui sul oli seal konto, ei saa sa seda enam kasutada, kuid siiski võid taotleda oma andmete varukoopiat.
     title: See server suletakse
diff --git a/config/locales/eu.yml b/config/locales/eu.yml
index 97943a46bb..5720b3cc90 100644
--- a/config/locales/eu.yml
+++ b/config/locales/eu.yml
@@ -808,6 +808,7 @@ eu:
       original_status: Jatorrizko bidalketa
       reblogs: Bultzadak
       status_changed: Bidalketa aldatuta
+      title: Kontuaren bidalketak
       trending: Joera
       visibility: Ikusgaitasuna
       with_media: Multimediarekin
@@ -1056,6 +1057,7 @@ eu:
     migrate_account: Migratu beste kontu batera
     migrate_account_html: Kontu hau beste batera birbideratu nahi baduzu, <a href="%{path}">hemen konfiguratu</a> dezakezu.
     or_log_in_with: Edo hasi saioa honekin
+    privacy_policy_agreement_html: <a href="%{privacy_policy_path}" target="_blank">Pribatutasun politika</a> irakurri dut eta ados nago
     progress:
       details: Zure xehetasunak
       review: Gure berrikuspena
@@ -1078,6 +1080,7 @@ eu:
     security: Segurtasuna
     set_new_password: Ezarri pasahitza berria
     setup:
+      email_settings_hint_html: Egin klik bidali dizugun estekan %{email} helbidea egiaztatzeko. Hementxe itxarongo zaitugu.
       link_not_received: Ez duzu estekarik jaso?
       title: Begiratu zure sarrera-ontzia
     sign_in:
@@ -1085,6 +1088,7 @@ eu:
       title: "%{domain}-(e)an saioa hasi"
     sign_up:
       manual_review: "%{domain}-(e)n eginiko izen-emateak gure moderatzaileek eskuz aztertzen dituzte. Zure izen-ematea prozesatzen lagun gaitzazun, idatz ezazu zertxobait zuri buruz eta azaldu zergatik nahi duzun %{domain}-(e)n kontu bat."
+      preamble: Mastodon zerbitzari honetako kontu batekin, aukera izango duzu sareko edozein pertsona jarraitzeko, ez dio axola kontua non ostatatua dagoen.
       title: "%{domain} zerbitzariko kontua prestatuko dizugu."
     status:
       account_status: Kontuaren egoera
@@ -1287,6 +1291,20 @@ eu:
       merge_long: Mantendu dauden erregistroak eta gehitu berriak
       overwrite: Gainidatzi
       overwrite_long: Ordeztu oraingo erregistroak berriekin
+    overwrite_preambles:
+      blocking_html: "<strong>Blokeatutakoen zerrenda</strong> <strong>%{filename}</strong> fitxategiak dituen <strong>%{total_items} kontu</strong>ekin ordeztear zaude."
+      bookmarks_html: "<strong>Laster-markak</strong> <strong>%{filename}</strong> fitxategiak dituen <strong>%{total_items} argitalpen</strong>ekin ordeztear zaude."
+      domain_blocking_html: "<strong>Blokeatutako domeinuen zerrenda</strong> <strong>%{filename}</strong> fitxategiak dituen <strong>%{total_items} domeinu</strong>ekin ordeztear zaude."
+      following_html: "<strong>%{filename}</strong> fitxategiak dituen <strong>%{total_items} kontu</strong>ei <strong>jarraitzear</strong> zaude; <strong>gainontzekoak jarraitzeari utziko diozu</strong>."
+      lists_html: "<strong>Zerrendak</strong> <strong>%{filename}</strong> fitxategiak duen edukiarekin ordeztear zaude. <strong>%{total_items} kontu</strong> gehituko dira zerrenda berrietara."
+      muting_html: "<strong>Mutututako kontuen zerrenda</strong> <strong>%{filename}</strong> fitxategiak dituen <strong>%{total_items} kontu</strong>ekin ordeztear zaude."
+    preambles:
+      blocking_html: "<strong>%{filename}</strong> fitxategiak dituen <strong>%{total_items} kontu</strong>ak <strong>blokeatzear</strong> zaude."
+      bookmarks_html: "<strong>%{filename}</strong> fitxategiak dituen <strong>%{total_items} argitalpen</strong>ei <strong>laster-marka</strong> jartzear zaude."
+      domain_blocking_html: "<strong>%{filename}</strong> fitxategiak dituen <strong>%{total_items} domeinu</strong>ak <strong>blokeatzear</strong> zaude."
+      following_html: "<strong>%{filename}</strong> fitxategiak dituen <strong>%{total_items} kontu</strong>ri <strong>jarraitzear</strong> zaude."
+      lists_html: "<strong>%{filename}</strong> fitxategiak dituen <strong>%{total_items} kontu</strong> <strong>zerrendetan</strong> gehitzear zaude. Zerrenda berriak sortuko dira zerrendarik ez badago."
+      muting_html: "<strong>%{filename}</strong> fitxategiak dituen <strong>%{total_items} kontu</strong>ak <strong>mututzear</strong> zaude."
     preface: Beste zerbitzari bateko datuak inportatu ditzakezu, esaterako jarraitzen duzun edo blokeatu duzun jendearen zerrenda.
     recent_imports: Azken inportazioak
     states:
@@ -1529,6 +1547,7 @@ eu:
   scheduled_statuses:
     over_daily_limit: 'Egun horretarako programatutako bidalketa kopuruaren muga gainditu duzu: %{limit}'
     over_total_limit: 'Programatutako bidalketa kopuruaren muga gainditu duzu: %{limit}'
+    too_soon: Programatutako data etorkizunean egon behar du
   self_destruct:
     lead_html: Zoritxarrez, <strong>%{domain}</strong> betirako itxiko da. Kontu bat baduzu bertan, ezin izango duzu erabiltzen jarraitu, baina, oraindik zure datuen babeskopia bat eska dezakezu.
     title: Zerbitzari hau ixtear dago
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index f1c74829c9..a498a7f305 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -187,7 +187,6 @@ fa:
         create_domain_block: ایجاد انسداد دامنه
         create_email_domain_block: ایجاد انسداد دامنهٔ رایانامه
         create_ip_block: ایجاد قاعدهٔ آی‌پی
-        create_relay: ایجاد رله
         create_unavailable_domain: ایجاد دامنهٔ ناموجود
         create_user_role: ایجاد نقش
         demote_user: تنزل کاربر
@@ -199,22 +198,18 @@ fa:
         destroy_email_domain_block: حذف انسداد دامنهٔ رایانامه
         destroy_instance: پاکسازی دامنه
         destroy_ip_block: حذف قاعدهٔ آی‌پی
-        destroy_relay: حذف رله
         destroy_status: حذف وضعیت
         destroy_unavailable_domain: حذف دامنهٔ ناموجود
         destroy_user_role: نابودی نقش
         disable_2fa_user: از کار انداختن ورود دومرحله‌ای
         disable_custom_emoji: از کار انداختن اموجی سفارشی
-        disable_relay: غیرفعال‌سازی رله
         disable_sign_in_token_auth_user: از کار انداختن تأیید هویت ژتون رایانامه‌ای برای کاربر
         disable_user: از کار انداختن کاربر
         enable_custom_emoji: به کار انداختن اموجی سفارشی
-        enable_relay: فعال‌سازی رله
         enable_sign_in_token_auth_user: به کار انداختن تأیید هویت ژتون رایانامه‌ای برای کاربر
         enable_user: به کار انداختن کاربر
         memorialize_account: یادسپاری حساب
         promote_user: ترفیع کاربر
-        publish_terms_of_service: انتشار شرایط خدمات
         reject_appeal: رد کردن درخواست تجدیدنظر
         reject_user: رد کاربر
         remove_avatar_user: برداشتن تصویر نمایه
@@ -252,7 +247,6 @@ fa:
         create_domain_block_html: "%{name} دامنهٔ %{target} را مسدود کرد"
         create_email_domain_block_html: "%{name} دامنهٔ رایانامهٔ %{target} را مسدود کرد"
         create_ip_block_html: "%{name} برای آی‌پی %{target} قانونی ایجاد کرد"
-        create_relay_html: "%{name} یک رله %{target} ایجاد کرد"
         create_unavailable_domain_html: "%{name} تحویل محتوا به دامنه %{target} را متوقف کرد"
         create_user_role_html: "%{name} نقش %{target} را ایجاد کرد"
         demote_user_html: "%{name} کاربر %{target} را تنزل داد"
@@ -264,22 +258,18 @@ fa:
         destroy_email_domain_block_html: "%{name} انسداد دامنهٔ رایانامهٔ %{target} را برداشت"
         destroy_instance_html: "%{name} دامنه %{target} را پاکسازی کرد"
         destroy_ip_block_html: "%{name} قاعدهٔ آی‌پی %{target} را حذف کرد"
-        destroy_relay_html: "%{name} رله %{target} را حذف کرد"
         destroy_status_html: "%{name} وضعیت %{target} را برداشت"
         destroy_unavailable_domain_html: "%{name} تحویل محتوا به دامنه %{target} را از سر گرفت"
         destroy_user_role_html: "%{name} نقش %{target} را حذف کرد"
         disable_2fa_user_html: "%{name} ضرورت ورود دو مرحله‌ای را برای کاربر %{target} غیر فعال کرد"
         disable_custom_emoji_html: "%{name} شکلک %{target} را غیرفعال کرد"
-        disable_relay_html: "%{name} رله %{target} را غیرفعال کرد"
         disable_sign_in_token_auth_user_html: "%{name}، احراز هویت با توکن رایانامه را برای %{target} غیرفعال کرد"
         disable_user_html: "%{name} ورود را برای کاربر %{target} غیرفعال کرد"
         enable_custom_emoji_html: "%{name} شکلک %{target} را فعال کرد"
-        enable_relay_html: "%{name} رله %{target} را فعال کرد"
         enable_sign_in_token_auth_user_html: "%{name}، احراز هویت با توکن رایانامه را برای %{target} فعال کرد"
         enable_user_html: "%{name} ورود را برای کاربر %{target} فعال کرد"
         memorialize_account_html: "%{name} حساب %{target} را تبدیل به صفحهٔ یادمان کرد"
         promote_user_html: "%{name} کاربر %{target} را ترفیع داد"
-        publish_terms_of_service_html: "%{name} به‌روزرسانی‌های شرایط خدمات را منتشر کرد"
         reject_appeal_html: "%{name} درخواست تجدیدنظر تصمیم مدیر را از %{target} رد کرد"
         reject_user_html: "%{name} ثبت نام %{target} را رد کرد"
         remove_avatar_user_html: "%{name} تصویر نمایهٔ %{target} را حذف کرد"
@@ -291,7 +281,7 @@ fa:
         silence_account_html: "%{name} حساب %{target} را محدود کرد"
         suspend_account_html: "%{name} حساب %{target} را تعلیق کرد"
         unassigned_report_html: "%{name} گزارش %{target} را از حالت محول شده خارج کرد"
-        unblock_email_account_html: "%{name} مسدودی نشانی رایانامهٔ %{target} را رفع کرد"
+        unblock_email_account_html: "%{name} نشانی رایانامهٔ %{target} را رفع مسدودیت کرد"
         unsensitive_account_html: "%{name} علامت حساس رسانهٔ %{target} را برداشت"
         unsilence_account_html: "%{name} محدودیت حساب %{target} را برداشت"
         unsuspend_account_html: "%{name} حساب %{target} را از تعلیق خارج کرد"
@@ -309,7 +299,6 @@ fa:
       title: سیاههٔ بازرسی
       unavailable_instance: "(نام دامنه ناموجود)"
     announcements:
-      back: بازگشت به اعلامیه‌ها
       destroyed_msg: اعلامیه با موفقیت حذف شد!
       edit:
         title: ویرایش اعلامیه
@@ -318,9 +307,6 @@ fa:
       new:
         create: ساختن اعلامیه
         title: اعلامیهٔ تازه
-      preview:
-        explanation_html: 'رایانامه برای <strong>%{display_count} کاربر</strong> فرستاده خواهد شد. متن زیر در رایانامه قرار خواهد گرفت:'
-        title: پیش‌نمایش آگاهی اعلامیه
       publish: انتشار
       published_msg: اعلامیه با موفقیت منتشر شد!
       scheduled_for: زمان‌بسته برای %{time}
@@ -461,7 +447,7 @@ fa:
         title: مسدودسازی دامنهٔ رایانامهٔ جدید
       no_email_domain_block_selected: هیچ انسداد دامنهٔ رایانامه‌ای تغییر نکرد زیرا هیچ‌کدامشان انتخاب نشده بودند
       not_permitted: مجاز نیست
-      resolved_dns_records_hint_html: نام دامنه به دامنه‌های MX زیر منتقل می شود که در نهایت مسئولیت پذیرش رایانامه را بر عهده دارند. انسداد دامنهٔ MX، ثبت‌نام از هر نشانی رایانامه‌ای را که از همان دامنهٔ MX استفاده می‌کند را مسدود می‌کند؛ حتا اگر نام دامنهٔ نمایان متفاوت باشد. <strong>مراقب باشید ارائه‌دهندگان رایانامهٔ بزرگ را مسدود نکنید.</strong>
+      resolved_dns_records_hint_html: نام دامنه به دامنه های MX زیر منتقل می شود که در نهایت مسئولیت پذیرش ایمیل را بر عهده دارند. مسدود کردن دامنه MX، ثبت نام از هر آدرس ایمیلی را که از همان دامنه MX استفاده می کند، مسدود می کند، حتی اگر نام دامنه قابل مشاهده متفاوت باشد. <strong>مراقب باشید ارائه دهندگان ایمیل اصلی را مسدود نکنید.</strong>
       resolved_through_html: از طریق %{domain} حل شد
       title: دامنه‌های رایانامهٔ مسدود شده
     export_domain_allows:
@@ -479,22 +465,6 @@ fa:
       new:
         title: درون‌ریزی انسدادهای دامنه
       no_file: هیچ پرونده‌ای گزیده نشده
-    fasp:
-      debug:
-        callbacks:
-          delete: حذف
-      providers:
-        active: فعال
-        delete: حذف
-        finish_registration: تکمیل ثبت‌نام
-        name: نام
-        providers: ارائه دهندگان
-        registrations:
-          confirm: تایید
-          reject: رد کردن
-        save: ذخیره
-        sign_in: ورود
-        status: وضعیت
     follow_recommendations:
       description_html: "<strong>پیشنهادات پیگیری به کاربران جدید کک می‌کند تا سریع‌تر محتوای جالب را پیدا کنند</strong>. زمانی که کاربری هنوز به اندازه کافی با دیگران تعامل نداشته است تا پیشنهادات پیگیری شخصی‌سازی‌شده دریافت کند، این حساب‌ها را به جای آن فهرست مشاهده خواهد کرد. این حساب‌ها به صورت روزانه و در ترکیب با بیشتری تعاملات و بالاترین دنبال‌کنندگان محلی برای یک زبان مشخص بازمحاسبه می‌شوند."
       language: برای زبان
@@ -633,7 +603,7 @@ fa:
         resolve_description_html: هیچ کنشی علیه حساب گزارش شده انجام نخواهد شد. هیچ شکایتی ضبط نشده و گزارش بسته خواهد شد.
         silence_description_html: این حساب فقط برای کسانی قابل مشاهده خواهد بود که قبلاً آن را دنبال می کنند یا به صورت دستی آن را جستجو می کنند و دسترسی آن را به شدت محدود می کند. همیشه می توان برگرداند. همه گزارش‌های مربوط به این حساب را می‌بندد.
         suspend_description_html: اکانت و تمامی محتویات آن غیرقابل دسترسی و در نهایت حذف خواهد شد و تعامل با آن غیر ممکن خواهد بود. قابل برگشت در عرض 30 روز همه گزارش‌های مربوط به این حساب را می‌بندد.
-      actions_description_html: تصمیم گیری کنش اقدامی برای حل این گزارش. در صورت انجام کنش تنبیهی روی حساب گزارش شده، غیر از زمان یکه دستهٔ <strong>هرزنامه</strong> گزیده باشد، برایش آگاهی رایانامه‌ای فرستاده خواهد شد.
+      actions_description_html: تصمیم بگیرید که چه اقدامی برای حل این گزارش انجام دهید. اگر اقدام تنبیهی علیه حساب گزارش شده انجام دهید، یک اعلان ایمیل برای آنها ارسال می شود، به جز زمانی که دسته <strong>هرزنامه</strong> انتخاب شده باشد.
       actions_description_remote_html: تصمیم بگیرید که چه اقدامی برای حل این گزارش انجام دهید. این فقط بر نحوه ارتباط سرور <strong>شما</strong> با این حساب راه دور و مدیریت محتوای آن تأثیر می گذارد.
       actions_no_posts: این گزارش هیچ پست مرتبطی برای حذف ندارد
       add_to_report: افزودن بیش‌تر به گزارش
@@ -699,7 +669,7 @@ fa:
         delete_data_html: نمایه و محتویات <strong>@%{acct}</strong> را 30 روز بعد حذف کنید، مگر اینکه در این مدت معلق نشوند
         preview_preamble_html: "<strong>@%{acct}</strong> اخطاری با محتوای زیر دریافت خواهد کرد:"
         record_strike_html: ضبط شکایتی علیه <strong>‪@%{acct}‬</strong> برای کمک به تصمیم‌گیری برای قانون‌شکنی‌های آیندهٔ این حساب
-        send_email_html: فرستادن رایانامهٔ هشدار به ‪<strong>@%{acct}</strong>‬
+        send_email_html: یک ایمیل هشدار به <strong>@%{acct}</strong> ارسال کنید
         warning_placeholder: استدلال اضافی اختیاری برای اقدام تعدیل.
       target_origin: خاستگاه حساب گزارش‌شده
       title: گزارش‌ها
@@ -739,7 +709,7 @@ fa:
         manage_appeals: مدیریت درخواست‌های بازنگری
         manage_appeals_description: به کاربران امکان می‌دهد درخواست‌های تجدیدنظر علیه اقدامات تعدیل را بررسی کنند
         manage_blocks: مدیریت مسدودی‌ها
-        manage_blocks_description: می‌گذارد کاربران فراهم‌کنندگان رایانامه و نشانی‌های آی‌پی را مسدود کنند
+        manage_blocks_description: به کاربران اجازه می دهد تا ارائه دهندگان ایمیل و آدرس های آی پی را مسدود کنند
         manage_custom_emojis: مدیریت ایموجی‌های سفارشی
         manage_custom_emojis_description: به کاربران اجازه می دهد تا ایموجی های سفارشی را روی سرور مدیریت کنند
         manage_federation: مدیریت خودگردانی
@@ -755,9 +725,9 @@ fa:
         manage_settings: مدیریت تنظیمات
         manage_settings_description: اجازه به کاربران برای تغییر تنظیمات پایگاه
         manage_taxonomies: مدیریت طیقه‌بندی‌ها
-        manage_taxonomies_description: می‌گذارد کاربران محتوای داغ را بررسی و تنظیمات برچسب را به‌روز کنند
+        manage_taxonomies_description: به کاربران امکان می‌دهد محتوای پرطرفدار را بررسی کنند و تنظیمات هشتگ را به‌روزرسانی کنند
         manage_user_access: مدیریت دسترسی کاربران
-        manage_user_access_description: می‌گذارد کاربران هویت‌سنجی دو مرحله‌ای دیگر کاربران را از کار انداخته، نشانی رایانامه‌شان را تغییر داده و گذرواژه‌شان را بازنشانی کنند
+        manage_user_access_description: به کاربران اجازه می دهد تا احراز هویت دو مرحله ای سایر کاربران را غیرفعال کنند، آدرس ایمیل آنها را تغییر دهند و رمز عبور خود را بازنشانی کنند
         manage_users: مدیریت کاربران
         manage_users_description: به کاربران اجازه می دهد تا جزئیات سایر کاربران را مشاهده کنند و اقدامات تعدیل را علیه آنها انجام دهند
         manage_webhooks: مدیریت قلّاب‌های وب
@@ -832,7 +802,7 @@ fa:
       destroyed_msg: بارگذاری پایگاه با موفقیت حذف شد!
     software_updates:
       critical_update: بحرانی — لطفاً به سرعت به‌روز کنید
-      description: توصیه می‌شود نصب ماستودونتان را به‌روز نگه داشته تا از جدیدترین اصلاحات و ویژگی‌ها بهره‌مند شوید. همچنین گاهی به‌روز رسانی به‌موقع ماستودون برای پیشگیری از مشکلات امنیتی ضروریست. برای همین ماستودون هر ۳۰ دقیقه به‌روز رسانی‌ها را بررسی کرده و بنا به ترجیحات آگاهی رایانامه‌ایتان آگاهتان خواهد کرد.
+      description: توصیه می شود نصب ماستودون خود را به روز نگه دارید تا از آخرین اصلاحات و ویژگی ها بهره مند شوید. علاوه بر این، گاهی اوقات برای جلوگیری از مشکلات امنیتی، به روز رسانی ماستودون به موقع ضروری است. به این دلایل، ماستودون هر 30 دقیقه یکبار به‌روزرسانی‌ها را بررسی می‌کند و طبق اولویت‌های اعلان ایمیل شما را مطلع می‌کند.
       documentation_link: بیش‌تر بیاموزید
       release_notes: یادداشت‌های انتشار
       title: به‌روز رسانی‌های موجود
@@ -848,10 +818,8 @@ fa:
       back_to_account: بازگشت به صفحهٔ حساب
       back_to_report: بازگشت به صفحهٔ گزارش
       batch:
-        add_to_report: 'افزودن به گزارش #%{id}'
         remove_from_report: برداشتن از گزارش
         report: گزارش
-      contents: مطالب
       deleted: پاک‌شده
       favourites: برگزیده‌ها
       history: تاریخچهٔ نگارش
@@ -860,17 +828,13 @@ fa:
       media:
         title: رسانه
       metadata: فراداده
-      no_history: این پست ویرایش نشده است
       no_status_selected: هیچ فرسته‌ای تغییری نکرد زیرا هیچ‌کدام از آن‌ها انتخاب نشده بودند
       open: گشودن فرسته
       original_status: فرستهٔ اصلی
       reblogs: تقویت‌ها
-      replied_to_html: به %{acct_link} پاسخ داد
       status_changed: فرسته تغییر کرد
-      status_title: پست توسط @%{name}
-      title: پست‌های حساب - @%{name}
+      title: نوشته‌های حساب
       trending: پرطرفدار
-      view_publicly: مشاهده عمومی
       visibility: نمایانی
       with_media: دارای عکس یا ویدیو
     strikes:
@@ -947,36 +911,6 @@ fa:
       search: جست‌وجو
       title: برچسب‌ها
       updated_msg: تنظیمات برچسب‌ها با موفقیت به‌روز شد
-    terms_of_service:
-      back: بازگشت به شرایط خدمات
-      changelog: چه چیزی تغییر کرده است
-      create: از خودت استفاده کن
-      current: فعلی
-      draft: پیش نویس
-      generate: از قالب استفاده کنید
-      generates:
-        action: ایجاد کنید
-        chance_to_review_html: "<strong>شرایط خدمات ایجاد شده به صورت خودکار منتشر نخواهد شد.</strong> شما فرصتی خواهید داشت که نتایج را بررسی کنید. لطفا مشخصات لازم را برای ادامه وارد کنید."
-        explanation_html: الگوی شرایط خدمات ارائه شده فقط برای اهداف اطلاعاتی است و نباید به عنوان مشاوره حقوقی در مورد هر موضوعی تلقی شود. لطفاً در مورد وضعیت خود و سؤالات حقوقی خاصی که دارید با مشاور حقوقی خود مشورت کنید.
-        title: راه اندازی شرایط خدمات
-      going_live_on_html: زنده. اعمال شده از %{date}
-      history: تاریخچه
-      live: زنده
-      no_history: هنوز هیچ تغییری در شرایط خدمات ثبت نشده است.
-      no_terms_of_service_html: شما در حال حاضر هیچ شرایط خدماتی را پیکربندی نکرده‌اید. شرایط خدمات به منظور ارائه وضوح و محافظت از شما در برابر مسئولیت های احتمالی در اختلافات با کاربران شما است.
-      notified_on_html: کاربران در %{date} مطلع شدند
-      notify_users: به کاربران اطلاع دهید
-      preview:
-        explanation_html: 'رایانامه برای <strong>%{display_count} کاربری</strong> که پیش از %{date} ثبت‌نام کرده‌اند فرستاده خواهد شد. متن زیر در رایانامه قرار خواهد گرفت:'
-        send_preview: ارسال پیش نمایش به %{email}
-        send_to_all:
-          one: "%{display_count} ایمیل ارسال کنید"
-          other: فرستادن %{display_count} رایانامه
-        title: پیش نمایش اعلان شرایط خدمات
-      publish: انتشار دهید
-      published_on_html: منتشر شده در %{date}
-      save_draft: ذخیره پیش نویس
-      title: شرایط خدمات
     title: مدیریت
     trends:
       allow: اجازه
@@ -1157,7 +1091,7 @@ fa:
       hint_html: فقط یک چیز دیگر! ما باید تأیید کنیم که شما یک انسان هستید (این برای جلوگیری از هرزنامه است!). کپچا زیر را حل کنید و روی "ادامه" کلیک کنید.
       title: بررسی های امنیتی
     confirmations:
-      awaiting_review: نشانی رایانامه‌تان تأیید شد! عوامل %{domain} دارند ثبت‌نامتان را بررسی می‌کنند. در صورت تأیید حسابتان رایانامه‌ای خواهید گرفت!
+      awaiting_review: آدرس ایمیل شما تایید شد! کارکنان %{domain} اکنون در حال بررسی ثبت نام شما هستند. اگر حساب شما را تایید کنند یک ایمیل دریافت خواهید کرد!
       awaiting_review_title: ثبت‌نامتان دارد بررسی می‌شود
       clicking_this_link: زدن این پیوند
       login_link: ورود
@@ -1165,7 +1099,7 @@ fa:
       redirect_to_app_html: باید به برنامه <strong>%{app_name}</strong> هدایت می‌شوید. اگر این اتفاق نیفتاد، %{clicking_this_link} را امتحان کنید یا به صورت دستی به برنامه برگردید.
       registration_complete: ثبت نام شما در %{domain} اکنون کامل شده است!
       welcome_title: خوش آمدید، %{name}!
-      wrong_email_hint: اگر نشانی رایانامه درست نیست می‌توانید در تنظیمات حساب تغییرش دهید.
+      wrong_email_hint: اگر آن آدرس ایمیل درست نیست، می‌توانید آن را در تنظیمات حساب تغییر دهید.
     delete_account: پاک‌کردن حساب
     delete_account_html: اگر می‌خواهید حساب خود را پاک کنید، از <a href="%{path}">این‌جا</a> پیش بروید. از شما درخواست تأیید خواهد شد.
     description:
@@ -1184,6 +1118,7 @@ fa:
     migrate_account: نقل مکان به یک حساب دیگر
     migrate_account_html: اگر می‌خواهید این حساب را به حساب دیگری منتقل کنید، <a href="%{path}">این‌جا را کلیک کنید</a>.
     or_log_in_with: یا ورود به وسیلهٔ
+    privacy_policy_agreement_html: <a href="%{privacy_policy_path}" target="_blank">سیاست محرمانگی</a> را خوانده و پذیرفته‌ام
     progress:
       confirm: تأیید رایانامه
       details: جزئیات شما
@@ -1207,17 +1142,17 @@ fa:
     security: امنیت
     set_new_password: تعین گذرواژه جدید
     setup:
-      email_below_hint_html: بررسی شاخهٔ هرزنامه یا درخواست رایانامه‌ای دیگر. در صورت اشتباه بودن نشانی رایانامه می‌توانید تصحیحش کنید.
-      email_settings_hint_html: برای شروع استفاده از ماستودون روی پیوندی که به %{email} فرستادیم کلیک کنید. همین جا منتظر می مانیم.
+      email_below_hint_html: پوشه هرزنامه خود را بررسی کنید یا یک پوشه دیگر درخواست کنید. در صورت اشتباه می توانید آدرس ایمیل خود را تصحیح کنید.
+      email_settings_hint_html: برای تأیید %{email}، روی پیوندی که برای شما ارسال کردیم ضربه بزنید. همین جا منتظر می‌مانیم.
       link_not_received: پیوندی نگرفتید؟
-      new_confirmation_instructions_sent: تا چند دقیقهٔ دیگر رایانامه‌ای جدید با پیوند تأییدیه خواهید گرفت!
+      new_confirmation_instructions_sent: تا چند دقیقه دیگر یک ایمیل جدید با لینک تایید دریافت خواهید کرد!
       title: صندوق ورودیتان را بررسی کنید
     sign_in:
       preamble_html: با اطلاعات کاربری <strong>%{domain}</strong> خود وارد شوید. اگر حساب شما روی سرور دیگری میزبانی شود، نمی توانید در اینجا وارد شوید.
       title: ورود به %{domain}
     sign_up:
       manual_review: ثبت‌نام‌ها در %{domain} از طریق بازبینی دستی توسط گردانندگان ما انجام می‌شود. برای کمک به ما در پردازش ثبت نام خود، کمی در مورد خودتان و اینکه چرا می خواهید یک حساب در %{domain} داشته باشید، بنویسید.
-      preamble: با داشتن یک حساب کاربری در این سرور ماستودون، می‌توانید هر شخص دیگری را در فدیورس دنبال کنید، صرف نظر از اینکه حساب او در کجا میزبانی می‌شود.
+      preamble: با داشتن یک حساب کاربری در این سرور ماستودون، می‌توانید هر شخص دیگری را در شبکه دنبال کنید، صرف نظر از اینکه حساب او در کجا میزبانی می‌شود.
       title: بیایید روی %{domain} برپایتان کنیم.
     status:
       account_status: وضعیت حساب
@@ -1229,8 +1164,6 @@ fa:
       view_strikes: دیدن شکایت‌های گذشته از حسابتان
     too_fast: فرم با سرعت بسیار زیادی فرستاده شد، دوباره تلاش کنید.
     use_security_key: استفاده از کلید امنیتی
-    user_agreement_html: من خوانده ام و موافقم <a href="%{terms_of_service_path}" target="_blank">شرایط خدمات</a> و <a href="%{privacy_policy_path}" target="_blank">سیاست حفظ حریم خصوصی</a>
-    user_privacy_agreement_html: من <a href="%{privacy_policy_path}" target="_blank">خط‌مشی رازداری</a> را خوانده‌ام و با آن موافقم
   author_attribution:
     example_title: متن نمونه
     hint_html: آیا در خارج از ماستودون اخبار یا مقالات وبلاگ می نویسید؟ نحوه دریافت اعتبار زمانی که آنها در ماستودون به اشتراک گذاشته می شوند را کنترل کنید.
@@ -1436,43 +1369,19 @@ fa:
       overwrite: بازنویسی
       overwrite_long: داده‌های فعلی را پاک کنید و داده‌های تازه‌ای بیفزایید
     overwrite_preambles:
-      blocking_html:
-        one: می‌خواهید <strong>فهرست مسدود</strong> خود را با حداکثر <strong>%{count} حساب</strong> از <strong>%{filename}</strong> جایگزین کنید.
-        other: شما می‌خواهید <strong>فهرست مسدود خود را</strong> با حداکثر <strong>%{count} حساب</strong> از <strong>%{filename}</strong> جایگزین کنید.
-      bookmarks_html:
-        one: شما می‌خواهید <strong>نشانک‌های خود را</strong> با حداکثر <strong>%{count} پست</strong> از <strong>%{filename}</strong> جایگزین کنید.
-        other: شما می‌خواهید <strong>نشانک‌های خود را</strong> با حداکثر <strong>%{count} پست ها</strong> از <strong>%{filename}</strong> جایگزین کنید.
-      domain_blocking_html:
-        one: شما می‌خواهید <strong>لیست مسدودسازی دامنه خود را</strong> با حداکثر <strong>%{count} دامنه</strong> از <strong>%{filename}</strong> جایگزین کنید.
-        other: شما می خواهید <strong>لیست مسدودسازی دامنه خود را</strong> با حداکثر <strong>%{count} دامنه</strong> از <strong>%{filename}</strong> جایگزین کنید.
-      following_html:
-        one: شما در شرف <strong>دنبال کردن</strong> حداکثر <strong>%{count} حساب</strong> از <strong>%{filename}</strong> هستید و <strong>دنبال کردن افراد دیگری را متوقف می‌کنید</strong>.
-        other: شما در شرف <strong>دنبال کردن</strong> حداکثر <strong>%{count} حساب</strong> از <strong>%{filename}</strong> هستید و <strong>دنبال کردن افراد دیگری را متوقف می‌کنید</strong>.
-      lists_html:
-        one: شما می خواهید <strong>لیست های خود را</strong> با محتوای <strong>%{filename}</strong> جایگزین کنید. حداکثر <strong>%{count} حساب</strong> به لیست‌های جدید اضافه می‌شود.
-        other: شما می خواهید <strong>لیست های خود را</strong> با محتوای <strong>%{filename}</strong> جایگزین کنید. حداکثر <strong>%{count} حساب</strong> به لیست‌های جدید اضافه می‌شود.
-      muting_html:
-        one: شما می‌خواهید <strong>لیست حساب‌های بی‌صدا را جایگزین کنید</strong> با حداکثر <strong>%{count} حساب</strong> از <strong>%{filename}</strong>.
-        other: شما می‌خواهید <strong>لیست حساب‌های بی‌صدا را جایگزین کنید</strong> با حداکثر <strong>%{count} حساب</strong> از <strong>%{filename}</strong>.
+      blocking_html: شما می‌خواهید <strong>فهرست مسدود خود را</strong> با حداکثر <strong>%{total_items} حساب</strong> از <strong>%{filename}</strong> جایگزین کنید.
+      bookmarks_html: شما می‌خواهید <strong>نشانک‌های خود را</strong> با حداکثر <strong>%{total_items} پست</strong> از <strong>%{filename}</strong> جایگزین کنید.
+      domain_blocking_html: شما می‌خواهید <strong>لیست مسدودی دامنه خود را</strong> با حداکثر <strong>%{total_items} دامنه</strong> از <strong>%{filename}</strong> جایگزین کنید.
+      following_html: شما در شرف <strong>دنبال کردن</strong> حداکثر <strong>%{total_items} حساب</strong> از <strong>%{filename}</strong> هستید و <strong>دنبال کردن افراد دیگری را متوقف می‌کنید</strong>.
+      lists_html: شما می خواهید <strong>لیست های خود را</strong> با محتوای <strong>%{filename}</strong> جایگزین کنید. حداکثر <strong>%{total_items} حساب</strong> به لیست‌های جدید اضافه می‌شود.
+      muting_html: شما می‌خواهید <strong>لیست حساب‌های بی‌صدا را جایگزین کنید</strong> با حداکثر <strong>%{total_items} حساب</strong> از <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: شما در شرف <strong>مسدود کردن</strong> حداکثر <strong>%{count} حساب</strong> از <strong>%{filename}</strong> هستید.
-        other: شما در شرف <strong>مسدود کردن</strong> حداکثر <strong>%{count} حساب</strong> از <strong>%{filename}</strong> هستید.
-      bookmarks_html:
-        one: شما می‌خواهید تا <strong>%{count} پست</strong> را از <strong>%{filename}</strong> به <strong>نشانک‌ها</strong> خود اضافه کنید.
-        other: شما می‌خواهید تا <strong>%{count} پست</strong> را از <strong>%{filename}</strong> به <strong>نشانک‌ها</strong> خود اضافه کنید.
-      domain_blocking_html:
-        one: شما در شرف <strong>مسدود کردن</strong> تا <strong>%{count} دامنه</strong> از <strong>%{filename}</strong> هستید.
-        other: شما در شرف <strong>مسدود کردن</strong> تا <strong>%{count} دامنه</strong> از <strong>%{filename}</strong> هستید.
-      following_html:
-        one: شما در شرف <strong>دنبال کردن</strong> حداکثر <strong>%{count} حساب</strong> از <strong>%{filename}</strong> هستید.
-        other: شما در شرف <strong>دنبال کردن</strong> حداکثر <strong>%{count} حساب</strong> از <strong>%{filename}</strong> هستید.
-      lists_html:
-        one: شما در حال افزودن <strong>%{count} حساب</strong> از <strong>%{filename}</strong> به <strong>لیست‌های</strong> خود هستید. اگر لیستی برای افزودن وجود نداشته باشد، لیست های جدیدی ایجاد می شود.
-        other: شما می‌خواهید تا <strong>%{count} حساب</strong> از <strong>%{filename}</strong> را به <strong>فهرست‌های</strong> خود اضافه کنید. اگر لیستی برای افزودن وجود نداشته باشد، لیست های جدیدی ایجاد می شود.
-      muting_html:
-        one: شما در حال <strong>بی صدا کردن</strong> تا <strong>%{count} حساب</strong> از <strong>%{filename}</strong> هستید.
-        other: شما در حال <strong>بی صدا کردن</strong> تا <strong>%{count} حساب</strong> از <strong>%{filename}</strong> هستید.
+      blocking_html: شما در شرف <strong>مسدود کردن</strong> حداکثر <strong>%{total_items} حساب</strong> از <strong>%{filename}</strong> هستید.
+      bookmarks_html: شما می‌خواهید تا <strong>%{total_items} پست</strong> از <strong>%{filename}</strong> را به <strong>نشانک‌ها</strong> خود اضافه کنید.
+      domain_blocking_html: شما در شرف <strong>مسدود کردن</strong> تا <strong>%{total_items} دامنه</strong> از <strong>%{filename}</strong> هستید.
+      following_html: شما در شرف <strong>دنبال کردن</strong> حداکثر <strong>%{total_items} حساب</strong> از <strong>%{filename}</strong> هستید.
+      lists_html: شما می‌خواهید تا <strong>%{total_items} حساب</strong> از <strong>%{filename}</strong> را به <strong>فهرست‌های</strong> خود اضافه کنید. اگر لیستی برای افزودن وجود نداشته باشد، لیست های جدیدی ایجاد می شود.
+      muting_html: شما در شرف <strong>بی صدا کردن</strong> تا <strong>%{total_items} حساب</strong> از <strong>%{filename}</strong> هستید.
     preface: می‌توانید داده‌هایی را که از کارسازی دیگر برون‌ریخته‌اید، چون سیاهه‌ای از افرادی که پی گرفته یا مسدود می‌کنید را درون‌ریزی کنید.
     recent_imports: واردشده‌های اخیر
     states:
@@ -1543,7 +1452,7 @@ fa:
     unsubscribe:
       action: بله. لغو اشتراک
       complete: لغو اشتراک شد
-      confirmation_html: مطمئنید که می‌خواهید اشتراک %{type} را از ماستودون روی %{domain} برای رایانامهٔ %{email} لغو کنید؟ همواره می‌توانید از <a href="%{settings_path}">تنظیمات آگاهی رایانامه‌ای</a> دوباره مشترک شوید.
+      confirmation_html: آیا مطمئنید که می‌خواهید از دریافت %{type} برای ماستودون در %{domain} به ایمیل خود در %{email} لغو اشتراک کنید؟ همیشه می‌توانید از <a href="%{settings_path}">تنظیمات اعلان ایمیل</a> خود مجدداً مشترک شوید.
       emails:
         notification_emails:
           favourite: رایانامه‌های آگاهی برگزیدن
@@ -1551,8 +1460,8 @@ fa:
           follow_request: رایانامه‌های درخواست پی‌گیری
           mention: رایانامه‌های آگاهی اشاره
           reblog: رایانامه‌های آگاهی تقویت
-      resubscribe_html: اگر اشتراک را اشتباهی لغو کردید می‌توانید از <a href="%{settings_path}">تنظیمات آگاهی رایانامه‌ای</a> دوباره مشترک شوید.
-      success_html: دیگر %{type} را از ماستودون روی %{domain} برای رایانامهٔ %{email} نخواهید گرفت.
+      resubscribe_html: اگر به اشتباه اشتراک را لغو کرده‌اید، می‌توانید از <a href="%{settings_path}">تنظیمات اعلان ایمیل</a> خود مجدداً اشتراک کنید.
+      success_html: دیگر %{type} را برای ماستودون در %{domain} به ایمیل خود در %{email} دریافت نخواهید کرد.
       title: لغو اشتراک
   media_attachments:
     validations:
@@ -1729,7 +1638,7 @@ fa:
   scheduled_statuses:
     over_daily_limit: شما از حد مجاز %{limit} فرسته زمان‌بندی‌شده در آن روز فراتر رفته‌اید
     over_total_limit: شما از حد مجاز %{limit} فرسته زمان‌بندی‌شده فراتر رفته‌اید
-    too_soon: تاریخ باید در آینده باشد
+    too_soon: زمان تعیین‌شده باید در آینده باشد
   self_destruct:
     lead_html: متأسفانه، <strong>%{domain}</strong> برای همیشه در حال بسته شدن است. اگر حسابی در آنجا داشتید، نمی‌توانید به استفاده از آن ادامه دهید، اما همچنان می‌توانید از داده‌های خود نسخه پشتیبان درخواست کنید.
     title: این کارساز دارد بسته می‌شود
@@ -1892,8 +1801,6 @@ fa:
       too_late: برای درخواست تجدیدنظر این شکایت بیش از حد دیر شده
   tags:
     does_not_match_previous_name: با نام پیشین مطابق نیست
-  terms_of_service:
-    title: شرایط خدمات
   themes:
     contrast: ماستودون (سایه‌روشن بالا)
     default: ماستودون (تیره)
@@ -1925,10 +1832,6 @@ fa:
     recovery_instructions_html: اگر تلفن خود را گم کردید، می‌توانید با یکی از کدهای بازیابی زیر کنترل حساب خود را به دست بگیرید. <strong>این کدها را در جای امنی نگه دارید.</strong> مثلاً آن‌ها را چاپ کنید و کنار سایر مدارک مهم خود قرار دهید.
     webauthn: کلیدهای امنیتی
   user_mailer:
-    announcement_published:
-      description: 'مدیران %{domain} اعلامیه‌ای داده‌اند:'
-      subject: اعلامیهٔ خدمت
-      title: اعلامیهٔ خدمت %{domain}
     appeal_approved:
       action: تنظیمات حساب
       explanation: درخواست تجدیدنظر اخطار علیه حساب شما در %{strike_date} که در %{appeal_date} ارسال کرده‌اید، پذیرفته شده است. حساب شما بار دیگر در وضعیت خوبی قرار دارد.
@@ -1958,15 +1861,6 @@ fa:
       further_actions_html: اگر این شما نبودید، توصیه می کنیم فورا %{action} را فعال کنید و برای ایمن نگه داشتن حساب خود، احراز هویت دو مرحله ای را فعال کنید.
       subject: حساب شما از یک آدرس آی پی جدید قابل دسترسی است
       title: یک ورود جدید
-    terms_of_service_changed:
-      agreement: با ادامه استفاده از %{domain}، با این شرایط موافقت می کنید. اگر با شرایط به‌روزرسانی شده مخالف هستید، می‌توانید در هر زمان با حذف حساب خود، قرارداد خود را با %{domain} فسخ کنید.
-      changelog: 'در یک نگاه، معنای این به‌روزرسانی برای شما چیست:'
-      description: 'این رایانانه را می‌گیرید چرا که دارین اغییراتی در شرایط خدمتتان در %{domain} می‌دهیم. این به‌روز رسانی‌ها از %{date} اعمال خواهند شد. توصیه می‌کنیم شرایط به‌روز شده را به طور کامل در این‌جا بازبینی کنید:'
-      description_html: این رایانانه را می‌گیرید چرا که دارین اغییراتی در شرایط خدمتتان در %{domain} می‌دهیم. این به‌روز رسانی‌ها از <strong>%{date}</strong> اعمال خواهند شد. توصیه می‌کنیم شرایط به‌روز شده را به طور کامل در <a href="%{path}" target="_blank">این‌جا</a> بازبینی کنید.
-      sign_off: تیم %{domain}
-      subject: به‌روزرسانی‌های شرایط خدمات ما
-      subtitle: شرایط خدمات %{domain} در حال تغییر است
-      title: به روز رسانی مهم
     warning:
       appeal: فرستادن یک درخواست تجدیدنظر
       appeal_description: اگر فکر می‌کنید این یک خطا است، می‌توانید یک درخواست تجدیدنظر به کارکنان %{instance} ارسال کنید.
@@ -2043,7 +1937,7 @@ fa:
     follow_limit_reached: شما نمی‌توانید بیش از %{limit} نفر را پی بگیرید
     go_to_sso_account_settings: به تنظیمات حساب فراهمگر هوبتتان بروید
     invalid_otp_token: کد ورود دومرحله‌ای نامعتبر است
-    otp_lost_help_html: اگر به هیچ‌کدامشان دسترسی ندارید با %{email} تماس بگیرید
+    otp_lost_help_html: اگر شما دسترسی به هیچ‌کدامشان ندارید، باید با ایمیل %{email} تماس بگیرید
     rate_limited: تلاش ّای هویت‌سنجی بیش از حد. لطفاً بعداً دوباره تلاش کنید.
     seamless_external_login: با خدمتی خارجی وارد شده‌اید، برای همین تنظیمات رایانامه و گذرواژه در دسترس نیستند.
     signed_in_as: 'واردشده به نام:'
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index 9a5bf97afe..cc9ee0bd86 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -187,7 +187,6 @@ fi:
         create_domain_block: Luo verkkotunnuksen esto
         create_email_domain_block: Luo sähköpostiverkkotunnuksen esto
         create_ip_block: Luo IP-sääntö
-        create_relay: Luo välittäjä
         create_unavailable_domain: Luo ei-saatavilla oleva verkkotunnus
         create_user_role: Luo rooli
         demote_user: Alenna käyttäjä
@@ -199,22 +198,18 @@ fi:
         destroy_email_domain_block: Poista sähköpostiverkkotunnuksen esto
         destroy_instance: Tyhjennä verkkotunnus
         destroy_ip_block: Poista IP-sääntö
-        destroy_relay: Poista välittäjä
         destroy_status: Poista julkaisu
         destroy_unavailable_domain: Poista ei-saatavilla oleva verkkotunnus
         destroy_user_role: Hävitä rooli
         disable_2fa_user: Poista kaksivaiheinen todennus käytöstä
         disable_custom_emoji: Poista mukautettu emoji käytöstä
-        disable_relay: Poista välittäjä käytöstä
         disable_sign_in_token_auth_user: Poista sähköpostitunnuksella todennus käytöstä käyttäjältä
         disable_user: Poista tili käytöstä
         enable_custom_emoji: Ota mukautettu emoji käyttöön
-        enable_relay: Ota välittäjä käyttöön
         enable_sign_in_token_auth_user: Ota sähköpostitunnuksella todennus käyttöön käyttäjälle
         enable_user: Ota tili käyttöön
         memorialize_account: Muuta muistotiliksi
         promote_user: Ylennä käyttäjä
-        publish_terms_of_service: Julkaise käyttöehdot
         reject_appeal: Hylkää valitus
         reject_user: Hylkää käyttäjä
         remove_avatar_user: Poista profiilikuva
@@ -252,7 +247,6 @@ fi:
         create_domain_block_html: "%{name} esti verkkotunnuksen %{target}"
         create_email_domain_block_html: "%{name} esti sähköpostiverkkotunnuksen %{target}"
         create_ip_block_html: "%{name} loi säännön IP-osoitteelle %{target}"
-        create_relay_html: "%{name} loi välittäjän %{target}"
         create_unavailable_domain_html: "%{name} pysäytti toimituksen verkkotunnukseen %{target}"
         create_user_role_html: "%{name} loi roolin %{target}"
         demote_user_html: "%{name} alensi käyttäjän %{target}"
@@ -264,22 +258,18 @@ fi:
         destroy_email_domain_block_html: "%{name} kumosi sähköpostiverkkotunnuksen %{target} eston"
         destroy_instance_html: "%{name} tyhjensi verkkotunnuksen %{target}"
         destroy_ip_block_html: "%{name} poisti säännön IP-osoitteelta %{target}"
-        destroy_relay_html: "%{name} poisti välittäjän %{target}"
         destroy_status_html: "%{name} poisti käyttäjän %{target} julkaisun"
         destroy_unavailable_domain_html: "%{name} jatkoi toimitusta verkkotunnukseen %{target}"
         destroy_user_role_html: "%{name} poisti roolin %{target}"
         disable_2fa_user_html: "%{name} poisti käyttäjältä %{target} vaatimuksen kaksivaiheiseen todentamiseen"
         disable_custom_emoji_html: "%{name} poisti emojin %{target} käytöstä"
-        disable_relay_html: "%{name} poisti välittäjän %{target} käytöstä"
         disable_sign_in_token_auth_user_html: "%{name} poisti sähköpostitunnuksella todennuksen käytöstä käyttäjältä %{target}"
         disable_user_html: "%{name} poisti kirjautumisen käytöstä käyttäjältä %{target}"
         enable_custom_emoji_html: "%{name} otti emojin %{target} käyttöön"
-        enable_relay_html: "%{name} otti välittäjän %{target} käyttöön"
         enable_sign_in_token_auth_user_html: "%{name} otti sähköpostitunnuksella todennuksen käyttöön käyttäjälle %{target}"
         enable_user_html: "%{name} otti kirjautumisen käyttöön käyttäjälle %{target}"
         memorialize_account_html: "%{name} muutti käyttäjän %{target} tilin muistosivuksi"
         promote_user_html: "%{name} ylensi käyttäjän %{target}"
-        publish_terms_of_service_html: "%{name} julkaisi päivityksiä käyttöehtoihin"
         reject_appeal_html: "%{name} hylkäsi käyttäjän %{target} valituksen moderointipäätöksestä"
         reject_user_html: "%{name} hylkäsi käyttäjän %{target} rekisteröitymisen"
         remove_avatar_user_html: "%{name} poisti käyttäjän %{target} profiilikuvan"
@@ -309,7 +299,6 @@ fi:
       title: Tarkastusloki
       unavailable_instance: "(verkkotunnus ei saatavilla)"
     announcements:
-      back: Takaisin tiedotteisiin
       destroyed_msg: Tiedotteen poisto onnistui!
       edit:
         title: Muokkaa tiedotetta
@@ -318,10 +307,6 @@ fi:
       new:
         create: Luo tiedote
         title: Uusi tiedote
-      preview:
-        disclaimer: Koska käyttäjät eivät voi kieltäytyä niistä, sähköposti-ilmoitukset tulee rajata tärkeisiin tiedotteisiin, kuten ilmoituksiin henkilötietojen tietoturvaloukkauksista tai palvelimen sulkeutumisesta.
-        explanation_html: "<strong>%{display_count} käyttäjälle</strong> lähetetään sähköpostia. Sähköpostiviestiin sisällytetään seuraava teksti:"
-        title: Esikatsele tiedoteilmoitus
       publish: Julkaise
       published_msg: Tiedotteen julkaisu onnistui!
       scheduled_for: Ajoitettu %{time}
@@ -458,7 +443,7 @@ fi:
       domain: Verkkotunnus
       new:
         create: Lisää verkkotunnus
-        resolve: Resolvoi verkkotunnus
+        resolve: Selvitä verkkotunnus
         title: Estä uusi sähköpostiverkkotunnus
       no_email_domain_block_selected: Sähköpostiverkkotunnusten estoja ei muutettu, koska yhtäkään ei ollut valittuna
       not_permitted: Ei sallittu
@@ -480,24 +465,6 @@ fi:
       new:
         title: Tuo verkkotunnusten estoja
       no_file: Yhtäkään tiedostoa ei ole valittu
-    fasp:
-      debug:
-        callbacks:
-          created_at: Luotu
-          delete: Poista
-          ip: IP-osoite
-      providers:
-        delete: Poista
-        finish_registration: Viimeistele rekisteröinti
-        name: Nimi
-        public_key_fingerprint: Julkisen avaimen sormenjälki
-        registration_requested: Rekisteröintiä pyydetty
-        registrations:
-          confirm: Vahvista
-          reject: Hylkää
-        save: Tallenna
-        sign_in: Kirjaudu sisään
-        status: Tila
     follow_recommendations:
       description_html: "<strong>Seurantasuositukset auttavat uusia käyttäjiä löytämään nopeasti kiinnostavaa sisältöä</strong>. Kun käyttäjä ei ole ollut tarpeeksi vuorovaikutuksessa muiden kanssa, jotta hänelle olisi muodostunut henkilökohtaisia seuraamissuosituksia, suositellaan niiden sijaan näitä tilejä. Ne lasketaan päivittäin uudelleen yhdistelmästä tilejä, jotka ovat viime aikoina olleet aktiivisimmin sitoutuneita ja joilla on suurimmat paikalliset seuraajamäärät tietyllä kielellä."
       language: Kielelle
@@ -851,10 +818,8 @@ fi:
       back_to_account: Takaisin tilin sivulle
       back_to_report: Takaisin raporttisivulle
       batch:
-        add_to_report: Lisää raporttiin nro %{id}
         remove_from_report: Poista raportista
         report: Raportoi
-      contents: Sisältö
       deleted: Poistettu
       favourites: Suosikit
       history: Versiohistoria
@@ -863,17 +828,13 @@ fi:
       media:
         title: Media
       metadata: Metadata
-      no_history: Tätä julkaisua ei ole muokattu
       no_status_selected: Julkaisuja ei muutettu, koska yhtään ei ollut valittuna
       open: Avaa julkaisu
       original_status: Alkuperäinen julkaisu
       reblogs: Edelleen jako
-      replied_to_html: Vastaus käyttäjälle %{acct_link}
       status_changed: Julkaisua muutettu
-      status_title: Julkaisu käyttäjältä @%{name}
-      title: Tilin julkaisut - @%{name}
+      title: Tilin julkaisut
       trending: Suosituttua
-      view_publicly: Näytä julkisesti
       visibility: Näkyvyys
       with_media: Sisältää mediaa
     strikes:
@@ -950,36 +911,6 @@ fi:
       search: Hae
       title: Aihetunnisteet
       updated_msg: Aihetunnisteiden asetusten päivitys onnistui
-    terms_of_service:
-      back: Takaisin käyttöehtoihin
-      changelog: Mikä on muuttunut
-      create: Käytä omiasi
-      current: Voimassa olevat
-      draft: Luonnos
-      generate: Käytä mallia
-      generates:
-        action: Luo
-        chance_to_review_html: "<strong>Luotuja käyttöehtoja ei julkaista automaattisesti.</strong> Sinulla on mahdollisuus tarkistaa lopputulos. Jatka täyttämällä tarvittavat tiedot."
-        explanation_html: Tarjottu käyttöehtomalli on tarkoitettu vain tiedoksi, eikä sitä pidä tulkita oikeudellisena neuvontana missään yhteydessä. Käänny oman oikeusavustajasi puoleen tilanteessasi ja erityisissä oikeudellisissa kysymyksissäsi.
-        title: Käyttöehtojen määritys
-      going_live_on_html: Voimassa %{date} alkaen
-      history: Historia
-      live: Julki
-      no_history: Käyttöehtoihin ei ole vielä tehty muutoksia.
-      no_terms_of_service_html: Sinulla ei ole tällä hetkellä määritettyjä käyttöehtoja. Käyttöehtojen tarkoituksena on antaa selvyyttä ja suojata sinua mahdollisilta vastuilta riitatilanteissa käyttäjiesi kanssa.
-      notified_on_html: Ilmoitettu käyttäjille %{date}
-      notify_users: Ilmoita käyttäjille
-      preview:
-        explanation_html: 'Sähköpostia lähetetään <strong>%{display_count} käyttäjälle</strong>, jotka ovat rekisteröityneet ennen %{date}. Sähköpostiviestissä on seuraava teksti:'
-        send_preview: Lähetä esikatselu osoitteeseen %{email}
-        send_to_all:
-          one: Lähetä %{display_count} sähköpostiviesti
-          other: Lähetä %{display_count} sähköpostiviestiä
-        title: Esikatsele käyttöehtojen ilmoitus
-      publish: Julkaise
-      published_on_html: Julkaistu %{date}
-      save_draft: Tallenna luonnos
-      title: Käyttöehdot
     title: Ylläpito
     trends:
       allow: Salli
@@ -1187,6 +1118,7 @@ fi:
     migrate_account: Muuta toiseen tiliin
     migrate_account_html: Jos haluat ohjata tämän tilin toiseen, voit <a href="%{path}">asettaa toisen tilin tästä</a>.
     or_log_in_with: Tai käytä kirjautumiseen
+    privacy_policy_agreement_html: Olen lukenut ja hyväksyn <a href="%{privacy_policy_path}" target="_blank">tietosuojakäytännön</a>
     progress:
       confirm: Vahvista sähköpostiosoite
       details: Omat tietosi
@@ -1211,7 +1143,7 @@ fi:
     set_new_password: Aseta uusi salasana
     setup:
       email_below_hint_html: Tarkista roskapostikansiosi tai pyydä uusi viesti. Voit korjata sähköpostiosoitteesi tarvittaessa.
-      email_settings_hint_html: Jotta voit aloittaa Mastodonin käytön, napsauta linkkiä, jonka lähetimme osoitteeseen %{email}. Odotamme täällä.
+      email_settings_hint_html: Napsauta lähettämäämme linkkiä vahvistaaksesi osoitteen %{email}. Odotamme täällä.
       link_not_received: Etkö saanut linkkiä?
       new_confirmation_instructions_sent: Saat pian uuden vahvistuslinkin sisältävän sähköpostiviestin!
       title: Tarkista sähköpostilaatikkosi
@@ -1220,7 +1152,7 @@ fi:
       title: Kirjaudu palvelimelle %{domain}
     sign_up:
       manual_review: Palvelimen %{domain} ylläpito tarkastaa rekisteröitymiset käsin. Helpottaaksesi rekisteröitymisesi käsittelyä kerro hieman itsestäsi ja siitä, miksi haluat luoda käyttäjätilin palvelimelle %{domain}.
-      preamble: Kun sinulla on tili tällä Mastodon-palvelimella, voit seurata kaikkia muita fediversumin käyttäjiä riippumatta siitä, missä heidän tilinsä on.
+      preamble: Kun sinulla on tili tällä Mastodon-palvelimella, voit seurata kaikkia muita verkossa olevia käyttäjiä riippumatta siitä, missä heidän tilinsä on.
       title: Otetaan %{domain} käyttöösi.
     status:
       account_status: Tilin tila
@@ -1232,8 +1164,6 @@ fi:
       view_strikes: Näytä aiemmat tiliäsi koskevat varoitukset
     too_fast: Lomake lähetettiin liian nopeasti, yritä uudelleen.
     use_security_key: Käytä suojausavainta
-    user_agreement_html: Olen lukenut ja hyväksyn <a href="%{terms_of_service_path}" target="_blank">käyttöehdot</a> ja <a href="%{privacy_policy_path}" target="_blank">tietosuojakäytännön</a>
-    user_privacy_agreement_html: Olen lukenut ja hyväksyn <a href="%{privacy_policy_path}" target="_blank">tietosuojakäytännön</a>
   author_attribution:
     example_title: Esimerkkiteksti
     hint_html: Kirjoitatko uutisia tai blogitekstejä Mastodonin ulkopuolella? Määrää, kuinka tulet tunnustetuksi, kun niitä jaetaan Mastodonissa.
@@ -1439,43 +1369,19 @@ fi:
       overwrite: Korvaa
       overwrite_long: Korvaa nykyiset tietueet uusilla
     overwrite_preambles:
-      blocking_html:
-        one: Olet aikeissa <strong>korvata estoluettelosi</strong> kaikkiaan <strong>%{count} tilillä</strong> tiedostosta <strong>%{filename}</strong>.
-        other: Olet aikeissa <strong>korvata estoluettelosi</strong> kaikkiaan <strong>%{count} tilillä</strong> tiedostosta <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Olet aikeissa <strong>korvata kirjanmerkkisi</strong> kaikkiaan <strong>%{count} julkaisulla</strong> tiedostosta <strong>%{filename}</strong>.
-        other: Olet aikeissa <strong>korvata kirjanmerkkisi</strong> kaikkiaan <strong>%{count} julkaisulla</strong> tiedostosta <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Olet aikeissa <strong>korvata verkkotunnusten estoluettelosi</strong> kaikkiaan <strong>%{count} verkkotunnuksella</strong> tiedostosta <strong>%{filename}</strong>.
-        other: Olet aikeissa <strong>korvata verkkotunnusten estoluettelosi</strong> kaikkiaan <strong>%{count} verkkotunnuksella</strong> tiedostosta <strong>%{filename}</strong>.
-      following_html:
-        one: Olet aikeissa <strong>seurata</strong> kaikkiaan <strong>%{count} tiliä</strong> tiedostosta <strong>%{filename}</strong> ja <strong>lopettaa kaikkien muiden seuraamisen</strong>.
-        other: Olet aikeissa <strong>seurata</strong> kaikkiaan <strong>%{count} tiliä</strong> tiedostosta <strong>%{filename}</strong> ja <strong>lopettaa kaikkien muiden seuraamisen</strong>.
-      lists_html:
-        one: Olet aikeissa <strong>korvata listojasi</strong> tiedoston <strong>%{filename}</strong> sisällöllä. Uusiin listoihin lisätään kaikkiaan <strong>%{count} tili</strong>.
-        other: Olet aikeissa <strong>korvata listojasi</strong> tiedoston <strong>%{filename}</strong> sisällöllä. Uusiin listoihin lisätään kaikkiaan <strong>%{count} tiliä</strong>.
-      muting_html:
-        one: Olet aikeissa <strong>korvata mykistettyjen tilien luettelosi</strong> kaikkiaan <strong>%{count} tilillä</strong> tiedostosta <strong>%{filename}</strong>.
-        other: Olet aikeissa <strong>korvata mykistettyjen tilien luettelosi</strong> kaikkiaan <strong>%{count} tilillä</strong> tiedostosta <strong>%{filename}</strong>.
+      blocking_html: Olet aikeissa <strong>korvata estoluettelosi</strong> kaikkiaan <strong>%{total_items} tilillä</strong> tiedostosta <strong>%{filename}</strong>.
+      bookmarks_html: Olet aikeissa <strong>korvata kirjanmerkkisi</strong> kaikkiaan <strong>%{total_items} julkaisulla</strong> tiedostosta <strong>%{filename}</strong>.
+      domain_blocking_html: Olet aikeissa <strong>korvata verkkotunnusten estoluettelosi</strong> kaikkiaan <strong>%{total_items} verkkotunnuksella</strong> tiedostosta <strong>%{filename}</strong>.
+      following_html: Olet aikeissa <strong>seurata</strong> kaikkiaan <strong>%{total_items} tiliä</strong> tiedostosta <strong>%{filename}</strong> ja <strong>lopettaa kaikkien muiden seuraamisen</strong>.
+      lists_html: Olet aikeissa <strong>korvata listojasi</strong> tiedoston <strong>%{filename}</strong> sisällöllä. Uusiin listoihin lisätään kaikkiaan <strong>%{total_items} tiliä</strong>.
+      muting_html: Olet aikeissa <strong>korvata mykistettyjen tilien luettelosi</strong> kaikkiaan <strong>%{total_items} tilillä</strong> tiedostosta <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Olet aikeissa <strong>estää</strong> kaikkiaan <strong>%{count} tilin</strong> tiedostosta <strong>%{filename}</strong>.
-        other: Olet aikeissa <strong>estää</strong> kaikkiaan <strong>%{count} tiliä</strong> tiedostosta <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Olet aikeissa lisätä kaikkiaan <strong>%{count} julkaisun</strong> tiedostosta <strong>%{filename}</strong><strong>kirjanmerkkeihisi</strong>.
-        other: Olet aikeissa lisätä kaikkiaan <strong>%{count} julkaisua</strong> tiedostosta <strong>%{filename}</strong><strong>kirjanmerkkeihisi</strong>.
-      domain_blocking_html:
-        one: Olet aikeissa <strong>estää</strong> kaikkiaan <strong>%{count} verkkotunnuksen</strong> tiedostosta <strong>%{filename}</strong>.
-        other: Olet aikeissa <strong>estää</strong> kaikkiaan <strong>%{count} verkkotunnusta</strong> tiedostosta <strong>%{filename}</strong>.
-      following_html:
-        one: Olet aikeissa <strong>seurata</strong> kaikkiaan <strong>%{count} tiliä</strong> tiedostosta <strong>%{filename}</strong>.
-        other: Olet aikeissa <strong>seurata</strong> kaikkiaan <strong>%{count} tiliä</strong> tiedostosta <strong>%{filename}</strong>.
-      lists_html:
-        one: Olet aikeissa lisätä <strong>listoihisi</strong> kaikkiaan <strong>%{count} tilin</strong> tiedostosta <strong>%{filename}</strong>. Uusia listoja luodaan, jos sopivaa kohdelistaa ei ole olemassa.
-        other: Olet aikeissa lisätä <strong>listoihisi</strong> kaikkiaan <strong>%{count} tiliä</strong> tiedostosta <strong>%{filename}</strong>. Uusia listoja luodaan, jos sopivaa kohdelistaa ei ole olemassa.
-      muting_html:
-        one: Olet aikeissa <strong>mykistää</strong> kaikkiaan <strong>%{count} tilin</strong> tiedostosta <strong>%{filename}</strong>.
-        other: Olet aikeissa <strong>mykistää</strong> kaikkiaan <strong>%{count} tiliä</strong> tiedostosta <strong>%{filename}</strong>.
+      blocking_html: Olet aikeissa <strong>estää</strong> kaikkiaan <strong>%{total_items} tiliä</strong> tiedostosta <strong>%{filename}</strong>.
+      bookmarks_html: Olet aikeissa lisätä kaikkiaan <strong>%{total_items} julkaisua</strong> tiedostosta <strong>%{filename}</strong><strong>kirjanmerkkeihisi</strong>.
+      domain_blocking_html: Olet aikeissa <strong>estää</strong> kaikkiaan <strong>%{total_items} verkkotunnusta</strong> tiedostosta <strong>%{filename}</strong>.
+      following_html: Olet aikeissa <strong>seurata</strong> kaikkiaan <strong>%{total_items} tiliä</strong> tiedostosta <strong>%{filename}</strong>.
+      lists_html: Olet aikeissa lisätä <strong>listoihisi</strong> kaikkiaan <strong>%{total_items} tiliä</strong> tiedostosta <strong>%{filename}</strong>. Uusia listoja luodaan, jos sopivaa kohdelistaa ei ole olemassa.
+      muting_html: Olet aikeissa <strong>mykistää</strong> kaikkiaan <strong>%{total_items} tiliä</strong> tiedostosta <strong>%{filename}</strong>.
     preface: Voit tuoda toiselta palvelimelta viemiäsi tietoja, kuten seuraamiesi tai estämiesi käyttäjien luettelon.
     recent_imports: Viimeksi tuotu
     states:
@@ -1732,7 +1638,7 @@ fi:
   scheduled_statuses:
     over_daily_limit: Olet ylittänyt %{limit} ajoitetun julkaisun rajan tälle päivälle
     over_total_limit: Olet ylittänyt %{limit} ajoitetun julkaisun rajan
-    too_soon: päivämäärän on oltava tulevaisuudessa
+    too_soon: Ajoitetun päiväyksen pitää olla tulevaisuudessa
   self_destruct:
     lead_html: Valitettavasti <strong>%{domain}</strong> sulkeutuu pysyvästi. Jos sinulla on siellä tili, et voi jatkaa sen käyttöä mutta voit yhä pyytää varmuuskopiota tiedoistasi.
     title: Tämä palvelin sulkeutuu
@@ -1895,8 +1801,6 @@ fi:
       too_late: On liian myöhäistä vedota tähän varoitukseen
   tags:
     does_not_match_previous_name: ei vastaa edellistä nimeä
-  terms_of_service:
-    title: Käyttöehdot
   themes:
     contrast: Mastodon (suuri kontrasti)
     default: Mastodon (tumma)
@@ -1928,10 +1832,6 @@ fi:
     recovery_instructions_html: Jos menetät puhelimesi, voit kirjautua tilillesi jollakin alla olevista palautuskoodeista. <strong>Pidä palautuskoodit hyvässä tallessa</strong>. Voit esimerkiksi tulostaa ne ja säilyttää muiden tärkeiden papereiden joukossa.
     webauthn: Suojausavaimet
   user_mailer:
-    announcement_published:
-      description: 'Palvelimen %{domain} ylläpito tiedottaa:'
-      subject: Palvelutiedote
-      title: Palvelimen %{domain} palvelutiedote
     appeal_approved:
       action: Tilin asetukset
       explanation: "%{appeal_date} lähettämäsi valitus tiliisi kohdistuvasta varoituksesta %{strike_date} on hyväksytty. Tilisi on jälleen hyvässä kunnossa."
@@ -1961,15 +1861,6 @@ fi:
       further_actions_html: Jos tämä et ollut sinä, suosittelemme, että %{action} heti ja otat käyttöön kaksivaiheisen todennuksen pitääksesi tilisi turvassa.
       subject: Tiliäsi on käytetty uudesta IP-osoitteesta
       title: Uusi kirjautuminen
-    terms_of_service_changed:
-      agreement: Jatkamalla palvelun %{domain} käyttöä hyväksyt nämä ehdot. Jos et hyväksy päivitettyjä ehtoja, voit milloin tahansa päättää sopimuksesi palvelun %{domain} kanssa poistamalla tilisi.
-      changelog: 'Lyhyesti, mitä tämä päivitys tarkoittaa sinulle:'
-      description: 'Sait tämän sähköpostiviestin, koska muutamme palvelun %{domain} käyttöehtoja. Muutokset tulevat voimaan %{date}. Kehotamme tutustumaan päivitettyihin ehtoihin kokonaisuudessaan täällä:'
-      description_html: Sait tämän sähköpostiviestin, koska muutamme palvelun %{domain} käyttöehtoja. Muutokset tulevat voimaan <strong>%{date}</strong>. Kehotamme tutustumaan <a href="%{path}" target="_blank">päivitettyihin ehtoihin kokonaisuudessaan täällä</a>.
-      sign_off: Palvelimen %{domain} tiimi
-      subject: Käyttöehtojemme päivitykset
-      subtitle: Palvelimen %{domain} käyttöehdot muuttuvat
-      title: Tärkeä päivitys
     warning:
       appeal: Lähetä valitus
       appeal_description: Jos uskot, että tämä on virhe, voit hakea muutosta palvelimen %{instance} ylläpidolta.
diff --git a/config/locales/fo.yml b/config/locales/fo.yml
index 8e611c346d..638c2da9d3 100644
--- a/config/locales/fo.yml
+++ b/config/locales/fo.yml
@@ -187,7 +187,6 @@ fo:
         create_domain_block: Stovna navnaøkjablokering
         create_email_domain_block: Stovna t-post-økisnavnablokk
         create_ip_block: Stovna IP reglu
-        create_relay: Stovna reiðlag
         create_unavailable_domain: Stovna navnaøki, sum ikki er tøkt
         create_user_role: Stovna leiklut
         demote_user: Lækka brúkara í tign
@@ -199,22 +198,18 @@ fo:
         destroy_email_domain_block: Strika t-posta-økisnavnablokk
         destroy_instance: Reinsa navnaøki
         destroy_ip_block: Strika IP reglu
-        destroy_relay: Strika reiðlag
         destroy_status: Strika post
         destroy_unavailable_domain: Strika navnaøki, sum ikki er tøkt
         destroy_user_role: Bein burtur leiklut
         disable_2fa_user: Ger 2FA óvirkið
         disable_custom_emoji: Ger serligt kenslutekn óvirkið
-        disable_relay: Ger reiðlag óvirkið
         disable_sign_in_token_auth_user: Ger váttan við teldupostateknum óvirkna fyri brúkara
         disable_user: Ger brúkara óvirknan
         enable_custom_emoji: Ger serligt kenslutekn virkið
-        enable_relay: Ger reiðlag virkið
         enable_sign_in_token_auth_user: Ger váttan við teldupostateknum virkna fyri brúkara
         enable_user: Ger brúkara virknan
         memorialize_account: Minnst til Konto
         promote_user: Vís fram Brúkara
-        publish_terms_of_service: Útgev tænastutreytir
         reject_appeal: Avvís mótmali
         reject_user: Avvís Brúkara
         remove_avatar_user: Sletta Avatar
@@ -252,7 +247,6 @@ fo:
         create_domain_block_html: "%{name} blokeraði navnaøkið %{target}"
         create_email_domain_block_html: "%{name} blokeraði teldupostanavnaøkið %{target}"
         create_ip_block_html: "%{name} gjørdi reglu fyri IP %{target}"
-        create_relay_html: "%{name} gjørdi eitt reiðlag %{target}"
         create_unavailable_domain_html: "%{name} steðgaði veiting til navnaøkið %{target}"
         create_user_role_html: "%{name} stovnaði %{target} leiklutin"
         demote_user_html: "%{name} lækkaði tignina hjá brúkaranum %{target}"
@@ -264,22 +258,18 @@ fo:
         destroy_email_domain_block_html: "%{name} leysgav teldupostanavnaøkið %{target}"
         destroy_instance_html: "%{name} reinsaði navnaøkið %{target}"
         destroy_ip_block_html: "%{name} slettaðar reglur fyri IP %{target}"
-        destroy_relay_html: "%{name} strikaði reiðlagið %{target}"
         destroy_status_html: "%{name} slettaði upplegg hjá %{target}"
         destroy_unavailable_domain_html: "%{name} tók upp aftir veiting til navnaøkið %{target}"
         destroy_user_role_html: "%{name} slettaði leiklutin hjá %{target}"
         disable_2fa_user_html: "%{name} slepti kravið um váttan í tveimum stigum fyri brúkaran %{target}"
         disable_custom_emoji_html: "%{name} gjørdi kensluteknið %{target} óvirkið"
-        disable_relay_html: "%{name} gjørdi reiðlagið %{target} óvirkið"
         disable_sign_in_token_auth_user_html: "%{name} gjørdi váttan við teldupostteknum óvirkna fyri %{target}"
         disable_user_html: "%{name} gjørdi innritan hjá brúkaranum %{target} óvirkna"
         enable_custom_emoji_html: "%{name} gjørdi kensluteknið %{target} virkið"
-        enable_relay_html: "%{name} gjørdi reiðlagið %{target} virkið"
         enable_sign_in_token_auth_user_html: "%{name} gjørdi váttan við teldupostteknum virkna fyri %{target}"
         enable_user_html: "%{name} gjørdi innritan virkna fyri brúkaran %{target}"
         memorialize_account_html: "%{name} broytti kontuna hjá %{target} til eina minnissíðu"
         promote_user_html: "%{name} flutti brúkaran %{target} fram"
-        publish_terms_of_service_html: "%{name} útgav dagføringar til tænastutreytirnar"
         reject_appeal_html: "%{name} avvísti umsjónaráheitan frá %{target}"
         reject_user_html: "%{name} avvísti skráseting hjá %{target}"
         remove_avatar_user_html: "%{name} strikaði eftirgjørda skapningin hjá %{target}"
@@ -309,7 +299,6 @@ fo:
       title: Skoðanarloggur
       unavailable_instance: "(økisnavn ikki tøkt)"
     announcements:
-      back: Aftur til kunngerðir
       destroyed_msg: Kunngerð strikað!
       edit:
         title: Rætta kunngerð
@@ -318,10 +307,6 @@ fo:
       new:
         create: Stovna kunngerð
         title: Nýggj kunngerð
-      preview:
-        disclaimer: Av tí at brúkarar ikki kunnu velja tær frá, eiga teldupostfráboðanir at vera avmarkaðar til týdningarmiklar kunngerðir, sosum trygdarbrot og boð um at ambætarin verður tikin niður.
-        explanation_html: 'Teldubrævið verður sent til <strong>%{display_count} brúkarar</strong>. Fylgjandi tekstur kemur við í teldubrævið:'
-        title: Undanvís fráboðan um kunngerð
       publish: Legg út
       published_msg: Kunngerð útgivin!
       scheduled_for: Sett á skrá %{time}
@@ -480,36 +465,6 @@ fo:
       new:
         title: Innflyt navnaøkjablokeringar
       no_file: Eingin fíla vald
-    fasp:
-      debug:
-        callbacks:
-          created_at: Stovnað
-          delete: Strika
-          ip: IP adressa
-          request_body: Umbønskroppur
-          title: Kemb afturkøll
-      providers:
-        active: Virkin
-        base_url: Grund-URL
-        callback: Afturkall
-        delete: Strika
-        edit: Rætta veitara
-        finish_registration: Enda skráseting
-        name: Navn
-        providers: Veitarar
-        public_key_fingerprint: Fingramerki á almennum lykli
-        registration_requested: Skráseting umbiðin
-        registrations:
-          confirm: Vátta
-          description: Tú hevur móttikið eina skráseting frá einum FASP. Avvís, um tú ikki setti hetta í gongd. Um tú setti tað í gongd, so skal tú vandaliga samanbera navn og lyklafingramerki, áðrenn tú góðkennir skrásetingina.
-          reject: Avvís
-          title: Vátta FASP-skráseting
-        save: Goym
-        select_capabilities: Vel evni
-        sign_in: Rita inn
-        status: Støða
-        title: Fediverse Auxiliary Service Providers
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Viðmæli at fylgja hjálpa nýggjum brúkarum til skjótt at finna áhugavert innihald</strong>. Tá ein brúkari ikki hevur havt samband við aðrar brúkarar til at gera persónlig tilmæli, so verða hesar kontur viðmæltar í staðin. Konturnar verða roknaðar av nýggjum dagliga frá einari blanding av kontum við flestu hugbindingum og flestu lokalu fylgjarum fyri eitt givið mál."
       language: Fyri mál
@@ -863,10 +818,8 @@ fo:
       back_to_account: Aftur til kontusíðu
       back_to_report: Aftur til meldingarsíðu
       batch:
-        add_to_report: 'Legg afturat melding #%{id}'
         remove_from_report: Strika frá melding
         report: Melding
-      contents: Innihald
       deleted: Strikað
       favourites: Dámdir postar
       history: Útgávusøga
@@ -875,17 +828,13 @@ fo:
       media:
         title: Miðlar
       metadata: Metadátur
-      no_history: Hesin posturin er ikki broyttur
       no_status_selected: Eingir postar vóru broyttir, tí eingir vóru valdir
       open: Lat post upp
       original_status: Upprunapostur
       reblogs: Endurbloggar
-      replied_to_html: Svaraði %{acct_link}
       status_changed: Postur broyttur
-      status_title: Postar hjá @%{name}
-      title: Kontupostar - @%{name}
+      title: Postar hjá kontu
       trending: Vælumtókt
-      view_publicly: Vís fyri øllum
       visibility: Sýni
       with_media: Við miðli
     strikes:
@@ -962,36 +911,6 @@ fo:
       search: Leita
       title: Frámerki
       updated_msg: Frámerkjastillingar dagførdar
-    terms_of_service:
-      back: Aftur til tænastutreytir
-      changelog: Hvat er broytt
-      create: Brúka tínar egnu
-      current: Núverandi
-      draft: Kladda
-      generate: Brúka leist
-      generates:
-        action: Framleið
-        chance_to_review_html: "<strong>Framleiddu tænastutreytirnar verða ikki útgivnar av sær sjálvum.</strong> Tú fær møguleika at eftirhyggja úrslitini. Vinarliga útfyll neyðugu smálutirnar fyri at halda fram."
-        explanation_html: Leisturin við tænastutreytum er einans til kunningar og skal ikki fatast sum løgfrøðislig ráðgeving yvirhøvur. Vinarliga spyr tín egna løgfrøðisliga ráðgeva um tína støðu og ítøkiligu løgfrøðisligu spurningarnar hjá tær.
-        title: Uppseting av tænastutreytum
-      going_live_on_html: Galdandi, frá %{date}
-      history: Søga
-      live: Beinleiðis
-      no_history: Enn eru ongar skrásettar broytingar í tænastutreytunum.
-      no_terms_of_service_html: Í løtuni hevur tú ongar tænastutreytir uppsettar. Hugsanin við tænastutreytum er at veita greidleika og at verja teg ímóti møguligum ábyrgdum í ósemjum við tínar brúkarar.
-      notified_on_html: Fráboðan latin brúkarum %{date}
-      notify_users: Gev brúkarum fráboðan
-      preview:
-        explanation_html: 'Teldubrævið verður sent til <strong>%{display_count} brúkarar</strong>, sum hava stovna kontu áðrenn %{date}. Fylgjandi tekstur kemur við í teldubrævið:'
-        send_preview: Send undanvísing til %{email}
-        send_to_all:
-          one: Send %{display_count} teldubræv
-          other: Send %{display_count} teldubrøv
-        title: Undanvís fráboðan um tænastutreytir
-      publish: Útgev
-      published_on_html: Útgivið %{date}
-      save_draft: Goym kladdu
-      title: Tænastutreytir
     title: Umsiting
     trends:
       allow: Loyv
@@ -1199,6 +1118,7 @@ fo:
     migrate_account: Flyt til eina aðra kontu
     migrate_account_html: Ynskir tú at víðaribeina hesa kontuna til eina aðra, so kanst tú <a href="%{path}">seta tað upp her</a>.
     or_log_in_with: Ella innrita við
+    privacy_policy_agreement_html: Eg havi lisið og taki undir við <a href="%{privacy_policy_path}" target="_blank">privatlívspolitikkinum</a>
     progress:
       confirm: Vátta teldupost
       details: Tínir smálutir
@@ -1223,7 +1143,7 @@ fo:
     set_new_password: Áset nýtt loyniorð
     setup:
       email_below_hint_html: Kekka mappuna við ruskposti ella bið um ein annan. Tú kanst rætta teldupostadressuna, um hon er skeiv.
-      email_settings_hint_html: Trýst á leinkið, sum vit sendu til %{email} fyri at byrja at brúka Mastodon. Vit bíða beint her.
+      email_settings_hint_html: Kekka leinkið, sum vit sendu tær at eftirkanna %{email}. Vit bíða beint her.
       link_not_received: Fekk tú einki leinki?
       new_confirmation_instructions_sent: Tú fer at móttaka eitt nýtt teldubræv við váttanarleinkinum um nakrar fáar minuttir!
       title: Kekka innbakkan hjá tær
@@ -1232,7 +1152,7 @@ fo:
       title: Rita inn á %{domain}
     sign_up:
       manual_review: Tilmeldingar til %{domain} fara ígjøgnum eina manuella eftirkanning av okkara kjakleiðarum. Fyri at hjálpa okkum at skunda undir skrásetingina, skriva eitt sindur um teg sjálva/n og hví tú vil hava eina kontu á %{domain}.
-      preamble: Við eini kontu á hesum Mastodon ambætaranum ber til hjá tær at fylgja ein og hvønn annan persón á fediversinum, óansæð hvar teirra konta er hýst.
+      preamble: Við eini kontu á hesum Mastodon ambætaranum ber til hjá tær at fylgja ein og hvønn annan persón á netverkinum, óansæð hvar teirra konta er hýst.
       title: Latum okkum fáa teg settan upp á %{domain}.
     status:
       account_status: Kontustøða
@@ -1244,8 +1164,6 @@ fo:
       view_strikes: Vís eldri atsóknir móti tíni kontu
     too_fast: Oyðublaðið innsent ov skjótt, royn aftur.
     use_security_key: Brúka trygdarlykil
-    user_agreement_html: Eg havi lisið og taki undir við <a href="%{terms_of_service_path}" target="_blank">tænastutreytunum</a> og <a href="%{privacy_policy_path}" target="_blank">privatlívspolitikkinum</a>
-    user_privacy_agreement_html: I have lisið og taki undir við <a href="%{privacy_policy_path}" target="_blank">privatlívspolitikkinum</a>
   author_attribution:
     example_title: Tekstadømi
     hint_html: Skrivar tú tíðindi ella greinar til bloggin uttanfyri Mastodon? Her kanst tú stýra, hvussu tú verður tilsipað/ur, tá ið títt tilfar verður deilt á Mastodon.
@@ -1451,43 +1369,19 @@ fo:
       overwrite: Skriva omaná
       overwrite_long: Legg nýggj teigarøð inn fyri tey verandi
     overwrite_preambles:
-      blocking_html:
-        one: Tú ert í ferð við at <strong>útskifta blokeringslistan hjá tær</strong> við upp til <strong>%{count} kontu</strong> frá <strong>%{filename}</strong>.
-        other: Tú ert í ferð við at <strong>útskifta blokeringslistan hjá tær</strong> við upp til <strong>%{count} kontum</strong> frá <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Tú ert í ferð við at <strong>útskifta tíni bókamerki</strong> við upp til <strong>%{count} posti</strong> frá <strong>%{filename}</strong>.
-        other: Tú ert í ferð við at <strong>útskifta tíni bókamerki</strong> við upp til <strong>%{count} postum</strong> frá <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Tú ert í ferð við at <strong>útskifta navnaøkisblokeringslistan hjá tær</strong> við upp til <strong>%{count} navnaøki</strong> frá <strong>%{filename}</strong>.
-        other: Tú ert í ferð við at <strong>útskifta navnaøkisblokeringslistan hjá tær</strong> við upp til <strong>%{count} navnaøkjum</strong> frá <strong>%{filename}</strong>.
-      following_html:
-        one: Tú ert í ferð við at <strong>fylgja</strong> upp til <strong>%{count} kontu</strong> frá <strong>%{filename}</strong> og <strong>at gevast at fylgja øðrum</strong>.
-        other: Tú ert í ferð við at <strong>fylgja</strong> upp til <strong>%{count} kontum</strong> frá <strong>%{filename}</strong> og <strong>at gevast at fylgja øðrum</strong>.
-      lists_html:
-        one: Tú ert í ferð við at <strong>skifta listarnar hjá tær út</strong> við tað, sum er í <strong>%{filename}</strong>. Upp til <strong>%{count} konta</strong> verður løgd afturat nýggjum listum.
-        other: Tú ert í ferð við at <strong>skifta listarnar hjá tær út</strong> við tað, sum er í <strong>%{filename}</strong>. Upp til <strong>%{count} kontur</strong> verða lagdar afturat nýggjum listum.
-      muting_html:
-        one: Tú ert í ferð við at <strong>útskifta listan hjá tær við doyvdum kontum</strong> við upp til <strong>%{count} kontu</strong> frá <strong>%{filename}</strong>.
-        other: Tú ert í ferð við at <strong>útskifta listan hjá tær við doyvdum kontum</strong> við upp til <strong>%{count} kontum</strong> frá <strong>%{filename}</strong>.
+      blocking_html: Tú ert í ferð við at <strong>útskifta blokeringslistan hjá tær</strong> við upp til <strong>%{total_items} kontum</strong> frá <strong>%{filename}</strong>.
+      bookmarks_html: Tú ert í ferð við at <strong>útskifta tíni bókamerki</strong> við upp til <strong>%{total_items} postum</strong> frá <strong>%{filename}</strong>.
+      domain_blocking_html: Tú ert í ferð við at <strong>útskifta navnaøkisblokeringslistan hjá tær</strong> við upp til <strong>%{total_items} navnaøkjum</strong> frá <strong>%{filename}</strong>.
+      following_html: Tú ert í ferð við at <strong>fylgja</strong> upp til <strong>%{total_items} kontum</strong> frá <strong>%{filename}</strong> og <strong>at gevast at fylgja øðrum</strong>.
+      lists_html: Tú ert í ferð við at <strong>skifta listarnar hjá tær út</strong> við tað, sum er í <strong>%{filename}</strong>. Upp til <strong>%{total_items} kontur</strong> verða lagdar afturat nýggjum listum.
+      muting_html: Tú ert í ferð við at <strong>útskifta listan hjá tær við doyvdum kontum</strong> við upp til <strong>%{total_items} kontum</strong> frá <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Tú ert í ferð við at <strong>blokera</strong> upp til <strong>%{count} kontu</strong> frá <strong>%{filename}</strong>.
-        other: Tú ert í ferð við at <strong>blokera</strong> upp til <strong>%{count} kontur</strong> frá <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Tú ert í ferð við at leggja upp til <strong>%{count} post</strong> frá <strong>%{filename}</strong> afturat tínum <strong>bókamerkjum</strong>.
-        other: Tú ert í ferð við at leggja upp til <strong>%{count} postar</strong> frá <strong>%{filename}</strong> afturat tínum <strong>bókamerkjum</strong>.
-      domain_blocking_html:
-        one: Tú ert í ferð við at <strong>blokera</strong> upp til <strong>%{count} navnaøki</strong> frá <strong>%{filename}</strong>.
-        other: Tú ert í ferð við at <strong>blokera</strong> upp til <strong>%{count} navnaøki</strong> frá <strong>%{filename}</strong>.
-      following_html:
-        one: Tú ert í ferð við at <strong>fylgja</strong> upp til <strong>%{count} kontu</strong> frá <strong>%{filename}</strong>.
-        other: Tú ert í ferð við at <strong>fylgja</strong> upp til <strong>%{count} kontur</strong> frá <strong>%{filename}</strong>.
-      lists_html:
-        one: Tú ert í ferð við at leggja upp til <strong>%{count} kontu</strong> frá <strong>%{filename}</strong> afturat tínum <strong>listum</strong>. Nýggir listar verða stovnaðir, um eingin listi er at leggja afturat.
-        other: Tú ert í ferð við at leggja upp til <strong>%{count} kontur</strong> frá <strong>%{filename}</strong> afturat tínum <strong>listum</strong>. Nýggir listar verða stovnaðir, um eingin listi er at leggja afturat.
-      muting_html:
-        one: Tú ert í ferð við at <strong>doyva</strong> upp til <strong>%{count} kontu</strong> frá <strong>%{filename}</strong>.
-        other: Tú ert í ferð við at <strong>doyva</strong> upp til <strong>%{count} kontur</strong> frá <strong>%{filename}</strong>.
+      blocking_html: Tú ert í ferð við at <strong>blokera</strong> upp til <strong>%{total_items} kontur</strong> frá <strong>%{filename}</strong>.
+      bookmarks_html: Tú ert í ferð við at leggja upp til <strong>%{total_items} postar</strong> frá <strong>%{filename}</strong> afturat tínum <strong>bókamerkjum</strong>.
+      domain_blocking_html: Tú ert í ferð við at <strong>blokera</strong> upp til <strong>%{total_items} navnaøki</strong> frá <strong>%{filename}</strong>.
+      following_html: Tú ert í ferð við at <strong>fylgja</strong> upp til <strong>%{total_items} kontur</strong> frá <strong>%{filename}</strong>.
+      lists_html: Tú ert í ferð við at leggja upp til <strong>%{total_items} kontur</strong> frá <strong>%{filename}</strong> afturat tínum <strong>listum</strong>. Nýggir listar verða stovnaðir, um eingin listi er at leggja afturat.
+      muting_html: Tú ert í ferð við at <strong>doyva</strong> upp til <strong>%{total_items} kontur</strong> frá <strong>%{filename}</strong>.
     preface: Tú kanst innlesa dátur, sum tú hevur útlisið frá einum øðrum ambætara, so sum listar av fólki, sum tú fylgir ella blokerar.
     recent_imports: Feskar innflytingar
     states:
@@ -1744,7 +1638,7 @@ fo:
   scheduled_statuses:
     over_daily_limit: Tú er komin at markinum, sum er %{limit} skrálagdir postar, í dag
     over_total_limit: Tú er komin at markinum, sum er %{limit} skrálagdir postar
-    too_soon: dagfesting má vera í framtíðini
+    too_soon: Ætlanardagfestingin má vera í framtíðini
   self_destruct:
     lead_html: Tíverri, <strong>%{domain}</strong> er í ferð við at blíva lukkað niður med alla. Um tú hevði eina kontu har, so ber ikki til framhaldandi at brúka hana, men tú kann framvegis biðja um trygdaravrit av tínum dátum.
     title: Hesin ambætarin er í ferð við at lukka
@@ -1907,8 +1801,6 @@ fo:
       too_late: Tað er ov seint at kæra hesa atsókn
   tags:
     does_not_match_previous_name: samsvarar ikki við undanfarna navnið
-  terms_of_service:
-    title: Tænastutreytir
   themes:
     contrast: Mastodon (høgur kontrastur)
     default: Mastodon (myrkt)
@@ -1940,10 +1832,6 @@ fo:
     recovery_instructions_html: Missir tú atgongd til telefonina, so kanst tú brúka eina av kodunum til endurgerð niðanfyri at fáa atgongd aftur til kontu tína. <strong>Goym kodurnar til endurgerð trygt</strong>. Til dømis kanst tú prenta tær og goyma tær saman við øðrum týdningarmiklum skjølum.
     webauthn: Trygdarlyklar
   user_mailer:
-    announcement_published:
-      description: 'Umsitararnir av %{domain} kunngera:'
-      subject: Tænastukunngerð
-      title: "%{domain} tænastukunngerð"
     appeal_approved:
       action: Kontustillingar
       explanation: Kæran um atsóknina móti kontu tínari %{strike_date}, sum tú sendi inn %{appeal_date}, er góðkend. Konta tín er aftur tignarlig.
@@ -1973,15 +1861,6 @@ fo:
       further_actions_html: Var hetta ikki tú, so mæla vit til, at tú %{action} beinan vegin og at tú ger váttan í tveimum stigum virkna fyri at konta tín kann vera trygg.
       subject: Atgongd er fingin til kontu tína frá eini nýggjari IP adressu
       title: Ein nýggj innritan
-    terms_of_service_changed:
-      agreement: Við framhaldandi at brúka %{domain} góðtekur tú hesar treytir. Tekur tú ikki undir við dagførdu treytunum, so kanst tú til einhvørja tíð uppsiga avtaluna við %{domain} við at strika kontu tína.
-      changelog: 'Í stuttum merkir henda dagføringin:'
-      description: 'Tú móttekur hetta teldubrævið, tí at vit gera nakrar broytingar í okkara tænastutreytum á %{domain}. Broytingarnar vera galdandi frá %{date}. Vit eggja tær til at eftirhyggja dagførdu treytirnar her:'
-      description_html: Tú móttekur hetta teldubrævið, tí at vit gera nakrar broytingar í okkara tænastutreytum á %{domain}. Broytingarnar vera galdandi frá <strong>%{date}</strong>. Vit eggja tær til at eftirhyggja <a href="%{path}" target="_blank">dagførdu treytirnar her</a>.
-      sign_off: "%{domain} toymið"
-      subject: Dagføringar til okkara tænastutreytir
-      subtitle: Tænastutreytirnar hjá %{domain} eru við at verða broyttar
-      title: Týdningarmikil dagføring
     warning:
       appeal: Innsend eina kæru
       appeal_description: Trýrt tú, at hetta er ein feilur, so kanst tú senda eina kæru til starvsfólkini á %{instance}.
diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml
index e85952f91c..5093e5d733 100644
--- a/config/locales/fr-CA.yml
+++ b/config/locales/fr-CA.yml
@@ -187,7 +187,6 @@ fr-CA:
         create_domain_block: Créer un blocage de domaine
         create_email_domain_block: Création d'un blocage de domaine de courrier électronique
         create_ip_block: Créer une règle IP
-        create_relay: Créer un relais
         create_unavailable_domain: Créer un domaine indisponible
         create_user_role: Créer le rôle
         demote_user: Rétrograder l’utilisateur·ice
@@ -199,22 +198,18 @@ fr-CA:
         destroy_email_domain_block: Supprimer le blocage de domaine de courriel
         destroy_instance: Purge du domaine
         destroy_ip_block: Supprimer la règle IP
-        destroy_relay: Supprimer le relais
         destroy_status: Supprimer le message
         destroy_unavailable_domain: Supprimer le domaine indisponible
         destroy_user_role: Détruire le rôle
         disable_2fa_user: Désactiver l’A2F
         disable_custom_emoji: Désactiver les émojis personnalisés
-        disable_relay: Désactiver le relais
         disable_sign_in_token_auth_user: Désactiver le jeton d'authentification par e-mail pour l'utilisateur
         disable_user: Désactiver le compte
         enable_custom_emoji: Activer les émojis personnalisées
-        enable_relay: Activer le relais
         enable_sign_in_token_auth_user: Activer le jeton d'authentification par e-mail pour l'utilisateur
         enable_user: Activer l’utilisateur
         memorialize_account: Ériger en mémorial
         promote_user: Promouvoir l’utilisateur
-        publish_terms_of_service: Publier les conditions d'utilisation
         reject_appeal: Rejeter l'appel
         reject_user: Rejeter l’utilisateur
         remove_avatar_user: Supprimer l’avatar
@@ -252,7 +247,6 @@ fr-CA:
         create_domain_block_html: "%{name} a bloqué le domaine %{target}"
         create_email_domain_block_html: "%{name} a bloqué le domaine d'e-mail %{target}"
         create_ip_block_html: "%{name} a créé une règle pour l'IP %{target}"
-        create_relay_html: "%{name} a créé un relais %{target}"
         create_unavailable_domain_html: "%{name} a arrêté la livraison vers le domaine %{target}"
         create_user_role_html: "%{name} a créé le rôle %{target}"
         demote_user_html: "%{name} a rétrogradé l'utilisateur·rice %{target}"
@@ -264,22 +258,18 @@ fr-CA:
         destroy_email_domain_block_html: "%{name} a débloqué le domaine d'e-mail %{target}"
         destroy_instance_html: "%{name} a purgé le domaine %{target}"
         destroy_ip_block_html: "%{name} a supprimé la règle pour l'IP %{target}"
-        destroy_relay_html: "%{name} a supprimé le relais %{target}"
         destroy_status_html: "%{name} a supprimé le message de %{target}"
         destroy_unavailable_domain_html: "%{name} a repris la livraison au domaine %{target}"
         destroy_user_role_html: "%{name} a supprimé le rôle %{target}"
         disable_2fa_user_html: "%{name} a désactivé l'authentification à deux facteurs pour l'utilisateur·rice %{target}"
         disable_custom_emoji_html: "%{name} a désactivé l'émoji %{target}"
-        disable_relay_html: "%{name} a désactivé le relais %{target}"
         disable_sign_in_token_auth_user_html: "%{name} a désactivé l'authentification par jeton de courriel pour %{target}"
         disable_user_html: "%{name} a désactivé la connexion de l'utilisateur·rice %{target}"
         enable_custom_emoji_html: "%{name} a activé l'émoji %{target}"
-        enable_relay_html: "%{name} a activé le relais %{target}"
         enable_sign_in_token_auth_user_html: "%{name} a activé l'authentification par jeton de courriel pour %{target}"
         enable_user_html: "%{name} a activé la connexion de l'utilisateur·rice %{target}"
         memorialize_account_html: "%{name} a converti le compte de %{target} en un mémorial"
         promote_user_html: "%{name} a promu l'utilisateur·rice %{target}"
-        publish_terms_of_service_html: "%{name} a publié des mises à jour des conditions d'utilisation"
         reject_appeal_html: "%{name} a rejeté l'appel de la décision de modération émis par %{target}"
         reject_user_html: "%{name} a rejeté l’inscription de %{target}"
         remove_avatar_user_html: "%{name} a supprimé l'avatar de %{target}"
@@ -309,7 +299,6 @@ fr-CA:
       title: Journal d’audit
       unavailable_instance: "(nom de domaine indisponible)"
     announcements:
-      back: Retour aux annonces
       destroyed_msg: Annonce supprimée avec succès !
       edit:
         title: Modifier l’annonce
@@ -318,9 +307,6 @@ fr-CA:
       new:
         create: Créer une annonce
         title: Nouvelle annonce
-      preview:
-        explanation_html: 'L''e-mail sera envoyé à <strong>%{display_count} utilisateurs</strong>. Le texte suivant sera inclus :'
-        title: Aperçu de la notification d'annonce
       publish: Publier
       published_msg: Annonce publiée avec succès !
       scheduled_for: Planifiée pour %{time}
@@ -482,15 +468,6 @@ fr-CA:
       new:
         title: Importer des blocages de domaine
       no_file: Aucun fichier sélectionné
-    fasp:
-      providers:
-        registrations:
-          confirm: Confirmer
-          reject: Rejeter
-        save: Enregistrer
-        select_capabilities: Sélectionnez les Capacités
-        sign_in: Se connecter
-        status: État
     follow_recommendations:
       description_html: "<strong>Les recommandations d'abonnement aident les nouvelles personnes à trouver rapidement du contenu intéressant</strong>. Si un·e utilisateur·rice n'a pas assez interagi avec les autres pour avoir des recommandations personnalisées, ces comptes sont alors recommandés. La sélection est mise à jour quotidiennement depuis un mélange de comptes ayant le plus d'interactions récentes et le plus grand nombre d'abonné·e·s locaux pour une langue donnée."
       language: Pour la langue
@@ -844,10 +821,8 @@ fr-CA:
       back_to_account: Retour à la page du compte
       back_to_report: Retour à la page du rapport
       batch:
-        add_to_report: 'Ajouter au rapport #%{id}'
         remove_from_report: Retirer du rapport
         report: Signalement
-      contents: Contenu
       deleted: Supprimé
       favourites: Favoris
       history: Historique de version
@@ -856,17 +831,13 @@ fr-CA:
       media:
         title: Médias
       metadata: Metadonnés
-      no_history: Ce message n'a pas été édité
       no_status_selected: Aucun message n’a été modifié car aucun n’a été sélectionné
       open: Ouvrir le message
       original_status: Message original
       reblogs: Partages
-      replied_to_html: Répondu à %{acct_link}
       status_changed: Publication modifiée
-      status_title: Posté par @%{name}
-      title: Messages du compte - @%{name}
+      title: Messages du compte
       trending: Tendances
-      view_publicly: Afficher de manière publique
       visibility: Visibilité
       with_media: Avec médias
     strikes:
@@ -943,36 +914,6 @@ fr-CA:
       search: Recherche
       title: Hashtags
       updated_msg: Paramètres du hashtag mis à jour avec succès
-    terms_of_service:
-      back: Retour aux conditions d'utilisation
-      changelog: Nouveautés
-      create: Utilisez vos propres
-      current: Courant
-      draft: Brouillon
-      generate: Utiliser un modèle
-      generates:
-        action: Générer
-        chance_to_review_html: "<strong>Les conditions d'utilisation générées ne seront pas publiées automatiquement.</strong> Vous aurez la possibilité de vérifier les résultats. Veuillez remplir les informations nécessaires pour continuer."
-        explanation_html: Le modèle de conditions d'utilisation fourni l'est uniquement à titre informatif et ne doit pas être interprété comme un conseil juridique sur quelque sujet que ce soit. Veuillez consulter votre propre conseiller juridique sur votre situation et les questions juridiques spécifiques que vous vous posez.
-        title: Configuration des Conditions d'Utilisation
-      going_live_on_html: En direct, à compter du %{date}
-      history: Historique
-      live: En cours d'utilisation
-      no_history: Il n'y a pas encore de modifications enregistrées des conditions d'utilisation.
-      no_terms_of_service_html: Vous n'avez actuellement aucune condition d'utilisation configurée. Les conditions d'utilisation ont pour but de clarifier les droits et obligations de chacun lors de l'utilisation du service et de vous protéger contre d'éventuelles responsabilités en cas de litige avec vos utilisateurs.
-      notified_on_html: Utilisateurs notifiés le `%{date}`
-      notify_users: Notifier les utilisateurs
-      preview:
-        explanation_html: 'L''e-mail sera envoyé aux utilisateurs <strong>%{display_count}</strong> qui se sont inscrits avant %{date}. Le texte suivant sera inclus dans l''e-mail :'
-        send_preview: Envoyer un aperçu à %{email}
-        send_to_all:
-          one: Envoyer %{display_count} email
-          other: Envoyer %{display_count} emails
-        title: Notification concernant l'aperçu des conditions d'utilisation
-      publish: Publier
-      published_on_html: Publié le %{date}
-      save_draft: Enregistrer le brouillon
-      title: Conditions d'utilisation
     title: Administration
     trends:
       allow: Autoriser
@@ -1180,6 +1121,7 @@ fr-CA:
     migrate_account: Déménager vers un compte différent
     migrate_account_html: Si vous voulez rediriger ce compte vers un autre, vous pouvez le <a href="%{path}">configurer ici</a>.
     or_log_in_with: Ou authentifiez-vous avec
+    privacy_policy_agreement_html: J’ai lu et j’accepte la <a href="%{privacy_policy_path}" target="_blank">politique de confidentialité</a>
     progress:
       confirm: Confirmation de l'adresse mail
       details: Vos infos
@@ -1204,7 +1146,7 @@ fr-CA:
     set_new_password: Définir le nouveau mot de passe
     setup:
       email_below_hint_html: Consultez votre dossier de courrier indésirable ou demandez-en un autre. Vous pouvez corriger votre adresse e-mail si elle est incorrecte.
-      email_settings_hint_html: Cliquez sur le lien que nous avons envoyé à %{email} pour commencer à utiliser Mastodon. Nous vous attendrons ici.
+      email_settings_hint_html: Cliquez sur le lien que nous vous avons envoyé pour vérifier %{email}. Nous vous attendrons ici.
       link_not_received: Vous n'avez pas reçu de lien?
       new_confirmation_instructions_sent: Vous allez recevoir un nouvel e-mail avec le lien de confirmation dans quelques minutes !
       title: Vérifiez votre boîte de réception
@@ -1213,7 +1155,7 @@ fr-CA:
       title: Se connecter à %{domain}
     sign_up:
       manual_review: Les inscriptions sur %{domain} passent par une revue manuelle de nos modérateurs. Pour les aider, écrivez un peu plus sur vous et pourquoi vous souhaitez créer un compte sur %{domain}.
-      preamble: Avec un compte sur ce serveur Mastodon, vous pourrez suivre n'importe quelle autre personne du fediverse, quel que soit l'endroit où son compte est hébergé.
+      preamble: Avec un compte sur ce serveur Mastodon, vous serez en mesure de suivre toute autre personne sur le réseau, quel que soit l’endroit où son compte est hébergé.
       title: Mettons les choses en place pour %{domain}.
     status:
       account_status: État du compte
@@ -1225,8 +1167,6 @@ fr-CA:
       view_strikes: Voir les sanctions précédemment appliquées à votre compte
     too_fast: Formulaire envoyé trop rapidement, veuillez réessayer.
     use_security_key: Utiliser la clé de sécurité
-    user_agreement_html: J'ai lu et j'accepte les <a href="%{terms_of_service_path}" target="_blank">conditions d'utilisation</a> et la <a href="%{privacy_policy_path}" target="_blank">politique de confidentialité</a>
-    user_privacy_agreement_html: J’ai lu et j’accepte la <a href="%{privacy_policy_path}" target="_blank">politique de confidentialité</a>
   author_attribution:
     example_title: Exemple de texte
     hint_html: Vous écrivez des nouvelles ou des articles de blog en dehors de Mastodon ? Contrôlez la façon dont vous êtes crédité lorsqu'ils sont partagés sur Mastodon.
@@ -1432,43 +1372,19 @@ fr-CA:
       overwrite: Écraser
       overwrite_long: Remplacer les enregistrements actuels par les nouveaux
     overwrite_preambles:
-      blocking_html:
-        one: Vous allez <strong>remplacer votre liste de blocage</strong> par près de <strong>%{count} compte</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>remplacer votre liste de blocage</strong> par près de <strong>%{count} comptes</strong> tirés de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Vous allez <strong>remplacer vos signets</strong> par près de <strong>%{count} post</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>remplacer vos signets</strong> par près de <strong>%{count} posts</strong> tirés de <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Vous allez <strong>remplacer votre liste de blocage de domaines</strong> par près de <strong>%{count} domaine</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>remplacer votre liste de blocage de domaines</strong> par près de <strong>%{count} domaines</strong> tirés de <strong>%{filename}</strong>.
-      following_html:
-        one: Vous allez <strong>suivre</strong> jusqu’à <strong>%{count} compte</strong> depuis <strong>%{filename}</strong> et <strong>arrêter de suivre n’importe qui d’autre</strong>.
-        other: Vous allez <strong>suivre</strong> jusqu’à <strong>%{count} comptes</strong> depuis <strong>%{filename}</strong> et <strong>arrêter de suivre n’importe qui d’autre</strong>.
-      lists_html:
-        one: Vous allez <strong>remplacer vos listes</strong> par le contenu de <strong>%{filename}</strong>. Près de <strong>%{count} compte</strong> sera ajouté à de nouvelles listes.
-        other: Vous allez <strong>remplacer vos listes</strong> par le contenu de <strong>%{filename}</strong>. Près de <strong>%{count} comptes</strong> seront ajoutés à de nouvelles listes.
-      muting_html:
-        one: Vous allez <strong>remplacer votre liste de comptes masqués</strong> par près de <strong>%{count} compte</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>remplacer votre liste de comptes masqués</strong> par près de <strong>%{count} comptes</strong> tirés de <strong>%{filename}</strong>.
+      blocking_html: Vous allez <strong>remplacer votre liste de blocages</strong> par jusqu'à <strong>%{total_items} comptes</strong> tirés de <strong>%{filename}</strong>.
+      bookmarks_html: Vous allez <strong>remplacer vos signets</strong> par jusqu'à <strong>%{total_items} posts</strong> tirés de <strong>%{filename}</strong>.
+      domain_blocking_html: Vous allez <strong>remplacer votre liste de blocages de domaines</strong> par jusqu'à <strong>%{total_items} domaines</strong> tirés de <strong>%{filename}</strong>.
+      following_html: Vous allez <strong>suivre</strong> jusqu’à <strong>%{total_items} comptes</strong> depuis <strong>%{filename}</strong> et <strong>arrêter de suivre n’importe qui d’autre</strong>.
+      lists_html: Vous allez <strong>remplacer vos listes</strong> par le contenu de <strong>%{filename}</strong>. Jusqu'à <strong>%{total_items} comptes</strong> seront ajoutés à de nouvelles listes.
+      muting_html: Vous allez <strong>remplacer votre liste de comptes masqués</strong> par jusqu'à <strong>%{total_items} comptes</strong> tirés de <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Vous allez <strong>bloquer</strong> près de <strong>%{count} compte</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>bloquer</strong> près de <strong>%{count} comptes</strong> tirés de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Vous allez ajouter près de <strong>%{count} message</strong> de <strong>%{filename}</strong> à vos <strong>signets</strong>.
-        other: Vous allez ajouter près de <strong>%{count} messages</strong> de <strong>%{filename}</strong> à vos <strong>signets</strong>.
-      domain_blocking_html:
-        one: Vous allez <strong>bloquer</strong> près de <strong>%{count} domaine</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>bloquer</strong> près de <strong>%{count} domaines</strong> tirés de <strong>%{filename}</strong>.
-      following_html:
-        one: Vous allez <strong>suivre</strong> près de <strong>%{count} compte</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>suivre</strong> près de <strong>%{count} comptes</strong> tirés de <strong>%{filename}</strong>.
-      lists_html:
-        one: Vous allez ajouter près de <strong>%{count} compte</strong> depuis <strong>%{filename}</strong> à vos <strong>listes</strong>. De nouvelles listes seront créées si besoin.
-        other: Vous allez ajouter près de <strong>%{count} comptes</strong> depuis <strong>%{filename}</strong> à vos <strong>listes</strong>. De nouvelles listes seront créées si besoin.
-      muting_html:
-        one: Vous allez <strong>masquer</strong> près de <strong>%{count} compte</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>masquer</strong> près de <strong>%{count} comptes</strong> tirés de <strong>%{filename}</strong>.
+      blocking_html: Vous allez <strong>bloquer</strong> jusqu'à <strong>%{total_items} comptes</strong> tirés de <strong>%{filename}</strong>.
+      bookmarks_html: Vous allez ajouter jusqu'à <strong>%{total_items} messages</strong> de <strong>%{filename}</strong> à vos <strong>signets</strong>.
+      domain_blocking_html: Vous allez <strong>bloquer</strong> jusqu'à <strong>%{total_items} domaines</strong> tirés de <strong>%{filename}</strong>.
+      following_html: Vous allez <strong>suivre</strong> jusqu'à <strong>%{total_items} comptes</strong> tirés de <strong>%{filename}</strong>.
+      lists_html: Vous allez ajouter jusqu'à <strong>%{total_items} comptes</strong> depuis <strong>%{filename}</strong> à vos <strong>listes</strong>. De nouvelles listes seront créées s'il n'y a aucune liste à laquelle les ajouter.
+      muting_html: Vous allez <strong>masquer</strong> jusqu'à <strong>%{total_items} comptes</strong> tirés de <strong>%{filename}</strong>.
     preface: Vous pouvez importer certaines données que vous avez exporté d’un autre serveur, comme une liste des personnes que vous suivez ou bloquez sur votre compte.
     recent_imports: Importations récentes
     states:
@@ -1725,7 +1641,7 @@ fr-CA:
   scheduled_statuses:
     over_daily_limit: Vous avez dépassé la limite de %{limit} messages planifiés par jour
     over_total_limit: Vous avez dépassé la limite de %{limit} messages planifiés
-    too_soon: la date doit se situer dans le futur
+    too_soon: La date planifiée doit être dans le futur
   self_destruct:
     lead_html: Malheureusement, <strong>%{domain}</strong> ferme définitivement. Si vous y aviez un compte, vous ne pourrez pas continuer à l’utiliser, mais vous pouvez toujours demander une sauvegarde de vos données.
     title: Ce serveur est en cours de fermeture
@@ -1888,8 +1804,6 @@ fr-CA:
       too_late: Il est trop tard pour faire appel à cette sanction
   tags:
     does_not_match_previous_name: ne correspond pas au nom précédent
-  terms_of_service:
-    title: Conditions d'utilisation
   themes:
     contrast: Mastodon (Contraste élevé)
     default: Mastodon (Sombre)
@@ -1921,10 +1835,6 @@ fr-CA:
     recovery_instructions_html: Si vous perdez l’accès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour retrouver l’accès à votre compte. <strong>Conservez les codes de récupération en sécurité</strong>. Par exemple, en les imprimant et en les stockant avec vos autres documents importants.
     webauthn: Clés de sécurité
   user_mailer:
-    announcement_published:
-      description: 'Les administrateurs de %{domain} font une annonce :'
-      subject: Annonce de service
-      title: Annonce de service de %{domain}
     appeal_approved:
       action: Paramètres du compte
       explanation: L'appel de la sanction contre votre compte mise en place le %{strike_date} que vous avez soumis le %{appeal_date} a été approuvé. Votre compte est de nouveau en règle.
@@ -1954,15 +1864,6 @@ fr-CA:
       further_actions_html: Si ce n’était pas vous, nous vous recommandons de %{action} immédiatement et d’activer l’authentification à deux facteurs afin de garder votre compte sécurisé.
       subject: Votre compte a été accédé à partir d'une nouvelle adresse IP
       title: Une nouvelle connexion
-    terms_of_service_changed:
-      agreement: En continuant d'utiliser %{domain}, vous acceptez ces conditions. Si vous n'êtes pas d'accord avec les conditions mises à jour, vous pouvez résilier votre accord avec %{domain} à tout moment en supprimant votre compte.
-      changelog: 'En un coup d''œil, voici ce que cette mise à jour signifie pour vous :'
-      description: 'Vous recevez cet e-mail parce que nous apportons des modifications à nos conditions de service à %{domain}. Ces modifications entreront en vigueur le %{date}. Nous vous encourageons à consulter l''intégralité des conditions mises à jour ici :'
-      description_html: Vous recevez cet e-mail parce que nous apportons des modifications à nos conditions de service à %{domain}. Ces mises à jour entreront en vigueur le <strong>%{date}</strong>. Nous vous encourageons à consulter l'intégralité des <a href="%{path}" target="_blank">conditions mises à jour ici</a>.
-      sign_off: L'équipe %{domain}
-      subject: Mises à jour de nos conditions d'utilisation
-      subtitle: Les conditions d'utilisation de `%{domain}` changent
-      title: Mise à jour importante
     warning:
       appeal: Faire appel
       appeal_description: Si vous pensez qu'il s'agit d'une erreur, vous pouvez faire appel auprès de l'équipe de %{instance}.
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 25218bd019..c348dc5f02 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -187,7 +187,6 @@ fr:
         create_domain_block: Créer un blocage de domaine
         create_email_domain_block: Création d'un blocage de domaine de courrier électronique
         create_ip_block: Créer une règle IP
-        create_relay: Créer un relais
         create_unavailable_domain: Créer un domaine indisponible
         create_user_role: Créer le rôle
         demote_user: Rétrograder l’utilisateur·ice
@@ -199,22 +198,18 @@ fr:
         destroy_email_domain_block: Supprimer le blocage de domaine de courriel
         destroy_instance: Purge du domaine
         destroy_ip_block: Supprimer la règle IP
-        destroy_relay: Supprimer le relais
         destroy_status: Supprimer le message
         destroy_unavailable_domain: Supprimer le domaine indisponible
         destroy_user_role: Détruire le rôle
         disable_2fa_user: Désactiver l’A2F
         disable_custom_emoji: Désactiver les émojis personnalisés
-        disable_relay: Désactiver le relais
         disable_sign_in_token_auth_user: Désactiver le jeton d'authentification par e-mail pour l'utilisateur
         disable_user: Désactiver le compte
         enable_custom_emoji: Activer les émojis personnalisées
-        enable_relay: Activer le relais
         enable_sign_in_token_auth_user: Activer le jeton d'authentification par e-mail pour l'utilisateur
         enable_user: Activer le compte
         memorialize_account: Ériger en mémorial
         promote_user: Promouvoir le compte
-        publish_terms_of_service: Publier les conditions d'utilisation
         reject_appeal: Rejeter l'appel
         reject_user: Rejeter le compte
         remove_avatar_user: Supprimer l’avatar
@@ -252,7 +247,6 @@ fr:
         create_domain_block_html: "%{name} a bloqué le domaine %{target}"
         create_email_domain_block_html: "%{name} a bloqué le domaine d'e-mail %{target}"
         create_ip_block_html: "%{name} a créé une règle pour l'IP %{target}"
-        create_relay_html: "%{name} a créé un relais %{target}"
         create_unavailable_domain_html: "%{name} a arrêté la livraison vers le domaine %{target}"
         create_user_role_html: "%{name} a créé le rôle %{target}"
         demote_user_html: "%{name} a rétrogradé l'utilisateur·rice %{target}"
@@ -264,22 +258,18 @@ fr:
         destroy_email_domain_block_html: "%{name} a débloqué le domaine d'e-mail %{target}"
         destroy_instance_html: "%{name} a purgé le domaine %{target}"
         destroy_ip_block_html: "%{name} a supprimé la règle pour l'IP %{target}"
-        destroy_relay_html: "%{name} a supprimé le relais %{target}"
         destroy_status_html: "%{name} a supprimé le message de %{target}"
         destroy_unavailable_domain_html: "%{name} a repris la livraison au domaine %{target}"
         destroy_user_role_html: "%{name} a supprimé le rôle %{target}"
         disable_2fa_user_html: "%{name} a désactivé l'authentification à deux facteurs pour l'utilisateur·rice %{target}"
         disable_custom_emoji_html: "%{name} a désactivé l'émoji %{target}"
-        disable_relay_html: "%{name} a désactivé le relais %{target}"
         disable_sign_in_token_auth_user_html: "%{name} a désactivé l'authentification par jeton de courriel pour %{target}"
         disable_user_html: "%{name} a désactivé la connexion de l'utilisateur·rice %{target}"
         enable_custom_emoji_html: "%{name} a activé l'émoji %{target}"
-        enable_relay_html: "%{name} a activé le relais %{target}"
         enable_sign_in_token_auth_user_html: "%{name} a activé l'authentification par jeton de courriel pour %{target}"
         enable_user_html: "%{name} a activé la connexion de l'utilisateur·rice %{target}"
         memorialize_account_html: "%{name} a converti le compte de %{target} en un mémorial"
         promote_user_html: "%{name} a promu l'utilisateur·rice %{target}"
-        publish_terms_of_service_html: "%{name} a publié des mises à jour des conditions d'utilisation"
         reject_appeal_html: "%{name} a rejeté l'appel de la décision de modération émis par %{target}"
         reject_user_html: "%{name} a rejeté l’inscription de %{target}"
         remove_avatar_user_html: "%{name} a supprimé l'avatar de %{target}"
@@ -309,7 +299,6 @@ fr:
       title: Journal d’audit
       unavailable_instance: "(nom de domaine indisponible)"
     announcements:
-      back: Retour aux annonces
       destroyed_msg: Annonce supprimée avec succès !
       edit:
         title: Modifier l’annonce
@@ -318,9 +307,6 @@ fr:
       new:
         create: Créer une annonce
         title: Nouvelle annonce
-      preview:
-        explanation_html: 'L''e-mail sera envoyé à <strong>%{display_count} utilisateurs</strong>. Le texte suivant sera inclus :'
-        title: Aperçu de la notification d'annonce
       publish: Publier
       published_msg: Annonce publiée avec succès !
       scheduled_for: Planifiée pour %{time}
@@ -482,15 +468,6 @@ fr:
       new:
         title: Importer des blocages de domaine
       no_file: Aucun fichier sélectionné
-    fasp:
-      providers:
-        registrations:
-          confirm: Confirmer
-          reject: Rejeter
-        save: Enregistrer
-        select_capabilities: Sélectionnez les Capacités
-        sign_in: Se connecter
-        status: État
     follow_recommendations:
       description_html: "<strong>Les recommandations d'abonnement aident les nouvelles personnes à trouver rapidement du contenu intéressant</strong>. Si un·e utilisateur·rice n'a pas assez interagi avec les autres pour avoir des recommandations personnalisées, ces comptes sont alors recommandés. La sélection est mise à jour quotidiennement depuis un mélange de comptes ayant le plus d'interactions récentes et le plus grand nombre d'abonné·e·s locaux pour une langue donnée."
       language: Pour la langue
@@ -844,10 +821,8 @@ fr:
       back_to_account: Retour à la page du compte
       back_to_report: Retour à la page du rapport
       batch:
-        add_to_report: 'Ajouter au rapport #%{id}'
         remove_from_report: Retirer du rapport
         report: Signalement
-      contents: Contenu
       deleted: Supprimé
       favourites: Favoris
       history: Historique de version
@@ -856,17 +831,13 @@ fr:
       media:
         title: Médias
       metadata: Metadonnés
-      no_history: Ce message n'a pas été édité
       no_status_selected: Aucun message n’a été modifié car aucun n’a été sélectionné
       open: Ouvrir le message
       original_status: Message original
       reblogs: Partages
-      replied_to_html: Répondu à %{acct_link}
       status_changed: Message modifié
-      status_title: Posté par @%{name}
-      title: Messages du compte - @%{name}
+      title: Messages du compte
       trending: Tendances
-      view_publicly: Afficher de manière publique
       visibility: Visibilité
       with_media: Avec médias
     strikes:
@@ -943,36 +914,6 @@ fr:
       search: Recherche
       title: Hashtags
       updated_msg: Paramètres du hashtag mis à jour avec succès
-    terms_of_service:
-      back: Retour aux conditions d'utilisation
-      changelog: Nouveautés
-      create: Utilisez vos propres
-      current: Courant
-      draft: Brouillon
-      generate: Utiliser un modèle
-      generates:
-        action: Générer
-        chance_to_review_html: "<strong>Les conditions d'utilisation générées ne seront pas publiées automatiquement.</strong> Vous aurez la possibilité de vérifier les résultats. Veuillez remplir les informations nécessaires pour continuer."
-        explanation_html: Le modèle de conditions d'utilisation fourni l'est uniquement à titre informatif et ne doit pas être interprété comme un conseil juridique sur quelque sujet que ce soit. Veuillez consulter votre propre conseiller juridique sur votre situation et les questions juridiques spécifiques que vous vous posez.
-        title: Configuration des Conditions d'Utilisation
-      going_live_on_html: En direct, à compter du %{date}
-      history: Historique
-      live: En cours d'utilisation
-      no_history: Il n'y a pas encore de modifications enregistrées des conditions d'utilisation.
-      no_terms_of_service_html: Vous n'avez actuellement aucune condition d'utilisation configurée. Les conditions d'utilisation ont pour but de clarifier les droits et obligations de chacun lors de l'utilisation du service et de vous protéger contre d'éventuelles responsabilités en cas de litige avec vos utilisateurs.
-      notified_on_html: Utilisateurs notifiés le `%{date}`
-      notify_users: Notifier les utilisateurs
-      preview:
-        explanation_html: 'L''e-mail sera envoyé aux utilisateurs <strong>%{display_count}</strong> qui se sont inscrits avant %{date}. Le texte suivant sera inclus dans l''e-mail :'
-        send_preview: Envoyer un aperçu à %{email}
-        send_to_all:
-          one: Envoyer %{display_count} email
-          other: Envoyer %{display_count} emails
-        title: Notification concernant l'aperçu des conditions d'utilisation
-      publish: Publier
-      published_on_html: Publié le %{date}
-      save_draft: Enregistrer le brouillon
-      title: Conditions d'utilisation
     title: Administration
     trends:
       allow: Autoriser
@@ -1180,6 +1121,7 @@ fr:
     migrate_account: Déménager vers un compte différent
     migrate_account_html: Si vous voulez rediriger ce compte vers un autre, vous pouvez le <a href="%{path}">configurer ici</a>.
     or_log_in_with: Ou authentifiez-vous avec
+    privacy_policy_agreement_html: J’ai lu et j’accepte la <a href="%{privacy_policy_path}" target="_blank">politique de confidentialité</a>
     progress:
       confirm: Confirmation de l'adresse mail
       details: Vos infos
@@ -1204,7 +1146,7 @@ fr:
     set_new_password: Définir le nouveau mot de passe
     setup:
       email_below_hint_html: Consultez votre dossier de courrier indésirable ou demandez-en un autre. Vous pouvez corriger votre adresse e-mail si elle est incorrecte.
-      email_settings_hint_html: Cliquez sur le lien que nous avons envoyé à %{email} pour commencer à utiliser Mastodon. Nous vous attendrons ici.
+      email_settings_hint_html: Cliquez sur le lien que nous vous avons envoyé pour vérifier l’adresse %{email}. Nous vous attendons ici.
       link_not_received: Vous n'avez pas reçu de lien ?
       new_confirmation_instructions_sent: Vous allez recevoir un nouvel e-mail avec le lien de confirmation dans quelques minutes !
       title: Vérifiez votre boîte de réception
@@ -1213,7 +1155,7 @@ fr:
       title: Se connecter à %{domain}
     sign_up:
       manual_review: Les inscriptions sur %{domain} passent par une revue manuelle de nos modérateurs. Pour les aider, écrivez un peu plus sur vous et pourquoi vous souhaitez créer un compte sur %{domain}.
-      preamble: Avec un compte sur ce serveur Mastodon, vous pourrez suivre n'importe quelle autre personne du fediverse, quel que soit l'endroit où son compte est hébergé.
+      preamble: Avec un compte sur ce serveur Mastodon, vous serez en mesure de suivre toute autre personne sur le réseau, quel que soit l’endroit où son compte est hébergé.
       title: Mettons les choses en place pour %{domain}.
     status:
       account_status: État du compte
@@ -1225,8 +1167,6 @@ fr:
       view_strikes: Voir les sanctions précédemment appliquées à votre compte
     too_fast: Formulaire envoyé trop rapidement, veuillez réessayer.
     use_security_key: Utiliser la clé de sécurité
-    user_agreement_html: J'ai lu et j'accepte les <a href="%{terms_of_service_path}" target="_blank">conditions d'utilisation</a> et la <a href="%{privacy_policy_path}" target="_blank">politique de confidentialité</a>
-    user_privacy_agreement_html: J’ai lu et j’accepte la <a href="%{privacy_policy_path}" target="_blank">politique de confidentialité</a>
   author_attribution:
     example_title: Exemple de texte
     hint_html: Vous écrivez des nouvelles ou des articles de blog en dehors de Mastodon ? Contrôlez la façon dont vous êtes crédité lorsqu'ils sont partagés sur Mastodon.
@@ -1432,43 +1372,19 @@ fr:
       overwrite: Écraser
       overwrite_long: Remplacer les enregistrements actuels par les nouveaux
     overwrite_preambles:
-      blocking_html:
-        one: Vous allez <strong>remplacer votre liste de blocage</strong> par près de <strong>%{count} compte</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>remplacer votre liste de blocage</strong> par près de <strong>%{count} comptes</strong> tirés de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Vous allez <strong>remplacer vos signets</strong> par près de <strong>%{count} post</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>remplacer vos signets</strong> par près de <strong>%{count} posts</strong> tirés de <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Vous allez <strong>remplacer votre liste de blocage de domaines</strong> par près de <strong>%{count} domaine</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>remplacer votre liste de blocage de domaines</strong> par près de <strong>%{count} domaines</strong> tirés de <strong>%{filename}</strong>.
-      following_html:
-        one: Vous allez <strong>suivre</strong> jusqu’à <strong>%{count} compte</strong> depuis <strong>%{filename}</strong> et <strong>arrêter de suivre n’importe qui d’autre</strong>.
-        other: Vous allez <strong>suivre</strong> jusqu’à <strong>%{count} comptes</strong> depuis <strong>%{filename}</strong> et <strong>arrêter de suivre n’importe qui d’autre</strong>.
-      lists_html:
-        one: Vous allez <strong>remplacer vos listes</strong> par le contenu de <strong>%{filename}</strong>. Près de <strong>%{count} compte</strong> sera ajouté à de nouvelles listes.
-        other: Vous allez <strong>remplacer vos listes</strong> par le contenu de <strong>%{filename}</strong>. Près de <strong>%{count} comptes</strong> seront ajoutés à de nouvelles listes.
-      muting_html:
-        one: Vous allez <strong>remplacer votre liste de comptes masqués</strong> par près de <strong>%{count} compte</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>remplacer votre liste de comptes masqués</strong> par près de <strong>%{count} comptes</strong> tirés de <strong>%{filename}</strong>.
+      blocking_html: Vous allez <strong>remplacer votre liste de blocage</strong> par près de <strong>%{total_items} comptes</strong> tirés de <strong>%{filename}</strong>.
+      bookmarks_html: Vous allez <strong>remplacer vos signets</strong> par près de <strong>%{total_items} posts</strong> tirés de <strong>%{filename}</strong>.
+      domain_blocking_html: Vous allez <strong>remplacer votre liste de blocage de domaines</strong> par près de <strong>%{total_items} domaines</strong> tirés de <strong>%{filename}</strong>.
+      following_html: Vous allez <strong>suivre</strong> jusqu’à <strong>%{total_items} comptes</strong> depuis <strong>%{filename}</strong> et <strong>arrêter de suivre n’importe qui d’autre</strong>.
+      lists_html: Vous allez <strong>remplacer vos listes</strong> par le contenu de <strong>%{filename}</strong>. Près de <strong>%{total_items} comptes</strong> seront ajoutés à de nouvelles listes.
+      muting_html: Vous allez <strong>remplacer votre liste de comptes masqués</strong> par près de <strong>%{total_items} comptes</strong> tirés de <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Vous allez <strong>bloquer</strong> près de <strong>%{count} compte</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>bloquer</strong> près de <strong>%{count} comptes</strong> tirés de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Vous allez ajouter près de <strong>%{count} message</strong> de <strong>%{filename}</strong> à vos <strong>signets</strong>.
-        other: Vous allez ajouter près de <strong>%{count} messages</strong> de <strong>%{filename}</strong> à vos <strong>signets</strong>.
-      domain_blocking_html:
-        one: Vous allez <strong>bloquer</strong> près de <strong>%{count} domaine</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>bloquer</strong> près de <strong>%{count} domaines</strong> tirés de <strong>%{filename}</strong>.
-      following_html:
-        one: Vous allez <strong>suivre</strong> près de <strong>%{count} compte</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>suivre</strong> près de <strong>%{count} comptes</strong> tirés de <strong>%{filename}</strong>.
-      lists_html:
-        one: Vous allez ajouter près de <strong>%{count} compte</strong> depuis <strong>%{filename}</strong> à vos <strong>listes</strong>. De nouvelles listes seront créées si besoin.
-        other: Vous allez ajouter près de <strong>%{count} comptes</strong> depuis <strong>%{filename}</strong> à vos <strong>listes</strong>. De nouvelles listes seront créées si besoin.
-      muting_html:
-        one: Vous allez <strong>masquer</strong> près de <strong>%{count} compte</strong> tiré de <strong>%{filename}</strong>.
-        other: Vous allez <strong>masquer</strong> près de <strong>%{count} comptes</strong> tirés de <strong>%{filename}</strong>.
+      blocking_html: Vous allez <strong>bloquer</strong> près de <strong>%{total_items} comptes</strong> tirés de <strong>%{filename}</strong>.
+      bookmarks_html: Vous allez ajouter près de <strong>%{total_items} messages</strong> de <strong>%{filename}</strong> à vos <strong>signets</strong>.
+      domain_blocking_html: Vous allez <strong>bloquer</strong> près de <strong>%{total_items} domaines</strong> tirés de <strong>%{filename}</strong>.
+      following_html: Vous allez <strong>suivre</strong> près de <strong>%{total_items} comptes</strong> tirés de <strong>%{filename}</strong>.
+      lists_html: Vous allez ajouter près de <strong>%{total_items} comptes</strong> depuis <strong>%{filename}</strong> à vos <strong>listes</strong>. De nouvelles listes seront créées si besoin.
+      muting_html: Vous allez <strong>masquer</strong> près de <strong>%{total_items} comptes</strong> tirés de <strong>%{filename}</strong>.
     preface: Vous pouvez importer certaines données que vous avez exporté d’un autre serveur, comme une liste des personnes que vous suivez ou bloquez sur votre compte.
     recent_imports: Récents imports
     states:
@@ -1725,7 +1641,7 @@ fr:
   scheduled_statuses:
     over_daily_limit: Vous avez dépassé la limite de %{limit} messages planifiés par jour
     over_total_limit: Vous avez dépassé la limite de %{limit} messages planifiés
-    too_soon: la date doit se situer dans le futur
+    too_soon: La date planifiée doit être dans le futur
   self_destruct:
     lead_html: Malheureusement, <strong>%{domain}</strong> ferme définitivement. Si vous y aviez un compte, vous ne pourrez pas continuer à l’utiliser, mais vous pouvez toujours demander une sauvegarde de vos données.
     title: Ce serveur est en cours de fermeture
@@ -1888,8 +1804,6 @@ fr:
       too_late: Il est trop tard pour faire appel à cette sanction
   tags:
     does_not_match_previous_name: ne correspond pas au nom précédent
-  terms_of_service:
-    title: Conditions d'utilisation
   themes:
     contrast: Mastodon (Contraste élevé)
     default: Mastodon (Sombre)
@@ -1921,10 +1835,6 @@ fr:
     recovery_instructions_html: Si vous perdez l’accès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour retrouver l’accès à votre compte. <strong>Conservez les codes de récupération en sécurité</strong>. Par exemple, en les imprimant et en les stockant avec vos autres documents importants.
     webauthn: Clés de sécurité
   user_mailer:
-    announcement_published:
-      description: 'Les administrateurs de %{domain} font une annonce :'
-      subject: Annonce de service
-      title: Annonce de service de %{domain}
     appeal_approved:
       action: Paramètres du compte
       explanation: L'appel de la sanction contre votre compte mise en place le %{strike_date} que vous avez soumis le %{appeal_date} a été approuvé. Votre compte est de nouveau en règle.
@@ -1954,15 +1864,6 @@ fr:
       further_actions_html: Si ce n’était pas vous, nous vous recommandons de %{action} immédiatement et d’activer l’authentification à deux facteurs afin de garder votre compte sécurisé.
       subject: Une nouvelle adresse IP a accédé à votre compte
       title: Une nouvelle connexion
-    terms_of_service_changed:
-      agreement: En continuant d'utiliser %{domain}, vous acceptez ces conditions. Si vous n'êtes pas d'accord avec les conditions mises à jour, vous pouvez résilier votre accord avec %{domain} à tout moment en supprimant votre compte.
-      changelog: 'En un coup d''œil, voici ce que cette mise à jour signifie pour vous :'
-      description: 'Vous recevez cet e-mail parce que nous apportons des modifications à nos conditions de service à %{domain}. Ces modifications entreront en vigueur le %{date}. Nous vous encourageons à consulter l''intégralité des conditions mises à jour ici :'
-      description_html: Vous recevez cet e-mail parce que nous apportons des modifications à nos conditions de service à %{domain}. Ces mises à jour entreront en vigueur le <strong>%{date}</strong>. Nous vous encourageons à consulter l'intégralité des <a href="%{path}" target="_blank">conditions mises à jour ici</a>.
-      sign_off: L'équipe %{domain}
-      subject: Mises à jour de nos conditions d'utilisation
-      subtitle: Les conditions d'utilisation de `%{domain}` changent
-      title: Mise à jour importante
     warning:
       appeal: Faire appel
       appeal_description: Si vous pensez qu'il s'agit d'une erreur, vous pouvez faire appel auprès de l'équipe de %{instance}.
diff --git a/config/locales/fy.yml b/config/locales/fy.yml
index 119d08be2d..a3c5aef606 100644
--- a/config/locales/fy.yml
+++ b/config/locales/fy.yml
@@ -187,7 +187,6 @@ fy:
         create_domain_block: Domeinblokkade oanmeitsje
         create_email_domain_block: E-maildomeinblokkade oanmeitsje
         create_ip_block: IP-rigel oanmeitsje
-        create_relay: Relay oanmeitsje
         create_unavailable_domain: Net beskikber domein oanmeitsje
         create_user_role: Rol oanmeitsje
         demote_user: Brûker degradearje
@@ -199,22 +198,18 @@ fy:
         destroy_email_domain_block: E-maildomeinblokkade fuortsmite
         destroy_instance: Domein folslein fuortsmite
         destroy_ip_block: IP-rigel fuortsmite
-        destroy_relay: Relay fuortsmite
         destroy_status: Toot fuortsmite
         destroy_unavailable_domain: Net beskikber domein fuortsmite
         destroy_user_role: Rol permanint fuortsmite
         disable_2fa_user: Twa-stapsferifikaasje útskeakelje
         disable_custom_emoji: Lokale emoji útskeakelje
-        disable_relay: Relay útskeakelje
         disable_sign_in_token_auth_user: Ferifikaasje mei in tagongskoade fia e-mail foar de brûker útskeakelje
         disable_user: Brûker útskeakelje
         enable_custom_emoji: Lokale emoji ynskeakelje
-        enable_relay: Relay ynskeakelje
         enable_sign_in_token_auth_user: Ferifikaasje mei in tagongskoade fia e-mail foar de brûker ynskeakelje
         enable_user: Brûker ynskeakelje
         memorialize_account: De account yn in Yn memoriam wizigje
         promote_user: Brûker promovearje
-        publish_terms_of_service: Algemiene gebrûksbetingsten publisearje
         reject_appeal: Beswier ôfwize
         reject_user: Brûker ôfwize
         remove_avatar_user: Profylfoto fuortsmite
@@ -252,7 +247,6 @@ fy:
         create_domain_block_html: Domein %{target} is troch %{name} blokkearre
         create_email_domain_block_html: "%{name} hat it e-maildomein %{target} blokkearre"
         create_ip_block_html: "%{name} hat de rigel foar IP %{target} oanmakke"
-        create_relay_html: "%{name} hat in relay oanmakke %{target}"
         create_unavailable_domain_html: "%{name} hat de besoarging foar domein %{target} beëinige"
         create_user_role_html: "%{name} hat de rol %{target} oanmakke"
         demote_user_html: Brûker %{target} is troch %{name} degradearre
@@ -264,22 +258,18 @@ fy:
         destroy_email_domain_block_html: "%{name} hat it e-maildomein %{target} deblokkearre"
         destroy_instance_html: "%{name} hat it domein %{target} folslein fuortsmiten"
         destroy_ip_block_html: "%{name} hat de rigel foar IP %{target} fuortsmiten"
-        destroy_relay_html: "%{name} hat de relay %{target} fuortsmiten"
         destroy_status_html: Berjocht fan %{target} is troch %{name} fuortsmiten
         destroy_unavailable_domain_html: "%{name} hat de besoarging foar domein %{target} opnij starte"
         destroy_user_role_html: "%{name} hat de rol %{target} fuortsmiten"
         disable_2fa_user_html: De fereaske twa-stapsferifikaasje foar %{target} is troch %{name} útskeakele
         disable_custom_emoji_html: Emoji %{target} is troch %{name} útskeakele
-        disable_relay_html: "%{name} hat de relay %{target} útskeakele"
         disable_sign_in_token_auth_user_html: "%{name} hat ferifikaasje mei in tagongskoade fia e-mail útskeakele foar %{target}"
         disable_user_html: Oanmelden foar %{target} is troch %{name} útskeakele
         enable_custom_emoji_html: Emoji %{target} is troch %{name} ynskeakele
-        enable_relay_html: "%{name} hat de relay %{target} ynskeakele"
         enable_sign_in_token_auth_user_html: "%{name} hat ferifikaasje mei in tagongskoade fia e-mail ynskeakele foar %{target}"
         enable_user_html: Oanmelden foar %{target} is troch %{name} ynskeakele
         memorialize_account_html: De account %{target} is troch %{name} yn in Yn memoriam wizige
         promote_user_html: Brûker %{target} is troch %{name} promovearre
-        publish_terms_of_service_html: "%{name} publisearre updates fan de tsjinstbetingsten"
         reject_appeal_html: "%{name} hat it beswier tsjin de moderaasjemaatregel fan %{target} ôfwêzen"
         reject_user_html: "%{name} hat de registraasje fan %{target} ôfwêzen"
         remove_avatar_user_html: "%{name} hat de profylfoto fan %{target} fuortsmiten"
@@ -828,10 +818,8 @@ fy:
       back_to_account: Tebek nei accountside
       back_to_report: Tebek nei de rapportaazje
       batch:
-        add_to_report: 'Oan rapport #%{id} tafoegje'
         remove_from_report: Ut rapportaazje fuortsmite
         report: Rapportaazje
-      contents: Ynhâld
       deleted: Fuortsmiten
       favourites: Favoriten
       history: Ferzjeskiednis
@@ -840,17 +828,13 @@ fy:
       media:
         title: Media
       metadata: Metagegevens
-      no_history: Dit berjocht is net bewurke
       no_status_selected: Der binne gjin berjochten wizige, omdat der gjin ien selektearre waard
       open: Berjocht toane
       original_status: Oarspronklik berjocht
       reblogs: Boosts
-      replied_to_html: Antwurde op %{acct_link}
       status_changed: Berjocht wizige
-      status_title: Berjocht fan @%{name}
-      title: Accountberjochten - @%{name}
+      title: Accountberjochten
       trending: Trending
-      view_publicly: Yn it iepenbier besjen
       visibility: Sichtberheid
       with_media: Mei media
     strikes:
@@ -927,35 +911,6 @@ fy:
       search: Sykje
       title: Hashtags
       updated_msg: Hashtagynstellingen mei sukses bywurke
-    terms_of_service:
-      back: Tebek nei de gebrûksbetingsten
-      changelog: Wat is wizige
-      create: Brûk jo eigen
-      current: Aktuele
-      draft: Konsept
-      generate: Sjabloan brûke
-      generates:
-        action: Generearje
-        chance_to_review_html: "<strong>De generearre gebrûksbetingsten wurde net automatysk publisearre.</strong> Jo krije gelegenheid om de resultaten te besjen. Folje de nedige gegevens yn om troch te gean."
-        explanation_html: It sjabloan foar de gebrûksbetingsten is útslutend bedoeld foar ynformative doeleinen en mei net opfette wurde as juridysk advys oer hokker ûnderwerp dan ek. Freegje in eigen juridysk adviseur oer jo situaasje en foar spesifike juridyske fragen.
-        title: Gebrûksbetingsten ynstelle
-      history: Skiednis
-      live: Aktueel
-      no_history: Der binne noch gjin bewarre wizigingen fan de gebrûksbetingsten.
-      no_terms_of_service_html: Jo hawwe op dit stuit gjin servicebetingsten konfigurearre. De gebrûksbetingsten binne bedoeld om dúdlikheid te ferskaffen en jo te beskermjen tsjin mooglike ferplichtingen yn konflikten mei brûkers.
-      notified_on_html: Brûkers ynljochte op %{date}
-      notify_users: Brûkers ynformearje
-      preview:
-        explanation_html: 'It e-mailberjocht wurdt ferstjoerd nei <strong>%{display_count} brûkers</strong> dy’t harren oanmeld hawwe foar %{date}. De folgjende tekst sil yn it e-mailberjocht opnommen wurde:'
-        send_preview: Foarbyld ferstjoere nei %{email}
-        send_to_all:
-          one: "%{display_count} e-mailberjocht ferstjoere"
-          other: "%{display_count} e-mailberjochten ferstjoere"
-        title: Foarbyld fan Melding gebrûksbetingsten
-      publish: Publisearje
-      published_on_html: Publisearre op %{date}
-      save_draft: Konsept bewarje
-      title: Gebrûksbetingsten
     title: Behear
     trends:
       allow: Tastean
@@ -1163,6 +1118,7 @@ fy:
     migrate_account: Nei in oar account ferhúzje
     migrate_account_html: Wannear’t jo dizze account nei in oare account trochferwize wolle, kinne jo <a href="%{path}">dit hjir ynstelle</a>.
     or_log_in_with: Of oanmelde mei
+    privacy_policy_agreement_html: Ik haw it <a href="%{privacy_policy_path}" target="_blank">privacybelied</a> lêzen en gean dêrmei akkoard
     progress:
       confirm: E-mailadres werhelje
       details: Jo gegevens
@@ -1187,7 +1143,7 @@ fy:
     set_new_password: Nij wachtwurd ynstelle
     setup:
       email_below_hint_html: Kontrolearje jo map Net-winske, of freegje in nije befêstigingskeppeling oan. Jo kinne jo e-mailadres wizigje as it ferkeard is.
-      email_settings_hint_html: Klik op de keppeling dy’t wy nei %{email} stjoerd hawwe om Mastodon te brûken. Wy wachtsje hjir ôf.
+      email_settings_hint_html: Klik op de keppeling dy’t wy jo stjoerd hawwe om %{email} te ferifiearjen. Wy wachtsje wol even.
       link_not_received: Gjin keppeling krigen?
       new_confirmation_instructions_sent: Jo ûntfange binnen inkelde minuten in nij e-mailberjocht mei de befêstigingskeppeling!
       title: Kontrolearje jo Postfek YN
@@ -1196,7 +1152,7 @@ fy:
       title: Oanmelde op %{domain}
     sign_up:
       manual_review: Ynskriuwingen op %{domain} wurde hânmjittich troch de moderator beoardiele. Skriuw wat oer josels en wêrom jo in account wolle op %{domain} om ús te helpen jo registraasje te ferwurkjen.
-      preamble: Mei in account op dizze Mastodon-server kinne jo elkenien folgje op de fediverse, nettsjinsteande wêr’t dizze persoan in account hat.
+      preamble: Jo kinne mei in Mastodon-account elkenien yn it netwurk folgen, wêr’t dizze persoan ek in account hat.
       title: Litte wy jo account op %{domain} ynstelle.
     status:
       account_status: Accountsteat
@@ -1208,8 +1164,6 @@ fy:
       view_strikes: Besjoch de earder troch moderatoaren fêststelde skeiningen dy’t jo makke hawwe
     too_fast: Formulier is te fluch yntsjinne. Probearje it nochris.
     use_security_key: Befeiligingskaai brûke
-    user_agreement_html: Ik haw de <a href="%{terms_of_service_path}" target="_blank">gebrûksbetingsten</a> en it <a href="%{privacy_policy_path}" target="_blank">privacybelied</a> lêzen en gean der mei akkoard
-    user_privacy_agreement_html: Ik haw it <a href="%{privacy_policy_path}" target="_blank">privacybelied</a> lêzen en gean dêrmei akkoard
   author_attribution:
     example_title: Faorbyldtekst
     hint_html: Skriuwe jo nijs- of blogartikelen bûten Mastodon? Bepaal hoe’t jo oahelle wurde as dizze dield wurde op Mastodon.
@@ -1415,43 +1369,19 @@ fy:
       overwrite: Oerskriuwe
       overwrite_long: Aktuele gegevens mei de nije gegevens ferfange
     overwrite_preambles:
-      blocking_html:
-        one: Jo steane op it punt om <strong>jo blokkearlist</strong> mei mear as <strong>%{count} account</strong> fan <strong>%{filename}</strong> te ferfangen.
-        other: Jo steane op it punt om <strong>jo blokkearlist</strong> mei mear as <strong>%{count} accounts</strong> fan <strong>%{filename}</strong> te ferfangen.
-      bookmarks_html:
-        one: Jo steane op it punt om <strong>jo blêdwizers</strong> mei mear as <strong>%{count} artikel</strong> fan <strong>%{filename}</strong> te ferfangen.
-        other: Jo steane op it punt om <strong>jo blêdwizers</strong> mei mear as <strong>%{count} artikelen</strong> fan <strong>%{filename}</strong> te ferfangen.
-      domain_blocking_html:
-        one: Jo steane op it punt om <strong>jo domeinblokkearlist</strong> mei mear as <strong>%{count} domein</strong> fan <strong>%{filename}</strong> te ferfangen.
-        other: Jo steane op it punt om <strong>jo domeinblokkearlist</strong> mei mear as <strong>%{count} domeinen</strong> fan <strong>%{filename}</strong> te ferfangen.
-      following_html:
-        one: Jo steane op it punt om <strong>%{count} account</strong> út <strong>%{filename}</strong> <strong>te folgjen</strong> en <strong>te stopjen mei folgjen fan alle oaren</strong>.
-        other: Jo steane op it punt om <strong>%{count} accounts</strong> út <strong>%{filename}</strong> <strong>te folgjen</strong> en <strong>te stopjen mei folgjen fan alle oaren</strong>.
-      lists_html:
-        one: Jo steane op it punt <strong>jo listen</strong> te ferfangen troch ynhâld fan <strong>%{filename}</strong>. Oant <strong>%{count} account</strong> sille oan nije listen tafoege wurde.
-        other: Jo steane op it punt <strong>jo listen</strong> te ferfangen troch ynhâld fan <strong>%{filename}</strong>. Oant <strong>%{count} accounts</strong> sille oan nije listen tafoege wurde.
-      muting_html:
-        one: Jo steane op it punt om <strong>jo list mei negearre accounts</strong> mei mear as <strong>%{count} account</strong> fan <strong>%{filename}</strong> út te ferfangen.
-        other: Jo steane op it punt om <strong>jo list mei negearre accounts</strong> mei mear as <strong>%{count} accounts</strong> fan <strong>%{filename}</strong> út te ferfangen.
+      blocking_html: Jo steane op it punt om <strong>jo blokkearlist</strong> mei mear as <strong>%{total_items} accounts</strong> fan <strong>%{filename}</strong> te ferfangen.
+      bookmarks_html: Jo steane op it punt om <strong>jo blêdwizers</strong> mei mear as <strong>%{total_items} artikelen</strong> fan <strong>%{filename}</strong> te ferfangen.
+      domain_blocking_html: Jo steane op it punt om <strong>jo domeinblokkearlist</strong> mei mear as <strong>%{total_items} domeinen</strong> fan <strong>%{filename}</strong> te ferfangen.
+      following_html: Jo steane op it punt om <strong>%{total_items} accounts</strong> út <strong>%{filename}</strong> <strong>te folgjen</strong> en <strong>te stopjen mei folgjen fan alle oaren</strong>.
+      lists_html: Jo steane op it punt <strong>jo listen</strong> te ferfangen troch ynhâld fan <strong>%{filename}</strong>. Oant <strong>%{total_items} accounts</strong> sille oan nije listen tafoege wurde.
+      muting_html: Jo steane op it punt om <strong>jo list mei negearre accounts</strong> mei mear as <strong>%{total_items} accounts</strong> fan <strong>%{filename}</strong> út te ferfangen.
     preambles:
-      blocking_html:
-        one: Jo steane op it punt om <strong>%{count} account</strong> fan <strong>%{filename}</strong> út te <strong>blokkearjen</strong>.
-        other: Jo steane op it punt om <strong>%{count} accounts</strong> fan <strong>%{filename}</strong> út te <strong>blokkearjen</strong>.
-      bookmarks_html:
-        one: Jo steane op it punt om <strong>%{count} berjocht</strong> fan <strong>%{filename}</strong> út oan jo <strong>blêdwizers</strong> ta te foegjen.
-        other: Jo steane op it punt om <strong>%{count} berjochten</strong> fan <strong>%{filename}</strong> út oan jo <strong>blêdwizers</strong> ta te foegjen.
-      domain_blocking_html:
-        one: Jo steane op it punt om <strong>%{count} domein</strong> fan <strong>%{filename}</strong> út te <strong>blokkearjen</strong>.
-        other: Jo steane op it punt om <strong>%{count} domeinen</strong> fan <strong>%{filename}</strong> út te <strong>blokkearjen</strong>.
-      following_html:
-        one: Jo steane op it punt om <strong>%{count} account</strong> fan <strong>%{filename}</strong> út te <strong>folgjen</strong>.
-        other: Jo steane op it punt om <strong>%{count} accounts</strong> fan <strong>%{filename}</strong> út te <strong>folgjen</strong>.
-      lists_html:
-        one: Jo steane op it punt om oant <strong>%{count} account</strong> fan <strong>%{filename}</strong> ta te foegjen oan jo <strong>listen</strong>. Nije listen wurde oanmakke as der gjin list is om oan ta te foegjen.
-        other: Jo steane op it punt om oant <strong>%{count} accounts</strong> fan <strong>%{filename}</strong> ta te foegjen oan jo <strong>listen</strong>. Nije listen wurde oanmakke as der gjin list is om oan ta te foegjen.
-      muting_html:
-        one: Jo steane op it punt om <strong>%{count} account</strong> fan <strong>%{filename}</strong> út te <strong>negearjen</strong>.
-        other: Jo steane op it punt om <strong>%{count} accounts</strong> fan <strong>%{filename}</strong> út te <strong>negearjen</strong>.
+      blocking_html: Jo steane op it punt om <strong>%{total_items} accounts</strong> fan <strong>%{filename}</strong> út te <strong>blokkearjen</strong>.
+      bookmarks_html: Jo steane op it punt om <strong>%{total_items} berjochten</strong> fan <strong>%{filename}</strong> út oan jo <strong>blêdwizers</strong> ta te foegjen.
+      domain_blocking_html: Jo steane op it punt om <strong>%{total_items} domeinen</strong> fan <strong>%{filename}</strong> út te <strong>blokkearjen</strong>.
+      following_html: Jo steane op it punt om <strong>%{total_items} accounts</strong> fan <strong>%{filename}</strong> út te <strong>folgjen</strong>.
+      lists_html: Jo steane op it punt om oant <strong>%{total_items} accounts</strong> fan <strong>%{filename}</strong> ta te foegjen oan jo <strong>listen</strong>. Nije listen wurde oanmakke as der gjin list is om oan ta te foegjen.
+      muting_html: Jo steane op it punt om <strong>%{total_items} accounts</strong> fan <strong>%{filename}</strong> út te <strong>negearjen</strong>.
     preface: Jo kinne bepaalde gegevens, lykas de minsken dy’t jo folgje of blokkearre hawwe, nei jo account op dizze server ymportearje. Jo moatte dizze gegevens wol earst op de oarspronklike server eksportearje.
     recent_imports: Resinte ymports
     states:
@@ -1708,7 +1638,7 @@ fy:
   scheduled_statuses:
     over_daily_limit: Jo binne oer de limyt fan %{limit} yn te plannen berjochten foar hjoed
     over_total_limit: Jo binne oer de limyt fan %{limit} yn te plannen berjochten
-    too_soon: datum moat yn de takomst lizze
+    too_soon: De datum foar it ynplande berjocht moat yn de takomst lizze
   self_destruct:
     lead_html: Spitigernôch sil <strong>%{domain}</strong> permanint ôfslute. As jo dêr in account hiene, kinne jo dizze net mear brûke, mar jo kinne noch hieltyd in reservekopy fan jo gegevens opfreegje.
     title: Deze server sil ôfslute
@@ -1871,8 +1801,6 @@ fy:
       too_late: De perioade dat jo beswier meitsje kinne tsjin dizze skeining is ferrûn
   tags:
     does_not_match_previous_name: komt net oerien mei de foarige namme
-  terms_of_service:
-    title: Gebrûksbetingsten
   themes:
     contrast: Mastodon (heech kontrast)
     default: Mastodon (donker)
@@ -1933,13 +1861,6 @@ fy:
       further_actions_html: Wannear’t jo dit net wiene, advisearje wy om daliks %{action} en om twa-stapsferifikaasje yn te skeakeljen, om sa jo account feilich te hâlden.
       subject: Jo account is fan in nij IP-adres ôf benadere
       title: In nije registraasje
-    terms_of_service_changed:
-      agreement: Troch %{domain} brûke te bliuwen, geane jo akkoard mei dizze betingsten. As jo it net iens binne mei de bywurke betingsten, kinne jo jo oerienkomst mei %{domain} op elk winske momint beëinigje troch jo account fuort te smiten.
-      changelog: 'Yn ien eachopslach betsjut dizze update foar jo:'
-      sign_off: It %{domain}-team
-      subject: Aktualisaasje fan ús tsjinstbetingsten
-      subtitle: De gebrûksbetingsten fan %{domain} wizigje
-      title: Wichtige update
     warning:
       appeal: Beswier yntsjinje
       appeal_description: Wannear’t jo tinke dat dit in flater is, kinne jo in beswier yntsjinje by de meiwurkers fan %{instance}.
diff --git a/config/locales/ga.yml b/config/locales/ga.yml
index 58d02fc6a9..b9ff79a85b 100644
--- a/config/locales/ga.yml
+++ b/config/locales/ga.yml
@@ -196,7 +196,6 @@ ga:
         create_domain_block: Cruthaigh Bloc Fearainn
         create_email_domain_block: Cruthaigh Bloc Fearainn Ríomhphoist
         create_ip_block: Cruthaigh riail IP
-        create_relay: Cruthaigh Leaschraolacháin
         create_unavailable_domain: Cruthaigh Fearann ​​Gan Fáil
         create_user_role: Cruthaigh Ról
         demote_user: Ísligh úsáideoir
@@ -208,22 +207,18 @@ ga:
         destroy_email_domain_block: Scrios Bloc Fearainn Ríomhphoist
         destroy_instance: Fearann ​​Purge
         destroy_ip_block: Scrios riail IP
-        destroy_relay: Scrios Leaschraolacháin
         destroy_status: Scrios Postáil
         destroy_unavailable_domain: Scrios Fearann ​​Gan Fáil
         destroy_user_role: Scrios ról
         disable_2fa_user: Díchumasaigh 2FA
         disable_custom_emoji: Díchumasaigh Emoji Saincheaptha
-        disable_relay: Díchumasaigh Leaschraolacháin
         disable_sign_in_token_auth_user: Díchumasaigh Fíordheimhniú Comhartha Ríomhphoist don Úsáideoir
         disable_user: Díchumasaigh Úsáideoir
         enable_custom_emoji: Cumasaigh Emoji Saincheaptha
-        enable_relay: Cumasaigh Leaschraolacháin
         enable_sign_in_token_auth_user: Cumasaigh Fíordheimhniú Comhartha Ríomhphoist don Úsáideoir
         enable_user: Cumasaigh Úsáideoir
         memorialize_account: Cuntas Cuimhneacháin
         promote_user: Úsáideoir a chur chun cinn
-        publish_terms_of_service: Foilsigh Téarmaí Seirbhíse
         reject_appeal: Diúltaigh Achomharc
         reject_user: Diúltaigh Úsáideoir
         remove_avatar_user: Bain Abhatár
@@ -261,7 +256,6 @@ ga:
         create_domain_block_html: "%{name} fearann ​​bactha %{target}"
         create_email_domain_block_html: Chuir %{name} fearann ​​ríomhphoist bactha %{target}
         create_ip_block_html: Chruthaigh %{name} riail don IP %{target}
-        create_relay_html: Chruthaigh %{name} athsheoladh %{target}
         create_unavailable_domain_html: Chuir %{name} deireadh leis an seachadadh chuig fearann ​​%{target}
         create_user_role_html: Chruthaigh %{name} %{target} ról
         demote_user_html: "%{name} úsáideoir scriosta %{target}"
@@ -273,22 +267,18 @@ ga:
         destroy_email_domain_block_html: "%{name} fearann ​​ríomhphoist díchoiscthe %{target}"
         destroy_instance_html: Glanadh %{name} fearann ​​%{target}
         destroy_ip_block_html: "%{name} scriosta riail le haghaidh IP %{target}"
-        destroy_relay_html: Scrios %{name} an athsheoladh %{target}
         destroy_status_html: Bhain %{name} postáil le %{target}
         destroy_unavailable_domain_html: D'athchrom %{name} ar an seachadadh chuig fearann ​​%{target}
         destroy_user_role_html: Scrios %{name} ról %{target}
         disable_2fa_user_html: Dhíchumasaigh %{name} riachtanas dhá fhachtóir don úsáideoir %{target}
         disable_custom_emoji_html: Dhíchumasaigh %{name} emoji %{target}
-        disable_relay_html: Dhíchumasaigh %{name} an athsheoladh %{target}
         disable_sign_in_token_auth_user_html: Dhíchumasaigh %{name} fíordheimhniú comhartha ríomhphoist le haghaidh %{target}
         disable_user_html: "%{name} logáil isteach díchumasaithe d'úsáideoir %{target}"
         enable_custom_emoji_html: "%{name} emoji cumasaithe %{target}"
-        enable_relay_html: Chuir %{name} an sealaíocht %{target} ar chumas
         enable_sign_in_token_auth_user_html: Chuir %{name} fíordheimhniú comhartha ríomhphoist ar chumas %{target}
         enable_user_html: "%{name} logáil isteach cumasaithe don úsáideoir %{target}"
         memorialize_account_html: Rinne %{name} cuntas %{target} a iompú ina leathanach cuimhneacháin
         promote_user_html: Chuir %{name} úsáideoir %{target} chun cinn
-        publish_terms_of_service_html: D'fhoilsigh %{name} nuashonruithe ar na téarmaí seirbhíse
         reject_appeal_html: Dhiúltaigh %{name} achomharc ar chinneadh modhnóireachta ó %{target}
         reject_user_html: Dhiúltaigh %{name} síniú suas ó %{target}
         remove_avatar_user_html: Bhain %{name} avatar %{target}
@@ -318,7 +308,6 @@ ga:
       title: Loga iniúchta
       unavailable_instance: "(ainm fearainn ar fáil)"
     announcements:
-      back: Ar ais chuig fógraí
       destroyed_msg: D'éirigh leis an bhfógra a scriosadh!
       edit:
         title: Cuir fógra in eagar
@@ -327,9 +316,6 @@ ga:
       new:
         create: Cruthaigh fógra
         title: Fógra nua
-      preview:
-        explanation_html: 'Seolfar an ríomhphost chuig <strong>%{display_count} úsáideoir</strong>. Beidh an téacs seo a leanas san áireamh sa ríomhphost:'
-        title: Fógra réamhamhairc na fógraíochta
       publish: Foilsigh
       published_msg: D’éirigh leis an bhfógra a fhoilsiú!
       scheduled_for: Sceidealta le haghaidh %{time}
@@ -503,36 +489,6 @@ ga:
       new:
         title: Iompórtáil bloic fearainn
       no_file: Níor roghnaíodh aon chomhad
-    fasp:
-      debug:
-        callbacks:
-          created_at: Cruthaithe ag
-          delete: Scrios
-          ip: Seoladh IP
-          request_body: Comhlacht iarratais
-          title: Glaonna Dífhabhtaithe
-      providers:
-        active: Gníomhach
-        base_url: Bun-URL
-        callback: Glao ar ais
-        delete: Scrios
-        edit: Cuir Soláthraí in Eagar
-        finish_registration: Críochnaigh clárú
-        name: Ainm
-        providers: Soláthraithe
-        public_key_fingerprint: Méarloirg eochair phoiblí
-        registration_requested: Clárú iarrtha
-        registrations:
-          confirm: Deimhnigh
-          description: Fuair ​​tú clárú ó FASP. Diúltaigh é murar chuir tú tús leis seo. Má thionscain tú é seo, déan comparáid chúramach idir an t-ainm agus an eochair-mhéarlorg sula ndearbhaítear an clárúchán.
-          reject: Diúltaigh
-          title: Deimhnigh Clárú FASP
-        save: Sábháil
-        select_capabilities: Roghnaigh Cumais
-        sign_in: Sínigh Isteach
-        status: Stádas
-        title: Soláthraithe Seirbhíse Cúnta Fediverse
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Lean na moltaí cabhraíonn sé le húsáideoirí nua ábhar suimiúil a aimsiú go tapa</strong>. Nuair nach mbíonn go leor idirghníomhaithe ag úsáideoir le daoine eile chun moltaí pearsantaithe a leanúint, moltar na cuntais seo ina ionad sin. Déantar iad a athríomh ar bhonn laethúil ó mheascán de chuntais a bhfuil na rannpháirtíochtaí is airde acu le déanaí agus na háirimh áitiúla is airde leanúna do theanga ar leith."
       language: Don teanga
@@ -904,10 +860,8 @@ ga:
       back_to_account: Ar ais go leathanach cuntais
       back_to_report: Ar ais go leathanach tuairisce
       batch:
-        add_to_report: 'Cuir leis an tuairisc # %{id}'
         remove_from_report: Bain den tuairisc
         report: Tuairisc
-      contents: Ábhar
       deleted: Scriosta
       favourites: Toghanna
       history: Stair leagan
@@ -916,17 +870,13 @@ ga:
       media:
         title: Meáin
       metadata: Meiteashonraí
-      no_history: Níl an postáil seo curtha in eagar
       no_status_selected: Níor athraíodh aon phostáil mar níor roghnaíodh ceann ar bith
       open: Oscail postáil
       original_status: Bunphostáil
       reblogs: Athbhlaganna
-      replied_to_html: D'fhreagair %{acct_link}
       status_changed: Athraíodh postáil
-      status_title: Postáil le @ %{name}
-      title: Postálacha cuntais - @%{name}
+      title: Poist chuntais
       trending: Ag treochtáil
-      view_publicly: Féach go poiblí
       visibility: Infheictheacht
       with_media: Le meáin
     strikes:
@@ -1003,39 +953,6 @@ ga:
       search: Cuardach
       title: Haischlibeanna
       updated_msg: D'éirigh le socruithe hashtag a nuashonrú
-    terms_of_service:
-      back: Ar ais go téarmaí seirbhíse
-      changelog: Cad atá athraithe
-      create: Bain úsáid as do chuid féin
-      current: Reatha
-      draft: Dréacht
-      generate: Úsáid teimpléad
-      generates:
-        action: Gin
-        chance_to_review_html: "<strong>Ní fhoilseofar na téarmaí seirbhíse ginte go huathoibríoch.</strong> Beidh deis agat na torthaí a athbhreithniú. Líon isteach na sonraí riachtanacha le leanúint ar aghaidh."
-        explanation_html: Is chun críocha faisnéise amháin atá an teimpléad téarmaí seirbhíse a chuirtear ar fáil, agus níor cheart é a fhorléiriú mar chomhairle dlí ar aon ábhar. Téigh i gcomhairle le do chomhairle dlí féin maidir le do chás agus ceisteanna dlí ar leith atá agat.
-        title: Téarmaí Socrú Seirbhíse
-      going_live_on_html: Beo, éifeachtach %{date}
-      history: Stair
-      live: Beo
-      no_history: Níl aon athruithe taifeadta ar théarmaí seirbhíse fós.
-      no_terms_of_service_html: Níl aon téarmaí seirbhíse cumraithe agat faoi láthair. Tá téarmaí seirbhíse i gceist le soiléireacht a sholáthar agus tú a chosaint ó dhliteanais ionchasacha i ndíospóidí le d’úsáideoirí.
-      notified_on_html: Cuireadh úsáideoirí ar an eolas ar %{date}
-      notify_users: Cuir úsáideoirí ar an eolas
-      preview:
-        explanation_html: 'Seolfar an ríomhphost chuig <strong>%{display_count} úsáideoir</strong> a chláraigh roimh %{date}. Beidh an téacs seo a leanas san áireamh sa ríomhphost:'
-        send_preview: Seol réamhamharc chuig %{email}
-        send_to_all:
-          few: Seol %{display_count} ríomhphost
-          many: Seol %{display_count} ríomhphost
-          one: Seol %{display_count} ríomhphost
-          other: Seol %{display_count} ríomhphost
-          two: Seol %{display_count} ríomhphost
-        title: Réamhamharc ar théarmaí an fhógra seirbhíse
-      publish: Foilsigh
-      published_on_html: Foilsithe ar %{date}
-      save_draft: Sábháil dréacht
-      title: Téarmaí Seirbhíse
     title: Riar
     trends:
       allow: Ceadaigh
@@ -1255,6 +1172,7 @@ ga:
     migrate_account: Bog chuig cuntas eile
     migrate_account_html: Más mian leat an cuntas seo a atreorú chuig ceann eile, is féidir leat <a href="%{path}">é a chumrú anseo</a>.
     or_log_in_with: Nó logáil isteach le
+    privacy_policy_agreement_html: Léigh mé agus aontaím leis an <a href="%{privacy_policy_path}" target="_blank">polasaí príobháideachais</a>
     progress:
       confirm: Deimhnigh ríomhphost
       details: Do chuid sonraí
@@ -1279,7 +1197,7 @@ ga:
     set_new_password: Socraigh pasfhocal nua
     setup:
       email_below_hint_html: Seiceáil d'fhillteán turscair, nó iarr ceann eile. Is féidir leat do sheoladh ríomhphoist a cheartú má tá sé mícheart.
-      email_settings_hint_html: Cliceáil ar an nasc a sheolamar chuig %{email} chun tús a chur le Mastodon a úsáid. Beidh muid ag fanacht ar dheis anseo.
+      email_settings_hint_html: Cliceáil ar an nasc a sheol muid chugat chun %{email} a fhíorú. Beidh muid ag fanacht ar dheis anseo.
       link_not_received: Nach bhfuair tú nasc?
       new_confirmation_instructions_sent: Gheobhaidh tú ríomhphost nua leis an nasc deimhnithe i gceann cúpla bomaite!
       title: Seiceáil do bhosca isteach
@@ -1288,7 +1206,7 @@ ga:
       title: Logáil isteach go %{domain}
     sign_up:
       manual_review: Téann clárúcháin ar %{domain} trí athbhreithniú láimhe ag ár modhnóirí. Chun cabhrú linn do chlárúchán a phróiseáil, scríobh beagán fút féin agus cén fáth a bhfuil cuntas uait ar %{domain}.
-      preamble: Agus cuntas agat ar an bhfreastalaí Mastodon seo, beidh tú in ann aon duine eile ar an bhfealsúnacht a leanúint, is cuma cá bhfuil a gcuntas á óstáil.
+      preamble: Le cuntas ar an bhfreastalaí Mastodon seo, beidh tú in ann aon duine eile ar an líonra a leanúint, beag beann ar an áit a bhfuil a gcuntas á óstáil.
       title: Déanaimis tú a shocrú ar %{domain}.
     status:
       account_status: Stádas cuntais
@@ -1300,8 +1218,6 @@ ga:
       view_strikes: Féach ar stailceanna san am atá caite i gcoinne do chuntais
     too_fast: Cuireadh an fhoirm isteach róthapa, triail arís.
     use_security_key: Úsáid eochair shlándála
-    user_agreement_html: Léigh mé agus aontaím leis na <a href="%{terms_of_service_path}" target="_blank">téarmaí seirbhíse</a> agus <a href="%{privacy_policy_path}" target="_blank">polasaí príobháideachais</a>
-    user_privacy_agreement_html: Léigh mé agus aontaím leis an <a href="%{privacy_policy_path}" target="_blank">polasaí príobháideachais</a>
   author_attribution:
     example_title: Téacs samplach
     hint_html: An bhfuil tú ag scríobh altanna nuachta nó blag lasmuigh de Mastodon? Rialú conas a gheobhaidh tú creidmheas nuair a roinntear iad ar Mastodon.
@@ -1528,79 +1444,19 @@ ga:
       overwrite: Forscríobh
       overwrite_long: Cuir na cinn nua in ionad na dtaifead reatha
     overwrite_preambles:
-      blocking_html:
-        few: Tá tú ar tí <strong>do liosta bloc</strong> a chur in ionad suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        many: Tá tú ar tí <strong>do liosta bloc</strong> a chur in ionad suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        one: Tá tú ar tí <strong>do liosta bloc</strong> a chur in ionad suas le <strong>%{count} cuntas</strong> ó <strong>%{filename}</strong>.
-        other: Tá tú ar tí <strong>do liosta bloc</strong> a chur in ionad suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        two: Tá tú ar tí <strong>do liosta bloc</strong> a chur in ionad suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: Tá tú ar tí <strong>do leabharmharcanna</strong> a chur in ionad suas le <strong>%{count} postáil</strong> ó <strong>%{filename}</strong>.
-        many: Tá tú ar tí <strong>do leabharmharcanna</strong> a chur in ionad suas le <strong>%{count} postáil</strong> ó <strong>%{filename}</strong>.
-        one: Tá tú ar tí <strong>do leabharmharcanna</strong> a chur in ionad suas le <strong>%{count} postáil</strong> ó <strong>%{filename}</strong>.
-        other: Tá tú ar tí <strong>do leabharmharcanna</strong> a chur in ionad suas le <strong>%{count} postáil</strong> ó <strong>%{filename}</strong>.
-        two: Tá tú ar tí <strong>do leabharmharcanna</strong> a chur in ionad suas le <strong>%{count} postáil</strong> ó <strong>%{filename}</strong>.
-      domain_blocking_html:
-        few: Tá tú ar tí <strong>do liosta bloc fearainn</strong> a chur in ionad suas le <strong>%{count} fearainn</strong> ó <strong>%{filename}</strong>.
-        many: Tá tú ar tí <strong>do liosta bloc fearainn</strong> a chur in ionad suas le <strong>%{count} fearainn</strong> ó <strong>%{filename}</strong>.
-        one: Tá tú ar tí <strong>do liosta bloc fearainn</strong> a chur in ionad suas le <strong>%{count} fearainn</strong> ó <strong>%{filename}</strong>.
-        other: Tá tú ar tí <strong>do liosta bloc fearainn</strong> a chur in ionad suas le <strong>%{count} fearainn</strong> ó <strong>%{filename}</strong>.
-        two: Tá tú ar tí <strong>do liosta bloc fearainn</strong> a chur in ionad suas le <strong>%{count} fearainn</strong> ó <strong>%{filename}</strong>.
-      following_html:
-        few: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{count} cuntas</strong> ó <strong>%{filename}</strong> agus <strong>stop ag leanúint aon duine eile</strong>.
-        many: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{count} cuntas</strong> ó <strong>%{filename}</strong> agus <strong>stop ag leanúint aon duine eile</strong>.
-        one: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{count} chuntais</strong> ó <strong>%{filename}</strong> agus <strong>stop a leanúint aon duine eile</strong>.
-        other: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{count} cuntas</strong> ó <strong>%{filename}</strong> agus <strong>stop ag leanúint aon duine eile</strong>.
-        two: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{count} cuntas</strong> ó <strong>%{filename}</strong> agus <strong>stop ag leanúint aon duine eile</strong>.
-      lists_html:
-        few: Tá tú ar tí <strong>do liostaí</strong> a chur in ionad inneachair <strong>%{filename}</strong>. Cuirfear suas le <strong>%{count} cuntais</strong> le liostaí nua.
-        many: Tá tú ar tí <strong>do liostaí</strong> a chur in ionad inneachair <strong>%{filename}</strong>. Cuirfear suas le <strong>%{count} cuntais</strong> le liostaí nua.
-        one: Tá tú ar tí <strong>do liostaí</strong> a chur in ionad inneachair <strong>%{filename}</strong>. Cuirfear suas le <strong>%{count} cuntas</strong> le liostaí nua.
-        other: Tá tú ar tí <strong>do liostaí</strong> a chur in ionad inneachair <strong>%{filename}</strong>. Cuirfear suas le <strong>%{count} cuntais</strong> le liostaí nua.
-        two: Tá tú ar tí <strong>do liostaí</strong> a chur in ionad inneachair <strong>%{filename}</strong>. Cuirfear suas le <strong>%{count} cuntais</strong> le liostaí nua.
-      muting_html:
-        few: Tá tú ar tí <strong>do liosta cuntas balbhaithe</strong> a chur in ionad suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        many: Tá tú ar tí <strong>do liosta cuntas balbhaithe</strong> a chur in ionad suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        one: Tá tú ar tí <strong>do liosta cuntais balbhaithe</strong> a chur in ionad suas le <strong>%{count} cuntas</strong> ó <strong>%{filename}</strong>.
-        other: Tá tú ar tí <strong>do liosta cuntas balbhaithe</strong> a chur in ionad suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        two: Tá tú ar tí <strong>do liosta cuntas balbhaithe</strong> a chur in ionad suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
+      blocking_html: Tá tú ar tí <strong>do liosta bloc</strong> a chur in ionad suas le <strong>%{total_items} cuntas</strong> ó <strong>%{filename}</strong>.
+      bookmarks_html: Tá tú ar tí <strong>do leabharmharcanna</strong> a chur in ionad suas le <strong>%{total_items} postáil</strong> ó <strong>%{filename}</strong>.
+      domain_blocking_html: Tá tú ar tí <strong>do liosta bloc fearainn</strong> a chur in ionad suas le <strong>%{total_items} fearainn</strong> ó <strong>%{filename}</strong>.
+      following_html: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{total_items} cuntas</strong> ó <strong>%{filename}</strong> agus <strong>stop a leanúint aon duine eile</strong>.
+      lists_html: Tá tú ar tí <strong>do liostaí</strong> a chur in ionad inneachair <strong>%{filename}</strong>. Cuirfear suas le <strong>%{total_items} cuntas</strong> le liostaí nua.
+      muting_html: Tá tú ar tí <strong>do liosta cuntas balbhaithe</strong> a chur in ionad suas le <strong>%{total_items} cuntas</strong> ó <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        few: Tá tú ar tí <strong>bhac</strong> suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        many: Tá tú ar tí <strong>bhac</strong> suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        one: Tá tú ar tí <strong>bac</strong> suas go dtí <strong>%{count} cuntas</strong> ó <strong>%{filename}</strong>.
-        other: Tá tú ar tí <strong>bhac</strong> suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        two: Tá tú ar tí <strong>bhac</strong> suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: Tá tú ar tí <strong>%{count} postáil</strong> ó <strong>%{filename}</strong> a chur le do <strong>leabharmharcanna</strong>.
-        many: Tá tú ar tí <strong>%{count} postáil</strong> ó <strong>%{filename}</strong> a chur le do <strong>leabharmharcanna</strong>.
-        one: Tá tú ar tí <strong>%{count} postáil</strong> ó <strong>%{filename}</strong> a chur le do <strong>leabharmharcanna</strong>.
-        other: Tá tú ar tí <strong>%{count} postáil</strong> ó <strong>%{filename}</strong> a chur le do <strong>leabharmharcanna</strong>.
-        two: Tá tú ar tí <strong>%{count} postáil</strong> ó <strong>%{filename}</strong> a chur le do <strong>leabharmharcanna</strong>.
-      domain_blocking_html:
-        few: Tá tú ar tí <strong>bhac</strong> suas le <strong>%{count} bhfearainn</strong> ó <strong>%{filename}</strong>.
-        many: Tá tú ar tí <strong>bhac</strong> suas le <strong>%{count} bhfearainn</strong> ó <strong>%{filename}</strong>.
-        one: Tá tú ar tí <strong>bac</strong> suas go dtí <strong>%{count} fearann</strong> ó <strong>%{filename}</strong>.
-        other: Tá tú ar tí <strong>bhac</strong> suas le <strong>%{count} bhfearainn</strong> ó <strong>%{filename}</strong>.
-        two: Tá tú ar tí <strong>bhac</strong> suas le <strong>%{count} bhfearainn</strong> ó <strong>%{filename}</strong>.
-      following_html:
-        few: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        many: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        one: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{count} cuntas</strong> ó <strong>%{filename}</strong>.
-        other: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        two: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-      lists_html:
-        few: Tá tú ar tí <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong> a chur le do <strong>liostaí</strong>. Cruthófar liostaí nua mura bhfuil aon liosta le cur leis.
-        many: Tá tú ar tí <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong> a chur le do <strong>liostaí</strong>. Cruthófar liostaí nua mura bhfuil aon liosta le cur leis.
-        one: Tá tú ar tí cuntas <strong>%{count}</strong> ó <strong>%{filename}</strong> a chur le do <strong>liostaí</strong>. Cruthófar liostaí nua mura bhfuil aon liosta le cur leis.
-        other: Tá tú ar tí <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong> a chur le do <strong>liostaí</strong>. Cruthófar liostaí nua mura bhfuil aon liosta le cur leis.
-        two: Tá tú ar tí <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong> a chur le do <strong>liostaí</strong>. Cruthófar liostaí nua mura bhfuil aon liosta le cur leis.
-      muting_html:
-        few: Tá tú ar tí <strong>balbhú</strong> suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        many: Tá tú ar tí <strong>balbhú</strong> suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        one: Tá tú ar tí <strong>balbhú</strong> suas go dtí <strong>%{count} cuntas</strong> ó <strong>%{filename}</strong>.
-        other: Tá tú ar tí <strong>balbhú</strong> suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
-        two: Tá tú ar tí <strong>balbhú</strong> suas le <strong>%{count} cuntais</strong> ó <strong>%{filename}</strong>.
+      blocking_html: Tá tú ar tí <strong>bloc</strong> suas le <strong>%{total_items} cuntas</strong> ó <strong>%{filename}</strong>.
+      bookmarks_html: Tá tú ar tí <strong>%{total_items} postáil</strong> ó <strong>%{filename}</strong> a chur le do <strong>leabharmharcanna</strong>.
+      domain_blocking_html: Tá tú ar tí <strong>bloc</strong> suas le <strong>%{total_items} fearainn</strong> ó <strong>%{filename}</strong>.
+      following_html: Tá tú ar tí <strong>leanúint</strong> suas go dtí <strong>%{total_items} cuntas</strong> ó <strong>%{filename}</strong>.
+      lists_html: Tá tú ar tí <strong>%{total_items} cuntas</strong> ó <strong>%{filename}</strong> a chur le do <strong>liostaí</strong>. Cruthófar liostaí nua mura bhfuil aon liosta le cur leis.
+      muting_html: Tá tú ar tí <strong>balbhú</strong> suas le <strong>%{total_items} cuntas</strong> ó <strong>%{filename}</strong>.
     preface: Is féidir leat sonraí a d’easpórtáil tú a allmhairiú ó fhreastalaí eile, mar shampla liosta de na daoine a bhfuil tú ag leanúint nó ag cur bac orthu.
     recent_imports: Allmhairí le déanaí
     states:
@@ -1860,7 +1716,7 @@ ga:
   scheduled_statuses:
     over_daily_limit: Tá an teorainn de %{limit} postáil sceidealaithe sáraithe agat don lá atá inniu ann
     over_total_limit: Tá an teorainn de %{limit} postáil sceidealaithe sáraithe agat
-    too_soon: caithfidh dáta a bheith sa todhchaí
+    too_soon: Caithfidh an dáta sceidealta a bheith sa todhchaí
   self_destruct:
     lead_html: Ar an drochuair, tá <strong>%{domain}</strong> ag dúnadh síos go buan. Má bhí cuntas agat ann, ní bheidh tú in ann leanúint ar aghaidh á úsáid, ach is féidir leat cúltaca de do shonraí a iarraidh fós.
     title: Tá an freastalaí seo ag dúnadh
@@ -2035,8 +1891,6 @@ ga:
       too_late: Tá sé ró-dhéanach achomharc a dhéanamh faoin stailc seo
   tags:
     does_not_match_previous_name: nach meaitseálann an t-ainm roimhe seo
-  terms_of_service:
-    title: Téarmaí Seirbhíse
   themes:
     contrast: Mastodon (Codarsnacht ard)
     default: Mastodon (Dorcha)
@@ -2068,10 +1922,6 @@ ga:
     recovery_instructions_html: Má chailleann tú rochtain ar do ghuthán riamh, is féidir leat ceann de na cóid athshlánaithe thíos a úsáid chun rochtain a fháil ar do chuntas arís. <strong>Coinnigh na cóid athshlánaithe slán</strong>. Mar shampla, is féidir leat iad a phriontáil agus iad a stóráil le doiciméid thábhachtacha eile.
     webauthn: Eochracha slándála
   user_mailer:
-    announcement_published:
-      description: 'Tá riarthóirí %{domain} ag déanamh fógra:'
-      subject: Fógra seirbhíse
-      title: "%{domain} fógra seirbhíse"
     appeal_approved:
       action: Socruithe cuntas
       explanation: Ceadaíodh achomharc na stailce i gcoinne do chuntais ar %{strike_date} a chuir tú isteach ar %{appeal_date}. Tá seasamh maith ag do chuntas arís.
@@ -2101,15 +1951,6 @@ ga:
       further_actions_html: Mura tusa a bhí ann, molaimid duit %{action} a dhéanamh láithreach agus fíordheimhniú dhá fhachtóir a chumasú chun do chuntas a choinneáil slán.
       subject: Fuarthas rochtain ar do chuntas ó sheoladh IP nua
       title: Síniú isteach nua
-    terms_of_service_changed:
-      agreement: Má leanann tú ar aghaidh ag úsáid %{domain}, tá tú ag aontú leis na téarmaí seo. Mura n-aontaíonn tú leis na téarmaí nuashonraithe, is féidir leat do chomhaontú le %{domain} a fhoirceannadh am ar bith trí do chuntas a scriosadh.
-      changelog: 'Sracfhéachaint, seo é a chiallaíonn an nuashonrú seo duit:'
-      description: 'Tá an ríomhphost seo á fháil agat toisc go bhfuil roinnt athruithe á ndéanamh againn ar ár dtéarmaí seirbhíse ag %{domain}. Beidh na nuashonruithe seo i bhfeidhm ar %{date}. Molaimid duit athbhreithniú iomlán a dhéanamh ar na téarmaí nuashonraithe anseo:'
-      description_html: Tá an ríomhphost seo á fháil agat toisc go bhfuil roinnt athruithe á ndéanamh againn ar ár dtéarmaí seirbhíse ag %{domain}. Tiocfaidh na nuashonruithe seo i bhfeidhm ar <strong>%{date}</strong>. Molaimid duit athbhreithniú a dhéanamh ar na <a href="%{path}" target="_blank">téarmaí nuashonraithe ina n-iomláine anseo</a>.
-      sign_off: Foireann %{domain}
-      subject: Nuashonruithe ar ár dtéarmaí seirbhíse
-      subtitle: Tá téarmaí seirbhíse %{domain} ag athrú
-      title: Nuashonrú tábhachtach
     warning:
       appeal: Cuir achomharc isteach
       appeal_description: Má chreideann tú gur earráid é seo, is féidir leat achomharc a chur isteach chuig foireann %{instance}.
diff --git a/config/locales/gd.yml b/config/locales/gd.yml
index b9b40ac8b5..1fdf4355d8 100644
--- a/config/locales/gd.yml
+++ b/config/locales/gd.yml
@@ -193,7 +193,6 @@ gd:
         create_domain_block: Cruthaich bacadh àrainne
         create_email_domain_block: Cruthaich bacadh àrainne puist-d
         create_ip_block: Cruthaich riaghailt IP
-        create_relay: Cruthaich ath-sheachadan
         create_unavailable_domain: Cruthaich àrainn nach eil ri fhaighinn
         create_user_role: Cruthaich dreuchd
         demote_user: Ìslich an cleachdaiche
@@ -205,17 +204,14 @@ gd:
         destroy_email_domain_block: Sguab às bacadh na h-àrainne puist-d
         destroy_instance: Purgaidich an àrainn
         destroy_ip_block: Sguab às an riaghailt IP
-        destroy_relay: Sguab às an t-ath-sheachadan
         destroy_status: Sguab às am post
         destroy_unavailable_domain: Sguab às àrainn nach eil ri fhaighinn
         destroy_user_role: Mill an dreuchd
         disable_2fa_user: Cuir an dearbhadh dà-cheumnach à comas
         disable_custom_emoji: Cuir an t-Emoji gnàthaichte à comas
-        disable_relay: Cuir an t-ath-sheachadan à comas
         disable_sign_in_token_auth_user: Cuir à comas dearbhadh le tòcan puist-d dhan chleachdaiche
         disable_user: Cuir an cleachdaiche à comas
         enable_custom_emoji: Cuir an t-Emoji gnàthaichte an comas
-        enable_relay: Cuir an ath-sheachadan an comas
         enable_sign_in_token_auth_user: Cuir an comas dearbhadh le tòcan puist-d dhan chleachdaiche
         enable_user: Cuir an cleachdaiche an comas
         memorialize_account: Dèan cuimhneachan dhen chunntas
@@ -257,7 +253,6 @@ gd:
         create_domain_block_html: Bhac %{name} an àrainn %{target}
         create_email_domain_block_html: Bhac %{name} an àrainn puist-d %{target}
         create_ip_block_html: Chruthaich %{name} riaghailt dhan IP %{target}
-        create_relay_html: Chruthaich %{name} an t-ath-sheachadan %{target}
         create_unavailable_domain_html: Sguir %{name} ris an lìbhrigeadh dhan àrainn %{target}
         create_user_role_html: Chruthaich %{name} an dreuchd %{target}
         demote_user_html: Dh’ìslich %{name} an cleachdaiche %{target}
@@ -269,17 +264,14 @@ gd:
         destroy_email_domain_block_html: Dì-bhac %{name} an àrainn puist-d %{target}
         destroy_instance_html: Phurgaidich %{name} an àrainn %{target}
         destroy_ip_block_html: Sguab %{name} às riaghailt dhan IP %{target}
-        destroy_relay_html: Sguab %{name} às an t-ath-sheachadan %{target}
         destroy_status_html: Thug %{name} post aig %{target} air falbh
         destroy_unavailable_domain_html: Lean %{name} air adhart leis an lìbhrigeadh dhan àrainn %{target}
         destroy_user_role_html: Sguab %{name} às an dreuchd %{target}
         disable_2fa_user_html: Chuir %{name} riatanas an dearbhaidh dà-cheumnaich à comas dhan chleachdaiche %{target}
         disable_custom_emoji_html: Chuir %{name} an Emoji %{target} à comas
-        disable_relay_html: Chuir %{name} an t-ath-sheachadan %{target} à comas
         disable_sign_in_token_auth_user_html: Chuir %{name} à comas dearbhadh le tòcan puist-d dha %{target}
         disable_user_html: Chuir %{name} an clàradh a-steach à comas dhan chleachdaiche %{target}
         enable_custom_emoji_html: Chuir %{name} an Emoji %{target} an comas
-        enable_relay_html: Chuir %{name} an t-ath-sheachadan %{target} an comas
         enable_sign_in_token_auth_user_html: Chuir %{name} an comas dearbhadh le tòcan puist-d dha %{target}
         enable_user_html: Chuir %{name} an clàradh a-steach an comas dhan chleachdaiche %{target}
         memorialize_account_html: Rinn %{name} duilleag cuimhneachain dhen chunntas aig %{target}
@@ -854,10 +846,8 @@ gd:
       back_to_account: Till gu duilleag a’ chunntais
       back_to_report: Till gu duilleag a’ ghearain
       batch:
-        add_to_report: 'Cuir ris a’ ghearan #%{id}'
         remove_from_report: Thoir air falbh on ghearan
         report: Gearan
-      contents: Susbaint
       deleted: Chaidh a sguabadh às
       favourites: Annsachdan
       history: Eachdraidh nan tionndadh
@@ -866,17 +856,13 @@ gd:
       media:
         title: Meadhanan
       metadata: Meata-dàta
-      no_history: Cha deach am post seo a dheasachadh
       no_status_selected: Cha deach post sam bith atharrachadh o nach deach gin dhiubh a thaghadh
       open: Fosgail am post
       original_status: Am post tùsail
       reblogs: Brosnachaidhean
-      replied_to_html: Freagairt do %{acct_link}
       status_changed: Post air atharrachadh
-      status_title: Post le @%{name}
-      title: Postaichean a’ chunntais – @%{name}
+      title: Postaichean a’ chunntais
       trending: A’ treandadh
-      view_publicly: Seall gu poblach
       visibility: Faicsinneachd
       with_media: Le meadhanan riutha
     strikes:
@@ -1168,6 +1154,7 @@ gd:
     migrate_account: Imrich gu cunntas eile
     migrate_account_html: Nam bu mhiann leat an cunntas seo ath-stiùireadh gu fear eile, ’s urrainn dhut <a href="%{path}">a rèiteachadh an-seo</a>.
     or_log_in_with: No clàraich a-steach le
+    privacy_policy_agreement_html: Leugh mi is tha mi ag aontachadh ris a’ <a href="%{privacy_policy_path}" target="_blank">phoileasaidh prìobhaideachd</a>
     progress:
       confirm: Dearbh am post-d
       details: Am fiosrachadh agad
@@ -1192,6 +1179,7 @@ gd:
     set_new_password: Suidhich facal-faire ùr
     setup:
       email_below_hint_html: Thoir sùil air pasgan an spama agad no iarr fear eile. ’S urrainn dhut an seòladh puist-d agad a chur ceart ma tha e ceàrr.
+      email_settings_hint_html: Briog air a’ cheangal a chuir sinn thugad a dhearbhadh %{email}. Fuirichidh sinn ort an-seo.
       link_not_received: Nach d’fhuair thu ceangal?
       new_confirmation_instructions_sent: Gheibh thu post-d ùr le ceangal dearbhaidh an ceann corra mionaid!
       title: Thoir sùil air a’ bhogsa a-steach agad
@@ -1200,6 +1188,7 @@ gd:
       title: Clàraich a-steach gu %{domain}
     sign_up:
       manual_review: Nì na maoir againn lèirmheas a làimh air clàraidhean air %{domain}. Airson ar cuideachadh le làimhseachadh do chlàraidh, sgrìobh beagan mu do dhèidhinn agus carson a tha thu ag iarraidh cunntas air %{domain}.
+      preamble: Le cunntas air an fhrithealaiche Mastodon seo, ’s urrainn dhut neach sam bith a leantainn air an lìonra, ge b’ e càit a bheil an cunntas aca-san ’ga òstadh.
       title: Suidhicheamaid %{domain} dhut.
     status:
       account_status: Staid a’ chunntais
@@ -1430,67 +1419,19 @@ gd:
       overwrite: Sgrìobh thairis air
       overwrite_long: Cuir na reacordan ùra an àite na feadhna a tha ann
     overwrite_preambles:
-      blocking_html:
-        few: Tha thu an impis suas ri <strong>%{count} cunntasan</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nam bacaidhean</strong> agad.
-        one: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nam bacaidhean</strong> agad.
-        other: Tha thu an impis suas ri <strong>%{count} cunntas</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nam bacaidhean</strong> agad.
-        two: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nam bacaidhean</strong> agad.
-      bookmarks_html:
-        few: Tha thu an impis suas ri <strong>%{count} postaichean</strong> o <strong>%{filename}</strong> a chur <strong>an àite nan comharra-lìn</strong> agad.
-        one: Tha thu an impis suas ri <strong>%{count} phost</strong> o <strong>%{filename}</strong> a chur <strong>an àite nan comharra-lìn</strong> agad.
-        other: Tha thu an impis suas ri <strong>%{count} post</strong> o <strong>%{filename}</strong> a chur <strong>an àite nan comharra-lìn</strong> agad.
-        two: Tha thu an impis suas ri <strong>%{count} phost</strong> o <strong>%{filename}</strong> a chur <strong>an àite nan comharra-lìn</strong> agad.
-      domain_blocking_html:
-        few: Tha thu an impis suas ri <strong>%{count} àrainnean</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nam bacaidhean àrainne</strong> agad.
-        one: Tha thu an impis suas ri <strong>%{count} àrainn</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nam bacaidhean àrainne</strong> agad.
-        other: Tha thu an impis suas ri <strong>%{count} àrainn</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nam bacaidhean àrainne</strong> agad.
-        two: Tha thu an impis suas ri <strong>%{count} àrainn</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nam bacaidhean àrainne</strong> agad.
-      following_html:
-        few: Tha thu an impis suas ri <strong>%{count} cunntasan</strong> o <strong>%{filename}</strong> <strong>a leantainn</strong> agus <strong>sguiridh tu a leantainn duine sam bith eile</strong>.
-        one: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> <strong>a leantainn</strong> agus <strong>sguiridh tu a leantainn duine sam bith eile</strong>.
-        other: Tha thu an impis suas ri <strong>%{count} cunntas</strong> o <strong>%{filename}</strong> <strong>a leantainn</strong> agus <strong>sguiridh tu a leantainn duine sam bith eile</strong>.
-        two: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> <strong>a leantainn</strong> agus <strong>sguiridh tu a leantainn duine sam bith eile</strong>.
-      lists_html:
-        few: Tha thu an impis susbaint <strong>%{filename}</strong> a chur <strong>an àite nan liostaichean agad</strong>. Thèid suas ri <strong>%{count} cunntasan</strong> a chur ri liostaichean ùra.
-        one: Tha thu an impis susbaint <strong>%{filename}</strong> a chur <strong>an àite nan liostaichean agad</strong>. Thèid suas ri <strong>%{count} chunntas</strong> a chur ri liostaichean ùra.
-        other: Tha thu an impis susbaint <strong>%{filename}</strong> a chur <strong>an àite nan liostaichean agad</strong>. Thèid suas ri <strong>%{count} cunntas</strong> a chur ri liostaichean ùra.
-        two: Tha thu an impis susbaint <strong>%{filename}</strong> a chur <strong>an àite nan liostaichean agad</strong>. Thèid suas ri <strong>%{count} chunntas</strong> a chur ri liostaichean ùra.
-      muting_html:
-        few: Tha thu an impis suas ri <strong>%{count} cunntasan</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nan cunntasan mùchte</strong> agad.
-        one: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nan cunntasan mùchte</strong> agad.
-        other: Tha thu an impis suas ri <strong>%{count} cunntas</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nan cunntasan mùchte</strong> agad.
-        two: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nan cunntasan mùchte</strong> agad.
+      blocking_html: Tha thu an impis suas ri <strong>%{total_items} cunntas(an)</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nam bacaidhean</strong> agad.
+      bookmarks_html: Tha thu an impis suas ri <strong>%{total_items} post(aichean)</strong> o <strong>%{filename}</strong> a chur <strong>an àite nan comharra-lìn</strong> agad.
+      domain_blocking_html: Tha thu an impis suas ri <strong>%{total_items} àrainn(ean)</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nam bacaidhean àrainne</strong> agad.
+      following_html: Tha thu an impis suas ri <strong>%{total_items} cunntas(an)</strong> o <strong>%{filename}</strong> <strong>a leantainn</strong> agus <strong>sguiridh tu a leantainn duine sam bith eile</strong>.
+      lists_html: Tha thu an impis susbaint <strong>%{filename}</strong> a chur <strong>an àite nan liostaichean agad</strong>. Thèid suas ri <strong>%{total_items}cunntas(an)</strong> a chur ri liostaichean ùra.
+      muting_html: Tha thu an impis suas ri <strong>%{total_items} cunntas(an)</strong> o <strong>%{filename}</strong> a chur <strong>an àite liosta nan cunntasan mùchte</strong> agad.
     preambles:
-      blocking_html:
-        few: Tha thu an impis suas ri <strong>%{count} cunntasan</strong> o <strong>%{filename}</strong> a <strong>bhacadh</strong>.
-        one: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a <strong>bhacadh</strong>.
-        other: Tha thu an impis suas ri <strong>%{count} cunntas</strong> o <strong>%{filename}</strong> a <strong>bhacadh</strong>.
-        two: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a <strong>bhacadh</strong>.
-      bookmarks_html:
-        few: Tha thu an impis suas ri <strong>%{count} postaichean</strong> o <strong>%{filename}</strong> a chur ris na h-<strong>annsachdan</strong> agad.
-        one: Tha thu an impis suas ri <strong>%{count} phost</strong> o <strong>%{filename}</strong> a chur ris na h-<strong>annsachdan</strong> agad.
-        other: Tha thu an impis suas ri <strong>%{count} post</strong> o <strong>%{filename}</strong> a chur ris na h-<strong>annsachdan</strong> agad.
-        two: Tha thu an impis suas ri <strong>%{count} phost</strong> o <strong>%{filename}</strong> a chur ris na h-<strong>annsachdan</strong> agad.
-      domain_blocking_html:
-        few: Tha thu an impis suas ri <strong>%{count} àrainnean</strong> o <strong>%{filename}</strong> a <strong>bhacadh</strong>.
-        one: Tha thu an impis suas ri <strong>%{count} àrainn</strong> o <strong>%{filename}</strong> a <strong>bhacadh</strong>.
-        other: Tha thu an impis suas ri <strong>%{count} àrainn</strong> o <strong>%{filename}</strong> a <strong>bhacadh</strong>.
-        two: Tha thu an impis suas ri <strong>%{count} àrainn</strong> o <strong>%{filename}</strong> a <strong>bhacadh</strong>.
-      following_html:
-        few: Tha thu an impis suas ri <strong>%{count} cunntasan</strong> o <strong>%{filename}</strong> a <strong>leantainn</strong>.
-        one: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a <strong>leantainn</strong>.
-        other: Tha thu an impis suas ri <strong>%{count} cunntas</strong> o <strong>%{filename}</strong> a <strong>leantainn</strong>.
-        two: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a <strong>leantainn</strong>.
-      lists_html:
-        few: Tha thu an impis suas ri <strong>%{count} cunntasan</strong> o <strong>%{filename}</strong> a chur ris na <strong>liostaichean</strong> agad. Thèid liostaichean ùra a chruthachadh mur eil liostaichean ann airson nan cunntasan a chur ris.
-        one: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a chur ris na <strong>liostaichean</strong> agad. Thèid liostaichean ùra a chruthachadh mur eil liostaichean ann airson nan cunntasan a chur ris.
-        other: Tha thu an impis suas ri <strong>%{count} cunntas</strong> o <strong>%{filename}</strong> a chur ris na <strong>liostaichean</strong> agad. Thèid liostaichean ùra a chruthachadh mur eil liostaichean ann airson nan cunntasan a chur ris.
-        two: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a chur ris na <strong>liostaichean</strong> agad. Thèid liostaichean ùra a chruthachadh mur eil liostaichean ann airson nan cunntasan a chur ris.
-      muting_html:
-        few: Tha thu an impis suas ri <strong>%{count} cunntasan</strong> o <strong>%{filename}</strong> a <strong>mhùchadh</strong>.
-        one: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a <strong>mhùchadh</strong>.
-        other: Tha thu an impis suas ri <strong>%{count} cunntas</strong> o <strong>%{filename}</strong> a <strong>mhùchadh</strong>.
-        two: Tha thu an impis suas ri <strong>%{count} chunntas</strong> o <strong>%{filename}</strong> a <strong>mhùchadh</strong>.
+      blocking_html: Tha thu an impis suas ri <strong>%{total_items} cunntas(an)</strong> o <strong>%{filename}</strong> a <strong>bhacadh</strong>.
+      bookmarks_html: Tha thu an impis suas ri <strong>%{total_items} post(aichean)</strong> o <strong>%{filename}</strong> a chur ris na h-<strong>annsachdan</strong> agad.
+      domain_blocking_html: Tha thu an impis suas ri <strong>%{total_items} àrainn(ean)</strong> o <strong>%{filename}</strong> a <strong>bhacadh</strong>.
+      following_html: Tha thu an impis suas ri <strong>%{total_items} cunntas(an)</strong> o <strong>%{filename}</strong> a <strong>leantainn</strong>.
+      lists_html: Tha thu an impis suas ri <strong>%{total_items} cunntas(an)</strong> o <strong>%{filename}</strong> a chur ris na <strong>liostaichean</strong> agad. Thèid liostaichean ùra a chruthachadh mur eil liostaichean ann airson nan cunntasan a chur ris.
+      muting_html: Tha thu an impis suas ri <strong>%{total_items} cunntas(an)</strong> o <strong>%{filename}</strong> a <strong>mhùchadh</strong>.
     preface: "’S urrainn dhut dàta ion-phortadh a dh’às-phortaich thu o fhrithealaiche eile, can liosta nan daoine a leanas tu no a tha thu a’ bacadh."
     recent_imports: Ion-phortaidhean o chionn goirid
     states:
@@ -1749,6 +1690,7 @@ gd:
   scheduled_statuses:
     over_daily_limit: Chaidh thu thar na crìoch de %{limit} post(aichean) sgeidealaichte an-diugh
     over_total_limit: Chaidh thu thar na crìoch de %{limit} post(aichean) sgeidealaichte
+    too_soon: Feumaidh ceann-latha an sgeideil a bhith san àm ri teachd
   self_destruct:
     lead_html: Gu mì-fhortanach, thèid <strong>%{domain}</strong> a dhùnadh gu buan. Ma tha cunntas agad ann, chan urrainn dhut cumail a’ gol ’ga chleachdadh ach ’s urrainn dhut lethbhreac-glèidhidh dhen dàta agad iarraidh fhathast.
     title: Tha am frithealaiche seo gu bhith dùnadh
@@ -1830,7 +1772,7 @@ gd:
       user_domain_block: Bhac thu %{target_name}
     lost_followers: An luchd-leantainn a chaill thu
     lost_follows: Daoine nach lean thu tuilleadh
-    preamble: Dh’fhaoidte gun caill thu dàimhean leantainn nuair a bhacas tu àrainn no nuair a chuireas na maoir romhpa gun cuir iad frithealaiche cèin à rèim. Nuair a thachras sin, ’s urrainn dhut liosta de na dàimhean dealaichte a luchdadh a-nuas airson sùil a thoirt orra agus an ion-phortadh gu frithealaiche eile ’s dòcha.
+    preamble: Dh’fhaoidte gun chaill thu dàimhean leantainn nuair a bhacas tu àrainn no nuair a chuireas na maoir romhpa gun cuir iad frithealaiche cèin à rèim. Nuair a thachras sin, ’s urrainn dhut liosta de na dàimhean dealaichte a luchdadh a-nuas airson sùil a thoirt orra agus an ion-phortadh gu frithealaiche eile ’s dòcha.
     purged: Chaidh am fiosrachadh mun fhrithealaiche seo a phurgaideachadh le rianairean an fhrithealaiche agad.
     type: Tachartas
   statuses:
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index ed51799e8a..6c6e2a1bdd 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -187,7 +187,6 @@ gl:
         create_domain_block: Crear bloquedo do Dominio
         create_email_domain_block: Crear Bloqueo de Dominio do correo
         create_ip_block: Crear regra IP
-        create_relay: Crear Repetidor
         create_unavailable_domain: Crear dominio Non dispoñible
         create_user_role: Crear Rol
         demote_user: Degradar usuaria
@@ -199,22 +198,18 @@ gl:
         destroy_email_domain_block: Eliminar bloqueo do dominio do correo
         destroy_instance: Purgar Dominio
         destroy_ip_block: Eliminar regra IP
-        destroy_relay: Eliminar Repetidor
         destroy_status: Eliminar publicación
         destroy_unavailable_domain: Eliminar dominio Non dispoñible
         destroy_user_role: Eliminar Rol
         disable_2fa_user: Desactivar 2FA
         disable_custom_emoji: Desactivar emoticona personalizada
-        disable_relay: Desactivar Repetidor
         disable_sign_in_token_auth_user: Desactivar Autenticación por Token no Correo para a Usuaria
         disable_user: Desactivar usuaria
         enable_custom_emoji: Activar emoticona personalizada
-        enable_relay: Activar Repetidor
         enable_sign_in_token_auth_user: Activar Autenticación con Token no Correo para a Usuaria
         enable_user: Activar usuaria
         memorialize_account: Transformar en conta conmemorativa
         promote_user: Promover usuaria
-        publish_terms_of_service: Publicar os Termos do Servizo
         reject_appeal: Rexeitar apelación
         reject_user: Rexeitar Usuaria
         remove_avatar_user: Eliminar avatar
@@ -252,7 +247,6 @@ gl:
         create_domain_block_html: "%{name} bloqueou o dominio %{target}"
         create_email_domain_block_html: "%{name} bloqueou o dominio de correo %{target}"
         create_ip_block_html: "%{name} creou regra para o IP %{target}"
-        create_relay_html: "%{name} creou un repetidor en %{target}"
         create_unavailable_domain_html: "%{name} deixou de interactuar co dominio %{target}"
         create_user_role_html: "%{name} creou o rol %{target}"
         demote_user_html: "%{name} degradou a usuaria %{target}"
@@ -264,22 +258,18 @@ gl:
         destroy_email_domain_block_html: "%{name} desbloqueou o dominio de correo %{target}"
         destroy_instance_html: "%{name} purgou o dominio %{target}"
         destroy_ip_block_html: "%{name} eliminou a regra para o IP %{target}"
-        destroy_relay_html: "%{name} eliminou o repetidor %{target}"
         destroy_status_html: "%{name} eliminou a publicación de %{target}"
         destroy_unavailable_domain_html: "%{name} retomou a interacción co dominio %{target}"
         destroy_user_role_html: "%{name} eliminou o rol %{target}"
         disable_2fa_user_html: "%{name} desactivou o requerimento do segundo factor para a usuaria %{target}"
         disable_custom_emoji_html: "%{name} desactivou o emoji %{target}"
-        disable_relay_html: "%{name} desactivou o repetidor %{target}"
-        disable_sign_in_token_auth_user_html: "%{name} desactivou a autenticación por token no correo para %{target}"
+        disable_sign_in_token_auth_user_html: "%{name} desactivou a autenticación por token no email para %{target}"
         disable_user_html: "%{name} desactivou as credenciais para a usuaria %{target}"
         enable_custom_emoji_html: "%{name} activou o emoji %{target}"
-        enable_relay_html: "%{name} activou o repetidor %{target}"
-        enable_sign_in_token_auth_user_html: "%{name} activou a autenticación con token no correo para %{target}"
+        enable_sign_in_token_auth_user_html: "%{name} activou a autenticación con token no email para %{target}"
         enable_user_html: "%{name} activou as credenciais para a usuaria %{target}"
         memorialize_account_html: "%{name} convertiu a conta de %{target} nunha páxina para o recordo"
         promote_user_html: "%{name} promocionou a usuaria %{target}"
-        publish_terms_of_service_html: "%{name} actualizou os termos do servizo"
         reject_appeal_html: "%{name} rexeitou a apelación da decisión da moderación de %{target}"
         reject_user_html: "%{name} rexeitou o rexistro de %{target}"
         remove_avatar_user_html: "%{name} eliminou o avatar de %{target}"
@@ -309,7 +299,6 @@ gl:
       title: Rexistro de auditoría
       unavailable_instance: "(nome de dominio non dispoñible)"
     announcements:
-      back: Anuncios anteriores
       destroyed_msg: Anuncio eliminado de xeito correcto!
       edit:
         title: Editar anuncio
@@ -318,10 +307,6 @@ gl:
       new:
         create: Crear anuncio
         title: Novo anuncio
-      preview:
-        disclaimer: As usuarias non poden omitilas, as notificiacións por correo deberían limitarse a anuncios importantes como fugas de datos personais ou notificación do cese do servizo.
-        explanation_html: 'Vaise enviar o correo a <strong>%{display_count} usuarias</strong>. Incluirase o seguinte texto no correo:'
-        title: Previsualización da notificación do anuncio
       publish: Publicar
       published_msg: Anuncio publicado de xeito correcto!
       scheduled_for: Programado para %{time}
@@ -480,36 +465,6 @@ gl:
       new:
         title: Importar bloqueos de dominio
       no_file: Ningún ficheiro seleccionado
-    fasp:
-      debug:
-        callbacks:
-          created_at: Creado o
-          delete: Eliminar
-          ip: Enderezo IP
-          request_body: Corpo da solicitude
-          title: Datos para depuración
-      providers:
-        active: Activo
-        base_url: URL base
-        callback: Resposta
-        delete: Eliminar
-        edit: Editar provedor
-        finish_registration: Finalizar rexistro
-        name: Nome
-        providers: Provedores
-        public_key_fingerprint: Impresión dixital da chave pública
-        registration_requested: Solicitouse o rexistro
-        registrations:
-          confirm: Confirmar
-          description: Recibiches unha solicitude de rexistro desde un FASP. Rexeitaa se non iniciaches ti o proceso. Se foi iniciada por ti, compara con atención o nome e impresión dixital antes de confirmar o rexistro.
-          reject: Rexeitar
-          title: Confirmar o rexistro do FASP
-        save: Gardar
-        select_capabilities: Seleccionar funcións
-        sign_in: Acceder
-        status: Estado
-        title: Provedores de Servizos Auxiliares do Fediverso
-      title: FASP
     follow_recommendations:
       description_html: "<strong>As recomendacións de seguimento son útiles para que as novas usuarias atopen contidos interesantes</strong>. Cando unha usuaria aínda non interactuou con outras para obter recomendacións de seguimento, estas contas serán recomendadas. Variarán a diario xa que se escollen en base ao maior número de interaccións e ao contador local de seguimentos para un idioma dado."
       language: Para o idioma
@@ -863,10 +818,8 @@ gl:
       back_to_account: Volver a páxina da conta
       back_to_report: Volver a denuncias
       batch:
-        add_to_report: 'Engadir á denuncia #%{id}'
         remove_from_report: Eliminar da denuncia
         report: Denuncia
-      contents: Contidos
       deleted: Eliminado
       favourites: Favoritas
       history: Historial de versións
@@ -875,17 +828,13 @@ gl:
       media:
         title: Medios
       metadata: Metadatos
-      no_history: Esta publicación foi editada
       no_status_selected: Non se cambiou ningunha publicación xa que ningunha foi seleccionada
       open: Abrir publicación
       original_status: Publicación orixinal
       reblogs: Promocións
-      replied_to_html: Respondeu a %{acct_link}
       status_changed: Publicación editada
-      status_title: Publicación de @%{name}
-      title: Publicacións da conta - @%{name}
+      title: Publicacións da conta
       trending: Popular
-      view_publicly: Ver publicamente
       visibility: Visibilidade
       with_media: con medios
     strikes:
@@ -962,36 +911,6 @@ gl:
       search: Buscar
       title: Cancelos
       updated_msg: Actualizaronse os axustes dos cancelos
-    terms_of_service:
-      back: Volver aos termos do servizo
-      changelog: Que cambios se fixeron
-      create: Usa os teus propios
-      current: Actuais
-      draft: Borrador
-      generate: Usar un modelo
-      generates:
-        action: Crear
-        chance_to_review_html: "<strong>Vanse publicar automaticamente os termos do servizo creados.</strong> Terás a oportunidade de revisar o resultado. Por favor completa os detalles precisos para continuar."
-        explanation_html: O modelo dos termos do servizo proporcionados é soamente informativo, e en ningún caso constitúe un consello legal. Por favor realiza unha consulta legal sobre a túa situación concreta e posibles cuestións legais que debas afrontar.
-        title: Configurar os Termos do Servizo
-      going_live_on_html: Aplicados, con efecto desde %{date}
-      history: Historial
-      live: Actuais
-      no_history: Non hai rexistrados cambios nos termos do servizo.
-      no_terms_of_service_html: Actualmente non tes configurados ningúns termos do servizo. Os Termos do servizo están pensados para dar claridade e protexerte de responsabilidades potenciais nas disputas coas túas usuarias.
-      notified_on_html: Informouse ás usuarias o %{date}
-      notify_users: Informar ás usuarias
-      preview:
-        explanation_html: 'Vaise enviar un correo a <strong>%{display_count} usuarias</strong> que crearon a conta antes do %{date}. Incluirase o seguinte texto no correo:'
-        send_preview: Enviar vista previa a %{email}
-        send_to_all:
-          one: Enviar %{display_count} correo
-          other: Enviar %{display_count} correos
-        title: Vista previa da notificación sobre os termos do servizo
-      publish: Publicar
-      published_on_html: Publicados o %{date}
-      save_draft: Gardar borrador
-      title: Termos do Servizo
     title: Administración
     trends:
       allow: Permitir
@@ -1199,6 +1118,7 @@ gl:
     migrate_account: Mover a unha conta diferente
     migrate_account_html: Se queres redirixir esta conta hacia outra diferente, podes <a href="%{path}">facelo aquí</a>.
     or_log_in_with: Ou accede con
+    privacy_policy_agreement_html: Lin e acepto a <a href="%{privacy_policy_path}" target="_blank">política de privacidade</a>
     progress:
       confirm: Confirmar correo
       details: Detalles
@@ -1223,7 +1143,7 @@ gl:
     set_new_password: Estabelecer novo contrasinal
     setup:
       email_below_hint_html: Mira no cartafol do spam, ou solicita outra. Podes cambiar o enderzo de correo se non é correcto.
-      email_settings_hint_html: Preme na ligazón que enviamos a %{email} para comezar a usar Mastodon. Agardámoste.
+      email_settings_hint_html: Preme na ligazón que che enviamos para verificar %{email}. Agardamos por ti.
       link_not_received: Non recibiches a ligazón?
       new_confirmation_instructions_sent: Nuns minutos recibirás un novo correo electrónico coa ligazón de confirmación!
       title: Mira a caixa de entrada
@@ -1232,7 +1152,7 @@ gl:
       title: Accede a %{domain}
     sign_up:
       manual_review: As novas contas en %{domain} son comprobadas manualmente pola moderación. Para axudarnos a xestionar o teu rexistro, escribe algo acerca de ti e por que queres unha conta en %{domain}.
-      preamble: Cunha conta neste servidor Mastodon poderás seguir a calquera outra persoa no fediverso, independentemente de onde estivese hospedada esa conta.
+      preamble: Cunha conta neste servidor Mastodon poderás seguir a calquera outra persoa na rede, independentemente de onde estivese hospedada esa conta.
       title: Imos crear a túa conta en %{domain}.
     status:
       account_status: Estado da conta
@@ -1244,8 +1164,6 @@ gl:
       view_strikes: Ver avisos anteriores respecto da túa conta
     too_fast: Formulario enviado demasiado rápido, inténtao outra vez.
     use_security_key: Usa chave de seguridade
-    user_agreement_html: Lin e acepto os <a href="%{terms_of_service_path}" target="_blank">termos do servizo</a> e a <a href="%{privacy_policy_path}" target="_blank">política de privacidade</a>
-    user_privacy_agreement_html: Lin e acepto a <a href="%{privacy_policy_path}" target="_blank">política de privacidade</a>
   author_attribution:
     example_title: Texto de mostra
     hint_html: Escribes novas ou artigos nun blog alleos a Mastodon? Xestiona o xeito en que podes dar crédito da túa autoría cando os compartes en Mastodon.
@@ -1334,7 +1252,7 @@ gl:
   errors:
     '400': A solicitude que enviou non é válida ou ten formato incorrecto.
     '403': Non ten permiso para ver esta páxina.
-    '404': A páxina que buscas non está aquí.
+    '404': A páxina que está a buscar non está aquí.
     '406': Esta páxina non está dispoñible no formato solicitado.
     '410': A páxina que estaba a buscar xa non existe.
     '422':
@@ -1451,43 +1369,19 @@ gl:
       overwrite: Sobreescribir
       overwrite_long: Sustituír rexistros actuais cos novos
     overwrite_preambles:
-      blocking_html:
-        one: Vas <strong>substituír a lista de bloqueos</strong> por <strong>%{count} conta</strong> desde <strong>%{filename}</strong>.
-        other: Vas <strong>substituír a lista de bloqueos</strong> por <strong>%{count} contas</strong> desde <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Vas <strong>substituír os marcadores</strong> por <strong>%{count} publicación</strong> desde <strong>%{filename}</strong>.
-        other: Vas <strong>substituír os marcadores</strong> por <strong>%{count} publicacións</strong> desde <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Vas <strong>substituír a lista de dominios bloqueados</strong> por <strong>%{count} dominio</strong> desde <strong>%{filename}</strong>.
-        other: Vas <strong>substituír a lista de dominios bloqueados</strong> por <strong>%{count} dominios</strong> desde <strong>%{filename}</strong>.
-      following_html:
-        one: Vas <strong>seguir</strong> a <strong>%{count} conta</strong> desde <strong>%{filename}</strong> e deixar de seguir a <strong>todas as outras contas</strong>.
-        other: Vas <strong>seguir</strong> a <strong>%{count} contas</strong> desde <strong>%{filename}</strong> e deixar de seguir a <strong>todas as outras contas</strong>.
-      lists_html:
-        one: Vas <strong>substituír as túas listas</strong> co contido de <strong>%{filename}</strong>. Vaise engadir <strong>%{count} conta</strong> ás novas listas.
-        other: Vas <strong>substituír as túas listas</strong> co contido de <strong>%{filename}</strong>. Vanse engadir <strong>%{count} contas</strong> ás novas listas.
-      muting_html:
-        one: Vas <strong>substituír a lista de contas acaladas</strong> por <strong>%{count} conta</strong> desde <strong>%{filename}</strong>.
-        other: Vas <strong>substituír a lista de contas acaladas</strong> por <strong>%{count} contas</strong> desde <strong>%{filename}</strong>.
+      blocking_html: Vas <strong>substituír a lista de bloqueos</strong> por <strong>%{total_items} contas</strong> desde <strong>%{filename}</strong>.
+      bookmarks_html: Vas <strong>substituír os marcadores</strong> por <strong>%{total_items} publicacións</strong> desde <strong>%{filename}</strong>.
+      domain_blocking_html: Vas <strong>substituír a lista de dominios bloqueados</strong> por <strong>%{total_items} dominios</strong> desde <strong>%{filename}</strong>.
+      following_html: Vas <strong>seguir</strong> estas <strong>%{total_items} contas</strong> desde <strong>%{filename}</strong> e deixar de seguir a <strong>todas as outras contas</strong>.
+      lists_html: Vas <strong>substituír as túas listas</strong> co contido de <strong>%{filename}</strong>. Vanse engadir <strong>%{total_items} contas</strong> ás novas listas.
+      muting_html: Vas <strong>substituír a lista de contas acaladas</strong> por <strong>%{total_items} contas</strong> desde <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Vas <strong>bloquear</strong> a <strong>%{count} conta</strong> desde <strong>%{filename}</strong>.
-        other: Vas <strong>bloquear</strong> a <strong>%{count} contas</strong> desde <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Vas engadir <strong>%{count} publicación</strong> desde <strong>%{filename}</strong> aos teus <strong>marcadores</strong>.
-        other: Vas engadir <strong>%{count} publicacións</strong> desde <strong>%{filename}</strong> aos teus <strong>marcadores</strong>.
-      domain_blocking_html:
-        one: Vas <strong>bloquear</strong> a <strong>%{count} dominio</strong> desde <strong>%{filename}</strong>.
-        other: Vas <strong>bloquear</strong> a <strong>%{count} dominios</strong> desde <strong>%{filename}</strong>.
-      following_html:
-        one: Vas <strong>seguir</strong> a <strong>%{count} conta</strong> desde <strong>%{filename}</strong>.
-        other: Vas <strong>seguir</strong> a <strong>%{count} contas</strong> desde <strong>%{filename}</strong>.
-      lists_html:
-        one: Vas engadir <strong>%{count} conta</strong> desde <strong>%{filename}</strong> ás túas <strong>listas</strong>. Crearánse novas listas se non hai listas ás que engadilas.
-        other: Vas engadir <strong>%{count} contas</strong> desde <strong>%{filename}</strong> ás túas <strong>listas</strong>. Crearánse novas listas se non hai listas ás que engadilas.
-      muting_html:
-        one: Vas <strong>acalar</strong> a <strong>%{count} conta</strong> desde <strong>%{filename}</strong>.
-        other: Vas <strong>acalar</strong> a <strong>%{count} contas</strong> desde <strong>%{filename}</strong>.
+      blocking_html: Vas <strong>bloquear</strong> estas <strong>%{total_items} contas</strong> desde <strong>%{filename}</strong>.
+      bookmarks_html: Vas engadir <strong>%{total_items} publicacións</strong> desde <strong>%{filename}</strong> aos teus <strong>marcadores</strong>.
+      domain_blocking_html: Vas <strong>bloquear</strong> estes <strong>%{total_items} dominios</strong> desde <strong>%{filename}</strong>.
+      following_html: Vas <strong>seguir</strong> estas <strong>%{total_items} contas</strong> desde <strong>%{filename}</strong>.
+      lists_html: Vas engadir <strong>%{total_items} contas</strong> desde <strong>%{filename}</strong> ás túas <strong>listas</strong>. Crearánse novas listas se non hai listas ás que engadilas.
+      muting_html: Vas <strong>acalar</strong> estas <strong>%{total_items} contas</strong> desde <strong>%{filename}</strong>.
     preface: Podes importar os datos que exportaches doutro servidor, tales como a lista de usuarias que estás a seguir ou bloquear.
     recent_imports: Importacións recentes
     states:
@@ -1744,7 +1638,7 @@ gl:
   scheduled_statuses:
     over_daily_limit: Excedeches o límite de %{limit} publicacións programadas para ese día
     over_total_limit: Excedeches o límite de %{limit} publicacións programadas
-    too_soon: a data ten que estar no futuro
+    too_soon: A data de programación debe estar no futuro
   self_destruct:
     lead_html: Desafortunadamente, <strong>%{domain}</strong> vai deixar de dar servizo. Se tes unha conta aquí non poderás seguir usándoa, mais podes solicitar un ficheiro cos teus datos.
     title: Este servidor vai fechar
@@ -1873,7 +1767,7 @@ gl:
     ignore_favs: Ignorar favoritas
     ignore_reblogs: Ignorar promocións
     interaction_exceptions: Excepcións baseadas en interaccións
-    interaction_exceptions_explanation: Ten en conta que non hai garantía de que se eliminen as túas publicacións se baixan do límite de promocións ou favorecementos se nalgún momento o tivese superado.
+    interaction_exceptions_explanation: Ten en conta que non hai garantía de que se eliminen as túas publicacións se baixan do límite de promocións ou favorecementos se nalgún momento o tivesen superado.
     keep_direct: Manter mensaxes directas
     keep_direct_hint: Non borrar ningunha das túas mensaxes directas
     keep_media: Manter publicacións que conteñen multimedia
@@ -1907,8 +1801,6 @@ gl:
       too_late: É demasiado tarde para recurrir este aviso
   tags:
     does_not_match_previous_name: non concorda co nome anterior
-  terms_of_service:
-    title: Termos do Servizo
   themes:
     contrast: Mastodon (Alto contraste)
     default: Mastodon (Escuro)
@@ -1940,10 +1832,6 @@ gl:
     recovery_instructions_html: Se perdeses o acceso ao teu teléfono, podes utilizar un dos códigos de recuperación inferiores para recuperar o acceso á conta. <strong>Garda os códigos nun lugar seguro</strong>. Por exemplo, podes imprimilos e gardalos xunto con outros documentos importantes.
     webauthn: Chaves de seguridade
   user_mailer:
-    announcement_published:
-      description: A administración de %{domain} publicou un anuncio
-      subject: Información de servizo
-      title: Información de servizo de %{domain}
     appeal_approved:
       action: Axustes da conta
       explanation: A apelación da acción contra a túa conta o %{strike_date} que enviaches o %{appeal_date} foi aprobada. A túa conta volve ao estado normal de uso.
@@ -1973,15 +1861,6 @@ gl:
       further_actions_html: Se non foches ti, aconsellámosche %{action} inmediatamente e activar o segundo factor de autenticación para manter conta segura.
       subject: Accedeuse á túa conta desde novos enderezos IP
       title: Novo acceso
-    terms_of_service_changed:
-      agreement: Se continúas a usar %{domain} aceptas estas condicións. Se non aceptas as condicións actualizadas podería rematar o acordo con %{domain} en calquera momento e eliminarse a túa conta.
-      changelog: 'Dunha ollada, aquí tes o que implican os cambios para ti:'
-      description: 'Estás a recibir este correo porque fixemos cambios nos termos do servizo en %{domain}. Estas actualizacións terán efecto desde o %{date}. Convidámoste a que revises os termos ao completo aquí:'
-      description_html: 'Estás a recibir este correo porque fixemos algúns cambios nos nosos termos do servizo en %{domain}. Estas actualizacións terán efecto desde o <strong>%{date}</strong>. Convidámoste a que leas <a href="%{path}" target="_blank">aquí as condicións actualizadas ao completo</a>:'
-      sign_off: O equipo de %{domain}
-      subject: Actualización dos nosos termos do servizo
-      subtitle: Cambiaron os termos do servizo de %{domain}
-      title: Notificación importante
     warning:
       appeal: Enviar unha apelación
       appeal_description: Se cres que esto é un erro, podes enviar un recurso á administración de %{instance}.
diff --git a/config/locales/he.yml b/config/locales/he.yml
index 457569d05a..e27339cdee 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -193,7 +193,6 @@ he:
         create_domain_block: יצירת דומיין חסום
         create_email_domain_block: יצירת חסימת דומיין דוא"ל
         create_ip_block: יצירת כלל IP
-        create_relay: יצירת ממסר
         create_unavailable_domain: יצירת דומיין בלתי זמין
         create_user_role: יצירת תפקיד
         demote_user: הורדת משתמש בדרגה
@@ -205,22 +204,18 @@ he:
         destroy_email_domain_block: מחיקת חסימת דומיין דוא"ל
         destroy_instance: טיהור דומיין
         destroy_ip_block: מחיקת כלל IP
-        destroy_relay: מחיקת ממסר
         destroy_status: מחיקת הודעה
         destroy_unavailable_domain: מחיקת דומיין בלתי זמין
         destroy_user_role: מחיקת תפקיד
         disable_2fa_user: השעיית זיהוי דו-גורמי
         disable_custom_emoji: השעיית אמוג'י מיוחד
-        disable_relay: השבתת ממסר
         disable_sign_in_token_auth_user: השעיית אסימון הזדהות בדוא"ל של משתמש
         disable_user: השעיית משתמש
         enable_custom_emoji: הפעלת אמוג'י מיוחד
-        enable_relay: החייאת ממסר
         enable_sign_in_token_auth_user: הפעלת אסימון הזדהות בדוא"ל של משתמש
         enable_user: אפשור משתמש
         memorialize_account: הנצחת חשבון
         promote_user: קידום משתמש
-        publish_terms_of_service: פרסום תנאי השירות
         reject_appeal: דחיית ערעור
         reject_user: דחיית משתמש
         remove_avatar_user: הסרת תמונת פרופיל
@@ -232,7 +227,7 @@ he:
         silence_account: הגבלת חשבון
         suspend_account: השעיית חשבון
         unassigned_report: ביטול הקצאת דו"ח
-        unblock_email_account: הסרת חסימת כתובת דוא"ל
+        unblock_email_account: ביטול חסימת כתובת דוא"ל
         unsensitive_account: ביטול Force-Sensitive לחשבון
         unsilence_account: ביטול השתקת חשבון
         unsuspend_account: ביטול השעיית חשבון
@@ -258,34 +253,29 @@ he:
         create_domain_block_html: "%{name} חסם/ה את הדומיין %{target}"
         create_email_domain_block_html: '%{name} חסם/ה את דומיין הדוא"ל %{target}'
         create_ip_block_html: "%{name} יצר/ה כלל עבור IP %{target}"
-        create_relay_html: "%{name} יצרו את הממסר %{target}"
         create_unavailable_domain_html: "%{name} הפסיק/ה משלוח לדומיין %{target}"
         create_user_role_html: "%{name} יצר את התפקיד של %{target}"
         demote_user_html: "%{name} הוריד/ה בדרגה את המשתמש %{target}"
         destroy_announcement_html: "%{name} מחק/ה את ההכרזה %{target}"
-        destroy_canonical_email_block_html: "%{name} הסירו חסימה מדואל %{target}"
+        destroy_canonical_email_block_html: "%{name} הסיר/ה חסימה מדואל %{target}"
         destroy_custom_emoji_html: "%{name} מחק אמוג'י של %{target}"
         destroy_domain_allow_html: "%{name} לא התיר/ה פדרציה עם הדומיין %{target}"
-        destroy_domain_block_html: החסימה על מתחם %{target} הוסרה ע"י %{name}
-        destroy_email_domain_block_html: הוסרה חסימת מתחם דוא"ל %{target} בידי %{name}
+        destroy_domain_block_html: "%{name} הסיר/ה חסימה מהדומיין %{target}"
+        destroy_email_domain_block_html: '%{name} הסיר/ה חסימה מדומיין הדוא"ל %{target}'
         destroy_instance_html: "%{name} טיהר/ה את הדומיין %{target}"
         destroy_ip_block_html: "%{name} מחק/ה את הכלל עבור IP %{target}"
-        destroy_relay_html: "%{name} מחקו את הממסר %{target}"
         destroy_status_html: ההודעה של %{target} הוסרה ע"י %{name}
         destroy_unavailable_domain_html: "%{name} התחיל/ה מחדש משלוח לדומיין %{target}"
         destroy_user_role_html: "%{name} ביטל את התפקיד של %{target}"
         disable_2fa_user_html: "%{name} ביטל/ה את הדרישה לאימות דו-גורמי למשתמש %{target}"
         disable_custom_emoji_html: "%{name} השבית/ה את האמוג'י %{target}"
-        disable_relay_html: "%{name} השביתו את הממסר %{target}"
         disable_sign_in_token_auth_user_html: '%{name} השבית/ה את האימות בעזרת אסימון דוא"ל עבור %{target}'
         disable_user_html: "%{name} חסם/ה כניסה מהמשתמש/ת %{target}"
         enable_custom_emoji_html: "%{name} אפשר/ה את האמוג'י %{target}"
-        enable_relay_html: "%{name} החיו את הממסר %{target}"
         enable_sign_in_token_auth_user_html: '%{name} אפשר/ה אימות בעזרת אסימון דוא"ל עבור %{target}'
         enable_user_html: "%{name} אפשר/ה כניסה עבור המשתמש %{target}"
         memorialize_account_html: "%{name} הפך/ה את חשבונו של %{target} לדף הנצחה"
         promote_user_html: "%{name} העלה בדרגה את המשתמש %{target}"
-        publish_terms_of_service_html: "%{name} פירסמו עדכון לתנאי השירות"
         reject_appeal_html: "%{name} דחו ערעור על החלטת הנהלת הקהילה מ-%{target}"
         reject_user_html: "%{name} דחו הרשמה מ-%{target}"
         remove_avatar_user_html: "%{name} הסירו את תמונת הפרופיל של %{target}"
@@ -315,7 +305,6 @@ he:
       title: ביקורת יומן
       unavailable_instance: "(שם מתחם לא זמין)"
     announcements:
-      back: חזרה להודעות
       destroyed_msg: הכרזה נמחקה בהצלחה!
       edit:
         title: עריכת הכרזה
@@ -324,10 +313,6 @@ he:
       new:
         create: יצירת הכרזה
         title: הכרזה חדשה
-      preview:
-        disclaimer: כיוון שהמשתמשים לא יכולים לבטל אותם, הודעות דוא"ל צריכות להיות מוגבלות בשימוש להודעות חשובות כגון הודעות על גניבת מידע אישי או הודעות על סגירת השרת.
-        explanation_html: 'הדואל ישלח אל <strong>%{display_count} משתמשיםות</strong>. להלן המלל שישלח בדואל:'
-        title: צפיה מקדימה בהודעה
       publish: פרסום
       published_msg: ההכרזה פורסמה בהצלחה!
       scheduled_for: מתוזמן ל-%{time}
@@ -496,36 +481,6 @@ he:
       new:
         title: יבוא רשימת שרתים חסומים
       no_file: לא נבחר קובץ
-    fasp:
-      debug:
-        callbacks:
-          created_at: תאריך יצירה
-          delete: מחיקה
-          ip: כתובת IP
-          request_body: גוף הבקשה
-          title: ניפוי תקלות בקריאות חוזרות
-      providers:
-        active: פעילים
-        base_url: קישור בסיס
-        callback: קריאה חוזרת
-        delete: מחיקה
-        edit: עריכת ספק
-        finish_registration: סיום הרשמה
-        name: שם
-        providers: ספקים
-        public_key_fingerprint: טביעת האצבע של המפתח הציבורי
-        registration_requested: נדרשת הרשמה
-        registrations:
-          confirm: אישור
-          description: קיבלת הרשמה דרך FASP. יש לדחות אותה אם לא ביקשת את ההרשמה הזו מיוזמתך. אם זו בקשה מיוזמתך, יש להשוות בהקפדה אם השם וטביעת האצבע של המפתח הציבורי תואמים לפני אישור הרישום.
-          reject: דחיה
-          title: אישור הרשמת FASP
-        save: שמירה
-        select_capabilities: בחירת יכולות
-        sign_in: כניסה
-        status: מצב
-        title: ספקי משנה לפדיוורס
-      title: פרוטוקול FASP
     follow_recommendations:
       description_html: "<strong>עקבו אחר ההמלצות על מנת לעזור למשתמשים חדשים למצוא תוכן מעניין</strong>. במידה ומשתמש לא תקשר מספיק עם משתמשים אחרים כדי ליצור המלצות מעקב, חשבונות אלה יומלצו במקום. הם מחושבים מחדש על בסיסי יומיומי מתערובת של החשבונות הפעילים ביותר עם החשבונות הנעקבים ביותר עבור שפה נתונה."
       language: עבור שפה
@@ -891,10 +846,8 @@ he:
       back_to_account: חזרה לדף החשבון
       back_to_report: חזרה לעמוד הדיווח
       batch:
-        add_to_report: להוסיף לדו"ח מספר %{id}
         remove_from_report: הסרה מהדיווח
         report: דווח
-      contents: תוכן
       deleted: מחוקים
       favourites: חיבובים
       history: היסטורית גרסאות
@@ -903,17 +856,13 @@ he:
       media:
         title: מדיה
       metadata: נתוני-מטא
-      no_history: הודעה זו לא נערכה
       no_status_selected: לא בוצעו שינויים בהודעות שכן לא נבחרו כאלו
       open: פתח הודעה
       original_status: הודעה מקורית
       reblogs: שיתופים
-      replied_to_html: בתגובה לחשבון %{acct_link}
       status_changed: הודעה שונתה
-      status_title: פרסום מאת @%{name}
-      title: פרסומי החשבון - @%{name}
+      title: הודעות החשבון
       trending: נושאים חמים
-      view_publicly: צפיה בפומבי
       visibility: נראות
       with_media: עם מדיה
     strikes:
@@ -990,38 +939,6 @@ he:
       search: חיפוש
       title: תגיות
       updated_msg: הגדרות תגיות עודכנו בהצלחה
-    terms_of_service:
-      back: חזרה אל תנאי השירות
-      changelog: מה נשתנה
-      create: הבאתי מהבית
-      current: גרסא נוכחית
-      draft: טיוטה
-      generate: שימוש בתבנית
-      generates:
-        action: לחולל
-        chance_to_review_html: "<strong>תנאי השירות שחוללו עצמונית לא יפורסמו אוטומטית.</strong> תהיה לך הזדמנות לעבור על התוצאה. יש למלא את הפרטים הבאים כדי להמשיך."
-        explanation_html: תבנית תנאי השירות סופקה לצרכי יידוע בלבד, ואין לראות בהם עצה חוקית על אף נושא. אנא התייעצו בעצמבם עם פרקליט לגבי מצבכם הייחודי ושאלות ספציפיות שעלולות להיות לכם.
-        title: הקמת מסמך תנאי השירות
-      going_live_on_html: בתוקף מתאריך %{date}
-      history: גרסאות העבר
-      live: הגרסא החיה
-      no_history: עוד לא נרשמו שינויים בתנאי השירות.
-      no_terms_of_service_html: עוד לא הוקם מסמך תנאי השירות. מסמך תנאי השירות מיועד להבהיר ולהגן עליך מאחריות חוקית במקרה של אי הסכמות מול המשתמשים שלך.
-      notified_on_html: המשתמשים קיבלו הודעה בתאריך %{date}
-      notify_users: להודיע למשתמשים
-      preview:
-        explanation_html: 'הדואל ישלח אל <strong>%{display_count} משתמשיםות</strong> שנרשמו לפני %{date}. להלן המלל שישלח בדואל:'
-        send_preview: שליחת הצצה מוקדמת אל %{email}
-        send_to_all:
-          many: שליחת %{display_count} הודעות דואל
-          one: שליחת הודעת דואל
-          other: שליחת %{display_count} הודעות דואל
-          two: שליחת שתי הודעות דואל
-        title: צפייה מוקדמת בתנאי השירות
-      publish: לפרסם
-      published_on_html: פורסם ביום %{date}
-      save_draft: שמירת טיוטה
-      title: תנאי השירות
     title: ניהול
     trends:
       allow: לאפשר
@@ -1237,6 +1154,7 @@ he:
     migrate_account: מעבר לחשבון אחר
     migrate_account_html: אם ברצונך להכווין את החשבון לעבר חשבון אחר, ניתן <a href="%{path}">להגדיר זאת כאן</a>.
     or_log_in_with: או התחבר באמצעות
+    privacy_policy_agreement_html: קראתי והסכמתי ל<a href="%{privacy_policy_path}" target="_blank">מדיניות הפרטיות</a>
     progress:
       confirm: אימות כתובת הדואל
       details: הפרטים שלך
@@ -1261,7 +1179,7 @@ he:
     set_new_password: סיסמה חדשה
     setup:
       email_below_hint_html: אנא בדקו בתיקיית הספאם, או בקשו קוד חדש. ניתן לתקן את הכתובת אם נפלה תקלדה.
-      email_settings_hint_html: יש ללחוץ על הקישורית ששלחנו אל %{email} כדי להתחיל להשתמש במסטודון. נמתין לך כאן.
+      email_settings_hint_html: לחצו על הקישור שנשלח כדי לאשר את הכתובת %{email}. אנו ממתינים פה.
       link_not_received: לא קיבלת קישור?
       new_confirmation_instructions_sent: אתם עומדים לקבל הודעת דואל חדשה עם קיש/ור אימות בדקות הקרובות!
       title: בדוק/בדקי את תיבת הדואר הנכנס שלך
@@ -1270,7 +1188,7 @@ he:
       title: התחבר אל %{domain}
     sign_up:
       manual_review: פתיחת חשבון אצל %{domain} עוברת בדיקה ידנית על ידי הצוות שלנו. כדי לסייע בתהליך הרישום שלכןם, כתבו לנו על עצמכןם ולמה אתןם רוצותים חשבון בשרת %{domain}.
-      preamble: בעזרת חשבון על שרת מסטודון זה, ניתן לעקוב אחרי כל אדם בפידרציה, ולא משנה באיזה שרת נמצא החשבון שלהם.
+      preamble: כיוון שמסטודון מבוזרת, תוכל/י להשתמש בחשבון שלך משרתי מסטודון או רשתות תואמות אחרות אם אין לך חשבון על שרת זה.
       title: הבה ניצור לך חשבון בשרת %{domain}.
     status:
       account_status: מצב חשבון
@@ -1282,8 +1200,6 @@ he:
       view_strikes: צפיה בעברות קודמות שנרשמו נגד חשבונך
     too_fast: הטופס הוגש מהר מדי, נסה/י שוב.
     use_security_key: שימוש במפתח אבטחה
-    user_agreement_html: קראתי וזו הסכמתי למסמך <a href="%{terms_of_service_path}" target="_blank">תנאי השירות</a> ו<a href="%{privacy_policy_path}" target="_blank">מדיניות הפרטיות</a>
-    user_privacy_agreement_html: קראתי והסכמתי ל<a href="%{privacy_policy_path}" target="_blank">מדיניות הפרטיות</a>
   author_attribution:
     example_title: טקסט לדוגמה
     hint_html: האם יש לך בלוג או טור חדשות שמתפרסם מחוץ למסטודון? ניתן לשלוט איך יוצג הקרדיט שלך כשמשתפים את הלינק במסטודון.
@@ -1503,67 +1419,19 @@ he:
       overwrite: דריסה
       overwrite_long: החלף רשומות נוכחיות בחדשות
     overwrite_preambles:
-      blocking_html:
-        many: אתם עומדים <strong>להחליף את רשימת החסימות</strong> עד כדי <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-        one: אתם עומדים <strong>להחליף את רשימת החסימות</strong> <strong>%{count} בחשבון אחד</strong> מהקובץ <strong>%{filename}</strong>.
-        other: אתם עומדים <strong>להחליף את רשימת החסימות</strong> עד כדי <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-        two: אתם עומדים <strong>להחליף את רשימת החסימות</strong> בעד <strong>שני חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-      bookmarks_html:
-        many: אתם עומדים <strong>להחליף את רשימת הסימניות</strong> עד כדי <strong>%{count} הודעות</strong> מהקובץ <strong>%{filename}</strong>.
-        one: אתם עומדים <strong>להחליף את רשימת הסימניות</strong><strong> בהודעה אחת</strong> מהקובץ <strong>%{filename}</strong>.
-        other: אתם עומדים <strong>להחליף את רשימת הסימניות</strong> עד כדי <strong>%{count} הודעות</strong> מהקובץ <strong>%{filename}</strong>.
-        two: אתם עומדים <strong>להחליף את רשימת הסימניות</strong> עד כדי <strong>שתי הודעות</strong> מהקובץ <strong>%{filename}</strong>.
-      domain_blocking_html:
-        many: אתם עומדים <strong>להחליף את רשימת חסימות השרתים</strong> עד כדי <strong>%{count} שרתים</strong> מהקובץ <strong>%{filename}</strong>.
-        one: אתם עומדים <strong>להחליף את רשימת חסימות השרתים</strong> <strong>בשרת אחד</strong> מהקובץ <strong>%{filename}</strong>.
-        other: אתם עומדים <strong>להחליף את רשימת חסימות השרתים</strong> עד כדי <strong>%{count} שרתים</strong> מהקובץ <strong>%{filename}</strong>.
-        two: אתם עומדים <strong>להחליף את רשימת חסימות השרתים</strong> עד כדי <strong>שני שרתים</strong> מהקובץ <strong>%{filename}</strong>.
-      following_html:
-        many: אתם עומדים <strong>לעקוב אחרי</strong> עד כדי <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong> ובמקביל <strong>להפסיק מעקב אחרי כל משתמש אחר</strong>.
-        one: אתם עומדים <strong>לעקוב</strong> אחרי <strong>חשבון אחד</strong> מהקובץ <strong>%{filename}</strong> ובמקביל <strong>להפסיק מעקב אחרי כל משתמש אחר</strong>.
-        other: אתם עומדים <strong>לעקוב אחרי</strong> עד כדי <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong> ובמקביל <strong>להפסיק מעקב אחרי כל משתמש אחר</strong>.
-        two: אתם עומדים <strong>לעקוב אחרי</strong> עד כדי <strong>שני חשבונות</strong> מהקובץ <strong>%{filename}</strong> ובמקביל <strong>להפסיק מעקב אחרי כל משתמש אחר</strong>.
-      lists_html:
-        many: הפעולה הבאה <strong>תחליף את רשימותיך</strong> בתוכן של <strong>%{filename}</strong>. עד <strong>%{count} חשבונות</strong> יתווספו לרשימות חדשות.
-        one: הפעולה הבאה <strong>תחליף את רשימותיך</strong> בתוכן של <strong>%{filename}</strong>. עד <strong>חשבון אחד</strong> יתווסף לרשימות חדשות.
-        other: הפעולה הבאה <strong>תחליף את רשימותיך</strong> בתוכן של <strong>%{filename}</strong>. עד <strong>%{count} חשבונות</strong> יתווספו לרשימות חדשות.
-        two: הפעולה הבאה <strong>תחליף את רשימותיך</strong> בתוכן של <strong>%{filename}</strong>. עד <strong>שני חשבונות</strong> יתווספו לרשימות חדשות.
-      muting_html:
-        many: אתם עומדים <strong>להחליף את רשימת ההשתקות</strong> בעד כדי <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-        one: אתם עומדים <strong>להחליף את רשימת ההשתקות</strong> ב<strong>חשבון אחד</strong> מהקובץ <strong>%{filename}</strong>.
-        other: אתם עומדים <strong>להחליף את רשימת ההשתקות</strong> בעד כדי <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-        two: אתם עומדים <strong>להחליף את רשימת ההשתקות</strong> בעד כדי <strong>שני חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
+      blocking_html: אתם עומדים <strong>להחליף את רשימת החסימות</strong> עד כדי <strong>%{total_items} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
+      bookmarks_html: אתם עומדים <strong>להחליף את רשימת הסימניות</strong> עד כדי <strong>%{total_items} הודעות</strong> מהקובץ <strong>%{filename}</strong>.
+      domain_blocking_html: אתם עומדים <strong>להחליף את רשימת חסימות השרתים</strong> עד כדי <strong>%{total_items} שרתים</strong> מהקובץ <strong>%{filename}</strong>.
+      following_html: אתם עומדים <strong>לעקוב</strong> עד כדי <strong>%{total_items} חשבונות</strong> מהקובץ <strong>%{filename}</strong> ובמקביל <strong>להפסיק מעקב אחרי כל משתמש אחר</strong>.
+      lists_html: הפעולה הבאה <strong>תחליף את רשימותיך</strong> בתוכן של <strong>%{filename}</strong>. עד <strong>%{total_items} חשבונות</strong> יתווספו לרשימות חדשות.
+      muting_html: אתם עומדים <strong>להחליף את רשימת ההשתקות</strong> עד כדי <strong>%{total_items} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        many: אתם עומדים <strong>לחסום</strong> עד <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-        one: אתם עומדים <strong>לחסום</strong><strong>חשבון אחד</strong> מהקובץ <strong>%{filename}</strong>.
-        other: אתם עומדים <strong>לחסום</strong> עד <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-        two: אתם עומדים <strong>לחסום</strong> עד <strong>שני חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-      bookmarks_html:
-        many: אתם עומדים להוסיף עד <strong>%{count} הודעות</strong> מהקובץ <strong>%{filename}</strong> לרשימת <strong>הסימניות שלכם</strong>.
-        one: אתם עומדים <strong>להוסיף</strong><strong>הודעה אחת</strong> מהקובץ <strong>%{filename}</strong> לרשימת <strong>הסימניות שלכם</strong>.
-        other: אתם עומדים להוסיף עד <strong>%{count} הודעות</strong> מהקובץ <strong>%{filename}</strong> לרשימת <strong>הסימניות שלכם</strong>.
-        two: אתם עומדים <strong>להוסיף</strong> עד <strong>שתי הודעות</strong> מהקובץ <strong>%{filename}</strong> לרשימת <strong>הסימניות שלכם</strong>.
-      domain_blocking_html:
-        many: אתם עומדים <strong>לחסום</strong> עד כדי <strong>%{count} שרתים</strong> מהקובץ <strong>%{filename}</strong>.
-        one: אתם עומדים <strong>לחסום</strong> עד <strong>שרת אחד</strong> מהקובץ <strong>%{filename}</strong>.
-        other: אתם עומדים <strong>לחסום</strong> עד כדי <strong>%{count} שרתים</strong> מהקובץ <strong>%{filename}</strong>.
-        two: אתם עומדים <strong>לחסום</strong> עד כדי <strong>שני שרתים</strong> מהקובץ <strong>%{filename}</strong>.
-      following_html:
-        many: אתם עומדים <strong>לעקוב</strong> אחרי עד <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-        one: אתם עומדים <strong>לעקוב</strong> אחרי עד <strong>חשבון אחד</strong> מהקובץ <strong>%{filename}</strong>.
-        other: אתם עומדים <strong>לעקוב</strong> אחרי עד <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-        two: אתם עומדים <strong>לעקוב</strong> אחרי עד <strong>שני חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-      lists_html:
-        many: הפעולה הבאה תוסיף עד <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong> אל ה<strong>רשימות</strong> שלך. רשימות חדשות יווצרו אם עוד לא קיימת רשימה להוסיף אליה.
-        one: הפעולה הבאה תוסיף עד <strong>חשבון אחד</strong> מהקובץ <strong>%{filename}</strong> אל ה<strong>רשימות</strong> שלך. רשימות חדשות יווצרו אם עוד לא קיימת רשימה להוסיף אליה.
-        other: הפעולה הבאה תוסיף עד <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong> אל ה<strong>רשימות</strong> שלך. רשימות חדשות יווצרו אם עוד לא קיימת רשימה להוסיף אליה.
-        two: הפעולה הבאה תוסיף עד <strong>שני חשבונות</strong> מהקובץ <strong>%{filename}</strong> אל ה<strong>רשימות</strong> שלך. רשימות חדשות יווצרו אם עוד לא קיימת רשימה להוסיף אליה.
-      muting_html:
-        many: אתם עומדים <strong>להשתיק</strong> עד <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-        one: אתם עומדים <strong>להשתיק</strong> עד <strong>חשבון אחד</strong> מהקובץ <strong>%{filename}</strong>.
-        other: אתם עומדים <strong>להשתיק</strong> עד <strong>%{count} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
-        two: אתם עומדים <strong>להשתיק</strong> עד <strong>שני חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
+      blocking_html: אתם עומדים <strong>לחסום</strong> עד <strong>%{total_items} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
+      bookmarks_html: אתם עומדים <strong>להוסיף</strong> עד <strong>%{total_items} הודעות</strong> מהקובץ <strong>%{filename}</strong> לרשימת <strong>הסימניות שלכם</strong>.
+      domain_blocking_html: אתם עומדים <strong>לחסום</strong> עד כדי <strong>%{total_items} שרתים</strong> מהקובץ <strong>%{filename}</strong>.
+      following_html: אתם עומדים <strong>לעקוב</strong> אחרי עד <strong>%{total_items} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
+      lists_html: הפעולה הבאה תוסיף עד <strong>%{total_items} חשבונות</strong> מהקובץ <strong>%{filename}</strong> אל ה<strong>רשימות</strong> שלך. רשימות חדשות יווצרו אם עוד לא קיימת רשימה להוסיף אליה.
+      muting_html: אתם עומדים <strong>להשתיק</strong> עד <strong>%{total_items} חשבונות</strong> מהקובץ <strong>%{filename}</strong>.
     preface: ניתן ליבא מידע מסויים כגון כל הנעקבים או המשתמשים החסומים לתוך חשבונך על שרת זה, מתוך קבצים שנוצרו על ידי יצוא משרת אחר כגון רשימת הנעקבים והחסומים שלך.
     recent_imports: ייבואים אחרונים
     states:
@@ -1822,7 +1690,7 @@ he:
   scheduled_statuses:
     over_daily_limit: חרגת מהמספר המקסימלי של הודעות מתוזמנות להיום, שהוא %{limit}
     over_total_limit: חרגת מהמספר המקסימלי של הודעות מתוזמנות, שהוא %{limit}
-    too_soon: התאריך חייב להיות עתידי
+    too_soon: תאריך התזמון חייב להיות בעתיד
   self_destruct:
     lead_html: לרוע המזל, <strong>%{domain}</strong> עומד לרדת באופן סופי. אם היה לך חשבון כאן, לא תהיה אפשרות להמשיך להשתמש בו, אבל ניתן לבקש גיבוי של כל המידע שלך.
     title: שרת זה בתהליכי סגירה
@@ -1993,8 +1861,6 @@ he:
       too_late: מאוחר מדי להגיש ערעור
   tags:
     does_not_match_previous_name: לא תואם את השם האחרון
-  terms_of_service:
-    title: תנאי השירות
   themes:
     contrast: מסטודון (ניגודיות גבוהה)
     default: מסטודון (כהה)
@@ -2026,10 +1892,6 @@ he:
     recovery_instructions_html: במידה והגישה למכשירך תאבד, ניתן לייצר קודי אחזור למטה על מנת לאחזר גישה לחשבונך בכל עת. <strong>נא לשמור על קודי הגישה במקום בטוח</strong>. לדוגמא על ידי הדפסתם ושמירתם עם מסמכים חשובים אחרים, או שימוש בתוכנה ייעודית לניהול סיסמאות וסודות.
     webauthn: מפתחות אבטחה
   user_mailer:
-    announcement_published:
-      description: 'צוות ההנהלה של %{domain} מפרסם הודעה:'
-      subject: הודעת שירות
-      title: הודעת שירות מאת %{domain}
     appeal_approved:
       action: הגדרות חשבון
       explanation: הערעור על העברה שנרשמה כנגד חשבונך ב-%{strike_date} שהגשת ב-%{appeal_date} התקבל. חשבונך חזר להיות נקי מכל רבב.
@@ -2059,15 +1921,6 @@ he:
       further_actions_html: אם לא את/ה התחברת, אנו ממליצים שתבצע/י %{action} מיידית ותאפשר/י אימות דו גורמי על מנת לשמור על החשבון בטוח.
       subject: נרשמה גישה לחשבונך מכתובת IP חדשה
       title: התחברות חדשה
-    terms_of_service_changed:
-      agreement: עם המשך השימוש בשרת %{domain} אתן מסכימות לתנאים הללו. אם אינכם מסכימים עם עדכוני תנאי השירות, אתן יכולות להפסיק את ההסכם עם %{domain} בכל עת על ידי מחיקת החשבון.
-      changelog: 'בקצרה, הנה משמעות העדכון עבורך:'
-      description: 'קיבלת הודעת דואל זו כיוון שאנו מבצעים שינויים במסמך תנאי השירות של %{domain}. השינויים יכנסו לתוקף בתאריך %{date}. אנו מעודדים אותך לעבור על השינויים במסמך המלא כאן:'
-      description_html: קיבלת הודעת דואל זו כיוון שאנו מבצעים שינויים במסמך תנאי השירות של %{domain}. השינויים יכנסו לתוקף בתאריך <strong>%{date}</strong>. אנו מעודדים אותך לעבור על <a href="%{path}" target="_blank">השינויים במסמך המלא כאן</a>.
-      sign_off: צוות %{domain}
-      subject: עדכונים לתנאי השירות שלנו
-      subtitle: מסמך תנאי השירות של %{domain} עוברים שינויים
-      title: עדכון חשוב
     warning:
       appeal: הגשת ערעור
       appeal_description: אם את/ה מאמין/ה שזו טעות, ניתן להגיש ערעור לצוות של %{instance}.
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index ef5d8d97e6..84fcc55354 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -187,7 +187,6 @@ hu:
         create_domain_block: Domain tiltás létrehozása
         create_email_domain_block: E-mail-domain-tiltás létrehozása
         create_ip_block: IP szabály létrehozása
-        create_relay: Továbbító létrehozása
         create_unavailable_domain: Elérhetetlen domain létrehozása
         create_user_role: Szerepkör létrehozása
         demote_user: Felhasználó lefokozása
@@ -199,22 +198,18 @@ hu:
         destroy_email_domain_block: E-mail-domain-tiltás törlése
         destroy_instance: Domain végleges törlése
         destroy_ip_block: IP szabály törlése
-        destroy_relay: Továbbító törlése
         destroy_status: Bejegyzés törlése
         destroy_unavailable_domain: Elérhetetlen domain törlése
         destroy_user_role: Szerepkör eltávolítása
         disable_2fa_user: Kétlépcsős hitelesítés letiltása
         disable_custom_emoji: Egyéni emodzsi letiltása
-        disable_relay: Továbbító letiltása
         disable_sign_in_token_auth_user: A felhasználó tokenes e-mail-hitelesítésének letiltása
         disable_user: Felhasználói letiltása
         enable_custom_emoji: Egyéni emodzsi engedélyezése
-        enable_relay: Továbbító engedélyezése
         enable_sign_in_token_auth_user: A felhasználó tokenes e-mail-hitelesítésének engedélyezése
         enable_user: Felhasználó engedélyezése
         memorialize_account: Fiók emlékké nyilvánítása
         promote_user: Felhasználó előléptetése
-        publish_terms_of_service: Felhasználási feltételek közzététele
         reject_appeal: Fellebbezés elutasítása
         reject_user: Felhasználó Elutasítása
         remove_avatar_user: Profilkép eltávolítása
@@ -252,7 +247,6 @@ hu:
         create_domain_block_html: "%{name} letiltotta a %{target} domaint"
         create_email_domain_block_html: "%{name} letiltotta a(z) %{target} e-mail-domaint"
         create_ip_block_html: "%{name} létrehozta a(z) %{target} IP-címre vonatkozó szabályt"
-        create_relay_html: "%{name} létrehozta az átirányítót: %{target}"
         create_unavailable_domain_html: "%{name} leállította a kézbesítést a %{target} domainbe"
         create_user_role_html: "%{name} létrehozta a(z) %{target} szerepkört"
         demote_user_html: "%{name} lefokozta %{target} felhasználót"
@@ -264,22 +258,18 @@ hu:
         destroy_email_domain_block_html: "%{name} engedélyezte a(z) %{target} e-mail-domaint"
         destroy_instance_html: "%{name} véglegesen törölte a(z) %{target} domaint"
         destroy_ip_block_html: "%{name} törölte a(z) %{target} IP-címre vonatkozó szabályt"
-        destroy_relay_html: "%{name} törölte az átirányítót: %{target}"
         destroy_status_html: "%{name} eltávolította %{target} felhasználó bejegyzését"
         destroy_unavailable_domain_html: "%{name} újraindította a kézbesítést a %{target} domainbe"
         destroy_user_role_html: "%{name} törölte a(z) %{target} szerepkört"
         disable_2fa_user_html: "%{name} kikapcsolta a kétlépcsős hitelesítést %{target} felhasználó fiókján"
         disable_custom_emoji_html: "%{name} letiltotta az emodzsit: %{target}"
-        disable_relay_html: "%{name} letiltotta az átirányítót: %{target}"
         disable_sign_in_token_auth_user_html: "%{name} letiltotta a tokenes e-mail-hitelesítést %{target} felhasználóra"
         disable_user_html: "%{name} letiltotta %{target} felhasználó bejelentkezését"
         enable_custom_emoji_html: "%{name} engedélyezte az emodzsit: %{target}"
-        enable_relay_html: "%{name} engedélyezte az átirányítót: %{target}"
         enable_sign_in_token_auth_user_html: "%{name} engedélyezte a tokenes e-mail-hitelesítést %{target} felhasználóra"
         enable_user_html: "%{name} engedélyezte %{target} felhasználó bejelentkezését"
         memorialize_account_html: "%{name} emléket állított %{target} felhasználónak"
         promote_user_html: "%{name} előléptette %{target} felhasználót"
-        publish_terms_of_service_html: "%{name} frissítette a felhasználási feltételeket"
         reject_appeal_html: "%{name} visszautasított egy fellebbezést %{target} moderátori döntéséről"
         reject_user_html: "%{name} elutasította %{target} regisztrációját"
         remove_avatar_user_html: "%{name} törölte %{target} profilképét"
@@ -309,7 +299,6 @@ hu:
       title: Audit napló
       unavailable_instance: "(domain név nem elérhető)"
     announcements:
-      back: Vissza a bejelentésekhez
       destroyed_msg: A közlemény sikeresen törölve!
       edit:
         title: Közlemény szerkesztése
@@ -318,10 +307,6 @@ hu:
       new:
         create: Közlemény létrehozása
         title: Új közlemény
-      preview:
-        disclaimer: Mivel a felhasználók nem iratkozhatnak le róluk, az e-mailes értesítéseket érdemes a fontos bejelentésekre korlátozni, mint a személyes adatokat érintő adatvédelmi incidensek vagy a kiszolgáló bezárására vonatkozó értesítések.
-        explanation_html: 'Az e-mail <strong>%{display_count} felhasználónak</strong> lesz elküldve. A következő szöveg fog szerepelni a levélben:'
-        title: Közleményértesítés előnézete
       publish: Közzététel
       published_msg: A közlemény sikeresen publikálva!
       scheduled_for: Ekkorra ütemezve %{time}
@@ -480,36 +465,6 @@ hu:
       new:
         title: Domain tiltások importálása
       no_file: Nincs fájl kiválasztva
-    fasp:
-      debug:
-        callbacks:
-          created_at: Létrehozva
-          delete: Törlés
-          ip: IP-cím
-          request_body: Kérés törzse
-          title: Hibakeresési visszahívások
-      providers:
-        active: Aktív
-        base_url: Alapwebcím
-        callback: Visszahívás
-        delete: Törlés
-        edit: Szolgáltató szerkesztése
-        finish_registration: Regisztráció befejezése
-        name: Név
-        providers: Szolgáltatók
-        public_key_fingerprint: Nyilvános kulcs ujjlenyomata
-        registration_requested: Regisztráció kérve
-        registrations:
-          confirm: Megerősítés
-          description: Regisztrációt kaptál egy FASP-től. Ha nem te kérted, utasítsd el. Ha te kérted, akkor a regisztráció megerősítése előtt figyelmesen hasonlítsd össze a nevét és a kulcsának ujjlenyomatát.
-          reject: Elutasítás
-          title: FASP regisztráció megerősítése
-        save: Mentés
-        select_capabilities: Képességek kiválasztása
-        sign_in: Bejelentkezés
-        status: Állapot
-        title: A Födiverzum segédszolgáltatói
-      title: FASP
     follow_recommendations:
       description_html: "<strong>A követési ajánlatok segítik az új felhasználókat az érdekes tartalmak gyors megtalálásában</strong>. Ha egy felhasználó még nem érintkezett eleget másokkal ahhoz, hogy személyre szabott ajánlatokat kapjon, ezeket a fiókokat ajánljuk helyette. Ezeket naponta újraszámítjuk a nemrég legtöbb embert foglalkoztató, illetve legtöbb helyi követővel rendelkező fiókok alapján."
       language: Ezen a nyelven
@@ -863,10 +818,8 @@ hu:
       back_to_account: Vissza a fiók oldalára
       back_to_report: Vissza a bejelentés oldalra
       batch:
-        add_to_report: 'Hozzáadás ehhez a jelentéshez: #%{id}'
         remove_from_report: Eltávolítás a bejelentésből
         report: Bejelentés
-      contents: Tartalom
       deleted: Törölve
       favourites: Kedvencek
       history: Verziótörténet
@@ -875,17 +828,13 @@ hu:
       media:
         title: Média
       metadata: Metaadatok
-      no_history: Ez a bejegyzés szerkesztve lett
       no_status_selected: Nem változtattunk meg egy bejegyzést sem, mert semmi sem volt kiválasztva
       open: Bejegyzés megnyitása
       original_status: Eredeti bejegyzés
       reblogs: Megosztások
-      replied_to_html: 'Válasz neki: %{acct_link}'
       status_changed: A bejegyzés megváltozott
-      status_title: 'Szerző: @%{name}'
-      title: Fiók bejegyzései – @%{name}
+      title: Fiók bejegyzései
       trending: Felkapott
-      view_publicly: Megtekintés nyilvánosan
       visibility: Láthatóság
       with_media: Médiával
     strikes:
@@ -962,36 +911,6 @@ hu:
       search: Keresés
       title: Hashtagek
       updated_msg: A hashtag beállításokat sikeresen frissítettük
-    terms_of_service:
-      back: Vissza a felhasználási feltételekhez
-      changelog: Mi változott
-      create: Saját használata
-      current: Jelenlegi
-      draft: Piszkozat
-      generate: Sablon használata
-      generates:
-        action: Előállítás
-        chance_to_review_html: "<strong>Az előállított felhasználási feltételek nem lesznek automatikusan közzétéve.</strong> Előtte áttekintheted az eredményt. A folytatáshoz töltsd ki a szükséges részleteket."
-        explanation_html: A felhasználási feltételek sablonja csak információs célokat szolgál, és semmilyen témában nem tekinthető jogi tanácsadásnak. Konzultálj a saját jogi tanácsadóddal a helyzetedről és a felmerülő konkrét jogi kérdésekről.
-        title: Felhasználási feltételek beállítása
-      going_live_on_html: 'Éles, hatálybalépés dátuma: %{date}'
-      history: Előzmények
-      live: Élő
-      no_history: Még nincsenek változtatások a szolgáltatási feltételekben.
-      no_terms_of_service_html: Még nincsenek felhasználási feltételek beállítva. A felhasználási feltételek célja, hogy egyértelműsítsen, és megvédjen a felelősségvállalásoktól a felhasználókkal szembeni lehetséges viták során.
-      notified_on_html: 'Felhasználók értesítve: %{date}'
-      notify_users: Felhasználók értesítése
-      preview:
-        explanation_html: 'Az e-mail <strong>%{display_count} felhasználónak</strong> lesz elküldve (akik %{date} előtt regisztráltak). A következő szöveg fog szerepelni a levélben:'
-        send_preview: 'Előnézet elküldése ide: %{email}'
-        send_to_all:
-          one: "%{display_count} e-mail elküldése"
-          other: "%{display_count} e-mail elküldése"
-        title: Felhasználási feltételek értesítésének előnézete
-      publish: Közzététel
-      published_on_html: 'Közzétéve: %{date}'
-      save_draft: Piszkozat mentése
-      title: Felhasználási feltételek
     title: Karbantartás
     trends:
       allow: Engedélyezés
@@ -1199,6 +1118,7 @@ hu:
     migrate_account: Felhasználói fiók költöztetése
     migrate_account_html: Ha át szeretnéd irányítani ezt a fiókodat egy másikra, akkor <a href="%{path}">itt állíthatod be</a>.
     or_log_in_with: Vagy jelentkezz be ezzel
+    privacy_policy_agreement_html: Elolvastam és egyetértek az <a href="%{privacy_policy_path}" target="_blank">adatvédemi nyilatkozattal</a>
     progress:
       confirm: E-mail megerősítése
       details: Saját adatok
@@ -1223,7 +1143,7 @@ hu:
     set_new_password: Új jelszó beállítása
     setup:
       email_below_hint_html: Nézd meg a levélszemét mappát, vagy kérj egy újat. Módosíthatod az e-mail-címet, ha az hibás.
-      email_settings_hint_html: A Mastodon használatának megkezdéséhez kattints a(z) %{email} címre küldött hivatkozásra. Itt fogunk várni.
+      email_settings_hint_html: Kattints a hivatkozásra, melyet a(z) %{email} megerősítése céljából küldtünk. Addig várni fogunk.
       link_not_received: Nem kaptad meg a hivatkozást?
       new_confirmation_instructions_sent: Néhány perc múlva új e-mailt fogsz kapni a megerősítési hivatkozással.
       title: Bejövő postaláda ellenőrzése
@@ -1232,7 +1152,7 @@ hu:
       title: 'Bejelentkezés ide: %{domain}'
     sign_up:
       manual_review: A(z) %{domain} regisztrációi a moderátorok kézi felülvizsgálatán mennek át. Hogy segítsd a regisztráció feldolgozását, írj röviden magadról, és hogy miért szeretnél fiókot a(z) %{domain} oldalon.
-      preamble: Egy fiókkal ezen a Mastodon kiszolgálón követhetsz bárkit a födiverzumban, függetlenül attól, hogy az illető fiókja melyik kiszolgálón található.
+      preamble: Egy fiókkal ezen a Mastodon-kiszolgálón követhetsz bárkit a hálózaton, függetlenül attól, hogy az illető fiókja melyik kiszolgálón található.
       title: Állítsuk be a fiókod a %{domain} kiszolgálón.
     status:
       account_status: Fiók állapota
@@ -1244,8 +1164,6 @@ hu:
       view_strikes: Fiókod ellen felrótt korábbi vétségek megtekintése
     too_fast: Túl gyorsan küldted el az űrlapot, próbáld később.
     use_security_key: Biztonsági kulcs használata
-    user_agreement_html: Elolvastam és egyetértek a <a href="%{terms_of_service_path}" target="_blank">felhasználási feltételekkel</a> és az <a href="%{privacy_policy_path}" target="_blank">adatvédelmi nyilatkozattal</a>
-    user_privacy_agreement_html: Elolvastam és egyetértek az <a href="%{privacy_policy_path}" target="_blank">adatvédemi nyilatkozattal</a>
   author_attribution:
     example_title: Mintaszöveg
     hint_html: Mastodonon kívül írsz híreket vagy blogbejegyzéseket? Szabályozd, hogyan tüntethetnek fel szerzőként, amikor Mastodonon osztják meg őket.
@@ -1451,43 +1369,19 @@ hu:
       overwrite: Felülírás
       overwrite_long: Lecseréljük újakkal a jelenlegi bejegyzéseket
     overwrite_preambles:
-      blocking_html:
-        one: 'Arra készülsz, hogy <strong>lecseréld a letiltási listát</strong> legfeljebb <strong>%{count} fiókra</strong> a következőből: <strong>%{filename}</strong>.'
-        other: 'Arra készülsz, hogy <strong>lecseréld a letiltási listát</strong> legfeljebb <strong>%{count} fiókra</strong> a következőből: <strong>%{filename}</strong>.'
-      bookmarks_html:
-        one: 'Arra készülsz, hogy <strong>lecseréld a könyvjelzőket</strong> legfeljebb <strong>%{count} bejegyzésre</strong> a következőből: <strong>%{filename}</strong>.'
-        other: 'Arra készülsz, hogy <strong>lecseréld a könyvjelzőket</strong> legfeljebb <strong>%{count} bejegyzésre</strong> a következőből: <strong>%{filename}</strong>.'
-      domain_blocking_html:
-        one: 'Arra készülsz, hogy <strong>lecseréld a domain letiltási listát</strong> legfeljebb <strong>%{count} domainre</strong> a következőből: <strong>%{filename}</strong>.'
-        other: 'Arra készülsz, hogy <strong>lecseréld a domain letiltási listát</strong> legfeljebb <strong>%{count} domainre</strong> a következőből: <strong>%{filename}</strong>.'
-      following_html:
-        one: 'Arra készülsz, hogy legfeljebb <strong>%{count} fiókot</strong> <strong>kövess</strong> a következőből: <strong>%{filename}</strong>, és <strong>abbahagyd mindenki más követését</strong>.'
-        other: 'Arra készülsz, hogy legfeljebb <strong>%{count} fiókot</strong> <strong>kövess</strong> a következőből: <strong>%{filename}</strong>, és <strong>abbahagyd mindenki más követését</strong>.'
-      lists_html:
-        one: Arra készülsz, hogy <strong>a listákat lecseréld</strong> a <strong>%{filename}</strong> tartalmával. Legfeljebb <strong>%{count} fiók</strong> kerül fel az új listákra.
-        other: Arra készülsz, hogy <strong>a listákat lecseréld</strong> a <strong>%{filename}</strong> tartalmával. Legfeljebb <strong>%{count} fiók</strong> kerül fel az új listákra.
-      muting_html:
-        one: 'Arra készülsz, hogy <strong>lecseréld a némított fiókok listáját</strong> legfeljebb <strong>%{count} fiókra</strong> a következőből: <strong>%{filename}</strong>.'
-        other: 'Arra készülsz, hogy <strong>lecseréld a némított fiókok listáját</strong> legfeljebb <strong>%{count} fiókra</strong> a következőből: <strong>%{filename}</strong>.'
+      blocking_html: 'Arra készülsz, hogy <strong>lecseréld a letiltási listát</strong> legfeljebb <strong>%{total_items} fiókra</strong> a következőből: <strong>%{filename}</strong>.'
+      bookmarks_html: 'Arra készülsz, hogy <strong>lecseréld a könyvjelzőket</strong> legfeljebb <strong>%{total_items} bejegyzésre</strong> a következőből: <strong>%{filename}</strong>.'
+      domain_blocking_html: 'Arra készülsz, hogy <strong>lecseréld a domain letiltási listát</strong> legfeljebb <strong>%{total_items} domainre</strong> a következőből: <strong>%{filename}</strong>.'
+      following_html: 'Arra készülsz, hogy legfeljebb <strong>%{total_items} fiókot</strong> <strong>kövess</strong> a következőből: <strong>%{filename}</strong>, és <strong>abbahagyd mindenki más követését</strong>.'
+      lists_html: Arra készülsz, hogy <strong>a listákat lecseréld</strong> a <strong>%{filename}</strong> tartalmával. Legfeljebb <strong>%{total_items} fiók</strong> kerül fel az új listákra.
+      muting_html: 'Arra készülsz, hogy <strong>lecseréld a némított fiókok listáját</strong> legfeljebb <strong>%{total_items} fiókra</strong> a következőből: <strong>%{filename}</strong>.'
     preambles:
-      blocking_html:
-        one: 'Arra készülsz, hogy legfeljebb <strong>%{count} fiókot</strong> <strong>letilts</strong> a következőből: <strong>%{filename}</strong>.'
-        other: 'Arra készülsz, hogy legfeljebb <strong>%{count} fiókot</strong> <strong>letilts</strong> a következőből: <strong>%{filename}</strong>.'
-      bookmarks_html:
-        one: 'Arra készülsz, hogy legfeljebb <strong>%{count} bejegyzést</strong> adj hozzá a <strong>könyvjelzőkhöz</strong> a következőből: <strong>%{filename}</strong>.'
-        other: 'Arra készülsz, hogy legfeljebb <strong>%{count} bejegyzést</strong> adj hozzá a <strong>könyvjelzőkhöz</strong> a következőből: <strong>%{filename}</strong>.'
-      domain_blocking_html:
-        one: 'Arra készülsz, hogy legfeljebb <strong>%{count} domaint</strong> <strong>letilts</strong> a következőből: <strong>%{filename}</strong>.'
-        other: 'Arra készülsz, hogy legfeljebb <strong>%{count} domaint</strong> <strong>letilts</strong> a következőből: <strong>%{filename}</strong>.'
-      following_html:
-        one: 'Arra készülsz, hogy legfeljebb <strong>%{count} fiókot</strong> <strong>kövess</strong> a következőből: <strong>%{filename}</strong>.'
-        other: 'Arra készülsz, hogy legfeljebb <strong>%{count} fiókot</strong> <strong>kövess</strong> a következőből: <strong>%{filename}</strong>.'
-      lists_html:
-        one: Arra készülsz, hogy legfeljebb <strong>%{count} fiókot</strong> hozzáadj a <strong>%{filename}</strong> fájlból a <strong>listákhoz</strong>. Új listák jönnek létre, ha nincs hozzáadható lista.
-        other: Arra készülsz, hogy legfeljebb <strong>%{count} fiókot</strong> hozzáadj a <strong>%{filename}</strong> fájlból a <strong>listákhoz</strong>. Új listák jönnek létre, ha nincs hozzáadható lista.
-      muting_html:
-        one: 'Arra készülsz, hogy legfeljebb <strong>%{count} fiókot</strong> <strong>némíts</strong> a következőből: <strong>%{filename}</strong>.'
-        other: 'Arra készülsz, hogy legfeljebb <strong>%{count} fiókot</strong> <strong>némíts</strong> a következőből: <strong>%{filename}</strong>.'
+      blocking_html: 'Arra készülsz, hogy legfeljebb <strong>%{total_items} fiókot</strong> <strong>letilts</strong> a következőből: <strong>%{filename}</strong>.'
+      bookmarks_html: 'Arra készülsz, hogy legfeljebb <strong>%{total_items} bejegyzést</strong> adj hozzá a <strong>könyvjelzőkhöz</strong> a következőből: <strong>%{filename}</strong>.'
+      domain_blocking_html: 'Arra készülsz, hogy legfeljebb <strong>%{total_items} domaint</strong> <strong>letilts</strong> a következőből: <strong>%{filename}</strong>.'
+      following_html: 'Arra készülsz, hogy legfeljebb <strong>%{total_items} fiókot</strong> <strong>kövess</strong> a következőből: <strong>%{filename}</strong>.'
+      lists_html: Arra készülsz, hogy legfeljebb <strong>%{total_items} fiókot</strong> hozzáadj a <strong>%{filename}</strong> fájlból a <strong>listákhoz</strong>. Új listák jönnek létre, ha nincs hozzáadható lista.
+      muting_html: 'Arra készülsz, hogy legfeljebb <strong>%{total_items} fiókot</strong> <strong>némíts</strong> a következőből: <strong>%{filename}</strong>.'
     preface: Itt importálhatod egy másik kiszolgálóról lementett adataidat, például követettjeid és letiltott felhasználóid listáját.
     recent_imports: Legutóbbi importálások
     states:
@@ -1744,7 +1638,7 @@ hu:
   scheduled_statuses:
     over_daily_limit: Túllépted az időzített bejegyzésekre vonatkozó %{limit} db-os napi limitet
     over_total_limit: Túllépted az időzített bejegyzésekre vonatkozó %{limit} db-os limitet
-    too_soon: a dátumnak a jövőben kell lennie
+    too_soon: Az időzített időpontnak a jövőben kell lennie
   self_destruct:
     lead_html: Sajnos a <strong>%{domain}</strong> végleg bezár. Ha volt itt fiókod, nem fogod tudni tovább használni, de kérheted majd az adataid biztonsági mentését.
     title: A kiszolgáló bezár
@@ -1907,8 +1801,6 @@ hu:
       too_late: Túl késő, hogy fellebbezd ezt a felrótt vétséget
   tags:
     does_not_match_previous_name: nem illeszkedik az előző névvel
-  terms_of_service:
-    title: Felhasználási feltételek
   themes:
     contrast: Mastodon (nagy kontrasztú)
     default: Mastodon (sötét)
@@ -1940,10 +1832,6 @@ hu:
     recovery_instructions_html: A visszaállítási kódok egyikének segítségével tudsz majd belépni, ha elveszítenéd a telefonod. <strong>Tartsd biztos helyen a visszaállítási kódjaid</strong>! Például nyomtasd ki őket és tárold a többi fontos iratoddal együtt.
     webauthn: Biztonsági kulcsok
   user_mailer:
-    announcement_published:
-      description: 'A(z) %{domain} adminisztrátorai a következő bejelentést teszik:'
-      subject: Szolgáltatási közlemény
-      title: A(z) %{domain} szolgáltatási közleménye
     appeal_approved:
       action: Fiók Beállításai
       explanation: A fiókod %{appeal_date}-i fellebbezése, mely a %{strike_date}-i vétségeddel kapcsolatos, jóváhagyásra került. A fiókod megint makulátlan.
@@ -1973,15 +1861,6 @@ hu:
       further_actions_html: Ha nem te voltál, akkor azt javasoljuk, hogy azonnal %{action} és engedélyezd a kétlépcsős hitelesítést, hogy biztonságban tudd a fiókodat.
       subject: A fiókodat egy új IP-címről érték el
       title: Új bejelentkezés
-    terms_of_service_changed:
-      agreement: A(z) %{domain} használatának folytatásával beleegyezel ezekbe a feltételekbe. Ha nem értesz egyet a frissített feltételekkel, akkor a fiókod törlésével megszakíthatod a(z) %{domain} weboldallal való megállapodásodat.
-      changelog: 'Dióhéjban ez a frissítés ezt jelenti számodra:'
-      description: 'Azért kapod ezt az e-mailt, mert változtatunk a(z) %{domain} felhasználási feltételein. A változások ekkor lépnek életbe: %{date}. Javasoljuk, hogy tekintsd át a frissített feltételeket teljes egészében itt:'
-      description_html: 'Azért kapod ezt az e-mailt, mert változtatunk a(z) %{domain} felhasználási feltételein. A változások ekkor lépnek életbe: <strong>%{date}</strong>. Javasoljuk, hogy tekintsd át a <a href="%{path}" target="_blank">frissített feltételeket teljes egészében itt</a>.'
-      sign_off: A(z) %{domain} csapata
-      subject: A felhasználási feltételei frissítései
-      subtitle: A(z) %{domain} felhasználási feltételei megváltoznak
-      title: Fontos frissítés
     warning:
       appeal: Fellebbezés beküldése
       appeal_description: Ha azt gondolod, hogy ez hibás, beküldhetsz egy fellebbezést a(z) %{instance} szerver csapatának.
diff --git a/config/locales/hy.yml b/config/locales/hy.yml
index dfbbe73e2a..201922d102 100644
--- a/config/locales/hy.yml
+++ b/config/locales/hy.yml
@@ -402,6 +402,7 @@ hy:
       deleted: Ջնջված է
       media:
         title: Մեդիա
+      title: Օգտատիրոջ գրառումները
       with_media: Մեդիայի հետ
     tags:
       review: Վերանայել գրառումը
@@ -462,6 +463,7 @@ hy:
     logout: Դուրս գալ
     migrate_account: Տեղափոխուել այլ հաշիւ
     or_log_in_with: Կամ մուտք գործել օգտագործելով՝
+    privacy_policy_agreement_html: Ես կարդացել եւ ընդունել եմ <a href="%{privacy_policy_path}" target="_blank">գաղնիութեան քաղաքականութիւնը</a>
     progress:
       details: Ձեր տուեալները
       review: Վաւերացում
@@ -481,6 +483,7 @@ hy:
       title: Մտնել %{domain}
     sign_up:
       manual_review: Գրանցումները %{domain}-ում վաւերացնում են մոդերատորնրը։ Մեզ օգնելու համար մի փոքր պատմէք ձեր մասին եւ թե ինչու էք ուզում գրանցուել։
+      preamble: Այս հանգոյցում հաշիւ ունենալով դուք կարող էք հերտեւել դաշնեզերքի ցանկացած օգտատիրոջ, անկախ նրանից թե որտեղ է նրա հաշիւը տեղակայուած։
       title: Ստեղծի՜ր հաշիւ %{domain}-ում
     status:
       account_status: Հաշուի կարգավիճակ
@@ -709,6 +712,8 @@ hy:
     remove_selected_followers: Հեռացնել նշուած հետեւորդներին
     remove_selected_follows: Ապահետեւել նշուած օգտատէրերին
     status: Հաշուի կարգավիճակ
+  scheduled_statuses:
+    too_soon: Նախադրուած ամսաթիւը պէտք է լինի ապագայում
   sessions:
     activity: Վերջին Ակտիւութիւնը
     browser: Դիտարկիչ
diff --git a/config/locales/ia.yml b/config/locales/ia.yml
index 760bddb4d7..f631d318a8 100644
--- a/config/locales/ia.yml
+++ b/config/locales/ia.yml
@@ -187,7 +187,6 @@ ia:
         create_domain_block: Crear blocada de dominio
         create_email_domain_block: Crear blocada de dominio de e-mail
         create_ip_block: Crear un regula IP
-        create_relay: Crear repetitor
         create_unavailable_domain: Crear dominio indisponibile
         create_user_role: Crear un rolo
         demote_user: Degradar usator
@@ -199,22 +198,18 @@ ia:
         destroy_email_domain_block: Deler blocada de dominio de e-mail
         destroy_instance: Purgar dominio
         destroy_ip_block: Deler le regula IP
-        destroy_relay: Deler repetitor
         destroy_status: Deler message
         destroy_unavailable_domain: Deler dominio indisponibile
         destroy_user_role: Destruer rolo
         disable_2fa_user: Disactivar A2F
         disable_custom_emoji: Disactivar emoji personalisate
-        disable_relay: Disactivar repetitor
         disable_sign_in_token_auth_user: Disactivar le authentication per token de e-mail pro le usator
         disable_user: Disactivar le usator
         enable_custom_emoji: Activar emoji personalisate
-        enable_relay: Activar repetitor
         enable_sign_in_token_auth_user: Activar le authentication per token de e-mail pro le usator
         enable_user: Activar le usator
         memorialize_account: Converter conto in memorial
         promote_user: Promover usator
-        publish_terms_of_service: Publicar le conditiones de servicio
         reject_appeal: Rejectar appello
         reject_user: Rejectar usator
         remove_avatar_user: Remover avatar
@@ -252,7 +247,6 @@ ia:
         create_domain_block_html: "%{name} blocava dominio %{target}"
         create_email_domain_block_html: "%{name} blocava le dominio de e-mail %{target}"
         create_ip_block_html: "%{name} creava regula pro IP %{target}"
-        create_relay_html: "%{name} ha create un repetitor %{target}"
         create_unavailable_domain_html: "%{name} stoppava livration al dominio %{target}"
         create_user_role_html: "%{name} creava rolo de %{target}"
         demote_user_html: "%{name} degradava usator %{target}"
@@ -264,22 +258,18 @@ ia:
         destroy_email_domain_block_html: "%{name} disblocava le dominio de e-mail %{target}"
         destroy_instance_html: "%{name} purgava le dominio %{target}"
         destroy_ip_block_html: "%{name} deleva le regula pro IP %{target}"
-        destroy_relay_html: "%{name} ha delite le repetitor %{target}"
         destroy_status_html: "%{name} removeva un message de %{target}"
         destroy_unavailable_domain_html: "%{name} reprendeva le livration al dominio %{target}"
         destroy_user_role_html: "%{name} deleva le rolo %{target}"
         disable_2fa_user_html: "%{name} disactivava le authentication a duo factores pro le usator %{target}"
         disable_custom_emoji_html: "%{name} disactivava le emoji %{target}"
-        disable_relay_html: "%{name} ha disactivate le repetitor %{target}"
         disable_sign_in_token_auth_user_html: "%{name} disactivava le authentication per token de e-mail pro %{target}"
         disable_user_html: "%{name} disactivava le apertura de session pro le usator %{target}"
         enable_custom_emoji_html: "%{name} activava le emoji %{target}"
-        enable_relay_html: "%{name} ha activate le repetitor %{target}"
         enable_sign_in_token_auth_user_html: "%{name} activava le authentication per token de e-mail pro %{target}"
         enable_user_html: "%{name} activava le apertura de session pro le usator %{target}"
         memorialize_account_html: "%{name} converteva le conto de %{target} in un pagina commemorative"
         promote_user_html: "%{name} promoveva le usator %{target}"
-        publish_terms_of_service_html: "%{name} ha actualisate le conditiones de servicio"
         reject_appeal_html: "%{name} refusava le appello del decision de moderation de %{target}"
         reject_user_html: "%{name} refusava le inscription de %{target}"
         remove_avatar_user_html: "%{name} removeva le avatar de %{target}"
@@ -828,10 +818,8 @@ ia:
       back_to_account: Retornar al pagina del conto
       back_to_report: Retro al pagina de reporto
       batch:
-        add_to_report: 'Adder al reporto #%{id}'
         remove_from_report: Remover del reporto
         report: Reporto
-      contents: Contento
       deleted: Delite
       favourites: Favorites
       history: Historia de versiones
@@ -840,17 +828,13 @@ ia:
       media:
         title: Multimedia
       metadata: Metadatos
-      no_history: Iste message non ha essite modificate
       no_status_selected: Necun message ha essite cambiate perque necun ha essite seligite
       open: Aperir message
       original_status: Message original
       reblogs: Republicationes
-      replied_to_html: Respondite a %{acct_link}
       status_changed: Message cambiate
-      status_title: Message de @%{name}
-      title: Messages del conto – @%{name}
+      title: Messages del conto
       trending: Tendentias
-      view_publicly: Vider publicamente
       visibility: Visibilitate
       with_media: Con multimedia
     strikes:
@@ -927,35 +911,6 @@ ia:
       search: Cercar
       title: Hashtags
       updated_msg: Parametros de hashtag actualisate con successo
-    terms_of_service:
-      back: Retornar al conditiones de servicio
-      changelog: Lo que ha cambiate
-      create: Usar tu proprie
-      current: Actual
-      draft: Provisori
-      generate: Usar modello
-      generates:
-        action: Generar
-        chance_to_review_html: "<strong>Le conditiones de servicio generate non essera publicate automaticamente.</strong> Tu habera le opportunitate de revider le resultatos. Per favor completa le detalios necessari pro continuar."
-        explanation_html: Le modello de conditiones de servicio es fornite solmente pro fines informative e non debe esser interpretate como consilio juridic sur alcun subjecto. Per favor consulta tu proprie consiliero juridic pro omne questiones juridic concrete concernente tu situation.
-        title: Configuration del conditiones de servicio
-      history: Historia
-      live: In uso
-      no_history: Il non ha ancora modificationes registrate del conditiones de servicio.
-      no_terms_of_service_html: Tu non ha actualmente configurate alcun conditiones de servicio. Le conditiones de servicio es visate a fornir claritiate e a proteger te contra possibile responsabilitates in caso de litigios con tu usatores.
-      notified_on_html: Usatores notificate le %{date}
-      notify_users: Notificar usatores
-      preview:
-        explanation_html: 'Le message de e-mail essera inviate a <strong>%{display_count} usatores</strong> qui se ha inscribite ante le %{date}. Illo includera le sequente texto:'
-        send_preview: Inviar previsualisation a %{email}
-        send_to_all:
-          one: Inviar %{display_count} message de e-mail
-          other: Inviar %{display_count} messages de e-mail
-        title: Previsualisar le notification sur le conditiones de servicio
-      publish: Publicar
-      published_on_html: Publicate le %{date}
-      save_draft: Salvar version provisori
-      title: Conditiones de servicio
     title: Administration
     trends:
       allow: Permitter
@@ -1163,6 +1118,7 @@ ia:
     migrate_account: Migrar a un altere conto
     migrate_account_html: Si tu vole rediriger iste conto a un altere, tu pote <a href="%{path}">configurar lo hic</a>.
     or_log_in_with: O aperi session con
+    privacy_policy_agreement_html: Io ha legite e accepta le <a href="%{privacy_policy_path}" target="_blank">politica de confidentialitate</a>
     progress:
       confirm: Confirmar e-mail
       details: Tu detalios
@@ -1187,7 +1143,7 @@ ia:
     set_new_password: Definir un nove contrasigno
     setup:
       email_below_hint_html: Consulta tu dossier de spam, o requesta un altere ligamine de confirmation. Tu pote corriger tu adresse de e-mail si illo es errate.
-      email_settings_hint_html: Clicca sur le ligamine que nos te ha inviate a %{email} pro comenciar a usar Mastodon. Nos te attendera hic.
+      email_settings_hint_html: Clicca sur le ligamine que nos te ha inviate pro verificar %{email}. Nos te attendera hic.
       link_not_received: Necun ligamine recipite?
       new_confirmation_instructions_sent: Tu recipera un nove e-mail con le ligamine de confirmation in poc minutas!
       title: Consulta tu cassa de entrata
@@ -1196,7 +1152,7 @@ ia:
       title: Aperir session sur %{domain}
     sign_up:
       manual_review: Le inscriptiones sur %{domain} passa per un revision manual de nostre moderatores. Pro adjutar nos a processar tu inscription, per favor scribe un poco sur te e explica proque tu vole un conto sur %{domain}.
-      preamble: Con un conto sur iste servitor de Mastodon, tu potera sequer qualcunque altere persona sur le Fediverso, independentemente de ubi su conto es albergate.
+      preamble: Con un conto sur iste servitor de Mastodon, tu potera sequer qualcunque altere persona sur le rete, independentemente de ubi su conto es albergate.
       title: Lassa nos installar tu conto sur %{domain}.
     status:
       account_status: Stato del conto
@@ -1208,8 +1164,6 @@ ia:
       view_strikes: Examinar le sanctiones passate contra tu conto
     too_fast: Formulario inviate troppo rapidemente. Tenta lo de novo.
     use_security_key: Usar clave de securitate
-    user_agreement_html: Io ha legite e accepta le <a href="%{terms_of_service_path}" target="_blank">conditiones de servicio</a> e le <a href="%{privacy_policy_path}" target="_blank">politica de confidentialitate</a>
-    user_privacy_agreement_html: Io ha legite e io accepta le <a href="%{privacy_policy_path}" target="_blank">politica de confidentialitate</a>
   author_attribution:
     example_title: Texto de exemplo
     hint_html: Scribe tu articulos de novas o de blog foras de Mastodon? Controla le maniera in que tu recipe attribution quando on los condivide sur Mastodon.
@@ -1415,43 +1369,19 @@ ia:
       overwrite: Superscriber
       overwrite_long: Reimplaciar registros actual con le noves
     overwrite_preambles:
-      blocking_html:
-        one: Tu va <strong>reimplaciar tu lista de blocada</strong> con usque <strong>%{count} conto</strong> ab <strong>%{filename}</strong>.
-        other: Tu va <strong>reimplaciar tu lista de blocada</strong> con usque <strong>%{count} contos</strong> ab <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Tu va <strong>reimplaciar tu marcapaginas</strong> con usque <strong>%{count} message</strong> ab <strong>%{filename}</strong>.
-        other: Tu va <strong>reimplaciar tu marcapaginas</strong> con usque <strong>%{count} messages</strong> ab <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Tu va <strong>reimplaciar tu blocada lista de dominios blocate</strong> con usque <strong>%{count} dominio</strong> ab <strong>%{filename}</strong>.
-        other: Tu va <strong>reimplaciar tu lista de dominios blocate</strong> con usque <strong>%{count} dominios</strong> ab <strong>%{filename}</strong>.
-      following_html:
-        one: Tu va <strong>sequer</strong> usque <strong>%{count} conto</strong> ab <strong>%{filename}</strong> e <strong>cessar de sequer ulle altere</strong>.
-        other: Tu va <strong>sequer</strong> usque <strong>%{count} contos</strong> ab <strong>%{filename}</strong> e <strong>cessar de sequer ulle altere</strong>.
-      lists_html:
-        one: Tu va <strong>reimplaciar tu listas</strong> con contentos ab <strong>%{filename}</strong>. Usque <strong>%{count} conto</strong> sera addite al nove listas.
-        other: Tu va <strong>reimplaciar tu listas</strong> con contentos ab <strong>%{filename}</strong>. Usque <strong>%{count} contos</strong> sera addite a nove listas.
-      muting_html:
-        one: Tu va <strong>reimplaciar tu lista de conto silentiate</strong> con usque <strong>%{count} conto</strong> ab <strong>%{filename}</strong>.
-        other: Tu va <strong>reimplaciar tu lista de contos silentiate</strong> con usque <strong>%{count} contos</strong> ab <strong>%{filename}</strong>.
+      blocking_html: Tu es sur le puncto de <strong>reimplaciar tu lista de blocadas</strong> per usque a <strong>%{total_items} contos</strong> proveniente de <strong>%{filename}</strong>.
+      bookmarks_html: Tu es sur le puncto de <strong>reimplaciar tu marcapaginas</strong> per usque a <strong>%{total_items} messages</strong> desde <strong>%{filename}</strong>.
+      domain_blocking_html: Tu es sur le puncto de <strong>reimplaciar tu lista de blocadas de dominio</strong> per usque a <strong>%{total_items} dominios</strong> proveniente de <strong>%{filename}</strong>.
+      following_html: Tu es sur le puncto de <strong>sequer</strong> usque a <strong>%{total_items} contos</strong> de <strong>%{filename}</strong> e <strong>cessar de sequer tote le alteres</strong>.
+      lists_html: Tu es sur le puncto de <strong>reimplaciar tu listas</strong> per le contento de <strong>%{filename}</strong>. Usque a <strong>%{total_items} contos</strong> essera addite a nove listas.
+      muting_html: Tu es sur le puncto de <strong>reimplaciar tu lista de contos silentiate</strong> con usque a <strong>%{total_items} contos</strong> desde <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Tu va <strong>blocar</strong> usque <strong>%{count} conto</strong> ab <strong>%{filename}</strong>.
-        other: Tu va <strong>blocar</strong> usque <strong>%{count} contos</strong> de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Tu va adder usque <strong>%{count} message</strong> ab <strong>%{filename}</strong> a tu <strong>marcapaginas</strong>.
-        other: Tu va adder usque <strong>%{count} messages</strong> ab <strong>%{filename}</strong> a tu <strong>marcapaginas</strong>.
-      domain_blocking_html:
-        one: Tu va <strong>blocar</strong> usque <strong>%{count} dominio</strong> ab <strong>%{filename}</strong>.
-        other: Tu va <strong>blocar</strong> usque <strong>%{count} dominios</strong> ab <strong>%{filename}</strong>.
-      following_html:
-        one: Tu va <strong>sequer</strong> usque <strong>%{count} conto</strong> ab <strong>%{filename}</strong>.
-        other: Tu va <strong>sequer</strong> usque <strong>%{count} contos</strong> de <strong>%{filename}</strong>.
-      lists_html:
-        one: Tu va adder usque <strong>%{count} conto</strong> ab <strong>%{filename}</strong> a tu <strong>lista</strong>. Nove listas sera create si il non ha lista a adder.
-        other: Tu va adder usque <strong>%{count} contos</strong> ab <strong>%{filename}</strong> a tu <strong>lista</strong>. Nove listas sera create si il non ha lista a adder.
-      muting_html:
-        one: Tu va <strong>silentiar</strong> usque <strong>%{count} conto</strong> ab <strong>%{filename}</strong>.
-        other: Tu va <strong>silentiar</strong> usque <strong>%{count} contos</strong> ab <strong>%{filename}</strong>.
+      blocking_html: Tu es sur le puncto de <strong>blocar</strong> usque a <strong>%{total_items} contos</strong> a partir de <strong>%{filename}</strong>.
+      bookmarks_html: Tu es sur le puncto de adder usque a <strong>%{total_items} messages</strong> desde <strong>%{filename}</strong> a tu <strong>marcapaginas</strong>.
+      domain_blocking_html: Tu es sur le puncto de <strong>blocar</strong> usque a <strong>%{total_items} dominios</strong> a partir de <strong>%{filename}</strong>.
+      following_html: Tu es sur le puncto de <strong>sequer</strong> usque a <strong>%{total_items} contos</strong> desde <strong>%{filename}</strong>.
+      lists_html: Tu es sur le puncto de adder usque <strong>%{total_items} contos</strong> desde <strong>%{filename}</strong> a tu <strong>listas</strong>. Nove listas essera create si il non ha un lista al qual adder los.
+      muting_html: Tu es sur le puncto de <strong>silentiar</strong> usque a <strong>%{total_items} contos</strong> desde <strong>%{filename}</strong>.
     preface: Tu pote importar datos que tu ha exportate de un altere servitor, como un lista de personas que tu seque o bloca.
     recent_imports: Importationes recente
     states:
@@ -1708,7 +1638,7 @@ ia:
   scheduled_statuses:
     over_daily_limit: Tu ha excedite le limite de %{limit} messages programmate pro hodie
     over_total_limit: Tu ha excedite le limite de %{limit} messages programmate
-    too_soon: le data debe esser in le futuro
+    too_soon: Le data programmate debe esser in le futuro
   self_destruct:
     lead_html: Infortunatemente, <strong>%{domain}</strong> tosto claudera permanentemente. Si tu habeva un conto illac, tu non potera continuar a usar lo, ma tu pote ancora requestar un copia de tu datos.
     title: Iste servitor va clauder
@@ -1871,8 +1801,6 @@ ia:
       too_late: Es troppo tarde pro appellar contra iste sanction
   tags:
     does_not_match_previous_name: non corresponde al nomine precedente
-  terms_of_service:
-    title: Conditiones de servicio
   themes:
     contrast: Mastodon (Alte contrasto)
     default: Mastodon (Obscur)
@@ -1933,13 +1861,6 @@ ia:
       further_actions_html: Si non se tractava de te, nos recommenda %{action} immediatemente e activar le authentication bifactorial pro mantener tu conto secur.
       subject: Alcuno ha accedite a tu conto desde un nove adresse IP
       title: Un nove apertura de session
-    terms_of_service_changed:
-      agreement: Si tu continua a usar %{domain}, tu accepta iste conditiones. Si tu non es de accordo con le conditiones actualisate, tu pote sempre eliminar tu conto pro terminar tu accordo con %{domain}.
-      changelog: 'In summario, ecce lo que iste actualisation significa pro te:'
-      sign_off: Le equipa de %{domain}
-      subject: Actualisationes de nostre conditiones de servicio
-      subtitle: Le conditiones de servicio de %{domain} ha cambiate
-      title: Actualisation importante
     warning:
       appeal: Submitter un appello
       appeal_description: Si tu crede que se tracta de un error, tu pote presentar un appello al personal de %{instance}.
diff --git a/config/locales/id.yml b/config/locales/id.yml
index 03d25a6e53..6a005fddaa 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -711,6 +711,7 @@ id:
       original_status: Kiriman asli
       reblogs: Reblog
       status_changed: Kiriman diubah
+      title: Status akun
       trending: Sedang tren
       visibility: Visibilitas
       with_media: Dengan media
@@ -903,6 +904,7 @@ id:
     migrate_account: Pindah ke akun berbeda
     migrate_account_html: Jika Anda ingin mengalihkan akun ini ke akun lain, Anda dapat <a href="%{path}">mengaturnya di sini</a>.
     or_log_in_with: Atau masuk dengan
+    privacy_policy_agreement_html: Saya telah membaca dan menerima <a href="%{privacy_policy_path}" target="_blank">kebijakan privasi</a>
     providers:
       cas: CAS
       saml: SAML
@@ -917,6 +919,7 @@ id:
     sign_in:
       title: Masuk ke %{domain}
     sign_up:
+      preamble: Dengan sebuah akun di server Mastodon ini, Anda akan dapat mengikuti orang lain dalam jaringan, di mana pun akun mereka berada.
       title: Mari kita siapkan Anda di %{domain}.
     status:
       account_status: Status akun
@@ -1292,6 +1295,7 @@ id:
   scheduled_statuses:
     over_daily_limit: Anda telah melampaui batas %{limit} kiriman terjadwal untuk sehari
     over_total_limit: Anda telah melampaui batas %{limit} kiriman terjadwal
+    too_soon: Tanggal terjadwal haruslah pada hari yang akan datang
   sessions:
     activity: Aktivitas terakhir
     browser: Peramban
diff --git a/config/locales/ie.yml b/config/locales/ie.yml
index 4e154b3114..7e8140a374 100644
--- a/config/locales/ie.yml
+++ b/config/locales/ie.yml
@@ -791,6 +791,7 @@ ie:
       original_status: Original posta
       reblogs: Boosts
       status_changed: Posta modificat
+      title: Postas del conto
       trending: Populari
       visibility: Visibilitá
       with_media: Con medie
@@ -1039,6 +1040,7 @@ ie:
     migrate_account: Mover te a un conto diferent
     migrate_account_html: Si tu vole redirecter ti-ci conto a un altri, tu posse <a href="%{path}">configurar it ci</a>.
     or_log_in_with: O intrar med
+    privacy_policy_agreement_html: Yo leet e consenti li <a href="%{privacy_policy_path}" target="_blank">politica pri privatie</a>
     progress:
       details: Tui detallies
       review: Nor revise
@@ -1061,6 +1063,7 @@ ie:
     security: Securitá
     set_new_password: Establisser nov passa-parol
     setup:
+      email_settings_hint_html: Clicca li ligament quel noi inviat a te por verificar %{email}. Noi va atender ci.
       link_not_received: Recivet null ligament?
       title: Vider tui inbuxe
     sign_in:
@@ -1068,6 +1071,7 @@ ie:
       title: Aperter session che %{domain}
     sign_up:
       manual_review: Adhesiones a %{domain} es tractat manualmen de nor moderatores. Por auxiliar nos tractar tui aplication, scri un poc pri te e pro quo tu vole un conto che %{domain}.
+      preamble: Med un conto che ti-ci Mastodon-servitor, tu va posser sequer quelcunc altri person in li retage, sin egarda a u logia su conto.
       title: Crear un conto che %{domain}.
     status:
       account_status: Statu del conto
@@ -1270,6 +1274,20 @@ ie:
       merge_long: Conservar existent registres e adjunter li novis
       overwrite: Remplazzar
       overwrite_long: Remplazzar existent registres per li novis
+    overwrite_preambles:
+      blocking_html: Tu va <strong>remplazzar tui liste de bloccat contos</strong> per til <strong>%{total_items} contos</strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Tu va <strong>remplazzar tui marcatores</strong> per til <strong>%{total_items} postas</strong> de <strong>%{filename}</strong>.
+      domain_blocking_html: Tu va <strong>remplazzar tui liste de bloccat dominias</strong> per til <strong>%{total_items} dominias</strong> de <strong>%{filename}</strong>.
+      following_html: Tu va <strong>sequer</strong> til <strong>%{total_items} contos</strong> de <strong>%{filename}</strong> e <strong>dessequer omni altri contos</strong>.
+      lists_html: Tu va <strong>remplazzar tui listes</strong> per li contenete de <strong>%{filename}</strong>. Til <strong>%{total_items} contos</strong> va esser adjuntet a nov listes.
+      muting_html: Tu va <strong>remplazzar tui liste de silentiat contos</strong> per til <strong>%{total_items} contos</strong> de <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Tu va <strong>bloccar</strong> til <strong>%{total_items} contos</strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Tu va adjunter til <strong>%{total_items} postas</strong> de <strong>%{filename}</strong> a tui <strong>marcatores</strong>.
+      domain_blocking_html: Tu va <strong>bloccar</strong> til <strong>%{total_items} dominias</strong> de <strong>%{filename}</strong>.
+      following_html: Tu va <strong>sequer</strong> til <strong>%{total_items} contos</strong> de <strong>%{filename}</strong>.
+      lists_html: Tu va adjunter til <strong>%{total_items} contos</strong> de <strong>%{filename}</strong> a tui <strong>listes</strong>. Nov listes va esser creat si ne hay un liste a quel adjunter.
+      muting_html: Tu va <strong>silentiar</strong> til <strong>%{total_items} contos</strong> de <strong>%{filename}</strong>.
     preface: Tu posse importar data quel tu ha exportat de un altri servitor, quam un liste del gente quem tu seque o blocca.
     recent_imports: Recent importationes
     states:
@@ -1512,6 +1530,7 @@ ie:
   scheduled_statuses:
     over_daily_limit: Tu ha atinget li límite de %{limit} planat postas por hodie
     over_total_limit: Tu ha atinget li límite de %{limit} planat postas
+    too_soon: Li planat date deve esser in li future
   self_destruct:
     lead_html: Ínfortunatmen, <strong>%{domain}</strong> va bentost permanentmen cluder. Si tu havet un conto ta, tu ne va posser continuar usar it, ma tu ancor posse demandar un archive de tui data.
     title: Ti-ci servitor va cluder bentost
diff --git a/config/locales/io.yml b/config/locales/io.yml
index ee905aaf86..dfb583450a 100644
--- a/config/locales/io.yml
+++ b/config/locales/io.yml
@@ -21,19 +21,15 @@ io:
       one: Posto
       other: Posti
     posts_tab_heading: Posti
-    self_follow_error: Sequar vua sua konto es ne permisita
   admin:
     account_actions:
       action: Agez
-      already_silenced: Ca konto ja limitigesis.
-      already_suspended: Ca konto ja blokusesis.
       title: Agez jero a %{acct}
     account_moderation_notes:
       create: Pozez noto
       created_msg: Jernoto sucesoze kreesis!
       destroyed_msg: Jernoto sucesoze efacesis!
     accounts:
-      add_email_domain_block: Blokusar retpostodomeno
       approve: Aprobez
       approved_msg: Sucesoze aprobis registroapliko di %{username}
       are_you_sure: Ka tu esas certa?
@@ -48,7 +44,6 @@ io:
         title: Chanjez retposto por %{username}
       change_role:
         changed_msg: Rolo sucesoze chanjesis!
-        edit_roles: Administrar uzantoroli
         label: Chanjez rolo
         no_role: Nula rolo
         title: Chanjez rolo por %{username}
@@ -61,33 +56,31 @@ io:
       demote: Despromocez
       destroyed_msg: Informi di %{username} nun aranjesis por efacesar aparante
       disable: Frostigez
-      disable_sign_in_token_auth: Desebligar retpostofichyurizo
-      disable_two_factor_authentication: Desebligar 2FA
+      disable_two_factor_authentication: Desaktivigez 2FA
       disabled: Desinterdiktita
       display_name: Profilnomo
       domain: Domeno
-      edit: Redaktar
+      edit: Modifikez
       email: E-mail
       email_status: Retpostostando
       enable: Defrostigez
-      enable_sign_in_token_auth: Ebligar retpostofichyurizo
-      enabled: Ebligita
+      enabled: Aktivigita
       enabled_msg: Sucesoze desfrostigas konto di %{username}
       followers: Uzanti
       follows: Uzati
-      header: Fundimajo
+      header: Kapimajo
       inbox_url: URL di mesajbuxo
-      invite_request_text: Adeskmotivi
+      invite_request_text: Juntomotivo
       invited_by: Invitesis da
       ip: IP
-      joined: Adeskis
+      joined: Juntita
       location:
         all: Omna
         local: Lokala
         remote: Nelokala
         title: Loko
       login_status: Enirstando
-      media_attachments: Audvidajaddonaji
+      media_attachments: Mediiatachaji
       memorialize: Memorializez
       memorialized: Memorializita
       memorialized_msg: Sucesoze chanjesis %{username} a memorialkonto
@@ -117,15 +110,15 @@ io:
       public: Publika
       push_subscription_expires: Abono PuSH expiras
       redownload: Rifreshigez profilo
-      redownloaded_msg: Sucese rifreshigis profilo di %{username} de origino
+      redownloaded_msg: Sucesoze rifreshis profilo di %{username} de origino
       reject: Refuzez
       rejected_msg: Sucesoze refuzis registroapliko di %{username}
       remote_suspension_irreversible: La datumi di ca konto esas nerenversebla efacita.
       remote_suspension_reversible_hint_html: Ca konto restriktesis che lua servilo, e la datumi tota efacesos ye %{date}. Ante ta tempo, la fora servilo povos restaurar ca konto sen irga mala efecti. Se vu volas efacar omna datumi dil konto quik, vu povas facar lo sube.
       remove_avatar: Efacez profilimajo
-      remove_header: Forigar fundimajo
+      remove_header: Efacez kapimajo
       removed_avatar_msg: Sucesoze efacis profilimajo di %{username}
-      removed_header_msg: Sucese forigis fundimajo di %{username}
+      removed_header_msg: Sucesoze efacis kapimajo di %{username}
       resend_confirmation:
         already_confirmed: Ca uzanto ja konfirmesis
         send: Risendez konfirmligilo
@@ -135,7 +128,6 @@ io:
       resubscribe: Riabonez
       role: Rolo
       search: Trovez
-      search_same_email_domain: Altra uzanti kun la sama retpostodomeno
       search_same_ip: Altra uzanti kun sama IP
       security: Sekureso
       security_measures:
@@ -155,7 +147,7 @@ io:
       suspend: Restriktez
       suspended: Restriktita
       suspension_irreversible: Ca informi di ca konto neinversigeble efacesis. Vu povas derestriktar konto por uzebligar lu ma ol ne riganos irga dati quon ol antee havis.
-      suspension_reversible_hint_html: Ca konto blokusesis, e ca informi tota forigesos ye %{date}. Se vu volas forigar omna informi di konto quik, vu povas agar lu dessupre.
+      suspension_reversible_hint_html: Ca konto restriktesis, e ca informi tota efacesos en %{date}. Ante ta tempo, konto povas riganesar sen irga mala efekti. Se vu volas efacar omna informi di konto quik, vu povas agar lu sube.
       title: Konti
       unblock_email: Deobstruktez retpostoadreso
       unblocked_email_msg: Sucesoze deobstruktis retpostoadreso di %{username}
@@ -176,45 +168,33 @@ io:
         approve_appeal: Aprobez apelo
         approve_user: Aprobez uzanto
         assigned_to_self_report: Taskigez raporto
-        change_email_user: Chanjar retpostoadreso por Uzanto
         change_role_user: Chanjez rolo di uzanto
         confirm_user: Konfirmez uzanto
         create_account_warning: Kreez averto
         create_announcement: Kreez anunco
-        create_canonical_email_block: Krear retpostoblokuso
         create_custom_emoji: Kreez kustumizita emocimajo
         create_domain_allow: Kreez domenpermiso
         create_domain_block: Kreez domenobstrukto
-        create_email_domain_block: Krear retpostodomenblokuso
         create_ip_block: Kreez IP-regulo
-        create_relay: Krear relayo
         create_unavailable_domain: Kreez nedisponebla domeno
         create_user_role: Kreez rolo
         demote_user: Despromocez uzanto
         destroy_announcement: Efacez anunco
-        destroy_canonical_email_block: Forigar retpostoblokuso
         destroy_custom_emoji: Efacez kustumizita emocimajo
         destroy_domain_allow: Efacez domenpermiso
         destroy_domain_block: Efacez domenobstrukto
-        destroy_email_domain_block: Forigar retpostodomenblokuso
         destroy_instance: Efacez domeno
         destroy_ip_block: Efacez IP-regulo
-        destroy_relay: Forigar relayo
         destroy_status: Efacez posto
         destroy_unavailable_domain: Efacez nedisponebla domeno
         destroy_user_role: Destruktez rolo
-        disable_2fa_user: Desebligar 2FA
-        disable_custom_emoji: Desebligar personesigita emocimajo
-        disable_relay: Desebligar relayo
-        disable_sign_in_token_auth_user: Desebligar retpostofichyurizo por uzanto
-        disable_user: Desaktivigar uzanto
-        enable_custom_emoji: Ebligar personesigita emocimajo
-        enable_relay: Ebligar relayo
-        enable_sign_in_token_auth_user: Ebligar retpostofichyurizo por uzanto
+        disable_2fa_user: Desaktivigez 2FA
+        disable_custom_emoji: Desaktivigez kustumizita emocimajo
+        disable_user: Desaktivigez uzanto
+        enable_custom_emoji: Aktivigez kustumizita emocimajo
         enable_user: Aktivigez uzanto
         memorialize_account: Memorializez konto
         promote_user: Promocez uzanto
-        publish_terms_of_service: Publikigar servtermini
         reject_appeal: Refuzez apelo
         reject_user: Refuzez uzanto
         remove_avatar_user: Efacez profilimajo
@@ -234,72 +214,56 @@ io:
         update_custom_emoji: Novigez kustumizita emocimajo
         update_domain_block: Novigez domenobstrukto
         update_ip_block: Kreez IP-regulo
-        update_report: Tildatigar raporto
         update_status: Novigez posto
         update_user_role: Novigez rolo
       actions:
         approve_appeal_html: "%{name} aprobis jerdecidapelo de %{target}"
         approve_user_html: "%{name} aprobis registro de %{target}"
         assigned_to_self_report_html: "%{name} taskigis raporto %{target} a su"
-        change_email_user_html: "%{name} chanjis la retpostoadreso di uzanto %{target}"
         change_role_user_html: "%{name} chanjis rolo di %{target}"
-        confirm_user_html: "%{name} konfirmis retpostoadreso di uzanto %{target}"
         create_account_warning_html: "%{name} sendis averto a %{target}"
         create_announcement_html: "%{name} kreis nova anunco %{target}"
-        create_canonical_email_block_html: "%{name} blokusis retposto kun la greto %{target}"
-        create_custom_emoji_html: "%{name} adkargis nova emocimajo %{target}"
+        create_custom_emoji_html: "%{name} adchargis nova emocimajo %{target}"
         create_domain_allow_html: "%{name} permisis federato kun domeno %{target}"
         create_domain_block_html: "%{name} obstruktis domeno %{target}"
-        create_email_domain_block_html: "%{name} blokusis retpostodomeno %{target}"
         create_ip_block_html: "%{name} kreis regulo por IP %{target}"
-        create_relay_html: "%{name} kreis relayo %{target}"
         create_unavailable_domain_html: "%{name} cesis sendo a domeno %{target}"
         create_user_role_html: "%{name} kreis rolo di %{target}"
         demote_user_html: "%{name} despromocis uzanto %{target}"
         destroy_announcement_html: "%{name} efacis anunco %{target}"
-        destroy_canonical_email_block_html: "%{name} desblokusis retposto kun greto %{target}"
         destroy_custom_emoji_html: "%{name} efacis emocimajo %{target}"
         destroy_domain_allow_html: "%{name} despermisis federato kun domeno %{target}"
         destroy_domain_block_html: "%{name} deobstruktis domeno %{target}"
-        destroy_email_domain_block_html: "%{name} desblokusis retpostodomeno %{target}"
         destroy_instance_html: "%{name} efacis domeno %{target}"
         destroy_ip_block_html: "%{name} efacis regulo por IP %{target}"
-        destroy_relay_html: "%{name} forigis la relayo %{target}"
         destroy_status_html: "%{name} efacis posto da %{target}"
         destroy_unavailable_domain_html: "%{name} durigis sendo a domeno %{target}"
         destroy_user_role_html: "%{name} efacis rolo di %{target}"
-        disable_2fa_user_html: "%{name} desebligis dufaktora bezono por uzanto %{target}"
-        disable_custom_emoji_html: "%{name} desebligis emocimajo %{target}"
-        disable_relay_html: "%{name} desebligis la relayo %{target}"
-        disable_sign_in_token_auth_user_html: "%{name} desebligis retpostofichyurizo por %{target}"
-        disable_user_html: "%{name} desebligis eniro por uzanto %{target}"
-        enable_custom_emoji_html: "%{name} ebligis emocimajo %{target}"
-        enable_relay_html: "%{name} ebligis la relayo %{target}"
-        enable_sign_in_token_auth_user_html: "%{name} ebligis retpostofishyurizo por %{target}"
-        enable_user_html: "%{name} ebligis eniro por uzanto %{target}"
+        disable_2fa_user_html: "%{name} desaktivigis 2-faktorbezono por uzanto %{target}"
+        disable_custom_emoji_html: "%{name} desaktivigis emocimajo %{target}"
+        disable_user_html: "%{name} desaktivigis eniro por uzanto %{target}"
+        enable_custom_emoji_html: "%{name} aktivigis emocimajo %{target}"
+        enable_user_html: "%{name} aktivigis eniro por uzanto %{target}"
         memorialize_account_html: "%{name} kauzigis konto di %{target} divenar memorialpagino"
         promote_user_html: "%{name} promocis uzanto %{target}"
-        publish_terms_of_service_html: "%{name} publikigis tildatigi ad la servtermini"
         reject_appeal_html: "%{name} refuzis jerdecidapelo de %{target}"
         reject_user_html: "%{name} refuzis registro de %{target}"
         remove_avatar_user_html: "%{name} efacis profilimajo de %{target}"
         reopen_report_html: "%{name} riapertis raporto %{target}"
-        resend_user_html: "%{name} risendis konfirmretposto por %{target}"
         reset_password_user_html: "%{name} richanjis pasvorto de uzanto %{target}"
         resolve_report_html: "%{name} rezolvis raporto %{target}"
-        sensitive_account_html: "%{name} markis audvidaji di %{target} quale trublema"
+        sensitive_account_html: "%{name} markizis medii di %{target} quale sentoza"
         silence_account_html: "%{name} limitizis konto di %{target}"
         suspend_account_html: "%{name} restriktis konto di %{target}"
         unassigned_report_html: "%{name} detaskigis raporto %{target}"
         unblock_email_account_html: "%{name} deobstruktis retpostoadreso di %{target}"
-        unsensitive_account_html: "%{name} desmarkizis audvidaji di %{target} quale trublema"
+        unsensitive_account_html: "%{name} demarkizis medii di %{target} quale sentoza"
         unsilence_account_html: "%{name} efacis limito di konto di %{target}"
         unsuspend_account_html: "%{name} derestriktis konto di %{target}"
         update_announcement_html: "%{name} novigis anunco %{target}"
         update_custom_emoji_html: "%{name} novigis emocimajo %{target}"
         update_domain_block_html: "%{name} novigis domenobstrukto por %{target}"
         update_ip_block_html: "%{name} kreis regulo por IP %{target}"
-        update_report_html: "%{name} tildatigis raporto %{target}"
         update_status_html: "%{name} novigis posto da %{target}"
         update_user_role_html: "%{name} chanjis rolo di %{target}"
       deleted_account: konto efacita
@@ -307,11 +271,10 @@ io:
       filter_by_action: Filtrez segun ago
       filter_by_user: Filtrez segun uzanto
       title: Kontrollogo
-      unavailable_instance: "(domennomo nedisponebla)"
     announcements:
       destroyed_msg: Anunco sucesoze efacesas!
       edit:
-        title: Redaktar anunco
+        title: Modifikez anunco
       empty: Nula anunci.
       live: Samtempe
       new:
@@ -327,7 +290,7 @@ io:
       updated_msg: Anunco sucesoza novigesas!
     critical_update_pending: Urjanta aktualigo vartesas
     custom_emojis:
-      assign_category: Juntar kategorio
+      assign_category: Insertez kategorio
       by_domain: Domeno
       copied_msg: Sucesoze kreis lokala kopiuro di emocimajo
       copy: Kopiez
@@ -336,18 +299,18 @@ io:
       created_msg: Emocimajo sucesoze kreesas!
       delete: Efacez
       destroyed_msg: Kustumizita emocimajo sucesoza destruktesas!
-      disable: Desebligar
-      disabled: Desebligita
-      disabled_msg: Sucese desebligis ta emocimajo
+      disable: Desaktivigez
+      disabled: Desaktivigita
+      disabled_msg: Sucesoze desaktivigis ta emocimajo
       emoji: Emocimajo
-      enable: Ebligar
-      enabled: Ebligita
-      enabled_msg: Sucese ebligis ta emocimajo
+      enable: Aktivigez
+      enabled: Aktivigita
+      enabled_msg: Sucesoze aktivigis ta emocimajo
       image_hint: Maximo grandeso di PNG o GIF esas %{size}
       list: Listo
       listed: Listita
       new:
-        title: Adjuntar nova personesigita emocimajo
+        title: Insertez nova kustumizita emocimajo
       no_emoji_selected: Nula emocimaji chanjesis pro ke nulo selektesis
       not_permitted: Vu ne permisesis agar co
       overwrite: Remplasez
@@ -359,11 +322,11 @@ io:
       unlisted: Delistigita
       update_failed_msg: Ne povas novigar ta emocimajo
       updated_msg: Emocimajo sucesoze novigesis!
-      upload: Adkargar
+      upload: Adchargez
     dashboard:
       active_users: aktiva uzanti
       interactions: interagi
-      media_storage: Audvidajkonservo
+      media_storage: Mediireteneso
       new_users: nova uzanti
       opened_reports: raporti apertesis
       pending_appeals_html:
@@ -411,7 +374,7 @@ io:
       created_msg: Domenobstrukto nun procedesas
       destroyed_msg: Domenobstrukto desagesis
       domain: Domeno
-      edit: Redaktar domenblokuso
+      edit: Modifikez domenobstrukto
       existing_domain_block: Vu ja exekutis plu rigoroza limiti a %{name}.
       existing_domain_block_html: Vu ja povis plu rigoroza limiti a %{name}, vu bezonas <a href="%{unblock_url}">deobstruktar</a> unesme.
       export: Exportez
@@ -428,41 +391,33 @@ io:
       no_domain_block_selected: Nula domenobstrukti ne chanjesis por ke nulo selektesis
       not_permitted: Vu ne permisesis agar co
       obfuscate: Nedicernebligez domennomo
-      obfuscate_hint: Parte celegez domennomo en la listo se reklamago di listo di domenlimito es ebligita
+      obfuscate_hint: Partala nedicernebligez domennomo en listo se reklamo di listo di domenlimito aktivigesas
       private_comment: Privata komento
       private_comment_hint: Komento pri ca domenlimito esas por interna uzo da jereri.
       public_comment: Publika komento
-      public_comment_hint: Komentez pri ca domenlimito por la publiko, se reklamago di listo di domenlimito es ebligita.
-      reject_media: Desaceptar audvidajdosieri
-      reject_media_hint: Forigas lokale konservita audvidajdosieri e desvolas deskargar irgo en la estonteso
+      public_comment_hint: Komentez pri ca domenlimito por la publiko, se reklamar listo di domenlimito aktivigesis.
+      reject_media: Refusez mediifaili
+      reject_media_hint: Efacas lokale retenita mediifaili e refuzas deschargar irgo en futuro. Nerelatata por restrikti
       reject_reports: Refuzez raporti
       reject_reports_hint: Ignorez omna raporti quo venas de ca domeno. Nerelatata por restrikti
       undo: Undo
       view: Videz domenobstrukto
     email_domain_blocks:
-      add_new: Adjuntar novo
-      allow_registrations_with_approval: Permisar registri kun aprobo
+      add_new: Insertez novo
       attempts_over_week:
         one: "%{count} probo de pos 1 week"
         other: "%{count} registroprobi de pos 1 week"
-      created_msg: Sucese blokusis retpostodomeno
       delete: Efacez
       dns:
         types:
           mx: Rekordo MX
       domain: Domeno
       new:
-        create: Adjuntar domeno
+        create: Insertez domeno
         resolve: Rezolvez domeno
-        title: Blokusar nova retpostodomeno
-      no_email_domain_block_selected: Nula retpostodomenblokusi chanjesis pro ke nulo selektesis
       not_permitted: Ne permisata
-      resolved_dns_records_hint_html: La domennomo indikas ad la desantea MX-domeni kua chefe aceptas retposto. <strong>Zorgemez e ne blokusez ega retpostoprovizanti.</strong>
       resolved_through_html: Rezolvesis tra %{domain}
-      title: Blokusita retpostodomeni
     export_domain_allows:
-      new:
-        title: Importacar domenpermisi
       no_file: Nula dosiero selektesas
     export_domain_blocks:
       import:
@@ -484,9 +439,6 @@ io:
       title: Sequez rekomendi
       unsuppress: Riganez sequorekomendo
     instances:
-      audit_log:
-        title: Lastatempa kontrolnoti
-        view_all: Vidar plena kontrolnoti
       availability:
         description_html:
           one: Se sendar a domeno falias <strong>%{count} dio</strong> sen sucesar, plusa sendoprobi ne agesos se sendo <em>de</em> la domeno ne ganesas.
@@ -508,7 +460,7 @@ io:
         description_html: Vu povas fixar kontenajguidili quo aplikesos a omna konti de ca domeno e irga oli subdomeni.
         limited_federation_mode_description_html: Vu povas selektar ka vu permisos federar kun ca domeno.
         policies:
-          reject_media: Desaceptar audvidaji
+          reject_media: Refusez medii
           reject_reports: Refusez raporti
           silence: Limito
           suspend: Restriktez
@@ -521,7 +473,7 @@ io:
         instance_followers_measure: nia sequanti ibe
         instance_follows_measure: olia sequanti hike
         instance_languages_dimension: Maxim uzata lingui
-        instance_media_attachments_measure: konservita audvidajaddonaji
+        instance_media_attachments_measure: retenata mediiatachaji
         instance_reports_measure: raporti pri oli
         instance_statuses_measure: retenata posti
       delivery:
@@ -552,11 +504,11 @@ io:
       total_followed_by_them: Sequesis da oli
       total_followed_by_us: Sequesis da ni
       total_reported: Raporti pri oli
-      total_storage: Audvidajaddonaji
+      total_storage: Mediiatachaji
       totals_time_period_hint_html: Sumi quo montresas sube inkluzas informi de pos la komenco.
       unknown_instance: Prezente ne esas registrago pri ta domeno che ca servilo.
     invites:
-      deactivate_all: Desebligar omno
+      deactivate_all: Deaktivigez omno
       filter:
         all: Omna
         available: Disponebla
@@ -565,7 +517,7 @@ io:
       title: Inviti
     ip_blocks:
       add_new: Kreez regulo
-      created_msg: Sucese adjuntis nova IP-regulo
+      created_msg: Sucesoze insertis nova regulo di IP
       delete: Efacez
       expires_in:
         '1209600': 2 semani
@@ -581,19 +533,19 @@ io:
     relationships:
       title: "%{acct} relatesi"
     relays:
-      add_new: Adjuntar nova relayo
+      add_new: Insertez nova relayo
       delete: Efacez
-      description_html: "<strong>Fratarrelayo</strong> es intermedia servilo qua interchanjas multa publika afishi inter servili qua abonas e publikigas ibe."
-      disable: Desebligar
-      disabled: Desebligita
-      enable: Ebligar
-      enable_hint: Pos ebligita, vua servilo abonos omna publika afishi de ca relayo, e komencos sendar publika afishi di ca servilo ad ol.
-      enabled: Ebligita
+      description_html: "<strong>Federatrelayo</strong> esas mediatajservilo quo interchanjas multa publika posti inter servili quo abonas e publikigas ibe. <strong>Ol povas helpar mikra e mezgranda servili deskovrar kontenajo de fediverso</strong>, quale ne bezonas lokala uzanti manuale sequar altra personi en nelokala servili."
+      disable: Desaktivigez
+      disabled: Desaktivigita
+      enable: Aktivigez
+      enable_hint: Pos aktivigesar, vua servilo abonos omna publika posti de ca relayo, e komencos sendar publika posti di ca servilo a ol.
+      enabled: Aktivigita
       inbox_url: URL di relayo
       pending: Vartas aprobo di relayo
-      save_and_enable: Konservar e ebligar
+      save_and_enable: Sparez e aktivigez
       setup: Facez relayokonekto
-      signatures_not_enabled: Relayi eble ne korekta funcionos dum ke sekurmodo o limigita fratarmodo es ebligita
+      signatures_not_enabled: Relayi ne korekta funcionos dum ke sekurmodo o limigita federatmodo aktivigesis
       status: Stando
       title: Relayi
     report_notes:
@@ -608,18 +560,13 @@ io:
       action_taken_by: Agesis da
       actions:
         delete_description_html: Raportizita posti efacesos e streko rekordigesos por helpar vu intensigar en nexta malagi da la sama konto.
-        mark_as_sensitive_description_html: Audvidaji en raportita afishi markesos quale trublema e streko rekordesos por helpar vu intensigar en posa malagi da la sama konto.
-        other_description_html: Videz plu multa preferaji por regularar konduto di konto e personesigar komuniko ad raportita konto.
+        mark_as_sensitive_description_html: Medii en raportizita posti markizesos quale sentoza e streko rekordigesos por helpar vu intensigar en nexta malagi da la sama konto.
+        other_description_html: Videz plu multa opcioni por dominacar konduto di konto e kustumizar komuniko a raportizita konto.
         resolve_description_html: Nulo agesos kontre raportizita konto, streko ne rekordizesos e raporto klozesos.
         silence_description_html: Konto esos videbla nur por personi qui ja sequas lo o manuale serchas lo, severe limitizante lua atingo. On sempre povas desfacar co. Klozas omna raporti kontra ca konto.
         suspend_description_html: Ca konto e omna kontenaji esos neacesebla e efacota, e interagar kun ol esos neposibla. Desfacebla dum 30 dii. Klozas omna raporti kontra ca konto.
-      actions_description_html: Decidez quala ago por traktar ca raporto.
       actions_description_remote_html: Selektez quo vu agos por solvar ca raporto. Co nur efektigos quale <strong>vua</strong> servilo komunikas kun ca fora konto e traktas lua kontenaji.
-      actions_no_posts: Ca raporto ne havas irga relatita afishi por forigar
-      add_to_report: Adjuntar plu ad raporto
-      already_suspended_badges:
-        local: Ja blokusita sur ca servilo
-        remote: Ja blokusita sur lua servilo
+      add_to_report: Insertez pluse a raporto
       are_you_sure: Ka vu esas certa?
       assign_to_self: Taskigez me
       assigned: Taskigita jerero
@@ -642,7 +589,7 @@ io:
       mark_as_unresolved: Markizez quale nerezolvita
       no_one_assigned: Nulu
       notes:
-        create: Adjuntar noto
+        create: Insertez noto
         create_and_resolve: Rezolvez per noto
         create_and_unresolve: Riapertez per noto
         delete: Efacez
@@ -656,7 +603,6 @@ io:
       report: 'Raporto #%{id}'
       reported_account: Raportizita konto
       reported_by: Raportizesis da
-      reported_with_application: Raportesis per apliko
       resolved: Rezolvesis
       resolved_msg: Raporto sucesoze rezolvesis!
       skip_to_actions: Saltez a agi
@@ -679,7 +625,6 @@ io:
         delete_data_html: Efacor la profilo e kontenaji di <strong>@%{acct}</strong> ye 30 dii de nun ecepte ke lu esus desrestrikita ante ta tempe
         preview_preamble_html: "<strong>@%{acct}</strong> recevos averto kun la sequanta kontenajo:"
         record_strike_html: Registrar punto kontra <strong>@%{acct}</strong> por helpar vu traktar futura reguloviolaci di ca konto
-        send_email_html: Sendar ad <strong>@%{acct}</strong> avertretposton
         warning_placeholder: Neobligata plusa expliko por la jero-ago.
       target_origin: Fonto di raportizita konto
       title: Raporti
@@ -689,19 +634,18 @@ io:
       updated_at: Novigesis
       view_profile: Videz profilo
     roles:
-      add_new: Adjuntar rolo
+      add_new: Insertez rolo
       assigned_users:
         one: "%{count} uzanto"
         other: "%{count} uzanti"
       categories:
         administration: Administro
-        devops: Developisto
         invites: Inviti
         moderation: Jero
         special: Specala
       delete: Efacez
       description_html: Per <strong>uzantoroli</strong>, vu povas kustumizar funciono e siti di Mastodon quon vua uzanti povas uzar.
-      edit: Redaktar rolo di '%{name}'
+      edit: Modifikez rolo di '%{name}'
       everyone: Originala permisi
       everyone_full_description_html: Co esas <strong>bazrolo</strong> quo efektigas <strong>omna uzanti</strong>, mem personi sen rolo. Omna altra roli ganas sama permisi de ol.
       permissions_count:
@@ -719,25 +663,23 @@ io:
         manage_appeals: Jerez apeli
         manage_appeals_description: Permisez uzanti kontrolar apeli kontra jero
         manage_blocks: Jerez obstrukti
-        manage_blocks_description: Permisar uzanti blokusar retpostoservili e IP-adresi
         manage_custom_emojis: Jerez kustumizita emocimaji
         manage_custom_emojis_description: Permisez uzanti jerar kustumizita emocimaji en la servilo
         manage_federation: Jerez federo
         manage_federation_description: Permisez uzanti obstruktar o permisez federo kun altra domeni, e dominacar sendebleso
         manage_invites: Jerez inviti
-        manage_invites_description: Permisez uzanti vidar e desebligar invitligili
+        manage_invites_description: Permisez uzanti vidar e desaktivigar invitligili
         manage_reports: Jerez raporti
         manage_reports_description: Permisez uzanti kontrolar raporti e jerez kontra oli
         manage_roles: Jerez roli
         manage_roles_description: Permisez uzanti jerar e ajustar plu basa roli di olia
         manage_rules: Jerez reguli
         manage_rules_description: Permisez uzanti chanjar servilreguli
-        manage_settings: Administrar preferaji
-        manage_settings_description: Permisez uzanti chanjar reteypreferaji
+        manage_settings: Jerez opcioni
+        manage_settings_description: Permisez uzanti chanjar sitopcioni
         manage_taxonomies: Jerez nomkategorii
-        manage_taxonomies_description: Permisez uzanti kontrolar populareska enhavajo e tildatigar gretvortpreferaji
+        manage_taxonomies_description: Permisez uzanti kontrolar tendencoza kontenajo e novigar hashtagopcioni
         manage_user_access: Jerez uzantoeniro
-        manage_user_access_description: Permisar uzanti desebligar dufaktoryurizo di altra uzanti, chanjar retpostoadresi e richanjar pasvorto
         manage_users: Jerez uzanti
         manage_users_description: Permisez uzanti vidar detali di altra uzanti e jerar kontra oli
         manage_webhooks: Jerez interrethoki
@@ -745,15 +687,14 @@ io:
         view_audit_log: Videz kontrollogo
         view_audit_log_description: Permisez uzanti vidar historio di administrala agi en la servilo
         view_dashboard: Videz chefpanelo
-        view_dashboard_description: Permisez uzanti uzar chefpanelo e diversa mezuri
-        view_devops: Developisto
+        view_dashboard_description: Permisez uzanti uzar chefpanelo e diversa opcioni
         view_devops_description: Permisez uzanti uzar chefpaneli Sidekiq e pgHero
       title: Roli
     rules:
-      add_new: Adjuntar regulo
+      add_new: Insertez regulo
       delete: Efacez
       description_html: Quankam maxim multa personi asertar ke lu ja lektis e konsentis serveskondicioni, frequente personi ne komplete lektas til problemo eventas. <strong>Faciligez on vidar vua reguli di servilo kurte per pozar puntostrukturlisto.</strong> Lasez singla reguli esar kurta e simpla, ma ne fendez a multa separata kozi anke.
-      edit: Redaktar regulo
+      edit: Modifikez regulo
       empty: Nula servilreguli fixesis til nun.
       title: Servilreguli
     settings:
@@ -769,11 +710,9 @@ io:
         preamble: Fabrikmarko di ca servilo diferentigas lu de altra servili en la reto. Ca informi forsan montresas che diversa loki. Do, ca informi debas esar klara.
         title: Fabrikmarkeso
       captcha_enabled:
-        desc_html: Co dependas externa skripti de hCaptcha kua eble es sekureso e privatesdesquieto. Do, uzez alternativa metodi tala aprobala o invitala registro.
         title: Postular ke nova uzanti solvos CAPTCHA por konfirmar lia konti
       content_retention:
-        danger_zone: Danjerzono
-        preamble: Selektar quale uzantigita kontenajo retenesar en Mastodon.
+        preamble: Dominacez quale uzantigita kontenajo retenesar en Mastodon.
         title: Kontenajreteneso
       default_noindex:
         desc_html: Efektigas omna uzanti qui ne personale chanjis ca opciono
@@ -781,18 +720,17 @@ io:
       discovery:
         follow_recommendations: Sequez rekomendaji
         preamble: Montrar interesanta kontenajo esas importanta ye voligar nova uzanti quo forsan ne savas irgu. Dominacez quale ca deskovrotraiti funcionar en ca servilo.
-        profile_directory: Profiluyo
+        profile_directory: Profilcheflisto
         public_timelines: Publika tempolinei
         publish_discovered_servers: Publikar deskovrita servili
         publish_statistics: Publikar statistiki
         title: Deskovro
-        trends: Populari
+        trends: Tendenci
       domain_blocks:
         all: A omnu
         disabled: A nulu
         users: A enirinta lokala uzanti
       registrations:
-        moderation_recommandation: Certez ke vu havas sata e reaktiva jergrupo ante vu desklozar registri ad omnu!
         preamble: Dominacez qua povas krear konto en ca servilo.
         title: Registragi
       registrations_mode:
@@ -800,7 +738,6 @@ io:
           approved: Aprobo bezonesas por registro
           none: Nulu povas registrar
           open: Irgu povas registrar
-        warning_hint: Ni rekomendas uzar "Aprobo bezonita por registro" se vu ne kunfidas ke vua jergrupo povas traktar spamo e mala registri bontempe.
       security:
         authorized_fetch: Postular autentikigo de federata servili
         authorized_fetch_hint: Postular autentikigo de federata servili kapabligar plu strikta enforcigo dil blokusi di uzanti e dil servilo. Tamen, co enduktos exekutado-lentigo, diminutos la atingebleso di via respondi, e forsan enduktos koncilieblesoproblemi kun kelka softwari federata. Pluse, co ne preventos aganti dedikita de acesar vua publika posti e konti.
@@ -808,11 +745,10 @@ io:
         federation_authentication: Enforcigo di federado-autentikigo
       title: Servilopcioni
     site_uploads:
-      delete: Forigar adkargita dosiero
-      destroyed_msg: Reteyadkargo sucese forigesis!
+      delete: Efacez adchargita failo
+      destroyed_msg: Sitadchargito sucesoze efacesis!
     software_updates:
       critical_update: Urjanta — voluntez aktualigar rapide
-      description: On rekomendesis lasar Mastodon-instalo esar tildatigita por bone ganar maxim nova repari e traiti. Do, Mastodon kontrolas tildatigi ye singla 30 minuti, e savigos vu segun vua retpostosavigpreferi.
       documentation_link: Lernez pluse
       release_notes: Emiso-noti
       title: Aktualigi disponebla
@@ -828,31 +764,25 @@ io:
       back_to_account: Retrovenez a kontopagino
       back_to_report: Retrovenez a raportpagino
       batch:
-        add_to_report: 'Adjuntar ad raporto #%{id}'
         remove_from_report: Efacez de raporto
         report: Raportizez
-      contents: Enhavaji
       deleted: Efacesis
       favourites: Favoriziti
       history: Historio di versioni
       in_reply_to: Respondante a
       language: Linguo
       media:
-        title: Audvidaji
+        title: Medii
       metadata: Metadatumi
-      no_history: Ca afisho ne redaktesis
       no_status_selected: Nula posti chanjesis pro ke nulo selektesis
       open: Apertez posto
       original_status: Originala posto
       reblogs: Dissemi
-      replied_to_html: Respondis ad %{acct_link}
       status_changed: Posto chanjita
-      status_title: Afisho da %{name}
-      title: Kontoafishi - @%{name}
+      title: Kontoposti
       trending: Populara
-      view_publicly: Vidar publike
       visibility: Videbleso
-      with_media: Kun audvidaji
+      with_media: Kun medii
     strikes:
       actions:
         delete_statuses: "%{name} efacis posti di %{target}"
@@ -868,20 +798,12 @@ io:
     system_checks:
       database_schema_check:
         message_html: Existas vartanta datamaturmigri. Startez por certigar ke la softwaro kondutar quale expektita
-      elasticsearch_health_red:
-        message_html: Elastiksercharo es nekapabla, serchtraiti es nedisponebla
-      elasticsearch_health_yellow:
-        message_html: Elastiksercharo es nekapabla, vu darfas volar inquestar la motivo
       elasticsearch_preset:
         action: Videz la dokumentajo
-        message_html: Vua elastiksercharo havas plu kam un nodo, ma Mastodon ne povas uzar ili.
       elasticsearch_preset_single_node:
         action: Videz la dokumentajo
-        message_html: Vua elastiksercharo havas nur un nodo, <code>ES_PRESET</code> devas fixesesar ad <code>single_node_cluster</code>.
-      elasticsearch_reset_chewy:
-        message_html: Vua elastikserchsistemindexo esas destildatigita pro preferajchanjo.
       elasticsearch_running_check:
-        message_html: Ne povas konektas ad Elasticsearch.
+        message_html: Ne povas konektas a Elasticsearch. Kontrolez ke ol functionas o desaktivigez textokompleta trovo
       elasticsearch_version_check:
         message_html: 'Nekonciliebla versiono di Elasticsearch: %{value}'
         version_comparison: Elasticsearch %{running_version} funcionas ma %{required_version} bezonesas
@@ -889,10 +811,7 @@ io:
         action: Jerez servilreguli
         message_html: Vu ne fixis irga servilreguli.
       sidekiq_process_check:
-        message_html: Sidekiq procedo ne funcionas ye %{value} fask(o)
-      software_version_check:
-        action: Vidar disponebla tildatigi
-        message_html: Mastodon-tildatigo es disponebla.
+        message_html: Sidekiq procedo ne funcionas ye %{value} fask(o). Kontrolez vua opciono di Sidekiq
       software_version_critical_check:
         action: Videz la aktualigi disponebla
         message_html: Urjanta Mastodon-aktualigo es disponebla, voluntez aktualigar la servilo tam rapide kam es posibla.
@@ -906,66 +825,16 @@ io:
         action: Konsultez hike por plu multa informo
         message_html: "<strong>Vua dosierokonservo es misfigurizita. La privateso di vua uzanti es domajebla.</strong>"
     tags:
-      moderation:
-        not_trendable: Ne popularebla
-        not_usable: Ne uzebla
-        pending_review: Vartas kontrolo
-        review_requested: Kontrolo demandesis
-        reviewed: Kontrolesis
-        title: Stando
-        trendable: Popularebla
-        unreviewed: Nekontrolesis
-        usable: Neuzebla
-      name: Nomo
-      newest: Maxim nova
-      oldest: Maxim desnova
-      open: Vidar publike
-      reset: Richanjar
       review: Kontrolez stando
-      search: Serchar
-      title: Gretvorti
-      updated_msg: Gretvortpreferaji tildatigesis sucese
-    terms_of_service:
-      back: Retroirar ad servtermini
-      changelog: Quo chanjesis
-      create: Uzar vua sua ajo
-      current: Nuntempa
-      draft: Skribureto
-      generate: Uzar shablono
-      generates:
-        action: Igar
-        chance_to_review_html: "<strong>Igita servtermini ne publikigos automate.</strong> Plenigez la necesa detali por daurar."
-      history: Historio
-      live: Nuna
-      no_history: Ne havas rekordita chanji di servtermini ankore.
-      no_terms_of_service_html: Vu nune ne havas irga fixita servtermini.
-      notified_on_html: Uzanti savigesis ye %{date}
-      notify_users: Savigar uzanti
-      preview:
-        explanation_html: La retposto sendesos ad <strong>%{display_count}uzanti</strong> qua registris ante %{date}.
-        send_preview: Sendar prevido ad %{email}
-        send_to_all:
-          one: Sendar %{display_count} retposto
-          other: Sendar %{display_count} retposti
-        title: Previdar servterminsavigo
-      publish: Publikigar
-      published_on_html: Publikigita ye %{date}
-      save_draft: Konservar skiso
-      title: Servtermini
+      updated_msg: Hashtagopcioni novigesis sucesoze
     title: Administro
     trends:
       allow: Permisez
       approved: Aprobesis
-      confirm_allow: Ka vu es certa ke vu volas permisar selektita etiketi?
-      confirm_disallow: Ka vu es certa ke vu volas despermisar selektita etiketi?
       disallow: Despermisez
       links:
         allow: Permisez ligilo
         allow_provider: Permisez publikiganto
-        confirm_allow: Ka vu es certa ke vu volas permisar selektita ligili?
-        confirm_allow_provider: Ka vu es certa ke vu volas permisar selektita provizanti?
-        confirm_disallow: Ka vu es certa ke vu volas despermisar selektita ligili?
-        confirm_disallow_provider: Ka vu es certa ke vu volas despermisar selektita provizanti?
         description_html: Co esas ligili quo nun multe partigesas da konti kun posti quon vua servilo vidas. Ol povas helpar vua uzanti lernar quo eventas en mondo. Ligili ne publike montresas til vu aprobar publikiganto. Vu povas anke permisar o refuzar individuala ligili.
         disallow: Despermisez ligilo
         disallow_provider: Despermisez publikiganto
@@ -975,33 +844,29 @@ io:
         shared_by_over_week:
           one: Partigesis da 1 persono de pos antea semano
           other: Partigesis da %{count} personi de pos antea semano
-        title: Populara ligili
+        title: Tendencoza ligili
         usage_comparison: Partigesis %{today} foyi hodie, la nombro esas %{yesterday} hiere
       not_allowed_to_trend: Ne permisita quale popularo
       only_allowed: Nur permisato
       pending_review: Vartas kontrolo
       preview_card_providers:
-        allowed: Ligili de ca publikiganto povas populareskar
-        description_html: Co esas domeni quo havas ligili qua ofte kunhavigesis che vua servilo. Vua aprobo anke esas por domeneti.
-        rejected: Ligili de ca publikiganto ne populareskos
+        allowed: Ligili de ca publikiganto povas divenar tendenco
+        description_html: Co esas domen quo havas ligili quo frequente partigesas en vua servilo. Ligili ne divenas tendenco publike se domeno di ligilo ne aprobesas. Vua aprobo (o refuzo) anke esas por subdomeni.
+        rejected: Ligili de ca publikiganto ne divenos tendenco
         title: Publikiganti
       rejected: Refuzesis
       statuses:
         allow: Permisez posto
         allow_account: Permisez skribanto
-        confirm_allow: Ka vu es certa ke vu volas permisar selektita standi?
-        confirm_allow_account: Ka vu es certa ke vu volas permisar selektita konti?
-        confirm_disallow: Ka vu es certa ke vu volas despermisar selektita standi?
-        confirm_disallow_account: Ka vu es certa ke vu volas despermisar selektita konti?
-        description_html: Co esas afishi quan vua servilo savas e nun partigesis e stelumesis multe nun. Afishi ne publike montresas til vu aprobar la verkanto, e la verkanto permisas lua konto sugestesas ad altra personi.
+        description_html: Co esas posti quon vua servilo savas quale nun partigesas e favorizesas multe nun. Ol povas helpar vua nova e retrovenanta uzanti trovar plu multa personi por sequar. Posti ne publike montresas til vu aprobar la skribanto, e la skribanto permisas sua konto sugestesas a altra personi. Vu povas anke permisar o refuzar individuala posti.
         disallow: Despermisez posto
         disallow_account: Despermisez skribanto
-        no_status_selected: Nula populara afishi chanjesis pro ke nulo selektesis
+        no_status_selected: Nula tendencoza posti chanjesis pro ke nulo selektesis
         not_discoverable: Skribanto ne konsentis pri esar deskovrebla
         shared_by:
-          one: Partigesis o stelumesis unfoye
-          other: Partigesis o stelumesis %{friendly_count} foye
-        title: Populara afishi
+          one: Partigesis o favorizesis 1 foye
+          other: Partigesis o favorizesis %{friendly_count} foye
+        title: Tendencoza posti
       tags:
         current_score: Nuna punto esas %{score}
         dashboard:
@@ -1014,38 +879,36 @@ io:
         listable: Povas sugestesar
         no_tag_selected: Nula tagi chanjesis pro ke nulo selektesis
         not_listable: Ne sugestesar
-        not_trendable: Ne aparos en populari
+        not_trendable: Ne aparas che tendenci
         not_usable: Ne povas uzesar
         peaked_on_and_decaying: Maxim uzita ye %{date}, nun diminutesas
-        title: Populara gretvorti
-        trendable: Povas aparar en populari
-        trending_rank: 'Popularo #%{rank}'
+        title: Tendencoza hashtagi
+        trendable: Povas aparar che tendenci
+        trending_rank: 'Tendencorango #%{rank}'
         usable: Povas uzesar
         usage_comparison: Uzesis %{today} foyi hodie, la nombro esas %{yesterday} hiere
         used_by_over_week:
           one: Uzesis da 1 persono de pos 1 semano
           other: Uzesis da %{count} personi de pos 1 semano
-      title: Rekomenditi & populari
-      trending: Populara
+      trending: Tendenco
     warning_presets:
-      add_new: Adjuntar novo
+      add_new: Insertez novo
       delete: Efacez
-      edit_preset: Redaktar avertdecido
+      edit_preset: Modifikez avertfixito
       empty: Vu ne fixis irga avertfixito til nun.
-      title: Avertpreferaji
     webhooks:
-      add_new: Adjuntar finpunto
+      add_new: Insertez finpunto
       delete: Efacez
       description_html: "<strong>Rethoko</strong> povigas Mastodon sendar <strong>samtempoavizi</strong> pri selektita eventi a vua sua apliko, por ke vua apliko povas <strong>automate kauzigar reakti</strong>."
-      disable: Desebligar
-      disabled: Desebligita
-      edit: Redaktar finpunto
+      disable: Desaktivigez
+      disabled: Desaktivigita
+      edit: Modifikez finpunto
       empty: Vu ne havas irga ajustita finpunti ankore.
-      enable: Ebligar
+      enable: Aktivigez
       enabled: Aktiva
       enabled_events:
-        one: 1 ebligita evento
-        other: "%{count} ebligita eventi"
+        one: 1 aktivigita evento
+        other: "%{count} aktivigita eventi"
       events: Eventi
       new: Nova rethoko
       rotate_secret: Rotacigez sekreto
@@ -1054,8 +917,6 @@ io:
       title: Rethoki
       webhook: Rethok
   admin_mailer:
-    auto_close_registrations:
-      subject: Registri di %{instance} automate chanjesis ad bezonar aprobo
     new_appeal:
       actions:
         delete_statuses: efacos ola posti
@@ -1084,12 +945,12 @@ io:
     new_trends:
       body: 'Ca kozi bezonas kontrol ante ol povas montresar publike:'
       new_trending_links:
-        title: Populara ligili
+        title: Tendencoza ligili
       new_trending_statuses:
-        title: Populara afishi
+        title: Tendencoza posti
       new_trending_tags:
-        title: Populara gretvorti
-      subject: Nova populari bezonas kontrolo sur %{instance}
+        title: Tendencoza hashtagi
+      subject: Nova tendenci bezonas kontrolesar che %{instance}
   aliases:
     add_new: Kreez alternativa nomo
     created_msg: Sucesoze kreis nova alternativa nomo. Vu povas nun komencar transfero de la olda konto.
@@ -1098,9 +959,9 @@ io:
     hint_html: Se vu volas transferesar de altra konto a co, hike vu povas krear alternativnomo, quo bezonesas ante vu povas durigar transferar sequanti de la olda konto a co. Ca ago esas <strong>nedanjeroza e inversigebla</strong>. <strong>Kontomigro komencesas de la olda konto</strong>.
     remove: Deligez alternative nomo
   appearance:
-    advanced_web_interface: Altnivela retintervizajo
-    advanced_web_interface_hint: 'Se vu volas uzar tota skrenlongeso, altnivela retintervizajo povigas vu modifikar multa dessama kolumni por vida multa informi en sama tempo quale vu volas: Hemo, savigi, fratara tempolineo, multa listi e gretvorti.'
-    animations_and_accessibility: Animi e adirebleso
+    advanced_web_interface: Avancata retintervizajo
+    advanced_web_interface_hint: 'Se vu volas uzar tota skrenlongeso, avancata retintervizajo povigas vu modifikar multa diferanta kolumni por vida multa informi en sama tempo quon vu volas: Hemo, avizi, federatata tempolineo, multa listi e hashtagi.'
+    animations_and_accessibility: Animacii e aceseso
     confirmation_dialogs: Konfirmdialogi
     discovery: Deskovro
     localization:
@@ -1109,9 +970,7 @@ io:
       guide_link_text: Omnu povas kontributar.
     sensitive_content: Sentoza kontenajo
   application_mailer:
-    notification_preferences: Chanjar retpostopreferaji
     salutation: "%{name},"
-    settings: 'Chanjar retpostopreferaji: %{link}'
     unsubscribe: Desabonez
     view: 'Vidar:'
     view_profile: Videz profilo
@@ -1138,18 +997,17 @@ io:
       redirect_to_app_html: Vu devas sendesir al apliko <strong>%{app_name}</strong>. Se to ne evenis, probez %{clicking_this_link} o retroirez manuale al apliko.
       registration_complete: Vua registrago che %{domain} nun es kompleta!
       welcome_title: Bonvenez, %{name}!
-      wrong_email_hint: Se ta retpostoadreso ne es korekta, vu povas chanjar lu en la kontopreferaji.
     delete_account: Efacez konto
     delete_account_html: Se vu volas efacar vua konto, vu povas <a href="%{path}">irar hike</a>. Vu demandesos konfirmar.
     description:
-      prefix_invited_by_user: "@%{name} invitas vu adeskar ca servilo di Mastodon!"
+      prefix_invited_by_user: "@%{name} invitas vu juntar ca servilo di Mastodon!"
       prefix_sign_up: Registrez che Mastodon hodie!
-      suffix: Per konto, vu povos sequar personi, postigar novaji e interchanjar mesaji kun uzanti de irga servilo di Mastodon e plu multo!
+      suffix: Per konto, vu povos sequar personi, postigar novaji e interchanjar mesaji kun uzanti de irga servilo di Mastodon e pluse!
     didnt_get_confirmation: Ka vu ne recevis konfirmoligilo?
     dont_have_your_security_key: Ka vu ne havas sekuresklefo?
     forgot_password: Pasvorto obliviita?
     invalid_reset_password_token: Pasvorto richanjoficho esas nevalida o expirita. Demandez novo.
-    link_to_otp: Insertez dufaktora kodexo de vua telefonilo o rigankodexo
+    link_to_otp: Insertez 2-faktorkodexo de vua fono o rigankodexo
     link_to_webauth: Uzez vua sekuresklefaparato
     log_in_with: Enirez per
     login: Enirar
@@ -1157,8 +1015,8 @@ io:
     migrate_account: Transferez a diferanta konto
     migrate_account_html: Se vu volas ridirektar ca konto a diferanto, vu povas <a href="%{path}">ajustar hike</a>.
     or_log_in_with: O eniras per
+    privacy_policy_agreement_html: Me lektis e konsentis <a href="%{privacy_policy_path}" target="_blank">privatesguidilo</a>
     progress:
-      confirm: Konfirmar retpostoadreso
       details: Vua detali
       review: Nia revuo
       rules: Aceptar reguli
@@ -1180,38 +1038,24 @@ io:
     security: Chanjar pasvorto
     set_new_password: Selektar nova pasvorto
     setup:
-      email_below_hint_html: Kontrolez vua spamdosieruyo, o demandez altra ligilo.
-      email_settings_hint_html: Klikez la ligilo quan ni sendis ad %{email} por komencar uzar Mastodon.
+      email_settings_hint_html: Uzez la ligilo quan ni sendis a vu por verifikar %{email}. Ni vartos hike.
       link_not_received: Ka vu ne recevis ligilo?
-      new_confirmation_instructions_sent: Vu recevos nova retposto kun la konfirmligilo pos kelka minuti!
       title: Kontrolez vua retposti
     sign_in:
       preamble_html: Enirez per vua <strong>%{domain}</strong> detali. Se vua konto esas che altra servilo, vu ne povos enirar hike.
       title: Enirez a %{domain}
     sign_up:
       manual_review: Registragi che %{domain} es revuata da nia personaro. Por helpar ni traktar vua aplikajo, skribez kelko pri vu e pro quo vu volas konto che %{domain}.
-      preamble: Per konto en ca servilo di Mastodon, on povas sequar irga persono en ca fediverso, ne ye ube ona konto gastigesis.
+      preamble: Per konto en ca servilo di Mastodon, on povas sequar irga persono en ca reto, ne ye ube ona konto hostagisas.
       title: Ni komencigez vu en %{domain}.
     status:
       account_status: Kontostando
-      confirming: Vartas retpostokonfirmo esar kompleta.
       functional: Vua konto esas tote funcionoza.
-      pending: Vua apliko bezonas kontrolo da nia laborero. Vu ganos retposto se vua apliko aprobesis.
       redirecting_to: Vua konto esas neaktiva pro ke ol nun ridirektesos a %{acct}.
       self_destruct: Pro ke %{domain} balde klozos, vu havas nur aceso limitizata a vua konto.
       view_strikes: Videz antea streki kontre vua konto
     too_fast: Formulario sendesis tro rapide, probez itere.
     use_security_key: Uzes sekuresklefo
-    user_agreement_html: Me lektas e asentas <a href="%{terms_of_service_path}" target="_blank">servtermini</a> e <a href="%{privacy_policy_path}" target="_blank">privatesguidilo</a>
-    user_privacy_agreement_html: Me lektas e konsentas <a href="%{privacy_policy_path}" target="_blank">privatesguidilo</a>
-  author_attribution:
-    example_title: Exemplotexto
-    hint_html: Ka vu skribas novaji o blogartikli ne en Mastodon?
-    instructions: 'Certigez ke ca kodexo es en HTML di vua artiklo:'
-    more_from_html: Plu de %{name}
-    s_blog: Blogo di %{name}
-    then_instructions: Pose, adjuntez la domennomo di publikigo en la dessupra spaco.
-    title: Verkantoagnosko
   challenge:
     confirm: Avancez
     hint_html: "<strong>Guidilo:</strong> Ni ne demandos vua pasvorto itere til 1 horo."
@@ -1239,7 +1083,7 @@ io:
       x_months: "%{count}mo"
       x_seconds: "%{count}s"
   deletes:
-    challenge_not_passed: Informo quan vu insertis es nekorekta
+    challenge_not_passed: Informo quon vu insertis ne esas korekta
     confirm_password: Insertez nuna pasvorto por verifikar vua identeso
     confirm_username: Insertez vua uzantonomo por konfirmar procedo
     proceed: Efacez konto
@@ -1284,7 +1128,6 @@ io:
       your_appeal_rejected: Vua apelo refuzesis
   edit_profile:
     basic_information: Fundamentala informo
-    hint_html: "<strong>Personesigez quon personi vidas sur vua publika profilo e apud vua afishi.</strong>."
     other: Altra
   errors:
     '400': Demando quon vu sendis esas nevalida o malstrukturala.
@@ -1300,26 +1143,27 @@ io:
       content: Regreto, ma evas nefuncionas che nia latero.
       title: Ca pagino ne esas korekta
     '503': La pagino ne povas montresas pro tempala servilfalio.
+    noscript_html: Por uzar retsoftwaro di Mastodon, aktivigez JavaScript. Alternative, probez 1 de <a href="%{apps_path}">konciliebla softwari</a> por Mastodon por vua platformo.
   existing_username_validator:
     not_found: ne povas trovar lokala uzanti kun ta uzantonomo
     not_found_multiple: ne povas trovar %{usernames}
   exports:
     archive_takeout:
       date: Dato
-      download: Deskargar vua arkivo
-      hint_html: Vu povas demandar arkivo di vua <strong>afishi e adkargita audvidaji</strong>. Vu povas demandar arkivo pos singla 7 dii.
+      download: Deschargez vua arkivo
+      hint_html: Vu povas demandar arkivo di vua <strong>posti e adchargita medii</strong>. Exportacita informi esos kun ActivityPub format, e lektebla da irga konciliebla softwaro. Vu povas demandar arkivo pos singla 7 dii.
       in_progress: Kompilar vua arkivo...
       request: Demandez vua arkivo
       size: Grandeso
     blocks: Tu blokusas
-    bookmarks: Lektosigni
+    bookmarks: Libromarki
     csv: CSV
     domain_blocks: Domenobstrukti
     lists: Listi
     mutes: Vu silencigesas
-    storage: Audvidajkonservo
+    storage: Konservado di kontenajo
   featured_tags:
-    add_new: Adjuntar novo
+    add_new: Insertez novo
     errors:
       limit: Vu ja pinglizis la maxima nombro de hastagi
     hint_html: "<strong>Quo esas estelita hashtagi?</strong> Ol montresas eminente che vua publika profilo e povigas personi vidar vua publika posti partikulare kun ta hashtagi. Oli esas bona utensilo por jeretar kreiva agaji e longa projetaji."
@@ -1331,11 +1175,11 @@ io:
       public: Publika tempolinei
       thread: Konversi
     edit:
-      add_keyword: Adjuntar klefvorto
+      add_keyword: Insertez klefvorto
       keywords: Klefvorti
       statuses: Individuala posti
       statuses_hint_html: Ca filtrilo aplikesas a selektita posti ne segun kad oli parigesas kun basa klefvorti. <a href="%{path}">Kontrolez o efacez posti de la filtrilo</a>.
-      title: Redaktar filtrilo
+      title: Modifikez filtrilo
     errors:
       deprecated_api_multiple_keywords: Ca parametri ne povas chanjesar per ca softwaro pro quo oli efektigas plu kam 1 filtrilklefvorto. Uzez plu recenta softwaro o interretintervizajo.
       invalid_context: Nula o nevalida kuntexto donesis
@@ -1357,7 +1201,7 @@ io:
       title: Filtrili
     new:
       save: Salvez nova filtrilo
-      title: Adjuntar nova filtrilo
+      title: Insertez nova filtrilo
     statuses:
       back_to_filter: Retrovenez a filtrilo
       batch:
@@ -1400,9 +1244,23 @@ io:
     mismatched_types_warning: Semblas ke vu forsan selektis la nekorekta tipo por ca importaco, voluntez kontrolar itere.
     modes:
       merge: Kombinez
-      merge_long: Konservar existanta rekordi e adjuntar novi
+      merge_long: Retenez displonebla rekordi e insertez novi
       overwrite: Remplasez
       overwrite_long: Remplasez nuna rekordi per novi
+    overwrite_preambles:
+      blocking_html: Vu <strong>substitucos vua blokusolisto</strong> per til <strong>%{total_items} konti</strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Vu <strong>substitucos vua libromarki</strong> per til <strong>%{total_items} posti</strong> de <strong>%{filename}</strong>.
+      domain_blocking_html: Vu <strong>substitucos vua domenoblokusolisto</strong> per til <strong>%{total_items} domeni</strong> de <strong>%{filename}</strong>.
+      following_html: Vu <strong>sequos</strong> til <strong>%{total_items} konti</strong> de <strong>%{filename}</strong> e <strong>haltar sequar irga altra konto</strong>.
+      lists_html: Vu <strong>substitucos vua listi</strong> kun la kontenaji di <strong>%{filename}</strong>. Til <strong>%{total_items} konti</strong> adjuntesos a nova listi.
+      muting_html: Vu <strong>substitucos vua listo di konti silencigita</strong> per til <strong>%{total_items} konti</strong> de <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Vu <strong>blokusos</strong> til <strong>%{total_items} konti</strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Vu adjuntos <strong>%{total_items} posti</strong> de <strong>%{filename}</strong> a vua <strong>libromarki</strong>.
+      domain_blocking_html: Vu <strong>blokusos</strong> til <strong>%{total_items} domeni</strong> de <strong>%{filename}</strong>.
+      following_html: Vu <strong>sequos</strong> til <strong>%{total_items} konti</strong> de <strong>%{filename}</strong>.
+      lists_html: Vu adjuntos til <strong>%{total_items} konti</strong> de <strong>%{filename}</strong> a vua <strong>listi</strong>. Nova listi kreesos se ne existas listo a quo adjuntar.
+      muting_html: Vu <strong>silencigos</strong> til <strong>%{total_items}</strong> konti en <strong>%{filename}</strong>.
     preface: Tu povas importacar kelka datumi, tal quala listi de omna homi quin tu sequas o blokusas, a tua konto di ca instaluro, per dosiero exportacita de altra instaluro.
     recent_imports: Importacaji recenta
     states:
@@ -1411,7 +1269,7 @@ io:
       scheduled: Projetita
       unconfirmed: Nekonfirmata
     status: Stando
-    success: Vua datumi es senprobleme adkargita e esos traktita balde
+    success: Tua datumi esis senprobleme importacita ed esos traktita quale projetita
     time_started: Komencita ye
     titles:
       blocking: Importacante konti blokusata
@@ -1426,14 +1284,14 @@ io:
       destructive: Blokusati e silencigati
     types:
       blocking: Listo de blokusiti
-      bookmarks: Lektosigni
+      bookmarks: Libromarki
       domain_blocking: Domenobstruktolisto
       following: Listo de sequati
       lists: Listi
       muting: Silenciglisto
-    upload: Adkargar
+    upload: Kargar
   invites:
-    delete: Desebligar
+    delete: Deaktivigez
     expired: Expiris
     expires_in:
       '1800': 30 minuti
@@ -1475,14 +1333,13 @@ io:
       title: Desabonez
   media_attachments:
     validations:
-      images_and_video: Ne povas addonar video ad afisho qua ja enhavas imaji
-      not_found: Audvidaji %{ids} es ne trovesis o ja addonesis ad altra afisho
-      not_ready: Ne povas addonar dosieri qua ankore ne finigas procedar.
-      too_many: Ne povas addonar plu kam 4 dosieri
+      images_and_video: Ne povas atachar video a posto quo ja kontenar imaji
+      not_ready: Ne povas faili quo ankore procedigesas. Probez itere pose!
+      too_many: Ne povas atachar plu kam 4 faili
   migrations:
     acct: Transferesis a
     cancel: Anulez ridirekto
-    cancel_explanation: Nuligar ridirekto riaktivigos vua nuna konto, ma ne riganos sequanti qua movigesis ad ta konto.
+    cancel_explanation: Anular ridirekto riaktivigos vua nuna konto, ma ne riganos sequanti quo transferesis a ta konto.
     cancelled_msg: Sucesoze anulis ridirekto.
     errors:
       already_moved: esas sama transferkonto
@@ -1505,7 +1362,7 @@ io:
       backreference_required: Nova konto mustas unesme ajustesar por dopreferar co
       before: 'Ante durar, lektez ca noti sorgeme:'
       cooldown: Pos transferesar, existas varttempo kande vu ne povas transferesar itere
-      disabled_account: Vua nuna konto ne esos tote uzebla pose.
+      disabled_account: Vua nuna konto ne divenos tote uzebla pose. Tamen, vu povas acesar informexportaco e anke riaktivigo.
       followers: Ca ago transferos omna sequanti de nuna konto a nova konto
       only_redirect_html: Alternative, vu povas <a href="%{path}">nur pozar ridirekto che vua profilo</a>.
       other_data: Altra informi ne transferesos automate
@@ -1543,7 +1400,7 @@ io:
       subject: "%{name} mencionis tu"
       title: Nova menciono
     poll:
-      subject: Votinquesto da %{name} fineskis
+      subject: Votposto di %{name} finis
     reblog:
       body: "%{name} repetis vua posto:"
       subject: "%{name} repetis vua posto"
@@ -1551,7 +1408,7 @@ io:
     status:
       subject: "%{name} nove postigis"
     update:
-      subject: "%{name} redaktis afisho"
+      subject: "%{name} modifikis posto"
   notifications:
     email_events_hint: 'Selektez eventi quon vu volas ganar avizi:'
   number:
@@ -1565,13 +1422,13 @@ io:
           thousand: K
           trillion: T
   otp_authentication:
-    code_hint: Insertez kodexo qua igesis da vua yurizaplikajo por konfirmar
-    description_html: Se vu ebligas <strong>dufaktora yurizo</strong> per yurizaplikajo, eniro bezonos vu havar vua telefonilo, quo igos fichi por vu enirar.
-    enable: Ebligar
-    instructions_html: "<strong>Skanez ca QR-kodexo ad Google Authenticator o simila TOTP-aplikajo per vua telefonilo</strong>."
+    code_hint: Insertez kodexo quo facesis da vua yurizsoftwaro por konfirmar
+    description_html: Se vu aktivigas <strong>2-faktoryurizo</strong> per yurizsoftwaro, eniro bezonos vu havar vua smartfono, quale facas fichi por vu por enirar.
+    enable: Aktivigez
+    instructions_html: "<strong>Skanez ca QR-kodexo a Google Authenticator o simila TOTP-softwaro en vua smartfono</strong>. Pos co, ta softwaro facos fichi quon vu bezonos insertar kande enirar."
     manual_instructions: 'Se vu ne povas skanar QR-kodexo e bezonas insertar manuala, subo esas textosekreto:'
     setup: Facez
-    wrong_code: Insertita kodexo es nevalida!
+    wrong_code: Insertita kodexo esas nevalida! Ka serviltempo e aparattempo esas korekta?
   pagination:
     newer: Nova
     next: Sequanta
@@ -1580,28 +1437,24 @@ io:
     truncate: "&hellip;"
   polls:
     errors:
-      already_voted: Vu ja votis sur ca votinquesto
-      duplicate_options: enhavas duopliga aji
-      duration_too_long: esas tro desproxima en la estonteso
+      already_voted: Vu ja votis che ca votposto
+      duplicate_options: havas duplikatkozi
+      duration_too_long: esas tro distanta en futuro
       duration_too_short: es tro balde
-      expired: La votinquesto ja fineskis
+      expired: Votposto ja finis
       invalid_choice: Selektita votselektajo ne existas
-      over_character_limit: ne povas esar plu longa kam %{max} literi por singlo
+      over_character_limit: ne povas esar plu longa kam %{max} literi por sing
       self_vote: On ne povas votar en sua propra inquesti
-      too_few_options: mustas havar plu kam un ajo
-      too_many_options: ne povas enhavar plu kam %{max} aji
+      too_few_options: mustas havar kozi
+      too_many_options: ne povas havar plu kam %{max} kozi
   preferences:
     other: Altra
-    posting_defaults: Originala afishago
+    posting_defaults: Originala postoopcioni
     public_timelines: Publika tempolinei
   privacy:
-    hint_html: "<strong>Personesigez quale vu volas vua profilo e vua afishi trovesis.</strong> Instante kontrolez ca preferaji por certigar ke ili aptas segun vu."
     privacy: Privateso
-    privacy_hint_html: Selektar quale vu volas montrar por altra personi.
     reach: Atingo
-    reach_hint_html: Selektar ka vu volas deskovresar e sequesar da nova personi. Ka vu volas altra personi vidar vu en ilia sequrekomendi?
     search: Sercho
-    search_hint_html: Selektar quale vu volas trovesar. Ka vu volas personi ne ek Mastodon trovar vua profilo kande serchar la interreto?
     title: Privateso e atingo
   privacy_policy:
     title: Privatesguidilo
@@ -1643,6 +1496,7 @@ io:
   scheduled_statuses:
     over_daily_limit: Vu ecesas limito quale esas %{limit} projetita posti por hodio
     over_total_limit: Vu ecesas limito quale esas %{limit} projetita posti
+    too_soon: Projetita dato mustas esar en futuro
   self_destruct:
     lead_html: Desfortunoze, <strong>%{domain}</strong> balde klozos. Se vu havis konto ibe, vu ne plus povas uzar it, ma vu ankore povas demandar exportacajo de vua datumi.
     title: Ca servilo balde klozos
@@ -1693,7 +1547,7 @@ io:
     view_authentication_history: Videz yurizeshistorio di vua konto
   settings:
     account: Konto
-    account_settings: Kontopreferaji
+    account_settings: Kontoopcioni
     aliases: Kontoaltnomi
     appearance: Aspekto
     authorized_apps: Yurizita apliki
@@ -1712,15 +1566,12 @@ io:
     strikes: Jerstreki
     two_factor_authentication: Dufaktora autentikigo
     webauthn_authentication: Sekuresklefi
-  severed_relationships:
-    download: Deskargo (%{count})
-    preamble: On eble desganas sequati e sequanti kande on blokusas domeno o kande ona administranti decidas blokusar deslokala servilo.
   statuses:
     attached:
       audio:
-        one: "%{count} sono"
-        other: "%{count} soni"
-      description: 'Addonajita: %{attached}'
+        one: "%{count} audio"
+        other: "%{count} audii"
+      description: 'Atachajo: %{attached}'
       image:
         one: "%{count} imajo"
         other: "%{count} imaji"
@@ -1733,14 +1584,14 @@ io:
     disallowed_hashtags:
       one: 'kontenas nepermisita hashtago: %{tags}'
       other: 'kontenas nepermisita hashtagi: %{tags}'
-    edited_at_html: Redaktesis ye %{date}
+    edited_at_html: Modifikesis ye %{date}
     errors:
       in_reply_not_found: Posto quon vu probas respondar semblas ne existas.
     over_character_limit: limito de %{max} signi ecesita
     pin_errors:
-      direct: Afishi qua nur videsebla ad mencionita uzanti ne povas adpinglesar
-      limit: Vu ja adpinglis la maxima afishquanto
-      ownership: Afisho di altra persono ne povas adpinglesar
+      direct: Posti quo povas videsar nur mencionita uzanti ne povas pinglagesar
+      limit: Vu ja pinglagis maxima posti
+      ownership: Posto di altra persono ne povas pinglagesar
       reblog: Repeto ne povas pinglizesar
     title: '%{name}: "%{quote}"'
     visibilities:
@@ -1762,16 +1613,16 @@ io:
     interaction_exceptions_explanation: Notale, ne existas garantio ke posti efacesos se oli iras sub la favoratala o repetala solii pos iras super oli.
     keep_direct: Retenez direta mesaji
     keep_direct_hint: Ne efacas irga vua direta mesaji
-    keep_media: Konservar afishi kun audvidajaddonaji
-    keep_media_hint: Ne forigas irga vua afishi qua havas audvidajaddonaji
-    keep_pinned: Konservar adpinglita afishi
-    keep_pinned_hint: Ne forigas irga vua adpinglita afishi
-    keep_polls: Konservar votinquesti
-    keep_polls_hint: Ne forigas irga vua votinquesti
-    keep_self_bookmark: Konservar afishi quan vu lektosignis
-    keep_self_bookmark_hint: Ne forigas vua sua afishi se vu lektosignis ili
-    keep_self_fav: Konservar afishi quan vu stelumis
-    keep_self_fav_hint: Ne forigas vua sua afishi se vu stelumis ili
+    keep_media: Retenez posti kun mediiatachaji
+    keep_media_hint: Ne efacas irga vua posti quo havas mediiatachaji
+    keep_pinned: Retenez pinglagita posti
+    keep_pinned_hint: Ne efacas irga vua pinglagita posti
+    keep_polls: Retenez votposti
+    keep_polls_hint: Ne efacas irga vua votposti
+    keep_self_bookmark: Retenez posti quon vu libromarkizis
+    keep_self_bookmark_hint: Ne efacas vua sua posti se vu libromarkizis li
+    keep_self_fav: Retenez posti quon vu favorizis
+    keep_self_fav_hint: Ne efacas vua sua posti se vu favorizis li
     min_age:
       '1209600': 2 semani
       '15778476': 6 monati
@@ -1782,7 +1633,7 @@ io:
       '63113904': 2 yari
       '7889238': 3 monati
     min_age_label: Oldeslimito
-    min_favs: Konservar stelumita afishi mine
+    min_favs: Retenez favorizita posti mine
     min_favs_hint: Ne efacas irgo ek vua posti qua havas adminime ca quanto de favoriziti. Restez vakua por efacar posti senegarde la nombro de favoriziti
     min_reblogs: Retenez posti repetita adminime
     min_reblogs_hint: Ne efacas irgo ek vua posti qua havas adminime ca quanto de repeti. Restez vakua por efacar posti senegarde la nombro de repeti
@@ -1803,12 +1654,12 @@ io:
       month: "%b %Y"
       time: "%H:%M"
   two_factor_authentication:
-    add: Adjuntar
+    add: Insertez
     disable: Extingar
-    disabled_success: Dufaktora yurizo sucese desebligesis
-    edit: Redaktar
-    enabled: Dufaktora yurizo es ebligita
-    enabled_success: Dufaktora yurizo sucese ebligesis
+    disabled_success: 2-faktoryurizeso sucesoze desaktivigesas
+    edit: Modifikez
+    enabled: 2-faktoryurizeso aktivigesas
+    enabled_success: 2-faktoryurizeso sucesoze aktivigesas
     generate_recovery_codes: Generate Recovery Codes
     lost_recovery_codes: Rigankodexi povigas vu riganas aceso a vua konto se vu perdas vua smartfono. Se vu perdas vua rigankodexi, vu povas rifacar hike. Vua olda rigankodexi nevalidigesos.
     methods: 2-faktormetodi
@@ -1827,16 +1678,13 @@ io:
       subject: Vua apelo de %{date} refuzesis
       title: Apelo refuzesis
     backup_ready:
-      extra: Ol es pronta por deskargo!
-      subject: Vua arkivo es pronta por deskargo
+      subject: Vua arkivo pronte deschargebla
       title: Arkivekpreno
-    failed_2fa:
-      further_actions_html: Se ol ne es vu, ni rekomendas ke vu %{action} quik pro ke ol eble maligita.
     suspicious_sign_in:
       change_password: chanjez vua pasvorto
       details: 'Co esas detali di eniro:'
       explanation: Ni deskovris eniro a vua konto de nova adreso IP.
-      further_actions_html: Se ol ne es vu, ni rekomendas ke vu %{action} quik e ebligas dufaktoryurizo por sekurigar vua konto.
+      further_actions_html: Se co ne agesis da vu, ni rekomendas ke vu %{action} quik e aktivigas 2-faktoryurizo por sekurigar vua konto.
       subject: Vua konto acesesis de nova adreso IP
       title: Nova eniro
     warning:
@@ -1847,11 +1695,11 @@ io:
         violation: Kontenajo nesequas ca komunitatguidili
       explanation:
         delete_statuses: Ol trovesis ke kelka vua posti nesequas komunitatguidilo e do efacesis da jereri di %{instance}.
-        disable: Vu ne povas uzar vua konto plue, ma vua profilo e altra informi restas kune.
-        mark_statuses_as_sensitive: Kelka vua afishi markesis quale trublema da administranti di %{instance}. Vu povas markar audvidaji quale sentema per su kande afishar en la estonteso.
-        sensitive: Pos co, omna vua adkargita audvidajdosieri markesos quale trublema e celesos dop klikaverto.
+        disable: Vu ne povas uzar vua konto pluse, ma vua profilo e altra informi restas bone. Vu povas demandar kopiur di vua informi, chanjar kontoopcioni e efacar vua konto.
+        mark_statuses_as_sensitive: Kelka vua posti markizesis quale sentoza da jereri di %{instance}. Do, personi bezonos kliktar medii en posti ante prevido montresas. Vu povas markizar medii quale sentoza per su kande postar en futuro.
+        sensitive: Pos co, omna vua adchargita mediifaili markizesos quale sentoza e celesos dop kliktoaverto.
         silence: Vu povas ankore uzar vua konto ma nur personi quo ja sequis vu vidos vua posti en ca servilo, e vu forsan neinkluzesas de diversa deskovrotraiti. Tamen, altra personi povas ankore manuala sequar vu.
-        suspend: Vu ne povas uzar vua konto plue, e vua profilo e altra informi ne es adirebla plue.
+        suspend: Vu ne povas uzar vua konto pluse, e vua profilo e altra informi ne esas acesebla pluse. Vu povas ankore enirar por demandar kopiur di vua informi til informi komplete efacesas pos cirkum 30 dii, ma ni retenos kelka bazala informi por preventar vu evitar restrikto.
       reason: 'Motivo:'
       statuses: 'Citita posti:'
       subject:
@@ -1871,12 +1719,7 @@ io:
         silence: Konto limitizesis
         suspend: Konto restriktigesis
     welcome:
-      apps_ios_action: Deskargez sur la aplikajbutiko
-      apps_step: Deskargez nia oficala aplikaji.
       explanation: Subo esas guidilo por helpar vu komencar
-      feature_creativity: Mastodon subtenas sono, video e imajafishi, adirpriskribi, votinquesti, enhavajaverti, anima imaji, personesigita emocimaji, imajettranchregulado e plu multo por helpar on expresar su enrete.
-      feature_moderation: Mastodon adportas decidebleso ad on. Adeskez servilo kun reguli quan vu akordas o gastigas vua sua kozo.
-      post_step: Salutez per texto, fotografi, videi o votinquesti.
       subject: Bonveno a Mastodon
       title: Bonveno, %{name}!
   users:
@@ -1890,19 +1733,19 @@ io:
     verification: Verifikeso
     verified_links: Vua ligili verifikata
   webauthn_credentials:
-    add: Adjuntar nova sekuresklefo
+    add: Insertez nova sekuresklefo
     create:
-      error: Esas problemo kande adjuntar vua sekuresklefo.
-      success: Vua sekuresklefo sucese adjuntesis.
+      error: Existas problemo kande insertar vua sekuresklefo. Probez itere.
+      success: Vua sekuresklefo sucesoze insertesas.
     delete: Efacez
     delete_confirmation: Ka vu certe volas efacar ca sekuresklefo?
-    description_html: Se vu ebligas <strong>sekuresklefyurizo</strong>, eniro bezonos vu uzar un ek vua sekuresklefi.
+    description_html: Se vu aktivigas <strong>sekuresklefyurizo</strong>, eniro bezonos vu uzar 1 de vua sekuresklefi.
     destroy:
       error: Existas problemo kande efacar vua sekuresklefo. Probez itere.
       success: Vua sekuresklefo sucesoze efacesis.
     invalid_credential: Nevalida sekuresklefo
     nickname_hint: Insertez nometo di vua nova sekuresklefo
-    not_enabled: Vu ne ebligis WebAuthn til nun
+    not_enabled: Vu ne aktivigesas WebAuthn til nun
     not_supported: Ca vidilo ne suportas sekuresklefi
-    otp_required: Por uzar sekuresklefi, ebligez dufaktora yurizo unesme.
+    otp_required: Por uzar sekuresklefi, aktivigez 2-faktoryurizeso unesme.
     registered_on: Registris ye %{date}
diff --git a/config/locales/is.yml b/config/locales/is.yml
index 0516fa4eb8..be0d8277e3 100644
--- a/config/locales/is.yml
+++ b/config/locales/is.yml
@@ -187,7 +187,6 @@ is:
         create_domain_block: Búa til útilokun léns
         create_email_domain_block: Búa til útilokun tölvupóstléns
         create_ip_block: Búa til IP-reglu
-        create_relay: Búa til endurvarpa
         create_unavailable_domain: Útbúa lén sem ekki er tiltækt
         create_user_role: Útbúa hlutverk
         demote_user: Lækka notanda í tign
@@ -199,22 +198,18 @@ is:
         destroy_email_domain_block: Eyða útilokun tölvupóstléns
         destroy_instance: Henda léni
         destroy_ip_block: Eyða IP-reglu
-        destroy_relay: Eyða endurvarpa
         destroy_status: Eyða færslu
         destroy_unavailable_domain: Eyða léni sem ekki er tiltækt
         destroy_user_role: Eyða hlutverki
         disable_2fa_user: Gera tveggja-þátta auðkenningu óvirka
         disable_custom_emoji: Gera sérsniðið tjáningartákn óvirkt
-        disable_relay: Gera endurvarpa óvirkan
         disable_sign_in_token_auth_user: Gera óvirka auðkenningu með teikni í tölvupósti fyrir notandann
         disable_user: Gera notanda óvirkan
         enable_custom_emoji: Virkja sérsniðið tjáningartákn
-        enable_relay: Virkja endurvarpa
         enable_sign_in_token_auth_user: Gera virka auðkenningu með teikni í tölvupósti fyrir notandann
         enable_user: Virkja notanda
         memorialize_account: Gera aðgang að minningargrein
         promote_user: Hækka notanda í tign
-        publish_terms_of_service: Birta þjónustuskilmála
         reject_appeal: Hafna áfrýjun
         reject_user: Hafna notanda
         remove_avatar_user: Fjarlægja auðkennismynd
@@ -252,7 +247,6 @@ is:
         create_domain_block_html: "%{name} útilokaði lénið %{target}"
         create_email_domain_block_html: "%{name} útilokaði póstlénið %{target}"
         create_ip_block_html: "%{name} útbjó reglu fyrir IP-vistfangið %{target}"
-        create_relay_html: "%{name} bjó til endurvarpa %{target}"
         create_unavailable_domain_html: "%{name} stöðvaði afhendingu til lénsins %{target}"
         create_user_role_html: "%{name} útbjó %{target} hlutverk"
         demote_user_html: "%{name} lækkaði notandann %{target} í tign"
@@ -264,22 +258,18 @@ is:
         destroy_email_domain_block_html: "%{name} aflétti útilokun af póstléninu %{target}"
         destroy_instance_html: "%{name} henti léninu %{target}"
         destroy_ip_block_html: "%{name} eyddi reglu fyrir IP-vistfangið %{target}"
-        destroy_relay_html: "%{name} eyddi endurvarpanum %{target}"
         destroy_status_html: "%{name} fjarlægði færslu frá %{target}"
         destroy_unavailable_domain_html: "%{name} hóf aftur afhendingu til lénsins %{target}"
         destroy_user_role_html: "%{name} eyddi hlutverki %{target}"
         disable_2fa_user_html: "%{name} gerði kröfu um tveggja-þátta innskráningu óvirka fyrir notandann %{target}"
         disable_custom_emoji_html: "%{name} gerði tjáningartáknið %{target} óvirkt"
-        disable_relay_html: "%{name} gerði endurvarpann %{target} óvirkan"
         disable_sign_in_token_auth_user_html: "%{name} gerði óvirka auðkenningu með teikni í tölvupósti fyrir %{target}"
         disable_user_html: "%{name} gerði innskráningu óvirka fyrir notandann %{target}"
         enable_custom_emoji_html: "%{name} gerði tjáningartáknið %{target} virkt"
-        enable_relay_html: "%{name} virkjaði endurvarpann %{target}"
         enable_sign_in_token_auth_user_html: "%{name} gerði virka auðkenningu með teikni í tölvupósti fyrir %{target}"
         enable_user_html: "%{name} gerði innskráningu virka fyrir notandann %{target}"
         memorialize_account_html: "%{name} breytti notandaaðgangnum %{target} í minningargreinarsíðu"
         promote_user_html: "%{name} hækkaði notandann %{target} í tign"
-        publish_terms_of_service_html: "%{name} gaf út uppfærða þjónustuskilmála"
         reject_appeal_html: "%{name} hafnaði áfrýjun á ákvörðun umsjónarmanns frá %{target}"
         reject_user_html: "%{name} hafnaði nýskráningu frá %{target}"
         remove_avatar_user_html: "%{name} fjarlægði auðkennismynd af %{target}"
@@ -309,7 +299,6 @@ is:
       title: Atvikaskrá
       unavailable_instance: "(heiti léns ekki tiltækt)"
     announcements:
-      back: Til baka í auglýsingar
       destroyed_msg: Það tókst að eyða auglýsingunni!
       edit:
         title: Breyta auglýsingu
@@ -318,10 +307,6 @@ is:
       new:
         create: Búa til auglýsingu
         title: Ný auglýsing
-      preview:
-        disclaimer: Þar sem notendur geta ekki afþakkað þær ætti aðeins að nota tilkynningar í tölvupósti fyrir mikilvægar upplýsingar á borð við persónuleg gagnabrot eða lokanir á netþjónum.
-        explanation_html: 'Tölvupósturinn verður sendur til <strong>%{display_count} notenda</strong>. Eftirfarandi texti verður í meginmáli póstsins:'
-        title: Forskoða tilkynninguna
       publish: Birta
       published_msg: Það tókst að birta auglýsinguna!
       scheduled_for: Áætlað %{time}
@@ -480,36 +465,6 @@ is:
       new:
         title: Flytja inn útilokanir léna
       no_file: Engin skrá valin
-    fasp:
-      debug:
-        callbacks:
-          created_at: Búið til
-          delete: Eyða
-          ip: IP-vistfang
-          request_body: Biðja um meginmál
-          title: Villuleita bakrakningar (callbacks)
-      providers:
-        active: Virkt
-        base_url: Grunnslóð
-        callback: Bakrakning (callback)
-        delete: Eyða
-        edit: Breyta þjónustuaðila
-        finish_registration: Ljúka skráningu
-        name: Nafn
-        providers: Þjónustuaðilar
-        public_key_fingerprint: Fingrafar dreifilykils
-        registration_requested: Beðið um skráningu
-        registrations:
-          confirm: Staðfesta
-          description: Þú fékkst skráningu frá FASP-þjónustuaðila. Hafnaðu þessu ef þú baðst ekki um þetta. Ef þú baðst um þetta, skaltu bera vandlega saman nöfn og fingraför lykla áður en þú staðfestir skráninguna.
-          reject: Hafna
-          title: Staðfesta FASP-skráningu
-        save: Vista
-        select_capabilities: Veldu eiginleika
-        sign_in: Skrá inn
-        status: Staða
-        title: Fediverse Auxiliary Service Providers þjónustuaðilar
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Að fylgja meðmælum hjálpar nýjum notendum að finna áhugavert efni á einfaldan máta</strong>. Þegar notandi hefur ekki átt í nægilegum samskiptum við aðra til að vera farinn að móta sér skoðanir á hverju hann vill fylgjast með, er mælt með að fylgjast með þessum aðgöngum. Þeir eru endurreiknaðir daglega út frá blöndu þeirra aðganga sem eru með hvað mestri þáttöku í umræðum og mesta fylgjendafjölda út frá hverju tungumáli."
       language: Fyrir tungumálið
@@ -865,10 +820,8 @@ is:
       back_to_account: Fara aftur á síðu notandaaðgangsins
       back_to_report: Til baka á kærusíðu
       batch:
-        add_to_report: 'Bæta við skýrslu #%{id}'
         remove_from_report: Fjarlægja úr kæru
         report: Kæra
-      contents: Efni
       deleted: Eytt
       favourites: Eftirlæti
       history: Útgáfuferill
@@ -877,17 +830,13 @@ is:
       media:
         title: Myndefni
       metadata: Lýsigögn
-      no_history: Færslunni hefur ekki verið breytt
       no_status_selected: Engum færslum var breytt þar sem engar voru valdar
       open: Opna færslu
       original_status: Upprunaleg færsla
       reblogs: Endurbirtingar
-      replied_to_html: Svaraði til %{acct_link}
       status_changed: Færslu breytt
-      status_title: Færsla frá @%{name}
-      title: Færslur notanda - @%{name}
+      title: Færslur notandaaðgangs
       trending: Vinsælt
-      view_publicly: Skoða opinberlega
       visibility: Sýnileiki
       with_media: Með myndefni
     strikes:
@@ -964,36 +913,6 @@ is:
       search: Leita
       title: Myllumerki
       updated_msg: Það tókst að uppfæra stillingar myllumerkja
-    terms_of_service:
-      back: Til baka í þjónustuskilmála
-      changelog: Hvað breyttist
-      create: Notaðu þína eigin
-      current: Núverandi
-      draft: Drög
-      generate: Nota sniðmát
-      generates:
-        action: Útbúa
-        chance_to_review_html: "<strong>Sjálfvirkt útbúnu þjónustuskilmálarnir verða ekki birtir sjálfkrafa.</strong> Þú munt geta yfirfarið textann. Fylltu inn nauðsynlegar upplýsingar til að halda áfram to proceed."
-        explanation_html: Sniðmátið fyrir þjónustuskilmála sem hér fylgir er einungis hugsat til upplýsingar og ætti ekki að taska sem lögfræðilegt álit varðandi hin ýmsu efni. Þú skalt endilega ráðfæra þig við löglærða í þínu umdæmi varðandi þau atriði sem eiga við um stöðu þína auk annarra spurninga um lögfræðileg mál sem þú gætir haft.
-        title: Uppsetning þjónustuskilmála
-      going_live_on_html: Í beinni, þann %{date}
-      history: Ferill
-      live: Í beinni
-      no_history: Það eru engar breytingar ennþá á þjónustuskilmálunum.
-      no_terms_of_service_html: Þú ert ekki með neina þjónustuskilmála stillta í augnablikinu. Þjónustuskilmálum er ætlað að gera ábyrgð ljósa og vernda þig fyrir mögulegum kröfum sem notendur gætu gert í ágreiningsmálum við þig.
-      notified_on_html: Notendur látnir vita þann %{date}
-      notify_users: Láta notendur vita
-      preview:
-        explanation_html: 'Tölvupósturinn verður sendur til <strong>%{display_count} notenda</strong> sem hafa skráð sig fyrir %{date}. Eftirfarandi texti verður hafður með í póstinum:'
-        send_preview: Senda forskoðun til %{email}
-        send_to_all:
-          one: Senda %{display_count} tölvupóst
-          other: Senda %{display_count} tölvupósta
-        title: Forskoða tilkynningu um breytingar á þjónustuskilmálum
-      publish: Birta
-      published_on_html: Gefið út þann %{date}
-      save_draft: Vista drög
-      title: Þjónustuskilmálar
     title: Stjórnendur
     trends:
       allow: Leyfa
@@ -1203,6 +1122,7 @@ is:
     migrate_account: Færa á annan notandaaðgang
     migrate_account_html: Ef þú vilt endurbeina þessum aðgangi á einhvern annan, geturðu <a href="%{path}">stillt það hér</a>.
     or_log_in_with: Eða skráðu inn með
+    privacy_policy_agreement_html: Ég hef lesið og samþykkt <a href="%{privacy_policy_path}" target="_blank">persónuverndarstefnuna</a>
     progress:
       confirm: Staðfesta tölvupóstfang
       details: Nánari upplýsingar þínar
@@ -1227,7 +1147,7 @@ is:
     set_new_password: Stilla nýtt lykilorð
     setup:
       email_below_hint_html: Skoðaðu ruslpóstmöppuna þína, eða biddu um annan póst. Þú getur leiðrétt tölvupóstfangið þitt ef þörf er á.
-      email_settings_hint_html: Smelltu á tengilinn sem við sendum á %{email} til að byrja að nota Mastodon. Við bíðum hér á meðan.
+      email_settings_hint_html: Ýttu á tengilinn sem við sendum þér til að staðfesta %{email}. Við bíðum á meðan.
       link_not_received: Fékkstu ekki neinn tengil?
       new_confirmation_instructions_sent: Þú munt fá nýjan tölvupóst með staðfestingartengli innan skamms!
       title: Athugaðu pósthólfið þitt
@@ -1236,7 +1156,7 @@ is:
       title: Skrá inn á %{domain}
     sign_up:
       manual_review: Nýskráningar á %{domain} fara í gegnum handvirka yfirferð hjá umsjónarfólkinu okkar. Til að flýta fyrir skráningarferlinu, ættirðu að skrifa smávegis um þig og ástæður þess að þú viljir skrá þig á %{domain}.
-      preamble: Með notandaaðgangi á þessum Mastodon-þjóni geturðu fylgst með hverjum sem er á sambandsnetinu, sama hvar notandaaðgangurinn þeirra er hýstur.
+      preamble: Með notandaaðgangi á þessum Mastodon-þjóni geturðu fylgst með hverjum sem er á netkerfinu, sama hvar notandaaðgangurinn þeirra er hýstur.
       title: Förum núna að setja þig upp á %{domain}.
     status:
       account_status: Staða notandaaðgangs
@@ -1248,8 +1168,6 @@ is:
       view_strikes: Skoða fyrri bönn notandaaðgangsins þíns
     too_fast: Innfyllingarform sent inn of hratt, prófaðu aftur.
     use_security_key: Nota öryggislykil
-    user_agreement_html: Ég hef lesið og samþykkt <a href="%{terms_of_service_path}" target="_blank">þjónustuskilmálana</a> og <a href="%{privacy_policy_path}" target="_blank">stefnuna um persónuvernd</a>
-    user_privacy_agreement_html: Ég hef lesið og samþykkt <a href="%{privacy_policy_path}" target="_blank">persónuverndarstefnuna</a>
   author_attribution:
     example_title: Sýnitexti
     hint_html: Ertu að skrifa fréttir eða bloggfærslur utan Mastodon? Stýrðu því hvernig vitnað er í þig þegar þeim er deilt á Mastodon.
@@ -1455,43 +1373,19 @@ is:
       overwrite: Skrifa yfir
       overwrite_long: Skipta út fyrirliggjandi færslum með þeim nýju
     overwrite_preambles:
-      blocking_html:
-        one: Þú er í þann mund að fara að <strong>skipta út útilokanalistanum þínum</strong> með allt að <strong>%{count} aðgangi</strong> úr <strong>%{filename}</strong>.
-        other: Þú er í þann mund að fara að <strong>skipta út útilokanalistanum þínum</strong> með allt að <strong>%{count} aðgöngum</strong> úr <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Þú er í þann mund að fara að <strong>skipta út bókamerkjunum þínum</strong> með allt að <strong>%{count} færslu</strong> úr <strong>%{filename}</strong>.
-        other: Þú er í þann mund að fara að <strong>skipta út bókamerkjunum þínum</strong> með allt að <strong>%{count} færslum</strong> úr <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Þú er í þann mund að fara að <strong>skipta út listanum þínum yfir útilokuð lén</strong> með allt að <strong>%{count} léni</strong> úr <strong>%{filename}</strong>.
-        other: Þú er í þann mund að fara að <strong>skipta út listanum þínum yfir útilokuð lén</strong> með allt að <strong>%{count} lénum</strong> úr <strong>%{filename}</strong>.
-      following_html:
-        one: Þú er í þann mund að fara að <strong>fylgjast með</strong> allt að <strong>%{count} aðgangi</strong> úr <strong>%{filename}</strong> og <strong>hætta að fylgjast með öllum öðrum</strong>.
-        other: Þú er í þann mund að fara að <strong>fylgjast með</strong> allt að <strong>%{count} aðgöngum</strong> úr <strong>%{filename}</strong> og <strong>hætta að fylgjast með öllum öðrum</strong>.
-      lists_html:
-        one: Þú ert í þann mund að fara að <strong>skipta út listunum þínum</strong> með efninu úr <strong>%{filename}</strong>. Allt að <strong>%{count} aðgangi</strong> verður bætt við nýju listana.
-        other: Þú ert í þann mund að fara að <strong>skipta út listunum þínum</strong> með efninu úr <strong>%{filename}</strong>. Allt að <strong>%{count} aðgöngum</strong> verður bætt við nýju listana.
-      muting_html:
-        one: Þú er í þann mund að fara að <strong>skipta út listanum þínum yfir útilokaða aðganga</strong> með allt að <strong>%{count} aðgangi</strong> úr <strong>%{filename}</strong>.
-        other: Þú er í þann mund að fara að <strong>skipta út listanum þínum yfir útilokaða aðganga</strong> með allt að <strong>%{count} aðgöngum</strong> úr <strong>%{filename}</strong>.
+      blocking_html: Þú er í þann mund að fara að <strong>skipta út útilokanalistanum þínum</strong> með allt að <strong>%{total_items} aðgöngum</strong> úr <strong>%{filename}</strong>.
+      bookmarks_html: Þú er í þann mund að fara að <strong>skipta út bókamerkjunum þínum</strong> með allt að <strong>%{total_items} færslum</strong> úr <strong>%{filename}</strong>.
+      domain_blocking_html: Þú er í þann mund að fara að <strong>skipta út listanum þínum yfir útilokuð lén</strong> með allt að <strong>%{total_items} lénum</strong> úr <strong>%{filename}</strong>.
+      following_html: Þú er í þann mund að fara að <strong>fylgjast með</strong> allt að <strong>%{total_items} aðgöngum</strong> úr <strong>%{filename}</strong> og <strong>hætta að fylgjast með öllum öðrum</strong>.
+      lists_html: Þú ert í þann mund að fara að <strong>skipta út listunum þínum</strong> með efninu úr <strong>%{filename}</strong>. Allt að <strong>%{total_items} aðgöngum</strong> verður bætt við nýju listana.
+      muting_html: Þú er í þann mund að fara að <strong>skipta út listanum þínum yfir útilokaða aðganga</strong> með allt að <strong>%{total_items} aðgöngum</strong> úr <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Þú ert í þann mund að <strong>útiloka</strong> allt að <strong>%{count} aðgang</strong> úr <strong>%{filename}</strong>.
-        other: Þú ert í þann mund að <strong>útiloka</strong> allt að <strong>%{count} aðganga</strong> úr <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Þú er í þann mund að fara að bæta við allt að <strong>%{count} færslu</strong> úr <strong>%{filename}</strong> við <strong>bókamerkin</strong> þín.
-        other: Þú er í þann mund að fara að bæta við allt að <strong>%{count} færslum</strong> úr <strong>%{filename}</strong> við <strong>bókamerkin</strong> þín.
-      domain_blocking_html:
-        one: Þú ert í þann mund að <strong>útiloka</strong> allt að <strong>%{count} lén</strong> úr <strong>%{filename}</strong>.
-        other: Þú er í þann mund að fara að <strong>útiloka</strong> allt að <strong>%{count} lén</strong> úr <strong>%{filename}</strong>.
-      following_html:
-        one: Þú ert að fara að <strong>fylgjast með</strong> allt að <strong>%{count} aðgangi</strong> úr <strong>%{filename}</strong>.
-        other: Þú er að fara að <strong>fylgjast með</strong> allt að <strong>%{count} aðgöngum</strong> úr <strong>%{filename}</strong>.
-      lists_html:
-        one: Þú ert í þann mund að fara að bæta við allt að <strong>%{count} aðgangi</strong> úr <strong>%{filename}</strong> við <strong>listana</strong> þína. Nýir listar verða útbúnir ef ekki finnst neinn listi til að bæta í.
-        other: Þú ert í þann mund að fara að bæta við allt að <strong>%{count} aðgöngum</strong> úr <strong>%{filename}</strong> við <strong>listana</strong> þína. Nýir listar verða útbúnir ef ekki finnst neinn listi til að bæta í.
-      muting_html:
-        one: Þú ert að fara að <strong>þagga niður í</strong> allt að <strong>%{count} aðgangi</strong> úr <strong>%{filename}</strong>.
-        other: Þú ert að fara að <strong>þagga niður í</strong> allt að <strong>%{count} aðgöngum</strong> úr <strong>%{filename}</strong>.
+      blocking_html: Þú er í þann mund að fara að <strong>útiloka</strong> allt að <strong>%{total_items} aðganga</strong> úr <strong>%{filename}</strong>.
+      bookmarks_html: Þú er í þann mund að fara að bæta við allt að <strong>%{total_items} færslum</strong> úr <strong>%{filename}</strong> við <strong>bókamerkin</strong> þín.
+      domain_blocking_html: Þú er í þann mund að fara að <strong>útiloka</strong> allt að <strong>%{total_items} lén</strong> úr <strong>%{filename}</strong>.
+      following_html: Þú er í þann mund að fara að <strong>fylgjast með</strong> allt að <strong>%{total_items} aðgöngum</strong> úr <strong>%{filename}</strong>.
+      lists_html: Þú ert í þann mund að fara að bæta við allt að <strong>%{total_items} aðgöngum</strong> úr <strong>%{filename}</strong> við <strong>listana</strong> þína. Nýir listar verða útbúnir ef ekki finnst neinn listi til að bæta í.
+      muting_html: Þú er í þann mund að fara að <strong>þagga</strong> allt að <strong>%{total_items} aðganga</strong> úr <strong>%{filename}</strong>.
     preface: Þú getur flutt inn gögn sem þú hefur flutt út frá öðrum vefþjóni, svo sem lista yfir fólk sem þú fylgist með eða útilokar.
     recent_imports: Nýlega flutt inn
     states:
@@ -1748,7 +1642,7 @@ is:
   scheduled_statuses:
     over_daily_limit: Þú hefur farið fram úr hámarkinu með %{limit} áætlaðar færslur fyrir þennan dag
     over_total_limit: Þú hefur farið fram úr hámarkinu með %{limit} áætlaðar færslur
-    too_soon: dagsetning verður að vera fram í tímann
+    too_soon: Áætluð dagsetning verður að vera í framtíðinni
   self_destruct:
     lead_html: Því miður, <strong>%{domain}</strong> er að hætta starfsemi endanlega. Ef þú varst með aðgang þar, muntu ekki geta haldið áfram að nota hann, en þú getur áfram beðið um afrit af gögnunum þínum.
     title: Þessi netþjónn er að hætta starfsemi
@@ -1911,8 +1805,6 @@ is:
       too_late: Það er orðið of sint að áfrýja þessari refsingu
   tags:
     does_not_match_previous_name: samsvarar ekki fyrra nafni
-  terms_of_service:
-    title: Þjónustuskilmálar
   themes:
     contrast: Mastodon (mikil birtuskil)
     default: Mastodon (dökkt)
@@ -1944,10 +1836,6 @@ is:
     recovery_instructions_html: Ef þú tapar símanum þínum geturðu notað einn af endurheimtukóðunum hér fyrir neðan til að fá aftur samband við notandaaðganginn þinn. <strong>Geymdu endurheimtukóðana á öruggum stað</strong>. Sem dæmi gætirðu prentað þá út og geymt með öðrum mikilvægum skjölum.
     webauthn: Öryggislyklar
   user_mailer:
-    announcement_published:
-      description: 'Stjórnendur %{domain} eru að senda frá sér yfirlýsingu:'
-      subject: Auglýsing vegna þjónustu
-      title: Auglýsing vegna þjónustu %{domain}
     appeal_approved:
       action: Stillingar notandaaðgangs
       explanation: Áfrýjun refsingarinnar gagnvart aðgangnum þínum þann %{strike_date} sem þú sendir inn þann %{appeal_date} hefur verið samþykkt. Notandaaðgangurinn þinn er aftur í góðu lagi.
@@ -1977,15 +1865,6 @@ is:
       further_actions_html: Ef þetta varst ekki þú, þá mælum við með að þú %{action} strax og virkjir tveggja-þátta auðkenningu til að halda aðgangnum þínum öruggum.
       subject: Skráð hefur verið inn á aðganginn þinn frá nýju IP-vistfangi
       title: Ný innskráning
-    terms_of_service_changed:
-      agreement: Með því að halda áfram að nota %{domain}, ert þú þar með að samþykkja þessa skilmála. Ef þú ert ósammála þessum uppfærðu skilmálum, geturðu hvenær sem er sagt upp samþykki þínu gagnvart %{domain} með því að eyða notandaaðgangi þínum.
-      changelog: 'Í stuttu máli er það þetta sem þessi uppfærsla þýðir fyrir þig:'
-      description: 'Þú ert að fá þennan tölvupóst vegna þess að við erum að gera breytingar á þjónustuskilmálum á %{domain}. Þessar breytingar taka gildi þann %{date}. Við hvetjum þig til að kynna þér þessar breytingar hér:'
-      description_html: Þú ert að fá þennan tölvupóst vegna þess að við erum að gera breytingar á þjónustuskilmálum á %{domain}. Þessar breytingar taka gildi þann <strong>%{date}</strong>. Við hvetjum þig til að kynna þér <a href="%{path}" target="_blank">þessar breytingar hér</a>.
-      sign_off: "%{domain}-teymið"
-      subject: Breytingar á þjónustuskilmálum okkar
-      subtitle: Þjónustuskilmálar eru að breytast á %{domain}
-      title: Mikilvæg uppfærsla
     warning:
       appeal: Senda inn áfrýjun
       appeal_description: Ef þú álítur að um mistök sé að ræða, geturðu sent áfrýjun til umsjónarmanna %{instance}.
diff --git a/config/locales/it.yml b/config/locales/it.yml
index 95096b07c8..554888abea 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -187,7 +187,6 @@ it:
         create_domain_block: Crea Blocco del Dominio
         create_email_domain_block: Crea blocco del dominio e-mail
         create_ip_block: Crea regola IP
-        create_relay: Crea Relay
         create_unavailable_domain: Crea Dominio Non Disponibile
         create_user_role: Crea Ruolo
         demote_user: Retrocedi Utente
@@ -199,22 +198,18 @@ it:
         destroy_email_domain_block: Elimina il blocco del dominio e-mail
         destroy_instance: Elimina Dominio
         destroy_ip_block: Elimina regola IP
-        destroy_relay: Elimina Relay
         destroy_status: Elimina Toot
         destroy_unavailable_domain: Elimina Dominio Non Disponibile
         destroy_user_role: Distruggi Ruolo
         disable_2fa_user: Disabilita A2F
         disable_custom_emoji: Disabilita Emoji Personalizzata
-        disable_relay: Disabilita Relay
         disable_sign_in_token_auth_user: Disabilita l'autenticazione del token e-mail per l'utente
         disable_user: Disabilita l'Utente
         enable_custom_emoji: Abilita Emoji Personalizzata
-        enable_relay: Abilita Relay
         enable_sign_in_token_auth_user: Abilita l'autenticazione del token e-mail per l'utente
         enable_user: Abilita l'Utente
         memorialize_account: Commemora Profilo
         promote_user: Promuovi Utente
-        publish_terms_of_service: Pubblicare i Termini di Servizio
         reject_appeal: Rifiuta Ricorso
         reject_user: Rifiuta Utente
         remove_avatar_user: Rimuovi Avatar
@@ -252,7 +247,6 @@ it:
         create_domain_block_html: "%{name} ha bloccato il dominio %{target}"
         create_email_domain_block_html: "%{name} ha bloccato il dominio e-mail %{target}"
         create_ip_block_html: "%{name} ha creato una regola per l'IP %{target}"
-        create_relay_html: "%{name} ha creato un relay %{target}"
         create_unavailable_domain_html: "%{name} ha interrotto la consegna al dominio %{target}"
         create_user_role_html: "%{name} ha creato il ruolo %{target}"
         demote_user_html: "%{name} ha retrocesso l'utente %{target}"
@@ -264,22 +258,18 @@ it:
         destroy_email_domain_block_html: "%{name} ha sbloccato il dominio e-mail %{target}"
         destroy_instance_html: "%{name} ha eliminato il dominio %{target}"
         destroy_ip_block_html: "%{name} ha eliminato la regola per l'IP %{target}"
-        destroy_relay_html: "%{name} ha eliminato il relay %{target}"
         destroy_status_html: "%{name} ha rimosso il toot di %{target}"
         destroy_unavailable_domain_html: "%{name} ha ripreso la consegna al dominio %{target}"
         destroy_user_role_html: "%{name} ha eliminato il ruolo %{target}"
         disable_2fa_user_html: "%{name} ha disabilitato l'autenticazione a due fattori per l'utente %{target}"
         disable_custom_emoji_html: "%{name} ha disabilitato emoji %{target}"
-        disable_relay_html: "%{name} ha disabilitato il relay %{target}"
         disable_sign_in_token_auth_user_html: "%{name} ha disabilitato l'autenticazione del token e-mail per %{target}"
         disable_user_html: "%{name} ha disabilitato l'accesso per l'utente %{target}"
         enable_custom_emoji_html: "%{name} ha abilitato l'emoji %{target}"
-        enable_relay_html: "%{name} ha abilitato il relay %{target}"
         enable_sign_in_token_auth_user_html: "%{name} ha abilitato l'autenticazione del token e-mail per %{target}"
         enable_user_html: "%{name} ha abilitato l'accesso per l'utente %{target}"
         memorialize_account_html: "%{name} ha trasformato il profilo di %{target} in una pagina commemorativa"
         promote_user_html: "%{name} ha promosso l'utente %{target}"
-        publish_terms_of_service_html: "%{name} ha pubblicato aggiornamenti ai termini di servizio"
         reject_appeal_html: "%{name} ha rifiutato il ricorso alla decisione di moderazione da %{target}"
         reject_user_html: "%{name} ha rifiutato l'iscrizione da %{target}"
         remove_avatar_user_html: "%{name} ha rimosso l'avatar di %{target}"
@@ -309,7 +299,6 @@ it:
       title: Registro di controllo
       unavailable_instance: "(nome di dominio non disponibile)"
     announcements:
-      back: Torna agli annunci
       destroyed_msg: Annuncio eliminato!
       edit:
         title: Modifica annuncio
@@ -318,9 +307,6 @@ it:
       new:
         create: Crea annuncio
         title: Nuovo annuncio
-      preview:
-        explanation_html: 'L''e-mail verrà inviata a <strong>%{display_count} utenti</strong>. Il seguente testo sarà incluso nell''e-mail:'
-        title: Anteprima della notifica dell'annuncio
       publish: Pubblica
       published_msg: Annuncio pubblicato!
       scheduled_for: Programmato per %{time}
@@ -479,36 +465,6 @@ it:
       new:
         title: Importare i blocchi di dominio
       no_file: Nessun file selezionato
-    fasp:
-      debug:
-        callbacks:
-          created_at: Creato il
-          delete: Cancella
-          ip: Indirizzo IP
-          request_body: Request body
-          title: Debug Callbacks
-      providers:
-        active: Attivo
-        base_url: Url di base
-        callback: Callback
-        delete: Cancella
-        edit: Modifica Provider
-        finish_registration: Registrazione terminata
-        name: Nome
-        providers: Providers
-        public_key_fingerprint: Chiave pubblica fingerprint
-        registration_requested: Registrazione richiesta
-        registrations:
-          confirm: Conferma
-          description: Hai ricevuto una registrazione da un FASP. Rifiutala se non l'hai avviata tu. Se l'hai avviata tu, confronta attentamente nome e impronta della chiave prima di confermare la registrazione.
-          reject: Rifiuta
-          title: Conferma registrazione FASP
-        save: Salva
-        select_capabilities: Seleziona Capacità
-        sign_in: Connettiti
-        status: Stato
-        title: Fornitori di Servizi Ausiliari per il Fediverso
-      title: FASP
     follow_recommendations:
       description_html: "<strong>I consigli su chi seguire aiutano i nuovi utenti a trovare rapidamente dei contenuti interessanti</strong>. Quando un utente non ha interagito abbastanza con altri per avere dei consigli personalizzati, vengono consigliati questi account. Sono ricalcolati ogni giorno da un misto di account con le più alte interazioni recenti e con il maggior numero di seguaci locali per una data lingua."
       language: Per lingua
@@ -862,10 +818,8 @@ it:
       back_to_account: Torna alla pagina dell'account
       back_to_report: Torna alla pagina del report
       batch:
-        add_to_report: 'Aggiungi alla segnalazione #%{id}'
         remove_from_report: Rimuovi dal report
         report: Rapporto
-      contents: Contenuti
       deleted: Cancellato
       favourites: Preferiti
       history: Cronologia delle versioni
@@ -874,17 +828,13 @@ it:
       media:
         title: Media
       metadata: Metadati
-      no_history: Questo post non è stato modificato
       no_status_selected: Nessun status è stato modificato perché nessuno era stato selezionato
       open: Apri il post
       original_status: Post originale
       reblogs: Condivisioni
-      replied_to_html: Risposta a %{acct_link}
       status_changed: Post modificato
-      status_title: Post di @%{name}
-      title: Post dell'account - @%{name}
+      title: Gli status dell'account
       trending: Di tendenza
-      view_publicly: Visualizza pubblicamente
       visibility: Visibilità
       with_media: con media
     strikes:
@@ -961,36 +911,6 @@ it:
       search: Cerca
       title: Hashtag
       updated_msg: Impostazioni hashtag aggiornate con successo
-    terms_of_service:
-      back: Torna ai termini di servizio
-      changelog: Cosa è cambiato
-      create: Usa i tuoi
-      current: Attuale
-      draft: Bozza
-      generate: Usa il modello
-      generates:
-        action: Genera
-        chance_to_review_html: "<strong>I termini di servizio generati non verranno pubblicati automaticamente.</strong> Avrai la possibilità di esaminare i risultati. Si prega di inserire i dettagli necessari per procedere."
-        explanation_html: Il modello di termini di servizio fornito è solo a scopo informativo e non deve essere interpretato come consulenza legale su alcun argomento. Si prega di consultare il proprio consulente legale, in merito alla propria situazione e alle specifiche questioni legali che si hanno.
-        title: Impostazione dei Termini di Servizio
-      going_live_on_html: In vigore il %{date}
-      history: Cronologia
-      live: In uso
-      no_history: Non sono ancora state registrate modifiche ai termini di servizio.
-      no_terms_of_service_html: Al momento non hai configurato alcun termine di servizio. I termini di servizio sono pensati per fornire chiarezza e proteggerti da potenziali responsabilità in caso di controversie con i tuoi utenti.
-      notified_on_html: 'Utenti notificati in data: %{date}'
-      notify_users: Notifica gli utenti
-      preview:
-        explanation_html: 'L''email verrà inviata a <strong>%{display_count} utenti</strong> che si sono registrati prima del giorno %{date}. Il seguente testo sarà incluso nell''email:'
-        send_preview: Invia l'anteprima a %{email}
-        send_to_all:
-          one: Invia %{display_count} email
-          other: Invia %{display_count} email
-        title: Anteprima della notifica dei termini di servizio
-      publish: Pubblica
-      published_on_html: 'Pubblicati in data: %{date}'
-      save_draft: Salva la bozza
-      title: Termini di Servizio
     title: Amministrazione
     trends:
       allow: Consenti
@@ -1200,6 +1120,7 @@ it:
     migrate_account: Sposta ad un account differente
     migrate_account_html: Se vuoi che questo account sia reindirizzato a uno diverso, puoi <a href="%{path}">configurarlo qui</a>.
     or_log_in_with: Oppure accedi con
+    privacy_policy_agreement_html: Ho letto e accetto l'<a href="%{privacy_policy_path}" target="_blank">informativa sulla privacy</a>
     progress:
       confirm: Conferma l'e-mail
       details: I tuoi dettagli
@@ -1224,7 +1145,7 @@ it:
     set_new_password: Imposta una nuova password
     setup:
       email_below_hint_html: Controlla la tua cartella spam o richiedine un'altra. Puoi correggere il tuo indirizzo e-mail, se è sbagliato.
-      email_settings_hint_html: Clicca sul collegamento che abbiamo inviato all'indirizzo %{email}, per iniziare a usare Mastodon. Ti aspettiamo qui.
+      email_settings_hint_html: Fai clic sul link che ti abbiamo inviato per verificare %{email}. Aspetteremo proprio qui.
       link_not_received: Non hai ricevuto un link?
       new_confirmation_instructions_sent: Riceverai una nuova e-mail con il link di conferma entro pochi minuti!
       title: Controlla la tua posta in arrivo
@@ -1233,7 +1154,7 @@ it:
       title: Accedi a %{domain}
     sign_up:
       manual_review: Le registrazioni su %{domain} vengono sottoposte a revisione manuale da parte dei nostri moderatori. Per aiutarci a elaborare la tua registrazione, scrivi qualcosa su di te e sul motivo per cui desideri un account su %{domain}.
-      preamble: Con un account su questo server Mastodon, potrai seguire qualsiasi altra persona sul fediverso, indipendentemente da dove sia ospitato il suo account.
+      preamble: Con un account su questo server Mastodon, sarai in grado di seguire qualsiasi altra persona sulla rete, indipendentemente da dove sia ospitato il suo account.
       title: Lascia che ti configuriamo su %{domain}.
     status:
       account_status: Stato dell'account
@@ -1245,8 +1166,6 @@ it:
       view_strikes: Visualizza le sanzioni precedenti prese nei confronti del tuo account
     too_fast: Modulo inviato troppo velocemente, riprova.
     use_security_key: Usa la chiave di sicurezza
-    user_agreement_html: Ho letto e accetto i <a href="%{terms_of_service_path}" target="_blank">termini di servizio</a> e l'<a href="%{privacy_policy_path}" target="_blank">informativa sulla privacy</a>
-    user_privacy_agreement_html: Ho letto e accetto l'<a href="%{privacy_policy_path}" target="_blank">informativa sulla privacy</a>
   author_attribution:
     example_title: Testo di esempio
     hint_html: Stai scrivendo notizie o articoli di blog al di fuori di Mastodon? Controlla come vieni accreditato quando vengono condivisi su Mastodon.
@@ -1452,43 +1371,19 @@ it:
       overwrite: Sovrascrivi
       overwrite_long: Sostituisci record attuali con quelli nuovi
     overwrite_preambles:
-      blocking_html:
-        one: Stai per <strong>sostituire la tua lista di blocchi</strong> con <strong>%{count} account</strong> da <strong>%{filename}</strong>.
-        other: Stai per <strong>sostituire la tua lista di blocchi</strong> con <strong>%{count} account</strong> da <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Stai per <strong>sostituire i tuoi segnalibri</strong> con <strong>%{count} post</strong> da <strong>%{filename}</strong>.
-        other: Stai per <strong>sostituire i tuoi segnalibri</strong> con <strong>%{count} post</strong> da <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Stai per <strong>sostituire la tua lista di domini bloccati</strong> con <strong>%{count} dominio</strong> da <strong>%{filename}</strong>.
-        other: Stai per <strong>sostituire la tua lista di domini bloccati</strong> con <strong>%{count} domini</strong> da <strong>%{filename}</strong>.
-      following_html:
-        one: Stai per <strong>seguire</strong> <strong>%{count} account</strong> da <strong>%{filename}</strong> e <strong>smettere di seguire chiunque altro</strong>.
-        other: Stai per <strong>seguire</strong> <strong>%{count} account</strong> da <strong>%{filename}</strong> e <strong>smettere di seguire chiunque altro</strong>.
-      lists_html:
-        one: Stai per <strong>sostituire le tue liste</strong> con il contenuto di <strong>%{filename}</strong>. Verrà aggiunto <strong>%{count} account</strong> alle nuove liste.
-        other: Stai per <strong>sostituire le tue liste</strong> con il contenuto di <strong>%{filename}</strong>. Verranno aggiunti <strong>%{count} account</strong> alle nuove liste.
-      muting_html:
-        one: Stai per <strong>sostituire la lista degli account silenziati</strong> con <strong>%{count} account</strong> da <strong>%{filename}</strong>.
-        other: Stai per <strong>sostituire la lista degli account silenziati</strong> con <strong>%{count} account</strong> da <strong>%{filename}</strong>.
+      blocking_html: Stai per <strong>sostituire la tua lista di blocchi</strong> con un massimo di <strong>%{total_items} account</strong> da <strong>%{filename}</strong>.
+      bookmarks_html: Stai per <strong>sostituire i tuoi segnalibri</strong> con un massimo di <strong>%{total_items} post</strong> da <strong>%{filename}</strong>.
+      domain_blocking_html: Stai per <strong>sostituire la tua lista di domini bloccati</strong> con un massimo di <strong>%{total_items} domini</strong> da <strong>%{filename}</strong>.
+      following_html: Stai per <strong>seguire</strong> fino a <strong>%{total_items} account</strong> da <strong>%{filename}</strong> e <strong>smettere di seguire chiunque altro</strong>.
+      lists_html: Stai per <strong>sostituire le tue liste</strong> con i contenuti di <strong>%{filename}</strong>. Fino a <strong>%{total_items} profili</strong> verranno aggiunti a nuove liste.
+      muting_html: Stai per <strong>sostituire la tua lista di account silenziati</strong> con un massimo di <strong>%{total_items} account</strong> da <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Stai per <strong>bloccare</strong> <strong>%{count} account</strong> da <strong>%{filename}</strong>.
-        other: Stai per <strong>bloccare</strong> <strong>%{count} account</strong> da <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Stai per aggiungere <strong>%{count} post</strong> da <strong>%{filename}</strong> ai tuoi <strong>segnalibri</strong>.
-        other: Stai per aggiungere <strong>%{count} post</strong> da <strong>%{filename}</strong> ai tuoi <strong>segnalibri</strong>.
-      domain_blocking_html:
-        one: Stai per <strong>bloccare</strong> <strong>%{count} dominio</strong> da <strong>%{filename}</strong>.
-        other: Stai per <strong>bloccare</strong> <strong>%{count} domini</strong> da <strong>%{filename}</strong>.
-      following_html:
-        one: Stai per <strong>seguire</strong> <strong>%{count} account</strong> da <strong>%{filename}</strong>.
-        other: Stai per <strong>seguire</strong> <strong>%{count} account</strong> da <strong>%{filename}</strong>.
-      lists_html:
-        one: Stai per aggiungere <strong>%{count} account</strong> da <strong>%{filename}</strong> alle tue <strong>liste</strong>. Saranno create nuove liste, se non ce ne sono altre da aggiungere.
-        other: Stai per aggiungere <strong>%{count} account</strong> da <strong>%{filename}</strong> alle tue <strong>liste</strong>. Saranno create nuove liste, se non ce ne sono altre da aggiungere.
-      muting_html:
-        one: Stai per <strong>silenziare</strong> <strong>%{count} account</strong> da <strong>%{filename}</strong>.
-        other: Stai per <strong>silenziare</strong> <strong>%{count} account</strong> da <strong>%{filename}</strong>.
+      blocking_html: Stai per <strong>bloccare</strong> fino a <strong>%{total_items} account</strong> da <strong>%{filename}</strong>.
+      bookmarks_html: Stai per aggiungere fino a <strong>%{total_items} post</strong> da <strong>%{filename}</strong> ai tuoi <strong>segnalibri</strong>.
+      domain_blocking_html: Stai per <strong>bloccare</strong> fino a <strong>%{total_items} domini</strong> da <strong>%{filename}</strong>.
+      following_html: Stai per <strong>seguire</strong> fino a <strong>%{total_items} account</strong> da <strong>%{filename}</strong>.
+      lists_html: Stai per aggiungere fino a <strong>%{total_items} profili</strong> da <strong>%{filename}</strong> alla tue <strong>liste</strong>. Le nuove liste saranno create se non c'è una lista a cui aggiungere.
+      muting_html: Stai per <strong>silenziare</strong> fino a <strong>%{total_items} account</strong> da <strong>%{filename}</strong>.
     preface: Puoi importare alcune informazioni, come le persone che segui o hai bloccato su questo server, da file creati da un'esportazione su un altro server.
     recent_imports: Importazioni recenti
     states:
@@ -1745,7 +1640,7 @@ it:
   scheduled_statuses:
     over_daily_limit: Hai superato il limite di %{limit} post programmati per questo giorno
     over_total_limit: Hai superato il limite di %{limit} post programmati
-    too_soon: la data deve essere nel futuro
+    too_soon: La data di pubblicazione deve essere nel futuro
   self_destruct:
     lead_html: Sfortunatamente, <strong>%{domain}</strong> sta chiudendo definitivamente. Se hai un account lì, non potrai continuare a usarlo, ma puoi ancora richiedere un backup dei tuoi dati.
     title: Questo server sta chiudendo
@@ -1908,8 +1803,6 @@ it:
       too_late: È troppo tardi per fare appello contro questa sanzione
   tags:
     does_not_match_previous_name: non corrisponde al nome precedente
-  terms_of_service:
-    title: Termini di Servizio
   themes:
     contrast: Mastodon (contrasto elevato)
     default: Mastodon (scuro)
@@ -1941,10 +1834,6 @@ it:
     recovery_instructions_html: Se perdi il telefono, puoi usare uno dei codici di recupero qui sotto per riottenere l'accesso al tuo account. <strong>Conserva i codici di recupero in un posto sicuro</strong>. Ad esempio puoi stamparli e conservarli insieme ad altri documenti importanti.
     webauthn: Chiavi di sicurezza
   user_mailer:
-    announcement_published:
-      description: 'Gli amministratori di %{domain} stanno facendo un annuncio:'
-      subject: Annuncio di servizio
-      title: Annuncio di servizio da %{domain}
     appeal_approved:
       action: Impostazioni account
       explanation: L'appello della sanzione contro il tuo account del %{strike_date} che hai inviato il %{appeal_date} è stato approvato. Il tuo account ha riottenuto la buona reputazione.
@@ -1974,15 +1863,6 @@ it:
       further_actions_html: Se non eri tu, ti consigliamo di %{action} subito e di abilitare l'autenticazione a due fattori per mantenere il tuo account al sicuro.
       subject: C'è stato un accesso al tuo account da un nuovo indirizzo IP
       title: Un nuovo accesso
-    terms_of_service_changed:
-      agreement: Continuando a usare %{domain}, accetti questi termini. Se non sei d'accordo con i termini aggiornati, puoi terminare il tuo accordo con %{domain} in qualsiasi momento eliminando il tuo account.
-      changelog: 'Ecco, in sintesi, cosa significa per te questo aggiornamento:'
-      description: 'Stai ricevendo questa e-mail perché stiamo apportando alcune modifiche ai nostri termini di servizio su %{domain}. Questi aggiornamenti entreranno in vigore il %{date}. Ti invitiamo a leggere i termini aggiornati per intero qui:'
-      description_html: Stai ricevendo questa e-mail perché stiamo apportando alcune modifiche ai nostri termini di servizio su %{domain}. Questi aggiornamenti entreranno in vigore il <strong>%{date}</strong>. Ti invitiamo a leggere i <a href="%{path}" target="_blank">termini aggiornati per intero qui</a>.
-      sign_off: Il team di %{domain}
-      subject: Aggiornamenti ai nostri termini di servizio
-      subtitle: I termini di servizio di %{domain} stanno cambiando
-      title: Aggiornamento importante
     warning:
       appeal: Presenta un appello
       appeal_description: Se credi che si tratti di un errore, puoi presentare un appello allo staff di %{instance}.
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 73de14abeb..dc7612d404 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -190,7 +190,6 @@ ja:
         create_domain_block: ドメインブロックを作成
         create_email_domain_block: メールドメインブロックを作成
         create_ip_block: IPルールを作成
-        create_relay: リレーの追加
         create_unavailable_domain: 配送できないドメインを作成
         create_user_role: ロールを作成
         demote_user: ユーザーを降格
@@ -202,24 +201,20 @@ ja:
         destroy_email_domain_block: メールドメインブロックを削除
         destroy_instance: ドメインをブロックする
         destroy_ip_block: IPルールを削除
-        destroy_relay: リレーの削除
         destroy_status: 投稿を削除
         destroy_unavailable_domain: 配送できないドメインを削除
         destroy_user_role: ロールを削除
         disable_2fa_user: 二要素認証を無効化
         disable_custom_emoji: カスタム絵文字を無効化
-        disable_relay: リレーの無効化
         disable_sign_in_token_auth_user: ユーザのメールトークン認証を無効化
         disable_user: ユーザーを無効化
         enable_custom_emoji: カスタム絵文字を有効化
-        enable_relay: リレーの有効化
         enable_sign_in_token_auth_user: ユーザのメールトークン認証を有効化
         enable_user: ユーザーを有効化
         force_cw_status: 投稿に警告文を追加
         force_sensitive_status: 投稿を閲覧注意に変更
         memorialize_account: 追悼アカウント化
         promote_user: ユーザーを昇格
-        publish_terms_of_service: サービス利用規約を公開
         reject_appeal: 抗議を却下
         reject_remote_account: リモートアカウントを拒否
         reject_user: ユーザーを拒否
@@ -259,7 +254,6 @@ ja:
         create_domain_block_html: "%{name}さんがドメイン %{target}をブロックしました"
         create_email_domain_block_html: "%{name} さんがメールドメイン %{target} をブロックしました"
         create_ip_block_html: "%{name}さんがIP %{target}のルールを作成しました"
-        create_relay_html: "%{name} さんがリレー %{target} を追加しました"
         create_unavailable_domain_html: "%{name}がドメイン %{target}への配送を停止しました"
         create_user_role_html: "%{name}さんがロール『%{target}』を作成しました"
         demote_user_html: "%{name}さんが%{target}さんを降格しました"
@@ -271,24 +265,20 @@ ja:
         destroy_email_domain_block_html: "%{name} さんがメールドメイン %{target} のブロックを外しました"
         destroy_instance_html: "%{name}さんがドメイン %{target}をブロックしました"
         destroy_ip_block_html: "%{name}さんが IP %{target}のルールを削除しました"
-        destroy_relay_html: "%{name} さんがリレー %{target} を削除しました"
         destroy_status_html: "%{name}さんが%{target}さんの投稿を削除しました"
         destroy_unavailable_domain_html: "%{name}がドメイン %{target}への配送を再開しました"
         destroy_user_role_html: "%{name}さんがロール『%{target}』を削除しました"
         disable_2fa_user_html: "%{name}さんが%{target}さんの二要素認証を無効化しました"
         disable_custom_emoji_html: "%{name}さんがカスタム絵文字 %{target}を無効化しました"
-        disable_relay_html: "%{name} さんがリレー %{target} を無効にしました"
         disable_sign_in_token_auth_user_html: "%{name} が %{target} のメールトークン認証を無効化しました"
         disable_user_html: "%{name}さんが%{target}さんのログインを無効化しました"
         enable_custom_emoji_html: "%{name}さんがカスタム絵文字 %{target}を有効化しました"
-        enable_relay_html: "%{name} さんがリレー %{target} を有効にしました"
         enable_sign_in_token_auth_user_html: "%{name} が %{target} のメールトークン認証を有効化しました"
         enable_user_html: "%{name}さんが%{target}さんのログインを有効化しました"
         force_cw_status_html: "%{name}さんが%{target}さんの投稿を強制的にCWにしました"
         force_sensitive_status_html: "%{name}さんが%{target}さんの投稿を強制的に閲覧注意にしました"
         memorialize_account_html: "%{name}さんが%{target}さんを追悼アカウントページに登録しました"
         promote_user_html: "%{name}さんが%{target}さんを昇格しました"
-        publish_terms_of_service_html: "%{name} がサービス利用規約の更新を公開しました"
         reject_appeal_html: "%{name}さんが%{target}からの抗議を却下しました"
         reject_remote_account_html: "%{name}さんが%{target}さんの参加を却下しました"
         reject_user_html: "%{name}さんが%{target}さんからの登録を拒否しました"
@@ -517,17 +507,6 @@ ja:
       new:
         title: ドメインブロックをインポート
       no_file: ファイルが選択されていません
-    fasp:
-      debug:
-        callbacks:
-          delete: 削除
-          ip: IPアドレス
-      providers:
-        delete: 削除
-        name: 名前
-        providers: プロバイダ
-        public_key_fingerprint: 公開キーのフィンガープリント
-        save: 保存
     follow_recommendations:
       description_html: "<strong>おすすめフォローは、新規ユーザーが興味のあるコンテンツをすばやく見つけるのに役立ちます。</strong>ユーザーが他のユーザーとの交流を十分にしていない場合、パーソナライズされたおすすめフォローを生成する代わりに、これらのアカウントが表示されます。最近のエンゲージメントが最も高いアカウントと、特定の言語のローカルフォロワー数が最も多いアカウントを組み合わせて、毎日再計算されます。"
       language: 言語
@@ -1082,10 +1061,8 @@ ja:
       back_to_account: アカウントページに戻る
       back_to_report: 通報ページに戻る
       batch:
-        add_to_report: 'レポート #%{id} に追加'
         remove_from_report: 通報から削除
         report: 通報
-      contents: 投稿内容
       deleted: 削除済み
       favourites: お気に入り
       force_cw: 強制CW
@@ -1096,20 +1073,16 @@ ja:
       media:
         title: メディア
       metadata: メタデータ
-      no_history: この投稿は編集されていません
       no_status_selected: 何も選択されていないため、変更されていません
       open: 投稿を開く
       original_status: オリジナルの投稿
       reblogs: ブースト
-      remove: 削除
+      remove: 投稿を削除
       remove_history: 編集履歴を削除
       remove_media: メディアを削除
-      replied_to_html: "%{acct_link}さんへの返信"
       status_changed: 投稿を変更しました
-      status_title: "@%{name} の投稿"
-      title: 投稿一覧 - @%{name}
+      title: 投稿一覧
       trending: トレンド
-      view_publicly: 元の投稿を開く
       visibility: 公開範囲
       with_media: メディアあり
     strikes:
@@ -1186,34 +1159,6 @@ ja:
       search: 検索
       title: ハッシュタグ
       updated_msg: ハッシュタグ設定が更新されました
-    terms_of_service:
-      back: 利用規約に戻る
-      changelog: 変更箇所
-      create: 自分のものを使う
-      current: 現在
-      draft: 下書き
-      generate: テンプレートを使用
-      generates:
-        action: 作成
-        chance_to_review_html: "<strong>生成された利用規約は自動的には公開されません。</strong>結果を確認する機会があります。手続きに必要な詳細を記入してください。"
-        explanation_html: 提供された利用規約のテンプレートは情報提供のみを目的としており、いかなる主題に関しても法的助言と見なされるべきではありません。ご自身の状況や具体的な法的質問については、必ずご自身の弁護士に相談してください。
-        title: 利用規約の設定
-      history: 履歴
-      live: 公開中
-      no_history: 利用規約の変更はまだ記録されていません。
-      no_terms_of_service_html: 現在、利用規約が設定されていません。利用規約は、明確さを提供し、ユーザーとのトラブルにおける潜在的な責任からあなたを保護するためのものです。
-      notified_on_html: "%{date}に通知されたユーザー"
-      notify_users: ユーザに通知
-      preview:
-        explanation_html: メールは、%{date} より前に登録した<strong>%{display_count}人のユーザー</strong>に送信されます。以下のテキストがメールに含まれます:
-        send_preview: "%{email} にプレビューを送信"
-        send_to_all:
-          other: "%{display_count}件のメールを送信"
-        title: サービス利用規約の通知をプレビュー
-      publish: 公開
-      published_on_html: "%{date} に公開"
-      save_draft: 下書きを保存
-      title: サービス利用規約
     title: 管理
     trends:
       allow: 許可
@@ -1363,11 +1308,17 @@ ja:
     hint_html: 他のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。エイリアス自体は<strong>無害で、取り消す</strong>ことができます。<strong>引っ越しは以前のアカウント側から開始する必要があります</strong>。
     remove: エイリアスを削除
   antennas:
+    contexts:
+      account: アカウント
+      domain: ドメイン
+      keyword: キーワード
+      tag: ハッシュタグ
+    edit:
+      available: 有効
+      description: アンテナは、サーバーが認識した全ての公開・ローカル公開投稿のうち、購読を拒否していないすべてのアカウントからの投稿が対象です。検出された投稿は、指定したリストに追加されます。
+      title: アンテナを編集
     errors:
-      duplicate_account: 重複アカウント
-      duplicate_domain: 重複ドメイン
-      duplicate_keyword: 重複キーワード
-      duplicate_tag: 重複タグ
+      empty_contexts: 絞り込み条件が1つも指定されていないため無効です(除外条件はカウントされません)
       invalid_list_owner: これはあなたのリストではありません
       limit:
         accounts: 登録できるアカウント数の上限に達しています
@@ -1378,6 +1329,23 @@ ja:
       over_ltl_limit: 所持できるLTLモード付きアンテナ数 (ホーム/リストそれぞれにつき%{limit}) を超えています
       over_stl_limit: 所持できるSTLモード付きアンテナ数 (ホーム/リストそれぞれにつき%{limit}) を超えています
       too_short_keyword: キーワードが短すぎます
+    index:
+      accounts:
+        other: "%{count}件のアカウント"
+      contexts: "%{contexts}のアンテナ"
+      delete: 削除
+      disabled: 無効
+      domains:
+        other: "%{count}件のドメイン"
+      empty: アンテナはありません。
+      expires_in: "%{distance}で期限切れ"
+      expires_on: 有効期限 %{date}
+      keywords:
+        other: "%{count}件のキーワード"
+      stl: STLモードが適用されます。購読拒否設定は無視されます。
+      tags:
+        other: "%{count}件のタグ"
+      title: アンテナ
   appearance:
     advanced_web_interface: 上級者向けUI
     advanced_web_interface_hint: ディスプレイを幅いっぱいまで活用したい場合、上級者向け UI をおすすめします。ホーム、通知、連合タイムライン、更にはリストやハッシュタグなど、様々な異なるカラムから望む限りの情報を一度に受け取れるような設定が可能になります。
@@ -1445,6 +1413,7 @@ ja:
     migrate_account: 別のアカウントに引っ越す
     migrate_account_html: 引っ越し先を明記したい場合は<a href="%{path}">こちら</a>で設定できます。
     or_log_in_with: または次のサービスでログイン
+    privacy_policy_agreement_html: <a href="%{privacy_policy_path}" target="_blank">プライバシーポリシー</a>を読み、同意します
     progress:
       confirm: メールアドレスの確認
       details: ユーザー情報
@@ -1472,7 +1441,7 @@ ja:
     set_new_password: 新しいパスワード
     setup:
       email_below_hint_html: 確認メールが迷惑メールフォルダに振り分けられていないか確認してください。メールアドレスを間違えた場合は、ここでメールアドレスの変更と確認メールの再送ができます。
-      email_settings_hint_html: "%{email} に送信したリンクをクリックして Mastodon の使用を開始します。ここでお待ちしています。"
+      email_settings_hint_html: メールに記載のリンクを開いて %{email} を確認してください。
       link_not_received: 確認メールを受信できない場合は
       new_confirmation_instructions_sent: 確認用のリンクを記載した新しいメールを送信しました
       title: 確認メールを送信しました
@@ -1481,7 +1450,7 @@ ja:
       title: "%{domain}にログイン"
     sign_up:
       manual_review: "%{domain} への登録にはモデレーターによる承認が必要です。審査の参考になるように、簡単な自己紹介や %{domain} に登録したい理由などを記入してください。"
-      preamble: この Mastodon サーバーのアカウントがあれば、fediverse上の他の人のアカウントがどこでホストされているかに関係なく、その人をフォローすることができます。
+      preamble: この Mastodon サーバーのアカウントがあれば、ネットワーク上の他の人のアカウントがどこでホストされているかに関係なく、その人をフォローすることができます。
       title: さあ %{domain} でセットアップしましょう.
     status:
       account_status: アカウントの状態
@@ -1493,8 +1462,6 @@ ja:
       view_strikes: 過去のストライクを表示
     too_fast: フォームの送信が速すぎます。もう一度やり直してください。
     use_security_key: セキュリティキーを使用
-    user_agreement_html: <a href="%{terms_of_service_path}" target="_blank">利用規約</a> および <a href="%{privacy_policy_path}" target="_blank">プライバシーポリシー</a>を読み、同意します。
-    user_privacy_agreement_html: <a href="%{privacy_policy_path}" target="_blank">プライバシーポリシー</a>を読み、同意します
     with_login_options: カスタムCSSを無効化しますか?
   author_attribution:
     example_title: サンプルテキスト
@@ -1696,31 +1663,19 @@ ja:
       overwrite: 上書き
       overwrite_long: 現在のレコードを新しいもので置き換えます
     overwrite_preambles:
-      blocking_html:
-        other: あなたは<strong>%{filename}</strong>から最大<strong>%{count}のアカウント</strong>で<strong>ブロックリストを置き換えようとしています</strong>。
-      bookmarks_html:
-        other: あなたは<strong>%{filename}</strong>から最大<strong>%{count}の投稿</strong>で<strong>ブックマークを置き換えようとしています</strong>。
-      domain_blocking_html:
-        other: あなたは<strong>%{filename}</strong>から最大<strong>%{count}のドメイン</strong>で<strong>ドメインブロックリストを置き換えようとしています</strong>。
-      following_html:
-        other: あなたは<strong>%{filename}</strong>から最大<strong>%{count}のアカウント</strong>を<strong>フォローし</strong>、<strong>他のすべてのユーザーのフォローを解除します</strong>。
-      lists_html:
-        other: あなたは<strong>%{filename}</strong>の内容で<strong>リストを置き換えようとしています</strong>。最大<strong>%{count}のアカウント</strong>が新しいリストに追加されます。
-      muting_html:
-        other: あなたは<strong>%{filename}</strong>から最大<strong>%{count}のアカウント</strong>で<strong>ミュートアカウントのリストを置き換えようとしています</strong>。
+      blocking_html: "<strong>%{filename}</strong>の<strong>%{total_items}個のアカウント</strong>で<strong>ブロックしたアカウントリストを置き換えます</strong>。"
+      bookmarks_html: "<strong>%{filename}</strong>の<strong>%{total_items}件の投稿</strong>で<strong>ブックマークの一覧を置き換えます</strong>。"
+      domain_blocking_html: "<strong>%{filename}</strong>の<strong>%{total_items}個のドメイン</strong>で<strong>非表示にしたドメインリストを置き換えます</strong>。"
+      following_html: "<strong>%{filename}</strong>の<strong>%{total_items}個のアカウント</strong>を<strong>フォローします</strong>。また、<strong>この中に含まれていないアカウントのフォローを解除します</strong>。"
+      lists_html: "<strong>作成済みのリスト</strong>を<strong>%{filename}の内容で置き換えます</strong>。<strong>%{total_items}個のアカウント</strong>が新しいリストに追加されます。"
+      muting_html: "<strong>%{filename}</strong>の<strong>%{total_items}個のアカウント</strong>で<strong>ミュートしたアカウントリストを置き換えます</strong>。"
     preambles:
-      blocking_html:
-        other: あなたは<strong>%{filename}</strong>から最大<strong>%{count}のアカウント</strong>を<strong>ブロックしようとしています</strong>。
-      bookmarks_html:
-        other: あなたは<strong>%{filename}</strong>から最大<strong>%{count}の投稿</strong>を<strong>ブックマークに追加しようとしています</strong>。
-      domain_blocking_html:
-        other: あなたは<strong>%{filename}</strong>から最大<strong>%{count}のドメイン</strong>を<strong>ブロックしようとしています</strong>。
-      following_html:
-        other: あなたは<strong>%{filename}</strong>から最大<strong>%{count}のアカウント</strong>を<strong>フォローしようとしています</strong>。
-      lists_html:
-        other: あなたは<strong>%{filename}</strong>から最大<strong>%{count}のアカウント</strong>を<strong>リストに追加しようとしています</strong>。追加するリストがない場合は新しいリストが作成されます。
-      muting_html:
-        other: あなたは<strong>%{filename}</strong>から最大<strong>%{count}のアカウント</strong>を<strong>ミュートしようとしています</strong>。
+      blocking_html: "<strong>%{filename}</strong>の<strong>%{total_items}個のアカウント</strong>を<strong>ブロックします</strong>。"
+      bookmarks_html: "<strong>%{filename}</strong>の<strong>%{total_items}件の投稿</strong>を<strong>ブックマークに追加します</strong>。"
+      domain_blocking_html: "<strong>%{filename}</strong>の<strong>%{total_items}個のドメイン</strong>を<strong>非表示にします</strong>。"
+      following_html: "<strong>%{filename}</strong>の<strong>%{total_items}個のアカウント</strong>を<strong>フォローします</strong>。"
+      lists_html: "<strong>%{filename}</strong>の<strong>%{total_items}個のアカウント</strong>を<strong>リストに追加します</strong>。追加先のリストがない場合は新しく作成されます。"
+      muting_html: "<strong>%{filename}</strong>の<strong>%{total_items}個のアカウント</strong>を<strong>ミュートします</strong>。"
     preface: 他のサーバーでエクスポートされたファイルから、フォロー/ブロックした情報をこのサーバー上のアカウントにインポートできます。
     recent_imports: 最近のインポート
     states:
@@ -2003,7 +1958,7 @@ ja:
   scheduled_statuses:
     over_daily_limit: その日予約できる投稿数 %{limit}を超えています
     over_total_limit: 予約できる投稿数 %{limit}を超えています
-    too_soon: 日付は未来でなければなりません
+    too_soon: より先の時間を指定してください
   self_destruct:
     lead_html: 残念ながら、<strong>%{domain}</strong> は恒久的に閉鎖されます。ここにお持ちだったアカウントを今後使うことはできませんが、これまでのデータのバックアップを要求することはまだ可能です。
     title: このサーバーは閉鎖されます
@@ -2197,8 +2152,6 @@ ja:
       too_late: このストライクに抗議するには遅すぎます
   tags:
     does_not_match_previous_name: 以前の名前と一致しません
-  terms_of_service:
-    title: サービス利用規約
   themes:
     contrast: Mastodon (ハイコントラスト)
     default: Mastodon (ダーク)
@@ -2260,13 +2213,6 @@ ja:
       further_actions_html: あなたがログインしていない場合は、すぐに%{action}し、アカウントを安全に保つために二要素認証を有効にすることをお勧めします。
       subject: 新しいIPアドレスからのアクセスがありました
       title: 新しいサインイン
-    terms_of_service_changed:
-      agreement: "%{domain} を引き続き使用することで、これらの条件に同意したことになります。更新された条件に同意しない場合は、アカウントを削除することでいつでも %{domain} との契約を終了することができます。"
-      changelog: 一目で分かる、この更新があなたにとって意味することは次の通りです:
-      sign_off: "%{domain} チーム"
-      subject: 利用規約の更新
-      subtitle: "%{domain} の利用規約が変更されています"
-      title: 重要な更新
     warning:
       appeal: 抗議を送信
       appeal_description: これが間違いだと思われる場合は、%{instance}のスタッフに申し立てすることができます。
diff --git a/config/locales/ka.yml b/config/locales/ka.yml
index f3178be8d3..2a0365fbf5 100644
--- a/config/locales/ka.yml
+++ b/config/locales/ka.yml
@@ -190,6 +190,7 @@ ka:
       media:
         title: მედია
       no_status_selected: სატუსები არ შეცვლილა, რადგან არცერთი არ მონიშნულა
+      title: ანგარიშის სტატუსები
       with_media: მედიით
     title: ადმინისტრაცია
   admin_mailer:
diff --git a/config/locales/kab.yml b/config/locales/kab.yml
index 85a88be7bb..e1abf2ac00 100644
--- a/config/locales/kab.yml
+++ b/config/locales/kab.yml
@@ -268,15 +268,6 @@ kab:
       no_file: Ula d yiwen ufaylu ma yettwafran
     export_domain_blocks:
       no_file: Ulac afaylu yettwafernen
-    fasp:
-      debug:
-        callbacks:
-          delete: Kkes
-          ip: Tansa IP
-      providers:
-        delete: Kkes
-        save: Sekles
-      title: FASP
     follow_recommendations:
       language: I tutlayt
       status: Addad
@@ -387,7 +378,6 @@ kab:
       everyone: Tisirag timezwura
       privileges:
         administrator: Anedbal
-        view_dashboard: Timẓriwt n tfelwit
     rules:
       add_new: Rnu alugen
       delete: Kkes
@@ -401,7 +391,6 @@ kab:
         title: Udem
       discovery:
         profile_directory: Akaram n imaɣnuten
-        title: Asnirem
         trends: Ayen mucaɛen
       domain_blocks:
         all: I medden akk
@@ -424,13 +413,13 @@ kab:
       account: Ameskar
       application: Asnas
       back_to_account: Tuɣalin ɣer usebter n umiḍan
-      contents: Agbur
       deleted: Yettwakkes
       favourites: Imenyafen
       language: Tutlayt
       media:
         title: Amidya
       open: Ldi tasuffeɣt
+      title: Tisuffaɣ n umiḍan
       trending: Ayen mucaɛen
       visibility: Abani
       with_media: S umidya
@@ -442,18 +431,8 @@ kab:
       software_version_patch_check:
         action: Wali ileqqman yellan
     tags:
-      name: Isem
-      newest: Amaynut
-      oldest: Aqbur
       search: Anadi
       title: Ihacṭagen
-    terms_of_service:
-      changelog: Amaynut
-      draft: Arewway
-      history: Amazray
-      publish: Asuffeɣ
-      save_draft: Sekles arewway
-      title: Tiwtilin n useqdec
     title: Tadbelt
     trends:
       allow: Sireg
@@ -517,6 +496,7 @@ kab:
     logout: Ffeɣ
     migrate_account: Gujj ɣer umiḍan nniḍen
     or_log_in_with: Neɣ eqqen s
+    privacy_policy_agreement_html: Ɣriɣ yerna qebleɣ <a href="%{privacy_policy_path}" target="_blank">tasertit n tbaḍnit</a>
     progress:
       confirm: Sentem imayl
       details: Isalli-inek
@@ -539,11 +519,13 @@ kab:
     security: Taɣellist
     set_new_password: Egr-d awal uffir amaynut
     setup:
+      email_settings_hint_html: Sit ɣef useɣwen i ak-d-nceyyeε akken ad tesfeqdeḍ %{email}. Ad nerǧu da kan.
       link_not_received: Ur k-id-iṣaḥ ara wassaɣ ?
     sign_in:
       preamble_html: Kcem ar <strong>%{domain}</strong> s inekcam-inek n tuqqna. Ma yella yezga-d umiḍan-ik deg uqeddac-nniḍen, ur tezmireḍ ara ad tkecmeḍ sya.
       title: Akeččum ɣer %{domain}
     sign_up:
+      preamble: S umiḍan deg uqeddac-a n Mastodon, ad tizmireḍ ad tḍefreḍ win i ak·kem-yehwan deg uẓeṭṭa, anida yebɣu yili umiḍan-nnsen.
       title: Iyya ad d-nessewjed tiɣawsiwin i %{domain}.
     status:
       account_status: Addad n umiḍan
@@ -615,7 +597,7 @@ kab:
   filters:
     contexts:
       account: Imeɣna
-      notifications: Ilɣa
+      notifications: Alɣuten
       thread: Idiwenniyen
     edit:
       add_keyword: Rnu awal tasarut
@@ -719,7 +701,6 @@ kab:
         units:
           billion: AṬ
           million: A
-          thousand: GM
           trillion: Am
   otp_authentication:
     enable: Rmed
@@ -810,7 +791,7 @@ kab:
     import: Kter
     import_and_export: Taktert d usifeḍ
     migrate: Tunigin n umiḍan
-    notifications: Ilɣa s imayl
+    notifications: Alɣuten s imayl
     preferences: Imenyafen
     profile: Ameɣnu
     relationships: Imeḍfaṛen akked wid i teṭṭafaṛeḍ
@@ -850,8 +831,6 @@ kab:
       '7889238': 3 n wayyuren
   stream_entries:
     sensitive_content: Agbur amḥulfu
-  terms_of_service:
-    title: Tiwtilin n useqdec
   themes:
     contrast: Maṣṭudun (agnil awriran)
     default: Maṣṭudun (Aberkan)
@@ -874,12 +853,9 @@ kab:
   user_mailer:
     appeal_approved:
       action: Iɣewwaṛen n umiḍan
-    terms_of_service_changed:
-      sign_off: Agraw n %{domain}
     warning:
       categories:
         spam: Aspam
-      reason: 'Taɣẓint:'
       title:
         disable: Amiḍan i igersen
         none: Ɣur-wat
diff --git a/config/locales/kk.yml b/config/locales/kk.yml
index a2123107ed..537104ba51 100644
--- a/config/locales/kk.yml
+++ b/config/locales/kk.yml
@@ -285,6 +285,7 @@ kk:
       media:
         title: Медиa
       no_status_selected: Бірде-бір статус өзгерген жоқ, себебі ештеңе таңдалмады
+      title: Аккаунт статустары
       with_media: Медиамен
     tags:
       review: Статусты көрсету
@@ -575,6 +576,7 @@ kk:
   scheduled_statuses:
     over_daily_limit: Сіз бір күндік %{limit} жазба лимитін тауыстыңыз
     over_total_limit: Сіз %{limit} жазба лимитін тауыстыңыз
+    too_soon: Жоспарланған күн болашақта болуы керек
   sessions:
     activity: Соңғы әрекеттер
     browser: Браузер
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index 1ce5ef2da9..ec941a54a8 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -184,7 +184,6 @@ ko:
         create_domain_block: 도메인 차단 추가
         create_email_domain_block: 이메일 도메인 차단 생성
         create_ip_block: IP 규칙 만들기
-        create_relay: 릴레이 생성
         create_unavailable_domain: 사용 불가능한 도메인 생성
         create_user_role: 역할 생성
         demote_user: 사용자 강등
@@ -194,24 +193,20 @@ ko:
         destroy_domain_allow: 도메인 허용 삭제
         destroy_domain_block: 도메인 차단 삭제
         destroy_email_domain_block: 이메일 도메인 차단 삭제
-        destroy_instance: 도메인 퍼지
+        destroy_instance: 도메인 제거
         destroy_ip_block: IP 규칙 삭제
-        destroy_relay: 릴레이 삭제
         destroy_status: 게시물 삭제
         destroy_unavailable_domain: 사용 불가능한 도메인 제거
         destroy_user_role: 역할 삭제
         disable_2fa_user: 2단계 인증 비활성화
         disable_custom_emoji: 커스텀 에모지 비활성화
-        disable_relay: 릴레이 비활성화
         disable_sign_in_token_auth_user: 사용자의 이메일 토큰 인증 비활성화
         disable_user: 사용자 비활성화
         enable_custom_emoji: 커스텀 에모지 활성화
-        enable_relay: 릴레이 활성화
         enable_sign_in_token_auth_user: 사용자의 이메일 토큰 인증 활성화
         enable_user: 사용자 활성화
         memorialize_account: 고인의 계정으로 전환
         promote_user: 사용자 승급
-        publish_terms_of_service: 이용 약관 게시
         reject_appeal: 이의 제기 거절
         reject_user: 사용자 거부
         remove_avatar_user: 아바타 지우기
@@ -249,7 +244,6 @@ ko:
         create_domain_block_html: "%{name} 님이 도메인 %{target}를 차단했습니다"
         create_email_domain_block_html: "%{name} 님이 이메일 도메인 %{target}를 차단했습니다"
         create_ip_block_html: "%{name} 님이 IP 규칙 %{target}을 만들었습니다"
-        create_relay_html: "%{name} 님이 릴레이 %{target}를 생성했습니다"
         create_unavailable_domain_html: "%{name} 님이 도메인 %{target}에 대한 전달을 중지했습니다"
         create_user_role_html: "%{name} 님이 %{target} 역할을 생성했습니다"
         demote_user_html: "%{name} 님이 사용자 %{target} 님을 강등했습니다"
@@ -261,22 +255,18 @@ ko:
         destroy_email_domain_block_html: "%{name} 님이 이메일 도메인 %{target}을 차단 해제하였습니다"
         destroy_instance_html: "%{name} 님이 도메인 %{target}를 제거했습니다"
         destroy_ip_block_html: "%{name} 님이 IP 규칙 %{target}을 삭제하였습니다"
-        destroy_relay_html: "%{name} 님이 릴레이 %{target}를 삭제했습니다"
         destroy_status_html: "%{name} 님이 %{target} 님의 게시물을 삭제했습니다"
         destroy_unavailable_domain_html: "%{name} 님이 도메인 %{target}에 대한 전달을 재개"
         destroy_user_role_html: "%{name} 님이 %{target} 역할을 삭제했습니다"
         disable_2fa_user_html: "%{name} 님이 사용자 %{target} 님의 2단계 인증을 비활성화 했습니다"
         disable_custom_emoji_html: "%{name} 님이 에모지 %{target}를 비활성화했습니다"
-        disable_relay_html: "%{name} 님이 릴레이 %{target}를 비활성화했습니다"
         disable_sign_in_token_auth_user_html: "%{name} 님이 %{target} 님의 이메일 토큰 인증을 비활성화했습니다"
         disable_user_html: "%{name} 님이 사용자 %{target}의 로그인을 비활성화했습니다"
         enable_custom_emoji_html: "%{name} 님이 에모지 %{target}를 활성화했습니다"
-        enable_relay_html: "%{name} 님이 릴레이 %{target}를 활성화했습니다"
         enable_sign_in_token_auth_user_html: "%{name} 님이 %{target} 님의 이메일 토큰 인증을 활성화했습니다"
         enable_user_html: "%{name} 님이 사용자 %{target}의 로그인을 활성화했습니다"
         memorialize_account_html: "%{name} 님이 %{target}의 계정을 고인의 계정 페이지로 전환했습니다"
         promote_user_html: "%{name} 님이 사용자 %{target}를 승급시켰습니다"
-        publish_terms_of_service_html: "%{name} 님이 이용 약관을 업데이트했습니다"
         reject_appeal_html: "%{name} 님이 %{target}의 중재 결정에 대한 이의 제기를 거절했습니다"
         reject_user_html: "%{name} 님이 %{target} 님의 가입을 거부했습니다"
         remove_avatar_user_html: "%{name} 님이 %{target} 님의 아바타를 지웠습니다"
@@ -306,7 +296,6 @@ ko:
       title: 감사 로그
       unavailable_instance: "(도메인네임 사용불가)"
     announcements:
-      back: 공지사항으로 돌아가기
       destroyed_msg: 공지가 성공적으로 삭제되었습니다!
       edit:
         title: 공지사항 편집
@@ -315,10 +304,6 @@ ko:
       new:
         create: 공지사항 생성
         title: 새 공지사항
-      preview:
-        disclaimer: 사용자들은 수신설정을 끌 수 없기 때문에 이메일 알림은 개인정보 유출이나 서버 종료와 같은 중요한 공지사항에만 사용해야 합니다.
-        explanation_html: "<strong>%{display_count} 명의 사용자</strong>에게 이메일이 발송됩니다. 다음 내용이 이메일에 포함됩니다:"
-        title: 공지사항 알림 미리보기
       publish: 게시
       published_msg: 공지사항이 성공적으로 발행되었습니다!
       scheduled_for: "%{time}에 예약됨"
@@ -474,36 +459,6 @@ ko:
       new:
         title: 도메인 차단 불러오기
       no_file: 선택된 파일이 없습니다
-    fasp:
-      debug:
-        callbacks:
-          created_at: 생성일
-          delete: 삭제
-          ip: IP 주소
-          request_body: 요청 바디
-          title: 디버그 콜백
-      providers:
-        active: 활성
-        base_url: Base URL
-        callback: 콜백
-        delete: 삭제
-        edit: 제공자 편집
-        finish_registration: 등록 완료
-        name: 이름
-        providers: 제공자
-        public_key_fingerprint: 공개키 핑거프린트
-        registration_requested: 가입 요청됨
-        registrations:
-          confirm: 확인
-          description: FASP로부터 등록을 받았습니다. 본인이 시작하지 않았다면 거부하세요. 본인이 시작했다면 등록을 확인하기 전에 이름과 키 핑거프린트를 주의 깊게 비교하세요.
-          reject: 거부
-          title: FASP 가입 확인
-        save: 저장
-        select_capabilities: 권한 선택
-        sign_in: 가입
-        status: 상태
-        title: 연합우주 보조 서비스 제공자
-      title: FASP
     follow_recommendations:
       description_html: "<strong>팔로우 추천은 새 사용자들이 관심 가는 콘텐트를 빠르게 찾을 수 있도록 도와줍니다</strong>. 사용자가 개인화 된 팔로우 추천이 만들어지기 위한 충분한 상호작용을 하지 않은 경우, 이 계정들이 대신 추천 됩니다. 이들은 해당 언어에 대해 많은 관심을 갖거나 많은 로컬 팔로워를 가지고 있는 계정들을 섞어서 날마다 다시 계산 됩니다."
       language: 언어 필터
@@ -571,7 +526,7 @@ ko:
         title: 중재
       private_comment: 비공개 주석
       public_comment: 공개 주석
-      purge: 퍼지
+      purge: 제거
       purge_description_html: 이 도메인이 영구적으로 오프라인 상태라고 생각되면, 스토리지에서 이 도메인의 모든 계정 레코드와 관련 데이터를 삭제할 수 있습니다. 이 작업은 시간이 좀 걸릴 수 있습니다.
       title: 연합
       total_blocked_by_us: 우리에게 차단 됨
@@ -851,10 +806,8 @@ ko:
       back_to_account: 계정으로 돌아가기
       back_to_report: 신고 페이지로 돌아가기
       batch:
-        add_to_report: '신고 #%{id}에 추가'
         remove_from_report: 신고에서 제거
         report: 신고
-      contents: 내용
       deleted: 삭제됨
       favourites: 좋아요
       history: 버전 이력
@@ -863,17 +816,13 @@ ko:
       media:
         title: 미디어
       metadata: 메타데이터
-      no_history: 이 게시물은 수정되지 않았습니다
       no_status_selected: 아무 것도 선택 되지 않아 어떤 게시물도 바뀌지 않았습니다
       open: 게시물 열기
       original_status: 원본 게시물
       reblogs: 리블로그
-      replied_to_html: "%{acct_link} 님에게 답장"
       status_changed: 게시물 변경됨
-      status_title: "@%{name} 님의 게시물"
-      title: 계정 게시물 - @%{name}
+      title: 계정 게시물
       trending: 유행 중
-      view_publicly: 공개시점으로 보기
       visibility: 공개 설정
       with_media: 미디어 있음
     strikes:
@@ -950,35 +899,6 @@ ko:
       search: 검색
       title: 해시태그
       updated_msg: 해시태그 설정이 성공적으로 갱신되었습니다
-    terms_of_service:
-      back: 이용약관으로 돌아가기
-      changelog: 변경사항
-      create: 직접 사용
-      current: 현재
-      draft: 초안
-      generate: 템플릿 사용
-      generates:
-        action: 생성
-        chance_to_review_html: "<strong>생성된 이용 약관은 자동으로 게시되지 않을 것입니다.</strong> 결과를 확인할 기회가 있습니다. 진행하려면 필요한 정보들을 입력하세요."
-        explanation_html: 제공되는 이용약관 틀은 정보 제공만을 목적으로 하며 법률 조언으로 해석하면 안 됩니다. 귀하의 상황에 맞는 법률 자문을 받아주시기 바랍니다.
-        title: 이용 약관 설정
-      going_live_on_html: "%{date} 시행중"
-      history: 역사
-      live: 활성
-      no_history: 기록된 이용약관 변경이 아직 없습니다.
-      no_terms_of_service_html: 현재 설정된 이용약관이 없습니다. 이용약관은 명확성을 제공하고 귀하와 사용자간 분쟁에서 발생할 수 있는 책임으로부터 보호할 수 있습니다.
-      notified_on_html: 사용자들이 %{date}에 알림을 받았습니다
-      notify_users: 사용자들에게 알리기
-      preview:
-        explanation_html: "%{date} 이전에 가입한 <strong>%{display_count} 명의 사용자</strong>에게 이메일이 발송됩니다. 다음 내용이 이메일에 포함됩니다:"
-        send_preview: 미리보기를 %{email}에 보내기
-        send_to_all:
-          other: "%{display_count} 건의 메일 발송"
-        title: 이용 약관 알림 미리보기
-      publish: 게시
-      published_on_html: "%{date}에 게시됨"
-      save_draft: 초안 저장
-      title: 이용 약관
     title: 관리
     trends:
       allow: 허용
@@ -1182,6 +1102,7 @@ ko:
     migrate_account: 계정 옮기기
     migrate_account_html: 이 계정을 다른 계정으로 리디렉션 하길 원하는 경우 <a href="%{path}">여기</a>에서 설정할 수 있습니다.
     or_log_in_with: 다른 방법으로 로그인 하려면
+    privacy_policy_agreement_html: <a href="%{privacy_policy_path}" target="_blank">개인정보처리방침</a>을 읽고 동의합니다
     progress:
       confirm: 이메일 확인
       details: 세부사항
@@ -1206,7 +1127,7 @@ ko:
     set_new_password: 새 암호 설정
     setup:
       email_below_hint_html: 스팸 폴더를 체크해보거나, 새로 요청할 수 있습니다. 이메일을 잘못 입력한 경우 수정할 수 있습니다.
-      email_settings_hint_html: "%{email}로 보낸 링크를 클릭해 마스토돈을 시작하세요. 기다리고 있겠습니다."
+      email_settings_hint_html: "%{email}을 인증하기 위해 우리가 보낸 링크를 누르세요. 여기서 기다리겠습니다."
       link_not_received: 링크를 못 받으셨나요?
       new_confirmation_instructions_sent: 확인 링크가 담긴 이메일이 몇 분 안에 도착할것입니다!
       title: 수신함 확인하기
@@ -1215,7 +1136,7 @@ ko:
       title: "%{domain}에 로그인"
     sign_up:
       manual_review: "%{domain} 가입은 중재자들의 심사를 거쳐 진행됩니다. 가입 절차를 원활하게 하기 위해, 간단한 자기소개와 왜 %{domain}에 계정을 만들려고 하는지 적어주세요."
-      preamble: 이 마스토돈 서버의 계정을 통해, 연합우주에 속한 다른 사람들을, 그들이 어떤 서버에 있든 팔로우 할 수 있습니다.
+      preamble: 이 마스토돈 서버의 계정을 통해, 네트워크에 속한 다른 사람들을, 그들이 어떤 서버에 있든 팔로우 할 수 있습니다.
       title: "%{domain}에 가입하기 위한 정보들을 입력하세요."
     status:
       account_status: 계정 상태
@@ -1227,8 +1148,6 @@ ko:
       view_strikes: 내 계정에 대한 과거 중재 기록 보기
     too_fast: 너무 빠르게 양식이 제출되었습니다, 다시 시도하세요.
     use_security_key: 보안 키 사용
-    user_agreement_html: <a href="%{terms_of_service_path}" target="_blank">이용 약관</a>과 <a href="%{privacy_policy_path}" target="_blank">개인정보처리방침</a>을 읽었고 동의합니다
-    user_privacy_agreement_html: <a href="%{privacy_policy_path}" target="_blank">개인정보처리방침</a>을 읽었고 동의합니다
   author_attribution:
     example_title: 예시 텍스트
     hint_html: 마스토돈 밖에서 뉴스나 블로그 글을 쓰시나요? 마스토돈에 공유되었을 때 어떻게 표시될지를 제어하세요.
@@ -1427,31 +1346,19 @@ ko:
       overwrite: 덮어쓰기
       overwrite_long: 기존 것을 모두 지우고 새로 추가
     overwrite_preambles:
-      blocking_html:
-        other: 나의 <strong>차단 목록</strong>을 <strong>%{filename}</strong>에서 가져온 <strong>%{count} 개의 계정</strong>으로 덮어 씌우려고 합니다.
-      bookmarks_html:
-        other: 나의 <strong>북마크</strong>를 <strong>%{filename}</strong>에서 가져온 <strong>%{count} 개의 게시물</strong>로 덮어 씌우려고 합니다.
-      domain_blocking_html:
-        other: 나의 <strong>도메인 차단 목록</strong>을 <strong>%{filename}</strong>에서 가져온 <strong>%{count} 개의 도메인</strong>으로 덮어 씌우려고 합니다.
-      following_html:
-        other: "<strong>%{filename}</strong>에서 가져온 <strong>%{count} 개의 계정</strong>을 <strong>팔로우</strong>하고 <strong>나머지 계정을 팔로우 해제</strong>하려고 합니다."
-      lists_html:
-        other: 나의 <strong>리스트</strong>를 <strong>%{filename}</strong>에서 가져온 <strong>%{count} 개의 계정</strong>으로 덮어 씌우려고 합니다.
-      muting_html:
-        other: 나의 <strong>뮤트한 계정 목록</strong>을 <strong>%{filename}</strong>에서 가져온 <strong>%{count} 개의 계정</strong>으로 덮어 씌우려고 합니다.
+      blocking_html: 나의 <strong>차단 목록</strong>을 <strong>%{filename}</strong>에서 가져온 <strong>%{total_items} 개의 계정</strong>으로 덮어 씌우려고 합니다.
+      bookmarks_html: 나의 <strong>북마크</strong>를 <strong>%{filename}</strong>에서 가져온 <strong>%{total_items} 개의 게시물</strong>로 덮어 씌우려고 합니다.
+      domain_blocking_html: 나의 <strong>도메인 차단 목록</strong>을 <strong>%{filename}</strong>에서 가져온 <strong>%{total_items} 개의 도메인</strong>으로 덮어 씌우려고 합니다.
+      following_html: "<strong>%{filename}</strong>에서 가져온 <strong>%{total_items} 개의 계정</strong>을 <strong>팔로우</strong>하고 <strong>나머지 계정을 팔로우 해제</strong>하려고 합니다."
+      lists_html: 나의 <strong>리스트</strong>를 <strong>%{filename}</strong>에서 가져온 <strong>%{total_items} 개의 계정</strong>으로 덮어 씌우려고 합니다.
+      muting_html: 나의 <strong>뮤트한 계정 목록</strong>을 <strong>%{filename}</strong>에서 가져온 <strong>%{total_items} 개의 계정</strong>으로 덮어 씌우려고 합니다.
     preambles:
-      blocking_html:
-        other: "<strong>%{filename}</strong>에서 가져온 <strong>%{count}개의 계정</strong>을 차단하려고 합니다."
-      bookmarks_html:
-        other: "<strong>%{filename}</strong>에서 가져온 <strong>%{count}개의 게시물</strong>을 북마크에 추가하려고 합니다."
-      domain_blocking_html:
-        other: "<strong>%{filename}</strong>에서 가져온 <strong>%{count}개의 도메인</strong>을 차단하려고 합니다."
-      following_html:
-        other: "<strong>%{filename}</strong>에서 가져온 <strong>%{count}개의 계정</strong>을 팔로우하려고 합니다."
-      lists_html:
-        other: "<strong>%{filename}</strong>에서 가져온 <strong>%{count}개의 계정</strong>을 내 <strong>리스트</strong>에 추가하려고 합니다. 추가할 리스트가 존재하지 않으면 새로 생성될 것입니다."
-      muting_html:
-        other: "<strong>%{filename}</strong>에서 가져온 <strong>%{count}개의 계정</strong>을 뮤트하려고 합니다."
+      blocking_html: "<strong>%{filename}</strong>에서 가져온 <strong>%{total_items}개의 계정</strong>을 차단하려고 합니다."
+      bookmarks_html: "<strong>%{filename}</strong>에서 가져온 <strong>%{total_items}개의 게시물</strong>을 북마크에 추가하려고 합니다."
+      domain_blocking_html: "<strong>%{filename}</strong>에서 가져온 <strong>%{total_items}개의 도메인</strong>을 차단하려고 합니다."
+      following_html: "<strong>%{filename}</strong>에서 가져온 <strong>%{total_items}개의 계정</strong>을 팔로우하려고 합니다."
+      lists_html: "<strong>%{filename}</strong>에서 가져온 <strong>%{total_items}개의 계정</strong>을 내 <strong>리스트</strong>에 추가하려고 합니다. 추가할 리스트가 존재하지 않으면 새로 생성될 것입니다."
+      muting_html: "<strong>%{filename}</strong>에서 가져온 <strong>%{total_items}개의 계정</strong>을 뮤트하려고 합니다."
     preface: 다른 서버에서 내보내기 한 파일에서 팔로우 / 차단 정보를 이 계정으로 불러올 수 있습니다.
     recent_imports: 최근의 가져오기
     states:
@@ -1707,7 +1614,7 @@ ko:
   scheduled_statuses:
     over_daily_limit: 그 날짜에 대한 %{limit}개의 예약 게시물 제한을 초과합니다
     over_total_limit: 예약 게시물 제한 %{limit}을 초과합니다
-    too_soon: 미래의 날짜여야 합니다
+    too_soon: 예약 날짜는 미래여야 합니다
   self_destruct:
     lead_html: 안타깝게도, <strong>%{domain}</strong>은 영구적으로 폐쇄됩니다. 이곳의 계정을 가지고 있었다면, 이제 이용할 수 없지만 백업 데이터는 요청할 수 있습니다.
     title: 이 서버는 폐쇄중입니다
@@ -1866,8 +1773,6 @@ ko:
       too_late: 이의를 제기하기에 너무 늦었습니다
   tags:
     does_not_match_previous_name: 이전 이름과 맞지 않습니다
-  terms_of_service:
-    title: 이용 약관
   themes:
     contrast: 마스토돈 (고대비)
     default: 마스토돈 (어두움)
@@ -1899,10 +1804,6 @@ ko:
     recovery_instructions_html: 휴대전화를 분실한 경우, 아래 복구 코드 중 하나를 사용해 계정에 접근할 수 있습니다. <strong>복구 코드는 안전하게 보관해 주십시오.</strong> 이 코드를 인쇄해 중요한 서류와 함께 보관하는 것도 좋습니다.
     webauthn: 보안 키
   user_mailer:
-    announcement_published:
-      description: "%{domain}의 관리자가 공지사항을 게시했습니다:"
-      subject: 서비스 공지사항
-      title: "%{domain} 서비스 공지사항"
     appeal_approved:
       action: 계정 설정
       explanation: "%{strike_date}에 일어난 중재결정에 대한 소명을 %{appeal_date}에 작성했으며 승낙되었습니다. 당신의 계정은 정상적인 상태로 돌아왔습니다."
@@ -1932,15 +1833,6 @@ ko:
       further_actions_html: 직접 로그인한 것이 아니라면, 지금 바로 %{action}과 2단계 인증을 활성화하여 계정을 안전하게 보호하시길 권해드립니다.
       subject: 계정이 새로운 IP에서 접근됨
       title: 새로운 로그인
-    terms_of_service_changed:
-      agreement: "%{domain}을 계속 사용하는 것으로 약관에 동의하는 것으로 간주합니다. 약관에 동의하지 않는 경우 계정을 삭제함으로써 언제든 동의를 철회할 수 있습니다."
-      changelog: '이번 변경사항의 주요 내용입니다:'
-      description: "%{domain}의 이용 약관이 변경되었기 때문에 발송된 이메일입니다. 이 변경사항은 %{date}부터 효력을 발휘합니다. 변경된 전체 약관을 확인하시길 권합니다:"
-      description_html: '%{domain}의 이용 약관이 변경되었기 때문에 발송된 이메일입니다. 이 변경사항은 <strong>%{date}</strong>부터 효력을 발휘합니다. <a href="%{path}" target="_blank">변경된 전체 약관</a>을 확인하시길 권합니다.'
-      sign_off: "%{domain} 팀"
-      subject: 변경된 이용 약관
-      subtitle: "%{domain}의 이용 약관이 변경됩니다"
-      title: 중요한 알림
     warning:
       appeal: 이의 제출하기
       appeal_description: 이것이 오류라고 생각한다면, %{instance}의 중재자에게 이의신청을 할 수 있습니다.
diff --git a/config/locales/ku.yml b/config/locales/ku.yml
index 06f3e32f20..ddd214441c 100644
--- a/config/locales/ku.yml
+++ b/config/locales/ku.yml
@@ -720,6 +720,7 @@ ku:
       original_status: Şandiyê resen
       reblogs: Ji nû ve nivîsandin
       status_changed: Şandî hate guhertin
+      title: Şandiyên ajimêr
       trending: Rojev
       visibility: Xuyabarî
       with_media: Bi medya yê re
@@ -916,6 +917,7 @@ ku:
     migrate_account: Livandin bo ajimêreke din
     migrate_account_html: Ku tu dixwazî ev ajimêr li ajimêreke cuda beralî bikî, tu dikarî <a href="%{path}">ji vir de saz bike</a>.
     or_log_in_with: An têketinê bike bi riya
+    privacy_policy_agreement_html: Min <a href="%{privacy_policy_path}" target="_blank">Politîka taybetiyê</a> xwend û dipejirînim
     providers:
       cas: CAS
       saml: SAML
@@ -931,6 +933,7 @@ ku:
       preamble_html: Têketinê bike bi riya <strong>%{domain}</strong> xwe. Ku ajimêrê te li ser rajekareke cuda hatiye pêşkêşkirin, tu yê nikaribû têketinê bikî vir.
       title: Têkeve %{domain}
     sign_up:
+      preamble: Bi ajimêrekê li ser vê rajekarê Mastodon re, tu yê karîbî her keseke din li ser torê bişopînî, her ku ajimêrê wan li ku derê tê pêşkêşkirin.
       title: Ka em te bi rê bixin li ser %{domain}.
     status:
       account_status: Rewşa ajimêr
@@ -1314,6 +1317,7 @@ ku:
   scheduled_statuses:
     over_daily_limit: Te sînorê %{limit} şandiyên demsazkirî yên ji bo îro derbas kir
     over_total_limit: Te sînorê %{limit} şandiyên demsazkirî derbas kir
+    too_soon: Dîroka bernamesazkirinê divê dîrokeke ji îro pêşvetir be
   sessions:
     activity: Çalakiya dawî
     browser: Gerok
diff --git a/config/locales/la.yml b/config/locales/la.yml
index cc92bf6d28..edd99ac23d 100644
--- a/config/locales/la.yml
+++ b/config/locales/la.yml
@@ -31,3 +31,28 @@ la:
       destroyed_msg: Nota moderationis feliciter deleta est!
     accounts:
       are_you_sure: Esne certus?
+    statuses:
+      title: Ratiōnis publicātiōnēs
+  auth:
+    privacy_policy_agreement_html: Lēgī et cōnsēnsī ad <a href="%{privacy_policy_path}" target="_blank">pōlīticam prīvātī tūtelam</a>
+    setup:
+      email_settings_hint_html: Premī nexum quem tibi mīsimus ut %{email} comprobēs. Hīc manēbimus.
+    sign_up:
+      preamble: Cum ratiōne in hāc servēnce Mastodonī, quemlibet alium hominem in rēte sequī poteris, ubicumque ratiō eius administrētur.
+  imports:
+    overwrite_preambles:
+      blocking_html: Mox <strong>līstam blōcātiōnis tuam</strong> substituēbis cum usque ad <strong>%{total_items} ratiōnēs</strong> e <strong>%{filename}</strong>.
+      bookmarks_html: Mox <strong>signa tua</strong> substituēbis cum usque ad <strong>%{total_items} prōnūntiātiōnēs</strong> e <strong>%{filename}</strong>.
+      domain_blocking_html: Mox <strong>līstam blōcātiōnis dominī tuī</strong> substituēbis cum usque ad <strong>%{total_items} dominī</strong> e <strong>%{filename}</strong>.
+      following_html: Mox <strong>sequēris</strong> usque ad <strong>%{total_items} ratiōnēs</strong> e <strong>%{filename}</strong> et <strong>desinēs sequī quemquam alium</strong>.
+      lists_html: Mox <strong>līstās tuās</strong> substituēbis cum contentīs <strong>%{filename}</strong>. Usque ad <strong>%{total_items} ratiōnēs</strong> ad novās līstās addentur.
+      muting_html: Mox <strong>līstam ratiōnum sīlentiōrum tuārum</strong> substituēbis cum usque ad <strong>%{total_items} ratiōnēs</strong> e <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Mox <strong>blōcābis</strong> usque ad <strong>%{total_items} ratiōnēs</strong> e <strong>%{filename}</strong>.
+      bookmarks_html: '"Mox usque ad <strong>%{total_items} nūmēra</strong> e <strong>%{filename}</strong> adde in <strong>līstās tuās</strong>."'
+      domain_blocking_html: Mox <strong>blōcābis</strong> usque ad <strong>%{total_items} dōmina</strong> e <strong>%{filename}</strong>.
+      following_html: Mox <strong>sequēris</strong> usque ad <strong>%{total_items} ratiōnēs</strong> e <strong>%{filename}</strong>.
+      lists_html: Mox usque ad <strong>%{total_items} ratiōnēs</strong> e <strong>%{filename}</strong> adde in <strong>līstās tuās</strong>. Novae līstās creābuntur, sī līstā ad quam addere nōn est.
+      muting_html: Mox <strong>tacēbis</strong> usque ad <strong>%{total_items} ratiōnēs</strong> e <strong>%{filename}</strong>.
+  scheduled_statuses:
+    too_soon: Dāta dēfīnīta in futurō esse dēbēbit.
diff --git a/config/locales/lad.yml b/config/locales/lad.yml
index c1364fbb01..10871b826b 100644
--- a/config/locales/lad.yml
+++ b/config/locales/lad.yml
@@ -462,18 +462,6 @@ lad:
       new:
         title: Importa blokos de domeno
       no_file: Dinguna dosya tiene sido eskojida
-    fasp:
-      debug:
-        callbacks:
-          delete: Efasa
-      providers:
-        name: Nombre
-        registrations:
-          confirm: Konfirma
-          reject: Refuza
-        save: Guadra
-        sign_in: Konektate
-        status: Estado
     follow_recommendations:
       description_html: "<strong>Las rekomendasyones de kuentos ayudan a los muevos utilizadores a topar presto kontenido enteresante</strong>. Kuando un utilizador no tiene enteraktuado kon otros lo sufisiente komo para djenerar rekomendasyones personalizadas de kuentos a las ke segir, en sus lugar se le rekomiendan estes kuentos. Se rekalkulan diariamente a partir de una mikstura de kuentos kon el mayor numero de enteraksyones rezientes i kon el mayor numero de suivantes lokales kon una lingua determinada."
       language: Para la lingua
@@ -824,7 +812,6 @@ lad:
       batch:
         remove_from_report: Kita del raporto
         report: Raporto
-      contents: Kontenidos
       deleted: Efasado
       favourites: Favoritos
       history: Estoria de versiones
@@ -838,6 +825,7 @@ lad:
       original_status: Publikasyon orijinala
       reblogs: Repartajasyones
       status_changed: Publikasyon trokada
+      title: Publikasyones del kuento
       trending: Trendes
       visibility: Vizivilita
       with_media: Kon multimedia
@@ -914,9 +902,6 @@ lad:
       search: Bushka
       title: Etiketas
       updated_msg: Konfigurasyon de etiketas aktualizada kon sukseso
-    terms_of_service:
-      live: En bivo
-      publish: Publika
     title: Administrasyon
     trends:
       allow: Permete
@@ -1113,6 +1098,7 @@ lad:
     migrate_account: Transferate a otro kuento
     migrate_account_html: Si keres readresar este kuento a otra distinta, puedes <a href="%{path}">konfigurarlo aki</a>.
     or_log_in_with: O konektate kon tu kuento kon
+    privacy_policy_agreement_html: Tengo meldado i acheto la <a href="%{privacy_policy_path}" target="_blank">politika de privasita</a>
     progress:
       confirm: Konfirma posta
       details: Tus detalyos
@@ -1137,6 +1123,7 @@ lad:
     set_new_password: Establese muevo kod
     setup:
       email_below_hint_html: Mira en tu kuti de spam o solisita de muevo. Si el adreso de posta elektronika ke aparese aki es yerrado, puedes trokarlo aki.
+      email_settings_hint_html: Klika el atadjiko ke te embimos para verifikar %{email}. Asperaremos aki.
       link_not_received: No risivites un atadijo?
       new_confirmation_instructions_sent: Resiviras un muevo mesaj de posta elektronika kon el atadjio de konfirmasyon en unos minutos!
       title: Reviza tu kuti de arivo
@@ -1145,7 +1132,7 @@ lad:
       title: Konektate kon %{domain}
     sign_up:
       manual_review: Las enrejistrasyones en %{domain} pasan por la revizyon manuala de muestros moderadores. Para ayudarmos a prosesar tu enrejistrasyon, eskrive un poko sovre ti i por ke keres un kuento en %{domain}.
-      preamble: Kon un kuento en este sirvidor de Mastodon, podras segir a kualkier otra persona en el fediverso, endependientemente del sirvidor en el ke se tope.
+      preamble: Kon un kuento en este sirvidor de Mastodon, podras segir a kualkier otra persona en la red, endependientemente del sirvidor en el ke se tope.
       title: Kriya kuento de Mastodon en %{domain}.
     status:
       account_status: Estado del kuento
@@ -1357,6 +1344,20 @@ lad:
       merge_long: Manten rejistros egzistentes i adjusta muevos
       overwrite: Sobreskrive
       overwrite_long: Mete muevos rejistros en vez de los aktuales
+    overwrite_preambles:
+      blocking_html: Estas a punto de <strong>substituyir tu lista de blokos</strong> por asta <strong>%{total_items} kuentos </strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Estas a punto de <strong>substituyir tus markadores</strong> por asta <strong>%{total_items} publikasyones</strong> ke vinyeron de <strong>%{filename}</strong>.
+      domain_blocking_html: Estas a punto de <strong>substituyir tu lista de blokos de domeno</strong> por asta <strong>%{total_items} domenos </strong> de <strong>%{filename}</strong>.
+      following_html: Estas a punto de <strong>segir</strong> asta <strong>%{total_items} kuentos</strong> de <strong>%{filename}</strong> i <strong>deshar de segir todos los otros kuentos</strong>.
+      lists_html: Estas a punto de <strong>sustituyir tus listas</strong> con el kontenido de <strong>%{filename}</strong>. Asta <strong>%{total_items} kuentos</strong> seran adjustados a muevas listas.
+      muting_html: Estas a punto de <strong>substituyir tu lista de kuentos silensyados</strong> por asta <strong>%{total_items} kuentos </strong> de <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Estas a punto de <strong>blokar</strong> asta <strong>%{total_items} kuentos </strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Estas a punto de adjustar asta <strong>%{total_items} publikasyones</strong> de <strong>%{filename}</strong> a tus <strong>markadores</strong>.
+      domain_blocking_html: Estas a punto de <strong>blokar</strong> asta <strong>%{total_items} domenos </strong> de <strong>%{filename}</strong>.
+      following_html: Estas a punto de <strong>segir</strong> asta <strong>%{total_items} kuentos </strong> de <strong>%{filename}</strong>.
+      lists_html: Estas a punto de adjustar <strong>%{total_items} kuentos</strong> dizde <strong>%{filename}</strong> a tus <strong>listas</strong>. Se kriyaran muevas listas si no ay listas para adjustarlas.
+      muting_html: Estas a punto de <strong>silensyar</strong> asta <strong>%{total_items} kuentos </strong> de <strong>%{filename}</strong>.
     preface: Puedes importar siertos datos, komo todas las personas a las kualas estas sigiendo o blokando en tu kuento en esta instansya, dizde dosyas eksportadas de otra instansya.
     recent_imports: Importasyones resyentes
     states:
@@ -1612,6 +1613,7 @@ lad:
   scheduled_statuses:
     over_daily_limit: Tienes superado el limito de %{limit} publikasyones programadas para akel diya
     over_total_limit: Tienes superado el limito de %{limit} publikasyones programadas
+    too_soon: La data programada deve estar en el avenir
   self_destruct:
     lead_html: Malorozamente, <strong>%{domain}</strong> va serrar permanentemente. Si teniyas un kuento ayi, ya no podras utilizarlo, ama ainda puedes solisitar una kopya de tus datos.
     title: Este sirvidor esta serrando
diff --git a/config/locales/lt.yml b/config/locales/lt.yml
index f0da6d5f15..df3779d81e 100644
--- a/config/locales/lt.yml
+++ b/config/locales/lt.yml
@@ -4,7 +4,7 @@ lt:
     about_mastodon_html: 'Ateities socialinis tinklas: jokių reklamų, jokių įmonių sekimo, etiškas dizainas ir decentralizacija! Turėk savo duomenis su Mastodon.'
     contact_missing: Nenustatyta
     contact_unavailable: Nėra
-    hosted_on: "„Mastodon“ talpinamas domene %{domain}"
+    hosted_on: Mastodon talpinamas %{domain}
     title: Apie
   accounts:
     followers:
@@ -185,7 +185,6 @@ lt:
         create_domain_allow: Kurti domeno leidimą
         create_domain_block: Kurti domeno bloką
         create_ip_block: Kurti IP taisyklę
-        create_relay: Kurti perdavimą
         create_unavailable_domain: Kurti nepasiekiamą domeną
         create_user_role: Kurti vaidmenį
         demote_user: Pažeminti naudotoją
@@ -195,20 +194,16 @@ lt:
         destroy_domain_block: Ištrinti domeno bloką
         destroy_instance: Išvalyti domeną
         destroy_ip_block: Ištrinti IP taisyklę
-        destroy_relay: Ištrinti perdavimą
         destroy_status: Ištrinti įrašą
         destroy_unavailable_domain: Ištrinti nepasiekiamą domeną
         destroy_user_role: Sunaikinti vaidmenį
         disable_2fa_user: Išjungti 2FA
         disable_custom_emoji: Išjungti pasirinktinį jaustuką
-        disable_relay: Išjungti perdavimą
         disable_user: Išjungti naudotoją
         enable_custom_emoji: Įjungti pasirinktinį jaustuką
-        enable_relay: Įjungti perdavimą
         enable_user: Įjungti naudotoją
         memorialize_account: Memorializuoti paskyrą
         promote_user: Paaukštinti naudotoją
-        publish_terms_of_service: Publikuoti paslaugų sąlygas
         reject_appeal: Atmesti apeliaciją
         reject_user: Atmesti naudotoją
         remove_avatar_user: Pašalinti avatarą
@@ -242,7 +237,6 @@ lt:
         create_domain_allow_html: "%{name} leido federaciją su domenu %{target}"
         create_domain_block_html: "%{name} užblokavo domeną %{target}"
         create_ip_block_html: "%{name} sukūrė taisyklę IP %{target}"
-        create_relay_html: "%{name} sukūrė perdavimą %{target}"
         create_unavailable_domain_html: "%{name} sustabdė tiekimą į domeną %{target}"
         create_user_role_html: "%{name} sukūrė %{target} vaidmenį"
         demote_user_html: "%{name} pažemino naudotoją %{target}"
@@ -252,20 +246,16 @@ lt:
         destroy_domain_block_html: "%{name} atblokavo domeną %{target}"
         destroy_instance_html: "%{name} išvalė domeną %{target}"
         destroy_ip_block_html: "%{name} ištrynė taisyklę IP %{target}"
-        destroy_relay_html: "%{name} ištrynė perdavimą %{target}"
         destroy_status_html: "%{name} pašalino įrašą %{target}"
         destroy_unavailable_domain_html: "%{name} pratęsė tiekimą į domeną %{target}"
         destroy_user_role_html: "%{name} ištrynė %{target} vaidmenį"
         disable_2fa_user_html: "%{name} išjungė dviejų veiksnių reikalavimą naudotojui %{target}"
         disable_custom_emoji_html: "%{name} išjungė jaustuką %{target}"
-        disable_relay_html: "%{name} išjungė perdavimą %{target}"
         disable_user_html: "%{name} išjungė prisijungimą naudotojui %{target}"
         enable_custom_emoji_html: "%{name} įjungė jaustuką %{target}"
-        enable_relay_html: "%{name} įjungė perdavimą %{target}"
         enable_user_html: "%{name} įjungė prisijungimą naudotojui %{target}"
         memorialize_account_html: "%{name} pavertė %{target} paskyrą į atminimo puslapį"
         promote_user_html: "%{name} paaukštino naudotoją %{target}"
-        publish_terms_of_service_html: "%{name} publikavo paslaugų sąlygų naujinimus"
         reject_appeal_html: "%{name} atmetė prižiūjimo veiksmo apeliaciją iš %{target}"
         reject_user_html: "%{name} atmetė registraciją iš %{target}"
         remove_avatar_user_html: "%{name} pašalino %{target} avatarą"
@@ -294,7 +284,6 @@ lt:
       title: Audito žurnalas
       unavailable_instance: "(domeno pavadinimas neprieinamas)"
     announcements:
-      back: Atgal į skelbimus
       destroyed_msg: Skelbimas sėkmingai ištrintas.
       edit:
         title: Redaguoti skelbimą
@@ -303,9 +292,6 @@ lt:
       new:
         create: Sukurti skelbimą
         title: Naujas skelbimas
-      preview:
-        explanation_html: 'El. laiškas bus išsiųstas <strong>%{display_count} naudotojams</strong>. Į el. laišką bus įtrauktas toliau nurodytas tekstas:'
-        title: Peržiūrėti skelbimo pranešimą
       publish: Skelbti
       published_msg: Skelbimas sėkmingai paskelbtas.
       scheduled_for: Suplanuota %{time}
@@ -357,7 +343,7 @@ lt:
       opened_reports: atidaryti ataskaitos
       pending_appeals_html:
         few: "<strong>%{count}</strong> laukiantys apeliacijos"
-        many: "<strong>%{count}</strong> laukiamos apeliacijos"
+        many: "<strong>%{count}</strong> laukiama apeliacija"
         one: "<strong>%{count}</strong> laukiama apeliacija"
         other: "<strong>%{count}</strong> laukiančių apeliacijų"
       pending_reports_html:
@@ -595,9 +581,7 @@ lt:
       back_to_account: Grįžti į paskyros puslapį
       back_to_report: Grįžti į ataskaitos puslapį
       batch:
-        add_to_report: 'Pridėti į ataskaitą #%{id}'
         remove_from_report: Pašalinti iš ataskaitos
-      contents: Turinys
       deleted: Ištrinta
       favourites: Mėgstami
       history: Versijų istorija
@@ -606,15 +590,11 @@ lt:
       media:
         title: Medija
       metadata: Metaduomenys
-      no_history: Šis įrašas nebuvo redaguotas
       no_status_selected: Jokie įrašai nebuvo pakeisti, nes nė vienas buvo pasirinktas
       open: Atidaryti įrašą
       original_status: Originalus įrašas
-      replied_to_html: Atsakyta į %{acct_link}
-      status_title: Paskelbė @%{name}
-      title: Paskyros įrašai – @%{name}
+      title: Paskyros įrašai
       trending: Tendencinga
-      view_publicly: Peržiūrėti viešai
       with_media: Su medija
     system_checks:
       database_schema_check:
@@ -649,37 +629,6 @@ lt:
       reset: Atkurti
       search: Paieška
       title: Saitažodžiai
-    terms_of_service:
-      back: Atgal į paslaugų sąlygas
-      changelog: Kas pasikeitė
-      create: Naudoti savo
-      current: Dabartinė
-      draft: Parengti iš naujo
-      generate: Naudoti šabloną
-      generates:
-        action: Generuoti
-        chance_to_review_html: "<strong>Sugeneruotos paslaugų sąlygos nebus publikuojamos automatiškai.</strong> Turėsite galimybę peržiūrėti rezultatus. Kad tęstumėte, užpildykite reikiamą informaciją."
-        explanation_html: Pateiktas paslaugų sąlygų šablonas yra tik informacinio pobūdžio ir neturėtų būti laikomas teisiniu patarimu bet kokiu klausimu. Dėl savo situacijos ir konkrečių teisinių klausimų pasitarkite su savo teisininku.
-        title: Paslaugų sąlygų sąranka
-      history: Istorija
-      live: Tiesioginis
-      no_history: Kol kas nėra įrašyta paslaugų sąlygų pakeitimų.
-      no_terms_of_service_html: Šiuo metu nesate sukonfigūravę jokių paslaugų sąlygų. Paslaugų sąlygos skirtos suteikti aiškumo ir apsaugoti jus nuo galimų įsipareigojimų ginčuose su naudotojais.
-      notified_on_html: Naudotojams pranešta %{date}
-      notify_users: Pranešti naudotojus
-      preview:
-        explanation_html: 'El. laiškas bus išsiųstas <strong>%{display_count} naudotojams</strong>, kurie užsiregistravo iki %{date}. Į el. laišką bus įtrauktas toks tekstas:'
-        send_preview: Siųsti peržiūrą į %{email}
-        send_to_all:
-          few: Siųsti %{display_count} el. laiškus
-          many: Siųsti %{display_count} el. laiško
-          one: Siųsti %{display_count} el. laišką
-          other: Siųsti %{display_count} el. laiškų
-        title: Peržiūrėti paslaugų sąlygų pranešimą
-      publish: Publikuoti
-      published_on_html: Publikuota %{date}
-      save_draft: Išsaugoti juodraštį
-      title: Paslaugų sąlygos
     title: Administracija
     trends:
       allow: Leisti
@@ -832,6 +781,7 @@ lt:
     migrate_account: Persikelti prie kitos paskyros
     migrate_account_html: Jei nori šią paskyrą nukreipti į kitą, gali <a href="%{path}">sukonfigūruoti ją čia</a>.
     or_log_in_with: Arba prisijungti su
+    privacy_policy_agreement_html: Perskaičiau ir sutinku su <a href="%{privacy_policy_path}" target="_blank">privatumo politika</a>
     progress:
       details: Tavo duomenys
       review: Mūsų peržiūra
@@ -854,21 +804,19 @@ lt:
     security: Apsauga
     set_new_password: Nustatyti naują slaptažodį
     setup:
-      email_settings_hint_html: Spustelėkite nuorodą, kurią atsiuntėme adresu %{email}, kad pradėtumėte naudoti „Mastodon“. Lauksime čia.
+      email_settings_hint_html: Spustelėkite mūsų atsiųstą nuorodą, kad patvirtintumėte %{email}. Mes lauksime čia.
       link_not_received: Negavai nuorodos?
       title: Patikrinti pašto dėžutę
     sign_in:
       preamble_html: Prisijunk su savo <strong>%{domain}</strong> kredencialais. Jei tavo yra kitame serveryje, čia prisijungti negalėsi.
       title: Prisijungti prie %{domain}
     sign_up:
-      preamble: Su šio „Mastodon“ serverio paskyra galėsite sekti bet kurį kitą fediversijoje esantį asmenį, nepriklausomai nuo to, kur yra jo paskyra.
+      preamble: Su paskyra šiame „Mastodon“ serveryje galėsite sekti bet kurį kitą tinkle esantį asmenį, nepriklausomai nuo to, kur yra jų paskyra.
     status:
       account_status: Paskyros būsena
       redirecting_to: Tavo paskyra yra neaktyvi, nes šiuo metu ji nukreipiama į %{acct}.
       self_destruct: Kadangi %{domain} uždaromas, turėsi tik ribotą prieigą prie savo paskyros.
       view_strikes: Peržiūrėti ankstesnius savo paskyros pažeidimus
-    user_agreement_html: Perskaičiau ir sutinku su <a href="%{terms_of_service_path}" target="_blank">paslaugų sąlygomis</a> ir <a href="%{privacy_policy_path}" target="_blank">privatumo politika</a>
-    user_privacy_agreement_html: Perskaičiau ir sutinku su <a href="%{privacy_policy_path}" target="_blank">privatumo politika</a>
   author_attribution:
     example_title: Teksto pavyzdys
     hint_html: Ar rašote naujienas ar tinklaraščio straipsnius už „Mastodon“ ribų? Valdykite, kaip būsite nurodomi, kai jais bus bendrinama platformoje „Mastodon“.
@@ -975,6 +923,16 @@ lt:
       merge_long: Išsaugoti esančius įrašus ir pridėti naujus
       overwrite: Perrašyti
       overwrite_long: Pakeisti senus įrašus naujais
+    overwrite_preambles:
+      blocking_html: Ketinate <strong>pakeisti savo blokavimo sąrašą</strong> iki <strong>%{total_items} paskyrų</strong> iš <strong>%{filename}</strong>.
+      bookmarks_html: Ketinate <strong>pakeisti savo žymes</strong> iki <strong>%{total_items} įrašų</strong> iš <strong>%{filename}</strong>.
+      domain_blocking_html: Ketinate <strong>pakeisti savo domeno blokavimo sąrašą</strong> iki <strong>%{total_items} domenų</strong> iš <strong>%{filename}</strong>.
+      following_html: Ketinate <strong>sekti</strong> iki <strong>%{total_items} paskyrų</strong> iš <strong>%{filename}</strong> ir <strong>nustoti sekti kitus</strong>.
+    preambles:
+      domain_blocking_html: Ketinate <strong>užblokuoti</strong> iki <strong>%{total_items} domenų</strong> iš <strong>%{filename}</strong>.
+      following_html: Ketinate <strong>sekti</strong> iki <strong>%{total_items} paskyrų</strong> iš <strong>%{filename}</strong>.
+      lists_html: Ketinate įtraukti iki <strong>%{total_items} paskyrų</strong> iš <strong>%{filename}</strong> į savo <strong>sąrašus</strong>. Nauji sąrašai bus sukurti, jei nėra sąrašo, į kurį būtų galima įtraukti.
+      muting_html: Ketinate <strong>nutildyti</strong> iki <strong>%{total_items} paskyrų</strong> iš <strong>%{filename}</strong>.
     preface: Gali importuoti duomenis, kuriuos eksportavai iš kito serverio, pavyzdžiui, sekamų arba blokuojamų žmonių sąrašą.
     success: Jūsų informacija sėkmingai įkelta ir bus apdorota kaip įmanoma greičiau
     types:
@@ -1110,7 +1068,7 @@ lt:
   scheduled_statuses:
     over_daily_limit: Jūs pasieketė limitą (%{limit}) galimų toot'ų per dieną
     over_total_limit: Jūs pasieketė %{limit} limitą galimų toot'ų
-    too_soon: data turi būti ateityje.
+    too_soon: Suplanuota data turi būti ateityje.
   sessions:
     activity: Paskutinė veikla
     browser: Naršyklė
@@ -1202,8 +1160,6 @@ lt:
     min_age_label: Amžiaus riba
   stream_entries:
     sensitive_content: Jautrus turinys
-  terms_of_service:
-    title: Paslaugų sąlygos
   themes:
     contrast: Mastodon (didelis kontrastas)
     default: Mastodon (tamsi)
@@ -1223,10 +1179,6 @@ lt:
     recovery_instructions_html: Jeigu prarandate prieiga prie telefono, jūs galite naudoti atkūrimo kodus esančius žemiau, kad atgautumėte priega prie savo paskyros.<strong>Laikykite atkūrimo kodus saugiai</strong> Pavyzdžiui, galite norėti juos išspausdinti, ir laikyti kartu su kitais svarbiais dokumentais.
     webauthn: Saugumo raktai
   user_mailer:
-    announcement_published:
-      description: "%{domain} administratoriai daro skelbimą:"
-      subject: Paslaugos skelbimas
-      title: "%{domain} paslaugos skelbimas"
     appeal_approved:
       action: Paskyros nustatymai
       subtitle: Tavo paskyros būklė vėl yra gera.
@@ -1245,13 +1197,6 @@ lt:
       title: Nepavyko atlikti dvigubo tapatybės nustatymo
     suspicious_sign_in:
       further_actions_html: Jei tai buvai ne tu, rekomenduojame nedelsiant %{action} ir įjungti dvigubą tapatybės nustatymą, kad tavo paskyra būtų saugi.
-    terms_of_service_changed:
-      agreement: Tęsiant naudojimąsi %{domain}, jūs sutinkate su šiomis sąlygomis. Jei nesutinkate su atnaujintomis sąlygomis, bet kuriuo metu galite nutraukti sutartį su %{domain} ištrindami savo paskyrą.
-      changelog: Trumpai apie tai, ką šis naujinimas reiškia jums
-      sign_off: "%{domain} komanda"
-      subject: Paslaugų sąlygų atnaujinimai
-      subtitle: Keičiasi %{domain} paslaugų sąlygos
-      title: Svarbus naujinimas
     warning:
       categories:
         spam: Šlamštas
diff --git a/config/locales/lv.yml b/config/locales/lv.yml
index 2bb5abf2de..a3197c6b36 100644
--- a/config/locales/lv.yml
+++ b/config/locales/lv.yml
@@ -19,10 +19,10 @@ lv:
     pin_errors:
       following: Tev ir jāseko personai, kuru vēlies atbalstīt
     posts:
-      one: Ieraksts
-      other: Ieraksti
-      zero: Ierakstu
-    posts_tab_heading: Ieraksti
+      one: Ziņa
+      other: Ziņas
+      zero: Ziņu
+    posts_tab_heading: Ziņas
     self_follow_error: Nav ļauts sekot savam kontam
   admin:
     account_actions:
@@ -32,24 +32,24 @@ lv:
       title: Veikt satura pārraudzības darbību %{acct}
     account_moderation_notes:
       create: Atstāt piezīmi
-      created_msg: Satura pārraudzības piezīme ir sekmīgi izveidota.
-      destroyed_msg: Satura pārraudzības piezīme ir sekmīgi iznīcināta.
+      created_msg: Satura pārraudzības piezīme ir veiksmīgi izveidota.
+      destroyed_msg: Satura pārraudzības piezīme ir veiksmīgi iznīcināta.
     accounts:
       add_email_domain_block: Liegt e-pasta domēnu
       approve: Apstiprināt
-      approved_msg: "%{username} reģistrēšanās pieteikums sekmīgi apstiprināts"
+      approved_msg: Veiksmīgi apstiprināts %{username} reģistrēšanās pieteikums
       are_you_sure: Vai esi pārliecināts?
       avatar: Profila attēls
       by_domain: Domēns
       change_email:
-        changed_msg: E-pasta adrese sekmīgi nomainīta.
+        changed_msg: E-pasts veiksmīgi nomainīts!
         current_email: Pašreizējais e-pasts
         label: Mainīt e-pastu
         new_email: Jaunā e-pasta adrese
         submit: Mainīt e-pastu
         title: Mainīt e-pastu %{username}
       change_role:
-        changed_msg: Loma sekmīgi nomainīta.
+        changed_msg: Loma veiksmīgi nomainīta!
         edit_roles: Pārvaldīt lietotāju lomas
         label: Mainīt lomu
         no_role: Nav lomas
@@ -74,7 +74,7 @@ lv:
       enable: Atsaldēt
       enable_sign_in_token_auth: Iespējot autentificēšanos ar e-pasta pilnvaru
       enabled: Iespējots
-      enabled_msg: "%{username} konts sekmīgi atsaldēts"
+      enabled_msg: Veiksmīgi atsaldēts %{username} konts
       followers: Sekotāji
       follows: Seko
       header: Galvene
@@ -92,7 +92,7 @@ lv:
       media_attachments: Multivides pielikumi
       memorialize: Pārvērst atmiņās
       memorialized: Piemiņa saglabāta
-      memorialized_msg: "%{username} sekmīgi pārveidots par piemiņas kontu"
+      memorialized_msg: "%{username} veiksmīgi pārvērsts par piemiņas kontu"
       moderation:
         active: Aktīvie
         all: Visi
@@ -120,19 +120,19 @@ lv:
       public: Publisks
       push_subscription_expires: PuSH abonements beidzas
       redownload: Atsvaidzināt profilu
-      redownloaded_msg: "%{username} profils sekmīgi atsvaidzināts no pirmavota"
+      redownloaded_msg: Veiksmīgi atsvaidzināts %{username} profils no izcelsmes
       reject: Noraidīt
-      rejected_msg: "%{username} reģistrēšanās pieteikums sekmīgi noraidīts"
+      rejected_msg: Veiksmīgi noraidīts %{username} reģistrēšanās pieteikums
       remote_suspension_irreversible: Šī konta dati ir neatgriezeniski dzēsti.
       remote_suspension_reversible_hint_html: Konts ir apturēts viņu serverī, un dati tiks pilnībā noņemti %{date}. Līdz tam attālais serveris var atjaunot šo kontu bez jebkādām negatīvām sekām. Ja vēlaties nekavējoties noņemt visus konta datus, varat to izdarīt tālāk.
       remove_avatar: Noņemt profila attēlu
       remove_header: Noņemt galveni
-      removed_avatar_msg: "%{username} profila attēls sekmīgi noņemts"
-      removed_header_msg: "%{username} galvenes attēls sekmīgi noņemts"
+      removed_avatar_msg: Veiksmīgi noņemts %{username} profila attēls
+      removed_header_msg: Veiksmīgi noņemts %{username} galvenes attēls
       resend_confirmation:
         already_confirmed: Šis lietotājs jau ir apstiprināts
         send: Atkārtoti nosūtīt apstiprinājuma saiti
-        success: Apstiprinājuma saite sekmīgi nosūtīta.
+        success: Apstiprinājuma saite veiksmīgi nosūtīta!
       reset: Atiestatīt
       reset_password: Atiestatīt paroli
       resubscribe: Pieteikties vēlreiz
@@ -144,15 +144,15 @@ lv:
       security_measures:
         only_password: Tikai parole
         password_and_2fa: Parole un 2FA
-      sensitive: Uzspiest atzīmēšanu kā jūtīgu
-      sensitized: Atzīmēts kā jūtīgs
+      sensitive: Sensitīvs
+      sensitized: Atzīmēts kā sensitīvs
       shared_inbox_url: Koplietotās iesūtnes URL
       show:
         created_reports: Sastādītie ziņojumi
         targeted_reports: Ziņojuši citi
       silence: Ierobežot
       silenced: Ierobežots
-      statuses: Ieraksti
+      statuses: Ziņas
       strikes: Iepriekšējie streiki
       subscribe: Abonēt
       suspend: Apturēt
@@ -161,14 +161,14 @@ lv:
       suspension_reversible_hint_html: Konta darbība ir apturēta, un dati tiks pilnībā noņemti %{date}. Līdz tam kontu var atjaunot bez jebkādām nelabvēlīgām sekām. Ja vēlies nekavējoties noņemt visus konta datus, to vari izdarīt zemāk.
       title: Konti
       unblock_email: Atbloķēt e-pasta adresi
-      unblocked_email_msg: "%{username} e-pasta adreses liegšana sekmīgi atcelta"
+      unblocked_email_msg: Veiksmīgi atbloķēta %{username} e-pasta adrese
       unconfirmed_email: Neapstiprināts e-pasts
-      undo_sensitized: Atcelt uzspiestu atzīmēšanu kā jūtīgu
+      undo_sensitized: Atcelt sensitivizēšanu
       undo_silenced: Atsaukt ierobežojumu
       undo_suspension: Atsaukt apturēšanu
-      unsilenced_msg: "%{username} konta ierobežojums sekmīgi atsaukts"
+      unsilenced_msg: Veiksmīgi atsaukts %{username} konta ierobežojums
       unsubscribe: Anulēt abonementu
-      unsuspended_msg: "%{username} konts apturēšana sekmīgi atcelta"
+      unsuspended_msg: Veiksmīgi neapturēts %{username} konts
       username: Lietotājvārds
       view_domain: Skatīt domēna kopsavilkumu
       warn: Brīdināt
@@ -176,7 +176,7 @@ lv:
       whitelisted: Atļauts federācijai
     action_logs:
       action_types:
-        approve_appeal: Apstiprināt pārsūdzību
+        approve_appeal: Apstiprināt Apelāciju
         approve_user: Apstiprināt lietotāju
         assigned_to_self_report: Piešķirt Pārskatu
         change_email_user: Mainīt lietotāja e-pasta adresi
@@ -190,7 +190,6 @@ lv:
         create_domain_block: Izveidot Domēna Bloku
         create_email_domain_block: Izveidot e-pasta domēna liegumu
         create_ip_block: Izveidot IP noteikumu
-        create_relay: Izveidot retranslāciju
         create_unavailable_domain: Izveidot Nepieejamu Domēnu
         create_user_role: Izveidot lomu
         demote_user: Pazemināt Lietotāju
@@ -202,35 +201,31 @@ lv:
         destroy_email_domain_block: Izdzēst e-pasta domēna liegumu
         destroy_instance: Attīrīt domēnu
         destroy_ip_block: Dzēst IP noteikumu
-        destroy_relay: Izdzēst retranslāciju
         destroy_status: Izdzēst Rakstu
         destroy_unavailable_domain: Dzēst Nepieejamu Domēnu
         destroy_user_role: Iznīcināt lomu
         disable_2fa_user: Atspējot 2FA
         disable_custom_emoji: Atspējot pielāgotu emocijzīmi
-        disable_relay: Atspējot retranslāciju
         disable_sign_in_token_auth_user: Atspējot autentificēšanos ar e-pasta pilnvaru lietotājam
         disable_user: Atspējot Lietotāju
         enable_custom_emoji: Iespējot pielāgotu emocijzīmi
-        enable_relay: Iespējot retranslāciju
         enable_sign_in_token_auth_user: Iespējot autentificēšanos ar e-pasta pilnvaru lietotājam
         enable_user: Ieslēgt Lietotāju
         memorialize_account: Saglabāt Kontu Piemiņai
         promote_user: Izceltt Lietotāju
-        publish_terms_of_service: Publicēt pakalpojuma izmantošanas noteikumus
-        reject_appeal: Noraidīt pārsūdzību
+        reject_appeal: Noraidīt Apelāciju
         reject_user: Noraidīt lietotāju
         remove_avatar_user: Noņemt profila attēlu
         reopen_report: Atkārtoti Atvērt Ziņojumu
-        resend_user: Atkārtoti nosūtīt apstiprinājuma e-pasta ziņojumu
+        resend_user: Atkārtoti nosūtīt Apstiprinājuma Pastu
         reset_password_user: Atiestatīt Paroli
         resolve_report: Atrisināt Ziņojumu
-        sensitive_account: Uzspiesti atzimēt kontu kā jūtīgu
+        sensitive_account: Piespiedu sensitīvizēt kontu
         silence_account: Ierobežot Kontu
         suspend_account: Apturēt Kontu
         unassigned_report: Atcelt Pārskata Piešķiršanu
         unblock_email_account: Atbloķēt e-pasta adresi
-        unsensitive_account: Atsaukt uzspiestu konta atzīmēšanu kā jūtīgu
+        unsensitive_account: Atsaukt Konta Piespiedu Sensitivizēšanu
         unsilence_account: Atcelt Konta Ierobežošanu
         unsuspend_account: Atcelt konta apturēšanu
         update_announcement: Atjaunināt Paziņojumu
@@ -241,7 +236,7 @@ lv:
         update_status: Atjaunināt ziņu
         update_user_role: Atjaunināt lomu
       actions:
-        approve_appeal_html: "%{name} apstiprināja satura pārraudzības lēmuma pārsūdzību no %{target}"
+        approve_appeal_html: "%{name} apstiprināja satura pārraudzības lēmuma iebildumu no %{target}"
         approve_user_html: "%{name} apstiprināja reģistrēšanos no %{target}"
         assigned_to_self_report_html: "%{name} piešķīra pārskatu %{target} sev"
         change_email_user_html: "%{name} nomainīja lietotāja %{target} e-pasta adresi"
@@ -259,11 +254,9 @@ lv:
         create_user_role_html: "%{name} nomainīja %{target} lomu"
         demote_user_html: "%{name} pazemināja lietotāju %{target}"
         destroy_announcement_html: "%{name} izdzēsa paziņojumu %{target}"
-        destroy_canonical_email_block_html: "%{name} atcēla e-pasta adreses liegumu ar jaucējvērtību %{target}"
         destroy_custom_emoji_html: "%{name} izdzēsa emocijzīmi %{target}"
         destroy_domain_allow_html: "%{name} neatļāva federāciju ar domēnu %{target}"
         destroy_domain_block_html: "%{name} atbloķēja domēnu %{target}"
-        destroy_email_domain_block_html: "%{name} atcēla e-pasta domēna %{target} liegumu"
         destroy_instance_html: "%{name} attīrija domēnu %{target}"
         destroy_ip_block_html: "%{name} izdzēsa nosacījumu priekš IP %{target}"
         destroy_status_html: "%{name} noņēma ziņu %{target}"
@@ -276,20 +269,19 @@ lv:
         enable_user_html: "%{name} iespējoja pieteikšanos lietotājam %{target}"
         memorialize_account_html: "%{name} pārvērta %{target} kontu par atmiņas lapu"
         promote_user_html: "%{name} paaugstināja lietotāju %{target}"
-        publish_terms_of_service_html: "%{name} padarīja pieejamus pakalpojuma izmantošanas noteikumu atjauninājumus"
-        reject_appeal_html: "%{name} noraidīja satura pārraudzības lēmuma pārsūdzību no %{target}"
+        reject_appeal_html: "%{name} noraidīja satura pārraudzības lēmuma iebildumu no %{target}"
         reject_user_html: "%{name} noraidīja reģistrēšanos no %{target}"
         remove_avatar_user_html: "%{name} noņēma %{target} profila attēlu"
         reopen_report_html: "%{name} atkārtoti atvēra ziņojumu %{target}"
         resend_user_html: "%{name} atkārtoti nosūtīja %{target} apstiprinājuma e-pasta ziņojumu"
         reset_password_user_html: "%{name} atiestatīja paroli lietotājam %{target}"
         resolve_report_html: "%{name} atrisināja ziņojumu %{target}"
-        sensitive_account_html: "%{name} atzīmēja %{target} informācijas nesēju kā jūtīgu"
+        sensitive_account_html: "%{name} atzīmēja %{target} multividi kā sensitīvu"
         silence_account_html: "%{name} ierobežoja %{target} kontu"
         suspend_account_html: "%{name} apturēja %{target} kontu"
         unassigned_report_html: "%{name} nepiešķīra ziņojumu %{target}"
         unblock_email_account_html: "%{name} atbloķēja %{target} e-pasta adresi"
-        unsensitive_account_html: "%{name} atcēla %{target} informācijas nesēja atzīmēšanu kā jūtīgu"
+        unsensitive_account_html: "%{name} atmarķēja %{target} multividi kā sensitīvu"
         unsilence_account_html: "%{name} atcēla ierobežojumu %{target} kontam"
         unsuspend_account_html: "%{name} neapturēja %{target} kontu"
         update_announcement_html: "%{name} atjaunināja paziņojumu %{target}"
@@ -306,8 +298,7 @@ lv:
       title: Audita žurnāls
       unavailable_instance: "(domēna vārds nav pieejams)"
     announcements:
-      back: Atgriezties pie paziņojumiem
-      destroyed_msg: Paziņojums sekmīgi izdzēsts.
+      destroyed_msg: Paziņojums ir veiksmīgi izdzēsts!
       edit:
         title: Labot paziņojumu
       empty: Neviens paziņojums netika atrasts.
@@ -315,35 +306,32 @@ lv:
       new:
         create: Izveidot paziņojumu
         title: Jauns paziņojums
-      preview:
-        explanation_html: 'E-pasta ziņojums tiks nosūtīts <strong>%{display_count} lietotājiem</strong>. Šis teksts tiks iekļauts e-pasta ziņojumā:'
-        title: Priekšskatīt paziņojumu
       publish: Publicēt
-      published_msg: Paziņojums sekmīgi publicēts.
+      published_msg: Paziņojums ir veiksmīgi publicēts!
       scheduled_for: Plānots uz %{time}
       scheduled_msg: Paziņojums ieplānots publicēšanai!
       title: Paziņojumi
       unpublish: Atcelt publicēšanu
-      unpublished_msg: Paziņojuma publicēšana sekmīgi atcelta!
-      updated_msg: Paziņojums sekmīgi atjaunināts!
+      unpublished_msg: Paziņojuma publicēšana veiksmīgi atcelta!
+      updated_msg: Paziņojums ir veiksmīgi atjaunināts!
     critical_update_pending: Gaida kritisko atjauninājumu
     custom_emojis:
       assign_category: Piešķirt kategoriju
       by_domain: Domēns
-      copied_msg: Emocijzīmes vietējā kopija ir sekmīgi izveidota
+      copied_msg: Emocijas vietējā kopija ir veiksmīgi izveidota
       copy: Kopēt
       copy_failed_msg: Nevarēja izveidot šīs emocijzīmes vietējo kopiju
       create_new_category: Izveidot jaunu kategoriju
-      created_msg: Emocijzīme sekmīgi izveidota.
+      created_msg: Emocijzīme veiksmīgi izveidota!
       delete: Dzēst
-      destroyed_msg: Emocijzīme sekmīgi iznīcināta!
+      destroyed_msg: Emocijzīme ir veiksmīgi iznīcināta!
       disable: Atspējot
       disabled: Atspējots
-      disabled_msg: Šī emocijzīme sekmīgi atspējota
+      disabled_msg: Šī emocijzīme ir veiksmīgi atspējota
       emoji: Emocijzīmes
       enable: Iespējot
       enabled: Iespējots
-      enabled_msg: Šī emocijzīme ir sekmīgi iespējota
+      enabled_msg: Šī emocijzīme ir veiksmīgi iespējota
       image_hint: PNG vai GIF līdz %{size}
       list: Saraksts
       listed: Uzrakstītas
@@ -359,7 +347,7 @@ lv:
       unlist: Izslēgt
       unlisted: Neminētie
       update_failed_msg: Nevarēja atjaunināt šo emocijzīmi
-      updated_msg: Emocijzīme sekmīgi atjaunināta.
+      updated_msg: Emocijzīme veiksmīgi atjaunināta!
       upload: Augšupielādēt
     dashboard:
       active_users: aktīvie lietotāji
@@ -397,7 +385,7 @@ lv:
         title: Apelācijas
     domain_allows:
       add_new: Atļaut federāciju ar domēnu
-      created_msg: Domēns tika sekmīgi atļauts federācijai
+      created_msg: Domēns ir veiksmīgi atļauts federācijai
       destroyed_msg: Domēns ir aizliegts federācijai
       export: Eksportēt
       import: Importēt
@@ -410,7 +398,7 @@ lv:
         permanent_action: Apturēšanas atsaukšana neatjaunos nekādus datus vai attiecības.
         preamble_html: Tu gatavojies apturēt domēna <strong>%{domain}</strong> un tā apakšdomēnu darbību.
         remove_all_data: Tādējādi no tava servera tiks noņemts viss šī domēna kontu saturs, multivide un profila dati.
-        stop_communication: Tavs serveris pārtrauks sazināties ar šiem serveriem.
+        stop_communication: Jūsu serveris pārtrauks sazināties ar šiem serveriem.
         title: Apstiprināt domēna %{domain} bloķēšanu
         undo_relationships: Tādējādi tiks atsauktas jebkuras sekošanas attiecības starp šo un tavu serveru kontiem.
       created_msg: Domēna bloķēšana tagad tiek apstrādāta
@@ -435,9 +423,9 @@ lv:
       obfuscate: Apslēpt domēna vārdu
       obfuscate_hint: Daļēji apslēpt domēna nosaukumu sarakstā, ja ir iespējota domēna ierobežojumu saraksta reklamēšana
       private_comment: Privāts komentārs
-      private_comment_hint: Atstāt piebildi par šo domēna ierobežojumu satura pārraudzītāju iekšējai lietošanai.
+      private_comment_hint: Atstāj komentāru par šo domēna ierobežojumu moderatoru iekšējai lietošanai.
       public_comment: Publisks komentārs
-      public_comment_hint: Piebilde par šo domēna ierobežojumu vispārējai sabiedrībai, ja ir iespējota domēnu ierobežojumu saraksta reklamēšana.
+      public_comment_hint: Atstāj komentāru par šo domēna ierobežojumu plašai sabiedrībai, ja ir iespējota domēnu ierobežojumu saraksta reklamēšana.
       reject_media: Noraidīt multivides failus
       reject_media_hint: Noņem lokāli saglabātos multivides failus un atsaka tos lejupielādēt nākotnē. Nav nozīmes apturēšanai
       reject_reports: Noraidīt ziņojumus
@@ -451,7 +439,6 @@ lv:
         one: "%{count} mēģinājums pagājušajā nedēļā"
         other: "%{count} reģistrēšanās mēģinājumi pagājušajā nedēļā"
         zero: "%{count} mēģinājumu pagājušajā nedēļā"
-      created_msg: Sekmīgi liegts e-pasta domēns
       delete: Dzēst
       dns:
         types:
@@ -460,10 +447,7 @@ lv:
       new:
         create: Pievienot domēnu
         resolve: Atrisināt domēnu
-        title: Liegt jaunu e-pasta domēnu
-      no_email_domain_block_selected: Neviens e-pasta domēna bloks netika mainīts, jo neviens netika atlasīts
       not_permitted: Nav atļauta
-      resolved_dns_records_hint_html: Domēna vārds saistās ar zemāk norādītajiem MX domēniem, kuri beigās ir atbildīgi par e-pasta pieņemšana. MX domēna liegšana liegs reģistrēšanos no jebkuras e-pasta adreses, kas izmanto to pašu MX domēnu, pat ja redzamais domēna vārds ir atšķirīgs. <strong>Jāuzmanās, lai neliegtu galvenos e-pasta pakalpojuma sniedzējus.</strong>
       resolved_through_html: Atrisināts, izmantojot %{domain}
       title: Bloķētie e-pasta domēni
     export_domain_allows:
@@ -481,35 +465,6 @@ lv:
       new:
         title: Importēt bloķētos domēnus
       no_file: Nav atlasīts neviens fails
-    fasp:
-      debug:
-        callbacks:
-          created_at: Izveidots
-          delete: Izdzēst
-          ip: IP adrese
-          request_body: Pieprasījuma saturs
-          title: Atkļūdošanas atpakaļsaukumi
-      providers:
-        base_url: Pamata URL
-        callback: Atpakaļsaukums
-        delete: Izdzēst
-        edit: Labot nodrošinātāju
-        finish_registration: Pabeigt reģistrēšanos
-        name: Nosaukums
-        providers: Nodrošinātāji
-        public_key_fingerprint: Publiskās atslēgas pirkstu nospiedums
-        registration_requested: Pieprasīta reģistrēšanās
-        registrations:
-          confirm: Apstiprināt
-          description: Tu saņēmi reģistrēšanos no FĀPS. Tā ir jānoraida, ja to neveici. Ja veici, rūpīgi jāsalīdzina nosaukums un atslēgas pirkstu nospiedums, pirms reģistrēšanās apstiprināšanas.
-          reject: Noraidīt
-          title: Apstiprināt FĀPS reģistrēšanos
-        save: Saglabāt
-        select_capabilities: Atlasīt spējas
-        sign_in: Pieteikties
-        status: Stāvoklis
-        title: Fediverse ārējie pakalpojumu sniedzēji
-      title: FĀPS
     follow_recommendations:
       description_html: "<strong>Sekošanas ieteikumi palīdz jauniem lietotājiem ātri arast saistošu saturu</strong>. Kad lietotājs nav pietiekami mijiedarbojies ar citiem, lai veidotos pielāgoti sekošanas iteikumi, tiek ieteikti šie konti. Tie tiek pārskaitļoti ik dienas, izmantojot kontu, kuriem ir augstākās nesenās iesaistīšanās un lielākais vietējo sekotāju skaits norādītajā valodā."
       language: Valodai
@@ -534,7 +489,7 @@ lv:
           zero: Neizdevušies mēģinājumi %{count} dienās.
         no_failures_recorded: Nav reģistrētu kļūdu.
         title: Pieejamība
-        warning: Pēdējais mēģinājums izveidot savienojumu ar šo serveri ir bijis nesekmīgs
+        warning: Pēdējais mēģinājums izveidot savienojumu ar šo serveri ir bijis neveiksmīgs
       back_to_all: Visas
       back_to_limited: Ierobežotās
       back_to_warning: Brīdinājums
@@ -560,7 +515,7 @@ lv:
         instance_languages_dimension: Populārākās valodas
         instance_media_attachments_measure: saglabātie multivides pielikumi
         instance_reports_measure: ziņojumi par viņiem
-        instance_statuses_measure: saglabātie ieraksti
+        instance_statuses_measure: saglabātās ziņas
       delivery:
         all: Visas
         clear: Notīrīt piegādes kļūdas
@@ -603,7 +558,7 @@ lv:
       title: Uzaicinājumi
     ip_blocks:
       add_new: Izveidot noteikumu
-      created_msg: Sekmīgi pievienota jauna IP kārtula
+      created_msg: Veiksmīgi pievienots jauns IP noteikums
       delete: Dzēst
       expires_in:
         '1209600': 2 nedēļas
@@ -625,7 +580,7 @@ lv:
       disable: Atspējot
       disabled: Atspējots
       enable: Iespējot
-      enable_hint: Tiklīdz iespējots, serveris abonēs visus šī releja publiskos ierakstus un sāks tam sūtīt šī iservera publiskos ierakstus.
+      enable_hint: Kad tas būs iespējots, tavs serveris abonēs visas publiskās ziņas no šī releja un sāks tam sūtīt šī servera publiskās ziņas.
       enabled: Iespējots
       inbox_url: Releja URL
       pending: Gaida apstiprinājumu no releja
@@ -635,8 +590,8 @@ lv:
       status: Statuss
       title: Releji
     report_notes:
-      created_msg: Ziņojuma piezīme sekmīgi izveidota.
-      destroyed_msg: Ziņojuma piezīme sekmīgi izdzēsta.
+      created_msg: Ziņojuma piezīme ir veiksmīgi izveidota!
+      destroyed_msg: Ziņojuma piezīme ir veiksmīgi izdzēsta!
     reports:
       account:
         notes:
@@ -647,7 +602,7 @@ lv:
       action_taken_by: Veiktā darbība
       actions:
         delete_description_html: Raksti, par kurām ziņots, tiks dzēsti, un tiks reģistrēts brīdinājums, lai palīdzētu tev izvērst turpmākos pārkāpumus saistībā ar to pašu kontu.
-        mark_as_sensitive_description_html: Informācijas nesēji ierakstos, par kuriem ziņots, tiks atzīmēti kā jūtīgi, un tiks iegrāmatots brīdinājums, lai palīdzētu ziņot par turpmākiem tā paša konta pārkāpumiem.
+        mark_as_sensitive_description_html: Multividesu faili ziņojumos, par kuriem ziņots, tiks atzīmēti kā sensitīvi, un tiks reģistrēts brīdinājums, lai palīdzētu tev izvērst turpmākus pārkāpumus saistībā ar to pašu kontu.
         other_description_html: Skatīt vairāk iespēju kontrolēt konta uzvedību un pielāgot saziņu ar paziņoto kontu.
         resolve_description_html: Pret norādīto kontu netiks veiktas nekādas darbības, netiks reģistrēts brīdinājums, un ziņojums tiks slēgts.
         silence_description_html: Konts būs redzams tikai tiem, kas tam jau seko vai meklē to manuāli, ievērojami ierobežojot tā sasniedzamību. To vienmēr var atgriezt. Tiek aizvērti visi šī konta pārskati.
@@ -656,12 +611,9 @@ lv:
       actions_description_remote_html: Izlem, kādas darbības jāveic, lai atrisinātu šo ziņojumu. Tas ietekmēs tikai to, kā <strong>tavs</strong> serveris sazinās ar šo attālo kontu un apstrādā tā saturu.
       actions_no_posts: Šim ziņojumam nav saistītu ierakstu, kurus izdzēst
       add_to_report: Pievienot varāk paziņošanai
-      already_suspended_badges:
-        local: Jau ir apturēts šajā serverī
-        remote: Jau ir apturēts viņu serverī
       are_you_sure: Vai esi pārliecināts?
       assign_to_self: Piešķirt man
-      assigned: Piešķirtais satura pārraudzītājs
+      assigned: Piešķirtais moderators
       by_target_domain: Ziņotā konta domēns
       cancel: Atcelt
       category: Kategorija
@@ -677,7 +629,7 @@ lv:
       forwarded_replies_explanation: Šis ziņojums ir no attāla lietotāja un par attālu saturu. Tas tika pārvirzīts šeit, jo saturs, par kuru tika ziņots, ir atbilde vienam no šī servera lietotājiem.
       forwarded_to: Pārsūtīti %{domain}
       mark_as_resolved: Atzīmēt kā atrisinātu
-      mark_as_sensitive: Atzīmēt kā jūtīgu
+      mark_as_sensitive: Atzīmēt kā sensitīvu
       mark_as_unresolved: Atzīmēt kā neatrisinātu
       no_one_assigned: Neviena
       notes:
@@ -687,8 +639,8 @@ lv:
         delete: Dzēst
         placeholder: Jāapraksta veiktās darbības vai jebkuri citi saistītie atjauninājumi...
         title: Piezīmes
-      notes_description_html: Apskatīt un atstāt piezīmes citiem satura pārraudzītājiem un sev nākotnei
-      processed_msg: 'Ziņojums #%{id} sekmīgi apstrādāts'
+      notes_description_html: Skati un atstāj piezīmes citiem moderatoriem un sev nākotnei
+      processed_msg: 'Pārskats #%{id} veiksmīgi apstrādāts'
       quick_actions_description_html: 'Veic ātro darbību vai ritini uz leju, lai skatītu saturu, par kuru ziņots:'
       remote_user_placeholder: attālais lietotājs no %{instance}
       reopen: Atkārtoti atvērt ziņojumu
@@ -697,7 +649,7 @@ lv:
       reported_by: Ziņoja
       reported_with_application: Ziņots no lietotnes
       resolved: Atrisināts
-      resolved_msg: Ziņojums sekmīgi atrisināts.
+      resolved_msg: Ziņojums veiksmīgi atrisināts!
       skip_to_actions: Pāriet uz darbībām
       status: Statuss
       statuses: Ziņotais saturs
@@ -705,12 +657,12 @@ lv:
       summary:
         action_preambles:
           delete_html: 'Jūs gatavojaties <strong>noņemt</strong> dažas no lietotāja <strong>@%{acct}</strong> ziņām. Tas:'
-          mark_as_sensitive_html: 'Tu gatavojies <strong>atzīmēt</strong> dažus no lietotāja <strong>@%{acct}</strong> ierakstiem kā <strong>jūtīgus</strong>. Tas:'
+          mark_as_sensitive_html: 'Jūs gatavojaties <strong>atzīmēt</strong> dažas no lietotāja <strong>@%{acct}</strong> ziņām kā <strong>sensitīvas</strong>. Tas:'
           silence_html: 'Jūs gatavojaties <strong>ierobežot</strong> <strong>@%{acct}</strong> kontu. Tas:'
           suspend_html: 'Jūs gatavojaties <strong>apturēt</strong> <strong>@%{acct}</strong> kontu. Tas:'
         actions:
-          delete_html: Noņemt aizskarošos ierakstus
-          mark_as_sensitive_html: Atzīmēt aizskarošo ierakstu informācijas nesējus kā jūtīgus
+          delete_html: Noņemt aizskarošās ziņas
+          mark_as_sensitive_html: Atzīmēt aizskarošo ziņu multivides saturu kā sensitīvu
           silence_html: Ievērojami ierobežo <strong>@%{acct}</strong> sasniedzamību, padarot viņa profilu un saturu redzamu tikai cilvēkiem, kas jau seko tam vai pašrocīgi uzmeklē profilu
           suspend_html: Apturēt <strong>@%{acct}</strong>, padarot viņu profilu un saturu nepieejamu un neiespējamu mijiedarbību ar
         close_report: 'Atzīmēt ziņojumu #%{id} kā atrisinātu'
@@ -718,9 +670,8 @@ lv:
         delete_data_html: Dzēsiet lietotāja <strong>@%{acct}</strong> profilu un saturu pēc 30 dienām, ja vien to darbība pa šo laiku netiks atcelta
         preview_preamble_html: "<strong>@%{acct}</strong> saņems brīdinājumu ar šādu saturu:"
         record_strike_html: Ierakstiet brīdinājumu pret <strong>@%{acct}</strong>, lai palīdzētu jums izvērst turpmākus pārkāpumus no šī konta
-        send_email_html: Nosūtīt <strong>@%{acct}</strong> brīdinājuma e-pasta ziņojumu
         warning_placeholder: Izvēles papildu pamatojums satura pārraudzības darbībai.
-      target_origin: Konta, par kuru ziņots, izcelsme
+      target_origin: Ziņotā konta izcelsme
       title: Ziņojumi
       unassign: Atsaukt
       unknown_action_msg: 'Nezināms konts: %{action}'
@@ -760,7 +711,6 @@ lv:
         manage_appeals: Pārvaldīt Pārsūdzības
         manage_appeals_description: Ļauj lietotājiem pārskatīt iebildumus pret satura pārraudzības darbībām
         manage_blocks: Pārvaldīt Bloķus
-        manage_blocks_description: Ļauj lietotājiem liegt e-pasta pakalpojumu sniedzējus un IP adreses
         manage_custom_emojis: Pārvaldīt Pielāgotās Emocijzīmes
         manage_custom_emojis_description: Ļauj lietotājiem pārvaldīt pielāgotās emocijzīmes serverī
         manage_federation: Pārvaldīt Federāciju
@@ -778,7 +728,6 @@ lv:
         manage_taxonomies: Pārvaldīt Taksonomijas
         manage_taxonomies_description: Ļauj lietotājiem pārskatīt aktuālāko saturu un atjaunināt tēmturu iestatījumus
         manage_user_access: Pārvaldīt Lietotāju Piekļuves
-        manage_user_access_description: Ļauj lietotājiem atspējot citu lietotāju divupakāpju autentificēšanos, mainīt savu e-pasta adresi un atiestatīt savu paroli
         manage_users: Pārvaldīt Lietotājus
         manage_users_description: Ļauj lietotājiem skatīt citu lietotāju informāciju un veikt pret viņiem satura pārraudzības darbības
         manage_webhooks: Pārvaldīt Tīmekļa Aizķeres
@@ -814,7 +763,7 @@ lv:
         title: Pieprasīt jaunajiem lietotājiem atrisināt CAPTCHA, lai apstiprinātu savu kontu
       content_retention:
         danger_zone: Bīstama sadaļa
-        preamble: Pārraugi, kā Mastodon tiek glabāts lietotāju izveidots saturs!
+        preamble: Kontrolē, kā Mastodon tiek glabāts lietotāju ģenerēts saturs.
         title: Satura saglabāšana
       default_noindex:
         desc_html: Ietekmē visus lietotājus, kuri paši nav mainījuši šo iestatījumu
@@ -850,10 +799,9 @@ lv:
       title: Servera iestatījumi
     site_uploads:
       delete: Dzēst augšupielādēto failu
-      destroyed_msg: Vietnes augšupielāde sekmīgi izdzēsta.
+      destroyed_msg: Vietnes augšupielāde ir veiksmīgi izdzēsta!
     software_updates:
       critical_update: Kritiski - lūdzu, ātri atjaunini
-      description: Ir ieteicams uzturēt savu Mastodon serveri atjauninātu, lai gūtu labumu no jaunākajiem labojumiem un iespējām. Vēl jo vairāk, dažreiz ir ļoti svarīgi savlaicīgi atjaunināt Mastodon, lai izvairītos no drošības nepilnībām. Šo iemeslu dēļ Mastodon pārbauda atjauninājumus ik pēc 30 minūtēm, un paziņos atbilstoši e-pasta paziņojumu iestatījumiem.
       documentation_link: Uzzināt vairāk
       release_notes: Laidiena piezīmes
       title: Pieejamie atjauninājumi
@@ -869,10 +817,8 @@ lv:
       back_to_account: Atpakaļ uz konta lapu
       back_to_report: Atpakaļ uz paziņojumu lapu
       batch:
-        add_to_report: 'Pievienot atskaitei #%{id}'
         remove_from_report: Noņemt no ziņojuma
         report: Ziņojums
-      contents: Saturs
       deleted: Dzēstie
       favourites: Izlase
       history: Versiju vēsture
@@ -881,26 +827,22 @@ lv:
       media:
         title: Multivide
       metadata: Metadati
-      no_history: Šis ieraksts nav bijis labots
-      no_status_selected: Neviens ieraksts netika mainīts, jo nekas netika atlasīts
+      no_status_selected: Neviena ziņa netika mainīta, jo neviena netika atlasīta
       open: Atvērt ziņu
-      original_status: Sākotnējais ieraksts
+      original_status: Oriģinālā ziņa
       reblogs: Reblogi
-      replied_to_html: Atbildēja %{acct_link}
-      status_changed: Ieraksts izmainīts
-      status_title: Publicēja @%{name}
-      title: Konta ieraksti - @%{name}
+      status_changed: Ziņa mainīta
+      title: Konta ziņas
       trending: Aktuāli
-      view_publicly: Skatīt publiski
       visibility: Redzamība
       with_media: Ar multividi
     strikes:
       actions:
         delete_statuses: "%{name} izdzēsa %{target} publikācijas"
         disable: "%{name} iesaldēja %{target} kontu"
-        mark_statuses_as_sensitive: "%{name} atzīmēja %{target} ierakstu kā jūtīgu"
+        mark_statuses_as_sensitive: "%{name} atzīmēja %{target} ziņu kā sensitīvu"
         none: "%{name} nosūtīja brīdinājumu %{target}"
-        sensitive: "%{name} atzīmēja %{target} kontu kā jūtīgu"
+        sensitive: "%{name} atzīmēja %{target} kontu kā sensitīvu"
         silence: "%{name} ierobežoja %{target} kontu"
         suspend: "%{name} apturēja %{target} kontu"
       appeal_approved: Pārsūdzēts
@@ -908,7 +850,7 @@ lv:
       appeal_rejected: Apelācija noraidīta
     system_checks:
       database_schema_check:
-        message_html: Ir nepabeigtas datubāzes migrācijas. Lūgums palaist tās, lai nodrošinātu, ka lietotne darbojas, kā paredzēts
+        message_html: Notiek datubāzu migrācijas. Lūdzu, palaid tās, lai nodrošinātu, ka lietojumprogramma darbojas, kā paredzēts
       elasticsearch_health_red:
         message_html: Elasticsearch klasteris ir neveselīgs (sarkans statuss), meklēšanas līdzekļi nav pieejami
       elasticsearch_health_yellow:
@@ -950,13 +892,11 @@ lv:
         message_html: "<strong>Tava objektu krātuve ir nepareizi konfigurēta. Tavu lietotāju privātums ir apdraudēts.</strong>"
     tags:
       moderation:
-        not_trendable: Nav izplatīts
         not_usable: Nav izmantojams
         pending_review: Gaida pārskatīšanu
         review_requested: Pieprasīta pārskatīšana
         reviewed: Pārskatīts
         title: Stāvoklis
-        trendable: Izplatīts
         unreviewed: Nepārskatīts
         usable: Izmantojams
       name: Nosaukums
@@ -968,51 +908,14 @@ lv:
       search: Meklēt
       title: Tēmturi
       updated_msg: Tēmtura iestatījumi ir veiksmīgi atjaunināti
-    terms_of_service:
-      back: Atpakaļ uz pakalpojuma izmantošanas noteikumiem
-      changelog: Kas ir mainījies
-      create: Izmantot savus
-      current: Pašreizējie
-      draft: Melnraksts
-      generate: Izmantot sagatavi
-      generates:
-        action: Izveidot
-        chance_to_review_html: "<strong>Izveidotie pakalpojuma izmantošanas noteikumi netiks laisti klajā automātiski.</strong> Būs iespēja izskatīt iznākumu. Lūgums norādīt nepieciešamo informāciju, lai turpinātu."
-        explanation_html: Pakalpojuma izmantošanas noteikumu sagatave tiek piedāvāta tikai izzināšanas nolūkam, un to nevajadzētu izmantot kā juridisku padomu jebkurā jautājumā. Lūgums sazināties ar savu juridisko padomdevēju par saviem apstākļiem un noteiktiem juridiskiem jautājumiem.
-        title: Pakalpojuma izmantošānas noteikumu uzstādīšana
-      going_live_on_html: Darbībā, spēkā no %{date}
-      history: Vēsture
-      live: Darbībā
-      no_history: Nav ierakstu par pakalpojuma izmantošanas noteikumu izmaiņām.
-      no_terms_of_service_html: Pašlaik nav uzstādīti pakalpojuma izmantošanas noteikumi. Tie ir paredzēti, lai sniegtu skaidrību un aizsargātu no iespējamas atbildības strīdos ar lietotājiem.
-      notified_on_html: Lietotājiem paziņots %{date}
-      notify_users: Paziņot lietotājiem
-      preview:
-        explanation_html: 'E-pasta ziņojums tiks nosūtīts <strong>%{display_count} lietotājiem</strong>, kuri ir reģistrējušies pirms %{date}. Šis teksts tiks iekļauts e-pasta ziņojumā:'
-        send_preview: Nosūtīt priekšskatījumu uz %{email}
-        send_to_all:
-          one: Nosūtīt %{display_count} e-pasta ziņojumu
-          other: Nosūtīt %{display_count} e-pasta ziņojumus
-          zero: Nosūtīt %{display_count} e-pasta ziņojumu
-        title: Priekškatīt pakalpojuma izmantošanas noteikumu paziņojumu
-      publish: Publicēt
-      published_on_html: Publicēti %{date}
-      save_draft: Saglabāt melnrakstu
-      title: Pakalpojuma izmantošanas noteikumi
     title: Pārvaldība
     trends:
       allow: Atļaut
       approved: Apstiprināts
-      confirm_allow: Vai tiešām atļaut atlasītos tēmturus?
-      confirm_disallow: Vai tiešām neatļaut atlasītās birkas?
       disallow: Neatļaut
       links:
         allow: Atļaut saiti
         allow_provider: Atļaut publicētāju
-        confirm_allow: Vai tiešām atļaut atlasītās saites?
-        confirm_allow_provider: Vai tiešām atļaut atlasītos nodrošinātājus?
-        confirm_disallow: Vai tiešām neatļaut atlasītās saites?
-        confirm_disallow_provider: Vai tiešām neatļaut atlasītos nodrošinātājus?
         description_html: Šīs ir saites, kuras pašlaik bieži koplieto konti, no kuriem Tavs serveris redz ziņas. Tas var palīdzēt Taviem lietotājiem uzzināt, kas notiek pasaulē. Neviena saite netiek publiski rādīta, līdz tu apstiprini izdevēju. Tu vari arī atļaut vai noraidīt atsevišķas saites.
         disallow: Neatļaut saiti
         disallow_provider: Neatļaut publicētāju
@@ -1037,10 +940,6 @@ lv:
       statuses:
         allow: Ļaut veikt ierakstus
         allow_account: Atļaut autoru
-        confirm_allow: Vai tiešām atļaut atlasītos stāvokļus?
-        confirm_allow_account: Vai tiešām atļaut atlasītos kontus?
-        confirm_disallow: Vai tiešām neatļaut atlasītos stāvokļus?
-        confirm_disallow_account: Vai tiešām neatļaut atlasītos kontus?
         description_html: Šie ir ieraksti, par kuriem zina Tavs serveris un kuri pašlaik tiek daudz kopīgoti un pievienoti izlasēm. Tas var palīdzēt jaunajiem lietotājiem un tiem, kuri atgriežas, atrast vairāk cilvēku, kam sekot. Neviens ieraksts netiek publiski rādīts, līdz apstiprināsi autoru un ja autors ļauj savu kontu ieteikt citiem. Tu vari arī atļaut vai noraidīt atsevišķus ierakstus.
         disallow: Neļaut veikt ierakstus
         disallow_account: Neatļaut autoru
@@ -1086,7 +985,7 @@ lv:
     webhooks:
       add_new: Pievienot galapunktu
       delete: Dzēst
-      description_html: "<strong>Tīmekļa aizķere</strong> ļauj Mastodon nosūtīt <strong>reāllaika paziņojumus</strong> Tavai lietotnei par izvēlētajiem notikumiem, lai Tava lietotne varētu <strong>automātiski izraisīt atbildes darbības</strong>."
+      description_html: Izmantojot <strong>tīmekļa aizķeri</strong>, Mastodon var nosūtīt jūsu lietojumprogrammai <strong>reāllaika paziņojumus</strong> par izvēlētajiem notikumiem, lai tava lietojumprogramma varētu <strong>automātiski izraisīt reakcijas</strong>.
       disable: Atspējot
       disabled: Atspējots
       edit: Labot galapunktu
@@ -1105,16 +1004,13 @@ lv:
       title: Tīmekļa āķi
       webhook: Tīmekļa āķis
   admin_mailer:
-    auto_close_registrations:
-      body: Nesenu satura pārraudzības darbību trūkuma dēļ reģistrācija %{instance} ir automātiski pārslēgta nepieciešamība pēc pašrocīgas izskatīšanas, lai novērstu %{instance} izmantošana kā platformu iespējami sliktiem dalībniekiem. Jebkurā brīdī var ieslēgt atpakaļ atvērtu reģistrēšanos.
-      subject: Reģistrēšanās %{instance} tika automātiski pārslēgta, lai pieprasītu apstiprināšanu
     new_appeal:
       actions:
         delete_statuses: lai izdzēstu viņu ierakstus
         disable: lai iesaldētu viņu kontu
-        mark_statuses_as_sensitive: lai atzīmētu viņu ierakstus kā jūtīgus
+        mark_statuses_as_sensitive: lai atzīmētu viņu ziņas kā sensitīvas
         none: brīdinājums
-        sensitive: lai atzīmētu viņu kontu kā jūtīgu
+        sensitive: lai atzīmētu viņu kontu kā sensitīvu
         silence: lai ierobežotu viņu kontu
         suspend: lai apturētu viņu kontu
       body: "%{target} iebilst %{action_taken_by} satura pārraudzības lēmumam no %{date}, kas bija %{type}. Viņi rakstīja:"
@@ -1144,8 +1040,8 @@ lv:
       subject: Tiek pārskatītas jaunas tendences %{instance}
   aliases:
     add_new: Izveidot aizstājvārdu
-    created_msg: Sekmīgi izveidots jauns aizstājvārds. Tagad vari uzsākt pārvietošanu no vecā konta.
-    deleted_msg: Aizstājvārds tika sekmīgi noņemts. Pāreja no tā konta uz šo vairs nebūs iespējama.
+    created_msg: Veiksmīgi izveidots jauns aizstājvārds. Tagad vari sākt pārvietošanu no vecā konta.
+    deleted_msg: Aizstājvārds tika veiksmīgi noņemts. Pāreja no šī konta uz šo vairs nebūs iespējama.
     empty: Tev nav aizstājvārdu.
     hint_html: Ja vēlies pāriet no cita konta uz šo, šeit vari izveidot aizstājvārdu, kas ir nepieciešams, lai varētu turpināt sekotāju pārvietošanu no vecā konta uz šo. Šī darbība pati par sevi ir <strong>nekaitīga un atgriezeniska</strong>. <strong>Konta migrācija tiek sākta no vecā konta</strong>.
     remove: Atsaistīt aizstājvārdu
@@ -1159,7 +1055,7 @@ lv:
       body: Mastodon ir tulkojuši brīvprātīgie.
       guide_link: https://crowdin.com/project/mastodon
       guide_link_text: Ikviens var piedalīties.
-    sensitive_content: Jūtīgs saturs
+    sensitive_content: Sensitīvs saturs
   application_mailer:
     notification_preferences: Mainīt e-pasta uztādījumus
     salutation: "%{name},"
@@ -1169,11 +1065,11 @@ lv:
     view_profile: Skatīt profilu
     view_status: Skatīt ziņu
   applications:
-    created: Lietotne sekmīgi izveidota
-    destroyed: Lietotnes sekmīgi izdzēsta
+    created: Lietojumprogramma ir veiksmīgi izveidota
+    destroyed: Lietojumprogramma ir veiksmīgi dzēsta
     logout: Iziet
-    regenerate_token: Atkārtoti izveidot piekļuves pilnvaru
-    token_regenerated: Piekļuves pilnvara sekmīgi izveidota no jauna
+    regenerate_token: Atjaunot piekļuves marķieri
+    token_regenerated: Piekļuves marķieris veiksmīgi atjaunots
     warning: Esi ļoti uzmanīgs ar šiem datiem. Nekad nedalies ne ar vienu ar tiem!
     your_token: Tavs piekļuves marķieris
   auth:
@@ -1202,7 +1098,7 @@ lv:
     dont_have_your_security_key: Vai tev nav drošības atslēgas?
     forgot_password: Aizmirsi paroli?
     invalid_reset_password_token: Paroles atiestatīšanas pilnvara nav derīga, vai tai ir beidzies derīgums. Lūdzu, pieprasi jaunu.
-    link_to_otp: Jāievada divpakāpju kods no tālruņa vai atkpes kods
+    link_to_otp: Jāievada divpakāpju kods no tālruņa vai atkopšanas kods
     link_to_webauth: Lieto savu drošības atslēgas iekārtu
     log_in_with: Pieslēgties ar
     login: Pieteikties
@@ -1210,6 +1106,7 @@ lv:
     migrate_account: Pāriešana uz citu kontu
     migrate_account_html: Ja vēlies novirzīt šo kontu uz citu, tu vari <a href="%{path}">to konfigurēt šeit</a>.
     or_log_in_with: Vai piesakies ar
+    privacy_policy_agreement_html: Esmu izlasījis un piekrītu <a href="%{privacy_policy_path}" target="_blank">privātuma politikai</a>
     progress:
       confirm: Apstiprināt e-pasta adresi
       details: Tavi dati
@@ -1226,15 +1123,15 @@ lv:
       accept: Pieņemt
       back: Atpakaļ
       invited_by: 'Tu vari pievienoties %{domain}, pateicoties uzaicinājumam, ko saņēmi no:'
-      preamble: Tos iestata un ievieš %{domain} satura pārraudzītāji.
-      preamble_invited: Pirms turpināt, lūgums apsvērt pamatnoteikumus, kurus norādījuši %{domain} satura pārraudzītāji.
+      preamble: Tos iestata un ievieš %{domain} moderatori.
+      preamble_invited: Pirms turpināt, lūdzu, apsver galvenos noteikumus, ko noteikuši %{domain} moderatori.
       title: Daži pamatnoteikumi.
       title_invited: Tu esi uzaicināts.
     security: Drošība
     set_new_password: Iestatīt jaunu paroli
     setup:
       email_below_hint_html: Jāpārbauda sava surogātpasta mape vai jāpieprasa vēl vienu! Savu e-pasta adresi var labot, ja tā ir nepareiza.
-      email_settings_hint_html: Jāatver saite, kuru mēs nosūtījām uz %{email}, lai sāktu izmantot Mastodon. Mēs gaidīsim šeit pat.
+      email_settings_hint_html: Jāatver saite, kuru mēs nosūtījām, lai apliecinātu %{email}. Mēs pagaidīsim tepat.
       link_not_received: Vai nesaņēmi sati?
       new_confirmation_instructions_sent: Pēc dažām minūtēm saņemsi jaunu e-pasta ziņojumu ar apstiprinājuma saiti.
       title: Pārbaudi savu iesūtni
@@ -1242,28 +1139,22 @@ lv:
       preamble_html: Jāpiesakās ar saviem <strong>%{domain}</strong> piekļuves datiem. Ja konts tiek mitināts citā serverī, šeit nevarēs pieteikties.
       title: Pieteikties %{domain}
     sign_up:
-      manual_review: Reģistrāciju %{domain} pašrocīgi izskata mūsu satura pārraudzītāji. Lai palīdzētu mums apstrādāt Tavu reģistrāciju, uzraksti mazliet par sevi un to, kāpēc vēlies kontu %{domain}!
-      preamble: Ar kontu šajā Mastodon serverī varēsi sekot jebkuram citam cilvēkam fediversā, neatkarīgi no tā, kur tiek mitināts viņu konts.
+      manual_review: Reģistrācijas domēnā %{domain} manuāli pārbauda mūsu moderatori. Lai palīdzētu mums apstrādāt tavu reģistrāciju, uzraksti mazliet par sevi un to, kāpēc vēlies kontu %{domain}.
+      preamble: Ar kontu šajā Mastodon serverī varēsi sekot jebkuram citam tīklā esošam cilvēkam neatkarīgi no tā, kur tiek mitināts tā konts.
       title: Atļauj tevi iestatīt %{domain}.
     status:
       account_status: Konta statuss
       confirming: Gaida e-pasta adreses apstiprināšanas pabeigšanu.
       functional: Tavs konts ir pilnā darba kārtībā.
-      pending: Tavs pieteikums ir rindā uz izskatīšanu, ko veic mūsu personāls. Tas var aizņemt kādu laiku. Tu saņemsi e-pasta ziņojumu, ja Tavs pieteikums tiks apstiprināts.
       redirecting_to: Tavs konts ir neaktīvs, jo pašlaik tas tiek novirzīts uz %{acct}.
       self_destruct: Tā kā %{domain} tiek slēgts, tu iegūsi tikai ierobežotu piekļuvi savam kontam.
       view_strikes: Skati iepriekšējos brīdinājumus par savu kontu
     too_fast: Veidlapa ir iesniegta pārāk ātri, mēģini vēlreiz.
     use_security_key: Lietot drošības atslēgu
-    user_agreement_html: Es esmu izlasījis un piekrītu <a href="%{terms_of_service_path}" target="_blank">pakalpojuma izmantošanas noteikumiem</a> un <a href="%{privacy_policy_path}" target="_blank">privātuma nosacījumiem</a>
-    user_privacy_agreement_html: Es izlasīju un piekrītu <a href="%{privacy_policy_path}" target="_blank">privātuma pamatnostādnēm</a>
   author_attribution:
     example_title: Parauga teksts
-    hint_html: Vai Tu Raksti ziņu vai emuāra rakstus ārpus Mastodon? Pārraugi apliecinājumus, kad raksti tiek kopīgoti Mastodon.
-    instructions: 'Jāpārliecinās, ka šis kods ir raksta HTML:'
     more_from_html: Vairāk no %{name}
     s_blog: "%{name} emuāri"
-    then_instructions: Tad jāpievieno publicējuma domēna vārds zemāk esošajā laukā.
     title: Autora attiecinājums
   challenge:
     confirm: Turpināt
@@ -1296,7 +1187,7 @@ lv:
     confirm_password: Ievadi savu pašreizējo paroli, lai pārbaudītu savu identitāti
     confirm_username: Ievadi savu lietotājvārdu, lai apstiprinātu procedūru
     proceed: Dzēst kontu
-    success_msg: Tavs konts tika sekmīgi izdzēsts
+    success_msg: Tavs konts tika veiksmīgi dzēsts
     warning:
       before: 'Pirms turpināšanas lūgums uzmanīgi izlasīt šīs piezīmes:'
       caches: Citu serveru kešatmiņā saglabātais saturs var saglabāties
@@ -1312,10 +1203,10 @@ lv:
     strikes:
       action_taken: Veiktā darbība
       appeal: Apelācija
-      appeal_approved: Šis brīdinājums tika sekmīgi pārsūdzēts un vairs nav spēkā
+      appeal_approved: Šis brīdinājums ir veiksmīgi pārsūdzēts un vairs nav spēkā
       appeal_rejected: Apelācija ir noraidīta
       appeal_submitted_at: Apelācija iesniegta
-      appealed_msg: Tava pārsūdzība ir iesniegta. Ja tā tiks apstiprināta, Tev tiks paziņots.
+      appealed_msg: Jūsu apelācija ir iesniegta. Ja tā tiks apstiprināta, jums tiks paziņots.
       appeals:
         submit: Iesniegt apelāciju
       approve_appeal: Apstiprināt apelāciju
@@ -1330,14 +1221,14 @@ lv:
       title_actions:
         delete_statuses: Ziņas noņemšana
         disable: Konta iesaldēšana
-        mark_statuses_as_sensitive: Ierakstu atzīmēšana kā jūtīgus
+        mark_statuses_as_sensitive: Ziņu atzīmēšana kā sensitīvas
         none: Brīdinājums
-        sensitive: Konta atzīmēšana kā jūtīgu
+        sensitive: Konta atzīmēšana kā sensitīvs
         silence: Konta ierobežošana
         suspend: Konta apturēšana
-      your_appeal_approved: Tava pārsūdzība tika apstiprināta
+      your_appeal_approved: Jūsu apelācija ir apstiprināta
       your_appeal_pending: Jūs esat iesniedzis apelāciju
-      your_appeal_rejected: Tava pārsūdzība tika noraidīta
+      your_appeal_rejected: Jūsu apelācija ir noraidīta
   edit_profile:
     basic_information: Pamata informācija
     hint_html: "<strong>Pielāgo, ko cilvēki redz Tavā publiskajā profilā un blakus Taviem ierakstiem.</strong> Ir lielāka iespējamība, ka citi clivēki sekos Tev un mijiedarbosies ar Tevi, ja Tev ir aizpildīts profils un profila attēls."
@@ -1436,7 +1327,7 @@ lv:
       other: Atlasīti visi <strong>%{count}</strong> vienumi, kas atbilst tavam meklēšanas vaicājumam.
       zero: Atlasīts <strong>%{count}</strong> vienumu, kas atbilst tavam meklēšanas vaicājumam.
     cancel: Atcelt
-    changes_saved_msg: Izmaiņas sekmīgi saglabātas.
+    changes_saved_msg: Izmaiņas veiksmīgi saglabātas!
     confirm: Apstiprināt
     copy: Kopēt
     delete: Dzēst
@@ -1469,55 +1360,19 @@ lv:
       overwrite: Pārrakstīt
       overwrite_long: Nomainīt pašreizējos ierakstus ar jauniem
     overwrite_preambles:
-      blocking_html:
-        one: Tu gatavojies <strong>aizstāt savu lieguma sarakstu</strong> ar līdz <strong>%{count} kontam</strong> no <strong>%{filename}</strong>.
-        other: Tu gatavojies <strong>aizstāt savu lieguma sarakstu</strong> ar līdz <strong>%{count} kontiem</strong> no <strong>%{filename}</strong>.
-        zero: Tu gatavojies <strong>aizstāt savu lieguma sarakstu</strong> ar līdz <strong>%{count} kontiem</strong> no <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Tu gatavojies <strong>aizstāt savas grāmatzīmes</strong> ar līdz <strong>%{count} ierakstam</strong> no <strong>%{filename}</strong>.
-        other: Tu gatavojies <strong>aizstāt savas grāmatzīmes</strong> ar līdz <strong>%{count} ierakstiem</strong> no <strong>%{filename}</strong>.
-        zero: Tu gatavojies <strong>aizstāt savas grāmatzīmes</strong> ar līdz <strong>%{count} ierakstiem</strong> no <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Tu gatavojies <strong>aizstāt savu liegto domēnu sarakstu</strong> ar līdz <strong>%{count} domēnam</strong> no <strong>%{filename}</strong>.
-        other: Tu gatavojies <strong>aizstāt savu liegto domēnu sarakstu</strong> ar līdz <strong>%{count} domēniem</strong> no <strong>%{filename}</strong>.
-        zero: Tu gatavojies <strong>aizstāt savu liegto domēnu sarakstu</strong> ar līdz <strong>%{count} domēniem</strong> no <strong>%{filename}</strong>.
-      following_html:
-        one: Tu gatavojies <strong>sekot</strong> līdz <strong>%{count} kontam</strong> no <strong>%{filename}</strong> un <strong>pārtrauksi sekot citiem</strong>.
-        other: Tu gatavojies <strong>sekot</strong> līdz <strong>%{count} kontiem</strong> no <strong>%{filename}</strong> un <strong>pārtrauksi sekot citiem</strong>.
-        zero: Tu gatavojies <strong>sekot</strong> līdz <strong>%{count} kontiem</strong> no <strong>%{filename}</strong> un <strong>pārtrauksi sekot citiem</strong>.
-      lists_html:
-        one: Tu gatavojies <strong>aizstāt savus sarakstus</strong> ar <strong>%{filename}</strong> saturu. Līdz <strong>%{count} kontam</strong> tiks pievienoti jaunajos sarakstos.
-        other: Tu gatavojies <strong>aizstāt savus sarakstus</strong> ar <strong>%{filename}</strong> saturu. Līdz <strong>%{count} kontiem</strong> tiks pievienoti jaunajos sarakstos.
-        zero: Tu gatavojies <strong>aizstāt savus sarakstus</strong> ar <strong>%{filename}</strong> saturu. Līdz <strong>%{count} kontiem</strong> tiks pievienoti jaunajos sarakstos.
-      muting_html:
-        one: Tu gatavojies <strong>aizstāt savu apklusināto kontu sarakstu</strong> ar līdz <strong>%{count} kontam</strong> no <strong>%{filename}</strong>.
-        other: Tu gatavojies <strong>aizstāt savu apklusināto kontu sarakstu</strong> ar līdz <strong>%{count} kontiem</strong> no <strong>%{filename}</strong>.
-        zero: Tu gatavojies <strong>aizstāt savu apklusināto kontu sarakstu</strong> ar līdz <strong>%{count} kontiem</strong> no <strong>%{filename}</strong>.
+      blocking_html: Tu gatavojies <strong>aizstāt savu bloķēto sarakstu</strong> ar līdz pat <strong>%{total_items} kontiem</strong> no <strong>%{filename}</strong>.
+      bookmarks_html: Tu gatavojies <strong>aizstāt savas bloķētās izlases</strong> ar līdz pat <strong>%{total_items} ziņām</strong> no <strong>%{filename}</strong>.
+      domain_blocking_html: Tu gatavojies <strong>aizstāt savu bloķēto domēnu sarakstu</strong> ar līdz pat <strong>%{total_items} domēniem</strong> no <strong>%{filename}</strong>.
+      following_html: Tu gatavojies <strong>sekot</strong> līdz pat <strong>%{total_items} kontiem</strong> no <strong>%{filename}</strong> un <strong>pārtrauksi sekot citiem</strong>.
+      lists_html: Tu gatavojies <strong>aizstāt savus sarakstus</strong> ar <strong>%{filename}</strong> saturu. Līdz <strong>%{total_items} kontiem</strong> tiks pievienoti jauni saraksti.
+      muting_html: Tu gatavojies <strong>aizstāt savu noklusināto kontu sarakstu</strong> ar līdz pat <strong>%{total_items} kontiem</strong> no <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Tu gatavojies <strong>liegt</strong> līdz <strong>%{count} kontam</strong> no <strong>%{filename}</strong>.
-        other: Tu gatavojies <strong>liegt</strong> līdz <strong>%{count} kontiem</strong> no <strong>%{filename}</strong>.
-        zero: Tu gatavojies <strong>liegt</strong> līdz <strong>%{count} kontiem</strong> no <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Tu gatavojies pievienot līdz <strong>%{count} ierakstam</strong> no <strong>%{filename}</strong> savām <strong>grāmatzīmēm</strong>.
-        other: Tu gatavojies pievienot līdz <strong>%{count} ierakstiem</strong> no <strong>%{filename}</strong> savām <strong>grāmatzīmēm</strong>.
-        zero: Tu gatavojies pievienot līdz <strong>%{count} ierakstiem</strong> no <strong>%{filename}</strong> savām <strong>grāmatzīmēm</strong>.
-      domain_blocking_html:
-        one: Tu gatavojies <strong>liegt</strong> līdz <strong>%{count} domēnam</strong> no <strong>%{filename}</strong>.
-        other: Tu gatavojies <strong>liegt</strong> līdz <strong>%{count} domēniem</strong> no <strong>%{filename}</strong>.
-        zero: Tu gatavojies <strong>liegt</strong> līdz <strong>%{count} domēniem</strong> no <strong>%{filename}</strong>.
-      following_html:
-        one: Tu gatavojies <strong>sekot</strong> līdz <strong>%{count} kontam</strong> no <strong>%{filename}</strong>.
-        other: Tu gatavojies <strong>sekot</strong> līdz <strong>%{count} kontiem</strong> no <strong>%{filename}</strong>.
-        zero: Tu gatavojies <strong>sekot</strong> līdz <strong>%{count} kontiem</strong> no <strong>%{filename}</strong>.
-      lists_html:
-        one: Tu gatavojies pievienot līdz pat <strong>%{count} kontam</strong> no <strong>%{filename}</strong> saviem <strong>sarakstiem</strong>. Tiks izveidoti jauni saraksti, ja nav saraksta, kurā pievienot.
-        other: Tu gatavojies pievienot līdz pat <strong>%{count} kontiem</strong> no <strong>%{filename}</strong> saviem <strong>sarakstiem</strong>. Tiks izveidoti jauni saraksti, ja nav saraksta, kurā pievienot.
-        zero: Tu gatavojies pievienot līdz pat <strong>%{count} kontiem</strong> no <strong>%{filename}</strong> saviem <strong>sarakstiem</strong>. Tiks izveidoti jauni saraksti, ja nav saraksta, kurā pievienot.
-      muting_html:
-        one: Tu gatavojies <strong>apklusināt</strong> līdz pat <strong>%{count} kontam</strong> no <strong>%{filename}</strong>.
-        other: Tu gatavojies <strong>apklusināt</strong> līdz pat <strong>%{count} kontiem</strong> no <strong>%{filename}</strong>.
-        zero: Tu gatavojies <strong>apklusināt</strong> līdz pat <strong>%{count} kontiem</strong> no <strong>%{filename}</strong>.
+      blocking_html: Tu gatavojies <strong>bloķēt</strong> līdz pat <strong>%{total_items} kontiem</strong> no <strong>%{filename}</strong>.
+      bookmarks_html: Tu gatavojies pievienot līdz pat <strong>%{total_items} ziņām</strong> no <strong>%{filename}</strong> savām <strong>grāmatzīmēm</strong>.
+      domain_blocking_html: Tu gatavojies <strong>bloķēt</strong> līdz pat <strong>%{total_items} domēniem</strong> no <strong>%{filename}</strong>.
+      following_html: Tu gatavojies <strong>sekot</strong> līdz pat <strong>%{total_items} kontiem</strong> no <strong>%{filename}</strong>.
+      lists_html: Tu gatavojies pievienot līdz pat <strong>%{total_items} kontiem</strong> no <strong>%{filename}</strong> saviem <strong>sarakstiem</strong>. Jauni saraksti tiks izveidoti, ja nav saraksta, ko pievienot.
+      muting_html: Tu gatavojies <strong>noklusināt</strong> līdz pat <strong>%{total_items} kontiem</strong> no <strong>%{filename}</strong>.
     preface: Tu vari ievietot datus, kurus esi izguvis no cita servera, kā, piemēram, cilvēku sarakstu, kuriem Tu seko vai kurus bloķē.
     recent_imports: Nesen importēts
     states:
@@ -1526,7 +1381,7 @@ lv:
       scheduled: Ieplānots
       unconfirmed: Neapstiprināti
     status: Statuss
-    success: Tavi dati tika sekmīgi augšupielādēti un tiks apstrādāti noteiktajā laikā
+    success: Tavi dati tika veiksmīgi augšupielādēti un tiks apstrādāti noteiktajā laikā
     time_started: Sākuma laiks
     titles:
       blocking: Importē bloķētos kontus
@@ -1558,7 +1413,7 @@ lv:
       '604800': 1 nedēļa
       '86400': 1 diena
     expires_in_prompt: Nekad
-    generate: Izveidot uzaicinājuma saiti
+    generate: Ģenerēt uzaicinājuma saiti
     invalid: Šis uzaicinājums nav derīgs
     invited_by: 'Tevi uzaicināja:'
     max_uses:
@@ -1566,7 +1421,7 @@ lv:
       other: "%{count} lietojumi"
       zero: "%{count} lietojumu"
     max_uses_prompt: Nav ierobežojuma
-    prompt: Jāizveido un jākopīgo saites ar citiem, lai nodrošinātu piekļuvi šim serverim
+    prompt: Izveido un kopīgo saites ar citiem, lai piešķirtu piekļuvi šim serverim
     table:
       expires_at: Beidzas
       uses: Lieto
@@ -1583,13 +1438,12 @@ lv:
     description_html: Ja pamani darbības, kuras neatpazīsti, jāapsver iespēja nomainīt savu paroli un iespējot divpakāpju autentifikāciju.
     empty: Nav pieejama autentifikācijas vēsture
     failed_sign_in_html: Neizdevies pieteikšanās mēģinājums ar %{method} no %{ip} (%{browser})
-    successful_sign_in_html: Sekmīga pieteikšanās ar %{method} no %{ip} (%{browser})
+    successful_sign_in_html: Veiksmīga pieteikšanās ar %{method} no %{ip} (%{browser})
     title: Autentifikācijas vēsture
   mail_subscriptions:
     unsubscribe:
       action: Jā, atcelt abonēšanu
       complete: Anulēts
-      confirmation_html: Vai tiešām atteikties no %{type} saņemšanas savā e-pasta adresē %{email} par %{domain} esošo Mastodon? Vienmēr var abonēt no jauna savos <a href="%{settings_path}">e-pasta paziņojumu iestatījumos</a>.
       emails:
         notification_emails:
           favourite: izlases paziņojumu e-pasta ziņojumi
@@ -1597,13 +1451,10 @@ lv:
           follow_request: sekošanas pieprasījumu e-pasta ziņojumi
           mention: pieminēšanas paziņojumu e-pasta ziņojumi
           reblog: pastiprinājumu paziņojumu e-pasta ziņojumi
-      resubscribe_html: Ja abonements tika atcelts kļūdas dēļ, abonēt no jauna var savos <a href="%{settings_path}">e-pasta paziņojumu iestatījumos</a>.
-      success_html: Tu vairs savā e-pasta adresē %{email} nesaņemsi %{type} par %{domain} esošo Mastodon.
       title: Atcelt abonēšanu
   media_attachments:
     validations:
       images_and_video: Nevar pievienot videoklipu tādai ziņai, kura jau satur attēlus
-      not_found: Informācijas nesējs %{ids} nav atrasts vai jau pievienots citam ierakstam
       not_ready: Nevar pievienot failus, kuru apstrāde nav pabeigta. Pēc brīža mēģini vēlreiz!
       too_many: Nevar pievienot vairāk kā 4 failus
   migrations:
@@ -1681,7 +1532,6 @@ lv:
       subject: "%{name} laboja ierakstu"
   notifications:
     administration_emails: Pārvaldītāju e-pasta paziņojumi
-    email_events: E-pasta paziņojumu notikumi
     email_events_hint: 'Atlasi notikumus, par kuriem vēlies saņemt paziņojumus:'
   number:
     human:
@@ -1694,10 +1544,10 @@ lv:
           thousand: K
           trillion: T
   otp_authentication:
-    code_hint: Jāievada autentificētājlietotnes izveidotais kods, lai apstiprinātu
-    description_html: Jā iespējo <strong>divpakāpju autentifikāciju</strong> ar autentificētājlietotni, pieteikšanās laikā būs nepieciešams tālrunis, kurā tiks izveidoti ievadāmie kodi.
+    code_hint: Lai apstiprinātu, ievadi autentifikācijas lietotnes ģenerēto kodu
+    description_html: Jā iespējo <strong>divpakāpju autentifikāciju</strong> ar autentificēšanas lietotni, pieteikšanās laikā būs nepieciešams tālrunis, kurā tiks izveidoti ievadāmie kodi.
     enable: Iespējot
-    instructions_html: "<strong>Šis kvadrātods savā tālrunī jānolasa ar Google Authenticator vai līdzīgu TOTP lietotni</strong>. No šī brīža šī lietotne veidos ciparvienības, kas būs jāievada piesakoties."
+    instructions_html: "<strong>Skenē šo QR kodu Google Authenticator vai līdzīgā TOTP lietotnē savā tālrunī</strong>. No šī brīža šī lietotne ģenerēs marķierus, kas tev būs jāievada, piesakoties."
     manual_instructions: 'Ja nevari noskenēt QR kodu un tas ir jāievada manuāli, šeit ir noslēpums vienkāršā tekstā:'
     setup: Iestatīt
     wrong_code: Ievadītais kods bija nederīgs. Vai servera un ierīces laiks ir pareizs?
@@ -1738,9 +1588,6 @@ lv:
     errors:
       limit_reached: Sasniegts dažādu reakciju limits
       unrecognized_emoji: nav atpazīta emocijzīme
-  redirects:
-    prompt: Ja uzticies šai saitei, jāklikšķina uz tās, lai turpinātu.
-    title: Tu atstāj %{instance}.
   relationships:
     activity: Konta aktivitāte
     confirm_follow_selected_followers: Vai tiešām vēlies sekot atlasītajiem sekotājiem?
@@ -1775,7 +1622,7 @@ lv:
   scheduled_statuses:
     over_daily_limit: Tu esi pārsniedzis šodien ieplānoto %{limit} ziņu ierobežojumu
     over_total_limit: Tu esi pārsniedzis ieplānoto %{limit} ziņu ierobežojumu
-    too_soon: datumam jābūt nākotnē
+    too_soon: Ieplānotajam datumam ir jābūt nākotnē
   self_destruct:
     lead_html: Diemžēl domēns <strong>%{domain}</strong> tiek neatgriezeniski slēgts. Ja tev tur bija konts, tu nevarēsi turpināt to lietot, taču joprojām vari pieprasīt savu datu kopiju.
     title: Šis serveris tiek slēgts
@@ -1822,7 +1669,7 @@ lv:
       windows_mobile: Windows Mobile
       windows_phone: Windows Phone
     revoke: Atsaukt
-    revoke_success: Sesija sekmīgi atsaukta
+    revoke_success: Sesija veiksmīgi atsaukta
     title: Sesijas
     view_authentication_history: Skatīt sava konta autentifikācijas vēsturi
   settings:
@@ -1852,13 +1699,9 @@ lv:
   severed_relationships:
     download: Lejupielādēt (%{count})
     event_type:
-      account_suspension: Konta apturēšana (%{target_name})
-      domain_block: Servera apturēšana (%{target_name})
       user_domain_block: Jūs bloķējāt %{target_name}
     lost_followers: Zaudētie sekotāji
     lost_follows: Zaudētie sekojumi
-    preamble: Tu vari zaudēt sekojamos un sekotājus, kad liedz domēnu vai kad satura pārraudzītāji izlemj apturēt attālu serveri. Kad t as notiek, būs iespējams lejupielādēt sarakstus ar pārtrauktajām saiknēm, kurus tad var izpētīt un, iespējams, ievietot citā serverī.
-    purged: Informāciju par šo serveri notīrīja Tava servera pārvaldītāji.
     type: Notikums
   statuses:
     attached:
@@ -1887,10 +1730,10 @@ lv:
       in_reply_not_found: Šķiet, ka ziņa, uz kuru tu mēģini atbildēt, nepastāv.
     over_character_limit: pārsniegts %{max} rakstzīmju ierobežojums
     pin_errors:
-      direct: Ierakstus, kas ir redzami tikai pieminētajiem lietotājiem, nevar piespraust
-      limit: Jau ir piesprausts lielākais iespējamais ierakstu skaits
+      direct: Ziņojumus, kas ir redzami tikai minētajiem lietotājiem, nevar piespraust
+      limit: Tu jau esi piespraudis maksimālo ziņu skaitu
       ownership: Kāda cita ierakstu nevar piespraust
-      reblog: Pastiprinātu ierakstu nevar piespraust
+      reblog: Izceltu ierakstu nevar piespraust
     title: "%{name}: “%{quote}”"
     visibilities:
       direct: Tiešs
@@ -1913,8 +1756,8 @@ lv:
     keep_direct_hint: Nedzēš nevienu tavu tiešo ziņojumu
     keep_media: Saglabāt ziņas ar multivides pielikumiem
     keep_media_hint: Neizdzēš nevienu no tavām ziņām, kurām ir multivides pielikumi
-    keep_pinned: Paturēt piespraustos ierakstus
-    keep_pinned_hint: Neizdzēš nevienu no Tevis piespraustajiem ierakstiem
+    keep_pinned: Saglabāt piespraustās ziņas
+    keep_pinned_hint: Nedzēš nevienu tavis piesprausto ziņu
     keep_polls: Saglabāt aptaujas
     keep_polls_hint: Nedzēš nevienu tavu aptauju
     keep_self_bookmark: Saglabāt ziņas, kuras esi pievienojis grāmatzīmēm
@@ -1932,23 +1775,20 @@ lv:
       '7889238': 3 mēneši
     min_age_label: Vecuma slieksnis
     min_favs: Saglabāt ziņas izlsasē vismaz
-    min_favs_hint: Neizdzēš nevienu no Taviem ierakstiem, kas ir pievienoti šādā daudzumā izlašu. Atstāt tukšu, lai izdzēstu ierakstus neatkarīgi no tā, cik izlasēs tie ir pievienoti
+    min_favs_hint: Nedzēš nevienu jūsu ziņu, kas ir saņēmusi vismaz tik daudz izcēlumu. Atstājiet tukšu, lai dzēstu ziņas neatkarīgi no to izcēlumu skaita
     min_reblogs: Saglabāt ziņas izceltas vismaz
     min_reblogs_hint: Neizdzēš nevienu no tavām ziņām, kas ir izceltas vismaz tik reižu. Atstāj tukšu, lai dzēstu ziņas neatkarīgi no to izcēlumu skaita
   stream_entries:
-    sensitive_content: Jūtīgs saturs
+    sensitive_content: Sensitīvs saturs
   strikes:
     errors:
       too_late: Brīdinājuma apstrīdēšanas laiks ir nokavēts
   tags:
     does_not_match_previous_name: nesakrīt ar iepriekšējo nosaukumu
-  terms_of_service:
-    title: Pakalpojuma izmantošanas noteikumi
   themes:
     contrast: Mastodon (Augsts kontrasts)
     default: Mastodon (Tumšs)
     mastodon-light: Mastodon (Gaišs)
-    system: Automātisks (ievēro sistēmas izskatu)
   time:
     formats:
       default: "%b %d, %Y, %H:%M"
@@ -1962,45 +1802,31 @@ lv:
   two_factor_authentication:
     add: Pievienot
     disable: Atspējot 2FA
-    disabled_success: Divpakāpju autentificēšanās sekmīgi atspējota
+    disabled_success: Divpakāpju autentifikācija veiksmīgi atspējota
     edit: Labot
     enabled: Divpakāpju autentifikācija ir iespējota
-    enabled_success: Divpakāpju autentificēšanās sekmīgi iespējota
-    generate_recovery_codes: Izveidot atkopes kodus
-    lost_recovery_codes: Atkopes kodi ļauj atgūt piekļuvi savam kontam, ja ir pazaudēts tālrunis. Ja ir pazaudēti atkopes kodi, tos var izveidot šeit. Iepriekšējie atkopšanas kodi kļūs nederīgi.
+    enabled_success: Divpakāpju autentifikācija veiksmīgi iespējota
+    generate_recovery_codes: Ģenerēt atkopšanas kodus
+    lost_recovery_codes: Atkopšanas kodi ļauj atgūt piekļuvi tavam kontam, ja pazaudē tālruni. Ja esi pazaudējis atkopšanas kodus, tu vari tos ģenerēt šeit. Tavi vecie atkopšanas kodi tiks anulēti.
     methods: Divpakāpju veidi
     otp: Autentifikātora lietotne
-    recovery_codes: Veidot atkopes kodu rezerves kopijas
-    recovery_codes_regenerated: Atkopes kodi sekmīgi izveidoti no jauna
-    recovery_instructions_html: Ja kādreiz zaudēsi piekļuvi savam tālrunim, vari izmantot kādu no zemāk norādītajiem atkopes kodiem, lai atgūtu piekļuvi savam kontam. <strong>Atkpes kodi jātur drošībā</strong>. Piemēram, tos var izdrukāt un glabāt kopā ar citiem svarīgiem dokumentiem.
+    recovery_codes: Veidot atkopšanas kodu rezerves kopijas
+    recovery_codes_regenerated: Atkopšanas kodi veiksmīgi atjaunoti
+    recovery_instructions_html: Ja kādreiz zaudēsi piekļuvi savam tālrunim, vari izmantot kādu no tālāk norādītajiem atkopšanas kodiem, lai atgūtu piekļuvi savam kontam. <strong>Glabā atkopšanas kodus drošībā</strong>. Piemēram, tu vari tos izdrukāt un uzglabāt kopā ar citiem svarīgiem dokumentiem.
     webauthn: Drošības atslēgas
   user_mailer:
-    announcement_published:
-      description: "%{domain} pārvaldītāji veic paziņojumu:"
-      subject: Pakalpojuma paziņojums
-      title: "%{domain} pakalpojuma paziņojums"
     appeal_approved:
       action: Konta iestatījumi
-      explanation: Pārsūdzība par brīdinājumu Tavam kontam %{strike_date}, ko iesniedzi %{appeal_date}, ir apstiprināta. Tavs konts atkal ir labā stāvoklī.
-      subject: Tava %{date} iesniegtā pārsūdzība tika apstiprināta
-      subtitle: Tavs konts atkal ir labā stāvoklī.
+      explanation: Apelācija par brīdinājumu jūsu kontam %{strike_date}, ko iesniedzāt %{appeal_date}, ir apstiprināta. Jūsu konts atkal ir labā stāvoklī.
+      subject: Jūsu %{date} apelācija ir apstiprināta
       title: Apelācija apstiprināta
     appeal_rejected:
-      explanation: Pārsūdzība par brīdinājumu Tavam kontam %{strike_date}, ko iesniedzi %{appeal_date}, tika noraidīta.
-      subject: Tava %{date} iesniegta pārsūdzība tika noraidīta
-      subtitle: Tava pārsūdzība tika noraidīta.
+      explanation: Apelācija par brīdinājumu jūsu kontam %{strike_date}, ko iesniedzāt %{appeal_date}, ir noraidīta.
+      subject: Jūsu %{date} apelācija ir noraidīta
       title: Apelācija noraidīta
     backup_ready:
-      explanation: Tu pieprasīji pilnu sava Mastodon konta rezerves kopiju.
-      extra: Tā tagad ir gatava lejupielādei.
       subject: Tavs arhīvs ir gatavs lejupielādei
       title: Arhīva līdzņemšana
-    failed_2fa:
-      details: 'Šeit ir informācija par pieteikšanās mēģinājumu:'
-      explanation: Kāds mēģināja pieteikties Tavā kontā, bet norādīja nederīgu otro autentificēšanās soli.
-      further_actions_html: Ja tas nebiji Tu, mēs iesakām nekavējoties %{action}, jo var būt noticis drošības pārkāpums.
-      subject: Otrās pakāpes autentificēšanās atteice
-      title: Neizdevās otrās pakāpes autentificēšanās
     suspicious_sign_in:
       change_password: mainīt paroli
       details: 'Šeit ir pieteikšanās izvērsums:'
@@ -2008,14 +1834,6 @@ lv:
       further_actions_html: Ja tas nebiji tu, iesakām nekavējoties %{action} un iespējot divu faktoru autentifikāciju, lai tavs konts būtu drošībā.
       subject: Tavam kontam ir piekļūts no jaunas IP adreses
       title: Jauna pieteikšanās
-    terms_of_service_changed:
-      agreement: Ar %{domain} izmantošanas tuprināšanu tiek piekrists šiem noteikumiem. Ja ir iebildumi pret atjauninātajiem noteikumiem, savu piekrišanu var atcelt jebkurā laikā ar sava konta izdzēšanu.
-      changelog: 'Šeit īsumā ir aprakstīts, ko šis atjauninājums nozīmē:'
-      description: 'Šis e-pasta ziņojums tika saņemts, jo mēs veicam dažas izmaiņas savos pakalpojuma izmantošanas noteikumos %{domain}. Šie atjauninājumi stāsies spēkā %{date}. Mēs aicinām pārskatīt pilnus atjauninātos noteikumus šeit:'
-      sign_off: "%{domain} komanda"
-      subject: Mūsu pakalpojuma izmantošanas noteikumu atjauninājumi
-      subtitle: Mainās %{domain} pakalpojuma izmantošanas noteikumi
-      title: Svarīgs atjauninājums
     warning:
       appeal: Iesniegt apelāciju
       appeal_description: Ja uzskatāt, ka tā ir kļūda, varat iesniegt apelāciju %{instance} darbiniekiem.
@@ -2023,10 +1841,10 @@ lv:
         spam: Spams
         violation: Saturs pārkāpj šādas kopienas pamatnostādnes
       explanation:
-        delete_statuses: Tika noteikts, ka daži no Taviem ierakstiem pārkāpj vienu vai vairākas kopienas vadlīnijas, tādējādi tos noņēma %{instance} satura pārraudzītāji.
+        delete_statuses: Tika konstatēts, ka dažas no tavām ziņām pārkāpj vienu vai vairākas kopienas vadlīnijas, un rezultātā %{instance} moderatori tās noņēma.
         disable: Tu vairs nevari izmantot savu kontu, taču tavs profils un citi dati paliek neskarti. Tu vari pieprasīt savu datu dublējumu, mainīt konta iestatījumus vai dzēst kontu.
-        mark_statuses_as_sensitive: "%{instance} satura pārraudzītāji dažus no Taviem ierakstiem ir atzīmējuši kā jūtīgus. Tas nozīmē, ka cilvēkiem būs jāpiesit ierakstos esošajiem informāijas nesējiem, pirms tiek attēlots to priekšskatījums. Tu pats vari atzīmēt informācijas nesēju kā jūtīgu, kad nākotnē tādu ievietosi."
-        sensitive: Turpmāk visi augšupielādētās informācijas nesēju datnes tiks atzīmētas kā jūtīgas un paslēptas aiz klikšķināma brīdinājuma.
+        mark_statuses_as_sensitive: "%{instance} moderatori dažus no Taviem ierakstiem ir atzīmējuši kā jutīgus. Tas nozīmē, ka cilvēkiem būs jāpiesit ierakstos esošajiem informāijas nesējiem, pirms tiek attēlots priekšskatījums. Tu arī pats vari atzīmēt informācijas nesēju kā jutīgu, kad nākotnē tādu ievietosi."
+        sensitive: No šī brīža visi augšupielādētie multivides faili tiks atzīmēti kā sensitīvi un paslēpti aiz klikšķa brīdinājuma.
         silence: Tu joprojām vari izmantot savu kontu, taču tikai tie cilvēki, kuri jau tev seko, redzēs tavas ziņas šajā serverī, un tev var tikt liegtas dažādas atklāšanas funkcijas. Tomēr citi joprojām var tev manuāli sekot.
         suspend: Tu vairs nevari izmantot savu kontu, un tavs profils un citi dati vairs nav pieejami. Tu joprojām vari pieteikties, lai pieprasītu savu datu dublēšanu, līdz dati tiks pilnībā noņemti aptuveni 30 dienu laikā, taču mēs saglabāsim dažus pamata datus, lai neļautu tev izvairīties no apturēšanas.
       reason: 'Iemesls:'
@@ -2034,17 +1852,17 @@ lv:
       subject:
         delete_statuses: Tavas ziņas %{acct} tika noņemtas
         disable: Tavs konts %{acct} tika iesaldēts
-        mark_statuses_as_sensitive: Tavi ieraksti %{acct} ir atzīmēti kā jūtīgi
+        mark_statuses_as_sensitive: Tavas ziņas vietnē %{acct} ir atzīmētas kā sensitīvas
         none: Brīdinājums par %{acct}
-        sensitive: Tavi ieraksti %{acct} turpmāk tiks atzīmēti kā jūtīgi
+        sensitive: Tavas ziņas vietnē %{acct} turpmāk tiks atzīmētas kā sensitīvas
         silence: Tavs konts %{acct} tika ierobežots
         suspend: Tava konta %{acct} darbība ir apturēta
       title:
         delete_statuses: Izdzēstās ziņas
         disable: Konts iesaldēts
-        mark_statuses_as_sensitive: Ieraksti atzīmēti kā jūtīgi
+        mark_statuses_as_sensitive: Ziņas ir atzīmēts kā sensitīvas
         none: Brīdinājums
-        sensitive: Konts ir atzīmēts kā jūtīgs
+        sensitive: Konts ir atzīmēts kā sensitīvs
         silence: Konts ierobežots
         suspend: Konts apturēts
     welcome:
@@ -2101,13 +1919,13 @@ lv:
     add: Pievienot jaunu drošības atslēgu
     create:
       error: Pievienojot drošības atslēgu, radās problēma. Lūdzu mēģini vēlreiz.
-      success: Tava drošības atslēga tika sekmīgi pievienota.
+      success: Tava drošības atslēga tika veiksmīgi pievienota.
     delete: Dzēst
     delete_confirmation: Vai tiešām vēlies dzēst šo drošības atslēgu?
     description_html: Ja iespējosi <strong>drošības atslēgas autentifikāciju</strong>, piesakoties būs jāizmanto viena no tavām drošības atslēgām.
     destroy:
       error: Dzēšot tavu drošības atslēgu, radās problēma. Lūdzu mēģini vēlreiz.
-      success: Tava drošības atslēga tika sekmīgi izdēsta.
+      success: Tava drošības atslēga tika veiksmīgi izdēsta.
     invalid_credential: Nederīga drošības atslēga
     nickname_hint: Ievadi savas jaunās drošības atslēgas segvārdu
     not_enabled: Tu vel neesi iespējojis WebAuthn
diff --git a/config/locales/ms.yml b/config/locales/ms.yml
index 68f4e872b6..e0ebc2b175 100644
--- a/config/locales/ms.yml
+++ b/config/locales/ms.yml
@@ -28,7 +28,6 @@ ms:
       created_msg: Catatan penyederhanaan telah berjaya dicipta!
       destroyed_msg: Catatan penyederhanaan telah berjaya dipadam!
     accounts:
-      add_email_domain_block: Sekat domain e-mel
       approve: Luluskan
       approved_msg: Berjaya meluluskan permohonan pendaftaran %{username}
       are_you_sure: Adakah anda pasti?
@@ -147,7 +146,7 @@ ms:
       suspension_irreversible: Data akaun ini telah dipadam secara kekal. Anda boleh nyahgantungkannya untuk membuatkan akaun ini boleh digunakan semula tetapi data lama tidak akan diperolehi.
       suspension_reversible_hint_html: Akaun ini telah digantung, dan datanya akan dibuang pada %{date}. Sebelum tarikh itu, akaun ini boleh diperoleh semula tanpa kesan buruk. Jika anda mahu memadamkan kesemua data akaun ini serta-merta, anda boleh melakukannya di bawah.
       title: Akaun
-      unblock_email: Nyahsekat alamat e-mel
+      unblock_email: Menyahsekat alamat e-mel
       unblocked_email_msg: Alamat e-mel %{username} berjaya dinyahsekat
       unconfirmed_email: E-mel belum disahkan
       undo_sensitized: Nyahtanda sensitif
@@ -170,21 +169,17 @@ ms:
         confirm_user: Sahkan Pengguna
         create_account_warning: Cipta Amaran
         create_announcement: Cipta Pengumuman
-        create_canonical_email_block: Cipta Penyekatan E-mel
         create_custom_emoji: Cipta Emoji Tersendiri
         create_domain_allow: Cipta Pelepasan Domain
         create_domain_block: Cipta Penyekatan Domain
-        create_email_domain_block: Cipta Penyekatan Domain E-mel
         create_ip_block: Cipta peraturan alamat IP
         create_unavailable_domain: Cipta Domain Tidak Tersedia
         create_user_role: Cipta Peranan
         demote_user: Turunkan Taraf Pengguna
         destroy_announcement: Padam Pengumuman
-        destroy_canonical_email_block: Padam Penyekatan E-mel
         destroy_custom_emoji: Padam Emoji Tersendiri
         destroy_domain_allow: Padam Pelepasan Domain
         destroy_domain_block: Padam Penyekatan Domain
-        destroy_email_domain_block: Padam Penyekatan Domain E-mel
         destroy_instance: Padamkan Domain
         destroy_ip_block: Padam peraturan alamat IP
         destroy_status: Padam Hantaran
@@ -208,7 +203,7 @@ ms:
         silence_account: Diamkan Akaun
         suspend_account: Gantungkan Akaun
         unassigned_report: Menyahtugaskan Laporan
-        unblock_email_account: Nyahsekat alamat e-mel
+        unblock_email_account: Menyahsekat alamat e-mel
         unsensitive_account: Nyahtanda media di akaun anda sebagai sensitif
         unsilence_account: Nyahdiamkan Akaun
         unsuspend_account: Nyahgantungkan Akaun
@@ -773,6 +768,7 @@ ms:
       original_status: Hantaran asal
       reblogs: Ulang siar
       status_changed: Hantaran diubah
+      title: Hantaran akaun
       trending: Sohor kini
       visibility: Visibiliti
       with_media: Dengan media
@@ -1006,6 +1002,7 @@ ms:
     migrate_account: Pindah kepada akaun lain
     migrate_account_html: Jika anda ingin mengubah hala akaun ini kepada akaun lain, anda boleh <a href="%{path}">konfigurasikannya di sini</a>.
     or_log_in_with: Atau daftar masuk dengan
+    privacy_policy_agreement_html: Saya telah membaca dan bersetuju menerima <a href="%{privacy_policy_path}" target="_blank">dasar privasi</a>
     progress:
       details: Maklumat anda
       review: Ulasan kami
@@ -1028,6 +1025,7 @@ ms:
     security: Keselamatan
     set_new_password: Tetapkan kata laluan baharu
     setup:
+      email_settings_hint_html: Klik pautan yang kami hantar kepada anda untuk mengesahkan %{email}. Kami akan tunggu di sini.
       link_not_received: Tidak mendapat pautan?
       title: Semak peti masuk anda
     sign_in:
@@ -1035,6 +1033,7 @@ ms:
       title: Log masuk ke %{domain}
     sign_up:
       manual_review: Pendaftaran pada %{domain} melalui semakan manual oleh penyederhana kami. Untuk membantu kami memproses pendaftaran anda, tulis sedikit tentang diri anda dan sebab anda mahukan akaun di %{domain}.
+      preamble: Dengan akaun pada server Mastodon ini, anda akan dapat mengikuti mana-mana orang lain di rangkaian, tidak kira di mana akaun mereka dihoskan.
       title: Mari sediakan anda pada %{domain}.
     status:
       account_status: Status akaun
@@ -1149,7 +1148,7 @@ ms:
     csv: CSV
     domain_blocks: Domain disekat
     lists: Senarai
-    mutes: Redaman anda
+    mutes: Awak bisu
     storage: Storan Media
   featured_tags:
     add_new: Tambah baharu
@@ -1224,6 +1223,20 @@ ms:
       merge_long: Simpan rekod sedia ada dan tambah rekod baharu
       overwrite: Tulis ganti
       overwrite_long: Gantikan rekod semasa dengan yang baharu
+    overwrite_preambles:
+      blocking_html: Anda akan <strong>menggantikan senarai blok anda</strong> dengan sehingga <strong>%{total_items} akaun</strong> daripada <strong>%{filename}</strong>.
+      bookmarks_html: Anda akan <strong>menggantikan penanda halaman anda</strong> dengan sehingga <strong>%{total_items} siaran</strong> daripada <strong>%{filename}</strong>.
+      domain_blocking_html: Anda akan <strong>menggantikan senarai blok domain anda</strong> dengan sehingga <strong>%{total_items} domain</strong> daripada <strong>%{filename}</strong>.
+      following_html: Anda akan <strong>mengikuti</strong> sehingga <strong>%{total_items} akaun</strong> daripada <strong>%{filename}</strong> dan <strong>berhenti mengikuti orang lain</strong>.
+      lists_html: Anda akan <strong>menggantikan senarai anda</strong> dengan kandungan <strong>%{filename}</strong>. Sehingga <strong>%{total_items} akaun</strong> akan ditambahkan pada senarai baharu.
+      muting_html: Anda akan <strong>menggantikan senarai akaun yang diredamkan</strong> dengan sehingga <strong>%{total_items} akaun</strong> daripada <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Anda akan <strong>menyekat</strong> sehingga <strong>%{total_items} akaun</strong> daripada <strong>%{filename}</strong>.
+      bookmarks_html: Anda akan menambah sehingga <strong>%{total_items} pos</strong> daripada <strong>%{filename}</strong> ke <strong>penanda halaman</strong> anda.
+      domain_blocking_html: Anda akan <strong>menyekat</strong> sehingga <strong>%{total_items} domain</strong> daripada <strong>%{filename}</strong>.
+      following_html: Anda akan <strong>mengikuti</strong> sehingga <strong>%{total_items} akaun</strong> daripada <strong>%{filename}</strong>.
+      lists_html: Anda akan menambah sehingga <strong>%{total_items} akaun</strong> daripada <strong>%{filename}</strong> ke <strong>senarai</strong> anda. Senarai baharu akan dibuat jika tiada senarai untuk ditambah.
+      muting_html: Anda akan <strong>membisukan</strong> sehingga <strong>%{total_items} akaun</strong> daripada <strong>%{filename}</strong>.
     preface: Anda boleh mengimport data yang telah anda eksport dari server lain, seperti senarai orang yang anda ikuti atau sekat.
     recent_imports: Import terkini
     states:
@@ -1240,11 +1253,10 @@ ms:
       domain_blocking: Mengimport domain yang disekat
       following: Mengimport akaun diikuti
       lists: Mengimport senarai
-      muting: Mengimport akaun teredam
+      muting: Mengimport akaun diredam
     type: Jenis import
     type_groups:
       constructive: Ikutan & Penanda Halaman
-      destructive: Sekatan dan redaman
     types:
       blocking: Senarai menyekat
       bookmarks: Penanda buku
@@ -1461,6 +1473,7 @@ ms:
   scheduled_statuses:
     over_daily_limit: Anda telah melebihi had %{limit} pos berjadual untuk hari ini
     over_total_limit: Anda telah melebihi had %{limit} pos berjadual
+    too_soon: Tarikh yang dijadualkan mestilah pada masa hadapan
   self_destruct:
     lead_html: Malangnya, <strong>%{domain}</strong> akan ditutup secara kekal. Jika anda mempunyai akaun di situ, anda tidak akan dapat terus menggunakannya, tetapi anda masih boleh meminta sandaran data anda.
     title: Pelayan ini akan ditutup
diff --git a/config/locales/my.yml b/config/locales/my.yml
index cba06d15f4..598915fad9 100644
--- a/config/locales/my.yml
+++ b/config/locales/my.yml
@@ -764,6 +764,7 @@ my:
       original_status: မူရင်းပို့စ်
       reblogs: Reblog များ
       status_changed: ပို့စ်ပြောင်းပြီးပါပြီ
+      title: အကောင့်ပို့စ်များ
       trending: လက်ရှိခေတ်စားနေခြင်း
       visibility: မြင်နိုင်မှု
       with_media: မီဒီယာနှင့်အတူ
@@ -994,6 +995,7 @@ my:
     migrate_account: အခြားအကောင့်တစ်ခုသို့ ရွှေ့ရန်
     migrate_account_html: ဤအကောင့်ကို အခြားအကောင့်သို့ ပြန်ညွှန်းလိုပါက <a href="%{path}">ဤနေရာတွင် စီစဉ်သတ်မှတ်နိုင်သည်</a>။
     or_log_in_with: သို့မဟုတ် အကောင့်ဖြင့် ဝင်ရောက်ပါ
+    privacy_policy_agreement_html: <a href="%{privacy_policy_path}" target="_blank">ကိုယ်ရေးအချက်အလက်မူဝါဒ</a> ကို ဖတ်ပြီး သဘောတူလိုက်ပါပြီ
     progress:
       details: သင့်အသေးစိတ်အချက်အလက်များ
       review: ကျွန်ုပ်တို့၏သုံးသပ်ချက်
@@ -1016,6 +1018,7 @@ my:
     security: လုံခြုံရေး
     set_new_password: စကားဝှက်အသစ် သတ်မှတ်ပါ။
     setup:
+      email_settings_hint_html: "%{email} အတည်ပြုရန် သင့်ထံပေးပို့သော လင့်ခ်ကို နှိပ်ပါ။ စောင့်နေပါမည်။"
       link_not_received: လင့်ခ် မရခဲ့ဘူးလား။
       title: သင့်ဝင်စာပုံးကို စစ်ဆေးပါ
     sign_in:
@@ -1023,6 +1026,7 @@ my:
       title: "%{domain} သို့ အကောင့်ဝင်ရန်"
     sign_up:
       manual_review: "%{domain} ၌ အကောင့်ဖွင့်ခြင်းများတွင် ကျွန်ုပ်တို့၏ ကြီးကြပ်သူများမှ ကိုယ်တိုင်သုံးသပ် လုပ်ဆောင်လျက်ရှိပါသည်။ သင့်အကြောင်းနှင့် %{domain} တွင် အကောင့်ဖွင့်လိုသည့်အကြောင်း အနည်းငယ်ရေးသားခြင်းဖြင့် သင့်အကောင့်စာရင်းသွင်းခြင်းမှာ ကျွန်ုပ်တို့ကို အကူအညီဖြစ်စေပါသည်။"
+      preamble: ဤ Mastodon အကောင့်တစ်ခုဖြင့် သင်သည် ကွန်ရက်ပေါ်ရှိ အခြားမည်သူ့မဆို မည်သည့်ဆာဗာတွင်ရှိစေကာမူ သင်စောင့်ကြည့်နိုင်မည်ဖြစ်ပါသည်။
       title: "%{domain} တွင် ထည့်သွင်းရန်။"
     status:
       account_status: အကောင့်အခြေအနေ
@@ -1218,6 +1222,20 @@ my:
       merge_long: ရှိပြီးသားမှတ်တမ်းများ သိမ်းဆည်းပြီး အသစ်များ ထပ်ထည့်ပါ
       overwrite: ထပ်ရေးရန်
       overwrite_long: လက်ရှိမှတ်တမ်းများကို အသစ်များဖြင့် အစားထိုးပါ
+    overwrite_preambles:
+      blocking_html: သင်သည် <strong>%{filename}</strong> မှ <strong>%{total_items} အကောင့်များ</strong> အထိ <strong>သင့်ပိတ်ဆို့စာရင်း</strong>ကို အစားထိုးပါတော့မည်။
+      bookmarks_html: သင်သည် <strong>%{filename}</strong> မှ <strong>%{total_items} ပို့စ်များ</strong> အထိ <strong>သင့် bookmark များ</strong>ကို အစားထိုးပါတော့မည်။
+      domain_blocking_html: သင်သည် <strong>%{filename}</strong> မှ <strong>%{total_items} ဒိုမိန်းများ</strong> ဖြင့် <strong>သင်၏ ဒိုမိန်းပိတ်ဆို့စာရင်း</strong>ကို အစားထိုးပါတော့မည်။
+      following_html: သင်သည် <strong>%{filename}</strong> မှ <strong>%{total_items} အကောင့်များ</strong> အထိ <strong>စောင့်ကြည့်</strong> ပြီး <strong>အခြားမည်သူ့ကိုမျှ စောင့်မကြည့်တော့ပါ</strong> ။
+      lists_html: သင်သည် <strong>%{filename}</strong> ၏ အကြောင်းအရာများဖြင့် <strong>သင့်စာရင်းများကို အစားထိုး</strong> ပါတော့မည်။ <strong>%{total_items} အကောင့်များ</strong>အထိ စာရင်းအသစ်များသို့ ပေါင်းထည့်ပါမည်။
+      muting_html: သင်သည် <strong>%{filename}</strong> မှ <strong>%{total_items} အကောင့်များ</strong> ဖြင့် <strong>အသံပိတ်ထားသော သင့်အကောင့်များစာရင်း</strong>ကို အစားထိုးပါတော့မည်။
+    preambles:
+      blocking_html: သင်သည် <strong>%{filename}</strong> မှ <strong>%{total_items} အကောင့်များ</strong> အထိ <strong>ပိတ်ဆို့</strong>ပါတော့မည်။
+      bookmarks_html: သင်သည် <strong>%{filename}</strong> မှ <strong>%{total_items} ပို့စ်များ</strong> အထိ သင့် <strong> Bookmark များ</strong> သို့ ပေါင်းထည့်တော့မည်။
+      domain_blocking_html: သင်သည် <strong>%{filename}</strong> မှ <strong>%{total_items} ဒိုမိန်းများ</strong> အထိ <strong>ပိတ်ဆို့</strong>ပါတော့မည်။
+      following_html: သင်သည် <strong>%{filename}</strong> မှ <strong>%{total_items} အကောင့်များ</strong> အထိ <strong>စောင့်ကြည့်</strong> ပါတော့မည်။
+      lists_html: သင်သည် <strong>%{filename}</strong> မှ <strong>%{total_items} အကောင့်များ</strong> ကို သင့် <strong>စာရင်းများ</strong> သို့ ပေါင်းထည့်ပါတော့မည်။ ထည့်ရန်စာရင်းမရှိပါက စာရင်းအသစ်များကို ဖန်တီးပါမည်။
+      muting_html: သင်သည် <strong>%{filename}</strong> မှ <strong>%{total_items} အကောင့်များ</strong> အထိ <strong>အသံတိတ်</strong>ပါတော့မည်။
     preface: သင်စောင့်ကြည့်နေသည့်လူများစာရင်း သို့မဟုတ် ပိတ်ပင်ထားသည့်စာရင်းကဲ့သို့သော အခြားဆာဗာတစ်ခုမှ သင်ထုတ်ယူထားသည့်အချက်အလက်များကို ပြန်လည်ထည့်သွင်းနိုင်သည်။
     recent_imports: လတ်တလောထည့်သွင်းမှုများ
     states:
@@ -1453,6 +1471,7 @@ my:
   scheduled_statuses:
     over_daily_limit: ယနေ့အတွက် စီစဉ်ထားသည့် ပို့စ်များ၏ ကန့်သတ်ချက် %{limit} ကို ကျော်လွန်သွားပါပြီ
     over_total_limit: စီစဉ်ထားသည့် ပို့စ်များ၏ ကန့်သတ်ချက် %{limit} ကို ကျော်လွန်သွားပါပြီ
+    too_soon: စီစဉ်ထားသောရက်စွဲမှာ အနာဂတ်အတွက်ဖြစ်သည်
   self_destruct:
     title: ဤဆာဗာ ပိတ်ထားပါသည်
   sessions:
diff --git a/config/locales/nan.yml b/config/locales/nan.yml
index 9180b7b064..452824b984 100644
--- a/config/locales/nan.yml
+++ b/config/locales/nan.yml
@@ -44,12 +44,24 @@ nan:
         instance_languages_dimension: Tsia̍p用ê語言
     statuses:
       language: 語言
+      title: 口座ê PO文
     trends:
       tags:
         dashboard:
           tag_languages_dimension: Tsia̍p用ê語言
+  auth:
+    privacy_policy_agreement_html: 我有讀,同意<a href="%{privacy_policy_path}" target="_blank">隱私權政策</a>
+    setup:
+      email_settings_hint_html: 請點gún所送ê連結來驗證 %{email}。Gún tī tsia等lí。
+    sign_up:
+      preamble: 用tī tsit臺Mastodon服侍器ê口座,lí thang跟tuè其他佇tsit ê網路ê lâng,無論in ê口座khǹg tī tueh。
+  imports:
+    overwrite_preambles:
+      blocking_html: Lí teh-beh用<strong>%{filename}</strong> 內底ê <strong>%{total_items} ê口座</strong>,替換lí ê封鎖列單。
+    preambles:
+      muting_html: Lí teh-beh kā <strong>%{filename}</strong>內底ê<strong>%{total_items} ê口座</strong><strong>消音</strong>。
   scheduled_statuses:
-    too_soon: Tio̍h用未來ê日期。
+    too_soon: 預定PO文ê時間kan-ta tī未來
   statuses:
     default_language: Kap界面ê語言sio kâng
   user_mailer:
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 5acd93ca13..f1de8265bf 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -187,7 +187,6 @@ nl:
         create_domain_block: Domeinblokkade aanmaken
         create_email_domain_block: E-maildomeinblokkade aanmaken
         create_ip_block: IP-regel aanmaken
-        create_relay: Relay aanmaken
         create_unavailable_domain: Niet beschikbaar domein aanmaken
         create_user_role: Rol aanmaken
         demote_user: Gebruiker degraderen
@@ -199,22 +198,18 @@ nl:
         destroy_email_domain_block: Blokkade van e-maildomein verwijderen
         destroy_instance: Domein volledig verwijderen
         destroy_ip_block: IP-regel verwijderen
-        destroy_relay: Relay verwijderen
         destroy_status: Toot verwijderen
         destroy_unavailable_domain: Niet beschikbaar domein verwijderen
         destroy_user_role: Rol permanent verwijderen
         disable_2fa_user: Tweestapsverificatie uitschakelen
         disable_custom_emoji: Lokale emojij uitschakelen
-        disable_relay: Relay uitschakelen
         disable_sign_in_token_auth_user: Verificatie met een toegangscode via e-mail voor de gebruiker uitschakelen
         disable_user: Gebruiker uitschakelen
         enable_custom_emoji: Lokale emoji inschakelen
-        enable_relay: Relay inschakelen
         enable_sign_in_token_auth_user: Verificatie met een toegangscode via e-mail voor de gebruiker inschakelen
         enable_user: Gebruiker inschakelen
         memorialize_account: Het account in een In memoriam veranderen
         promote_user: Gebruiker promoveren
-        publish_terms_of_service: Algemene gebruiksvoorwaarden publiceren
         reject_appeal: Bezwaar afwijzen
         reject_user: Gebruiker afwijzen
         remove_avatar_user: Profielfoto verwijderen
@@ -252,7 +247,6 @@ nl:
         create_domain_block_html: Domein %{target} is door %{name} geblokkeerd
         create_email_domain_block_html: "%{name} heeft het e-maildomein %{target} geblokkeerd"
         create_ip_block_html: "%{name} maakte regel aan voor IP %{target}"
-        create_relay_html: "%{name} heeft een relay aangemaakt %{target}"
         create_unavailable_domain_html: "%{name} heeft de bezorging voor domein %{target} beëindigd"
         create_user_role_html: "%{name} maakte de rol %{target} aan"
         demote_user_html: Gebruiker %{target} is door %{name} gedegradeerd
@@ -264,22 +258,18 @@ nl:
         destroy_email_domain_block_html: "%{name} heeft het e-maildomein %{target} gedeblokkeerd"
         destroy_instance_html: "%{name} verwijderde het domein %{target} volledig"
         destroy_ip_block_html: "%{name} verwijderde regel voor IP %{target}"
-        destroy_relay_html: "%{name} heeft de relay %{target} verwijderd"
         destroy_status_html: Bericht van %{target} is door %{name} verwijderd
         destroy_unavailable_domain_html: "%{name} heeft de bezorging voor domein %{target} hervat"
         destroy_user_role_html: "%{name} verwijderde de rol %{target}"
         disable_2fa_user_html: De vereiste tweestapsverificatie voor %{target} is door %{name} uitgeschakeld
         disable_custom_emoji_html: Emoji %{target} is door %{name} uitgeschakeld
-        disable_relay_html: "%{name} heeft de relay %{target} uitgeschakeld"
         disable_sign_in_token_auth_user_html: "%{name} heeft verificatie met een toegangscode via e-mail uitgeschakeld voor %{target}"
         disable_user_html: Inloggen voor %{target} is door %{name} uitgeschakeld
         enable_custom_emoji_html: Emoji %{target} is door %{name} ingeschakeld
-        enable_relay_html: "%{name} heeft de relay %{target} ingeschakeld"
         enable_sign_in_token_auth_user_html: "%{name} heeft verificatie met een toegangscode via e-mail ingeschakeld voor %{target}"
         enable_user_html: Inloggen voor %{target} is door %{name} ingeschakeld
         memorialize_account_html: Het account %{target} is door %{name} in een In memoriam veranderd
         promote_user_html: Gebruiker %{target} is door %{name} gepromoveerd
-        publish_terms_of_service_html: "%{name} publiceerde bijgewerkte gebruiksvoorwaarden"
         reject_appeal_html: "%{name} heeft het bezwaar tegen de moderatiemaatregel van %{target} afgewezen"
         reject_user_html: "%{name} heeft de registratie van %{target} afgewezen"
         remove_avatar_user_html: "%{name} verwijderde de profielfoto van %{target}"
@@ -309,7 +299,6 @@ nl:
       title: Auditlog
       unavailable_instance: "(domeinnaam niet beschikbaar)"
     announcements:
-      back: Terug naar mededelingen
       destroyed_msg: Verwijderen van mededeling geslaagd!
       edit:
         title: Mededeling bewerken
@@ -318,10 +307,6 @@ nl:
       new:
         create: Mededeling aanmaken
         title: Nieuwe mededeling
-      preview:
-        disclaimer: Omdat gebruikers zich niet voor deze e-mails kunnen afmelden, moeten e-mailmeldingen worden beperkt tot belangrijke aankondigingen, zoals het lekken van gebruikersgegevens of meldingen over het sluiten van deze server.
-        explanation_html: 'De e-mail wordt verzonden naar <strong>%{display_count} gebruikers</strong>. De volgende tekst wordt in het bericht opgenomen:'
-        title: Voorbeeld van mededeling
       publish: Inschakelen
       published_msg: Publiceren van mededeling geslaagd!
       scheduled_for: Ingepland voor %{time}
@@ -480,36 +465,6 @@ nl:
       new:
         title: Domeinblokkades importeren
       no_file: Geen bestand geselecteerd
-    fasp:
-      debug:
-        callbacks:
-          created_at: Aangemaakt op
-          delete: Verwijderen
-          ip: IP-adres
-          request_body: Body opvragen
-          title: Callbacks debuggen
-      providers:
-        active: Ingeschakeld
-        base_url: Basis-URL
-        callback: Callback
-        delete: Verwijderen
-        edit: Aanbieder bewerken
-        finish_registration: Registratie voltooien
-        name: Naam
-        providers: Aanbieders
-        public_key_fingerprint: Vingerafdruk van publieke sleutel
-        registration_requested: Registratie aangevraagd
-        registrations:
-          confirm: Bevestigen
-          description: Je hebt een registratie ontvangen van een FASP. Weiger deze als je dit niet hebt aangevraagd. Heb je dit wel aangevraagd, vergelijk dan zorgvuldig de naam en de vingerafdruk van de sleutel voordat je de registratie bevestigt.
-          reject: Afwijzen
-          title: FASP-registratie bevestigen
-        save: Opslaan
-        select_capabilities: Mogelijkheden selecteren
-        sign_in: Inloggen
-        status: Status
-        title: Fediverse Auxiliary Service Providers
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Deze aanbevolen accounts helpen nieuwe gebruikers snel interessante inhoud</strong>te vinden. Wanneer een gebruiker niet met andere gebruikers genoeg interactie heeft gehad om gepersonaliseerde aanbevelingen te krijgen, worden in plaats daarvan deze accounts aanbevolen. Deze accounts worden dagelijks opnieuw berekend met behulp van accounts met het hoogste aantal recente interacties en het hoogste aantal lokale volgers in een bepaalde taal."
       language: Voor taal
@@ -863,10 +818,8 @@ nl:
       back_to_account: Terug naar accountpagina
       back_to_report: Terug naar de rapportage
       batch:
-        add_to_report: 'Toevoegen aan rapport #%{id}'
         remove_from_report: Uit de rapportage verwijderen
         report: Rapportage
-      contents: Inhoud
       deleted: Verwijderd
       favourites: Favorieten
       history: Versiegeschiedenis
@@ -875,17 +828,13 @@ nl:
       media:
         title: Media
       metadata: Metagegevens
-      no_history: Dit bericht is niet bewerkt
       no_status_selected: Er werden geen berichten gewijzigd, omdat er geen enkele werd geselecteerd
       open: Bericht tonen
       original_status: Oorspronkelijk bericht
       reblogs: Boosts
-      replied_to_html: Reageerde op %{acct_link}
       status_changed: Bericht veranderd
-      status_title: Bericht van @%{name}
-      title: Accountberichten - @%{name}
+      title: Berichten van account
       trending: Trending
-      view_publicly: In het openbaar bekijken
       visibility: Zichtbaarheid
       with_media: Met media
     strikes:
@@ -962,36 +911,6 @@ nl:
       search: Zoeken
       title: Hashtags
       updated_msg: Instellingen hashtag succesvol bijgewerkt
-    terms_of_service:
-      back: Terug naar de gebruiksvoorwaarden
-      changelog: Wat is veranderd
-      create: Gebruik je eigen
-      current: Huidige
-      draft: Concept
-      generate: Sjabloon gebruiken
-      generates:
-        action: Genereren
-        chance_to_review_html: "<strong>De gegenereerde gebruiksvoorwaarden worden niet automatisch gepubliceerd.</strong> Je krijgt de gelegenheid om de resultaten eerst te bekijken. Vul de benodigde gegevens in om verder te gaan."
-        explanation_html: Het sjabloon voor de gebruiksvoorwaarden is uitsluitend bedoeld voor informatieve doeleinden en mag niet worden opgevat als juridisch advies over welk onderwerp dan ook. Raadpleeg een eigen juridisch adviseur over jouw situatie en voor specifieke juridische vragen.
-        title: Gebruiksvoorwaarden instellen
-      going_live_on_html: Actueel met ingang van %{date}
-      history: Geschiedenis
-      live: Actueel
-      no_history: Er zijn nog geen opgeslagen wijzigingen van de gebruiksvoorwaarden.
-      no_terms_of_service_html: Je hebt momenteel geen gebruiksvoorwaarden geconfigureerd. De gebruiksvoorwaarden zijn bedoeld om duidelijkheid te verschaffen en je te beschermen tegen mogelijke aansprakelijkheid als gevolg van geschillen met gebruikers.
-      notified_on_html: Gebruikers geïnformeerd op %{date}
-      notify_users: Gebruikers informeren
-      preview:
-        explanation_html: 'De e-mail wordt verzonden naar <strong>%{display_count} gebruikers</strong> die zich hebben aangemeld voor %{date}. De volgende tekst zal in de e-mail worden opgenomen:'
-        send_preview: Voorbeeld verzenden naar %{email}
-        send_to_all:
-          one: "%{display_count} e-mail verzenden"
-          other: "%{display_count} e-mails verzenden"
-        title: Voorbeeld tonen van de melding over de gebruiksvoorwaarden
-      publish: Publiceren
-      published_on_html: Gepubliceerd op %{date}
-      save_draft: Concept opslaan
-      title: Gebruiksvoorwaarden
     title: Beheer
     trends:
       allow: Goedkeuren
@@ -1199,6 +1118,7 @@ nl:
     migrate_account: Naar een ander account verhuizen
     migrate_account_html: Wanneer je dit account naar een ander account wilt doorverwijzen, kun je <a href="%{path}">dit hier instellen</a>.
     or_log_in_with: Of inloggen met
+    privacy_policy_agreement_html: Ik heb het <a href="%{privacy_policy_path}" target="_blank">privacybeleid</a> gelezen en ga daarmee akkoord
     progress:
       confirm: E-mailadres bevestigen
       details: Jouw gegevens
@@ -1223,7 +1143,7 @@ nl:
     set_new_password: Nieuw wachtwoord instellen
     setup:
       email_below_hint_html: Controleer je map met spam, of vraag een nieuwe bevestigingslink aan. Je kan je e-mailadres corrigeren als het verkeerd is.
-      email_settings_hint_html: Klik op de link die we naar %{email} hebben gestuurd om Mastodon te gebruiken. We wachten wel even.
+      email_settings_hint_html: Klik op de link die we je hebben gestuurd om %{email} te verifiëren. We wachten wel even.
       link_not_received: Geen link ontvangen?
       new_confirmation_instructions_sent: Je ontvangt binnen enkele minuten een nieuwe e-mail met de bevestigingslink!
       title: Kijk in je mailbox
@@ -1232,7 +1152,7 @@ nl:
       title: Inloggen op %{domain}
     sign_up:
       manual_review: Inschrijvingen op %{domain} worden handmatig door onze moderatoren beoordeeld. Schrijf wat over jezelf en waarom je een account op %{domain} wilt. Hiermee help je ons om de aanvraag van jouw account goed te kunnen verwerken.
-      preamble: Met een account op deze Mastodon-server kun je iedereen in de fediverse volgen, ongeacht waar deze persoon een account heeft.
+      preamble: Je kunt met een Mastodon-account iedereen in het netwerk volgen, ongeacht waar deze persoon een account heeft.
       title: Laten we je account op %{domain} instellen.
     status:
       account_status: Accountstatus
@@ -1244,8 +1164,6 @@ nl:
       view_strikes: Bekijk de eerder door moderatoren vastgestelde overtredingen die je hebt gemaakt
     too_fast: Formulier is te snel ingediend. Probeer het nogmaals.
     use_security_key: Beveiligingssleutel gebruiken
-    user_agreement_html: Ik heb de <a href="%{terms_of_service_path}" target="_blank">gebruiksvoorwaarden</a> en het <a href="%{privacy_policy_path}" target="_blank">privacybeleid</a> gelezen en ga ermee akkoord
-    user_privacy_agreement_html: Ik heb het <a href="%{privacy_policy_path}" target="_blank">privacybeleid</a> gelezen en ga ermee akkoord
   author_attribution:
     example_title: Voorbeeldtekst
     hint_html: Schrijf je nieuws- of blogartikelen buiten Mastodon? Bepaal hoe je geattribueerd wordt als deze gedeeld worden op Mastodon.
@@ -1451,43 +1369,19 @@ nl:
       overwrite: Overschrijven
       overwrite_long: Huidige gegevens met de nieuwe gegevens vervangen
     overwrite_preambles:
-      blocking_html:
-        one: Je staat op het punt om vanuit <strong>%{filename}</strong> je <strong>blokkeerlijst te vervangen</strong> met in het totaal <strong>%{count} account</strong>.
-        other: Je staat op het punt om vanuit <strong>%{filename}</strong> je <strong>blokkeerlijst te vervangen</strong> met in het totaal <strong>%{count} accounts</strong>.
-      bookmarks_html:
-        one: Je staat op het punt om vanuit <strong>%{filename}</strong> je <strong>bladwijzers te vervangen</strong> met in het totaal <strong>%{count} bericht</strong>.
-        other: Je staat op het punt om vanuit <strong>%{filename}</strong> je <strong>bladwijzers te vervangen</strong> met in het totaal <strong>%{count} berichten</strong>.
-      domain_blocking_html:
-        one: Je staat op het punt om vanuit <strong>%{filename}</strong> je <strong>lijst met geblokkeerde domeinen te vervangen</strong> met in het totaal <strong>%{count} domein</strong>.
-        other: Je staat op het punt om vanuit <strong>%{filename}</strong> je <strong>lijst met geblokkeerde domeinen te vervangen</strong> met in het totaal <strong>%{count} domeinen</strong>.
-      following_html:
-        one: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} account te volgen</strong> en <strong>de rest te ontvolgen</strong>.
-        other: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} accounts te volgen</strong> en <strong>de rest te ontvolgen</strong>.
-      lists_html:
-        one: Je staat op het punt om met de inhoud van <strong>%{filename}</strong> je <strong>lijsten te vervangen</strong>. In het totaal wordt <strong>%{count} account</strong> aan de nieuwe lijst(en) toegevoegd.
-        other: Je staat op het punt om met de inhoud van <strong>%{filename}</strong> je <strong>lijsten te vervangen</strong>. In het totaal worden <strong>%{count} accounts</strong> aan de nieuwe lijst(en) toegevoegd.
-      muting_html:
-        one: Je staat op het punt om vanuit <strong>%{filename}</strong> je <strong>negeerlijst te vervangen</strong> met in het totaal <strong>%{count} account</strong>.
-        other: Je staat op het punt om vanuit <strong>%{filename}</strong> je <strong>blokkeerlijst te vervangen</strong> met in het totaal <strong>%{count} accounts</strong>.
+      blocking_html: Je staat op het punt <strong>jouw lijst met geblokkeerde accounts</strong> te vervangen door <strong>%{total_items} accounts</strong> vanuit <strong>%{filename}</strong>.
+      bookmarks_html: Je staat op het punt <strong>jouw bladwijzers</strong> te vervangen door <strong>%{total_items} berichten</strong> vanuit <strong>%{filename}</strong>.
+      domain_blocking_html: Je staat op het punt <strong>jouw lijst met geblokkeerde domeinen</strong> te vervangen door <strong>%{total_items} domeinen</strong> vanuit <strong>%{filename}</strong>.
+      following_html: Je staat op het punt om <strong>%{total_items} accounts</strong> <strong>te volgen</strong> vanuit <strong>%{filename}</strong> en <strong>te stoppen met het volgen van alle andere accounts</strong>.
+      lists_html: Je staat op het punt <strong>je lijsten</strong> te vervangen door inhoud van <strong>%{filename}</strong>. Er worden totaal <strong>%{total_items} accounts</strong> aan nieuwe lijsten toegevoegd.
+      muting_html: Je staat op het punt <strong>jouw lijst met genegeerde accounts</strong> te vervangen door <strong>%{total_items} accounts</strong> vanuit <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} account te blokkeren</strong>.
-        other: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} accounts te blokkeren</strong>.
-      bookmarks_html:
-        one: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} bericht</strong> aan je <strong>bladwijzers</strong> toe te voegen.
-        other: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} berichten</strong> aan je <strong>bladwijzers</strong> toe te voegen.
-      domain_blocking_html:
-        one: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} domein te blokkeren</strong>.
-        other: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} domeinen te blokkeren</strong>.
-      following_html:
-        one: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} account te volgen</strong>.
-        other: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} accounts te volgen</strong>.
-      lists_html:
-        one: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} account</strong> aan je <strong>lijsten</strong> toe te voegen. Er wordt een nieuwe lijst aangemaakt wanneer er geen lijst is om het aan toe te voegen.
-        other: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} accounts</strong> aan je <strong>lijsten</strong> toe te voegen. Er worden nieuwe lijsten aangemaakt wanneer er geen lijsten zijn om ze aan toe te voegen.
-      muting_html:
-        one: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} account te negeren</strong>.
-        other: Je staat op het punt om vanuit <strong>%{filename}</strong> in het totaal <strong>%{count} accounts te negeren</strong>.
+      blocking_html: Je staat op het punt om <strong>%{total_items} accounts</strong> te <strong>blokkeren</strong> vanuit <strong>%{filename}</strong>.
+      bookmarks_html: Je staat op het punt om <strong>%{total_items} berichten</strong> aan je <strong>bladwijzers</strong> toe te voegen vanuit <strong>%{filename}</strong>.
+      domain_blocking_html: Je staat op het punt om <strong>%{total_items} accounts</strong> te <strong>negeren</strong> vanuit <strong>%{filename}</strong>.
+      following_html: Je staat op het punt om <strong>%{total_items} accounts</strong> te <strong>volgen</strong> vanuit <strong>%{filename}</strong>.
+      lists_html: Je staat op het punt om tot <strong>%{total_items} accounts</strong> van <strong>%{filename}</strong> toe te voegen aan je <strong>lijsten</strong>. Nieuwe lijsten worden aangemaakt als er geen lijst is om aan toe te voegen.
+      muting_html: Je staat op het punt om <strong>%{total_items} accounts</strong> te <strong>negeren</strong> vanuit <strong>%{filename}</strong>.
     preface: Je kunt bepaalde gegevens, zoals de mensen die jij volgt of hebt geblokkeerd, naar jouw account op deze server importeren. Je moet deze gegevens wel eerst op de oorspronkelijke server exporteren.
     recent_imports: Recent geïmporteerd
     states:
@@ -1744,7 +1638,7 @@ nl:
   scheduled_statuses:
     over_daily_limit: Je hebt de limiet van %{limit} in te plannen berichten voor vandaag overschreden
     over_total_limit: Je hebt de limiet van %{limit} in te plannen berichten overschreden
-    too_soon: datum moet in de toekomst liggen
+    too_soon: De datum voor het ingeplande bericht moet in de toekomst liggen
   self_destruct:
     lead_html: Helaas gaat <strong>%{domain}</strong> permanent sluiten. Als je daar een account had, kun je deze niet meer gebruiken, maar je kunt nog steeds een back-up van je gegevens opvragen.
     title: Deze server gaat sluiten
@@ -1907,8 +1801,6 @@ nl:
       too_late: De periode dat je bezwaar kunt maken tegen deze overtreding is verstreken
   tags:
     does_not_match_previous_name: komt niet overeen met de vorige naam
-  terms_of_service:
-    title: Gebruiksvoorwaarden
   themes:
     contrast: Mastodon (hoog contrast)
     default: Mastodon (donker)
@@ -1940,10 +1832,6 @@ nl:
     recovery_instructions_html: Wanneer je ooit de toegang verliest tot jouw telefoon, kan je met behulp van een van de herstelcodes hieronder opnieuw toegang krijgen tot jouw account. <strong>Zorg ervoor dat je de herstelcodes op een veilige plek bewaart</strong>. Je kunt ze bijvoorbeeld printen en ze samen met andere belangrijke documenten bewaren.
     webauthn: Beveiligingssleutels
   user_mailer:
-    announcement_published:
-      description: 'De beheerders van %{domain} doen een mededeling:'
-      subject: Service-aankondiging
-      title: "%{domain} service aankondiging"
     appeal_approved:
       action: Accountinstellingen
       explanation: Het bezwaar tegen een door een moderator vastgestelde overtreding van jou op %{strike_date}, ingediend op %{appeal_date}, is goedgekeurd. De eerder vastgestelde overtreding is hierbij niet langer geldig.
@@ -1973,15 +1861,6 @@ nl:
       further_actions_html: Wanneer jij dit niet was, adviseren wij om onmiddellijk %{action} en om tweestapsverificatie in te schakelen, om zo je account veilig te houden.
       subject: Jouw account is vanaf een nieuw IP-adres benaderd
       title: Een nieuwe registratie
-    terms_of_service_changed:
-      agreement: Door %{domain} te blijven gebruiken, ga je akkoord met deze voorwaarden. Als je het niet eens bent met de bijgewerkte voorwaarden, kun je je overeenkomst met %{domain} op elk gewenst moment beëindigen door je account te verwijderen.
-      changelog: 'In een oogopslag betekent deze update voor jou:'
-      description: 'Je ontvangt dit bericht, omdat we enkele wijzigingen aanbrengen in onze gebruiksvoorwaarden op %{domain}. Deze aanpassingen worden van kracht op %{date}. We raden je aan om de bijgewerkte voorwaarden hier volledig te bekijken:'
-      description_html: Je ontvangt dit bericht, omdat we enkele wijzigingen aanbrengen in onze gebruiksvoorwaarden op %{domain}. Deze aanpassingen worden van kracht op <strong>%{date}</strong>. We raden je aan om de <a href="%{path}" target="_blank">bijgewerkte voorwaarden hier</a> volledig te bestuderen.
-      sign_off: Het %{domain}-team
-      subject: Onze bijgewerkte gebruiksvoorwaarden
-      subtitle: De gebruiksvoorwaarden van %{domain} veranderen
-      title: Belangrijke update
     warning:
       appeal: Bezwaar indienen
       appeal_description: Wanneer je denkt dat dit een fout is, kun je een bezwaar indienen bij de medewerkers van %{instance}.
diff --git a/config/locales/nn.yml b/config/locales/nn.yml
index bcdcf01d9a..f316628f6d 100644
--- a/config/locales/nn.yml
+++ b/config/locales/nn.yml
@@ -187,7 +187,6 @@ nn:
         create_domain_block: Opprett domene-blokk
         create_email_domain_block: Opprett blokkering av e-postdomene
         create_ip_block: Opprett IP-regel
-        create_relay: Opprett eit relé
         create_unavailable_domain: Opprett utilgjengeleg domene
         create_user_role: Opprett rolle
         demote_user: Degrader brukar
@@ -199,22 +198,18 @@ nn:
         destroy_email_domain_block: Fjern blokkering av e-postdomene
         destroy_instance: Slett domene
         destroy_ip_block: Slett IP-regel
-        destroy_relay: Slett relé
         destroy_status: Slett status
         destroy_unavailable_domain: Slett utilgjengeleg domene
         destroy_user_role: Øydelegg rolle
         disable_2fa_user: Skruv av 2FA
         disable_custom_emoji: Skruv av tilpassa emoji
-        disable_relay: Skru av reléet
         disable_sign_in_token_auth_user: Slå av e-post-token-autentisering for brukar
         disable_user: Skruv av brukar
         enable_custom_emoji: Skruv på tilpassa emoji
-        enable_relay: Skru på reléet
         enable_sign_in_token_auth_user: Slå på e-post-token-autentisering for brukar
         enable_user: Skruv på brukar
         memorialize_account: Opprett minnekonto
         promote_user: Forfrem brukar
-        publish_terms_of_service: Legg ut bruksvilkåra
         reject_appeal: Avvis appell
         reject_user: Avvis brukar
         remove_avatar_user: Fjern avatar
@@ -252,7 +247,6 @@ nn:
         create_domain_block_html: "%{name} blokkerte domenet %{target}"
         create_email_domain_block_html: "%{name} blokkerte e-postdomenet %{target}"
         create_ip_block_html: "%{name} oppretta ein regel for IP-en %{target}"
-        create_relay_html: "%{name} laga reléet %{target}"
         create_unavailable_domain_html: "%{name} stogga levering til domenet %{target}"
         create_user_role_html: "%{name} oppretta rolla %{target}"
         demote_user_html: "%{name} degraderte brukaren %{target}"
@@ -264,22 +258,18 @@ nn:
         destroy_email_domain_block_html: "%{name} avblokkerte e-postdomenet %{target}"
         destroy_instance_html: "%{name} tømde domenet %{target}"
         destroy_ip_block_html: "%{name} sletta ein regel for IP-en %{target}"
-        destroy_relay_html: "%{name} sletta reléet %{target}"
         destroy_status_html: "%{name} fjerna innlegget frå %{target}"
         destroy_unavailable_domain_html: "%{name} tok opp att levering til domenet %{target}"
         destroy_user_role_html: "%{name} sletta rolla %{target}"
         disable_2fa_user_html: "%{name} tok vekk krav om tofaktorautentisering for brukaren %{target}"
         disable_custom_emoji_html: "%{name} deaktiverte emojien %{target}"
-        disable_relay_html: "%{name} skrudde av reléet %{target}"
         disable_sign_in_token_auth_user_html: "%{name} deaktiverte e-post-token-autentisering for %{target}"
         disable_user_html: "%{name} slo av innlogging for brukaren %{target}"
         enable_custom_emoji_html: "%{name} aktiverte emojien %{target}"
-        enable_relay_html: "%{name} skrudde på reléet %{target}"
         enable_sign_in_token_auth_user_html: "%{name} aktiverte e-post-token-autentisering for %{target}"
         enable_user_html: "%{name} aktiverte innlogging for brukaren %{target}"
         memorialize_account_html: "%{name} endret %{target}s konto til en minneside"
         promote_user_html: "%{name} fremja brukaren %{target}"
-        publish_terms_of_service_html: "%{name} endra bruksvilkåra"
         reject_appeal_html: "%{name} avviste klagen frå %{target} på modereringa"
         reject_user_html: "%{name} avslo registrering fra %{target}"
         remove_avatar_user_html: "%{name} fjerna %{target} sitt profilbilete"
@@ -828,10 +818,8 @@ nn:
       back_to_account: Tilbake til kontosida
       back_to_report: Attende til rapporteringssida
       batch:
-        add_to_report: 'Legg til rapport #%{id}'
         remove_from_report: Fjern fra rapport
         report: Rapport
-      contents: Innhald
       deleted: Sletta
       favourites: Favorittar
       history: Versjonshistorikk
@@ -840,17 +828,13 @@ nn:
       media:
         title: Media
       metadata: Metadata
-      no_history: Dette innlegget har ikkje blitt redigert
       no_status_selected: Ingen statusar vart endra sidan ingen vart valde
       open: Opne innlegg
       original_status: Opprinnelig innlegg
       reblogs: Framhevingar
-      replied_to_html: Svarte %{acct_link}
       status_changed: Innlegg endret
-      status_title: Innlegg av @%{name}
-      title: Kontoinnlegg - @%{name}
+      title: Kontostatusar
       trending: Populært
-      view_publicly: Vis offentleg
       visibility: Synlighet
       with_media: Med media
     strikes:
@@ -927,35 +911,6 @@ nn:
       search: Søk
       title: Emneknaggar
       updated_msg: Emneknagginnstillingane er oppdaterte
-    terms_of_service:
-      back: Tilbake til bruksvilkåra
-      changelog: Kva er endra
-      create: Bruk dine eigne
-      current: Gjeldande
-      draft: Utkast
-      generate: Bruk mal
-      generates:
-        action: Generer
-        chance_to_review_html: "<strong>Dei genererte bruksvilkåra blir ikkje lagde ut automatisk</strong> Du får høve til å sjå gjennom resultatet og fylla inn dei detaljane som trengst."
-        explanation_html: Malen for bruksvilkår er berre til informasjon, og du bør ikkje gå ut frå han som juridiske råd. Viss du har spørsmål om lovverk, bør du spørja ein advokat.
-        title: Oppsett for bruksvilkår
-      history: Historikk
-      live: Direkte
-      no_history: Det er ikkje registrert nokon endringar i bruksvilkåra enno.
-      no_terms_of_service_html: Du har ingen bruksvilkår for Mastodon-tenaren din. Slike vilkår klårgjer plikter og rettar for bruk av tenesta, og kan avgrensa ansvaret ditt viss du kjem opp i usemje med brukarar.
-      notified_on_html: Brukarane vart varsla %{date}
-      notify_users: Varsle brukarane
-      preview:
-        explanation_html: 'Eposten vil bli send til <strong>%{display_count} brukarar</strong> som har registrert seg før %{date}. Denne teksten vil stå i eposten:'
-        send_preview: Send førehandsvising til %{email}
-        send_to_all:
-          one: Send %{display_count} epost
-          other: Send %{display_count} epostar
-        title: Førehandsvis varsling om endring av bruksvilkår
-      publish: Legg ut
-      published_on_html: Lagt ut %{date}
-      save_draft: Lagre kladd
-      title: Bruksvilkår
     title: Leiing
     trends:
       allow: Tillat
@@ -1163,6 +1118,7 @@ nn:
     migrate_account: Flytt til ein annan konto
     migrate_account_html: Om du vil visa denne kontoen til ein anna, kan du <a href="%{path}">skipe det her</a>.
     or_log_in_with: Eller logg inn med
+    privacy_policy_agreement_html: Jeg har lest og godtar <a href="%{privacy_policy_path}" target="_blank">retningslinjer for personvern</a>
     progress:
       confirm: Stadfest e-post
       details: Opplysingane dine
@@ -1187,7 +1143,7 @@ nn:
     set_new_password: Lag nytt passord
     setup:
       email_below_hint_html: Sjekk søppelpostmappa di, eller be om ein ny. Du kan endra e-postadressa di dersom ho er feil.
-      email_settings_hint_html: Klikk på lenka me sende til %{email} for å byrja å bruka Mastodon. Me ventar her til så lenge.
+      email_settings_hint_html: Klikk lenka me sende deg for å stadfesta %{email}. Me sit her og ventar.
       link_not_received: Fekk du ikkje lenka?
       new_confirmation_instructions_sent: Du vil få ein ny e-post med stadfestingslenke innan nokre minutt!
       title: Sjekk innboksen din
@@ -1196,7 +1152,7 @@ nn:
       title: Logg inn på %{domain}
     sign_up:
       manual_review: Når du lagar ein konto på %{domain}, vil moderatorane våre gå gjennom påmeldinga di manuelt. For å hjelpa oss med påmeldinga di, er det fint om du skriv litt om deg sjølv og kvifor du vil ha ein konto på %{domain}.
-      preamble: Med ein konto på denne Mastodon-tenaren kan du fylgja andre folk på allheimen, uansett kvar dei har brukarkontoen sin.
+      preamble: Med ein konto på denne Mastodon-tenaren kan du fylgja andre folk på nettverket, uansett kvar dei har brukarkontoen sin.
       title: La oss få deg satt i gang på %{domain}.
     status:
       account_status: Kontostatus
@@ -1208,8 +1164,6 @@ nn:
       view_strikes: Vis tidligere advarsler mot kontoen din
     too_fast: Skjemaet ble sendt inn for raskt, prøv på nytt.
     use_security_key: Bruk sikkerhetsnøkkel
-    user_agreement_html: Eg godtek <a href="%{terms_of_service_path}" target="_blank">bruksvilkåra</a> og <a href="%{privacy_policy_path}" target="_blank">personvernvllkåra</a>
-    user_privacy_agreement_html: Eg har lese og godtar <a href="%{privacy_policy_path}" target="_blank">personvernerklæringa</a>
   author_attribution:
     example_title: Eksempeltekst
     hint_html: Skriv du nyhende eller blogginnlegg utanfor Mastodon? Her kan du kontrollera korleis du blir kreditert når artiklane dine blir delte på Mastodon.
@@ -1415,43 +1369,19 @@ nn:
       overwrite: Skriv over
       overwrite_long: Byt ut dei noverande oppføringane med dei nye
     overwrite_preambles:
-      blocking_html:
-        one: Du skal til å <strong>byta ut blokkeringslista di</strong> med opp til <strong>%{count} brukarkonto</strong> frå <strong>%{filename}</strong>.
-        other: Du skal til å <strong>byta ut blokkeringslista di</strong> med opp til <strong>%{count} brukarkontoar</strong> frå <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Du skal til å <strong>byta ut bokmerka dine</strong> med opp til <strong>%{count} innlegg</strong> frå <strong>%{filename}</strong>.
-        other: Du skal til å <strong>byta ut bokmerka dine</strong> med opp til <strong>%{count} innlegg</strong> frå <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Du skal til å <strong>byta ut domeneblokkeringslista di</strong> med opp til <strong>%{count} domene</strong> frå <strong>%{filename}</strong>.
-        other: Du skal til å <strong>byta ut domeneblokkeringslista di</strong> med opp til <strong>%{count} domene</strong> frå <strong>%{filename}</strong>.
-      following_html:
-        one: Du skal til å <strong>fylgja</strong> opp til <strong>%{count} brukarkonto</strong> frå <strong>%{filename}</strong> og <strong>slutta å fylgja alle andre</strong>.
-        other: Du skal til å <strong>fylgja</strong> opp til <strong>%{count} brukarkontoar</strong> frå <strong>%{filename}</strong> og <strong>slutta å fylgja alle andre</strong>.
-      lists_html:
-        one: Du er i ferd med å <strong>erstatta listene dine</strong> med innhaldet i <strong>%{filename}</strong>. Opptil <strong>%{count} konto</strong> vil bli lagt til i dei nye listene.
-        other: Du er i ferd med å <strong>erstatta listene dine</strong> med innhaldet i <strong>%{filename}</strong>. Opptil <strong>%{count} kontoar</strong> vil bli lagt til i dei nye listene.
-      muting_html:
-        one: Du skal til å <strong>byta ut lista di over dempa brukarkontoar</strong> med opp til <strong>%{count} brukarkonto</strong> frå <strong>%{filename}</strong>.
-        other: Du skal til å <strong>byta ut lista di over dempa brukarkontoar</strong> med opp til <strong>%{count} brukarkontoar</strong> frå <strong>%{filename}</strong>.
+      blocking_html: Du skal til å <strong>byta ut blokkeringslista di</strong> med opp til <strong>%{total_items} brukarkontoar</strong> frå <strong>%{filename}</strong>.
+      bookmarks_html: Du skal til å <strong>byta ut bokmerka dine</strong> med opp til <strong>%{total_items} innlegg</strong> frå <strong>%{filename}</strong>.
+      domain_blocking_html: Du skal til å <strong>byta ut domeneblokkeringslista di</strong> med opp til <strong>%{total_items} domene</strong> frå <strong>%{filename}</strong>.
+      following_html: Du skal til å <strong>fylgja</strong> opp til <strong>%{total_items} brukarkontoar</strong> frå <strong>%{filename}</strong> og <strong>slutta å fylgja alle andre</strong>.
+      lists_html: Du er i ferd med å <strong>erstatta listene dine</strong> med innhaldet i <strong>%{filename}</strong>. Opptil <strong>%{total_items} kontoar</strong> vil bli lagt til i nye lister.
+      muting_html: Du skal til å <strong>byta ut lista di over dempa brukarkontoar</strong> med opp til <strong>%{total_items} brukarkontoar</strong> frå <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Du skal til å <strong>blokkera</strong> opp til <strong>%{count} brukarkonto</strong> frå <strong>%{filename}</strong>.
-        other: Du skal til å <strong>blokkera</strong> opp til <strong>%{count} brukarkontoar</strong> frå <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Du er i ferd med å leggja til opp til <strong>%{count} innlegg</strong> frå <strong>%{filename}</strong> til <strong>bokmerka dine</strong>.
-        other: Du er i ferd med å leggja til opp til <strong>%{count} innlegg</strong> frå <strong>%{filename}</strong> til <strong>bokmerka dine</strong>.
-      domain_blocking_html:
-        one: Du skal til å <strong>blokkera</strong> opp til <strong>%{count} domene</strong> frå <strong>%{filename}</strong>.
-        other: Du skal til å <strong>blokkera</strong> opp til <strong>%{count} domene</strong> frå <strong>%{filename}</strong>.
-      following_html:
-        one: Du er i ferd med å <strong>fylgja</strong> opp til <strong>%{count} brukarkonto</strong> frå <strong>%{filename}</strong>.
-        other: Du er i ferd med å <strong>fylgja</strong> opp til <strong>%{count} brukarkontoar</strong> frå <strong>%{filename}</strong>.
-      lists_html:
-        one: Du er i ferd med å leggja til opptil <strong>%{count} konto</strong> frå <strong>%{filename}</strong> til i <strong>listene</strong> dine. Nye lister vil blir oppretta om ingen lister finst frå før.
-        other: Du er i ferd med å leggja til opptil <strong>%{count} kontoar</strong> frå <strong>%{filename}</strong> til i <strong>listene</strong> dine. Nye lister vil blir oppretta om ingen lister finst frå før.
-      muting_html:
-        one: Du er i ferd med å <strong>dempa</strong> opp til <strong>%{count} brukarkonto</strong> frå <strong>%{filename}</strong>.
-        other: Du er i ferd med å <strong>dempa</strong> opp til <strong>%{count} brukarkontoar</strong> frå <strong>%{filename}</strong>.
+      blocking_html: Du skal til å <strong>blokkera</strong> opp til <strong>%{total_items} brukarkontoar</strong> frå <strong>%{filename}</strong>.
+      bookmarks_html: Du skal til å leggja til opp til <strong>%{total_items} innlegg</strong> frå <strong>%{filename}</strong> til <strong>bokmerka dine</strong>.
+      domain_blocking_html: Du skal til å <strong>blokkera</strong> opp til <strong>%{total_items} domene</strong> frå <strong>%{filename}</strong>.
+      following_html: Du skal til å <strong>fylgja</strong> opp til <strong>%{total_items} brukarkontoar</strong> frå <strong>%{filename}</strong>.
+      lists_html: Du er i ferd med å leggja til opptil <strong>%{total_items} kontoar</strong> frå <strong>%{filename}</strong> til i <strong>listene</strong> dine. Nye lister vil blir oppretta om ingen lister finst frå før.
+      muting_html: Du skal til å <strong>dempa</strong> opp til <strong>%{total_items} brukarkontoar</strong> frå <strong>%{filename}</strong>.
     preface: Du kan henta inn data som du har eksportert frå ein annan tenar, som t.d. ei liste over folka du fylgjer eller blokkerer.
     recent_imports: Siste importar
     states:
@@ -1708,7 +1638,7 @@ nn:
   scheduled_statuses:
     over_daily_limit: Du har overskredet grensen på %{limit} planlagte tuter for den dagen
     over_total_limit: Du har overskredet grensen på %{limit} planlagte tuter
-    too_soon: datoen må vera i framtida
+    too_soon: Den planlagte datoen må være i fremtiden
   self_destruct:
     lead_html: Diverre stengjer <strong>%{domain}</strong> dørene for godt. Viss du hadde ein brukarkonto der, vil du ikkje kunna halda fram å bruka han, men du kan få ut ein tryggingskopi av dataa dine.
     title: Denne tenaren stengjer
@@ -1871,8 +1801,6 @@ nn:
       too_late: Det er for seint å klage på denne prikken
   tags:
     does_not_match_previous_name: stemmar ikkje med det førre namnet
-  terms_of_service:
-    title: Bruksvilkår
   themes:
     contrast: Mastodon (Høg kontrast)
     default: Mastodon (Mørkt)
@@ -1933,13 +1861,6 @@ nn:
       further_actions_html: Om dette ikkje var deg, tilrår vi at du %{action} no og aktiverar 2-trinnsinnlogging for å halde kontoen din sikker.
       subject: Din konto er opna frå ei ny IP-adresse
       title: Ei ny pålogging
-    terms_of_service_changed:
-      agreement: Viss du held fram å bruka %{domain}, seier du deg einig i vilkåra. Viss du er usamd i dei oppdaterte vilkåra, kan du slutta å bruka %{domain} når du vil ved å sletta brukarkontoen din.
-      changelog: 'Denne oppdateringa, kort fortalt:'
-      sign_off: Folka på %{domain}
-      subject: Endra bruksvilkår
-      subtitle: Bruksvilkåra på %{domain} er endra
-      title: Viktig oppdatering
     warning:
       appeal: Send inn anke
       appeal_description: Om du meiner dette er ein feil, kan du sende inn ei klage til gjengen i %{instance}.
diff --git a/config/locales/no.yml b/config/locales/no.yml
index d70e60df0a..567775998a 100644
--- a/config/locales/no.yml
+++ b/config/locales/no.yml
@@ -773,7 +773,6 @@
       batch:
         remove_from_report: Fjern fra rapport
         report: Rapport
-      contents: Innhold
       deleted: Slettet
       favourites: Favoritter
       history: Versjonshistorikk
@@ -787,6 +786,7 @@
       original_status: Opprinnelig innlegg
       reblogs: Fremheve
       status_changed: Innlegg endret
+      title: Kontostatuser
       trending: Populært
       visibility: Synlighet
       with_media: Med media
@@ -854,13 +854,6 @@
       search: Søk
       title: Emneknagger
       updated_msg: Emneknagg innstillinger vellykket oppdatert
-    terms_of_service:
-      changelog: Hva har blitt endret
-      current: Nåværende
-      draft: Kladd
-      history: Historikk
-      publish: Publiser
-      title: Bruksvilkår
     title: Administrasjon
     trends:
       allow: Tillat
@@ -1051,6 +1044,7 @@
     migrate_account: Flytt til en annen konto
     migrate_account_html: Hvis du ønsker å henvise denne kontoen til en annen, kan du <a href="%{path}">konfigurere det her</a>.
     or_log_in_with: Eller logg inn med
+    privacy_policy_agreement_html: Jeg har lest og godtar <a href="%{privacy_policy_path}" target="_blank">retningslinjer for personvern</a>
     progress:
       confirm: Bekreft E-postadressen
       details: Dine opplysninger
@@ -1074,6 +1068,7 @@
     security: Sikkerhet
     set_new_password: Angi nytt passord
     setup:
+      email_settings_hint_html: Klikk på lenken vi sendte deg for å bekrefte %{email}. Vi venter her.
       link_not_received: Fikk du ikke lenken?
       title: Sjekk innboksen din
     sign_in:
@@ -1081,6 +1076,7 @@
       title: Logg inn på %{domain}
     sign_up:
       manual_review: Registreringer på %{domain} krever manuell gjennomgang av moderatorene våre. For å hjelpe oss med å behandle registreringen din, skriv litt om deg selv og hvorfor du vil ha en konto på %{domain}.
+      preamble: Med en konto på denne Mastodon-tjeneren vil du kunne følge andre personer på nettverket, uansett hvor kontoen deres holder til.
       title: La oss få deg satt i gang på %{domain}.
     status:
       account_status: Kontostatus
@@ -1283,6 +1279,20 @@
       merge_long: Behold eksisterende og legg til nye
       overwrite: Overskriv
       overwrite_long: Erstatt gjeldende med de nye
+    overwrite_preambles:
+      blocking_html: Du er i ferd med å <strong>erstatte din blokkeringsliste</strong> med inntil <strong>%{total_items} kontoer</strong> fra <strong>%{filename}</strong>.
+      bookmarks_html: Du er i ferd med å <strong>erstatte dine bokmerker</strong> med inntil <strong>%{total_items} innlegg</strong> fra <strong>%{filename}</strong>.
+      domain_blocking_html: Du er i ferd med å <strong>erstatte din domene-blokkeringsliste</strong> med inntil <strong>%{total_items} domener</strong> fra <strong>%{filename}</strong>.
+      following_html: Du er i ferd med å <strong>følge</strong> inntil <strong>%{total_items} kontoer</strong> fra <strong>%{filename}</strong> og <strong>slutte å følge alle andre</strong>.
+      lists_html: Du er i ferd med å <strong>erstatte dine lister</strong> med innholdet i <strong>%{filename}</strong>. Inntil <strong>%{total_items} kontoer</strong> legges til i nye lister.
+      muting_html: Du er i ferd med å <strong>erstatte listen over dempede kontoer</strong> med inntil <strong>%{total_items} kontoer</strong> fra <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Du er i ferd med å <strong>blokkere</strong> inntil <strong>%{total_items} kontoer</strong> fra <strong>%{filename}</strong>.
+      bookmarks_html: Du er i ferd med å legge til inntil <strong>%{total_items} innlegg</strong> fra <strong>%{filename}</strong> til dine <strong>bokmerker</strong>.
+      domain_blocking_html: Du er i ferd med å <strong>blokkere</strong> inntil <strong>%{total_items} domener</strong> fra <strong>%{filename}</strong>.
+      following_html: Du er i ferd med å <strong>følge</strong> inntil <strong>%{total_items} kontoer</strong> fra <strong>%{filename}</strong>.
+      lists_html: Du er i ferd med å legge inntil <strong>%{total_items} kontoer</strong> fra <strong>%{filename}</strong> til dine <strong>lister</strong>. Nye lister vil bli opprettet hvis det ikke finnes noen liste å legge til.
+      muting_html: Du er i ferd med å <strong>dempe</strong> inntil <strong>%{total_items} kontoer</strong> fra <strong>%{filename}</strong>.
     preface: Du kan importere data om brukere du følger eller blokkerer til kontoen din på denne instansen med eksportfiler fra andre instanser.
     recent_imports: Siste importer
     states:
@@ -1525,6 +1535,7 @@
   scheduled_statuses:
     over_daily_limit: Du har overskredet grensen på %{limit} planlagte innlegg for i dag
     over_total_limit: Du har overskredet grensen på %{limit} planlagte innlegg
+    too_soon: Den planlagte datoen må være i fremtiden
   self_destruct:
     lead_html: Dessverre stenger <strong>%{domain}</strong> for alltid. Hvis du hadde en konto der vil du ikke kunne fortsette å bruke den, men du kan fremdeles be om en sikkerhetskopi av dataene dine.
     title: Denne serveren stenger
@@ -1676,8 +1687,6 @@
       too_late: Det er for sent å anke denne advarselen
   tags:
     does_not_match_previous_name: samsvarer ikke med det forrige navnet
-  terms_of_service:
-    title: Bruksvilkår
   themes:
     contrast: Mastodon (Høykontrast)
     default: Mastodon
diff --git a/config/locales/oc.yml b/config/locales/oc.yml
index 258348d41c..a30126c44b 100644
--- a/config/locales/oc.yml
+++ b/config/locales/oc.yml
@@ -401,6 +401,7 @@ oc:
       media:
         title: Mèdia
       no_status_selected: Cap d’estatut pas cambiat estant que cap èra pas seleccionat
+      title: Estatuts del compte
       visibility: Visibilitat
       with_media: Amb mèdia
     system_checks:
@@ -481,6 +482,7 @@ oc:
     migrate_account: Mudar endacòm mai
     migrate_account_html: Se volètz mandar los visitors d’aqueste compte a un autre, podètz<a href="%{path}"> o configurar aquí</a>.
     or_log_in_with: O autentificatz-vos amb
+    privacy_policy_agreement_html: Ai legit e accepti la <a href="%{privacy_policy_path}" target="_blank">politica de confidencialitat</a>
     providers:
       cas: CAS
       saml: SAML
@@ -764,6 +766,7 @@ oc:
   scheduled_statuses:
     over_daily_limit: Avètz passat la limita de %{limit}  tuts programats per aquel jorn
     over_total_limit: Avètz passat la limita de %{limit}  tuts programats
+    too_soon: La data planificada deu èsser dins lo futur
   sessions:
     activity: Darrièra activitat
     browser: Navigator
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 50f39591af..0187ed68be 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -193,7 +193,6 @@ pl:
         create_domain_block: Utwórz blokadę domeny
         create_email_domain_block: Utwórz blokadę domeny e-mail
         create_ip_block: Utwórz regułę IP
-        create_relay: Utwórz przekaźnik
         create_unavailable_domain: Utwórz niedostępną domenę
         create_user_role: Utwórz rolę
         demote_user: Zdegraduj użytkownika
@@ -205,22 +204,18 @@ pl:
         destroy_email_domain_block: Usuń blokadę domeny e-mail
         destroy_instance: Wyczyść domenę
         destroy_ip_block: Usuń regułę IP
-        destroy_relay: Usuń przekaźnik
         destroy_status: Usuń wpis
         destroy_unavailable_domain: Usuń niedostępną domenę
         destroy_user_role: Zlikwiduj rolę
         disable_2fa_user: Wyłącz 2FA
         disable_custom_emoji: Wyłącz niestandardowe emoji
-        disable_relay: Wyłącz przekaźnik
         disable_sign_in_token_auth_user: Wyłącz uwierzytelnianie tokenem przez e-mail dla użytkownika
         disable_user: Wyłącz użytkownika
         enable_custom_emoji: Włącz niestandardowe emoji
-        enable_relay: Włącz przekaźnik
         enable_sign_in_token_auth_user: Włącz uwierzytelnianie tokenem przez e-mail dla użytkownika
         enable_user: Włącz użytkownika
         memorialize_account: Upamiętnij konto
         promote_user: Podnieś uprawnienia
-        publish_terms_of_service: Opublikuj Regulamin
         reject_appeal: Odrzuć odwołanie
         reject_user: Odrzuć użytkownika
         remove_avatar_user: Usuń awatar
@@ -258,7 +253,6 @@ pl:
         create_domain_block_html: "%{name} zablokował(a) domenę %{target}"
         create_email_domain_block_html: "%{name} dodał(a) domenę e-mail %{target} na czarną listę"
         create_ip_block_html: "%{name} stworzył(a) regułę dla IP %{target}"
-        create_relay_html: "%{name} utworzył przekaźnik %{target}"
         create_unavailable_domain_html: "%{name} przestał(a) doręczać na domenę %{target}"
         create_user_role_html: "%{name} utworzył rolę %{target}"
         demote_user_html: "%{name} zdegradował(a) użytkownika %{target}"
@@ -270,22 +264,18 @@ pl:
         destroy_email_domain_block_html: "%{name} usunął(-ęła) domenę e-mail %{target} z czarnej listy"
         destroy_instance_html: "%{name} usunął domenę %{target}"
         destroy_ip_block_html: "%{name} usunął(-ęła) regułę dla IP %{target}"
-        destroy_relay_html: "%{name} usunął przekaźnik %{target}"
         destroy_status_html: "%{name} usunął(-ęła) wpis użytkownika %{target}"
         destroy_unavailable_domain_html: "%{name} wznowił(a) doręczanie do domeny %{target}"
         destroy_user_role_html: "%{name} usunął rolę %{target}"
         disable_2fa_user_html: "%{name} wyłączył(a) uwierzytelnianie dwuskładnikowe użytkownikowi %{target}"
         disable_custom_emoji_html: "%{name} wyłączył(a) emoji %{target}"
-        disable_relay_html: "%{name} wyłączył przekaźnik %{target}"
         disable_sign_in_token_auth_user_html: "%{name} wyłączył/a uwierzytelnianie tokenem przez e-mail dla %{target}"
         disable_user_html: "%{name} zablokował(a) możliwość logowania użytkownikowi %{target}"
         enable_custom_emoji_html: "%{name} włączył(a) emoji %{target}"
-        enable_relay_html: "%{name} włączył przekaźnik %{target}"
         enable_sign_in_token_auth_user_html: "%{name} włączył/a uwierzytelnianie tokenem przez e-mail dla %{target}"
         enable_user_html: "%{name} przywrócił(a) możliwość logowania użytkownikowi %{target}"
         memorialize_account_html: "%{name} nadał(a) kontu %{target} status in memoriam"
         promote_user_html: "%{name} podniósł(a) uprawnienia użytkownikowi %{target}"
-        publish_terms_of_service_html: "%{name} opublikował aktualizacje warunków korzystania z usługi"
         reject_appeal_html: "%{name} zatwierdził(-a) odwołanie decyzji moderacyjnej od %{target}"
         reject_user_html: "%{name} odrzucił rejestrację od %{target}"
         remove_avatar_user_html: "%{name} usunął(-ęła) awatar użytkownikowi %{target}"
@@ -856,10 +846,8 @@ pl:
       back_to_account: Wróć na konto
       back_to_report: Wróć do strony zgłoszenia
       batch:
-        add_to_report: 'Dodaj do raportu #%{id}'
         remove_from_report: Usuń ze zgłoszenia
         report: Zgłoszenie
-      contents: Zawartość
       deleted: Usunięto
       favourites: Ulubione
       history: Historia wersji
@@ -868,17 +856,13 @@ pl:
       media:
         title: Multimedia
       metadata: Metadane
-      no_history: Ten wpis nie był edytowany
       no_status_selected: Żaden wpis nie został zmieniony, bo żaden nie został wybrany
       open: Otwarty post
       original_status: Oryginalny post
       reblogs: Podbicia
-      replied_to_html: Odpowiedziano na %{acct_link}
       status_changed: Post zmieniony
-      status_title: Wpis @%{name}
-      title: Wpisy konta - @%{name}
+      title: Wpisy konta
       trending: Popularne
-      view_publicly: Wyświetl publicznie
       visibility: Widoczność
       with_media: Z zawartością multimedialną
     strikes:
@@ -955,37 +939,6 @@ pl:
       search: Szukaj
       title: Hashtagi
       updated_msg: Pomyślnie uaktualniono ustawienia hashtagów
-    terms_of_service:
-      back: Powrót do warunków świadczenia usług
-      changelog: Co się zmieniło
-      create: Użyj własnego
-      current: Aktualna wersja
-      draft: Szkic
-      generate: Użyj szablonu
-      generates:
-        action: Wygeneruj
-        chance_to_review_html: "<strong>Wygenerowane warunki świadczenia usług nie zostaną automatycznie opublikowane.</strong> Będziesz miał szansę je sprawdzić raz jeszcze. Wypełnij wymagane pola, aby kontynuować."
-        explanation_html: Dostarczany wzór warunków świadczenia usług służy wyłącznie celom informacyjnym i nie powinien być interpretowany jako porada prawna w odniesieniu do żadnego przedmiotu. Proszę skonsultować się ze swoim doradcą prawnym w sprawie swojej sytuacji i konkretnych kwestii prawnych, które państwo posiadają.
-        title: Warunki korzystania z usługi
-      history: Historia
-      live: Rzeczywiste
-      no_history: Nie ma jeszcze zapisanych zmian w warunkach korzystania z usługi.
-      no_terms_of_service_html: Obecnie nie masz żadnych skonfigurowanych warunków korzystania z usługi. Warunki korzystania z usługi mają zapewnić jasność i ochronę przed potencjalnymi zobowiązaniami w sporach z użytkownikami.
-      notified_on_html: Użytkownicy powiadomieni o %{date}
-      notify_users: Powiadom użytkowników
-      preview:
-        explanation_html: 'E-mail zostanie wysłany do <strong>%{display_count} użytkowników</strong>, którzy zarejestrowali się przed %{date}. Otrzymają oni wiadomość o następującej treści:'
-        send_preview: Wyślij podgląd do %{email}
-        send_to_all:
-          few: Wyślij %{display_count} wiadomości e-mail
-          many: Wyślij %{display_count} wiadomości e-mail
-          one: Wyślij %{display_count} wiadomość e-mail
-          other: Wyślij %{display_count} wiadomości e-mail
-        title: Podgląd warunków powiadomienia o usłudze
-      publish: Publikuj
-      published_on_html: Opublikowano w dniu %{date}
-      save_draft: Zapisz wersję roboczą
-      title: Warunki korzystania z usługi
     title: Administracja
     trends:
       allow: Zezwól
@@ -1201,6 +1154,7 @@ pl:
     migrate_account: Przenieś konto
     migrate_account_html: Jeżeli chcesz skonfigurować przekierowanie z obecnego konta na inne, możesz <a href="%{path}">zrobić to tutaj</a>.
     or_log_in_with: Lub zaloguj się z użyciem
+    privacy_policy_agreement_html: Przeczytałem/am i akceptuję <a href="%{privacy_policy_path}" target="_blank">politykę prywatności</a>
     progress:
       confirm: Potwierdź adres e-mail
       details: Twoje dane
@@ -1225,7 +1179,7 @@ pl:
     set_new_password: Ustaw nowe hasło
     setup:
       email_below_hint_html: Sprawdź folder ze spamem lub poproś o inny link potwierdzający. Możesz poprawić swój adres e-mail, jeśli jest niepoprawny.
-      email_settings_hint_html: Kliknij link, który wysłaliśmy do %{email} , aby rozpocząć korzystanie z Mastodon. Poczekamy tutaj.
+      email_settings_hint_html: Kliknij link, który wysłaliśmy do Ciebie w celu zweryfikowania %{email}. Poczekamy tutaj.
       link_not_received: Nie otrzymano linku?
       new_confirmation_instructions_sent: Za kilka minut otrzymasz nowy e-mail z linkiem potwierdzającym!
       title: Sprawdź swoją skrzynkę odbiorczą
@@ -1246,8 +1200,6 @@ pl:
       view_strikes: Zobacz dawne ostrzeżenia nałożone na twoje konto
     too_fast: Zbyt szybko przesłano formularz, spróbuj ponownie.
     use_security_key: Użyj klucza bezpieczeństwa
-    user_agreement_html: Przeczytałem i akceptuję <a href="%{terms_of_service_path}" target="_blank">warunki korzystania z usługi</a> oraz <a href="%{privacy_policy_path}" target="_blank">politykę prywatności</a>
-    user_privacy_agreement_html: Przeczytałem/am/o i akceptuję <a href="%{privacy_policy_path}" target="_blank">politykę prywatności</a>
   author_attribution:
     example_title: Przykładowy tekst
     hint_html: Piszesz wiadomości albo bloga poza Mastodonem? Kontroluj jak będą ci przypisywane podczas dizielenia się nimi na Mastodonie.
@@ -1467,67 +1419,19 @@ pl:
       overwrite: Nadpisz
       overwrite_long: Zastąp obecne wpisy nowymi
     overwrite_preambles:
-      blocking_html:
-        few: Zamierzasz <strong>zastąpić swoją listę bloków</strong> maksymalnie <strong>%{count} kontami</strong> z <strong>%{filename}</strong>.
-        many: Zamierzasz <strong>zastąpić swoją listę bloków</strong> maksymalnie <strong>%{count} kontami</strong> z <strong>%{filename}</strong>.
-        one: Zamierzasz <strong>zastąpić swoją listę bloków</strong> maksymalnie <strong>%{count} kontem</strong> z <strong>%{filename}</strong>.
-        other: Zamierzasz <strong>zastąpić swoją listę bloków</strong> maksymalnie <strong>%{count} kontami</strong> z <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: Zamierzasz <strong>zastąpić swoje zakładki</strong> maksymalnie <strong>%{count} wpisami</strong> z <strong>%{filename}</strong>.
-        many: Zamierzasz <strong>zastąpić swoje zakładki</strong> maksymalnie <strong>%{count} wpisami</strong> z <strong>%{filename}</strong>.
-        one: Zamierzasz <strong>zastąpić swoje zakładki</strong> maksymalnie <strong>%{count} wpisem</strong> z <strong>%{filename}</strong>.
-        other: Zamierzasz <strong>zastąpić swoje zakładki</strong> maksymalnie <strong>%{count} wpisami</strong> z <strong>%{filename}</strong>.
-      domain_blocking_html:
-        few: Zamierzasz <strong>zastąpić swoją listę bloków domen</strong> maksymalnie <strong>%{count} domenami</strong> z <strong>%{filename}</strong>.
-        many: Zamierzasz <strong>zastąpić swoją listę bloków domen</strong> maksymalnie <strong>%{count} domenami</strong> z <strong>%{filename}</strong>.
-        one: Zamierzasz <strong>zastąpić swoją listę bloków domen</strong> maksymalnie <strong>%{count} domeną</strong> z <strong>%{filename}</strong>.
-        other: Zamierzasz <strong>zastąpić swoją listę bloków domen</strong> maksymalnie <strong>%{count} domenami</strong> z <strong>%{filename}</strong>.
-      following_html:
-        few: Zamierzasz <strong>zaobserwować</strong> maksymalnie <strong>%{count} konta</strong> z <strong>%{filename}</strong> i <strong>przestać obserwować kogokolwiek innego</strong>.
-        many: Zamierzasz <strong>zaobserwować</strong> maksymalnie <strong>%{count} kont</strong> z <strong>%{filename}</strong> i <strong>przestać obserwować kogokolwiek innego</strong>.
-        one: Zamierzasz <strong>zaobserwować</strong> maksymalnie <strong>%{count} konto</strong> z <strong>%{filename}</strong> i <strong>przestać obserwować kogokolwiek innego</strong>.
-        other: Zamierzasz <strong>zaobserwować</strong> maksymalnie <strong>%{count} kont</strong> z <strong>%{filename}</strong> i <strong>przestać obserwować kogokolwiek innego</strong>.
-      lists_html:
-        few: Zamierzasz <strong>zastąpić swoje listy</strong> maksymalnie <strong>%{count} kontami</strong> z <strong>%{filename}</strong>.
-        many: Zamierzasz <strong>zastąpić swoje listy</strong> maksymalnie <strong>%{count} kontami</strong> z <strong>%{filename}</strong>.
-        one: Zamierzasz <strong>zastąpić swoje listy</strong> maksymalnie <strong>%{count} kontem</strong> z <strong>%{filename}</strong>.
-        other: Zamierzasz <strong>zastąpić swoje listy</strong> maksymalnie <strong>%{count} kontami</strong> z <strong>%{filename}</strong>.
-      muting_html:
-        few: Zamierzasz <strong>zastąpić swoją listę wyciszonych</strong> maksymalnie <strong>%{count} kontami</strong> z <strong>%{filename}</strong>.
-        many: Zamierzasz <strong>zastąpić swoją listę wyciszonych</strong> maksymalnie <strong>%{count} kontami</strong> z <strong>%{filename}</strong>.
-        one: Zamierzasz <strong>zastąpić swoją listę wyciszonych</strong> maksymalnie <strong>%{count} kontem</strong> z <strong>%{filename}</strong>.
-        other: Zamierzasz <strong>zastąpić swoją listę wyciszonych</strong> maksymalnie <strong>%{count} kontami</strong> z <strong>%{filename}</strong>.
+      blocking_html: Zamierzasz <strong>zastąpić swoją listę bloków</strong> maksymalnie <strong>%{total_items} kont</strong> z <strong>%{filename}</strong>.
+      bookmarks_html: Zamierzasz <strong>zastąpić swoje zakładki</strong> maksymalnie <strong>%{total_items} wpisami</strong> z <strong>%{filename}</strong>.
+      domain_blocking_html: Zamierzasz <strong>zastąpić swoją listę bloków domen</strong> maksymalnie <strong>%{total_items} domenami</strong> z <strong>%{filename}</strong>.
+      following_html: Zamierzasz <strong>zaobserwować</strong> maksymalnie <strong>%{total_items} kont</strong> z <strong>%{filename}</strong> i <strong>przestać obserwować kogokolwiek innego</strong>.
+      lists_html: Zamierzasz <strong>zastąpić swoje listy</strong> maksymalnie <strong>%{total_items} kontami</strong> z <strong>%{filename}</strong>.
+      muting_html: Zamierzasz <strong>zastąpić swoją wyciszonych</strong> maksymalnie <strong>%{total_items} kontami</strong> z <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        few: Zamierzasz <strong>zablokować</strong> maksymalnie <strong>%{count} konta</strong> z <strong>%{filename}</strong>.
-        many: Zamierzasz <strong>zablokować</strong> maksymalnie <strong>%{count} kont</strong> z <strong>%{filename}</strong>.
-        one: Zamierzasz <strong>zablokować</strong> maksymalnie <strong>%{count} konto</strong> z <strong>%{filename}</strong>.
-        other: Zamierzasz <strong>zablokować</strong> maksymalnie <strong>%{count} kont</strong> z <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: Zamierzasz dodać maksymalnie <strong>%{count} wpisy</strong> do twoich <strong>zakładek</strong> z <strong>%{filename}</strong>.
-        many: Zamierzasz dodać maksymalnie <strong>%{count} wpisów</strong> do twoich <strong>zakładek</strong> z <strong>%{filename}</strong>.
-        one: Zamierzasz dodać maksymalnie <strong>%{count} wpis</strong> do twoich <strong>zakładek</strong> z <strong>%{filename}</strong>.
-        other: Zamierzasz dodać maksymalnie <strong>%{count} wpisów</strong> do twoich <strong>zakładek</strong> z <strong>%{filename}</strong>.
-      domain_blocking_html:
-        few: Zamierzasz <strong>zablokować</strong> maksymalnie <strong>%{count} domeny</strong> z <strong>%{filename}</strong>.
-        many: Zamierzasz <strong>zablokować</strong> maksymalnie <strong>%{count} domeny</strong> z <strong>%{filename}</strong>.
-        one: Zamierzasz <strong>zablokować</strong> maksymalnie <strong>%{count} domenę</strong> z <strong>%{filename}</strong>.
-        other: Zamierzasz <strong>zablokować</strong> maksymalnie <strong>%{count} domeny</strong> z <strong>%{filename}</strong>.
-      following_html:
-        few: Zamierzasz <strong>zaobserwować</strong> maksymalnie <strong>%{count} konta</strong> z <strong>%{filename}</strong>.
-        many: Zamierzasz <strong>zaobserwować</strong> maksymalnie <strong>%{count} kont</strong> z <strong>%{filename}</strong>.
-        one: Zamierzasz <strong>zaobserwować</strong> maksymalnie <strong>%{count} konto</strong> z <strong>%{filename}</strong>.
-        other: Zamierzasz <strong>zaobserwować</strong> maksymalnie <strong>%{count} kont</strong> z <strong>%{filename}</strong>.
-      lists_html:
-        few: Zamierzasz dodać maksymalnie <strong>%{count} konta</strong> do twoich <strong>list</strong> z <strong>%{filename}</strong>. Jeżeli nie ma list, nowe zostaną utworzone.
-        many: Zamierzasz dodać maksymalnie <strong>%{count} kont</strong> do twoich <strong>list</strong> z <strong>%{filename}</strong>. Jeżeli nie ma list, nowe zostaną utworzone.
-        one: Zamierzasz dodać maksymalnie <strong>%{count} konto</strong> do twoich <strong>list</strong> z <strong>%{filename}</strong>. Jeżeli nie ma list, nowe zostaną utworzone.
-        other: Zamierzasz dodać maksymalnie <strong>%{count} kont</strong> do twoich <strong>list</strong> z <strong>%{filename}</strong>. Jeżeli nie ma list, nowe zostaną utworzone.
-      muting_html:
-        few: Zamierzasz <strong>wyciszyć</strong> maksymalnie <strong>%{count} konta</strong> z <strong>%{filename}</strong>.
-        many: Zamierzasz <strong>wyciszyć</strong> maksymalnie <strong>%{count} kont</strong> z <strong>%{filename}</strong>.
-        one: Zamierzasz <strong>wyciszyć</strong> maksymalnie <strong>%{count} konto</strong> z <strong>%{filename}</strong>.
-        other: Zamierzasz <strong>wyciszyć</strong> maksymalnie <strong>%{count} kont</strong> z <strong>%{filename}</strong>.
+      blocking_html: Zamierzasz <strong>zablokować</strong> maksymalnie <strong>%{total_items} kont</strong> z <strong>%{filename}</strong>.
+      bookmarks_html: Zamierzasz dodać maksymalnie <strong>%{total_items} wpisów</strong> do twoich <strong>zakładek</strong> z <strong>%{filename}</strong>.
+      domain_blocking_html: Zamierzasz <strong>zablokować</strong> maksymalnie <strong>%{total_items} domen</strong> z <strong>%{filename}</strong>.
+      following_html: Zamierzasz <strong>zaobserwować</strong> maksymalnie <strong>%{total_items} kont</strong> z <strong>%{filename}</strong>.
+      lists_html: Zamierzasz dodać maksymalnie <strong>%{total_items} kont</strong> do twoich <strong>list</strong> z <strong>%{filename}</strong>. Jeżeli nie ma list, nowe zostaną utworzone.
+      muting_html: Zamierzasz <strong>wyciszyć</strong> maksymalnie <strong>%{total_items} kont</strong> z <strong>%{filename}</strong>.
     preface: Możesz zaimportować pewne dane (np. lista kont, które obserwujesz lub blokujesz) do swojego konta na tym serwerze, korzystając z danych wyeksportowanych z innego serwera.
     recent_imports: Ostatnie importy
     states:
@@ -1786,7 +1690,7 @@ pl:
   scheduled_statuses:
     over_daily_limit: Przekroczyłeś(-aś) limit %{limit} zaplanowanych wpisów na ten dzień
     over_total_limit: Przekroczyłeś(-aś) limit %{limit} zaplanowanych wpisów
-    too_soon: musi to być przyszła data
+    too_soon: Zaplanowana data musi wypadać w przyszłości
   self_destruct:
     lead_html: Niestety, <strong>%{domain}</strong> jest permanentnie zamykane. Konta z tego serwera nie będą dostępne, ale można jeszcze odzyskać kopię zapasową danych.
     title: Ten serwer jest zamykany
@@ -1957,8 +1861,6 @@ pl:
       too_late: Jest za późno na odwołanie się od tego ostrzeżenia
   tags:
     does_not_match_previous_name: nie pasuje do poprzedniej nazwy
-  terms_of_service:
-    title: Regulamin
   themes:
     contrast: Mastodon (Wysoki kontrast)
     default: Mastodon (Ciemny)
@@ -2019,13 +1921,6 @@ pl:
       further_actions_html: Jeśli to nie Ty, zalecamy %{action} natychmiastowo i włącz uwierzytelnianie dwuetapowe, aby Twoje konto było bezpieczne.
       subject: Uzyskano dostęp do twojego konta z nowego adresu IP
       title: Nowe logowanie
-    terms_of_service_changed:
-      agreement: Kontynuując używanie %{domain}, zgadzasz się na te warunki. Jeśli nie zgadzasz się ze zaktualizowanymi warunkami, możesz wypowiedzieć umowę z %{domain} w dowolnym momencie, usuwając swoje konto.
-      changelog: 'W skrócie oto co oznacza dla Ciebie ta aktualizacja:'
-      sign_off: Zespół %{domain}
-      subject: Aktualizacja warunków korzystania z usług
-      subtitle: Warunki korzystania z %{domain} zmieniają się
-      title: Ważna aktualizacja
     warning:
       appeal: Złóż odwołanie
       appeal_description: Jeśli uważasz, że zaszło nieporozumienie, możesz złożyć odwołanie do zespołu %{instance}.
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index a2e142ab55..e3c56e404b 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -142,7 +142,7 @@ pt-BR:
         only_password: Apenas senha
         password_and_2fa: Senha e autenticação de dois fatores
       sensitive: Sensíveis
-      sensitized: Marcadas como sensíveis
+      sensitized: marcadas como sensíveis
       shared_inbox_url: Link da caixa de entrada compartilhada
       show:
         created_reports: Denúncias criadas
@@ -187,7 +187,6 @@ pt-BR:
         create_domain_block: Bloquear domínio
         create_email_domain_block: Criar Bloqueio de Domínio de Email
         create_ip_block: Criar regra de IP
-        create_relay: Criar Retransmissão
         create_unavailable_domain: Criar domínio indisponível
         create_user_role: Criar cargo
         demote_user: Rebaixar usuário
@@ -199,22 +198,18 @@ pt-BR:
         destroy_email_domain_block: Deletar bloqueio de domínio Email
         destroy_instance: Limpar domínio
         destroy_ip_block: Excluir regra de IP
-        destroy_relay: Excluir Retransmissão
         destroy_status: Excluir publicação
         destroy_unavailable_domain: Excluir domínio indisponível
         destroy_user_role: Destruir cargo
         disable_2fa_user: Desativar autenticação de dois fatores
         disable_custom_emoji: Desativar emoji personalizado
-        disable_relay: Desativar Retransmissão
         disable_sign_in_token_auth_user: Desativar autenticação via Token de Email para Usuário
         disable_user: Desativar usuário
         enable_custom_emoji: Ativar emoji personalizado
-        enable_relay: Ativar Retransmissão
         enable_sign_in_token_auth_user: Ativar autenticação via Token de Email para Usuário
         enable_user: Ativar usuário
         memorialize_account: Converter conta em memorial
         promote_user: Promover usuário
-        publish_terms_of_service: Publicar termos de serviço
         reject_appeal: Rejeitar revisão
         reject_user: Rejeitar usuário
         remove_avatar_user: Remover imagem de perfil
@@ -252,7 +247,6 @@ pt-BR:
         create_domain_block_html: "%{name} bloqueou o domínio %{target}"
         create_email_domain_block_html: "%{name} bloqueou o domínio de e-mail %{target}"
         create_ip_block_html: "%{name} criou a regra para o IP %{target}"
-        create_relay_html: "%{name} criou uma retransmissão %{target}"
         create_unavailable_domain_html: "%{name} parou a entrega ao domínio %{target}"
         create_user_role_html: "%{name} criou o cargo %{target}"
         demote_user_html: "%{name} rebaixou o usuário %{target}"
@@ -264,22 +258,18 @@ pt-BR:
         destroy_email_domain_block_html: "%{name} desbloqueou o domínio de e-mail %{target}"
         destroy_instance_html: "%{name} limpou o domínio %{target}"
         destroy_ip_block_html: "%{name} excluiu a regra para o IP %{target}"
-        destroy_relay_html: "%{name} excluiu uma retransmissão %{target}"
         destroy_status_html: "%{name} removeu a publicação de %{target}"
         destroy_unavailable_domain_html: "%{name} retomou a entrega ao domínio %{target}"
         destroy_user_role_html: "%{name} excluiu o cargo %{target}"
         disable_2fa_user_html: "%{name} desativou a exigência da autenticação de dois fatores para o usuário %{target}"
         disable_custom_emoji_html: "%{name} desativou o emoji %{target}"
-        disable_relay_html: "%{name} desativou uma retransmissão %{target}"
         disable_sign_in_token_auth_user_html: "%{name} desativou a autenticação via token por e-mail para %{target}"
         disable_user_html: "%{name} desativou o login para %{target}"
         enable_custom_emoji_html: "%{name} ativou o emoji %{target}"
-        enable_relay_html: "%{name} ativou uma retransmissão %{target}"
         enable_sign_in_token_auth_user_html: "%{name} ativou a autenticação via token por e-mail para %{target}"
         enable_user_html: "%{name} ativou o login para %{target}"
         memorialize_account_html: "%{name} transformou a conta de %{target} em um memorial"
         promote_user_html: "%{name} promoveu o usuário %{target}"
-        publish_terms_of_service_html: "%{name} publicou atualizações aos termos de serviço"
         reject_appeal_html: "%{name} rejeitou a revisão da decisão da moderação em %{target}"
         reject_user_html: "%{name} rejeitou a inscrição de %{target}"
         remove_avatar_user_html: "%{name} removeu a imagem de perfil de %{target}"
@@ -309,7 +299,6 @@ pt-BR:
       title: Auditar histórico
       unavailable_instance: "(nome de domínio indisponível)"
     announcements:
-      back: Voltar aos anúncios
       destroyed_msg: Anúncio excluído!
       edit:
         title: Editar anúncio
@@ -318,9 +307,6 @@ pt-BR:
       new:
         create: Criar anúncio
         title: Novo anúncio
-      preview:
-        explanation_html: 'Esse e-mail será enviado a <strong>%{display_count} usuários</strong>. O texto a seguir será incluído ao e-mail:'
-        title: Visualizar anúncio
       publish: Publicar
       published_msg: Anúncio publicado!
       scheduled_for: Agendado para %{time}
@@ -479,36 +465,6 @@ pt-BR:
       new:
         title: Importar bloqueio de domínios
       no_file: Nenhum arquivo selecionado
-    fasp:
-      debug:
-        callbacks:
-          created_at: Criado em
-          delete: Apagar
-          ip: Endereço de IP
-          request_body: Corpo da solicitação
-          title: Depurar Callbacks
-      providers:
-        active: Ativo
-        base_url: URL Base
-        callback: Callback
-        delete: Apagar
-        edit: Editar provedor
-        finish_registration: Finalizar o cadastro
-        name: Nome
-        providers: Provedores
-        public_key_fingerprint: Impressão digital de chave pública
-        registration_requested: Cadastro solicitado
-        registrations:
-          confirm: Confirmar
-          description: Você recebeu um registro de um FASP. Rejeite se você não tiver iniciado isso. Se você iniciou isso, compare cuidadosamente o nome e a impressão digital da chave antes de confirmar o registro.
-          reject: Rejeitar
-          title: Confirmar o registro FASP
-        save: Salvar
-        select_capabilities: Selecionar recursos
-        sign_in: Entrar
-        status: Estado
-        title: Provedores de serviços auxiliares do Fediverso
-      title: FASP
     follow_recommendations:
       description_html: "<strong>A recomendação de contas ajuda os novos usuários a encontrar rapidamente conteúdo interessante</strong>. Quando um usuário ainda não tiver interagido o suficiente para gerar recomendações de contas, essas contas serão recomendadas. Essas recomendações são recalculadas diariamente a partir de uma lista de contas com alto engajamento e maior número de seguidores locais em uma dada língua."
       language: Na língua
@@ -862,10 +818,8 @@ pt-BR:
       back_to_account: Voltar para página da conta
       back_to_report: Voltar às denúncias
       batch:
-        add_to_report: 'Adicionar à denúncia #%{id}'
         remove_from_report: Remover da denúncia
         report: Denunciar
-      contents: Conteúdos
       deleted: Excluídos
       favourites: Favoritos
       history: Histórico de versões
@@ -874,17 +828,13 @@ pt-BR:
       media:
         title: Mídia
       metadata: Metadados
-      no_history: Esta publicação não foi editada
       no_status_selected: Nenhuma publicação foi modificada porque nenhuma estava selecionada
       open: Publicação aberta
       original_status: Publicação original
       reblogs: Reblogs
-      replied_to_html: Respondeu à %{acct_link}
       status_changed: Publicação alterada
-      status_title: Publicação de @%{name}
-      title: Publicações da conta - @%{name}
+      title: Publicações da conta
       trending: Em alta
-      view_publicly: Ver publicamente
       visibility: Visibilidade
       with_media: Com mídia
     strikes:
@@ -961,36 +911,6 @@ pt-BR:
       search: Buscar
       title: Hashtags
       updated_msg: Configurações de hashtag atualizadas
-    terms_of_service:
-      back: Voltar aos termos de serviço
-      changelog: O que há de novo
-      create: Usar o meu próprio
-      current: Atual
-      draft: Rascunho
-      generate: Usar modelo
-      generates:
-        action: Gerar
-        chance_to_review_html: "<strong>Os termos de serviço gerado não será publicado automaticamente.</strong>Você terá uma chance de revisar os resultados. Preencha os detalhes necessários para continuar"
-        explanation_html: O modelo de termos de serviço fornecido é apenas para fins informativos e não deve ser interpretado como aconselhamento jurídico sobre qualquer assunto. Consulte seu próprio advogado para esclarecer sua situação e dúvidas jurídicas específicas.
-        title: Configuração dos Termos de Serviço
-      going_live_on_html: Em vigor a partir de %{date}
-      history: Histórico
-      live: Em vigor
-      no_history: Ainda não há alterações registradas nos termos de serviço.
-      no_terms_of_service_html: Atualmente, você não tem nenhum termo de serviço configurado. Os termos de serviço servem para fornecer clareza e protegê-lo de possíveis responsabilidades em disputas com seus usuários.
-      notified_on_html: Usuários notificados em %{date}
-      notify_users: Notificar usuários
-      preview:
-        explanation_html: 'O e-mail será enviado para <strong>%{display_count} usuários</strong> que se inscreveram antes de %{date}. O seguinte texto será incluído no e-mail:'
-        send_preview: Enviar pŕevia para %{email}
-        send_to_all:
-          one: Enviar %{display_count} email
-          other: Enviar %{display_count} emails
-        title: Prévia da notificação dos termos de serviço
-      publish: Publicar
-      published_on_html: Publicado em %{date}
-      save_draft: Salvar rascunho
-      title: Termos de Serviço
     title: Administração
     trends:
       allow: Permitir
@@ -1198,6 +1118,7 @@ pt-BR:
     migrate_account: Mudar-se para outra conta
     migrate_account_html: Se você quer redirecionar essa conta para uma outra você pode <a href="%{path}">configurar isso aqui</a>.
     or_log_in_with: Ou entre com
+    privacy_policy_agreement_html: Eu li e concordo com a <a href="%{privacy_policy_path}" target="_blank">política de privacidade</a>
     progress:
       confirm: Confirmar e-mail
       details: Suas informações
@@ -1222,7 +1143,7 @@ pt-BR:
     set_new_password: Definir uma nova senha
     setup:
       email_below_hint_html: Verifique a sua pasta de spam, ou solicite outra. Você pode corrigir o seu endereço de e-mail se estiver errado.
-      email_settings_hint_html: Clique no link que enviamos para %{email} para começar a usar o Mastodon. Estaremos esperando aqui.
+      email_settings_hint_html: Clique no link que te enviamos para verificar %{email}. Esperaremos aqui.
       link_not_received: Não recebeu um link?
       new_confirmation_instructions_sent: Você receberá um novo e-mail com o link de confirmação em alguns minutos!
       title: Verifique sua caixa de entrada
@@ -1231,7 +1152,7 @@ pt-BR:
       title: Entrar em %{domain}
     sign_up:
       manual_review: Inscrições no %{domain} passam pela revisão manual dos nossos moderadores. Para nos ajudar a processar o seu cadastro, escreva um pouco sobre você e por que você quer uma conta no %{domain}.
-      preamble: Com uma conta neste servidor do Mastodon, você poderá seguir qualquer outra pessoa no fediverso, independentemente de onde a conta dela esteja hospedada.
+      preamble: Com uma conta neste servidor Mastodon, você poderá seguir qualquer outra pessoa na rede, independentemente de onde sua conta esteja hospedada.
       title: Então vamos lá criar uma conta em %{domain}.
     status:
       account_status: Status da conta
@@ -1243,8 +1164,6 @@ pt-BR:
       view_strikes: Veja os avisos anteriores em relação à sua conta
     too_fast: O formulário foi enviado muito rapidamente, tente novamente.
     use_security_key: Usar chave de segurança
-    user_agreement_html: Eu li e concordo com os <a href="%{terms_of_service_path}" target="_blank">termos de serviço</a> e <a href="%{privacy_policy_path}" target="_blank">política de privacidade</a>.
-    user_privacy_agreement_html: Eu li e concordo com a <a href="%{privacy_policy_path}" target="_blank">política de privacidade</a>.
   author_attribution:
     example_title: Texto de amostra
     hint_html: Você está escrevendo notícias ou artigos de blogs fora do Mastodon? Controle como você é credenciado quando eles forem compartilhados no Mastodon.
@@ -1450,43 +1369,19 @@ pt-BR:
       overwrite: Sobrescrever
       overwrite_long: Substituir os registros atuais com os novos
     overwrite_preambles:
-      blocking_html:
-        one: Você está prestes a <strong>trocar seu bloco de listas</strong> com mais de <strong>%{count} conta</strong> de <strong>%{filename}</strong>.
-        other: Você está prestes a <strong>substituir sua lista de blocos</strong> com mais de <strong>%{count} contas</strong> de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Você está prestes a <strong>substituir seus salvos</strong> por até <strong>%{count} publicação</strong> de <strong>%{filename}</strong>.
-        other: Você está prestes a <strong>substituir seus salvos</strong> por até <strong>%{count} publicações</strong> de <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Você está prestes a <strong>substituir seu bloco de lista do domínio</strong> com <strong>%{count} domínio</strong> de <strong>%{filename}</strong>.
-        other: Você está prestes a <strong>substituir sua lista de bloqueio de domínio</strong> com <strong>domínios%{count}</strong> de <strong>%{filename}</strong>.
-      following_html:
-        one: Você está prestes a <strong>seguir</strong> <strong>%{count} contas</strong> de <strong>%{filename}</strong> e <strong>parar de seguir todos os outros</strong>.
-        other: Você está prestes a <strong>seguir</strong> até <strong>%{count} contas</strong> de <strong>%{filename}</strong> e <strong>deixar de seguir qualquer outra pessoa</strong>.
-      lists_html:
-        one: Você está prestes a <strong>substituir suas listas</strong> pelo conteúdo de <strong>%{filename}</strong>. Até <strong>%{count} conta</strong> será adicionada às novas listas.
-        other: Você está prestes a <strong>substituir suas listas</strong> pelo conteúdo de <strong>%{filename}</strong>. Até <strong>%{count} contas</strong> serão adicionadas às novas listas.
-      muting_html:
-        one: Você está prestes a <strong>substituir sua lista de contas silenciadas</strong> por até <strong>%{count} conta</strong> de <strong>%{filename}</strong>.
-        other: Você está prestes a <strong>substituir sua lista de contas silenciadas</strong> por até <strong>%{count} contas</strong> de <strong>%{filename}</strong>.
+      blocking_html: Você está prestes a <strong>substituir sua lista de bloqueios</strong> com até <strong>contas%{total_items}</strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Você está prestes a <strong>substituir seus favoritos</strong> por até <strong>%{total_items} posts</strong> de <strong>%{filename}</strong>.
+      domain_blocking_html: Você está prestes a <strong>substituir sua lista de bloqueio de domínio</strong> com até <strong>domínios%{total_items}</strong> de <strong>%{filename}</strong>.
+      following_html: Você está prestes a <strong>seguir</strong> até <strong>%{total_items} contas</strong> de <strong>%{filename}</strong> e <strong>parar de seguir todos os outros</strong>.
+      lists_html: Você está prestes a <strong>substituir sua lista</strong> pelo conteúdo de <strong>%{filename}</strong>. Até <strong>%{total_items} contas</strong> serão adicionadas a novas listas.
+      muting_html: Você está prestes a <strong>substituir sua lista de contas silenciadas</strong> com até <strong>%{total_items} contas</strong> de <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Você está prestes a <strong>bloquear</strong> até <strong>%{count} conta</strong> de <strong>%{filename}</strong>.
-        other: Você está prestes a <strong>bloquear</strong> até <strong>%{count} contas</strong> de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Você está prestes a adicionar até <strong>%{count} post</strong> de <strong>%{filename}</strong> aos seus <strong>favoritos</strong>.
-        other: Você está prestes a adicionar até <strong>%{count} posts</strong> de <strong>%{filename}</strong> aos seus <strong>favoritos</strong>.
-      domain_blocking_html:
-        one: Você está prestes a <strong>bloquear</strong> até <strong>%{count} domínio</strong> de <strong>%{filename}</strong>.
-        other: Você está prestes a <strong>bloquear</strong> até <strong>%{count} domínios</strong> de <strong>%{filename}</strong>.
-      following_html:
-        one: Você está prestes a <strong>seguir</strong> até <strong>%{count} conta</strong> de <strong>%{filename}</strong>.
-        other: Você está prestes a <strong>seguir</strong> até <strong>%{count} contas</strong> de <strong>%{filename}</strong>.
-      lists_html:
-        one: Você está prestes a adicionar até <strong>%{count} conta</strong> a partir de <strong>%{filename}</strong> para suas <strong>listas</strong>. Novas listas serão criadas se não houver uma para a qual adicionar.
-        other: Você está prestes a adicionar até <strong>%{count} contas</strong> a partir de <strong>%{filename}</strong> para suas <strong>listas</strong>. Novas listas serão criadas se não houver uma para a qual adicionar.
-      muting_html:
-        one: Você está prestes a <strong>silenciar</strong> <strong>%{count}  conta</strong> de <strong>%{filename}</strong>.
-        other: Você está prestes a <strong>silenciar</strong> mais de <strong>%{count} contas</strong> de <strong>%{filename}</strong>.
+      blocking_html: Você está prestes a <strong>bloquear</strong> até <strong>%{total_items} contas</strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Você está prestes a adicionar até <strong>%{total_items} posts</strong> de <strong>%{filename}</strong> para seus <strong>favoritos</strong>.
+      domain_blocking_html: Você está prestes a <strong>bloquear</strong> até <strong>%{total_items} domínios</strong> de <strong>%{filename}</strong>.
+      following_html: Você está prestes a <strong>seguir</strong> até <strong>%{total_items} contas</strong> de <strong>%{filename}</strong>.
+      lists_html: Você está prestes a adicionar até <strong>%{total_items} contas</strong> a partir de <strong>%{filename}</strong> para suas <strong>listas</strong>. Novas listas serão criadas se não houver uma para a qual adicionar.
+      muting_html: Você está prestes a <strong>silenciar</strong> até <strong>contas%{total_items}</strong> de <strong>%{filename}</strong>.
     preface: Você pode importar dados que você exportou de outro servidor, como a lista de pessoas que você segue ou bloqueou.
     recent_imports: Importações recentes
     states:
@@ -1743,7 +1638,7 @@ pt-BR:
   scheduled_statuses:
     over_daily_limit: Você excedeu o limite de %{limit} publicações agendadas para esse dia
     over_total_limit: Você excedeu o limite de %{limit} publicações agendadas
-    too_soon: a data deve ser no futuro
+    too_soon: A data agendada precisa ser no futuro
   self_destruct:
     lead_html: Infelizmente, <strong>%{domain}</strong> está se encerrando de forma permanente. Se você tem uma conta lá, não poderá continuar a usá-la, mas ainda pode solicitar uma cópia dos seus dados.
     title: Este servidor está sendo fechado
@@ -1906,8 +1801,6 @@ pt-BR:
       too_late: É tarde demais para solicitar uma revisão desta punição
   tags:
     does_not_match_previous_name: não corresponde ao nome anterior
-  terms_of_service:
-    title: Termos de Serviço
   themes:
     contrast: Mastodon  (Alto contraste)
     default: Mastodon (Noturno)
@@ -1939,10 +1832,6 @@ pt-BR:
     recovery_instructions_html: Se você perder acesso ao seu celular, você pode usar um dos códigos de recuperação abaixo para acessar a sua conta. <strong>Mantenha os códigos de recuperação em um local seguro</strong>. Por exemplo, você pode imprimi-los e guardá-los junto a outros documentos importantes.
     webauthn: Chaves de segurança
   user_mailer:
-    announcement_published:
-      description: 'Os administradores do %{domain} estão fazendo um anúncio:'
-      subject: Anúncio de serviço
-      title: Anúncio de serviço de %{domain}
     appeal_approved:
       action: Configurações da conta
       explanation: A revisão da punição na sua conta em %{strike_date} que você enviou em %{appeal_date} foi aprovada. Sua conta está novamente em situação regular.
@@ -1972,15 +1861,6 @@ pt-BR:
       further_actions_html: Se não foi você, recomendamos que você %{action} imediatamente e ative a autenticação de dois fatores para manter sua conta segura.
       subject: Sua conta foi acessada a partir de um novo endereço IP
       title: Um novo login
-    terms_of_service_changed:
-      agreement: Ao continuar a usar %{domain}, você concorda com estes termos. Se discordar dos termos atualizados, poderá encerrar seu acordo com %{domain} a qualquer momento excluindo sua conta.
-      changelog: 'Em resumo, veja o que essa atualização significa para você:'
-      description: 'Você recebeu este e-mail porque faremos algumas mudanças nos Termos de Serviço do %{domain}. Essas atualizações entrarão em vigor a partir de %{date}. Veja o que mudou aqui:'
-      description_html: Você recebeu este e-mail porque faremos algumas mudanças nos Termos de Serviço do %{domain}. Essas atualizações entrarão em vigor a partir de <strong>%{date}</strong>. <a href="%{path}" target="_blank">Veja o que mudou aqui</a>.
-      sign_off: A equipe do %{domain}
-      subject: Atualizações dos nossos termos de serviço
-      subtitle: Os termos de serviço do %{domain} estão mudando.
-      title: atualização importante
     warning:
       appeal: Enviar uma revisão
       appeal_description: Se você acredita que isso é um erro, você pode enviar uma revisão para a equipe de %{instance}.
diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml
index ccd20e209d..2f8bd2da8f 100644
--- a/config/locales/pt-PT.yml
+++ b/config/locales/pt-PT.yml
@@ -187,7 +187,6 @@ pt-PT:
         create_domain_block: Criar bloqueio de domínio
         create_email_domain_block: Criar bloqueio de domínio de e-mail
         create_ip_block: Criar regra de IP
-        create_relay: Criar retransmissor
         create_unavailable_domain: Criar domínio indisponível
         create_user_role: Criar função
         demote_user: Despromover utilizador
@@ -199,22 +198,18 @@ pt-PT:
         destroy_email_domain_block: Eliminar bloqueio de domínio de e-mail
         destroy_instance: Purgar domínio
         destroy_ip_block: Eliminar regra de IP
-        destroy_relay: Eliminar retransmissor
         destroy_status: Eliminar publicação
         destroy_unavailable_domain: Eliminar domínio indisponível
         destroy_user_role: Eliminar função
         disable_2fa_user: Desativar 2FA
         disable_custom_emoji: Desativar emoji personalizado
-        disable_relay: Desativar retransmissor
         disable_sign_in_token_auth_user: Desativar token de autenticação por e-mail para o utilizador
         disable_user: Desativar utilizador
         enable_custom_emoji: Ativar emoji personalizado
-        enable_relay: Ativar retransmissor
         enable_sign_in_token_auth_user: Ativar token de autenticação por e-mail para o utilizador
         enable_user: Ativar utilizador
         memorialize_account: Transformar conta num memorial
         promote_user: Promover utilizador
-        publish_terms_of_service: Publicar Termos de Serviço
         reject_appeal: Rejeitar contestação
         reject_user: Rejeitar utilizador
         remove_avatar_user: Remover imagem de perfil
@@ -252,7 +247,6 @@ pt-PT:
         create_domain_block_html: "%{name} bloqueou o domínio %{target}"
         create_email_domain_block_html: "%{name} bloqueou o domínio de e-mail %{target}"
         create_ip_block_html: "%{name} criou uma regra para o IP %{target}"
-        create_relay_html: "%{name} criou o retransmissor %{target}"
         create_unavailable_domain_html: "%{name} parou as entregas ao domínio %{target}"
         create_user_role_html: "%{name} criou a função %{target}"
         demote_user_html: "%{name} despromoveu o utilizador %{target}"
@@ -264,22 +258,18 @@ pt-PT:
         destroy_email_domain_block_html: "%{name} desbloqueou o domínio de e-mail %{target}"
         destroy_instance_html: "%{name} purgou o domínio %{target}"
         destroy_ip_block_html: "%{name} eliminou a regra para o IP %{target}"
-        destroy_relay_html: "%{name} eliminou o retransmissor %{target}"
         destroy_status_html: "%{name} removeu a publicação de %{target}"
         destroy_unavailable_domain_html: "%{name} retomou as entregas ao domínio %{target}"
         destroy_user_role_html: "%{name} eliminou a função %{target}"
         disable_2fa_user_html: "%{name} desativou o requerimento de autenticação em dois passos para o utilizador %{target}"
         disable_custom_emoji_html: "%{name} desativou o emoji %{target}"
-        disable_relay_html: "%{name} desativou o retransmissor %{target}"
         disable_sign_in_token_auth_user_html: "%{name} desativou o token de autenticação por e-mail para %{target}"
         disable_user_html: "%{name} desativou o início de sessão para o utilizador %{target}"
         enable_custom_emoji_html: "%{name} ativou o emoji %{target}"
-        enable_relay_html: "%{name} ativou o retransmissor %{target}"
         enable_sign_in_token_auth_user_html: "%{name} ativou o token de autenticação por e-mail para %{target}"
         enable_user_html: "%{name} ativou o início de sessão para o utilizador %{target}"
         memorialize_account_html: "%{name} transformou a conta de %{target} num memorial"
         promote_user_html: "%{name} promoveu o utilizador %{target}"
-        publish_terms_of_service_html: "%{name} publicou a atualização dos termos de serviço"
         reject_appeal_html: "%{name} rejeitou a contestação da decisão de moderação de %{target}"
         reject_user_html: "%{name} rejeitou a inscrição de %{target}"
         remove_avatar_user_html: "%{name} removeu a imagem de perfil de %{target}"
@@ -309,7 +299,6 @@ pt-PT:
       title: Registo de auditoria
       unavailable_instance: "(nome de domínio indisponível)"
     announcements:
-      back: Voltar às mensagem de manutenção
       destroyed_msg: Mensagem de manutenção eliminada com sucesso!
       edit:
         title: Editar mensagem de manutenção
@@ -318,9 +307,6 @@ pt-PT:
       new:
         create: Criar mensagem de manutenção
         title: Nova mensagem de manutenção
-      preview:
-        explanation_html: 'O e-mail será enviado para <strong>%{display_count} utilizadores</strong>. O texto seguinte será incluído na mensagem de e-mail:'
-        title: Pré-visualização da mensagem de manutenção
       publish: Publicar
       published_msg: Mensagem de manutenção publicada com sucesso!
       scheduled_for: Agendado para %{time}
@@ -832,10 +818,8 @@ pt-PT:
       back_to_account: Voltar à página da conta
       back_to_report: Voltar à página da denúncia
       batch:
-        add_to_report: 'Adicionar à denúncia #%{id}'
         remove_from_report: Remover da denúncia
         report: Denúncia
-      contents: Conteúdo
       deleted: Eliminado
       favourites: Favoritos
       history: Histórico de versões
@@ -844,17 +828,13 @@ pt-PT:
       media:
         title: Multimédia
       metadata: Metadados
-      no_history: Esta publicação não foi editada
       no_status_selected: Nenhum estado foi alterado porque nenhum foi selecionado
       open: Abrir publicação
       original_status: Publicação original
       reblogs: Impulsos
-      replied_to_html: Respondeu a %{acct_link}
       status_changed: Publicação alterada
-      status_title: Publicado por @%{name}
-      title: Publicações da conta - @%{name}
+      title: Publicações da conta
       trending: Em tendência
-      view_publicly: Visualizar publicamente
       visibility: Visibilidade
       with_media: Com multimédia
     strikes:
@@ -931,36 +911,6 @@ pt-PT:
       search: Pesquisar
       title: Etiquetas
       updated_msg: 'Definições de #etiquetas atualizadas com sucesso'
-    terms_of_service:
-      back: Voltar aos Termos do Serviço
-      changelog: O que mudou
-      create: Use o seu próprio
-      current: Atual
-      draft: Rascunho
-      generate: Utilizar modelo
-      generates:
-        action: Gerar
-        chance_to_review_html: "<strong>Os termos de serviço gerados não serão publicados automaticamente.</strong> Terá a possibilidade de rever os resultados. Por favor, preencha os detalhes necessários para prosseguir."
-        explanation_html: O modelo de termos de serviço fornecido destina-se apenas a fins informativos e não deve ser interpretado como aconselhamento jurídico sobre qualquer assunto. Consulte o seu próprio consultor jurídico sobre a sua situação e questões jurídicas específicas que tenha.
-        title: Configuração dos Termos de Serviço
-      going_live_on_html: Em vigor desde %{date}
-      history: Histórico
-      live: Em tempo real
-      no_history: Ainda não há nenhuma alteração registada nos termos de serviço.
-      no_terms_of_service_html: Atualmente, não tem quaisquer termos de serviço configurados. Os termos de serviço destinam-se a proporcionar clareza e a protegê-lo de potenciais responsabilidades em litígios com os seus utilizadores.
-      notified_on_html: Utilizadores notificados em %{date}
-      notify_users: Notificar utilizadores
-      preview:
-        explanation_html: 'O e-mail será enviado para <strong>%{display_count} utilizadores</strong> que se inscreveram antes de %{date}. O texto seguinte será incluído na mensagem de e-mail:'
-        send_preview: Enviar pré-visualização para %{email}
-        send_to_all:
-          one: Enviar %{display_count} e-mail
-          other: Enviar %{display_count} e-mails
-        title: Pré-visualizar termos de notificação de serviço
-      publish: Publicar
-      published_on_html: Publicado em %{date}
-      save_draft: Guardar rascunho
-      title: Termos de Serviço
     title: Administração
     trends:
       allow: Permitir
@@ -1168,6 +1118,7 @@ pt-PT:
     migrate_account: Mudar para uma conta diferente
     migrate_account_html: Se desejas redirecionar esta conta para uma outra podes <a href="%{path}">configurar isso aqui</a>.
     or_log_in_with: Ou iniciar sessão com
+    privacy_policy_agreement_html: Eu li e concordo com a <a href="%{privacy_policy_path}" target="_blank">política de privacidade</a>
     progress:
       confirm: Confirmar e-mail
       details: Os teus dados
@@ -1192,7 +1143,7 @@ pt-PT:
     set_new_password: Editar palavra-passe
     setup:
       email_below_hint_html: Verifique a sua pasta de spam ou solicite outra. Pode corrigir o seu endereço de e-mail se estiver errado.
-      email_settings_hint_html: Clique na hiperligação que enviámos para %{email} para começar a utilizar o Mastodon. Estaremos à espera aqui mesmo.
+      email_settings_hint_html: Clica na hiperligação que enviamos para verificar %{email}. Esperaremos aqui.
       link_not_received: Não recebeu uma hiperligação?
       new_confirmation_instructions_sent: Irá receber uma nova mensagem de e-mail com a hiperligação de confirmação dentro de alguns minutos!
       title: Verifique a caixa de entrada do seu e-mail
@@ -1201,7 +1152,7 @@ pt-PT:
       title: Iniciar sessão em %{domain}
     sign_up:
       manual_review: As inscrições em %{domain} passam por uma revisão manual pelos nossos moderadores. Para nos ajudar a processar o teu pedido de inscrição, escreve um pouco sobre ti e o motivo para quereres uma conta em %{domain}.
-      preamble: Com uma conta neste servidor Mastodon, poderás seguir qualquer outra pessoa no fediverso, independentemente do local onde a tua conta está alojada.
+      preamble: Com uma conta neste servidor Mastodon, poderás seguir qualquer outra pessoa na rede, independentemente do servidor onde a conta esteja hospedada.
       title: Vamos lá inscrever-te em %{domain}.
     status:
       account_status: Estado da conta
@@ -1213,8 +1164,6 @@ pt-PT:
       view_strikes: Ver as reprimendas anteriores sobre a tua conta
     too_fast: Formulário enviado demasiado rapidamente, tenta novamente.
     use_security_key: Usar chave de segurança
-    user_agreement_html: Eu li e concordo com os <a href="%{terms_of_service_path}" target="_blank">termos do serviço</a> e <a href="%{privacy_policy_path}" target="_blank">política de privacidade</a>
-    user_privacy_agreement_html: Eu li e concordo com a <a href="%{privacy_policy_path}" target="_blank">política de privacidade</a>
   author_attribution:
     example_title: Texto de exemplo
     hint_html: Estás a escrever notícias ou artigos de blogue fora do Mastodon? Controla a forma como és creditados quando estes são partilhados no Mastodon.
@@ -1420,43 +1369,19 @@ pt-PT:
       overwrite: Substituir
       overwrite_long: Substituir os registos atuais pelos novos
     overwrite_preambles:
-      blocking_html:
-        one: Estás prestes a substituir a <strong>tua lista de bloqueios</strong> com até <strong>conta%{count}</strong> de <strong>%{filename}</strong>.
-        other: Estás prestes a <strong>substituir a tua lista de bloqueios</strong> com até <strong>%{count} contas</strong> de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Estás prestes a <strong>substituir os teus marcadores</strong> com até <strong>%{count} publicações</strong> de <strong>%{filename}</strong>.
-        other: Estás prestes a <strong>substituir os teus marcadores</strong> com até <strong>%{count} publicação</strong> de <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Estás prestes a <strong>substituir a tua lista de bloqueios de domínio</strong> com até <strong>%{count} domínio</strong> de <strong>%{filename}</strong>.
-        other: Estás prestes a <strong>substituir a tua lista de bloqueios de domínio</strong> com até <strong>%{count} domínios</strong> de <strong>%{filename}</strong>.
-      following_html:
-        one: Estás prestes a <strong>seguir</strong> até <strong>%{count} conta</strong> de <strong>%{filename}</strong> e <strong>parar de seguir quaisquer outras contas</strong>.
-        other: Estás prestes a <strong>seguir</strong> até <strong>%{count} contas</strong> de <strong>%{filename}</strong> e <strong>parar de seguir quaisquer outras contas</strong>.
-      lists_html:
-        one: Estás prestes a substituir <strong>as tuas listas</strong> pelo conteúdo de <strong>%{filename}</strong>. Até <strong>%{count} conta</strong> serão adicionadas a novas listas.
-        other: Estás prestes a substituir <strong>as tuas listas</strong> pelo conteúdo de <strong>%{filename}</strong>. Até <strong>%{count} contas</strong> serão adicionadas a novas listas.
-      muting_html:
-        one: Estás prestes a <strong>substituir a tua lista de conta ocultada</strong> com até <strong>%{count} conta</strong> de <strong>%{filename}</strong>.
-        other: Estás prestes a <strong>substituir a tua lista de contas ocultadas</strong> com até <strong>%{count} contas</strong> de <strong>%{filename}</strong>.
+      blocking_html: Estás prestes a <strong>substituir a tua lista de bloqueios</strong> com até <strong>%{total_items} contas</strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Estás prestes a <strong>substituir os teus marcadores</strong> com até <strong>%{total_items} publicações</strong> de <strong>%{filename}</strong>.
+      domain_blocking_html: Estás prestes a <strong>substituir a tua lista de bloqueios de domínio</strong> com até <strong>%{total_items} domínios</strong> de <strong>%{filename}</strong>.
+      following_html: Estás prestes a <strong>seguir</strong> até <strong>%{total_items} contas</strong> de <strong>%{filename}</strong> e <strong>parar de seguir quaisquer outras contas</strong>.
+      lists_html: Estás prestes a substituir <strong>as tuas listas</strong> pelo conteúdo de <strong>%{filename}</strong>. Até <strong>%{total_items} contas</strong> serão adicionadas a novas listas.
+      muting_html: Estás prestes a <strong>substituir a tua lista de contas ocultadas</strong> com até <strong>%{total_items} contas</strong> de <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Estás prestes a <strong>bloquear</strong> até <strong>%{count} conta</strong> de <strong>%{filename}</strong>.
-        other: Estás prestes a <strong>bloquear</strong> até <strong>%{count} contas</strong> de <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Estás prestes a adicionar até <strong>%{count} publicação</strong> de <strong>%{filename}</strong> aos teus <strong>marcadores</strong>.
-        other: Estás prestes a adicionar até <strong>%{count} publicações</strong> de <strong>%{filename}</strong> aos teus <strong>marcadores</strong>.
-      domain_blocking_html:
-        one: Estás prestes a <strong>bloquear</strong> até <strong>%{count} domínio</strong> de <strong>%{filename}</strong>.
-        other: Estás prestes a <strong>bloquear</strong> até <strong>%{count} domínios</strong> de <strong>%{filename}</strong>.
-      following_html:
-        one: Estás prestes a <strong>seguir</strong> até <strong>%{count} conta</strong> de <strong>%{filename}</strong>.
-        other: Estás prestes a <strong>seguir</strong> até <strong>%{count} contas</strong> de <strong>%{filename}</strong>.
-      lists_html:
-        one: Estás prestes a adicionar até <strong>%{count} conta</strong> do ficheiro <strong>%{filename}</strong> para <strong>as tuas listas</strong>. Novas listas serão criadas se não existir uma lista onde as adicionar.
-        other: Estás prestes a adicionar até <strong>%{count} contas</strong> do ficheiro <strong>%{filename}</strong> para <strong>as tuas listas</strong>. Novas listas serão criadas se não existir uma lista onde as adicionar.
-      muting_html:
-        one: Estás prestes a <strong>ocultar</strong> até <strong>%{count} conta</strong> de <strong>%{filename}</strong>.
-        other: Estás prestes a <strong>ocultar</strong> até <strong>%{count} contas</strong> de <strong>%{filename}</strong>.
+      blocking_html: Estás prestes a <strong>bloquear</strong> até <strong>%{total_items} contas</strong> de <strong>%{filename}</strong>.
+      bookmarks_html: Estás prestes a adicionar até <strong>%{total_items} publicações</strong> de <strong>%{filename}</strong> aos teus <strong>marcadores</strong>.
+      domain_blocking_html: Estás prestes a <strong>bloquear</strong> até <strong>%{total_items} domínios</strong> de <strong>%{filename}</strong>.
+      following_html: Estás prestes a <strong>seguir</strong> até <strong>%{total_items} contas</strong> de <strong>%{filename}</strong>.
+      lists_html: Estás prestes a adicionar até <strong>%{total_items} contas</strong> do ficheiro <strong>%{filename}</strong> para <strong>as tuas listas</strong>. Novas listas serão criadas se não existir uma lista onde as adicionar.
+      muting_html: Estás prestes a <strong>ocultar</strong> até <strong>%{total_items} contas</strong> de <strong>%{filename}</strong>.
     preface: Podes importar dados que tenhas exportado de outra instância, como a lista de pessoas que segues ou bloqueadas.
     recent_imports: Importações recentes
     states:
@@ -1713,7 +1638,7 @@ pt-PT:
   scheduled_statuses:
     over_daily_limit: Excedeste o limite de %{limit} publicações agendadas para hoje
     over_total_limit: Excedeste o limite de %{limit} publicações agendadas
-    too_soon: a data tem de ser no futuro
+    too_soon: A data de agendamento tem de ser futura
   self_destruct:
     lead_html: Infelizmente, <strong>%{domain}</strong> vai fechar definitivamente. Se tinhas conta nele, não podes continuar a usá-lo, mas ainda podes pedir uma cópia dos teus dados.
     title: Este servidor vai fechar
@@ -1876,8 +1801,6 @@ pt-PT:
       too_late: É tarde demais para contestar esta reprimenda
   tags:
     does_not_match_previous_name: não coincide com o nome anterior
-  terms_of_service:
-    title: Termos de Serviço
   themes:
     contrast: Mastodon (alto contraste)
     default: Mastodon (escuro)
@@ -1909,10 +1832,6 @@ pt-PT:
     recovery_instructions_html: Se perderes o seu telemóvel, poderás usar um dos códigos de recuperação para voltares a ter acesso à tua conta. <strong>Guarda os códigos de recuperação em lugar seguro</strong>. Por exemplo, podes imprimi-los e guardá-los junto a outros documentos importantes.
     webauthn: Chaves de segurança
   user_mailer:
-    announcement_published:
-      description: 'Os administradores do %{domain} estão a fazer um anúncio:'
-      subject: Mensagem de manutenção do serviço
-      title: 'Mensagem de manutenção de %{domain} '
     appeal_approved:
       action: Configurações da conta
       explanation: A contestação à reprimenda contra a tua conta em %{strike_date}, enviada a %{appeal_date}, foi aceite. A tua conta encontra-se novamente em situação regular.
@@ -1942,15 +1861,6 @@ pt-PT:
       further_actions_html: Se não foste tu, recomendamos %{action} imediatamente e ativar a autenticação de dois fatores para manter a conta segura.
       subject: A tua conta foi acedida a partir de um endereço IP novo
       title: Um início de sessão novo
-    terms_of_service_changed:
-      agreement: Ao continuar a utilizar %{domain}, concordas com estes termos. Se discordares dos termos atualizados, poderás rescindir o teu acordo com %{domain} a qualquer momento através da eliminação da tua conta.
-      changelog: 'Em resumo, eis o que esta atualização significa para ti:'
-      description: 'Está a receber esta mensagem porque vamos alterar os nossos termos de serviço em %{domain}. Estas atualizações entram em vigor em %{date}. Recomendamos que leia os termos de serviço atualizados em:'
-      description_html: Está a receber esta mensagem porque vamos alterar os nossos termos de serviço em %{domain}. Estas atualizações entram em vigor em <strong>%{date}</strong>. Recomendamos que leia os <a href="%{path}" target="_blank">termos de serviço atualizados aqui</a>.
-      sign_off: A equipa de %{domain}
-      subject: Atualizações dos nossos termos de serviço
-      subtitle: Os termos de serviço de %{domain} estão a mudar
-      title: Atualização importante
     warning:
       appeal: Submeter uma contestação
       appeal_description: Se achas que isto é um erro, podes submeter uma contestação para a equipa de %{instance}.
diff --git a/config/locales/ro.yml b/config/locales/ro.yml
index aeaa1e283c..2074c1fcbc 100644
--- a/config/locales/ro.yml
+++ b/config/locales/ro.yml
@@ -23,7 +23,6 @@ ro:
       one: Postare
       other: De Postări
     posts_tab_heading: Postări
-    self_follow_error: Urmărirea propriului cont nu este permisă
   admin:
     account_actions:
       action: Efectuează acțiunea
@@ -430,6 +429,8 @@ ro:
       discovery:
         follow_recommendations: Urmează recomandările
         profile_directory: Catalogul de profiluri
+    statuses:
+      title: Postări cont
   aliases:
     add_new: Creează un alias
     created_msg: A fost creat cu succes un alias nou. Acum puteţi iniţia mutarea din vechiul cont.
@@ -466,11 +467,16 @@ ro:
     migrate_account: Transfer către un alt cont
     migrate_account_html: Dacă dorești să redirecționezi acest cont către un altul, poți <a href="%{path}">configura asta aici</a>.
     or_log_in_with: Sau conectează-te cu
+    privacy_policy_agreement_html: Am citit și sunt de acord cu <a href="%{privacy_policy_path}" target="_blank">Politica de confidențialitate</a>
     register: Înregistrare
     registration_closed: "%{instance} nu acceptă membri noi"
     reset_password: Resetare parolă
     security: Securitate
     set_new_password: Setează o nouă parolă
+    setup:
+      email_settings_hint_html: Faceți clic pe link-ul pe care vi l-am trimis pentru a verifica %{email}. Vom aștepta chiar aici.
+    sign_up:
+      preamble: Cu un cont pe acest server Mastodon, veți putea urmări orice altă persoană din rețea, indiferent de locul unde este găzduit contul lor.
     status:
       account_status: Starea contului
       redirecting_to: Contul dvs. este inactiv deoarece în prezent se redirecționează către %{acct}.
@@ -547,6 +553,20 @@ ro:
     modes:
       overwrite: Suprascrie
       overwrite_long: Înlocuiţi înregistrările curente cu cele noi
+    overwrite_preambles:
+      blocking_html: Sunteți pe cale să <strong>înlocuiți lista cu conturi blocate</strong> cu până la <strong>%{total_items} conturi</strong> din <strong>%{filename}</strong>.
+      bookmarks_html: Sunteți pe cale să <strong>înlocuiți marcajele</strong> cu până la <strong>%{total_items} postări</strong> din <strong>%{filename}</strong>.
+      domain_blocking_html: Sunteți pe cale să <strong>înlocuiți lista cu domenii blocate</strong> cu până la <strong>%{total_items} domenii</strong> din <strong>%{filename}</strong>.
+      following_html: Sunteți pe cale să <strong>urmăriți</strong> până la <strong>%{total_items} conturi</strong> din <strong>%{filename}</strong> și să <strong>nu mai urmăriți pe altcineva</strong>.
+      lists_html: Sunteți pe cale să <strong>înlocuiți listele</strong> cu conținut din <strong>%{filename}</strong>. Până la <strong>%{total_items} conturi</strong> vor fi adăugate la listele noi.
+      muting_html: Sunteți pe cale să <strong>înlocuiți lista cu conturi amuțite</strong> cu până la <strong>%{total_items} conturi</strong> din <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Sunteți pe cale să <strong>blocați</strong> până la <strong>%{total_items} conturi</strong> din <strong>%{filename}</strong>.
+      bookmarks_html: Sunteți pe cale să adăugați până la <strong>%{total_items} postări</strong> din <strong>%{filename}</strong> în <strong>marcajele dvs</strong>.
+      domain_blocking_html: Sunteți pe cale să <strong>blocați</strong> până la <strong>%{total_items} domenii</strong> din <strong>%{filename}</strong>.
+      following_html: Sunteți pe cale să <strong>urmăriți</strong> până la <strong>%{total_items} conturi</strong> din <strong>%{filename}</strong>.
+      lists_html: Sunteți pe cale să adăugați până la <strong>%{total_items} conturi</strong> din <strong>%{filename}</strong> în <strong>listele dvs</strong>. Vor fi create liste noi dacă nu există nicio listă la care să adăugați.
+      muting_html: Sunteți pe cale să <strong>amuțiți</strong> până la <strong>%{total_items} conturi</strong> din <strong>%{filename}</strong>.
     preface: Puteți importa date pe care le-ați exportat de pe un alt server, cum ar fi o listă a persoanelor pe care le urmăriți sau blocați.
     success: Datele dvs. au fost încărcate cu succes și vor fi procesate acum în timp util
     types:
@@ -654,6 +674,7 @@ ro:
   scheduled_statuses:
     over_daily_limit: Ai depășit limita de %{limit} postări programate pentru acea zi
     over_total_limit: Ai depășit limita de %{limit} postări programate
+    too_soon: Data programată trebuie să fie în viitor
   sessions:
     activity: Ultima activitate
     browser: Navigator
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 1f01f622f5..e4e5c29a88 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -193,7 +193,6 @@ ru:
         create_domain_block: Блокировка доменов
         create_email_domain_block: Создание доменных блокировок e-mail
         create_ip_block: Создание правил для IP-адресов
-        create_relay: Создание ретранслятора
         create_unavailable_domain: Добавление домена в список недоступных
         create_user_role: Создание ролей
         demote_user: Разжалование пользователей
@@ -205,22 +204,18 @@ ru:
         destroy_email_domain_block: Удаление доменных блокировок e-mail
         destroy_instance: Очистить домен
         destroy_ip_block: Удаление правил для IP-адресов
-        destroy_relay: Удаление ретранслятора
         destroy_status: Удаление постов
         destroy_unavailable_domain: Исключение доменов из списка недоступных
         destroy_user_role: Удаление ролей
         disable_2fa_user: Отключение 2FA
         disable_custom_emoji: Отключение эмодзи
-        disable_relay: Отключение ретранслятора
         disable_sign_in_token_auth_user: Отключение аутентификации по e-mail кодам
         disable_user: Заморозка пользователей
         enable_custom_emoji: Включение эмодзи
-        enable_relay: Включение ретранслятора
         enable_sign_in_token_auth_user: Включение аутентификации по e-mail кодам
         enable_user: Разморозка пользователей
         memorialize_account: Присвоение пользователям статуса «мемориала»
         promote_user: Повышение пользователей
-        publish_terms_of_service: Опубликование пользовательского соглашения
         reject_appeal: Отклонение обжалований
         reject_user: Отклонение регистраций
         remove_avatar_user: Удаление аватаров
@@ -258,7 +253,6 @@ ru:
         create_domain_block_html: "%{name} заблокировал(а) домен %{target}"
         create_email_domain_block_html: "%{name} заблокировал(а) e-mail домен %{target}"
         create_ip_block_html: "%{name} создал(а) правило для IP %{target}"
-        create_relay_html: "%{name} создал(а) ретранслятор %{target}"
         create_unavailable_domain_html: "%{name} приостановил доставку на узел %{target}"
         create_user_role_html: "%{name} создал(а) роль %{target}"
         demote_user_html: "%{name} разжаловал(а) пользователя %{target}"
@@ -270,27 +264,23 @@ ru:
         destroy_email_domain_block_html: "%{name} снял(а) блокировку с e-mail домена %{target}"
         destroy_instance_html: "%{name} очистил(а) данные для домена %{target}"
         destroy_ip_block_html: "%{name} удалил(а) правило для IP %{target}"
-        destroy_relay_html: "%{name} удалил(а) ретранслятор %{target}"
         destroy_status_html: "%{name} удалил(а) пост пользователя %{target}"
         destroy_unavailable_domain_html: "%{name} возобновил доставку на узел %{target}"
         destroy_user_role_html: "%{name} удалил(а) роль %{target}"
         disable_2fa_user_html: "%{name} отключил(а) требование двухэтапной авторизации для пользователя %{target}"
         disable_custom_emoji_html: "%{name} отключил(а) эмодзи %{target}"
-        disable_relay_html: "%{name} отключил(а) ретранслятор %{target}"
         disable_sign_in_token_auth_user_html: "%{name} отключил(а) аутентификацию по e-mail кодам для %{target}"
         disable_user_html: "%{name} заморозил(а) пользователя %{target}"
         enable_custom_emoji_html: "%{name} включил(а) эмодзи %{target}"
-        enable_relay_html: "%{name} включил(а) ретранслятор %{target}"
         enable_sign_in_token_auth_user_html: "%{name} включил(а) аутентификацию по e-mail кодам для %{target}"
         enable_user_html: "%{name} разморозил(а) пользователя %{target}"
         memorialize_account_html: "%{name} перевел(а) учётную запись пользователя %{target} в статус памятника"
         promote_user_html: "%{name} повысил(а) пользователя %{target}"
-        publish_terms_of_service_html: "%{name} опубликовал(а) обновление пользовательского соглашения"
         reject_appeal_html: "%{name} отклонил(а) обжалование действий модерации от %{target}"
         reject_user_html: "%{name} отклонил(а) регистрацию %{target}"
         remove_avatar_user_html: "%{name} убрал(а) аватарку пользователя %{target}"
         reopen_report_html: "%{name} повторно открыл(а) жалобу %{target}"
-        resend_user_html: "%{name} повторно отправил(а) письмо с подтверждением для %{target}"
+        resend_user_html: "%{name} повторно отправил письмо с подтверждением для %{target}"
         reset_password_user_html: "%{name} сбросил(а) пароль пользователя %{target}"
         resolve_report_html: "%{name} решил(а) жалобу %{target}"
         sensitive_account_html: "%{name} установил(а) отметку файлов %{target} как «деликатного характера»"
@@ -315,7 +305,6 @@ ru:
       title: Журнал аудита
       unavailable_instance: "(доменное имя недоступно)"
     announcements:
-      back: Вернуться к объявлениям
       destroyed_msg: Объявление удалено.
       edit:
         title: Редактировать объявление
@@ -324,9 +313,6 @@ ru:
       new:
         create: Создать объявление
         title: Новое объявление
-      preview:
-        explanation_html: 'Сообщение будет отравлено <strong>%{display_count} пользователям</strong>. В теле письма будет указан следующий текст:'
-        title: Предпросмотр объявления по электронной почте
       publish: Опубликовать
       published_msg: Объявление опубликовано.
       scheduled_for: Запланировано на %{time}
@@ -475,7 +461,7 @@ ru:
         create: Создать блокировку
         resolve: Проверить домен
         title: Блокировка нового почтового домена
-      no_email_domain_block_selected: Блокировки почтовых доменов не были изменены, так как ни один из них не был выбран
+      no_email_domain_block_selected: Блоки почтовых доменов не были изменены, так как ни один из них не был выбран
       not_permitted: Не разрешено
       resolved_dns_records_hint_html: Доменное имя указывает на следующие MX-домены, которые в конечном итоге отвечают за прием электронной почты. Блокировка MX-домена будет блокировать регистрации с любого адреса электронной почты, который использует тот же MX-домен, даже если видимое доменное имя отличается от него. <strong>Будьте осторожны, чтобы не заблокировать основных провайдеров электронной почты</strong>
       resolved_through_html: Разрешено через %{domain}
@@ -495,10 +481,6 @@ ru:
       new:
         title: Импорт доменных блокировок
       no_file: Файл не выбран
-    fasp:
-      providers:
-        sign_in:
-        status: Пост
     follow_recommendations:
       description_html: "<strong>Следуйте рекомендациям, чтобы помочь новым пользователям быстро находить интересный контент</strong>. Если пользователь не взаимодействовал с другими в достаточной степени, чтобы сформировать персонализированные рекомендации, вместо этого рекомендуется использовать эти учетные записи. Они пересчитываются на ежедневной основе на основе комбинации аккаунтов с наибольшим количеством недавних взаимодействий и наибольшим количеством местных подписчиков для данного языка."
       language: Для языка
@@ -647,7 +629,7 @@ ru:
         suspend_description_html: Аккаунт и все его содержимое будут недоступны и в конечном итоге удалены, и взаимодействие с ним будет невозможно. Это действие можно отменить в течение 30 дней. Отменяет все жалобы против этого аккаунта.
       actions_description_html: Выберите действие, чтобы разрешить данную жалобу. Если вы примете меры модерации против аккаунта, его владелец получит уведомление по электронной почте, кроме тех случаев, когда выбрана категория <strong>Спам</strong>.
       actions_description_remote_html: Решите вопрос о том, какие меры необходимо принять для урегулирования этой жалобы. Это повлияет только на то, как <strong>ваш</strong> сервер взаимодействует с этим удаленным аккаунтом и обрабатывает его содержимое.
-      actions_no_posts: У этой жалобы нет связанных с ней постов для удаления
+      actions_no_posts: У этого отчета нет связанных с ним сообщений для удаления
       add_to_report: Прикрепить ещё
       already_suspended_badges:
         local: На этом сервере уже забанен
@@ -688,7 +670,7 @@ ru:
       report: Жалоба №%{id}
       reported_account: Учётная запись нарушителя
       reported_by: Отправитель жалобы
-      reported_with_application: Использованное для отправки жалобы приложение
+      reported_with_application: Сообщается с приложением
       resolved: Решённые
       resolved_msg: Жалоба обработана, спасибо!
       skip_to_actions: Перейти к действиям
@@ -836,7 +818,7 @@ ru:
           approved: Для регистрации требуется подтверждение
           none: Никто не может регистрироваться
           open: Все могут регистрироваться
-        warning_hint: Мы рекомендуем использовать "Требуется одобрение для регистрации", если вы не уверены, что ваша команда модераторов сможет своевременно справиться со спамом и злоумышленными регистрациями.
+        warning_hint: Мы рекомендуем использовать "Требуется одобрение для регистрации", если вы не уверены, что ваша команда модераторов сможет своевременно справиться со спамом и вредоносными регистрациями.
       security:
         authorized_fetch: Требовать аутентификацию от федеративных серверов
         authorized_fetch_hint: Требование аутентификации от федеративных серверов позволяет более строго соблюдать блокировки как на уровне пользователя, так и на уровне сервера. Однако при этом снижается производительность, уменьшается охват ваших ответов и могут возникнуть проблемы совместимости с некоторыми федеративными сервисами. Кроме того, это не помешает специальным исполнителям получать ваши публичные сообщения и учётные записи.
@@ -864,10 +846,8 @@ ru:
       back_to_account: Назад к учётной записи
       back_to_report: Вернуться к жалобе
       batch:
-        add_to_report: Добавить к жалобе №%{id}
         remove_from_report: Убрать из жалобы
         report: Пожаловаться
-      contents: Содержание
       deleted: Удалено
       favourites: Избранное
       history: История версий
@@ -876,17 +856,13 @@ ru:
       media:
         title: Файлы мультимедиа
       metadata: Метаданные
-      no_history: Этот пост не редактировался
       no_status_selected: Ничего не изменилось, так как ни один пост не был выделен
       open: Открыть запись
       original_status: Оригинальный пост
       reblogs: Продвинули
-      replied_to_html: Ответ пользователю %{acct_link}
       status_changed: Пост изменен
-      status_title: Пост пользователя @%{name}
-      title: Посты пользователя - @%{name}
+      title: Посты пользователя
       trending: Популярное
-      view_publicly: Открыть по публичной ссылке
       visibility: Видимость
       with_media: С файлами
     strikes:
@@ -946,55 +922,23 @@ ru:
     tags:
       moderation:
         not_trendable: Не в тренде
-        not_usable: Недоступный
-        pending_review: На рассмотрении
-        review_requested: Запрошено рассмотрение
-        reviewed: Рассмотренный
+        not_usable: Невозможно использовать
+        pending_review: В ожидании обзора
+        review_requested: Обзор запрошен
+        reviewed: Рассмотрено
         title: Статус
-        trendable: В тренде
-        unreviewed: Нерассмотренный
-        usable: Доступный
+        trendable: Трендовый
+        unreviewed: Без рецензии
+        usable: Полезное
       name: Название
-      newest: Сначала новые
-      oldest: Сначала старые
+      newest: Новейший
+      oldest: Старейший
       open: Посмотреть публично
-      reset: Сбросить
+      reset: Сброс
       review: Состояние проверки
       search: Поиск
       title: Хэштеги
       updated_msg: Настройки хэштега обновлены
-    terms_of_service:
-      back: Назад к пользовательскому соглашению
-      changelog: Что изменилось
-      create: Использовать свой текст
-      current: Действующее
-      draft: Черновик
-      generate: Использовать шаблон
-      generates:
-        action: Генерировать
-        chance_to_review_html: "<strong>Сгенерированное пользовательское соглашение не будет опубликовано автоматически.</strong> У вас будет возможность просмотреть результат. Введите все необходимые сведения, чтобы продолжить."
-        explanation_html: Шаблон пользовательского соглашения приводится исключительно в ознакомительных целях, и не может рассматриваться как юридическая консультация по тому или иному вопросу. Обратитесь к своему юрисконсульту насчёт вашей ситуации и имеющихся правовых вопросов.
-        title: Создание пользовательского соглашения
-      going_live_on_html: Вступило в силу с %{date}
-      history: История
-      live: Действует
-      no_history: Нет зафиксированных изменений пользовательского соглашения.
-      no_terms_of_service_html: На данный момент у вас отсутствует пользовательское соглашение. Пользовательское соглашение призвано обеспечить ясность и защитить вас от возможной ответственности в спорных ситуациях с пользователями.
-      notified_on_html: 'Дата уведомления пользователей: %{date}'
-      notify_users: Уведомить пользователей
-      preview:
-        explanation_html: 'Сообщение будет отравлено <strong>%{display_count} пользователям</strong>, которые зарегистрировались до %{date}. В теле письма будет указан следующий текст:'
-        send_preview: Отправить предпросмотр на %{email}
-        send_to_all:
-          few: Отправить %{display_count} сообщения
-          many: Отправить %{display_count} сообщений
-          one: Отправить %{display_count} сообщение
-          other: Отправить %{display_count} сообщений
-        title: Предпросмотр уведомления об изменении пользовательского соглашения
-      publish: Опубликовать
-      published_on_html: 'Дата публикации: %{date}'
-      save_draft: Сохранить черновик
-      title: Пользовательское соглашение
     title: Администрирование
     trends:
       allow: Разрешить
@@ -1008,7 +952,7 @@ ru:
         confirm_allow: Вы уверены, что хотите разрешить выбранные ссылки?
         confirm_allow_provider: Вы уверены, что хотите разрешить выбранных провайдеров?
         confirm_disallow: Вы уверены, что хотите запретить выбранные ссылки?
-        confirm_disallow_provider: Вы уверены, что хотите запретить выбранных провайдеров?
+        confirm_disallow_provider: Вы уверены, что хотите запретить выбранных поставщиков?
         description_html: Это ссылки, которыми в настоящее время много пользуются аккаунты, с которых ваш сервер видит сообщения. Это может помочь вашим пользователям узнать, что происходит в мире. Никакие ссылки не отображаются публично, пока вы не одобрите издателя. Вы также можете разрешить или отклонить индивидуальные ссылки.
         disallow: Запретить ссылку
         disallow_provider: Отклонить издание
@@ -1074,14 +1018,14 @@ ru:
           many: За последнюю неделю использовало %{count} человек
           one: За последнюю неделю использовал один человек
           other: За последнюю неделю использовал %{count} человек
-      title: Рекомендации и тренды
+      title: Рекомендации и тенденции
       trending: Популярное
     warning_presets:
       add_new: Добавить
       delete: Удалить
       edit_preset: Удалить шаблон предупреждения
       empty: Вы еще не определили пресеты предупреждений.
-      title: Шаблоны предупреждений
+      title: Предупреждающие пред установки
     webhooks:
       add_new: Добавить конечную точку
       delete: Удалить
@@ -1210,6 +1154,7 @@ ru:
     migrate_account: Перенос учётной записи
     migrate_account_html: Завели новую учётную запись? Перенаправьте подписчиков на неё — <a href="%{path}">настройте перенаправление тут</a>.
     or_log_in_with: Или войти с помощью
+    privacy_policy_agreement_html: Мной прочитана и принята <a href="%{privacy_policy_path}" target="_blank">политика конфиденциальности</a>
     progress:
       confirm: Подтвердите электронную почту
       details: Ваши данные
@@ -1234,7 +1179,7 @@ ru:
     set_new_password: Задать новый пароль
     setup:
       email_below_hint_html: Проверьте папку "Спам" или запросите другую. Вы можете исправить свой адрес электронной почты, если он неправильный.
-      email_settings_hint_html: Чтобы начать пользоваться Mastodon, пройдите по ссылке, которую мы отправили на %{email}. А мы пока подождём тут.
+      email_settings_hint_html: Нажмите на ссылку, которую мы отправили вам для проверки %{email}. Мы будем ждать прямо здесь.
       link_not_received: Не получили ссылку?
       new_confirmation_instructions_sent: Через несколько минут вы получите новое письмо со ссылкой для подтверждения!
       title: Проверьте свой почтовый ящик
@@ -1243,7 +1188,7 @@ ru:
       title: Войти в %{domain}
     sign_up:
       manual_review: Регистрация на %{domain} проходит через ручную проверку нашими модераторами. Чтобы помочь нам обработать вашу регистрацию, напишите немного о себе и о том, почему вы хотите получить аккаунт на %{domain}.
-      preamble: С учётной записью на этом сервере Mastodon вы сможете подписываться на всех других людей в федиверсе вне зависимости от того, где находятся их учётные записи.
+      preamble: С учётной записью на этом сервере Mastodon вы сможете следить за любым другим пользователем в сети, независимо от того, где размещён их аккаунт.
       title: Зарегистрируйтесь в %{domain}.
     status:
       account_status: Статус учётной записи
@@ -1255,8 +1200,6 @@ ru:
       view_strikes: Просмотр предыдущих замечаний в адрес вашей учетной записи
     too_fast: Форма отправлена слишком быстро, попробуйте еще раз.
     use_security_key: Использовать ключ безопасности
-    user_agreement_html: Мной прочитаны и приняты<a href="%{terms_of_service_path}" target="_blank">пользовательское соглашение</a> и <a href="%{privacy_policy_path}" target="_blank">политика конфиденциальности</a>
-    user_privacy_agreement_html: Мной прочитана и принята <a href="%{privacy_policy_path}" target="_blank">политика конфиденциальности</a>
   author_attribution:
     example_title: Образец текста
     hint_html: Публикуете ли вы свои статьи где-либо ещё кроме Mastodon? Если да, то ваше авторство может быть упомянуто, когда ими делятся в Mastodon.
@@ -1476,67 +1419,19 @@ ru:
       overwrite: Перезаписать
       overwrite_long: Перезаписать имеющиеся данные новыми.
     overwrite_preambles:
-      blocking_html:
-        few: Вы собираетесь <strong>заменить свой список блокировки</strong>, в котором сейчас <strong>%{count} аккаунта</strong>, из файла <strong>%{filename}</strong>.
-        many: Вы собираетесь <strong>заменить свой список блокировки</strong>, в котором сейчас <strong>%{count} аккаунов</strong>, из файла <strong>%{filename}</strong>.
-        one: Вы собираетесь <strong>заменить свой список блокировки</strong>, в котором сейчас <strong>%{count} аккаунт</strong>, из файла <strong>%{filename}</strong>.
-        other: Вы собираетесь <strong>заменить свой список блокировки</strong>, в котором сейчас <strong>%{count} аккаунтов</strong>, из файла <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: Вы собираетесь <strong>заменить свои закладки</strong>, в которых сейчас <strong>%{count} поста</strong>, из файла <strong>%{filename}</strong>.
-        many: Вы собираетесь <strong>заменить свои закладки</strong>, в которых сейчас <strong>%{count} постов</strong>, из файла <strong>%{filename}</strong>.
-        one: Вы собираетесь <strong>заменить свои закладки</strong>, в которых сейчас <strong>%{count} пост</strong>, из файла <strong>%{filename}</strong>.
-        other: Вы собираетесь <strong>заменить свои закладки</strong>, в которых сейчас <strong>%{count} постов</strong>, из файла <strong>%{filename}</strong>.
-      domain_blocking_html:
-        few: Вы собираетесь <strong>заменить свой список доменных блокировок</strong>, в котором сейчас <strong>%{count} домена</strong>, из файла <strong>%{filename}</strong>.
-        many: Вы собираетесь <strong>заменить свой список доменных блокировок</strong>, в котором сейчас <strong>%{count} доменов</strong>, из файла <strong>%{filename}</strong>.
-        one: Вы собираетесь <strong>заменить свой список доменных блокировок</strong>, в котором сейчас <strong>%{count} домен</strong>, из файла <strong>%{filename}</strong>.
-        other: Вы собираетесь <strong>заменить свой список доменных блокировок</strong>, в котором сейчас <strong>%{count} доменов</strong>, из файла <strong>%{filename}</strong>.
-      following_html:
-        few: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунта</strong> из файла <strong>%{filename}</strong> и <strong>отписаться от всех прочих</strong>.
-        many: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong> и <strong>отписаться от всех прочих</strong>.
-        one: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунт</strong> из файла <strong>%{filename}</strong> и <strong>отписаться от всех прочих</strong>.
-        other: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong> и <strong>отписаться от всех прочих</strong>.
-      lists_html:
-        few: Вы собираетесь <strong>заменить свои списки</strong> содержимым файла <strong>%{filename}</strong>. В новые списки будут добавлены <strong>%{count} аккаунта</strong>.
-        many: Вы собираетесь <strong>заменить свои списки</strong> содержимым файла <strong>%{filename}</strong>. В новые списки будут добавлены <strong>%{count} аккаунтов</strong>.
-        one: Вы собираетесь <strong>заменить свои списки</strong> содержимым файла <strong>%{filename}</strong>. В новые списки будет добавлен <strong>%{count} аккаунт</strong>.
-        other: Вы собираетесь <strong>заменить свои списки</strong> содержимым файла <strong>%{filename}</strong>. В новые списки будут добавлены <strong>%{count} аккаунтов</strong>.
-      muting_html:
-        few: Вы собираетесь <strong>заменить свой список игнорируемых пользователей</strong> списком из <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong>.
-        many: Вы собираетесь <strong>заменить свой список игнорируемых пользователей</strong> списком из <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong>.
-        one: Вы собираетесь <strong>заменить свой список игнорируемых пользователей</strong> списком из <strong>%{count} аккаунта</strong> из файла <strong>%{filename}</strong>.
-        other: Вы собираетесь <strong>заменить свой список игнорируемых пользователей</strong> списком из <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong>.
+      blocking_html: Вы собираетесь <strong>заменить свой блок-лист</strong> на <strong>%{total_items} аккаунтов</strong> из <strong>%{filename}</strong>.
+      bookmarks_html: Вы собираетесь <strong>заменить свои закладки</strong> на <strong>%{total_items} постов</strong> из <strong>%{filename}</strong>.
+      domain_blocking_html: Вы собираетесь <strong>заменить ваш список блокировки доменов</strong> на <strong>%{total_items} доменов</strong> из <strong>%{filename}</strong>.
+      following_html: Вы собираетесь <strong>следить</strong> за <strong>%{total_items} аккаунтами</strong> из <strong>%{filename}</strong> и <strong>прекратить следить за всеми остальными</strong>.
+      lists_html: Вы собираетесь <strong>заменить ваши списки</strong> содержимым <strong>%{filename}</strong>. До <strong>%{total_items} аккаунты</strong> будут добавлены в новые списки.
+      muting_html: Вы собираетесь <strong>заменить список отключенных аккаунтов</strong> на <strong>%{total_items} аккаунтов</strong> из <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        few: Вы собираетесь <strong>заблокировать</strong> <strong>%{count} аккаунта</strong> из файла <strong>%{filename}</strong>.
-        many: Вы собираетесь <strong>заблокировать</strong> <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong>.
-        one: Вы собираетесь <strong>заблокировать</strong> <strong>%{count} аккаунт</strong> из файла <strong>%{filename}</strong>.
-        other: Вы собираетесь <strong>заблокировать</strong> <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: Вы собираетесь добавить <strong>%{count} поста</strong> из файла <strong>%{filename}</strong> в свои <strong>закладки</strong>.
-        many: Вы собираетесь добавить <strong>%{count} постов</strong> из файла <strong>%{filename}</strong> в свои <strong>закладки</strong>.
-        one: Вы собираетесь добавить <strong>%{count} пост</strong> из файла <strong>%{filename}</strong> в свои <strong>закладки</strong>.
-        other: Вы собираетесь добавить <strong>%{count} постов</strong> из файла <strong>%{filename}</strong> в свои <strong>закладки</strong>.
-      domain_blocking_html:
-        few: Вы собираетесь <strong>заблокировать</strong> <strong>%{count} домена</strong> из файла <strong>%{filename}</strong>.
-        many: Вы собираетесь <strong>заблокировать</strong> <strong>%{count} доменов</strong> из файла <strong>%{filename}</strong>.
-        one: Вы собираетесь <strong>заблокировать</strong> <strong>%{count} домен</strong> из файла <strong>%{filename}</strong>.
-        other: Вы собираетесь <strong>заблокировать</strong> <strong>%{count} доменов</strong> из файла <strong>%{filename}</strong>.
-      following_html:
-        few: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунта</strong> из файла <strong>%{filename}</strong>.
-        many: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong>.
-        one: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунт</strong> из файла <strong>%{filename}</strong>.
-        other: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong>.
-      lists_html:
-        few: Вы собираетесь добавить <strong>%{count} аккаунта</strong> из файла <strong>%{filename}</strong> в свои <strong>списки</strong>. Если соответствующих списков нет, они будут созданы.
-        many: Вы собираетесь добавить <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong> в свои <strong>списки</strong>. Если соответствующих списков нет, они будут созданы.
-        one: Вы собираетесь добавить <strong>%{count} аккаунт</strong> из файла <strong>%{filename}</strong> в свои <strong>списки</strong>. Если соответствующих списков нет, они будут созданы.
-        other: Вы собираетесь добавить <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong> в свои <strong>списки</strong>. Если соответствующих списков нет, они будут созданы.
-      muting_html:
-        few: Вы собираетесь <strong>начать игнорировать</strong> <strong>%{count} аккаунта</strong> из файла <strong>%{filename}</strong>.
-        many: Вы собираетесь <strong>начать игнорировать</strong> <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong>.
-        one: Вы собираетесь <strong>начать игнорировать</strong> <strong>%{count} аккаунт</strong> из файла <strong>%{filename}</strong>.
-        other: Вы собираетесь <strong>начать игнорировать</strong> <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong>.
+      blocking_html: Вы собираетесь <strong>заблокировать</strong> до <strong>%{total_items} аккаунтов</strong> из <strong>%{filename}</strong>.
+      bookmarks_html: Вы собираетесь добавить до <strong>%{total_items} постов</strong> из <strong>%{filename}</strong> в свои <strong>закладки</strong>.
+      domain_blocking_html: Вы собираетесь <strong>блокировать</strong> до <strong>%{total_items} доменов</strong> от <strong>%{filename}</strong>.
+      following_html: Вы собираетесь <strong>следовать</strong> за <strong>%{total_items} аккаунтами</strong> из <strong>%{filename}</strong>.
+      lists_html: Вы собираетесь добавить до <strong>%{total_items} аккаунтов</strong> от <strong>%{filename}</strong> к вашим <strong>спискам</strong>. Новые списки будут созданы, если нет списка для добавления.
+      muting_html: Вы собираетесь <strong>отключить</strong> до <strong>%{total_items} аккаунтов</strong> из <strong>%{filename}</strong>.
     preface: Вы можете загрузить некоторые данные, например, списки людей, на которых Вы подписаны или которых блокируете, в Вашу учётную запись на этом узле из файлов, экспортированных с другого узла.
     recent_imports: Недавно импортированное
     states:
@@ -1795,7 +1690,7 @@ ru:
   scheduled_statuses:
     over_daily_limit: Вы превысили лимит в %{limit} запланированных постов на указанный день
     over_total_limit: Вы превысили лимит на %{limit} запланированных постов
-    too_soon: дата публикации должна быть в будущем
+    too_soon: Запланированная дата должна быть в будущем
   self_destruct:
     lead_html: К сожалению, <strong>%{domain}</strong> закрывается навсегда. Если вас учётная запись находиться здесь вы не сможете продолжить использовать его, но вы можете запросить резервную копию ваших данных.
     title: Этот сервер закрывается
@@ -1876,7 +1771,7 @@ ru:
       domain_block: Приостановка сервера (%{target_name})
       user_domain_block: Вы заблокировали %{target_name}
     lost_followers: Потерянные подписчики
-    lost_follows: Потерянные подписки
+    lost_follows: Потерянный следует
     preamble: Вы можете потерять подписчиков и последователей, если заблокируете домен или, если ваши модераторы решат приостановить работу удаленного сервера. Когда это произойдет, вы сможете загрузить списки разорванных отношений, чтобы проверить их и, возможно, импортировать на другой сервер.
     purged: Информация об этом сервере была удалена администраторами вашего сервера.
     type: Событие
@@ -1966,8 +1861,6 @@ ru:
       too_late: Слишком поздно обжаловать это замечание
   tags:
     does_not_match_previous_name: не совпадает с предыдущим именем
-  terms_of_service:
-    title: Пользовательское соглашение
   themes:
     contrast: Mastodon (высококонтрастная)
     default: Mastodon (тёмная)
@@ -1999,10 +1892,6 @@ ru:
     recovery_instructions_html: 'Пожалуйста, сохраните коды ниже в надёжном месте: они понадобятся, чтобы войти в учётную запись, если вы потеряете доступ к своему смартфону. Вы можете вручную переписать их, распечатать и спрятать среди важных документов или, например, в любимой книжке. <strong>Каждый код действителен только один раз</strong>.'
     webauthn: Ключи безопасности
   user_mailer:
-    announcement_published:
-      description: 'Администраторы %{domain} опубликовали новое объявление:'
-      subject: Сервисное объявление
-      title: Сервисное объявление %{domain}
     appeal_approved:
       action: Настройки аккаунта
       explanation: Апелляция на разблокировку против вашей учетной записи %{strike_date}, которую вы подали на %{appeal_date}, была одобрена. Ваша учетная запись снова на хорошем счету.
@@ -2032,15 +1921,6 @@ ru:
       further_actions_html: Если это были не вы, рекомендуем вам немедленно %{action} и включить двухфакторную авторизацию, чтобы обезопасить свою учётную запись.
       subject: В вашу учётную запись был выполнен вход с нового IP-адреса
       title: Выполнен вход
-    terms_of_service_changed:
-      agreement: Продолжая использовать %{domain}, вы соглашаетесь с этими условиями. Если вы не согласны с новыми условиями, вы в любой момент можете удалить вашу учётную запись на %{domain}.
-      changelog: 'Вот что обновление условий будет значит для вас в общих чертах:'
-      description: 'Вы получили это сообщение, потому что мы внесли некоторые изменения в пользовательское соглашение %{domain}. Эти изменения вступят в силу %{date}. Рекомендуем вам ознакомиться с обновлёнными условиями по ссылке:'
-      description_html: Вы получили это сообщение, потому что мы внесли некоторые изменения в пользовательское соглашение %{domain}. Эти изменения вступят в силу <strong>%{date}</strong>. Рекомендуем вам ознакомиться с <a href="%{path}" target="_blank">обновлёнными условиями</a>.
-      sign_off: Ваш %{domain}
-      subject: Обновления наших условий использования
-      subtitle: На %{domain} изменилось пользовательское соглашение
-      title: Важное обновление
     warning:
       appeal: Обжаловать
       appeal_description: Если вы считаете, что это ошибка, вы можете обратиться к поддержке %{instance}.
diff --git a/config/locales/sc.yml b/config/locales/sc.yml
index c9f0053bc4..9ab62cea71 100644
--- a/config/locales/sc.yml
+++ b/config/locales/sc.yml
@@ -582,6 +582,7 @@ sc:
       open: Aberi sa publicatzione
       original_status: Publicatzione originale
       status_changed: Publicatzione modificada
+      title: Istados de su contu
       trending: Populares
       visibility: Visibilidade
       with_media: Cun elementos multimediales
@@ -1066,6 +1067,7 @@ sc:
   scheduled_statuses:
     over_daily_limit: As superadu su lìmite de %{limit} publicatziones programmadas pro cudda die
     over_total_limit: As superadu su lìmite de %{limit} tuts programmados
+    too_soon: Sa data programmada depet èssere benidora
   sessions:
     activity: Ùrtima atividade
     browser: Navigadore
diff --git a/config/locales/sco.yml b/config/locales/sco.yml
index 209b761851..8cc733a4ac 100644
--- a/config/locales/sco.yml
+++ b/config/locales/sco.yml
@@ -713,6 +713,7 @@ sco:
       original_status: Original post
       reblogs: Reblogs
       status_changed: Post chynged
+      title: Accoont posts
       trending: Trendin
       visibility: Visibility
       with_media: Wi media
@@ -909,6 +910,7 @@ sco:
     migrate_account: Uise a different accoont
     migrate_account_html: Gin ye'r wantin fir tae redireck this accoont tae a different ane, ye kin <a href="%{path}">configure it here</a>.
     or_log_in_with: Or log in wi
+    privacy_policy_agreement_html: A'v read an A agree tae the <a href="%{privacy_policy_path}" target="_blank">privacy policy</a>
     providers:
       cas: CAS
       saml: SAML
@@ -921,6 +923,7 @@ sco:
     security: Security
     set_new_password: Set new passwird
     sign_up:
+      preamble: Wi a accoont on this Mastodon server, ye'll be able tae follae onie ither person on the netwirk, regairdless o whaur their accoont is hostit.
       title: Let's get ye set up on %{domain}.
     status:
       account_status: Accoont status
@@ -1304,6 +1307,7 @@ sco:
   scheduled_statuses:
     over_daily_limit: Ye'v went by the limit o %{limit} scheduled posts thit ye'r allooed fir the day
     over_total_limit: Ye'v went by the limit o %{limit} scheduled posts
+    too_soon: The schedult date haes tae be in the future
   sessions:
     activity: Last activity
     browser: Brooser
diff --git a/config/locales/si.yml b/config/locales/si.yml
index 5c31bc7e12..270e0ae3fc 100644
--- a/config/locales/si.yml
+++ b/config/locales/si.yml
@@ -607,6 +607,7 @@ si:
       open: ලිපිය අරින්න
       original_status: මුල් ලිපිය
       status_changed: ලිපිය සංශෝධිතයි
+      title: ගිණුමේ ලිපි
       trending: නැගී එන
       with_media: මාධ්‍ය සමඟ
     strikes:
@@ -1176,6 +1177,8 @@ si:
     content_warning: 'අන්තර්ගත අනතුරු ඇඟවීම:'
     descriptions:
       account: "@%{acct} වෙතින් ප්‍රසිද්ධ ලිපි"
+  scheduled_statuses:
+    too_soon: නියමිත දිනය අනාගතයේ විය යුතුය
   sessions:
     activity: අවසාන ක්‍රියාකාරකම
     browser: අතිරික්සුව
diff --git a/config/locales/simple_form.ar.yml b/config/locales/simple_form.ar.yml
index 45b1be79b7..0a665fb784 100644
--- a/config/locales/simple_form.ar.yml
+++ b/config/locales/simple_form.ar.yml
@@ -9,6 +9,7 @@ ar:
         indexable: قد تظهر منشوراتك الموجهة للعامة في نتائج البحث على ماستدون. فالأشخاص الذين تفاعلوا مع منشوراتك قد يكون بمقدورهم البحث عنها بغظ النظر عن ذلك.
         note: 'يمكنك @ذكر أشخاص آخرين أو استعمال #الوسوم.'
         show_collections: سيستطيع الناس من تصفح متابعيك و متابعاتك. سيرى الأشخاص الذين تتبعهم أنك تتبعهم دون أي شك.
+        unlocked: سيتمكن الآخرون من متابعتك دون طلب الموافقة. قم بتعطيله إن كنت ترغب في مراجعة تلك الطلبات يدويا باختيار قبول أو رفض المتابعين الجدد.
       account_alias:
         acct: ادخِل عنون الحساب username@domain الذي تودّ مغادرته
       account_migration:
@@ -313,11 +314,6 @@ ar:
         name: الوسم
         trendable: السماح لهذه الكلمة المفتاحية بالظهور تحت المتداوَلة
         usable: السماح للمنشورات باستخدام هذا الوسم محليا
-      terms_of_service:
-        text: شروط الخدمة
-      terms_of_service_generator:
-        domain: النطاق
-        jurisdiction: الاختصاص القانوني
       user:
         role: الدور
         time_zone: النطاق الزمني
diff --git a/config/locales/simple_form.ast.yml b/config/locales/simple_form.ast.yml
index 9e5db2ba30..cca9f72df1 100644
--- a/config/locales/simple_form.ast.yml
+++ b/config/locales/simple_form.ast.yml
@@ -4,6 +4,7 @@ ast:
     hints:
       account:
         note: 'Pues @mentar a otros perfiles o poner #etiquetes.'
+        unlocked: Los perfiles van ser a siguite ensin solicitar la to aprobación. Desmarca esta opción si quies revisar les solicitúes de siguimientu y escoyer si aceptales o refugales.
       admin_account_action:
         text_html: Opcional. Pues usar la sintaxis de los artículos y amestar <a href="%{path}">alvertencies preconfiguraes</a> p'aforrar tiempu
         type_html: Escueyi qué facer con <strong>%{acct}</strong>
diff --git a/config/locales/simple_form.be.yml b/config/locales/simple_form.be.yml
index b68e03b057..db6a94f8eb 100644
--- a/config/locales/simple_form.be.yml
+++ b/config/locales/simple_form.be.yml
@@ -9,6 +9,7 @@ be:
         indexable: Вашыя публічныя допісы могуць з'яўляцца ў рэзультатах пошуку Mastodon. Людзі, якія ўзаемадзейнічалі з вашымі допісамі, усё роўна маюць магчымасць іх знаходзіць.
         note: 'Вы можаце @згадаць іншых людзей або выкарыстоўваць #хэштэгі.'
         show_collections: Людзі змогуць праглядаць спіс вашых падпісак і падпісчыкаў. Людзі, на якіх вы падпісаны ў любым выпадку будуць бачыць, што вы іх чытаеце.
+        unlocked: Людзі змогуць сачыць за вамі, не запытваючы пацвярджэння. Зніміце сцяжок, калі вы хочаце праглядаць запыты на падпіску і выбіраць, прымаць ці адхіляць новых падпісчыкаў.
       account_alias:
         acct: Прызначце карыстальнік@дамен уліковага запісу з якога вы хочаце пераехаць
       account_migration:
@@ -313,9 +314,6 @@ be:
         name: Хэштэг
         trendable: Дазволіць паказ гэтага хэштэга ў трэндах
         usable: Дазволіць допісам выкарыстоўваць гэты хэштэг лакальна
-      terms_of_service_generator:
-        domain: Дамэн
-        jurisdiction: Юрысдыкцыя
       user:
         role: Роля
         time_zone: Часавы пояс
diff --git a/config/locales/simple_form.bg.yml b/config/locales/simple_form.bg.yml
index 6180f51dd3..567c55eae8 100644
--- a/config/locales/simple_form.bg.yml
+++ b/config/locales/simple_form.bg.yml
@@ -3,14 +3,14 @@ bg:
   simple_form:
     hints:
       account:
-        attribution_domains: Едно на ред. Защитава от фалшиви атрибути.
+        attribution_domains_as_text: Едно на ред. Защитава от фалшиви атрибути.
         discoverable: Вашите публични публикации и профил може да се представят или препоръчват в различни области на Mastodon и вашия профил може да се предлага на други потребители.
         display_name: Вашето пълно име или псевдоним.
         fields: Вашата начална страница, местоимения, години, всичко що искате.
         indexable: Вашите обществени публикации може да се появят в резултатите от търсене в Mastodon. Взаимодействалите с публикациите ви може да ги търсят независимо.
         note: 'Може да @споменавате други хора или #хаштагове.'
         show_collections: Хората ще може да разглеждат през вашите последвания и последователи. Хората, които сте следвали, ще видят, че ги следвате независимо от това.
-        unlocked: Хората ще могат да ви последват без изискване на одобрение. Размаркирайте, ако искате да преглеждате заявките за последване и избирате дали да приемете или отхвърлите новите последователи.
+        unlocked: Хората ще могат да ви последват без изискване на одобрение. Размаркирайте, ако искате да преглеждате заявките за последване и изберете дали да приемете или отхвърлите новите последователи.
       account_alias:
         acct: Посочете потребителско_име@домейн на акаунта си, от който искате да се преместите
       account_migration:
@@ -60,7 +60,6 @@ bg:
         setting_display_media_default: Скриване на мултимедия отбелязана като деликатна
         setting_display_media_hide_all: Винаги скриване на мултимедията
         setting_display_media_show_all: Винаги показване на мултимедията
-        setting_system_scrollbars_ui: Прилага се само към настолни браузъри, основаващи се на Safari и Chrome
         setting_use_blurhash: Преливането е въз основа на цветовете на скритите визуализации, но се замъгляват подробностите
         setting_use_pending_items: Да се показват обновявания на часовата ос само след щракване вместо автоматично превъртане на инфоканала
         username: Може да ползвате букви, цифри и долни черти
@@ -75,7 +74,6 @@ bg:
       filters:
         action: Изберете кое действие да се извърши, прецеждайки съвпаденията на публикацията
         actions:
-          blur: Скриване на мултимедия зад предупреждение, но без скриване на самия текст
           hide: Напълно скриване на филтрираното съдържание, сякаш не съществува
           warn: Скриване на филтрираното съдържание зад предупреждение, споменавайки заглавието на филтъра
       form_admin_settings:
@@ -89,7 +87,6 @@ bg:
         favicon: WEBP, PNG, GIF или JPG. Заменя стандартната сайтоикона на Mastodon с произволна икона.
         mascot: Замества илюстрацията в разширения уеб интерфейс.
         media_cache_retention_period: Мултимедийни файлове от публикации, направени от отдалечени потребители, се сринаха в сървъра ви. Задавайки положителна стойност, мултимедията ще се изтрие след посочения брой дни. Ако се искат мултимедийни данни след изтриването, то ще се изтегли пак, ако още е наличен източникът на съдържание. Поради ограниченията за това колко често картите за предварващ преглед на връзките анкетират сайтове на трети страни, се препоръчва да зададете тази стойност на поне 14 дни или картите за предварващ преглед на връзките няма да се обновяват при поискване преди този момент.
-        min_age: Потребителите ще се питат да потвърдят рождената си дата по време на регириране
         peers_api_enabled: Списък от имена на домейни, с които сървърът се е свързал във федивселената. Тук не се включват данни за това дали федерирате с даден сървър, а само за това дали сървърът ви знае за него. Това се ползва от услуги, събиращи статистика за федерацията в общия смисъл.
         profile_directory: Указателят на профили вписва всички потребители, избрали да бъдат откриваеми.
         require_invite_text: Когато регистрацията изисква ръчно одобрение, то направете текстовото поле за това "Защо желаете да се присъедините?" по-скоро задължително, отколкото по желание
@@ -132,22 +129,8 @@ bg:
         show_application: Винаги ще може да видите кое приложение е публикувало публикацията ви независимо.
       tag:
         name: Можете да смените само употребата на големи/малки букви, например, за да е по-четимо
-      terms_of_service:
-        changelog: Може да се структурира със синтаксиса на Markdown.
-        effective_date: Разумната времева рамка може да обхваща някъде от 10 до 30 дни от датата, на която сте известили потребителите си.
-        text: Може да се структурира със синтаксиса на Markdown.
-      terms_of_service_generator:
-        admin_email: Правните бележки включват насрещни известия, постановления на съда, заявки за сваляне и заявки от правоохранителните органи.
-        arbitration_address: Може да е същото като физическия адрес горе или "неприложимо", ако се употребява имейл.
-        arbitration_website: Може да е уеб формуляр или "неналично", ако употребявате имейл.
-        choice_of_law: Град, регион, територия, щат или държава, чиито вътрешни материални права ще уреждат всички искове.
-        dmca_email: Може да е същият имейл, използван за "Имейл адрес за правни известия" по-горе.
-        domain: Неповторимо идентифициране на онлайн услугата, която предоставяте.
-        jurisdiction: Впишете държавата, където живее всеки, който плаща сметките. Ако е дружество или друго образувание, то впишете държавата, в която е регистрирано, и градът, регионът, територията или щатът според случая.
-        min_age: Не трябва да е под изискваната минимална възраст от закона на юрисдикцията ви.
       user:
         chosen_languages: Само публикации на отметнатите езици ще се показват в публичните часови оси
-        date_of_birth: Трябва да се уверим, че сте поне на %{age}, за да употребявате Mastodon. Няма да съхраняваме това.
         role: Ролята управлява какви позволения има потребителят.
       user_role:
         color: Цветът, използван за ролите в потребителския интерфейс, като RGB в шестнадесетичен формат
@@ -161,7 +144,7 @@ bg:
         url: До къде ще се изпращат събитията
     labels:
       account:
-        attribution_domains: Уебсайтове, позволено им да приписват авторството ви
+        attribution_domains_as_text: Уебсайтове, на които е позволено да приписват авторството ви
         discoverable: Включване на профил и публикации в алгоритмите за откриване
         fields:
           name: Етикет
@@ -238,10 +221,8 @@ bg:
         setting_display_media_show_all: Показване на всичко
         setting_expand_spoilers: Винаги разширяване на публикации, отбелязани с предупреждения за съдържание
         setting_hide_network: Скриване на социалния ви свързан граф
-        setting_missing_alt_text_modal: Показване на диалогов прозорец потвърждение преди публикуване на мултимедия без алт. текст
         setting_reduce_motion: Обездвижване на анимациите
         setting_system_font_ui: Употреба на стандартния шрифт на системата
-        setting_system_scrollbars_ui: Употреба на системната подразбираща се лента за превъртане
         setting_theme: Тема на сайта
         setting_trends: Показване на днешното налагащо се
         setting_unfollow_modal: Показване на прозорче за потвърждение преди прекратяване следването на някого
@@ -260,7 +241,6 @@ bg:
         name: Хаштаг
       filters:
         actions:
-          blur: Скриване на мултимедията с предупреждение
           hide: Напълно скриване
           warn: Скриване зад предупреждение
       form_admin_settings:
@@ -274,7 +254,6 @@ bg:
         favicon: Сайтоикона
         mascot: Плашило талисман по избор (остаряло)
         media_cache_retention_period: Период на запазване на мултимедийния кеш
-        min_age: Минимално възрастово изискване
         peers_api_enabled: Публикуване на списъка с открити сървъри в API
         profile_directory: Показване на директорията от профили
         registrations_mode: Кой може да се регистрира
@@ -338,24 +317,7 @@ bg:
         name: Хаштаг
         trendable: Показване на хаштага да се появява под нашумели
         usable: Позволяване на публикациите да употребяват този хаштаг локално
-      terms_of_service:
-        changelog: Какво е променено?
-        effective_date: Дата на влизане в сила
-        text: Условия на услугата
-      terms_of_service_generator:
-        admin_email: Имейл адрес за правни известия
-        arbitration_address: Физичен адрес за арбитражни известия
-        arbitration_website: Уебсайт за подаване на арбитражни известия
-        choice_of_law: Избор на закон
-        dmca_address: Физичен адрес за DMCA/бележки за авторски права
-        dmca_email: Адрес на е-поща за DMCA/бележки за авторски права
-        domain: Домейн
-        jurisdiction: Законова юрисдикция
-        min_age: Минимална възраст
       user:
-        date_of_birth_1i: Ден
-        date_of_birth_2i: Месец
-        date_of_birth_3i: Година
         role: Роля
         time_zone: Часова зона
       user_role:
diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml
index 2f85738c32..168ccc697f 100644
--- a/config/locales/simple_form.ca.yml
+++ b/config/locales/simple_form.ca.yml
@@ -3,14 +3,14 @@ ca:
   simple_form:
     hints:
       account:
-        attribution_domains: Un per línia. Protegeix de falses atribucions.
+        attribution_domains_as_text: Un per línia. Protegeix de falses atribucions.
         discoverable: El teu perfil i els teus tuts públics poden aparèixer o ser recomanats en diverses àreas de Mastodon i el teu perfil pot ser suggerit a altres usuaris.
         display_name: El teu nom complet o el teu nom divertit.
         fields: La teva pàgina d'inici, pronoms, edat, el que vulguis.
         indexable: Els teus tuts públics poden aparèixer en els resultats de cerca de Mastodon. Les persones que han interaccionat amb el teus tuts seran sempre capaços de cercar-los.
         note: 'Pots @mencionar altra gent o #etiquetes.'
         show_collections: La gent podrà navegar pels teus seguits i seguidors. Qui segueixis sí que veurà que els segueixes.
-        unlocked: Us podran seguir sense la vostra aprovació. Desmarqueu si voleu moderar les peticions de seguiment i decidir quines autoritzeu o denegueu.
+        unlocked: La gent serà capaç de seguir-te sense sol·licitar-ho. Desmarca si vols revisar les sol·licituds i triar si acceptes o no els nous seguidors.
       account_alias:
         acct: Especifica l'usuari@domini del compte des d'on et vols traslladar
       account_migration:
@@ -60,7 +60,6 @@ ca:
         setting_display_media_default: Amaga el contingut gràfic marcat com a sensible
         setting_display_media_hide_all: Oculta sempre tot el contingut multimèdia
         setting_display_media_show_all: Mostra sempre el contingut gràfic
-        setting_system_scrollbars_ui: S'aplica només als navegadors d'escriptori basats en Safari i Chrome
         setting_use_blurhash: Els degradats es basen en els colors de les imatges ocultes, però n'enfosqueixen els detalls
         setting_use_pending_items: Amaga les actualitzacions de la línia de temps després de fer un clic, en lloc de desplaçar-les automàticament
         username: Pots emprar lletres, números i subratllats
@@ -75,7 +74,6 @@ ca:
       filters:
         action: Tria quina acció cal executar quan un apunt coincideixi amb el filtre
         actions:
-          blur: Amaga el contingut multimèdia rere un avís, sense amagar el text en si
           hide: Ocultar completament el contingut filtrat, comportant-se com si no existís
           warn: Oculta el contingut filtrat darrere d'un avís mencionant el títol del filtre
       form_admin_settings:
@@ -89,7 +87,6 @@ ca:
         favicon: WEBP, PNG, GIF o JPG. Canvia la icona per defecte de Mastodon a la pestanya del navegador per una de personalitzada.
         mascot: Anul·la la il·lustració en la interfície web avançada.
         media_cache_retention_period: El vostre servidor conserva una còpia dels fitxers multimèdia de les publicacions dels usuaris remots. Si s'indica un valor positiu, s'esborraran passats els dies indicats. Si el fitxer es torna a demanar un cop esborrat, es tornarà a baixar si el contingut origen segueix disponible. Per causa de les restriccions en la freqüència amb què es poden demanar les targetes de previsualització d'altres servidors, es recomana definir aquest valor com a mínim a 14 dies, o les targetes de previsualització no s'actualizaran a demanda abans d'aquest termini.
-        min_age: Es demanarà als usuaris la data de naixement durant la inscripció
         peers_api_enabled: Una llista de noms de domini que aquest servidor ha trobat al fedivers. No inclou cap dada sobre si estàs federat amb un servidor determinat, només si el teu en sap res. La fan servir, en un sentit general, serveis que recol·lecten estadístiques sobre la federació.
         profile_directory: El directori de perfils llista tots els usuaris que tenen activat ser descoberts.
         require_invite_text: Quan el registre requereixi aprovació manual, fes que sigui obligatori en lloc d'opcional d'escriure el text de la sol·licitud d'invitació "Per què vols unir-te?"
@@ -132,20 +129,8 @@ ca:
         show_application: Sempre podràs veure quina aplicació ha publicat els teus tuts.
       tag:
         name: Només pots canviar la caixa de les lletres, per exemple, per fer-la més llegible
-      terms_of_service:
-        changelog: S'hi pot fer servir sintaxi Markdown.
-        effective_date: Un termini raonable podria ser d'entre 10 i 30 dies des que notifiques els usuaris.
-        text: S'hi pot fer servir sintaxi Markdown.
-      terms_of_service_generator:
-        admin_email: Els avisos legals inclouen contraavisos, ordres judicials, sol·licituds de retirada i sol·licituds d'aplicació de la llei.
-        choice_of_law: Ciutat, regió, territori o estat els jutjats del qual hauran de dirimir qualsevol disputa legal.
-        dmca_email: Pot ser la mateixa que heu fet servir abans per a «adreça-e per als avisos legals».
-        domain: Identificació única del servei en línia que oferiu.
-        jurisdiction: Indiqueu el país on resideix qui paga les factures. Si és una empresa o una altra entitat, indiqueu el país en què està registrada, així com la ciutat, regió, territori o estat, segons calqui.
-        min_age: No hauria de ser inferior a l'edat mínima exigida per la llei de la vostra jurisdicció.
       user:
         chosen_languages: Quan estigui marcat, només es mostraran els tuts de les llengües seleccionades en les línies de temps públiques
-        date_of_birth: Ens hem d'assegurar que teniu %{age} anys com a mínim. No desarem aquesta dada.
         role: El rol controla quins permisos té l'usuari.
       user_role:
         color: Color que s'usarà per al rol a tota la interfície d'usuari, com a RGB en format hexadecimal
@@ -159,7 +144,7 @@ ca:
         url: On els esdeveniments seran enviats
     labels:
       account:
-        attribution_domains: Llocs web que us poden donar crèdit
+        attribution_domains_as_text: Webs que us poden donar crèdit
         discoverable: Permet el perfil i el tuts en els algorismes de descobriment
         fields:
           name: Etiqueta
@@ -236,10 +221,8 @@ ca:
         setting_display_media_show_all: Mostra-ho tot
         setting_expand_spoilers: Desplega sempre els tuts marcats amb advertències de contingut
         setting_hide_network: Amaga la teva xarxa
-        setting_missing_alt_text_modal: Mostra un diàleg de confirmació abans de publicar contingut sense text alternatiu
         setting_reduce_motion: Redueix el moviment de les animacions
         setting_system_font_ui: Usa la lletra predeterminada del sistema
-        setting_system_scrollbars_ui: Usa la barra de desplaçament predeterminada del sistema
         setting_theme: Tema del lloc
         setting_trends: Mostra les tendències d'avui
         setting_unfollow_modal: Mostra el diàleg de confirmació abans de deixar de seguir algú
@@ -258,7 +241,6 @@ ca:
         name: Etiqueta
       filters:
         actions:
-          blur: Amaga el contingut multimèdia amb un avís
           hide: Oculta completament
           warn: Oculta amb un avís
       form_admin_settings:
@@ -272,7 +254,6 @@ ca:
         favicon: Icona de preferits
         mascot: Mascota personalitzada (llegat)
         media_cache_retention_period: Període de retenció del cau multimèdia
-        min_age: Edat mínima requerida
         peers_api_enabled: Publica a l'API la llista de servidors descoberts
         profile_directory: Habilita el directori de perfils
         registrations_mode: Qui es pot registrar
@@ -336,24 +317,7 @@ ca:
         name: Etiqueta
         trendable: Permet que aquesta etiqueta aparegui en les tendències
         usable: Permet a les publicacions emprar aquesta etiqueta localment
-      terms_of_service:
-        changelog: Què ha canviat?
-        effective_date: Data efectiva
-        text: Condicions de servei
-      terms_of_service_generator:
-        admin_email: Adreça-e per als avisos legals
-        arbitration_address: Adreça física per a les notificacions d'arbitratge
-        arbitration_website: Lloc web per a enviar les notificacions d'arbitratge
-        choice_of_law: Legislació aplicable
-        dmca_address: Adreça física per a les notificacions de copyright/DMCA
-        dmca_email: Adreça-e per a les notificacions de copyright/DMCA
-        domain: Domini
-        jurisdiction: Jurisdicció
-        min_age: Edat mínima
       user:
-        date_of_birth_1i: Dia
-        date_of_birth_2i: Mes
-        date_of_birth_3i: Any
         role: Rol
         time_zone: Zona horària
       user_role:
diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml
index 00abf91d3e..1650502388 100644
--- a/config/locales/simple_form.cs.yml
+++ b/config/locales/simple_form.cs.yml
@@ -3,14 +3,14 @@ cs:
   simple_form:
     hints:
       account:
-        attribution_domains: Jeden na řádek. Chrání před falešným připisováním autorství.
+        attribution_domains_as_text: Jeden na řádek. Chrání před falešným připisování autorství.
         discoverable: Vaše veřejné příspěvky a profil mohou být zobrazeny nebo doporučeny v různých oblastech Mastodonu a váš profil může být navrhován ostatním uživatelům.
         display_name: Vaše celé jméno nebo přezdívka.
         fields: Vaše domovská stránka, zájmena, věk, cokoliv chcete.
         indexable: Vaše veřejné příspěvky se mohou objevit ve výsledcích vyhledávání na Mastodonu. Lidé, kteří s vašimi příspěvky interagovaly, je mohou stále vyhledávat.
         note: 'Můžete @zmínit jiné osoby nebo #hashtagy.'
         show_collections: Lidé budou moci procházet skrz sledující. Lidé, které sledujete, uvidí, že je sledujete bezohledně.
-        unlocked: Lidé vás budou moci sledovat, aniž by vás žádali o schválení. Zrušte zaškrtnutí, pokud chcete kontrolovat požadavky o sledování a vybírat si, zda přijmete nebo odmítnete nové sledující.
+        unlocked: Lidé vás budou moci sledovat, aniž by vás žádali o schválení. Zrušte zaškrtnutí, pokud chcete zkontrolovat požadavky a zvolte, zda přijmete nebo odmítněte nové následovníky.
       account_alias:
         acct: Zadejte svůj účet, ze kterého se chcete přesunout jinam, ve formátu přezdívka@doména
       account_migration:
@@ -60,7 +60,6 @@ cs:
         setting_display_media_default: Skrývat média označená jako citlivá
         setting_display_media_hide_all: Vždy skrývat média
         setting_display_media_show_all: Vždy zobrazovat média
-        setting_system_scrollbars_ui: Platí pouze pro desktopové prohlížeče založené na Safari nebo Chrome
         setting_use_blurhash: Gradienty jsou vytvořeny na základě barvev skrytých médií, ale zakrývají veškeré detaily
         setting_use_pending_items: Aktualizovat časovou osu až po kliknutí namísto automatického rolování kanálu
         username: Pouze písmena, číslice a podtržítka
@@ -75,7 +74,6 @@ cs:
       filters:
         action: Vyberte, jakou akci provést, když příspěvek odpovídá filtru
         actions:
-          blur: Skrýt média za varováním, bez skrytí samotného textu
           hide: Úplně schovat filtrovaný obsah tak, jako by neexistoval
           warn: Schovat filtrovaný obsah za varováním zmiňujicím název filtru
       form_admin_settings:
@@ -89,7 +87,6 @@ cs:
         favicon: WEBP, PNG, GIF nebo JPG. Nahradí výchozí favicon Mastodonu vlastní ikonou.
         mascot: Přepíše ilustraci v pokročilém webovém rozhraní.
         media_cache_retention_period: Mediální soubory z příspěvků vzdálených uživatelů se ukládají do mezipaměti na vašem serveru. Pokud je nastaveno na kladnou hodnotu, budou média po zadaném počtu dní odstraněna. Pokud jsou mediální data vyžádána po jejich odstranění, budou znovu stažena, pokud je zdrojový obsah stále k dispozici. Vzhledem k omezením týkajícím se četnosti dotazů karet náhledů odkazů na weby třetích stran se doporučuje nastavit tuto hodnotu alespoň na 14 dní, jinak nebudou karty náhledů odkazů na vyžádání aktualizovány dříve.
-        min_age: Uživatelé budou požádáni, aby při registraci potvrdili datum svého narození
         peers_api_enabled: Seznam názvů domén se kterými se tento server setkal ve fediversu. Neobsahuje žádná data o tom, zda jste federovali s daným serverem, pouze že o něm váš server ví. Toto je využíváno službami, které sbírají o federování statistiku v obecném smyslu.
         profile_directory: Adresář profilu obsahuje seznam všech uživatelů, kteří se přihlásili, aby mohli být nalezeni.
         require_invite_text: Pokud přihlášení vyžaduje ruční schválení, měl by být textový vstup „Proč se chcete připojit?“ povinný spíše než volitelný
@@ -132,23 +129,8 @@ cs:
         show_application: I tak budete vždy moci vidět, která aplikace zveřejnila váš příspěvek.
       tag:
         name: Můžete měnit pouze velikost písmen, například kvůli lepší čitelnosti
-      terms_of_service:
-        changelog: Může být strukturováno pomocí Markdown syntaxu.
-        effective_date: Rozumné časové rozpětí může být kdykoliv od 10 do 30 dnů od doby, kdy upozorníte své uživatele.
-        text: Může být strukturováno pomocí Markdown syntaxu.
-      terms_of_service_generator:
-        admin_email: Právní oznámení zahrnují protioznámení, soudní příkazy, žádosti o stáhnutí příspěvků a žádosti od právních vymahačů.
-        arbitration_address: Může být stejné jako výše uvedená fyzická adresa, nebo „N/A“, pokud používáte e-mail.
-        arbitration_website: Může být webový formulář nebo „N/A“, pokud používáte email.
-        choice_of_law: Město, region, území nebo stát, jehož vnitřní hmotné právo stanovuje povinnosti pro veškeré nároky.
-        dmca_address: V případě provozovatelů v USA použijte adresu zapsanou v DMCA Designated Agent Directory. Na přímou žádost je možné použít namísto domovské adresy PO box. V tomto případě použijte DMCA Designated Agent Post Office Box Waiver Request k zaslání emailu Copyright Office a řekněte jim, že jste malým moderátorem obsahu, který se obavá pomsty nabo odplaty za Vaši práci a potřebujete použít PO box, abyste schovali Vaši domovskou adresu před širokou veřejností.
-        dmca_email: Může být stejný e-mail použitý pro „E-mailová adresa pro právní upozornění.“
-        domain: Jedinečná identifikace online služby, kterou poskytujete.
-        jurisdiction: Uveďte zemi, kde žije ten, kdo platí účty. Pokud je to společnost nebo jiný subjekt, uveďte zemi, v níž je zapsán do obchodního rejstříku, a město, region, území, případně stát, pokud je to povinné.
-        min_age: Neměla by být pod minimálním věkem požadovaným zákony vaší jurisdikce.
       user:
         chosen_languages: Po zaškrtnutí budou ve veřejných časových osách zobrazeny pouze příspěvky ve zvolených jazycích
-        date_of_birth: Musíme se ujistit, že je Vám alespoň %{age}, abyste mohli používat Mastodon. Nebudeme to ukládat.
         role: Role určuje, která oprávnění uživatel má.
       user_role:
         color: Barva, která má být použita pro roli v celém UI, jako RGB v hex formátu
@@ -162,7 +144,7 @@ cs:
         url: Kam budou události odesílány
     labels:
       account:
-        attribution_domains: Webové stránky s povolením Vám připsat autorství
+        attribution_domains_as_text: Webové stránky s povolením Vám připsat autorství
         discoverable: Zobrazovat profil a příspěvky ve vyhledávacích algoritmech
         fields:
           name: Označení
@@ -239,10 +221,8 @@ cs:
         setting_display_media_show_all: Zobrazit vše
         setting_expand_spoilers: Vždy rozbalit příspěvky označené varováními o obsahu
         setting_hide_network: Skrýt mou síť
-        setting_missing_alt_text_modal: Zobrazit potvrzovací dialog před odesláním médií bez alt textu
         setting_reduce_motion: Omezit pohyb v animacích
         setting_system_font_ui: Použít výchozí písmo systému
-        setting_system_scrollbars_ui: Použít výchozí posuvník systému
         setting_theme: Vzhled stránky
         setting_trends: Zobrazit dnešní trendy
         setting_unfollow_modal: Před zrušením sledování zobrazovat potvrzovací okno
@@ -261,7 +241,6 @@ cs:
         name: Hashtag
       filters:
         actions:
-          blur: Skrýt média za varováním
           hide: Zcela skrýt
           warn: Skrýt s varováním
       form_admin_settings:
@@ -275,7 +254,6 @@ cs:
         favicon: Favicon
         mascot: Vlastní maskot (zastaralé)
         media_cache_retention_period: Doba uchovávání mezipaměti médií
-        min_age: Minimální věková hranice
         peers_api_enabled: Zveřejnit seznam nalezených serverů v API
         profile_directory: Povolit adresář profilů
         registrations_mode: Kdo se může přihlásit
@@ -339,24 +317,7 @@ cs:
         name: Hashtag
         trendable: Povolit zobrazení tohoto hashtagu mezi populárními
         usable: Povolit příspěvkům používat tento hashtag lokálně
-      terms_of_service:
-        changelog: Co se změnilo?
-        effective_date: Datum platnosti
-        text: Podmínky užití
-      terms_of_service_generator:
-        admin_email: E-mailová adresa pro právní upozornění
-        arbitration_address: Fyzická adresa pro upozornění na arbitrační řízení
-        arbitration_website: Webová stránka pro zasílání arbitračních upozornění
-        choice_of_law: Volba zákona
-        dmca_address: Fyzická adresa pro oznámení DMCA/porušení autorských práv
-        dmca_email: E-mailová adresa pro oznámení DMCA/porušení autorských práv
-        domain: Doména
-        jurisdiction: Právní příslušnost
-        min_age: Věková hranice
       user:
-        date_of_birth_1i: Den
-        date_of_birth_2i: Měsíc
-        date_of_birth_3i: Rok
         role: Role
         time_zone: Časové pásmo
       user_role:
diff --git a/config/locales/simple_form.cy.yml b/config/locales/simple_form.cy.yml
index c9c7862a91..a4aaec11ad 100644
--- a/config/locales/simple_form.cy.yml
+++ b/config/locales/simple_form.cy.yml
@@ -3,14 +3,14 @@ cy:
   simple_form:
     hints:
       account:
-        attribution_domains: Un i bob llinell. Yn diogelu rhag priodoli ffug.
+        attribution_domains_as_text: Un i bob llinell. Yn amddiffyn rhag priodoli ffug.
         discoverable: Mae'n bosibl y bydd eich postiadau cyhoeddus a'ch proffil yn cael sylw neu'n cael eu hargymell mewn gwahanol feysydd o Mastodon ac efallai y bydd eich proffil yn cael ei awgrymu i ddefnyddwyr eraill.
         display_name: Eich enw llawn neu'ch enw hwyl.
         fields: Eich tudalen cartref, rhagenwau, oed, neu unrhyw beth.
         indexable: Mae'n bosib y bydd eich postiadau cyhoeddus yn ymddangos yng nghanlyniadau chwilio ar Mastodon. Mae'n bosibl y bydd pobl sydd wedi rhyngweithio â'ch postiadau yn dal i allu eu chwilio.
         note: 'Gallwch @grybwyll pobl eraill neu #hashnodau.'
         show_collections: Bydd pobl yn gallu pori trwy'r rhai rydych yn eu dilyn a'ch dilynwyr. Bydd y bobl rydych chi'n eu dilyn yn gweld eich bod chi'n eu dilyn beth bynnag.
-        unlocked: Bydd pobl yn gallu eich dilyn heb ofyn am gymeradwyaeth. Dad-diciwch os ydych am adolygu ceisiadau i'ch dilyn a dewis a ydych am dderbyn neu wrthod dilynwyr newydd.
+        unlocked: Bydd pobl yn gallu eich dilyn heb ofyn am ganiatâd. Dad-diciwch os ydych am adolygu ceisiadau dilyn a dewis a ydych am dderbyn neu wrthod dilynwyr newydd.
       account_alias:
         acct: Rhowch enwdefnyddiwr@parth y cyfrif y hoffech chi symud ohono
       account_migration:
@@ -41,7 +41,7 @@ cy:
       defaults:
         autofollow: Bydd pobl sy'n cofrestru drwy'r gwahoddiad yn eich dilyn yn awtomatig
         avatar: WEBP, PNG, GIF neu JPG. %{size} ar y mwyaf. Bydd yn cael ei leihau i %{dimensions}px
-        bot: Rhoi gwybod i eraill bod y cyfrif hwn yn perfformio gweithredoedd awtomatig yn bennaf ac mae'n bosib nad yw'n cael ei fonitro
+        bot: Mae'r cyfrif hwn yn perfformio gweithredoedd awtomatig yn bennaf ac mae'n bosib nad yw'n cael ei fonitro
         context: Un neu fwy cyd-destun lle dylai'r hidlydd weithio
         current_password: At ddibenion diogelwch, nodwch gyfrinair y cyfrif cyfredol
         current_username: I gadarnhau, nodwch enw defnyddiwr y cyfrif cyfredol
@@ -60,7 +60,6 @@ cy:
         setting_display_media_default: Cuddio cyfryngau wedi eu marcio'n sensitif
         setting_display_media_hide_all: Cuddio cyfryngau bob tro
         setting_display_media_show_all: Dangos cyfryngau bob tro
-        setting_system_scrollbars_ui: Yn berthnasol i borwyr bwrdd gwaith yn seiliedig ar Safari a Chrome yn unig
         setting_use_blurhash: Mae graddiannau wedi'u seilio ar liwiau'r delweddau cudd ond maen nhw'n cuddio unrhyw fanylion
         setting_use_pending_items: Cuddio diweddariadau llinell amser y tu ôl i glic yn lle sgrolio'n awtomatig
         username: Gallwch ddefnyddio nodau, rhifau a thanlinellau
@@ -75,7 +74,6 @@ cy:
       filters:
         action: Dewiswch pa weithred i'w chyflawni pan fydd postiad yn cyfateb i'r hidlydd
         actions:
-          blur: Cuddio cyfryngau tu ôl i rybudd, heb guddio'r testun ei hun
           hide: Cuddiwch y cynnwys wedi'i hidlo'n llwyr, gan ymddwyn fel pe na bai'n bodoli
           warn: Cuddiwch y cynnwys wedi'i hidlo y tu ôl i rybudd sy'n sôn am deitl yr hidlydd
       form_admin_settings:
@@ -89,7 +87,6 @@ cy:
         favicon: WEBP, PNG, GIF neu JPG. Yn diystyru'r favicon Mastodon rhagosodedig gydag eicon cyfaddas.
         mascot: Yn diystyru'r darlun yn y rhyngwyneb gwe uwch.
         media_cache_retention_period: Mae ffeiliau cyfryngau o bostiadau a wneir gan ddefnyddwyr o bell yn cael eu storio ar eich gweinydd. Pan gaiff ei osod i werth positif, bydd y cyfryngau yn cael eu dileu ar ôl y nifer penodedig o ddyddiau. Os gofynnir am y data cyfryngau ar ôl iddo gael ei ddileu, caiff ei ail-lwytho i lawr, os yw'r cynnwys ffynhonnell yn dal i fod ar gael. Oherwydd cyfyngiadau ar ba mor aml y mae cardiau rhagolwg cyswllt yn pleidleisio i wefannau trydydd parti, argymhellir gosod y gwerth hwn i o leiaf 14 diwrnod, neu ni fydd cardiau rhagolwg cyswllt yn cael eu diweddaru ar alw cyn yr amser hwnnw.
-        min_age: Mae gofyn i ddefnyddwyr gadarnhau eu dyddiad geni wrth gofrestru
         peers_api_enabled: Rhestr o enwau parth y mae'r gweinydd hwn wedi dod ar eu traws yn y ffediws. Nid oes unrhyw ddata wedi'i gynnwys yma ynghylch a ydych chi'n ffedereiddio â gweinydd penodol, dim ond bod eich gweinydd yn gwybod amdano. Defnyddir hwn gan wasanaethau sy'n casglu ystadegau ar ffedereiddio mewn ystyr cyffredinol.
         profile_directory: Mae'r cyfeiriadur proffil yn rhestru'r holl ddefnyddwyr sydd wedi dewis i fod yn ddarganfyddiadwy.
         require_invite_text: Pan fydd angen cymeradwyaeth â llaw ar gyfer cofrestriadau, gwnewch y “Pam ydych chi am ymuno?” mewnbwn testun yn orfodol yn hytrach na dewisol
@@ -132,23 +129,8 @@ cy:
         show_application: Byddwch bob amser yn gallu gweld pa ap a gyhoeddodd eich postiad beth bynnag.
       tag:
         name: Dim ond er mwyn ei gwneud yn fwy darllenadwy y gallwch chi newid y llythrennau, er enghraifft
-      terms_of_service:
-        changelog: Mae modd ei strwythuro gyda chystrawen Markdown.
-        effective_date: Gall amserlen resymol amrywio rhwng 10 a 30 diwrnod o'r dyddiad y byddwch yn hysbysu'ch defnyddwyr.
-        text: Mae modd ei strwythuro gyda chystrawen Markdown.
-      terms_of_service_generator:
-        admin_email: Mae hysbysiadau cyfreithiol yn cynnwys gwrth-hysbysiadau, gorchmynion llys, ceisiadau tynnu i lawr, a cheisiadau gorfodi'r gyfraith.
-        arbitration_address: Gall fod yr un peth â'r cyfeiriad corfforol uchod, neu “D/A” os ydych chi'n defnyddio e-bost.
-        arbitration_website: Gall fod yn ffurflen we, neu “D/A” os ydych chi'n defnyddio e-bost.
-        choice_of_law: Dinas, rhanbarth, tiriogaeth neu wladwriaeth y bydd ei deddfau mewnol sylweddol yn llywodraethu unrhyw hawliadau a phob hawliad.
-        dmca_address: Ar gyfer gweithredwyr yr Unol Daleithiau, defnyddiwch y cyfeiriad sydd wedi'i gofrestru yn Designated Agent Directory y DMCA. Mae rhestriad blychau post ar gael ar gais uniongyrchol, defnyddiwch gais Designated Agent Post Office Box Waiver Request y DMCA i anfon e-bost at y Swyddfa Hawlfraint a disgrifiwch eich bod yn gymedrolwr cynnwys yn y cartref sy'n ofni dial neu ddialedd am eich gweithredoedd ac sydd angen defnyddio Blwch P.O. i dynnu eich cyfeiriad cartref o olwg y cyhoedd.
-        dmca_email: Gall fod yr un e-bost ag sy'n cael ei ddefnyddir ar gyfer “Cyfeiriad e-bost ar gyfer hysbysiadau cyfreithiol” uchod.
-        domain: Dynodiad unigryw o'r gwasanaeth ar-lein rydych chi'n ei ddarparu.
-        jurisdiction: Rhestrwch y wlad lle mae pwy bynnag sy'n talu'r biliau yn byw. Os yw'n gwmni neu'n endid arall, rhestrwch y wlad lle mae wedi'i ymgorffori, a'r ddinas, rhanbarth, tiriogaeth neu wladwriaeth fel y bo'n briodol.
-        min_age: Ni ddylai fod yn is na'r isafswm oedran sy'n ofynnol gan gyfreithiau eich awdurdodaeth.
       user:
         chosen_languages: Wedi eu dewis, dim ond tŵtiau yn yr ieithoedd hyn bydd yn cael eu harddangos mewn ffrydiau cyhoeddus
-        date_of_birth: Mae'n rhaid i ni sicrhau eich bod o leiaf %{age} i ddefnyddio Mastodon. Fyddwn ni ddim yn storio hwn.
         role: Mae'r rôl yn rheoli pa ganiatâd sydd gan y defnyddiwr.
       user_role:
         color: Lliw i'w ddefnyddio ar gyfer y rôl drwy'r UI, fel RGB mewn fformat hecs
@@ -162,7 +144,7 @@ cy:
         url: I ble bydd digwyddiadau'n cael eu hanfon
     labels:
       account:
-        attribution_domains: Gwefannau sy'n cael caniatâd i'ch cydnabod chi
+        attribution_domains_as_text: Gwefannau sy'n cael caniatâd i'ch cydnabod chi
         discoverable: Proffil nodwedd a phostiadau mewn algorithmau darganfod
         fields:
           name: Label
@@ -239,10 +221,8 @@ cy:
         setting_display_media_show_all: Dangos popeth
         setting_expand_spoilers: Dangos postiadau wedi'u marcio â rhybudd cynnwys bob tro
         setting_hide_network: Cuddio eich graff cymdeithasol
-        setting_missing_alt_text_modal: Dangos deialog cadarnhau cyn postio cyfrwng heb destun amgen
         setting_reduce_motion: Lleihau mudiant mewn animeiddiadau
         setting_system_font_ui: Defnyddio ffont rhagosodedig y system
-        setting_system_scrollbars_ui: Defnyddiwch far sgrolio rhagosodedig y system
         setting_theme: Thema'r wefan
         setting_trends: Dangos pynciau llosg heddiw
         setting_unfollow_modal: Dangos deialog cadarnhau cyn dad-ddilyn rhywun
@@ -261,7 +241,6 @@ cy:
         name: Hashnod
       filters:
         actions:
-          blur: Cuddio cyfryngau gyda rhybudd
           hide: Cuddio'n llwyr
           warn: Cuddio â rhybudd
       form_admin_settings:
@@ -275,7 +254,6 @@ cy:
         favicon: Favicon
         mascot: Mascot cyfaddas (hen)
         media_cache_retention_period: Cyfnod cadw storfa cyfryngau
-        min_age: Gofyniad oed ieuengaf
         peers_api_enabled: Cyhoeddi rhestr o weinyddion a ddarganfuwyd yn yr API
         profile_directory: Galluogi cyfeiriadur proffil
         registrations_mode: Pwy all gofrestru
@@ -339,24 +317,7 @@ cy:
         name: Hashnod
         trendable: Caniatáu i'r hashnod hwn ymddangos o dan bynciau llosg
         usable: Caniatáu i bostiadau ddefnyddio'r hashnod hwn yn lleol
-      terms_of_service:
-        changelog: Beth sydd wedi newid?
-        effective_date: Dyddiad dod i rym
-        text: Telerau Gwasanaeth
-      terms_of_service_generator:
-        admin_email: Cyfeiriad e-bost ar gyfer hysbysiadau cyfreithiol
-        arbitration_address: Cyfeiriad ffisegol ar gyfer hysbysiadau cyflafareddu
-        arbitration_website: Gwefan ar gyfer cyflwyno hysbysiadau cyflafareddu
-        choice_of_law: Dewis Cyfraith
-        dmca_address: Cyfeiriad ffisegol ar gyfer DMCA/hysbysiadau hawlfraint
-        dmca_email: Cyfeiriad e-bost ar gyfer DMCA/hysbysiadau hawlfraint
-        domain: Parth
-        jurisdiction: Awdurdodaeth gyfreithiol
-        min_age: Isafswm oedran
       user:
-        date_of_birth_1i: Dydd
-        date_of_birth_2i: Mis
-        date_of_birth_3i: Blwyddyn
         role: Rôl
         time_zone: Cylchfa amser
       user_role:
diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml
index 91582e60c5..24cb95813f 100644
--- a/config/locales/simple_form.da.yml
+++ b/config/locales/simple_form.da.yml
@@ -3,14 +3,14 @@ da:
   simple_form:
     hints:
       account:
-        attribution_domains: Ét pr. linje. Beskytter mod falske tilskrivninger.
+        attribution_domains_as_text: Ét pr. linje. Beskytter mod falske tilskrivninger.
         discoverable: Dine offentlige indlæg og profil kan blive fremhævet eller anbefalet i forskellige områder af Mastodon, og profilen kan blive foreslået til andre brugere.
         display_name: Dit fulde navn eller dit sjove navn.
         fields: Din hjemmeside, dine pronominer, din alder, eller hvad du har lyst til.
         indexable: Dine offentlige indlæg vil kunne vises i Mastodon-søgeresultater. Folk, som har interageret med dem, vil kunne finde dem uanset.
         note: 'Du kan @omtale andre personer eller #etiketter.'
         show_collections: Folk vil ikke kunne tjekke dine Følger og Følgere. Folk, du selv følger, vil stadig kunne se dette.
-        unlocked: Man vil kunne følges af folk uden først at godkende dem. Ønsker man at gennemgå Følg-anmodninger og individuelt acceptere/afvise nye følgere, så fjern markeringen.
+        unlocked: Folk vil kunne følge dig uden krav om godkendelse. Fjern markeringen, såfremt du ønsker at tjekke Følg-anmodninger og individuelt acceptere eller afvise nye Følgere.
       account_alias:
         acct: Angiv brugernavn@domain for den konto, hvorfra du vil flytte
       account_migration:
@@ -60,7 +60,6 @@ da:
         setting_display_media_default: Skjul medier med sensitiv-markering
         setting_display_media_hide_all: Skjul altid medier
         setting_display_media_show_all: Vis altid medier
-        setting_system_scrollbars_ui: Gælder kun for computerwebbrowsere baseret på Safari og Chrome
         setting_use_blurhash: Gradienter er baseret på de skjulte grafikelementers farver, men slører alle detaljer
         setting_use_pending_items: Skjul tidslinjeopdateringer bag et klik i stedet for brug af auto-feedrulning
         username: Bogstaver, cifre og understregningstegn kan benyttes
@@ -75,7 +74,6 @@ da:
       filters:
         action: Vælg handlingen til eksekvering, når et indlæg matcher filteret
         actions:
-          blur: Skjul medier bag en advarsel, uden at skjule selve teksten
           hide: Skjul filtreret indhold helt (adfærd som om, det ikke fandtes)
           warn: Skjul filtreret indhold bag en advarsel, der nævner filterets titel
       form_admin_settings:
@@ -89,7 +87,6 @@ da:
         favicon: WEBP, PNG, GIF eller JPG. Tilsidesætter standard Mastodon favikonet på mobilenheder med et tilpasset ikon.
         mascot: Tilsidesætter illustrationen i den avancerede webgrænseflade.
         media_cache_retention_period: Mediefiler fra indlæg oprettet af eksterne brugere er cachet på din server. Når sat til positiv værdi, slettes medier efter det angivne antal dage. Anmodes om mediedata efter de er slettet, gendownloades de, hvis kildeindholdet stadig er tilgængeligt. Grundet begrænsninger på, hvor ofte linkforhåndsvisningskort forespørger tredjeparts websteder, anbefales det at sætte denne værdi til mindst 14 dage, ellers opdateres linkforhåndsvisningskort ikke efter behov før det tidspunkt.
-        min_age: Brugere anmodes om at bekræfte deres fødselsdato under tilmelding
         peers_api_enabled: En liste med domænenavne, som denne server har stødt på i fediverset. Ingen data inkluderes her om, hvorvidt der fødereres med en given server, blot at din server kender til det. Dette bruges af tjenester, som indsamler generelle føderationsstatistikker.
         profile_directory: Profilmappen oplister alle brugere, som har valgt at kunne opdages.
         require_invite_text: Når tilmelding kræver manuel godkendelse, så gør “Hvorfor ønsker du at deltage?” tekstinput obligatorisk i stedet for valgfrit
@@ -132,23 +129,8 @@ da:
         show_application: Du vil dog altid kunne se, hvilken app, der offentliggjorde dit indlæg.
       tag:
         name: Kun bogstavtyper (store/små) kan ændres, eksempelvis for at gøre det mere læsbart
-      terms_of_service:
-        changelog: Kan struktureres med Markdown-syntaks.
-        effective_date: En rimelig tidsramme kan variere fra 10 til 30 dage fra den dato, hvor man underretter sine brugere.
-        text: Kan struktureres med Markdown-syntaks.
-      terms_of_service_generator:
-        admin_email: Juridiske bekendtgørelser omfatter imødegåelsesbekendtgørelser, retskendelser, nedtagelses- og retshåndhævelsesanmodninger.
-        arbitration_address: Kan være den samme som Fysisk adresse ovenfor, eller “Ingen” ved brug af e-mail.
-        arbitration_website: Kan være en webformular, eller “Intet” ved brug af e-mail.
-        choice_of_law: By, region, område eller angiv de interne materielle love, som vil være gældende for samtlige krav.
-        dmca_address: For amerikanske operatører, brug den adresse, der er registreret i DMCA Designated Agent Directory. En Postboksliste er tilgængelig på direkte anmodning, brug den DMCA Designated Agent Post Office Box Waiver Request til at e-maile Ophavsretskontoret og beskrive, at man er en hjemmebaseret indholdsmoderator, der frygter hævn eller gengældelse for sine handlinger og behøver en Postboks for at fjerne hjemmeadressen fra den offentlige visning.
-        dmca_email: Kan være samme e-mail, som brugt til “E-mailadresse til juridiske meddelelser” ovenfor.
-        domain: Unik identifikation af den onlinetjeneste, man leverer.
-        jurisdiction: Angiv landet, hvor betaleren af regningerne er bosiddende. Er det en virksomhed eller en anden entitet, angiv det land, hvor det er stiftet, og byen, regionen, området eller staten efter behov.
-        min_age: Bør ikke være under den iht. lovgivningen i det aktuelle retsområde krævede minimumsalder.
       user:
         chosen_languages: Når markeret, vil kun indlæg på de valgte sprog fremgå på offentlige tidslinjer
-        date_of_birth: Vi er nødt til at sikre, at man er fyldt %{age} for at bruge Mastodon. Vi gemmer ikke denne information.
         role: Rollen styrer, hvilke tilladelser brugeren er tildelt.
       user_role:
         color: Farven, i RGB hex-format, der skal bruges til rollen i hele UI'en
@@ -162,7 +144,7 @@ da:
         url: Hvor begivenheder sendes til
     labels:
       account:
-        attribution_domains: Websteder, man må krediteres af
+        attribution_domains_as_text: Hjemmesider, der må kreditere dig
         discoverable: Fremhæv profil og indlæg i opdagelsesalgoritmer
         fields:
           name: Etiket
@@ -239,10 +221,8 @@ da:
         setting_display_media_show_all: Vis alle
         setting_expand_spoilers: Ekspandér altid indlæg markeret med indholdsadvarsler
         setting_hide_network: Skjul din sociale graf
-        setting_missing_alt_text_modal: Vis bekræftelsesdialog inden medier uden alt-tekst lægges op
         setting_reduce_motion: Reducér animationsbevægelse
         setting_system_font_ui: Brug systemets standardskrifttype
-        setting_system_scrollbars_ui: Brug standard systemrullebjælke
         setting_theme: Webstedstema
         setting_trends: Vis dagens tendenser
         setting_unfollow_modal: Vis bekræftelsesdialog før ophør med at følge nogen
@@ -261,7 +241,6 @@ da:
         name: Hashtag
       filters:
         actions:
-          blur: Skjul medier med en advarsel
           hide: Skjul helt
           warn: Skjul bag en advarsel
       form_admin_settings:
@@ -275,7 +254,6 @@ da:
         favicon: Favikon
         mascot: Tilpasset maskot (ældre funktion)
         media_cache_retention_period: Media-cache opbevaringsperiode
-        min_age: Minimums alderskrav
         peers_api_enabled: Udgiv liste over fundne server i API'en
         profile_directory: Aktivér profiloversigt
         registrations_mode: Hvem, der kan tilmelde sig
@@ -339,24 +317,7 @@ da:
         name: Hashtag
         trendable: Tillad visning af denne etiket under tendenser
         usable: Tillad indlæg at benytte denne etiket lokalt
-      terms_of_service:
-        changelog: Hvad der er ændret?
-        effective_date: Ikrafttrædelsesdato
-        text: Tjenestevilkår
-      terms_of_service_generator:
-        admin_email: E-mailadresse til juridiske meddelelser
-        arbitration_address: Fysisk adresse til voldgiftsmeddelelser
-        arbitration_website: Websted til indsendelse af voldgiftsmeddelelser
-        choice_of_law: Lovvalg
-        dmca_address: Fysisk adresse til DMCA-/ophavsretsmeddelelser
-        dmca_email: E-mailadresse til DMCA-/ophavsretsmeddelelser
-        domain: Domæne
-        jurisdiction: Juridisk jurisdiktion
-        min_age: Minimumsalder
       user:
-        date_of_birth_1i: Dag
-        date_of_birth_2i: Måned
-        date_of_birth_3i: År
         role: Rolle
         time_zone: Tidszone
       user_role:
diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml
index 342a1dbe1c..d6d6536737 100644
--- a/config/locales/simple_form.de.yml
+++ b/config/locales/simple_form.de.yml
@@ -3,7 +3,7 @@ de:
   simple_form:
     hints:
       account:
-        attribution_domains: Eine Domain pro Zeile. Dadurch können falsche Zuschreibungen unterbunden werden.
+        attribution_domains_as_text: Eine Domain pro Zeile. Dadurch können falsche Zuschreibungen unterbunden werden.
         discoverable: Deine öffentlichen Beiträge und dein Profil können in verschiedenen Bereichen auf Mastodon angezeigt oder empfohlen werden und dein Profil kann anderen vorgeschlagen werden.
         display_name: Dein richtiger Name oder dein Fantasiename.
         fields: Deine Website, Pronomen, dein Alter – alles, was du möchtest.
@@ -60,7 +60,6 @@ de:
         setting_display_media_default: Medien mit Inhaltswarnung ausblenden
         setting_display_media_hide_all: Medien immer ausblenden
         setting_display_media_show_all: Medien mit Inhaltswarnung immer anzeigen
-        setting_system_scrollbars_ui: Betrifft nur Desktop-Browser, die auf Chrome oder Safari basieren
         setting_use_blurhash: Der Farbverlauf basiert auf den Farben der ausgeblendeten Medien, verschleiert aber jegliche Details
         setting_use_pending_items: Neue Beiträge hinter einem Klick verstecken, anstatt automatisch zu scrollen
         username: Du kannst Buchstaben, Zahlen und Unterstriche verwenden
@@ -75,7 +74,6 @@ de:
       filters:
         action: Gib an, welche Aktion ausgeführt werden soll, wenn ein Beitrag dem Filter entspricht
         actions:
-          blur: Medien mit einer Warnung ausblenden, ohne den Text selbst auszublenden
           hide: Den gefilterten Beitrag vollständig ausblenden, als hätte er nie existiert
           warn: Den gefilterten Beitrag hinter einer Warnung, die den Filtertitel beinhaltet, ausblenden
       form_admin_settings:
@@ -84,12 +82,11 @@ de:
         backups_retention_period: Nutzer*innen haben die Möglichkeit, Archive ihrer Beiträge zu erstellen, die sie später herunterladen können. Wenn ein positiver Wert gesetzt ist, werden diese Archive nach der festgelegten Anzahl von Tagen automatisch aus deinem Speicher gelöscht.
         bootstrap_timeline_accounts: Diese Konten werden bei den Follower-Empfehlungen für neu registrierte Nutzer*innen oben angeheftet.
         closed_registrations_message: Wird angezeigt, wenn Registrierungen deaktiviert sind
-        content_cache_retention_period: Sämtliche Beiträge von anderen Servern (einschließlich geteilte Beiträge und Antworten) werden, unabhängig von der Interaktion der lokalen Nutzer*innen mit diesen Beiträgen, nach der festgelegten Anzahl von Tagen gelöscht. Das betrifft auch Beiträge, die von lokalen Nutzer*innen favorisiert oder als Lesezeichen gespeichert wurden. Private Erwähnungen zwischen Nutzer*innen von verschiedenen Servern werden ebenfalls verloren gehen und können nicht wiederhergestellt werden. Diese Option richtet sich ausschließlich an Server mit speziellen Zwecken und wird die allgemeine Nutzungserfahrung beeinträchtigen, wenn sie für den allgemeinen Gebrauch aktiviert ist.
+        content_cache_retention_period: Sämtliche Beiträge von anderen Servern (einschließlich geteilte Beiträge und Antworten) werden, unabhängig von der Interaktion der lokalen Nutzer*innen mit diesen Beiträgen, nach der festgelegten Anzahl von Tagen gelöscht. Das betrifft auch Beiträge, die von lokalen Nutzer*innen favorisiert oder als Lesezeichen gespeichert wurden. Private Erwähnungen zwischen Nutzer*innen von verschiedenen Servern werden ebenfalls verloren gehen und können nicht wiederhergestellt werden. Das Verwenden dieser Option richtet sich ausschließlich an Server für spezielle Zwecke und wird die allgemeine Nutzungserfahrung beeinträchtigen, wenn sie für den allgemeinen Gebrauch aktiviert ist.
         custom_css: Du kannst benutzerdefinierte Stile auf die Web-Version von Mastodon anwenden.
         favicon: WEBP, PNG, GIF oder JPG. Überschreibt das Standard-Mastodon-Favicon mit einem eigenen Symbol.
         mascot: Überschreibt die Abbildung in der erweiterten Weboberfläche.
         media_cache_retention_period: Mediendateien aus Beiträgen von externen Nutzer*innen werden auf deinem Server zwischengespeichert. Wenn ein positiver Wert gesetzt ist, werden die Medien nach der festgelegten Anzahl von Tagen gelöscht. Sollten die Medien nach dem Löschvorgang wieder angefragt werden, werden sie erneut heruntergeladen, sofern der ursprüngliche Inhalt noch vorhanden ist. Es wird empfohlen, diesen Wert auf mindestens 14 Tage festzulegen, da die Häufigkeit der Abfrage von Linkvorschaukarten für Websites von Dritten begrenzt ist und die Linkvorschaukarten sonst nicht vor Ablauf dieser Zeit aktualisiert werden.
-        min_age: Nutzer*innen werden bei der Registrierung aufgefordert, ihr Geburtsdatum zu bestätigen
         peers_api_enabled: Eine Liste von Domains, die diesem Server im Fediverse begegnet sind. Hierbei werden keine Angaben darüber gemacht, ob du mit einem bestimmten Server föderierst, sondern nur, dass dein Server davon weiß. Dies wird von Diensten verwendet, die allgemein Statistiken übers Ferdiverse sammeln.
         profile_directory: Dieses Verzeichnis zeigt alle Profile an, die sich dafür entschieden haben, entdeckt zu werden.
         require_invite_text: Wenn Registrierungen eine manuelle Genehmigung erfordern, dann werden Nutzer einen Grund für ihre Registrierung angeben müssen
@@ -132,23 +129,8 @@ de:
         show_application: Du wirst immer sehen können, über welche App dein Beitrag veröffentlicht wurde.
       tag:
         name: Du kannst nur die Groß- und Kleinschreibung der Buchstaben ändern, um es z. B. lesbarer zu machen
-      terms_of_service:
-        changelog: Kann mit der Markdown-Syntax formatiert werden.
-        effective_date: Ein angemessener Zeitraum liegt zwischen 10 und 30 Tagen, nachdem deine Nutzer*innen benachrichtigt wurden.
-        text: Kann mit der Markdown-Syntax formatiert werden.
-      terms_of_service_generator:
-        admin_email: Rechtliche Hinweise umfassen Gegendarstellungen, Gerichtsbeschlüsse, Anfragen zum Herunternehmen von Inhalten und Anfragen von Strafverfolgungsbehörden.
-        arbitration_address: Kann wie die Anschrift hierüber oder „nicht verfügbar“ sein, sollte eine E-Mail-Adresse verwendet werden.
-        arbitration_website: Kann ein Web-Formular oder „nicht verfügbar“ sein, sollte eine E-Mail-Adresse verwendet werden.
-        choice_of_law: Stadt, Region, Gebiet oder Staat, dessen innerstaatliches materielles Recht für alle Ansprüche maßgebend ist.
-        dmca_address: US-Betreiber sollten die im „DMCA Designated Agent Directory“ eingetragene Adresse verwenden. Eine Postfachadresse ist auf direkte Anfrage verfügbar. Verwenden Sie die „DMCA Designated Agent Post Box Waiver“-Anfrage, um per E-Mail die Urheberrechtsbehörde darüber zu unterrichten, dass Sie Inhalte per Heimarbeit moderieren, eventuelle Rache oder Vergeltung für Ihre Handlungen befürchten und deshalb eine Postfachadresse benötigen, um Ihre Privatadresse nicht preiszugeben.
-        dmca_email: Kann dieselbe E-Mail wie bei „E-Mail-Adresse für rechtliche Hinweise“ sein.
-        domain: Einzigartige Identifizierung des angebotenen Online-Services.
-        jurisdiction: Gib das Land an, in dem die Person lebt, die alle Rechnungen bezahlt. Falls es sich dabei um ein Unternehmen oder eine andere Einrichtung handelt, gib das Land mit dem Sitz an, sowie die Stadt oder Region.
-        min_age: Sollte nicht unter dem gesetzlich vorgeschriebenen Mindestalter liegen.
       user:
         chosen_languages: Wenn du hier eine oder mehrere Sprachen auswählst, werden ausschließlich Beiträge in diesen Sprachen in deinen öffentlichen Timelines angezeigt
-        date_of_birth: Wir müssen sicherstellen, dass du mindestens %{age} Jahre alt bist, um Mastodon verwenden zu können. Das Alter wird nicht gespeichert.
         role: Die Rolle bestimmt, welche Berechtigungen das Konto hat.
       user_role:
         color: Farbe, die für diese Rolle in der gesamten Benutzerschnittstelle verwendet wird, als RGB im Hexadezimalsystem
@@ -162,7 +144,7 @@ de:
         url: Wohin Ereignisse gesendet werden
     labels:
       account:
-        attribution_domains: Websites, die auf dich verweisen dürfen
+        attribution_domains_as_text: Websites, die auf dich verweisen dürfen
         discoverable: Profil und Beiträge in Suchalgorithmen berücksichtigen
         fields:
           name: Beschriftung
@@ -239,10 +221,8 @@ de:
         setting_display_media_show_all: Alle Medien anzeigen
         setting_expand_spoilers: Beiträge mit Inhaltswarnung immer ausklappen
         setting_hide_network: Follower und „Folge ich“ nicht anzeigen
-        setting_missing_alt_text_modal: Bestätigungsdialog anzeigen, bevor Medien ohne Bildbeschreibung veröffentlicht werden
         setting_reduce_motion: Bewegung in Animationen verringern
         setting_system_font_ui: Standardschriftart des Browsers verwenden
-        setting_system_scrollbars_ui: Bildlaufleiste des Systems verwenden
         setting_theme: Design
         setting_trends: Heutige Trends anzeigen
         setting_unfollow_modal: Bestätigungsdialog beim Entfolgen eines Profils anzeigen
@@ -261,9 +241,8 @@ de:
         name: Hashtag
       filters:
         actions:
-          blur: Medien mit einer Warnung ausblenden
           hide: Vollständig ausblenden
-          warn: Mit einer Warnung ausblenden
+          warn: Mit einer Inhaltswarnung ausblenden
       form_admin_settings:
         activity_api_enabled: Aggregierte Nutzungsdaten über die API veröffentlichen
         app_icon: App-Symbol
@@ -275,7 +254,6 @@ de:
         favicon: Favicon
         mascot: Benutzerdefiniertes Maskottchen (Legacy)
         media_cache_retention_period: Aufbewahrungsfrist für Medien im Cache
-        min_age: Erforderliches Mindestalter
         peers_api_enabled: Die entdeckten Server im Fediverse über die API veröffentlichen
         profile_directory: Profilverzeichnis aktivieren
         registrations_mode: Wer darf ein neues Konto registrieren?
@@ -339,24 +317,7 @@ de:
         name: Hashtag
         trendable: Erlaube, dass dieser Hashtag in den Trends erscheint
         usable: Beiträge dürfen diesen Hashtag lokal verwenden
-      terms_of_service:
-        changelog: Was hat sich geändert?
-        effective_date: Datum des Inkrafttretens
-        text: Nutzungsbedingungen
-      terms_of_service_generator:
-        admin_email: E-Mail-Adresse für rechtliche Hinweise
-        arbitration_address: Anschrift für Schiedsverfahren
-        arbitration_website: Website zum Einreichen von Schiedsverfahren
-        choice_of_law: Wahl der Rechtsordnung
-        dmca_address: Anschrift für Urheberrechtsverletzungen
-        dmca_email: E-Mail-Adresse für Urheberrechtsverletzungen
-        domain: Domain
-        jurisdiction: Gerichtsstand
-        min_age: Mindestalter
       user:
-        date_of_birth_1i: Tag
-        date_of_birth_2i: Monat
-        date_of_birth_3i: Jahr
         role: Rolle
         time_zone: Zeitzone
       user_role:
diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml
index 62de683213..7c1d231665 100644
--- a/config/locales/simple_form.el.yml
+++ b/config/locales/simple_form.el.yml
@@ -3,7 +3,7 @@ el:
   simple_form:
     hints:
       account:
-        attribution_domains: Μία ανά γραμμή. Προστατεύει από ψευδείς ιδιότητες.
+        attribution_domains_as_text: Μία ανά γραμμή. Προστατεύει από ψευδείς ιδιότητες.
         discoverable: Οι δημόσιες δημοσιεύσεις και το προφίλ σου μπορεί να εμφανίζονται ή να συνιστώνται σε διάφορους τομείς του Mastodon και το προφίλ σου μπορεί να προτείνεται σε άλλους χρήστες.
         display_name: Το πλήρες ή το αστείο σου όνομα.
         fields: Η αρχική σου σελίδα, αντωνυμίες, ηλικία, ό,τι θες.
@@ -60,7 +60,6 @@ el:
         setting_display_media_default: Απόκρυψη ευαίσθητων πολυμέσων
         setting_display_media_hide_all: Μόνιμη απόκρυψη όλων των πολυμέσων
         setting_display_media_show_all: Πάντα εμφάνιση πολυμέσων
-        setting_system_scrollbars_ui: Ισχύει μόνο για προγράμματα περιήγησης υπολογιστή με βάση το Safari και το Chrome
         setting_use_blurhash: Οι χρωματισμοί βασίζονται στα χρώματα του κρυμμένου πολυμέσου αλλά θολώνουν τις λεπτομέρειες
         setting_use_pending_items: Εμφάνιση ενημερώσεων ροής μετά από κλικ αντί για αυτόματη κύλισή τους
         username: Μπορείς να χρησιμοποιήσεις γράμματα, αριθμούς και κάτω παύλες
@@ -130,14 +129,6 @@ el:
         show_application: Θα είσαι πάντα σε θέση να δεις ποια εφαρμογή δημοσίευσε την ανάρτησή σου όπως και να 'χει.
       tag:
         name: Μπορείς να αλλάξεις μόνο το πλαίσιο των χαρακτήρων, για παράδειγμα για να γίνει περισσότερο ευανάγνωστο
-      terms_of_service:
-        changelog: Μπορεί να δομηθεί με σύνταξη Markdown.
-        text: Μπορεί να δομηθεί με σύνταξη Markdown.
-      terms_of_service_generator:
-        admin_email: Οι νομικές ανακοινώσεις περιλαμβάνουν αντικρούσεις, δικαστικές αποφάσεις, αιτήματα που έχουν ληφθεί και αιτήματα επιβολής του νόμου.
-        dmca_address: Για τους φορείς των ΗΠΑ, χρησιμοποιήστε τη διεύθυνση που έχει καταχωρηθεί στο DMCA Designated Agent Directory. A P.O. Η λίστα είναι διαθέσιμη κατόπιν απευθείας αιτήματος, Χρησιμοποιήστε το αίτημα απαλλαγής από την άδεια χρήσης του καθορισμένου από το DMCA Agent Post Office Box για να στείλετε email στο Γραφείο Πνευματικών Δικαιωμάτων και περιγράψτε ότι είστε συντονιστής περιεχομένου με βάση το σπίτι, ο οποίος φοβάται την εκδίκηση ή την απόδοση για τις ενέργειές σας και πρέπει να χρησιμοποιήσετε ένα P.. Box για να αφαιρέσετε τη διεύθυνση οικίας σας από τη δημόσια προβολή.
-        domain: Μοναδικό αναγνωριστικό της διαδικτυακής υπηρεσίας που παρέχεις.
-        jurisdiction: Ανέφερε τη χώρα όπου ζει αυτός που πληρώνει τους λογαριασμούς. Εάν πρόκειται για εταιρεία ή άλλη οντότητα, ανέφερε τη χώρα όπου υφίσταται, και την πόλη, περιοχή, έδαφος ή πολιτεία ανάλογα με την περίπτωση.
       user:
         chosen_languages: Όταν ενεργοποιηθεί, στη δημόσια ροή θα εμφανίζονται τουτ μόνο από τις επιλεγμένες γλώσσες
         role: Ο ρόλος ελέγχει ποια δικαιώματα έχει ο χρήστης.
@@ -153,7 +144,7 @@ el:
         url: Πού θα σταλούν τα γεγονότα
     labels:
       account:
-        attribution_domains: Ιστοσελίδες επιτρέπεται να σας αναφέρουν
+        attribution_domains_as_text: Ιστοσελίδες που επιτρέπεται να σου δώσουν εύσημα
         discoverable: Παροχή προφίλ και αναρτήσεων σε αλγορίθμους ανακάλυψης
         fields:
           name: Περιγραφή
@@ -230,10 +221,8 @@ el:
         setting_display_media_show_all: Εμφάνιση όλων
         setting_expand_spoilers: Μόνιμη ανάπτυξη των τουτ με προειδοποίηση περιεχομένου
         setting_hide_network: Κρύψε τις διασυνδέσεις σου
-        setting_missing_alt_text_modal: Εμφάνιση διαλόγου επιβεβαίωσης πριν από την δημοσίευση πολυμέσων χωρίς alt κείμενο
         setting_reduce_motion: Μείωση κίνησης κινουμένων στοιχείων
         setting_system_font_ui: Χρήση της προεπιλεγμένης γραμματοσειράς του συστήματος
-        setting_system_scrollbars_ui: Χρήση προκαθορισμένης γραμμής κύλισης του συστήματος
         setting_theme: Θέμα ιστότοπου
         setting_trends: Εμφάνιση σημερινών τάσεων
         setting_unfollow_modal: Επιβεβαίωση πριν τη διακοπή παρακολούθησης κάποιου
@@ -328,17 +317,6 @@ el:
         name: Ετικέτα
         trendable: Εμφάνιση της ετικέτας στις τάσεις
         usable: Να επιτρέπεται η τοπική χρήση αυτής της ετικέτας από αναρτήσεις
-      terms_of_service:
-        changelog: Τι άλλαξε;
-        text: Όροι Παροχής Υπηρεσιών
-      terms_of_service_generator:
-        admin_email: Διεύθυνση email για τις νομικές ανακοινώσεις
-        arbitration_address: Φυσική διεύθυνση για τις ανακοινώσεις διαιτησίας
-        arbitration_website: Ιστοσελίδα για την υποβολή ειδοποιήσεων διαιτησίας
-        dmca_address: Φυσική διεύθυνση για ειδοποιήσεις DMCA/πνευματικών δικαιωμάτων
-        dmca_email: Διεύθυνση email για ειδοποιήσεις DMCA/πνευματικών δικαιωμάτων
-        domain: Τομέας
-        jurisdiction: Νομική δικαιοδοσία
       user:
         role: Ρόλος
         time_zone: Ζώνη ώρας
diff --git a/config/locales/simple_form.en-GB.yml b/config/locales/simple_form.en-GB.yml
index 5f5453edc3..039a69dba2 100644
--- a/config/locales/simple_form.en-GB.yml
+++ b/config/locales/simple_form.en-GB.yml
@@ -3,14 +3,14 @@ en-GB:
   simple_form:
     hints:
       account:
-        attribution_domains: One per line. Protects from false attributions.
+        attribution_domains_as_text: One per line. Protects from false attributions.
         discoverable: Your public posts and profile may be featured or recommended in various areas of Mastodon and your profile may be suggested to other users.
         display_name: Your full name or your fun name.
         fields: Your homepage, pronouns, age, anything you want.
         indexable: Your public posts may appear in search results on Mastodon. People who have interacted with your posts may be able to search them regardless.
         note: 'You can @mention other people or #hashtags.'
         show_collections: People will be able to browse through your follows and followers. People that you follow will see that you follow them regardless.
-        unlocked: People will be able to follow you without requesting approval. Uncheck if you want to review follow requests and choose whether to accept or reject new followers.
+        unlocked: People will be able to follow you without requesting approval. Uncheck if you want to review follow requests and chose whether to accept or reject new followers.
       account_alias:
         acct: Specify the username@domain of the account you want to move from
       account_migration:
@@ -60,7 +60,6 @@ en-GB:
         setting_display_media_default: Hide media marked as sensitive
         setting_display_media_hide_all: Always hide media
         setting_display_media_show_all: Always show media
-        setting_system_scrollbars_ui: Applies only to desktop browsers based on Safari and Chrome
         setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details
         setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed
         username: You can use letters, numbers, and underscores
@@ -130,14 +129,6 @@ en-GB:
         show_application: You will always be able to see which app published your post regardless.
       tag:
         name: You can only change the casing of the letters, for example, to make it more readable
-      terms_of_service:
-        changelog: Can be structured with Markdown syntax.
-        text: Can be structured with Markdown syntax.
-      terms_of_service_generator:
-        admin_email: Legal notices include counternotices, court orders, takedown requests, and law enforcement requests.
-        dmca_address: For US operators, use the address registered in the DMCA Designated Agent Directory. A P.O. Box listing is available upon direct request, use the DMCA Designated Agent Post Office Box Waiver Request to email the Copyright Office and describe that you are a home-based content moderator who fears revenge or retribution for your actions and need to use a P.O. Box to remove your home address from public view.
-        domain: Unique identification of the online service you are providing.
-        jurisdiction: List the country where whoever pays the bills lives. If it’s a company or other entity, list the country where it’s incorporated, and the city, region, territory or state as appropriate.
       user:
         chosen_languages: When checked, only posts in selected languages will be displayed in public timelines
         role: The role controls which permissions the user has.
@@ -153,7 +144,7 @@ en-GB:
         url: Where events will be sent to
     labels:
       account:
-        attribution_domains: Websites allowed to credit you
+        attribution_domains_as_text: Websites allowed to credit you
         discoverable: Feature profile and posts in discovery algorithms
         fields:
           name: Label
@@ -230,10 +221,8 @@ en-GB:
         setting_display_media_show_all: Show all
         setting_expand_spoilers: Always expand posts marked with content warnings
         setting_hide_network: Hide your social graph
-        setting_missing_alt_text_modal: Show confirmation dialogue before posting media without alt text
         setting_reduce_motion: Reduce motion in animations
         setting_system_font_ui: Use system's default font
-        setting_system_scrollbars_ui: Use system's default scrollbar
         setting_theme: Site theme
         setting_trends: Show today's trends
         setting_unfollow_modal: Show confirmation dialog before unfollowing someone
@@ -328,17 +317,6 @@ en-GB:
         name: Hashtag
         trendable: Allow this hashtag to appear under trends
         usable: Allow posts to use this hashtag locally
-      terms_of_service:
-        changelog: What's changed?
-        text: Terms of Service
-      terms_of_service_generator:
-        admin_email: Email address for legal notices
-        arbitration_address: Physical address for arbitration notices
-        arbitration_website: Website for submitting arbitration notices
-        dmca_address: Physical address for DMCA/copyright notices
-        dmca_email: Email address for DMCA/copyright notices
-        domain: Domain
-        jurisdiction: Legal jurisdiction
       user:
         role: Role
         time_zone: Time Zone
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index ff17536ee1..1a60ca8206 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -3,7 +3,7 @@ en:
   simple_form:
     hints:
       account:
-        attribution_domains: One per line. Protects from false attributions.
+        attribution_domains_as_text: One per line. Protects from false attributions.
         discoverable: Your public posts and profile may be featured or recommended in various areas of Mastodon and your profile may be suggested to other users.
         display_name: Your full name or your fun name.
         fields: Your homepage, pronouns, age, anything you want.
@@ -12,7 +12,7 @@ en:
         locked: People will request to follow you and you will be able to either accept or reject new followers.
         note: 'You can @mention other people or #hashtags.'
         show_collections: People will be able to browse through your follows and followers. People that you follow will see that you follow them regardless.
-        unlocked: People will be able to follow you without requesting approval. Uncheck if you want to review follow requests and choose whether to accept or reject new followers.
+        unlocked: People will be able to follow you without requesting approval. Uncheck if you want to review follow requests and chose whether to accept or reject new followers.
       account_alias:
         acct: Specify the username@domain of the account you want to move from
       account_migration:
@@ -66,25 +66,24 @@ en:
         setting_custom_css_lead: 'Be sure to remember: In the unlikely event that you make a mistake in entering your custom CSS and the screen does not display properly, you can disable your custom CSS from the link at the bottom of the sign-in screen. Open the sign-in screen in private mode of your browser, for example, and disable it.'
         setting_default_searchability: On kmyblue and Fedibird, the search is based on the search permission setting; on Misskey, all public, local public, and non-public posts are searched regardless of this setting; on Mastodon and Firefish, instead of search permission, the "Make public posts freely searchable on other servers" setting in the profile settings is applied. In Mastodon and Firefish, the "Make public posts freely searchable on other servers" setting in the profile settings is applied instead of the search permission.
         setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click
-        setting_disallow_unlisted_public_searchability: If you enable this setting, unlisted posts and the “everyone” search scope cannot coexist, making it impossible for unspecified users to search your posts.
+        setting_disallow_unlisted_public_searchability: この設定を有効にすると、非収載投稿と検索範囲「誰でも」は両立できず不特定多数からの検索が不可になります。Fedibirdと同じ挙動になります
         setting_display_media_default: Hide media marked as sensitive
         setting_display_media_hide_all: Always hide media
         setting_display_media_show_all: Always show media
         setting_dtl_force_searchability: 'With using #%{tag} tag, your post settings will be changed forcibly'
         setting_dtl_force_visibility: 'With using #%{tag} tag, your post settings will be changed forcibly'
         setting_emoji_reaction_policy: Even with this setting, users on non-kmyblue servers are free to put their emoji reaction on the post and share it within the same server. If you simply want to remove the emoji reaction from your own screen, you can disable it from the appearance settings
-        setting_emoji_reaction_streaming_notify_impl2: You can use the emoji reaction feature when using an app that supports this server’s unique functionality. However, since this has not been tested (and such apps have not even been identified), it may not work correctly.
+        setting_emoji_reaction_streaming_notify_impl2: 当該サーバーの独自機能に対応したアプリを利用時に、絵文字リアクション機能を利用できます。動作確認していないため(そもそもそのようなアプリ自体を確認できていないため)正しく動かない場合があります
         setting_enable_emoji_reaction: If turn off, other users still can react your posts
         setting_enabled_visibilities: If turn off, you cannot select and post the privacy.
-        setting_hide_network: It will hide the following and follower information from the profile page.
-        setting_public_post_to_unlisted: You can still post with local visibility using unsupported third-party apps, but public posts will no longer be possible outside of the web interface.
+        setting_hide_network: フォローとフォロワーの情報がプロフィールページで見られないようにします
+        setting_public_post_to_unlisted: 未対応のサードパーティアプリからもローカル公開で投稿できますが、公開投稿はWeb以外できなくなります
         setting_reject_send_limited_to_suspects: This applies to "Mutual Only" posts. Circle posts will be delivered without exception. Some Misskey servers have independently supported limited posting, but this is a setting for those who are concerned about it, as mutual-only posting exposes some of the users you are mutual with to Misskey users!
         setting_reject_unlisted_subscription: Misskey and its forks can **subscribe and search** for "non-following" posts from accounts they do not follow. This differs from kmyblue's behavior. It delivers posts in the specified public range to such servers as "followers only". Please understand, however, that due to its structure, it is difficult to handle perfectly and will occasionally be delivered as non-subscribed.
         setting_reverse_search_quote: Double-quotes will result in a search with a wider range of notation, which is the opposite of Mastodon's default behavior.
         setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります
         setting_stay_privacy: If you choose to enable this setting, please consider manually setting the visibility of the boost
         setting_stop_emoji_reaction_streaming: Helps to save communication capacity.
-        setting_system_scrollbars_ui: Applies only to desktop browsers based on Safari and Chrome
         setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details
         setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed
         username: You can use letters, numbers, and underscores
@@ -99,7 +98,7 @@ en:
       filters:
         action: Chose which action to perform when a post matches the filter
         actions:
-          blur: Hide media behind a warning, without hiding the text itself
+          half_warn: Hide the filtered content (exclude account info) behind a warning mentioning the filter's title
           hide: Completely hide the filtered content, behaving as if it did not exist
           warn: Hide the filtered content behind a warning mentioning the filter's title
       form_admin_settings:
@@ -117,14 +116,13 @@ en:
         favicon: WEBP, PNG, GIF or JPG. Overrides the default Mastodon favicon with a custom icon.
         mascot: Overrides the illustration in the advanced web interface.
         media_cache_retention_period: Media files from posts made by remote users are cached on your server. When set to a positive value, media will be deleted after the specified number of days. If the media data is requested after it is deleted, it will be re-downloaded, if the source content is still available. Due to restrictions on how often link preview cards poll third-party sites, it is recommended to set this value to at least 14 days, or link preview cards will not be updated on demand before that time.
-        min_age: Users will be asked to confirm their date of birth during sign-up
         peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense.
         profile_directory: The profile directory lists all users who have opted-in to be discoverable.
         receive_other_servers_emoji_reaction: It can cause load. It is recommended to enable it only when there are few people.
-        registrations_end_hour: Specifies the end of the time window during which new registrations can be made without approval. Registrations cannot be made before the start time or after this end time. Registrations outside this time window will require separate approval.
-        registrations_limit: If the current number of users exceeds this value, new registrations will not be possible unless the administrator increases the limit. Setting this to 0 disables the restriction.        
-        registrations_limit_per_day: If the number of users registered today exceeds this value, no new registrations will be allowed until 00:00 UTC the next day. Setting this to 0 disables the restriction.
-        registrations_start_hour: Specifies the start of the time window during which new registrations can be made without approval. Registrations cannot be made before this time or after the end time. Registrations outside this time window will require separate approval.
+        registrations_end_hour: 新規登録が承認なしで可能な時間帯の開始時間を指定します。これより前の時間に登録することはできません。終了時間より後にすることはできません。この時間帯から外れた新規登録には、別途承認が必要となります。
+        registrations_limit: 現在のユーザー数がこれを超過すると、管理者がこの数値を増やさない限り新規登録できません。0を指定すると、この制限を無効化します。
+        registrations_limit_per_day: 本日登録されたユーザー数がこれを超過すると、UTC時刻で翌日0時にならない限り新規登録できません。0を指定すると、この制限を無効化します。
+        registrations_start_hour: 新規登録が承認なしで可能な時間帯の終了時間を指定します。これより後の時間に登録することはできません。開始時間より前にすることはできません。この時間帯から外れた新規登録には、別途承認が必要となります。
         require_invite_text: When sign-ups require manual approval, make the “Why do you want to join?” text input mandatory rather than optional
         site_contact_email: How people can reach you for legal or support inquiries.
         site_contact_username: How people can reach you on Mastodon.
@@ -166,23 +164,8 @@ en:
         show_application: You will always be able to see which app published your post regardless.
       tag:
         name: You can only change the casing of the letters, for example, to make it more readable
-      terms_of_service:
-        changelog: Can be structured with Markdown syntax.
-        effective_date: A reasonable timeframe can range anywhere from 10 to 30 days from the date you notify your users.
-        text: Can be structured with Markdown syntax.
-      terms_of_service_generator:
-        admin_email: Legal notices include counternotices, court orders, takedown requests, and law enforcement requests.
-        arbitration_address: Can be the same as Physical address above, or “N/A” if using email.
-        arbitration_website: Can be a web form, or “N/A” if using email.
-        choice_of_law: City, region, territory or state the internal substantive laws of which shall govern any and all claims.
-        dmca_address: For US operators, use the address registered in the DMCA Designated Agent Directory. A P.O. Box listing is available upon direct request, use the DMCA Designated Agent Post Office Box Waiver Request to email the Copyright Office and describe that you are a home-based content moderator who fears revenge or retribution for your actions and need to use a P.O. Box to remove your home address from public view.
-        dmca_email: Can be the same email used for “Email address for legal notices” above.
-        domain: Unique identification of the online service you are providing.
-        jurisdiction: List the country where whoever pays the bills lives. If it’s a company or other entity, list the country where it’s incorporated, and the city, region, territory or state as appropriate.
-        min_age: Should not be below the minimum age required by the laws of your jurisdiction.
       user:
         chosen_languages: When checked, only posts in selected languages will be displayed in public timelines
-        date_of_birth: We have to make sure you're at least %{age} to use Mastodon. We won't store this.
         role: The role controls which permissions the user has.
       user_role:
         color: Color to be used for the role throughout the UI, as RGB in hex format
@@ -197,12 +180,12 @@ en:
     kmyblue: kmyblue
     labels:
       account:
-        attribution_domains: Websites allowed to credit you
+        attribution_domains_as_text: Websites allowed to credit you
         discoverable: Feature profile and posts in discovery algorithms
         fields:
           examples:
-            name_1: Example Gitea
-            value_1: Example https://giteahub.com
+            name_1: 例) GitHub
+            value_1: 例) https://github.com/xxxxxx
           name: Label
           value: Content
         indexable: Include public posts in search results
@@ -318,7 +301,6 @@ en:
         setting_hide_quote_unavailable_server: Hide quote menu from unavailable server
         setting_hide_status_reference_unavailable_server: Hide quiet quote (reference named by Fedibird) menu from unavailable server
         setting_lock_follow_from_bot: Request approval about bot follow
-        setting_missing_alt_text_modal: Show confirmation dialog before posting media without alt text
         setting_public_post_to_unlisted: Convert public post to public unlisted if not using Web app
         setting_reduce_motion: Reduce motion in animations
         setting_reject_public_unlisted_subscription: Reject sending public unlisted visibility/non-public searchability posts to Misskey, Calckey
@@ -326,7 +308,6 @@ en:
         setting_reject_unlisted_subscription: Reject sending unlisted visibility/non-public searchability posts to Misskey, Calckey
         setting_reverse_search_quote: Perform word-by-word search when search keywords are not enclosed in double quotes
         setting_show_application: Disclose application used to send posts
-        setting_show_avatar_on_filter: Show filtered posts with avatar and user profile
         setting_show_blocking_quote: Show posts which have a quote written by the user you are blocking
         setting_show_emoji_reaction_count: Show emoji reaction number
         setting_show_emoji_reaction_on_timeline: Show all emoji reactions on timeline
@@ -343,16 +324,14 @@ en:
         setting_stay_privacy: Keep visibility after post
         setting_stop_emoji_reaction_streaming: Disable emoji reaction streamings
         setting_system_font_ui: Use system's default font
-        setting_system_scrollbars_ui: Use system's default scrollbar
         setting_theme: Site theme
         setting_translatable_private: Allow other users translation of your private posts
         setting_trends: Show today's trends
         setting_unfollow_modal: Show confirmation dialog before unfollowing someone
         setting_use_blurhash: Show colorful gradients for hidden media
-        setting_use_custom_css: Enable custom CSS set by yourself
+        setting_use_custom_css: Enable custom CSS
         setting_use_pending_items: Slow mode
         setting_use_public_index: Include permitted accounts post to results of search
-        setting_use_server_css: Enable server CSS set by the administrator
         severity: Severity
         sign_in_token_attempt: Security code
         title: Title
@@ -379,7 +358,7 @@ en:
         name: Hashtag
       filters:
         actions:
-          blur: Hide media with a warning
+          half_warn: Half hide with a warning
           hide: Hide completely
           warn: Hide with a warning
         options:
@@ -404,7 +383,6 @@ en:
         favicon: Favicon
         mascot: Custom mascot (legacy)
         media_cache_retention_period: Media cache retention period
-        min_age: Minimum age requirement
         peers_api_enabled: Publish list of discovered servers in the API
         profile_directory: Enable profile directory
         receive_other_servers_emoji_reaction: Receive emoji reaction between other server users
@@ -481,24 +459,7 @@ en:
         name: Hashtag
         trendable: Allow this hashtag to appear under trends
         usable: Allow posts to use this hashtag locally
-      terms_of_service:
-        changelog: What's changed?
-        effective_date: Effective date
-        text: Terms of Service
-      terms_of_service_generator:
-        admin_email: Email address for legal notices
-        arbitration_address: Physical address for arbitration notices
-        arbitration_website: Website for submitting arbitration notices
-        choice_of_law: Choice of Law
-        dmca_address: Physical address for DMCA/copyright notices
-        dmca_email: Email address for DMCA/copyright notices
-        domain: Domain
-        jurisdiction: Legal jurisdiction
-        min_age: Minimum age
       user:
-        date_of_birth_1i: Day
-        date_of_birth_2i: Month
-        date_of_birth_3i: Year
         role: Role
         time_zone: Time zone
       user_role:
diff --git a/config/locales/simple_form.eo.yml b/config/locales/simple_form.eo.yml
index 1331ea7333..dfc9935194 100644
--- a/config/locales/simple_form.eo.yml
+++ b/config/locales/simple_form.eo.yml
@@ -3,14 +3,14 @@ eo:
   simple_form:
     hints:
       account:
-        attribution_domains: Unu por linio. Protektas kontraŭ falsaj atribuoj.
+        attribution_domains_as_text: Unu por linio. Protektas kontraŭ falsaj atribuoj.
         discoverable: Viaj publikaj afiŝoj kaj profilo povas esti prezentitaj aŭ rekomenditaj en diversaj lokoj de Mastodon kaj via profilo povas esti proponita al aliaj uzantoj.
         display_name: Via plena nomo aŭ via kromnomo.
         fields: Via retpaĝo, pronomoj, aĝo, ĉio, kion vi volas.
         indexable: Viaj publikaj afiŝoj povas aperi en serĉrezultoj ĉe Mastodon. Homoj, kiuj interagis kun viaj afiŝoj, eble povos serĉi ilin sendepende.
         note: 'Vi povas @mencii aliajn homojn aŭ #haŝetikedoj.'
         show_collections: Homoj povos foliumi viajn sekvatojn kaj sekvantojn. Homoj, kiujn vi sekvas, vidos, ke vi sekvas ilin ĉiaokaze.
-        unlocked: Homoj povos sekvi vin sen peti aprobon. Malmarku ĉu vi volas revizii sekvajn petojn kaj elektu ĉu akcepti aŭ malakcepti novajn sekvantojn.
+        unlocked: Homoj povos sekvi vin sen peto de aprobo. Malelektu se vi volas kontroli petojn de sekvado kaj elekti, ĉu akcepti aŭ malakcepti novajn sekvantojn.
       account_alias:
         acct: Specifu la uzantnomon@domajnon de la konto el kiu vi volas translokiĝi
       account_migration:
@@ -60,7 +60,6 @@ eo:
         setting_display_media_default: Kaŝi plurmediojn markitajn kiel tiklaj
         setting_display_media_hide_all: Ĉiam kaŝi la vidaŭdaĵojn
         setting_display_media_show_all: Ĉiam montri la vidaŭdaĵojn
-        setting_system_scrollbars_ui: Aplikas nur por surtablaj retumiloj baziĝas de Safari kaj Chrome
         setting_use_blurhash: Transirojn estas bazita sur la koloroj de la kaŝitaj aŭdovidaĵoj sed ne montri iun ajn detalon
         setting_use_pending_items: Kaŝi tempoliniajn ĝisdatigojn malantaŭ klako anstataŭ aŭtomate rulumi la fluon
         username: Vi povas uzi literojn, ciferojn kaj substrekojn
@@ -75,7 +74,6 @@ eo:
       filters:
         action: Elekti ago kiam mesaĝo kongruas la filtrilon
         actions:
-          blur: Kaŝi amaskomunikilaron malantaŭ averto, sen kaŝi la tekston mem
           hide: Tute kaŝigi la filtritajn enhavojn, kvazau ĝi ne ekzistis
           warn: Kaŝi la enhavon filtritan malantaŭ averto mencianta la nomon de la filtro
       form_admin_settings:
@@ -131,19 +129,8 @@ eo:
         show_application: Vi ĉiam povos vidi kiu aplikaĵo publikigis vian afiŝon ĉiaokaze.
       tag:
         name: Vi povas ŝanĝi nur la majuskladon de la literoj, ekzemple, por igi ĝin pli legebla
-      terms_of_service:
-        changelog: Povas esti strukturita per sintakso Markdown-a.
-        effective_date: Racia tempodaŭro povas varii ie ajn de 10 ĝis 30 tagoj de la dato, kiam vi sciigas viajn uzantojn.
-        text: Povas esti strukturita per sintakso Markdown-a.
-      terms_of_service_generator:
-        admin_email: Legalaj sciigoj povas esti kontraŭsciigoj, postulaĵoj de tribunalo, postulaĵoj pri forigo, kaj postulaĵoj de la policaro.
-        choice_of_law: Urbo, regiono, teritorio aŭ ŝtato, kies internaj substantivaj leĝoj regos iujn kaj ĉiujn asertojn.
-        dmca_address: Por tenantoj en Usono, uzu la adreson registritan en la DMCA Designated Agenŭ Directory. Registrolibro de poŝtskatoloj haveblas per direkta postulo, uzu la DMCA Designated Agent Post Office Box Waiver Request por retpoŝti la Ofico de Kopirajto kaj priskribu, ke vi estas hejm-trovigita administranto por enhavo kaj devas uzi Poŝtskatolon por forigi vian hejmadreson de publika vido.
-        domain: Unika identigilo de la retaj servicoj, ke vi provizas.
-        jurisdiction: Enlistigu la landon, kie loĝas kiu pagas la fakturojn. Se ĝi estas kompanio aŭ alia ento, listigu la landon, kie ĝi estas enkorpigita, kaj la urbon, regionon, teritorion aŭ ŝtaton laŭeble.
       user:
         chosen_languages: Kun tio markita nur mesaĝoj en elektitaj lingvoj aperos en publikaj tempolinioj
-        date_of_birth: Ni devas certigi, ke vi estas almenaŭ %{age} por uzi Mastodon. Ni ne konservos ĉi tion.
         role: La rolo kontrolas kiujn permesojn la uzanto havas.
       user_role:
         color: Koloro uzita por la rolo sur la UI, kun RGB-formato
@@ -157,7 +144,7 @@ eo:
         url: Kien eventoj sendotas
     labels:
       account:
-        attribution_domains: Retejoj permesitaj krediti vin
+        attribution_domains_as_text: Retejoj permesitaj krediti vin
         discoverable: Elstarigi profilon kaj afiŝojn en eltrovantaj algoritmoj
         fields:
           name: Etikedo
@@ -234,10 +221,8 @@ eo:
         setting_display_media_show_all: Montri ĉiujn
         setting_expand_spoilers: Ĉiam malfoldas mesaĝojn markitajn per averto pri enhavo
         setting_hide_network: Kaŝi viajn sekvantojn kaj sekvatojn
-        setting_missing_alt_text_modal: Montru konfirman dialogon antaŭ afiŝado de aŭdvidaĵoj sen altteksto
         setting_reduce_motion: Redukti la movecojn de la animacioj
         setting_system_font_ui: Uzi la dekomencan tiparon de la sistemo
-        setting_system_scrollbars_ui: Uzu la defaŭltan rulumilon de la sistemo
         setting_theme: Etoso de la retejo
         setting_trends: Montri hodiaŭajn furoraĵojn
         setting_unfollow_modal: Montri konfirman fenestron antaŭ ol ĉesi sekvi iun
@@ -256,7 +241,6 @@ eo:
         name: Kradvorto
       filters:
         actions:
-          blur: Kaŝi amaskomunikilaron kun averto
           hide: Kaŝi komplete
           warn: Kaŝi malantaŭ averto
       form_admin_settings:
@@ -333,24 +317,7 @@ eo:
         name: Kradvorto
         trendable: Permesi al ĉi tiu kradvorto aperi en furoraĵoj
         usable: Permesi afiŝojn uzi ĉi tiun kradvorton loke
-      terms_of_service:
-        changelog: Kio ŝanĝiĝis?
-        effective_date: Ekvalida dato
-        text: Kondiĉoj de uzado
-      terms_of_service_generator:
-        admin_email: Retpoŝtadreso por laŭleĝaj avizoj
-        arbitration_address: Fizika adreso por arbitraciaj avizoj
-        arbitration_website: Retejo por sendi arbitraciajn avizojn
-        choice_of_law: Elekto de leĝo
-        dmca_address: Fizika adreso por DMCA/kopirajto-avizoj
-        dmca_email: Retpoŝtadreso por DMCA/kopirajto-avizoj
-        domain: Domajno
-        jurisdiction: Laŭleĝa jurisdikcio
-        min_age: Minimuma aĝo
       user:
-        date_of_birth_1i: Tago
-        date_of_birth_2i: Monato
-        date_of_birth_3i: Jaro
         role: Rolo
         time_zone: Horzono
       user_role:
diff --git a/config/locales/simple_form.es-AR.yml b/config/locales/simple_form.es-AR.yml
index 4333db9fed..98254c5e99 100644
--- a/config/locales/simple_form.es-AR.yml
+++ b/config/locales/simple_form.es-AR.yml
@@ -3,14 +3,14 @@ es-AR:
   simple_form:
     hints:
       account:
-        attribution_domains: Uno por línea. Protege de falsas atribuciones.
+        attribution_domains_as_text: Una por línea. Protege de falsas atribuciones.
         discoverable: Tu perfil y publicaciones pueden ser destacadas o recomendadas en varias áreas de Mastodon, y tu perfil puede ser sugerido a otros usuarios.
         display_name: Tu nombre completo o tu pseudónimo.
         fields: Tu sitio web, pronombres, edad, o lo que quieras.
         indexable: Tus mensajes públicos pueden aparecer en los resultados de la búsqueda en Mastodon. La gente que interactuó con tus mensajes puede ser capaz de buscarlos sin importar el momento.
         note: 'Podés @mencionar otras cuentas o usar #etiquetas.'
         show_collections: La gente podrá navegar a través de tus seguidos y seguidores. Sin embargo, la gente que sigás, sabrá que lo estás haciendo.
-        unlocked: Las personas podrán seguirte sin solicitar aprobación. Desmarcá si querés revisar las solicitudes de seguimiento y elegir si querés aceptar o rechazar nuevos seguidores.
+        unlocked: La gente podrá seguirte sin solicitar aprobación. Desmarcá si querés revisar las solicitudes de seguimiento y elegir si aceptar o rechazar nuevos seguidores.
       account_alias:
         acct: Especificá el nombredeusuario@dominio de la cuenta desde la que querés mudarte
       account_migration:
@@ -60,7 +60,6 @@ es-AR:
         setting_display_media_default: Ocultar medios marcados como sensibles
         setting_display_media_hide_all: Siempre ocultar todos los medios
         setting_display_media_show_all: Siempre mostrar todos los medios
-        setting_system_scrollbars_ui: Solo aplica para navegadores web de escritorio basados en Safari y Chrome
         setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles
         setting_use_pending_items: Ocultar actualizaciones de la línea temporal detrás de un clic en lugar de desplazar automáticamente el flujo
         username: Podés usar letras, números y subguiones ("_")
@@ -75,7 +74,6 @@ es-AR:
       filters:
         action: Elegir qué acción realizar cuando un mensaje coincide con el filtro
         actions:
-          blur: Ocultar medios detrás de una advertencia, sin ocultar el texto en sí mismo
           hide: Ocultar completamente el contenido filtrado, comportándose como si no existiera
           warn: Ocultar el contenido filtrado detrás de una advertencia mencionando el título del filtro
       form_admin_settings:
@@ -89,7 +87,6 @@ es-AR:
         favicon: WEBP, PNG, GIF o JPG. Reemplaza el favicón predeterminado de Mastodon con uno personalizado.
         mascot: Reemplaza la ilustración en la interface web avanzada.
         media_cache_retention_period: Los archivos de medios de mensajes publicados por usuarios remotos se almacenan en la memoria caché en tu servidor. Cuando se establece un valor positivo, los medios se eliminarán después del número especificado de días. Si los datos multimedia se solicitan después de eliminarse, se volverán a descargar, si es que el contenido fuente todavía está disponible. Debido a restricciones en la frecuencia con la que las tarjetas de previsualización de enlace consultan a sitios web de terceros, se recomienda establecer este valor a, al menos, 14 días, o las tarjetas de previsualización de enlaces no se actualizarán a pedido antes de ese momento.
-        min_age: Se pedirá a los usuarios que confirmen su fecha de nacimiento durante el registro
         peers_api_enabled: Una lista de nombres de dominio que este servidor ha encontrado en el Fediverso. Acá no se incluye ningún dato sobre si federás con un servidor determinado, sólo que tu servidor lo conoce. Esto es usado por los servicios que recopilan estadísticas sobre la federación en un sentido general.
         profile_directory: El directorio de perfiles lista a todos los usuarios que han optado a que su cuenta pueda ser descubierta.
         require_invite_text: Cuando registros aprobación manual, hacé que la solicitud de invitación "¿Por qué querés unirte?" sea obligatoria, en vez de opcional
@@ -132,23 +129,8 @@ es-AR:
         show_application: Sin embargo, siempre podrás ver desde qué aplicación se envió tu mensaje.
       tag:
         name: Sólo podés cambiar la capitalización de las letras, por ejemplo, para que sea más legible
-      terms_of_service:
-        changelog: Se puede estructurar con sintaxis Markdown.
-        effective_date: Un plazo razonable puede oscilar entre 10 y 30 días a partir de la fecha de notificación a tus usuarios.
-        text: Se puede estructurar con sintaxis Markdown.
-      terms_of_service_generator:
-        admin_email: Los avisos legales incluyen devoluciones, órdenes judiciales, solicitudes de retiro y solicitudes de cumplimiento de la ley.
-        arbitration_address: Puede ser la misma que la dirección física anterior, o «N.D.» si usa correo electrónico.
-        arbitration_website: Puede ser un formulario web, o «N.D.» si usa correo electrónico.
-        choice_of_law: Ciudad, región, territorio o declaración de las leyes de sustancia interna de las que se regirán todas y cada una de las reclamaciones.
-        dmca_address: Para operadores estadounidenses, usen la dirección registrada en el Directorio de Agente Designado del DMCA. Una lista de casillas de correo está disponible bajo petición directa, usen la solicitud de exención del apartado postal del agente designado de la DMCA para enviar un correo electrónico a la Oficina de Derechos de Autor y describir que ustedes son moderadores de contenido basado en el hogar que temen venganza o represalia por sus acciones y que necesitan usar una casilla de correo para eliminar su dirección postal de la vista pública.
-        dmca_email: Puede ser el mismo correo electrónico utilizado para «Dirección de correo electrónico para avisos legales» de arriba.
-        domain: Identificación única del servicio en línea que estás proporcionando.
-        jurisdiction: Listá el país donde vive quien paga las facturas. Si es una empresa u otra entidad, enumerá el país donde está basada y la ciudad, región, territorio o provincia/estado, según corresponda.
-        min_age: No debería estar por debajo de la edad mínima requerida por las leyes de su jurisdicción.
       user:
         chosen_languages: Cuando estén marcados, sólo se mostrarán los mensajes en los idiomas seleccionados en las líneas temporales públicas
-        date_of_birth: Tenemos que asegurarnos de que al menos tenés %{age} años de edad para usar Mastodon. No almacenaremos esta información.
         role: El rol controla qué permisos tiene el usuario.
       user_role:
         color: Color que se utilizará para el rol a lo largo de la interface de usuario, como RGB en formato hexadecimal
@@ -162,7 +144,7 @@ es-AR:
         url: Adónde serán enviados los eventos
     labels:
       account:
-        attribution_domains: Sitios web autorizados a acreditarte
+        attribution_domains_as_text: Sitios web autorizados a acreditarte
         discoverable: Destacar perfil y mensajes en algoritmos de descubrimiento
         fields:
           name: Nombre de campo
@@ -239,10 +221,8 @@ es-AR:
         setting_display_media_show_all: Mostrar todo
         setting_expand_spoilers: Siempre expandir los mensajes marcados con advertencias de contenido
         setting_hide_network: Ocultá tu gráfica social
-        setting_missing_alt_text_modal: Mostrar diálogo de confirmación antes de enviar medios sin texto alternativo
         setting_reduce_motion: Reducir el movimiento de las animaciones
         setting_system_font_ui: Utilizar la tipografía predeterminada del sistema
-        setting_system_scrollbars_ui: Usar la barra de desplazamiento predeterminada del sistema operativo
         setting_theme: Tema del sitio
         setting_trends: Mostrar las tendencias de hoy
         setting_unfollow_modal: Mostrar diálogo de confirmación antes de dejar de seguir a una cuenta
@@ -261,7 +241,6 @@ es-AR:
         name: Etiqueta
       filters:
         actions:
-          blur: Ocultar medios con una advertencia
           hide: Ocultar completamente
           warn: Ocultar con una advertencia
       form_admin_settings:
@@ -275,7 +254,6 @@ es-AR:
         favicon: Favicón
         mascot: Mascota personalizada (legado)
         media_cache_retention_period: Período de retención de la caché de medios
-        min_age: Edad mínima requerida
         peers_api_enabled: Publicar lista de servidores descubiertos en la API
         profile_directory: Habilitar directorio de perfiles
         registrations_mode: Quién puede registrarse
@@ -339,24 +317,7 @@ es-AR:
         name: Etiqueta
         trendable: Permitir que esta etiqueta aparezca bajo tendencias
         usable: Permitir que los mensajes usen esta etiqueta localmente
-      terms_of_service:
-        changelog: "¿Qué cambió?"
-        effective_date: Fecha efectiva
-        text: Términos del servicio
-      terms_of_service_generator:
-        admin_email: Dirección de correo electrónico para avisos legales
-        arbitration_address: Dirección física para avisos de arbitraje
-        arbitration_website: Sitio web para enviar avisos de arbitraje
-        choice_of_law: Elección de ley
-        dmca_address: Dirección física para avisos de DMCA/derechos de autor
-        dmca_email: Dirección de correo electrónico para avisos de DMCA/derechos de autor
-        domain: Dominio
-        jurisdiction: Jurisdicción legal
-        min_age: Edad mínima
       user:
-        date_of_birth_1i: Día
-        date_of_birth_2i: Mes
-        date_of_birth_3i: Año
         role: Rol
         time_zone: Zona horaria
       user_role:
diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml
index b2f7daf500..135755bc7e 100644
--- a/config/locales/simple_form.es-MX.yml
+++ b/config/locales/simple_form.es-MX.yml
@@ -3,14 +3,14 @@ es-MX:
   simple_form:
     hints:
       account:
-        attribution_domains: Uno por línea. Protege contra atribuciones falsas.
+        attribution_domains_as_text: Uno por línea. Protege contra atribuciones falsas.
         discoverable: Tu perfil y las publicaciones públicas pueden ser destacadas o recomendadas en varias áreas de Mastodon y tu perfil puede ser sugerido a otros usuarios.
         display_name: Tu nombre completo o tu apodo.
         fields: Tu página de inicio, pronombres, edad, lo que quieras.
         indexable: Tus publicaciones públicas pueden aparecer en los resultados de búsqueda en Mastodon. Las personas que han interactuado con tus publicaciones pueden buscarlas en cualquier momento.
         note: 'Puedes @mencionar a otra gente o #etiquetas.'
         show_collections: Las personas pueden navegar a través de tus seguidos y seguidores. Las personas que te siguen pueden ver que las sigues.
-        unlocked: La gente podrá seguirte sin solicitar aprobación. Desmarca si quieres revisar las solicitudes de seguimiento y elige si quieres aceptar o rechazar nuevos seguidores.
+        unlocked: Las personas pueden seguirte sin solicitar la aprobación. No lo selecciones si quieres revisar las solicitudes de seguimiento y elegir si aceptas o rechazas a nuevos seguidores.
       account_alias:
         acct: Especifique el nombre de usuario@dominio de la cuenta desde la cual se desea migrar
       account_migration:
@@ -51,7 +51,7 @@ es-MX:
         inbox_url: Copia la URL de la página principal del relé que deseas usar
         irreversible: Las publicaciones filtradas desaparecerán irreversiblemente, incluso si este filtro es eliminado más adelante
         locale: El idioma de la interfaz de usuario, correos y notificaciones push
-        password: Usa al menos 8 caracteres
+        password: Utiliza al menos 8 caracteres
         phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de una publicación
         scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales.
         setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que han sido recientemente impulsadas (sólo afecta a las publicaciones recibidas recientemente)
@@ -60,7 +60,6 @@ es-MX:
         setting_display_media_default: Ocultar contenido multimedia marcado como sensible
         setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia
         setting_display_media_show_all: Mostrar siempre contenido multimedia marcado como sensible
-        setting_system_scrollbars_ui: Solo se aplica a los navegadores de escritorio basados en Safari y Chrome
         setting_use_blurhash: Los degradados se basan en los colores de los elementos visuales ocultos, pero ocultan cualquier detalle
         setting_use_pending_items: Ocultar las publicaciones de la línea de tiempo tras un clic en lugar de desplazar automáticamente el feed
         username: Puedes usar letras, números y guiones bajos
@@ -68,14 +67,13 @@ es-MX:
       domain_allow:
         domain: Este dominio podrá obtener datos de este servidor y los datos entrantes serán procesados y archivados
       email_domain_block:
-        domain: Este puede ser el nombre de dominio que se muestra en la dirección de correo o el registro MX que utiliza. Se comprobarán al registrarse.
+        domain: Este puede ser el nombre de dominio que se muestra en al dirección de correo o el registro MX que utiliza. Se comprobarán al registrarse.
         with_dns_records: Se hará un intento de resolver los registros DNS del dominio dado y los resultados serán también puestos en lista negra
       featured_tag:
-        name: 'Aquí están algunas de las etiquetas que más has usado recientemente:'
+        name: 'Aquí están algunas de las etiquetas que más has utilizado recientemente:'
       filters:
         action: Elegir qué acción realizar cuando una publicación coincide con el filtro
         actions:
-          blur: Ocultar contenido multimedia detrás de una advertencia, sin ocultar el propio texto
           hide: Ocultar completamente el contenido filtrado, comportándose como si no existiera
           warn: Ocultar el contenido filtrado detrás de una advertencia mencionando el título del filtro
       form_admin_settings:
@@ -89,15 +87,14 @@ es-MX:
         favicon: WEBP, PNG, GIF o JPG. Reemplaza el icono predeterminado de Mastodon con un icono personalizado.
         mascot: Reemplaza la ilustración en la interfaz web avanzada.
         media_cache_retention_period: Los archivos multimedia de las publicaciones realizadas por usuarios remotos se almacenan en caché en su servidor. Si se establece en un valor positivo, los archivos multimedia se eliminarán tras el número de días especificado. Si los datos multimedia se solicitan después de haber sido eliminados, se volverán a descargar, si el contenido de origen sigue estando disponible. Debido a las restricciones sobre la frecuencia con la que las tarjetas de previsualización de enlaces sondean sitios de terceros, se recomienda establecer este valor en al menos 14 días, o las tarjetas de previsualización de enlaces no se actualizarán bajo demanda antes de ese tiempo.
-        min_age: Se pedirá a los usuarios que confirmen su fecha de nacimiento al registrarse
-        peers_api_enabled: Una lista de nombres de dominio que este servidor ha encontrado en el fediverso. Aquí no se incluye ningún dato sobre si usted federa con un servidor determinado, solamente que su servidor lo sabe. Esto es usado por los servicios que recopilan estadísticas sobre la federación en un sentido general.
+        peers_api_enabled: Una lista de nombres de dominio que este servidor ha encontrado en el fediverso. Aquí no se incluye ningún dato sobre si usted federa con un servidor determinado, sólo que su servidor lo sabe. Esto es utilizado por los servicios que recopilan estadísticas sobre la federación en un sentido general.
         profile_directory: El directorio de perfiles lista a todos los usuarios que han optado por que su cuenta pueda ser descubierta.
         require_invite_text: Cuando los registros requieren aprobación manual, hace obligatoria la entrada de texto "¿Por qué quieres unirte?" en lugar de opcional
         site_contact_email: Cómo la gente puede ponerse en contacto contigo para consultas legales o de ayuda.
         site_contact_username: Cómo puede contactarte la gente en Mastodon.
         site_extended_description: Cualquier información adicional que pueda ser útil para los visitantes y sus usuarios. Se puede estructurar con formato Markdown.
         site_short_description: Una breve descripción para ayudar a identificar su servidor de forma única. ¿Quién lo administra, a quién va dirigido?
-        site_terms: Usa tu propia política de privacidad o déjala en blanco para usar la predeterminada Puede estructurarse con formato Markdown.
+        site_terms: Utiliza tu propia política de privacidad o déjala en blanco para usar la predeterminada Puede estructurarse con formato Markdown.
         site_title: Cómo puede referirse la gente a tu servidor además de por el nombre de dominio.
         status_page_url: URL de una página donde las personas pueden ver el estado de este servidor durante una interrupción
         theme: El tema que los visitantes no registrados y los nuevos usuarios ven.
@@ -132,23 +129,8 @@ es-MX:
         show_application: Siempre podrás ver desde cuál aplicación realizaste una publicación.
       tag:
         name: Sólo se puede cambiar el cajón de las letras, por ejemplo, para que sea más legible
-      terms_of_service:
-        changelog: Puede estructurarse con la sintaxis Markdown.
-        effective_date: Un plazo razonable puede oscilar entre 10 y 30 días a partir de la fecha en que notifica a sus usuarios.
-        text: Puede estructurarse con la sintaxis Markdown.
-      terms_of_service_generator:
-        admin_email: Las notificaciones legales incluyen contraavisos, órdenes judiciales, solicitudes de retirada y solicitudes de aplicación de la ley.
-        arbitration_address: Puede ser la misma que la dirección física anterior, o "N/A" si usa correo electrónico.
-        arbitration_website: Puede ser un formulario web, o “N/A” si usa correo electrónico.
-        choice_of_law: Ciudad, región, territorio o estado de las leyes de sustancia interna de las que se regirán todas y cada una de las reclamaciones.
-        dmca_address: Para los operadores de EE. UU., utilice la dirección registrada en el Directorio de Agentes Designados de la DMCA. Un listado de apartados de correos está disponible bajo petición directa, utilice la Solicitud de Renuncia de Apartado de Correos de Agente Designado de la DMCA para enviar un correo electrónico a la Oficina de Derechos de Autor y describa que usted es un moderador de contenidos desde su casa que teme venganza o represalias por sus acciones y necesita utilizar un apartado de correos para eliminar su dirección particular de la vista del público.
-        dmca_email: Puede ser el mismo correo electrónico usado para “Dirección de correo electrónico para avisos legales” de arriba.
-        domain: Identificación única del servicio en línea que presta.
-        jurisdiction: Indique el país de residencia de quien paga las facturas. Si se trata de una empresa u otra entidad, indique el país en el que está constituida y la ciudad, región, territorio o estado, según proceda.
-        min_age: No debe ser menor de la edad mínima exigida por las leyes de su jurisdicción.
       user:
         chosen_languages: Cuando se marca, solo se mostrarán las publicaciones en los idiomas seleccionados en las líneas de tiempo públicas
-        date_of_birth: Tenemos que asegurarnos de que tienes al menos %{age} para usar Mastodon. No almacenaremos esto.
         role: El rol controla qué permisos tiene el usuario.
       user_role:
         color: Color que se usará para el rol en toda la interfaz de usuario, como RGB en formato hexadecimal
@@ -162,7 +144,7 @@ es-MX:
         url: Donde los eventos serán enviados
     labels:
       account:
-        attribution_domains: Sitios web autorizados para acreditarte
+        attribution_domains_as_text: Sitios web autorizados para acreditarte
         discoverable: Destacar el perfil y las publicaciones en el algoritmo de descubrimiento
         fields:
           name: Etiqueta
@@ -239,10 +221,8 @@ es-MX:
         setting_display_media_show_all: Mostrar todo
         setting_expand_spoilers: Siempre expandir las publicaciones marcadas con advertencias de contenido
         setting_hide_network: Ocultar tu red
-        setting_missing_alt_text_modal: Mostrar cuadro de diálogo de confirmación antes de publicar contenido multimedia sin texto alternativo
         setting_reduce_motion: Reducir el movimiento de las animaciones
         setting_system_font_ui: Usar la fuente por defecto del sistema
-        setting_system_scrollbars_ui: Usar la barra de desplazamiento por defecto del sistema
         setting_theme: Tema del sitio
         setting_trends: Mostrar las tendencias de hoy
         setting_unfollow_modal: Mostrar diálogo de confirmación antes de dejar de seguir a alguien
@@ -261,7 +241,6 @@ es-MX:
         name: Etiqueta
       filters:
         actions:
-          blur: Ocultar contenido multimedia con una advertencia
           hide: Ocultar completamente
           warn: Ocultar con una advertencia
       form_admin_settings:
@@ -275,7 +254,6 @@ es-MX:
         favicon: Favicon
         mascot: Mascota personalizada (legado)
         media_cache_retention_period: Período de retención de caché multimedia
-        min_age: Edad mínima requerida
         peers_api_enabled: Publicar lista de servidores descubiertos en la API
         profile_directory: Habilitar directorio de perfiles
         registrations_mode: Quién puede registrarse
@@ -339,24 +317,7 @@ es-MX:
         name: Etiqueta
         trendable: Permitir que esta etiqueta aparezca bajo tendencias
         usable: Permitir a las publicaciones usar esta etiqueta localmente
-      terms_of_service:
-        changelog: "¿Qué es lo que ha cambiado?"
-        effective_date: Fecha de entrada en vigor
-        text: Condiciones del servicio
-      terms_of_service_generator:
-        admin_email: Dirección de correo electrónico para notificaciones legales
-        arbitration_address: Dirección física para notificaciones de arbitraje
-        arbitration_website: Sitio web para presentar notificaciones de arbitraje
-        choice_of_law: Elección de la ley
-        dmca_address: Dirección física para avisos de DMCA/derechos de autor
-        dmca_email: Dirección de correo electrónico para avisos de DMCA/derechos de autor
-        domain: Dominio
-        jurisdiction: Jurisdicción legal
-        min_age: Edad mínima
       user:
-        date_of_birth_1i: Día
-        date_of_birth_2i: Mes
-        date_of_birth_3i: Año
         role: Rol
         time_zone: Zona horaria
       user_role:
diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml
index d469c6ec3a..1e80b9ce05 100644
--- a/config/locales/simple_form.es.yml
+++ b/config/locales/simple_form.es.yml
@@ -3,14 +3,14 @@ es:
   simple_form:
     hints:
       account:
-        attribution_domains: Una por línea. Protege de falsas atribuciones.
+        attribution_domains_as_text: Una por línea. Protege de falsas atribuciones.
         discoverable: Tu perfil y publicaciones públicas pueden ser destacadas o recomendadas en varias áreas de Mastodon y tu perfil puede ser sugerido a otros usuarios.
         display_name: Tu nombre completo o tu apodo.
         fields: Tu carta de presentación, pronombres, edad, lo que quieras.
         indexable: Tus publicaciones públicas pueden aparecer en los resultados de búsqueda en Mastodon. Las personas que han interactuado con tus publicaciones pueden ser capaces de buscarlas sin importar su visibilidad.
         note: Puedes mencionar a otras personas o etiquetas.
         show_collections: Las personas podrán navegar a través de tus seguidos y seguidores. Esto no impide que las personas que sigues sepan que las estás siguiendo.
-        unlocked: Las personas podrán seguirte sin solicitar aprobación. Desmarca si quieres revisar las solicitudes de seguimiento y aceptar o rechazar nuevos seguidores.
+        unlocked: Las personas podrán seguirte sin pedir aprobación. Desmarca si deseas revisar las solicitudes de seguimiento y aceptar o rechazar nuevos seguidores.
       account_alias:
         acct: Especifica el nombre_de_usuario@dominio de la cuenta desde donde deseas migrar
       account_migration:
@@ -60,7 +60,6 @@ es:
         setting_display_media_default: Ocultar contenido multimedia marcado como sensible
         setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia
         setting_display_media_show_all: Mostrar siempre contenido multimedia marcado como sensible
-        setting_system_scrollbars_ui: Solo aplica para navegadores de escritorio basados en Safari y Chrome
         setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles
         setting_use_pending_items: Ocultar nuevas publicaciones detrás de un clic en lugar de desplazar automáticamente el feed
         username: Puedes usar letras, números y guiones bajos
@@ -75,7 +74,6 @@ es:
       filters:
         action: Elegir qué acción realizar cuando una publicación coincide con el filtro
         actions:
-          blur: Ocultar contenido multimedia detrás de una advertencia, sin ocultar el texto en sí
           hide: Ocultar completamente el contenido filtrado, comportándose como si no existiera
           warn: Ocultar el contenido filtrado detrás de una advertencia mencionando el título del filtro
       form_admin_settings:
@@ -89,7 +87,6 @@ es:
         favicon: WEBP, PNG, GIF o JPG. Reemplaza el favicon predeterminado de Mastodon con un icono personalizado.
         mascot: Reemplaza la ilustración en la interfaz web avanzada.
         media_cache_retention_period: Los archivos multimedia de las publicaciones creadas por usuarios remotos se almacenan en caché en tu servidor. Cuando se establece un valor positivo, estos archivos se eliminarán después del número especificado de días. Si los datos multimedia se solicitan después de eliminarse, se volverán a descargar, si el contenido fuente todavía está disponible. Debido a restricciones en la frecuencia con la que las tarjetas de previsualización de enlaces realizan peticiones a sitios de terceros, se recomienda establecer este valor a al menos 14 días, o las tarjetas de previsualización de enlaces no se actualizarán bajo demanda antes de ese momento.
-        min_age: Se pedirá a los usuarios que confirmen su fecha de nacimiento durante el registro
         peers_api_enabled: Una lista de nombres de dominio que este servidor ha encontrado en el Fediverso. Aquí no se incluye ningún dato sobre si federas con un servidor determinado, solo que tu servidor lo conoce. Esto es utilizado por los servicios que recopilan estadísticas sobre la federación en un sentido general.
         profile_directory: El directorio de perfiles lista a todos los usuarios que han optado por que su cuenta pueda ser descubierta.
         require_invite_text: Cuando los registros requieren aprobación manual, hace obligatoria la entrada de texto "¿Por qué quieres unirte?" en lugar de opcional
@@ -132,23 +129,8 @@ es:
         show_application: Tú siempre podrás ver desde qué aplicación se ha publicado tu publicación.
       tag:
         name: Sólo se puede cambiar el cajón de las letras, por ejemplo, para que sea más legible
-      terms_of_service:
-        changelog: Se puede estructurar con sintaxis Markdown.
-        effective_date: Un plazo razonable puede oscilar entre 10 y 30 días a partir de la fecha de notificación a los usuarios.
-        text: Se puede estructurar con sintaxis Markdown.
-      terms_of_service_generator:
-        admin_email: Los avisos legales incluyen devoluciones, órdenes judiciales, solicitudes de retiro y solicitudes de cumplimiento de la ley.
-        arbitration_address: Puede ser la misma que la dirección física anterior, o "N/A" si utiliza correo electrónico.
-        arbitration_website: Puede ser un formulario web, o “N/A” si utiliza correo electrónico.
-        choice_of_law: Ciudad, región, territorio o estado de los juzgados que regirán todas y cada una de las disputas legales.
-        dmca_address: Para operadores estadounidenses, utilice la dirección registrada en el DMCA Designated Agent Directory. Un listado P.O. Box está disponible bajo petición directa, use la DMCA Designated Agent Post Office Box Waiver Request para enviar un correo electrónico a la Oficina de Derechos de Autor y describir que usted es un moderador de contenido basado en el hogar que teme venganza o represalia por sus acciones y que necesita usar un P.O. Box para eliminar su dirección postal de la vista pública.
-        dmca_email: Puede ser el mismo correo electrónico utilizado para "Dirección de correo electrónico para avisos legales" de arriba.
-        domain: Identificación única del servicio en línea que estás proporcionando.
-        jurisdiction: Lista el país donde vive quien paga las facturas. Si es una empresa u otra entidad, enumere el país donde está basada y la ciudad, región, territorio o estado según corresponda.
-        min_age: No debería estar por debajo de la edad mínima requerida por las leyes de su jurisdicción.
       user:
         chosen_languages: Cuando se marca, solo se mostrarán las publicaciones en los idiomas seleccionados en las líneas de tiempo públicas
-        date_of_birth: Tenemos que asegurarnos de que al menos tienes %{age} años para usar Mastodon. No almacenaremos esta información.
         role: El rol controla qué permisos tiene el usuario.
       user_role:
         color: Color que se utilizará para el rol a lo largo de la interfaz de usuario, como RGB en formato hexadecimal
@@ -162,7 +144,7 @@ es:
         url: Donde los eventos serán enviados
     labels:
       account:
-        attribution_domains: Sitios web autorizados a acreditarte
+        attribution_domains_as_text: Sitios web autorizados a acreditarte
         discoverable: Destacar perfil y publicaciones en algoritmos de descubrimiento
         fields:
           name: Etiqueta
@@ -239,10 +221,8 @@ es:
         setting_display_media_show_all: Mostrar todo
         setting_expand_spoilers: Siempre expandir las publicaciones marcadas con advertencias de contenido
         setting_hide_network: Ocultar tu red
-        setting_missing_alt_text_modal: Mostrar diálogo de confirmación antes de publicar medios sin texto alternativo
         setting_reduce_motion: Reducir el movimiento de las animaciones
         setting_system_font_ui: Utilizar la tipografía por defecto del sistema
-        setting_system_scrollbars_ui: Utilizar barra de desplazamiento predeterminada del sistema
         setting_theme: Tema del sitio
         setting_trends: Mostrar las tendencias de hoy
         setting_unfollow_modal: Mostrar diálogo de confirmación antes de dejar de seguir a alguien
@@ -261,7 +241,6 @@ es:
         name: Etiqueta
       filters:
         actions:
-          blur: Ocultar contenido multimedia con una advertencia
           hide: Ocultar completamente
           warn: Ocultar con una advertencia
       form_admin_settings:
@@ -275,7 +254,6 @@ es:
         favicon: Favicon
         mascot: Mascota personalizada (legado)
         media_cache_retention_period: Período de retención de caché multimedia
-        min_age: Edad mínima requerida
         peers_api_enabled: Publicar lista de servidores descubiertos en la API
         profile_directory: Habilitar directorio de perfiles
         registrations_mode: Quién puede registrarse
@@ -339,24 +317,7 @@ es:
         name: Etiqueta
         trendable: Permitir que esta etiqueta aparezca bajo tendencias
         usable: Permitir a las publicaciones usar esta etiqueta localmente
-      terms_of_service:
-        changelog: "¿Qué ha cambiado?"
-        effective_date: Fecha de entrada en vigor
-        text: Términos del servicio
-      terms_of_service_generator:
-        admin_email: Dirección de correo electrónico para avisos legales
-        arbitration_address: Dirección física para avisos de arbitraje
-        arbitration_website: Sitio web para enviar avisos de arbitraje
-        choice_of_law: Legislación aplicable
-        dmca_address: Dirección física para avisos de DMCA/derechos de autor
-        dmca_email: Dirección de correo electrónico para avisos de DMCA/derechos de autor
-        domain: Dominio
-        jurisdiction: Jurisdicción legal
-        min_age: Edad mínima
       user:
-        date_of_birth_1i: Día
-        date_of_birth_2i: Mes
-        date_of_birth_3i: Año
         role: Rol
         time_zone: Zona horaria
       user_role:
diff --git a/config/locales/simple_form.et.yml b/config/locales/simple_form.et.yml
index 1eda0ec67b..a2ebf63b11 100644
--- a/config/locales/simple_form.et.yml
+++ b/config/locales/simple_form.et.yml
@@ -3,12 +3,14 @@ et:
   simple_form:
     hints:
       account:
+        attribution_domains_as_text: Üks rea peal. See kaitseb pahatahtlike viidete eest.
         discoverable: Su profiili ja avalikke postitusi võidakse Mastodoni erinevates piirkondades esile tõsta või soovitada ning su profiili soovitada teistele kasutajatele.
         display_name: Su täisnimi või naljanimi.
         fields: Su koduleht, sugu, vanus. Mistahes, mida soovid.
         indexable: Sinu avalikud postitused võivad ilmuda Mastodoni otsingutulemustes. Inimesed, kes on sinu postitustele reageerinud, saavad neid otsida nii või naa.
         note: 'Saad @mainida teisi inimesi või #silte.'
         show_collections: Inimesed saavad sirvida su jälgijaid ja jälgitavaid. Inimesed, keda sa jälgid, näevad seda sõltumata häälestuse valikust.
+        unlocked: Inimesed saavad sind jälgima hakata kinnitamist taotlemata. Eemalda märge, kui soovid jälgimistaotlusi üle vaadata ja valida, kas nõustuda või keelduda uute jälgijatega.
       account_alias:
         acct: Sisesta konto kasutajanimi@domeen, mille soovid siia ümber kolida
       account_migration:
@@ -127,9 +129,6 @@ et:
         show_application: Sa saad sõltumata sellest vaadata, milline äpp su postituse postitas.
       tag:
         name: Saad muuta ainult tähtede suurtähelisust, näiteks selleks, et muuta seda loetavamaks
-      terms_of_service_generator:
-        dmca_address: USA operaatorite puhul kasuta DMCA määratud esindajate kataloogis registreeritud aadressi. Postkastide loetelu on saadaval otsese taotluse korral. Kasuta DMCA määratud esindaja postkastist loobumise taotlust, et saata e-kiri autoriõiguse ametile ja kirjeldada, et oled kodus asuv sisu moderaator, kes kardab kättemaksu või kättemaksu oma tegevuse eest ja vajab postkasti, et eemaldada oma kodune aadress avalikust nähtavusest.
-        jurisdiction: Nimeta riik, kus elab see, kes arveid maksab. Kui tegemist on äriühingu või muu üksusega, märgi riik, kus see on asutatud, ning vajaduse korral linn, piirkond, territoorium või osariik.
       user:
         chosen_languages: Keelte valimisel näidatakse avalikel ajajoontel ainult neis keeltes postitusi
         role: Rollid määravad, millised õigused kasutajal on.
@@ -145,6 +144,7 @@ et:
         url: Kuhu sündmused saadetakse
     labels:
       account:
+        attribution_domains_as_text: Sinule viidata lubatud veebilehed
         discoverable: Tõsta postitused ja profiil avastamise algoritmides esile
         fields:
           name: Nimetus
diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml
index dfe6c0b6d7..f9de2de93c 100644
--- a/config/locales/simple_form.eu.yml
+++ b/config/locales/simple_form.eu.yml
@@ -3,7 +3,7 @@ eu:
   simple_form:
     hints:
       account:
-        attribution_domains: Lerroko bat. Atribuzio faltsuetatik babesten ditu.
+        attribution_domains_as_text: Lerroko bat. Atribuzio faltsuetatik babesten ditu.
         discoverable: Zure bidalketa publikoak eta profila nabarmendu edo gomendatu egin daitezke Mastodon-go hainbat eremutan eta zure profila beste erabiltzaile batzuei iradoki dakieke.
         display_name: Zure izena edo ezizena.
         fields: Zure webgunea, izenordainak, adina, nahi duzun guztia.
@@ -60,7 +60,6 @@ eu:
         setting_display_media_default: Ezkutatu hunkigarri gisa markatutako multimedia
         setting_display_media_hide_all: Ezkutatu multimedia guztia beti
         setting_display_media_show_all: Erakutsi beti hunkigarri gisa markatutako multimedia
-        setting_system_scrollbars_ui: Safari eta Chrome-n oinarritutako mahaigaineko nabigatzaileei bakarrik aplikatzen zaie
         setting_use_blurhash: Gradienteak ezkutatutakoaren koloreetan oinarritzen dira, baina xehetasunak ezkutatzen dituzte
         setting_use_pending_items: Ezkutatu denbora-lerroko eguneraketak klik baten atzean jarioa automatikoki korritu ordez
         username: Hizkiak, zenbakiak eta azpimarrak erabil ditzakezu
@@ -130,11 +129,6 @@ eu:
         show_application: Dena dela, beti ikusi ahal izango duzu zein aplikaziok argitaratu zuen zure bidalketa.
       tag:
         name: Letrak maiuskula/minuskulara aldatu ditzakezu besterik ez, adibidez irakurterrazago egiteko
-      terms_of_service:
-        changelog: Markdown-en sintaxiarekin egitura daiteke.
-        text: Markdown-en sintaxiarekin egitura daiteke.
-      terms_of_service_generator:
-        admin_email: Legezko abisuak, kontraindikazioak, agindu judizialak, erretiratzeko eskaerak eta legea betetzeko eskaerak barne.
       user:
         chosen_languages: Markatzean, hautatutako hizkuntzetan dauden tutak besterik ez dira erakutsiko.
       user_role:
@@ -149,7 +143,7 @@ eu:
         url: Nora bidaliko diren gertaerak
     labels:
       account:
-        attribution_domains: Akreditatzeko baimendutako webguneak
+        attribution_domains_as_text: Akreditatzeko baimendutako webguneak
         discoverable: Ezagutarazi profila eta bidalketak bilaketa algoritmoetan
         fields:
           name: Etiketa
@@ -321,11 +315,6 @@ eu:
         name: Traola
         trendable: Baimendu traola hau joeretan agertzea
         usable: Baimendu bidalketek traola lokal hau erabiltzea
-      terms_of_service:
-        changelog: Zer aldatu da?
-        text: Zerbitzuaren baldintzak
-      terms_of_service_generator:
-        domain: Domeinua
       user:
         role: Rola
         time_zone: Ordu zona
diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml
index 9f46cdec7d..640a396be0 100644
--- a/config/locales/simple_form.fa.yml
+++ b/config/locales/simple_form.fa.yml
@@ -3,14 +3,14 @@ fa:
   simple_form:
     hints:
       account:
-        attribution_domains: در هر خط، یک مورد. از ویژگی های نادرست محافظت می کند.
+        attribution_domains_as_text: یکی در هر خط. محافظت از اعتباردهی‌های اشتباه.
         discoverable: ممکن است نمایه و فرسته‌های عمومیتان در جاهای مختلف ماستودون نمایانده و توصیه شود و نمایه‌تان به دیگر کاربران پیشنهاد شود.
         display_name: نام کامل یا باحالتان.
         fields: صفحهٔ خانگی، تلفّظ، سن و هرچیزی که دوست دارید.
         indexable: ممکن است فرسته‌های عمومیتان در نتیجه‌های جست‌وجوی ماستودون ظاهر شود. افرادی که با فرسته‌هایتان تعامل داشتند در هر صورت می‌توانند جست‌وجویشان کنند.
         note: 'می‌توانید افراد دیگر را @نام برده یا #برچسب بزنید.'
         show_collections: افراد خواهند توانست پی‌گیران و پی‌گرفته شده‌هایتان را مرور کنند. افرادی که پی‌می‌گیریدشان در هر صورت خواهند دید که پی‌می‌گیریدشان.
-        unlocked: افراد می توانند بدون درخواست تایید شما را دنبال کنند. اگر می‌خواهید درخواست‌های فالو را بررسی کنید علامت آن را بردارید و انتخاب کنید که آیا دنبال‌کنندگان جدید را بپذیرید یا رد کنید.
+        unlocked: افراد خواهند توانست بدون درخواست تأیید پی‌بگیرندتان. اگر می‌خواهید درخواست‌های پی‌گیری را بازبینی کرده و بگزینید که پی‌گیران جدید را بپذیرید یا رد کنید، علامت را بردارید.
       account_alias:
         acct: مشخّص کردن username@domain حسابی که می‌خواهید از آن منتقل شوید
       account_migration:
@@ -46,11 +46,11 @@ fa:
         current_password: به دلایل امنیتی لطفاً گذرواژه این حساب را وارد کنید
         current_username: برای تأیید، لطفاً نام کاربری حساب فعلی را وارد کنید
         digest: تنها وقتی فرستاده می‌شود که مدتی طولانی فعالیتی نداشته باشید و در این مدت برای شما پیغام خصوصی‌ای نوشته شده باشد
-        email: تأییدیه‌ای برایتان رایانامه خواهد شد
+        email: به شما ایمیل تأییدی فرستاده خواهد شد
         header: یکی از قالب‌های WEBP، PNG، GIF یا JPG. بیشترین اندازه %{size}. تصویر به اندازهٔ %{dimensions} پیکسل تبدیل خواهد شد
         inbox_url: نشانی صفحهٔ اصلی رله‌ای را که می‌خواهید به کار ببرید کپی کنید
         irreversible: فرسته‌های پالوده به طور برگشت‌ناپذیری ناپدید می‌شوند، حتا اگر بعدها پالایه برداشته شود
-        locale: زبان میانای کاربری، رایانامه‌ها و آگاهی‌های ارسالی
+        locale: زبان واسط کاربری، رایانامه‌ها و آگاهی‌های ارسالی
         password: دست‌کم باید ۸ نویسه داشته باشد
         phrase: مستقل از کوچکی و بزرگی حروف، با متن اصلی یا هشدار محتوای فرسته‌ها مقایسه می‌شود
         scopes: واسط‌های برنامه‌نویسی که این برنامه به آن دسترسی دارد. اگر بالاترین سطح دسترسی را انتخاب کنید، دیگر نیازی به انتخاب سطح‌های پایینی ندارید.
@@ -60,7 +60,6 @@ fa:
         setting_display_media_default: تصویرهایی را که به عنوان حساس علامت زده شده‌اند پنهان کن
         setting_display_media_hide_all: همیشه همهٔ عکس‌ها و ویدیوها را پنهان کن
         setting_display_media_show_all: همیشه تصویرهایی را که به عنوان حساس علامت زده شده‌اند را نشان بده
-        setting_system_scrollbars_ui: فقط برای مرورگرهای دسکتاپ مبتنی بر سافاری و کروم اعمال می شود
         setting_use_blurhash: سایه‌ها بر اساس رنگ‌های به‌کاررفته در تصویر پنهان‌شده ساخته می‌شوند ولی جزئیات تصویر در آن‌ها آشکار نیست
         setting_use_pending_items: به جای پیش‌رفتن خودکار در فهرست، به‌روزرسانی فهرست نوشته‌ها را پشت یک کلیک پنهان کن
         username: تنها می‌توانید از حروف، اعداد، و زیرخط استفاده کنید
@@ -130,20 +129,6 @@ fa:
         show_application: خودتان همواره خواهید توانست ببینید که کدام کاره فرسته‌تان را منتشر کرده.
       tag:
         name: شما تنها می‌توانید بزرگی و کوچکی حروف را تغییر دهید تا مثلاً آن را خواناتر کنید
-      terms_of_service:
-        changelog: می توان با دستور Markdown ساختار داد.
-        effective_date: بازهٔ زمانی معقول می‌تواند بین ۱۰ تا ۳۰ روز از زمان آگاه کردن کاربران باشد.
-        text: می توان با دستور Markdown ساختار داد.
-      terms_of_service_generator:
-        admin_email: اخطارهای حقوقی شامل اخطارهای متقابل، دستورها دادگاه، درخواست‌های حذف و درخواست‌های اجرای قانون است.
-        arbitration_address: می‌تواند همان نشانی فیزیکی بالا یا در صورت استفاده از رایانامه N/A باشد.
-        arbitration_website: می‌تواند یک فرم وب یا در صورت استفاده از رایانامه N/A باشد.
-        choice_of_law: شهر، منطقه، قلمرو یا ایالتی که قوانین ماهوی داخلی آن بر هر یک از دعاوی حاکم است.
-        dmca_address: استفاده از نشانی ثبت شده در DMCA برای کاروران ایالات متحده. سیاههٔ صندوق‌های پستی با درخواست مستقیم موجود است. استفاده از درخواست لغو صندوق پست برای رایانامه به دفتر حق رونوشت و توضیح این که یک ناظر محتوای خانگی بوده که نگران انتقام یا تلافی کنش‌هایتان هستید و نیاز به صندوق پستی برای برداشتن نشانی خانه‌تان از دید عموم دارید.
-        dmca_email: می‌تواند همان رایانامهٔ استفاده شده برای «نشانی رایانامه برای اطّلاعیه‌های حقوقی» در بالا باشد.
-        domain: شناسایی منحصر به فرد سرویس آنلاینی که ارائه می کنید.
-        jurisdiction: کشوری را که هر کسی که صورتحسابها را پرداخت می کند در آن زندگی می کند، فهرست کنید. اگر یک شرکت یا نهاد دیگری است، کشوری که در آن ثبت شده است و شهر، منطقه، قلمرو یا ایالت در صورت لزوم فهرست کنید.
-        min_age: نباید کم‌تر از کمینهٔ زمان لازم از سوی قوانین حقوقیتان باشد.
       user:
         chosen_languages: اگر انتخاب کنید، تنها نوشته‌هایی که به زبان‌های برگزیدهٔ شما نوشته شده‌اند در فهرست نوشته‌های عمومی نشان داده می‌شوند
         role: نقش کنترل می کند که کاربر چه مجوزهایی دارد.
@@ -159,7 +144,7 @@ fa:
         url: جایی که رویدادها فرستاده می‌شوند
     labels:
       account:
-        attribution_domains: وب‌سایت‌هایی که اجازه دارند به شما اعتبار بدهند
+        attribution_domains_as_text: پابگاه‌های وبی که اجازهٔ اعتبار دهی به شما را دارند
         discoverable: مشخص کردن مشخصات و فرسته‌ها در الگوریتم‌های اکتشاف
         fields:
           name: برچسب
@@ -175,8 +160,8 @@ fa:
         text: متن از پیش آماده‌شده
         title: عنوان
       admin_account_action:
-        include_statuses: قرار دادن فرسته‌های گزارش شده در رایانامه
-        send_email_notification: آگاهی کاربر به ازای هر رایانامه
+        include_statuses: فرسته‌های گزارش‌شده را در ایمیل بگنجان
+        send_email_notification: اطلاع‌رسانی به کاربر از راه ایمیل
         text: هشدار موردی
         type: کنش
         types:
@@ -205,7 +190,7 @@ fa:
         current_password: گذرواژه کنونی
         data: داده‌ها
         display_name: نمایش به نام
-        email: نشانی رایانامه
+        email: نشانی ایمیل
         expires_in: تاریخ انقضا
         fields: اطلاعات تکمیلی نمایه
         header: تصویر زمینه
@@ -236,10 +221,8 @@ fa:
         setting_display_media_show_all: نمایش همه
         setting_expand_spoilers: همیشه فرسته‌هایی را که هشدار محتوا دارند کامل نشان بده
         setting_hide_network: نهفتن شبکهٔ ارتباطی
-        setting_missing_alt_text_modal: نمایش گفتگوی تایید قبل از ارسال رسانه بدون متن جایگزین
         setting_reduce_motion: کاستن از حرکت در پویانمایی‌ها
         setting_system_font_ui: به‌کاربردن قلم پیش‌فرض سیستم
-        setting_system_scrollbars_ui: از نوار اسکرول پیش فرض سیستم استفاده کنید
         setting_theme: تم سایت
         setting_trends: نشان‌دادن موضوعات پرطرفدار روز
         setting_unfollow_modal: نمایش پیغام تأیید پیش از لغو پیگیری دیگران
@@ -250,7 +233,7 @@ fa:
         title: عنوان
         type: نوع درون‌ریزی
         username: نام کاربری
-        username_or_email: نام کاربری یا رایانامه
+        username_or_email: نام کاربری یا ایمیل
         whole_word: تطابق واژهٔ کامل
       email_domain_block:
         with_dns_records: شامل رکوردهای MX و‌IPهای دامنه
@@ -309,12 +292,12 @@ fa:
       notification_emails:
         appeal: شخصی به تصمیم مدیر اعتراض کرد
         digest: فرستادن رایانامه‌های خلاصه
-        favourite: شخصی فرسته‌تان را برگزید
-        follow: شخصی پیتان گرفت
-        follow_request: شخصی خواست پیتان بگیرد
-        mention: شخصی از شما نام برد
-        pending_account: حساب تازهٔ نیازمند بررسی
-        reblog: شخصی فرسته‌تان را تقویت کرد
+        favourite: وقتی کسی نوشتهٔ شما را پسندید ایمیل بفرست
+        follow: وقتی کسی پیگیر شما شد ایمیل بفرست
+        follow_request: وقتی کسی درخواست پیگیری کرد ایمیل بفرست
+        mention: وقتی کسی از شما نام برد ایمیل بفرست
+        pending_account: وقتی حساب تازه‌ای نیاز به بازبینی داشت ایمیل بفرست
+        reblog: وقتی کسی فرستهٔ شما را تقویت کرد ایمیل بفرست
         report: گزارش جدیدی فرستاده شد
         software_updates:
           all: آگاهی برای همهٔ به‌روز رسانی‌ها
@@ -334,24 +317,7 @@ fa:
         name: برچسب
         trendable: بگذارید که این برچسب در موضوعات پرطرفدار دیده شود
         usable: اجازه به فرسته‌ها برای استفتاده از این برچسب به صورت محلی
-      terms_of_service:
-        changelog: چه چیزی تغییر کرده است؟
-        effective_date: تاریخ اعمال
-        text: شرایط خدمات
-      terms_of_service_generator:
-        admin_email: نشانی رایانامه برای اطّلاعیه‌های حقوقی
-        arbitration_address: آدرس فیزیکی اعلامیه های داوری
-        arbitration_website: وب سایت ارسال اخطارهای داوری
-        choice_of_law: انتخاب قانون
-        dmca_address: آدرس فیزیکی اعلامیه‌های DMCA/حق نسخه‌برداری
-        dmca_email: نشانی رایانامه برای اطّلاعیه‌های حق رونوشت
-        domain: دامنه
-        jurisdiction: صلاحیت قانونی
-        min_age: کمینهٔ زمان
       user:
-        date_of_birth_1i: روز
-        date_of_birth_2i: ماه
-        date_of_birth_3i: سال
         role: نقش
         time_zone: منطقهٔ زمانی
       user_role:
diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml
index 5489780ec7..a9e77b6cac 100644
--- a/config/locales/simple_form.fi.yml
+++ b/config/locales/simple_form.fi.yml
@@ -3,7 +3,7 @@ fi:
   simple_form:
     hints:
       account:
-        attribution_domains: Yksi riviä kohti. Suojaa vääriltä tekijän nimeämisiltä.
+        attribution_domains_as_text: Yksi riviä kohti. Suojaa vääriltä tekijän nimeämisiltä.
         discoverable: Julkisia julkaisujasi ja profiiliasi voidaan pitää esillä tai suositella Mastodonin eri alueilla ja profiiliasi voidaan ehdottaa toisille käyttäjille.
         display_name: Koko nimesi tai lempinimesi.
         fields: Verkkosivustosi, pronominisi, ikäsi ja mitä ikinä haluatkaan ilmoittaa.
@@ -60,7 +60,6 @@ fi:
         setting_display_media_default: Piilota arkaluonteiseksi merkitty mediasisältö
         setting_display_media_hide_all: Piilota mediasisältö aina
         setting_display_media_show_all: Näytä mediasisältö aina
-        setting_system_scrollbars_ui: Koskee vain Safari- ja Chrome-pohjaisia työpöytäselaimia
         setting_use_blurhash: Liukuvärit perustuvat piilotettujen kuvien väreihin mutta sumentavat yksityiskohdat
         setting_use_pending_items: Piilota aikajanan päivitykset napsautuksen taakse syötteen automaattisen vierityksen sijaan
         username: Voit käyttää kirjaimia, numeroita ja alaviivoja
@@ -75,7 +74,6 @@ fi:
       filters:
         action: Valitse, mikä toiminto suoritetaan, kun julkaisu vastaa suodatinta
         actions:
-          blur: Piilota media varoituksen taakse piilottamatta itse tekstiä
           hide: Piilota suodatettu sisältö kokonaan, ikään kuin sitä ei olisi olemassa
           warn: Piilota suodatettu sisältö varoituksen taakse, jossa mainitaan suodattimen nimi
       form_admin_settings:
@@ -89,7 +87,6 @@ fi:
         favicon: WEBP, PNG, GIF tai JPG. Korvaa oletusarvoisen Mastodonin sivustokuvakkeen haluamallasi kuvakkeella.
         mascot: Korvaa kuvituksen edistyneessä selainkäyttöliittymässä.
         media_cache_retention_period: Etäkäyttäjien tekemien julkaisujen mediatiedostot ovat välimuistissa palvelimellasi. Kun kentän arvo on positiivinen, media poistuu, kun määritetty määrä päiviä on kulunut. Jos mediaa pyydetään sen poistamisen jälkeen, se ladataan uudelleen, jos lähdesisältö on vielä saatavilla. Koska linkkien esikatselun kyselyitä kolmansien osapuolien sivustoille on rajoitettu, on suositeltavaa asettaa tämä arvo vähintään 14 päivään, tai linkkien kortteja ei päivitetä pyynnöstä ennen tätä ajankohtaa.
-        min_age: Käyttäjiä pyydetään rekisteröitymisen aikana vahvistamaan syntymäpäivänsä
         peers_api_enabled: Luettelo verkkotunnuksista, jotka tämä palvelin on kohdannut fediversumissa. Se ei kerro, federoitko tietyn palvelimen kanssa, vaan että palvelimesi on ylipäätään tietoinen siitä. Tätä tietoa käytetään palveluissa, jotka keräävät tilastoja federoinnista yleisellä tasolla.
         profile_directory: Profiilihakemisto luetteloi kaikki käyttäjät, jotka ovat valinneet olla löydettävissä.
         require_invite_text: Kun rekisteröityminen vaatii manuaalisen hyväksynnän, tee ”Miksi haluat liittyä?” -tekstikentästä pakollinen vapaaehtoisen sijaan
@@ -132,22 +129,8 @@ fi:
         show_application: Voit silti aina nähdä, mistä sovelluksesta julkaisusi lähetettiin.
       tag:
         name: Voit esimerkiksi vaihtaa suur- ja pienaakkosten kesken helppolukuistaaksesi tekstiäsi
-      terms_of_service:
-        changelog: Voidaan jäsentää Markdown-syntaksilla.
-        effective_date: Sopiva aika on 10–30 päivää siitä, kun olet ilmoittanut ehdoista käyttäjillesi.
-        text: Voidaan jäsentää Markdown-syntaksilla.
-      terms_of_service_generator:
-        admin_email: Oikeudellisiin ilmoituksiin kuuluvat vastailmoitukset, oikeuden määräykset, poistopyynnöt ja lainvalvontaviranomaisten pyynnöt.
-        arbitration_address: Voi olla sama kuin edellä mainittu Fyysinen osoite tai ”N/A”, jos käytät sähköpostia.
-        arbitration_website: Voi olla verkkolomake tai ”N/A”, jos käytät sähköpostia.
-        dmca_address: Yhdysvaltalaisten operaattoreiden on käytettävä DMCA Designated Agent Directory -luetteloon rekisteröityä osoitetta. Postilokeroluettelo on saatavissa suoralla pyynnöllä, joten käytä DMCA Designated Agent Post Office Box Waiver Request -lomaketta lähettääksesi sähköpostia tekijänoikeusvirastolle ja kuvaile, että olet kotona toimiva sisältömoderaattori, joka pelkää kostoa tai rangaistusta toimistaan ja tarvitsee postilokeroa pitääkseen kotiosoitteensa poissa julkisuudesta.
-        dmca_email: Voi olla sama kuin edellä mainittu ”Sähköpostiosoite oikeudellisille ilmoituksille”.
-        domain: Tarjoamasi verkkopalvelun yksilöllinen tunniste.
-        jurisdiction: Mainitse valtio, jossa laskujen maksaja asuu. Jos kyseessä on yritys tai muu yhteisö, mainitse valtio, johon se on rekisteröity, ja tarvittaessa kaupunki, alue, territorio tai osavaltio.
-        min_age: Ei pidä alittaa lainkäyttöalueesi lakien vaatimaa vähimmäisikää.
       user:
         chosen_languages: Jos valitset kieliä oheisesta luettelosta, vain niidenkieliset julkaisut näkyvät sinulle julkisilla aikajanoilla
-        date_of_birth: Meidän on varmistettava, että olet vähintään %{age} vuotta vanha, jotta voit käyttää Mastodonia. Emme tallenna tätä.
         role: Rooli määrää, millaiset käyttöoikeudet käyttäjällä on.
       user_role:
         color: Väri, jota käytetään roolille kaikkialla käyttöliittymässä, RGB-heksadesimaalimuodossa
@@ -161,7 +144,7 @@ fi:
         url: Mihin tapahtumat lähetetään
     labels:
       account:
-        attribution_domains: Verkkosivustot, jotka voivat antaa sinulle tunnustusta
+        attribution_domains_as_text: Verkkosivustot, jotka voivat antaa sinulle tunnustusta
         discoverable: Pidä profiiliasi ja julkaisujasi esillä löytämisalgoritmeissa
         fields:
           name: Nimike
@@ -238,10 +221,8 @@ fi:
         setting_display_media_show_all: Näytä kaikki
         setting_expand_spoilers: Laajenna aina sisältövaroituksilla merkityt julkaisut
         setting_hide_network: Piilota verkostotietosi
-        setting_missing_alt_text_modal: Näytä vahvistusikkuna ennen kuin julkaistaan mediaa ilman vaihtoehtoista tekstiä
         setting_reduce_motion: Vähennä animaatioiden liikettä
         setting_system_font_ui: Käytä järjestelmän oletusfonttia
-        setting_system_scrollbars_ui: Käytä järjestelmän oletusarvoista vierityspalkkia
         setting_theme: Sivuston teema
         setting_trends: Näytä päivän trendit
         setting_unfollow_modal: Kysy vahvistusta ennen seuraamisen lopettamista
@@ -260,7 +241,6 @@ fi:
         name: Aihetunniste
       filters:
         actions:
-          blur: Piilota media varoittaen
           hide: Piilota kokonaan
           warn: Piilota varoittaen
       form_admin_settings:
@@ -274,7 +254,6 @@ fi:
         favicon: Sivustokuvake
         mascot: Mukautettu maskotti (vanhentunut)
         media_cache_retention_period: Mediasisällön välimuistin säilytysaika
-        min_age: Vähimmäisikävaatimus
         peers_api_enabled: Julkaise löydettyjen palvelinten luettelo ohjelmointirajapinnassa
         profile_directory: Ota profiilihakemisto käyttöön
         registrations_mode: Kuka voi rekisteröityä
@@ -338,23 +317,7 @@ fi:
         name: Aihetunniste
         trendable: Salli tämän aihetunnisteen näkyä trendeissä
         usable: Salli julkaisujen käyttää tätä aihetunnistetta paikallisesti
-      terms_of_service:
-        changelog: Mikä on muuttunut?
-        effective_date: Voimaantulopäivä
-        text: Käyttöehdot
-      terms_of_service_generator:
-        admin_email: Sähköpostiosoite oikeudellisille ilmoituksille
-        arbitration_address: Fyysinen osoite välimiesmenettelyn ilmoituksille
-        arbitration_website: Sähköpostiosoite välimiesmenettelyn ilmoituksille
-        dmca_address: Fyysinen osoite DMCA-/tekijänoikeusilmoituksille
-        dmca_email: Sähköpostiosoite DMCA-/tekijänoikeusilmoituksille
-        domain: Verkkotunnus
-        jurisdiction: Lainkäyttöalue
-        min_age: Vähimmäisikä
       user:
-        date_of_birth_1i: Päivä
-        date_of_birth_2i: Kuukausi
-        date_of_birth_3i: Vuosi
         role: Rooli
         time_zone: Aikavyöhyke
       user_role:
diff --git a/config/locales/simple_form.fo.yml b/config/locales/simple_form.fo.yml
index 32d066ed18..e45183df99 100644
--- a/config/locales/simple_form.fo.yml
+++ b/config/locales/simple_form.fo.yml
@@ -3,7 +3,7 @@ fo:
   simple_form:
     hints:
       account:
-        attribution_domains: Eitt á hvørja reglu. Tað verjir fyri skeivum tilsipingum.
+        attribution_domains_as_text: Eitt á hvørja reglu. Tað verjir fyri skeivum tilsipingum.
         discoverable: Tínir almennu postar og tín vangi kunnu vera drigin fram og viðmæld ymsa staðni í Mastodon og vangin hjá tær kann vera viðmæltur øðrum brúkarum.
         display_name: Títt fulla navn og títt stuttliga navn.
         fields: Heimasíðan hjá tær, fornøvn, aldur ella hvat tú vil.
@@ -60,7 +60,6 @@ fo:
         setting_display_media_default: Fjal miðlafílur, sum eru merktar sum viðkvæmar
         setting_display_media_hide_all: Fjal altíð miðlafílur
         setting_display_media_show_all: Vís altíð miðlafílur
-        setting_system_scrollbars_ui: Er einans viðkomandi fyri skriviborðskagar grundaðir á Safari og Chrome
         setting_use_blurhash: Gradientar eru grundaðir á litirnar av fjaldu myndunum, men grugga allar smálutir
         setting_use_pending_items: Fjal tíðarlinjudagføringar aftan fyri eitt klikk heldur enn at skrulla tilføringina sjálvvirkandi
         username: Tú kanst brúka bókstavir, tøl og botnstrikur
@@ -75,7 +74,6 @@ fo:
       filters:
         action: Vel, hvat skal henda, tá eitt uppslag svarar til filtrið
         actions:
-          blur: Fjal miðlar aftanfyri eina ávaring, uttan at fjala sjálvan tekstin
           hide: Fjal filtreraða innihaldið fullkomiliga, ber seg at sum at tað ikki fanst
           warn: Fjal filtreraða innihaldið aftan fyri eina ávaring, sum nevnir heitið á filtrinum
       form_admin_settings:
@@ -89,7 +87,6 @@ fo:
         favicon: WEBP, PNG, GIF ella JPG. Býtir vanligu Mastodon fav-ikonina um við eina ser-ikon.
         mascot: Skúgvar til viks myndprýðingina í framkomna vev-markamótinum.
         media_cache_retention_period: Miðlafílur frá postum, sum fjarbrúkarar hava gjørt, verða goymdir á tínum ambætara. Tá hetta er sett til eitt virði størri enn 0, so verða miðlafílurnar strikaðar eftir ásetta talið av døgum. Um miðladátur verða umbidnar eftir at tær eru strikaðar, verða tær tiknar innaftur á ambætaran, um keldutilfarið enn er tøkt. Vegna avmarkingar á hvussu ofta undanvísingarkort til leinki spyrja triðjapartsstøð, so verður mælt til at seta hetta virðið til í minsta lagi 14 dagar. Annars verða umbønir um dagføringar av undanvísingarkortum til leinki ikki gjørdar áðrenn hetta.
-        min_age: Brúkarar verða spurdir um at vátta teirra føðingardag, tá tey skráseta seg
         peers_api_enabled: Ein listi við navnaøkjum, sum hesin ambætarin er komin framat í fediversinum. Ongar dátur eru tiknar við her um tú er sameind/ur við ein givnan ambætara, einans at tín ambætari veit um hann. Hetta verður brúkt av tænastum, sum gera hagtøl um sameining yvirhøvur.
         profile_directory: Vangaskráin listar allar brúkarar, sum hava valt at kunna uppdagast.
         require_invite_text: Tá tilmeldingar krevja serskilda góðkenning, set so "Hví vil tú vera við?" tekstateigin til at vera kravdan heldur enn valfrían
@@ -132,23 +129,8 @@ fo:
         show_application: Óansæð, so er altíð møguligt hjá tær at síggja, hvør app postaði tín post.
       tag:
         name: Tú kanst einans broyta millum stórar og smáar stavir, til dømis fyri at gera tað meira lesiligt
-      terms_of_service:
-        changelog: Kunnu vera uppbygdar við Markdown syntaksi.
-        effective_date: Ein rímulig tíðarfreist kann vera alt millum 10 og 30 dagar frá degnum, tú gevur boð til brúkararnar hjá tær.
-        text: Kunnu vera uppbygdar við Markdown syntaksi.
-      terms_of_service_generator:
-        admin_email: Løgfrøðisligar fráboðanir fata um rættarligar mótfráboðanir, úrskurðir, áheitanir um at taka niður og løgfrøðisligar áheitanir.
-        arbitration_address: Kann vera tann sami sum fysiski bústaðurin omanfyri ella "N/A", um teldupostur verður brúktur.
-        arbitration_website: Kann vera ein vevformularur ella "N/A", um teldupostur verður brúktur.
-        choice_of_law: Býur, øki, landspartur ella statur, hvørs innanhýsis lógir skulu stýra øllum krøvum.
-        dmca_address: Fyristøðufólk og -feløg í USA brúka bústaðin, ið er skrásettur í DMCA Designated Agent Directory. Ein postsmoguskráseting er tøk, um biðið verður um hana beinleiðis. Brúka DMCA Designated Agent Post Office Box Waiver Request fyri at senda teldubræv til Copyright Office og greið frá, at tú er ein kjakleiðari, sum virkar heimanifrá, og at tú er bangin fyri hevnd ella afturløning fyri tí, tú ger, og at tú hevur tørv á at brúka eina postsmogu fyri at fjala heimabústaðin fyri almenninginum.
-        dmca_email: Kann vera sami teldupoststaður, sum er brúktur til "Teldupoststaður fyri løgfrøðisligar fráboðanir" omanfyri.
-        domain: Makaleys eyðmerking av nettænastuni, sum tú veitir.
-        jurisdiction: Lista landið, har sum tann, ið rindar rokningarnar, livir. Er tað eitt felag ella ein onnur eind, lista landið, har tað er skrásett, umframt býin, økið, umveldið ella statin, alt eftir hvat er hóskandi.
-        min_age: Eigur ikki at vera undir lægsta aldri, sum lógirnar í tínum rættarøki krevja.
       user:
         chosen_languages: Tá hetta er valt, verða einans postar í valdum málum vístir á almennum tíðarlinjum
-        date_of_birth: Vit mugu tryggja okkum, at tú er í minsta lagi %{age} ár fyri at brúka Mastodon. Vit goyma ikki hesar upplýsingar.
         role: Leikluturin stýrir hvørji rættindi, brúkarin hevur.
       user_role:
         color: Litur, sum leikluturin hevur í øllum brúkaramarkamótinum, sum RGB og upplýst sum sekstandatal
@@ -162,7 +144,7 @@ fo:
         url: Hvar hendingar verða sendar til
     labels:
       account:
-        attribution_domains: Heimasíður, sum hava loyvi at sipa til tín
+        attribution_domains_as_text: Heimasíður, sum hava loyvi at sipa til tín
         discoverable: Framheva vanga og postar í uppdagingar-algoritmum
         fields:
           name: Spjaldur
@@ -239,10 +221,8 @@ fo:
         setting_display_media_show_all: Vís alt
         setting_expand_spoilers: Víðka altíð postar, sum eru merktir við innihaldsávaringum
         setting_hide_network: Fjal sosiala grafin hjá tær
-        setting_missing_alt_text_modal: Spyr um góðkenning áðrenn miðlar uttan alternativan tekst verða postaðir
         setting_reduce_motion: Minka um rørslu í teknimyndum
         setting_system_font_ui: Brúka vanliga skriftaslagið hjá skipanini
-        setting_system_scrollbars_ui: Brúka vanliga skrullibjálkan hjá skipanini
         setting_theme: Uppsetingareyðkenni
         setting_trends: Vís dagsins rák
         setting_unfollow_modal: Vís váttanarmynd, áðrenn tú gevst at fylgja onkrum
@@ -261,7 +241,6 @@ fo:
         name: Tvíkrossur
       filters:
         actions:
-          blur: Fjal miðlar við eini ávaring
           hide: Fjal fullkomiliga
           warn: Fjal við eini ávaring
       form_admin_settings:
@@ -275,7 +254,6 @@ fo:
         favicon: Favikon
         mascot: Serskildur maskottur (arvur)
         media_cache_retention_period: Tíðarskeið, har miðlagoymslur verða varðveittar
-        min_age: Aldursmark
         peers_api_enabled: Kunnger lista við uppdagaðum ambætarum í API'num
         profile_directory: Ger vangaskrá virkna
         registrations_mode: Hvør kann tilmelda seg
@@ -339,24 +317,7 @@ fo:
         name: Tvíkrossur
         trendable: Loyv hesum frámerki at síggjast undir rákum
         usable: Loyv postum at brúka hetta frámerki lokalt
-      terms_of_service:
-        changelog: Hvat er broytt?
-        effective_date: Galdandi frá
-        text: Tænastutreytir
-      terms_of_service_generator:
-        admin_email: Teldupoststaður fyri løgfrøðisligar fráboðanir
-        arbitration_address: Fysisk adressa fyri gerðarrættarfráboðanir
-        arbitration_website: Heimasíða har gerðarrættarfráboðanir kunnu innlatast
-        choice_of_law: Val av lóg
-        dmca_address: Fysiskur bústaður fyri DMCA/copyright fráboðanir
-        dmca_email: Teldubústaður fyri DMCA/copyright fráboðanir
-        domain: Navnaøki
-        jurisdiction: Løgdømi
-        min_age: Lægsti aldur
       user:
-        date_of_birth_1i: Dagur
-        date_of_birth_2i: Mánaði
-        date_of_birth_3i: Ár
         role: Leiklutur
         time_zone: Tíðarsona
       user_role:
diff --git a/config/locales/simple_form.fr-CA.yml b/config/locales/simple_form.fr-CA.yml
index f917183f55..fe8043a64a 100644
--- a/config/locales/simple_form.fr-CA.yml
+++ b/config/locales/simple_form.fr-CA.yml
@@ -3,14 +3,14 @@ fr-CA:
   simple_form:
     hints:
       account:
-        attribution_domains: Un par ligne. Protège contre les fausses attributions.
+        attribution_domains_as_text: Un par ligne. Protège contre les fausses attributions.
         discoverable: Vos messages publics et votre profil peuvent être mis en avant ou recommandés dans diverses parties de Mastodon et votre profil peut être suggéré à d’autres utilisateurs.
         display_name: Votre nom complet ou votre nom cool.
         fields: Votre page d'accueil, pronoms, âge, tout ce que vous voulez.
         indexable: Vos messages publics peuvent apparaître dans les résultats de recherche sur Mastodon. Les personnes qui ont interagi avec vos messages peuvent les trouver dans une recherche quoi qu’il arrive.
         note: 'Vous pouvez @mentionner d’autres personnes ou des #hashtags.'
         show_collections: Les gens pourront voir les personnes que vous suivez ou qui vous suivent. Ceux que vous suivez verront que vous les suivez dans tous les cas.
-        unlocked: Les personnes pourront vous suivre sans en demander la permission. Décochez cette case si vous souhaitez examiner les demandes de suivi et choisir d'accepter ou de rejeter les nouveaux followers.
+        unlocked: Les gens pourront vous suivre sans demander d'approbation. Décochez cette option si vous voulez valider les demandes d'abonnement et choisir d'accepter ou de rejeter les nouveaux abonnés.
       account_alias:
         acct: Spécifiez l’identifiant@domaine du compte que vous souhaitez faire migrer
       account_migration:
@@ -60,7 +60,6 @@ fr-CA:
         setting_display_media_default: Masquer les médias marqués comme sensibles
         setting_display_media_hide_all: Toujours masquer les médias
         setting_display_media_show_all: Toujours afficher les médias
-        setting_system_scrollbars_ui: S'applique uniquement aux navigateurs basés sur Safari et Chrome
         setting_use_blurhash: Les dégradés sont basés sur les couleurs des images cachées mais n’en montrent pas les détails
         setting_use_pending_items: Cacher les mises à jour des fils d’actualités derrière un clic, au lieu de les afficher automatiquement
         username: Vous pouvez utiliser des lettres, des chiffres, et des tirets bas
@@ -75,7 +74,6 @@ fr-CA:
       filters:
         action: Choisir l'action à effectuer quand un message correspond au filtre
         actions:
-          blur: Cacher les médias derrière un avertissement, sans cacher le texte
           hide: Cacher complètement le contenu filtré, faire comme s'il n'existait pas
           warn: Cacher le contenu filtré derrière un avertissement mentionnant le nom du filtre
       form_admin_settings:
@@ -89,7 +87,6 @@ fr-CA:
         favicon: WEBP, PNG, GIF ou JPG. Remplace la favicon Mastodon par défaut avec une icône personnalisée.
         mascot: Remplace l'illustration dans l'interface Web avancée.
         media_cache_retention_period: Les fichiers médias des messages publiés par des utilisateurs distants sont mis en cache sur votre serveur. Lorsque cette valeur est positive, les médias sont supprimés au terme du nombre de jours spécifié. Si les données des médias sont demandées après leur suppression, elles seront téléchargées à nouveau, dans la mesure où le contenu source est toujours disponible. En raison des restrictions concernant la fréquence à laquelle les cartes de prévisualisation des liens interrogent des sites tiers, il est recommandé de fixer cette valeur à au moins 14 jours, faute de quoi les cartes de prévisualisation des liens ne seront pas mises à jour à la demande avant cette échéance.
-        min_age: Les utilisateurs seront invités à confirmer leur date de naissance lors de l'inscription
         peers_api_enabled: Une liste de noms de domaine que ce serveur a rencontrés dans le fédiverse. Aucune donnée indiquant si vous vous fédérez ou non avec un serveur particulier n'est incluse ici, seulement l'information que votre serveur connaît un autre serveur. Cette option est utilisée par les services qui collectent des statistiques sur la fédération en général.
         profile_directory: L'annuaire des profils répertorie tous les utilisateurs qui ont opté pour être découverts.
         require_invite_text: Lorsque les inscriptions nécessitent une approbation manuelle, rendre le texte de l’invitation "Pourquoi voulez-vous vous inscrire ?" obligatoire plutôt que facultatif
@@ -132,23 +129,8 @@ fr-CA:
         show_application: Vous pourrez toujours voir quelle application vous avez utilisé pour publier un message dans tous les cas.
       tag:
         name: Vous ne pouvez modifier que la casse des lettres, par exemple, pour le rendre plus lisible
-      terms_of_service:
-        changelog: Peut être structuré avec la syntaxe Markdown.
-        effective_date: Un délai raisonnable peut varier entre 10 et 30 jours à compter de la date à laquelle vous informez vos utilisateurs.
-        text: Peut être structuré avec la syntaxe Markdown.
-      terms_of_service_generator:
-        admin_email: Les avis juridiques comprennent les contre-avis, les ordonnances judiciaires, les demandes de retrait et les demandes des forces de l'ordre.
-        arbitration_address: Il peut s'agir de la même que l'adresse physique ci-dessus, ou « N/A » si vous utilisez une adresse e-mail.
-        arbitration_website: Il peut s'agir d'un formulaire web ou de « N/A » s'il s'agit d'un courrier électronique.
-        choice_of_law: Ville, région, territoire ou État dont le droit matériel interne régit toute réclamation.
-        dmca_address: Pour les opérateurs américains, utilisez l'adresse enregistrée dans le répertoire des agents désignés du DMCA Designated Agent Directory. Une boîte postale est disponible sur demande directe. Utilisez le formulaire de demande de dérogation pour l'utilisation d'une boîte postale par un agent désigné du Designated Agent Post Office Box Waiver Request pour envoyer un e-mail au Bureau du droit d'auteur (Copyright Office) et expliquer que vous êtes un modérateur de contenu à domicile qui craint des représailles ou une vengeance pour ses actions et que vous avez besoin d'utiliser une boîte postale afin de masquer votre adresse personnelle au public.
-        dmca_email: Il peut s'agir du même courriel que celui utilisé pour l'« Adresse électronique pour les avis juridiques » ci-dessus.
-        domain: Identification unique du service en ligne que vous offrez.
-        jurisdiction: Indiquez le pays dans lequel réside la personne qui paie les factures. S'il s'agit d'une entreprise ou d'une autre entité, indiquez le pays dans lequel elle est enregistrée, ainsi que la ville, la région, le territoire ou l'État, le cas échéant.
-        min_age: Ne doit pas être en dessous de l’âge minimum requis par les lois de votre juridiction.
       user:
         chosen_languages: Lorsque coché, seuls les messages dans les langues sélectionnées seront affichés sur les fils publics
-        date_of_birth: Nous devons nous assurer que vous avez au moins %{age} pour utiliser Mastodon. Nous ne conserverons pas ces informations.
         role: Le rôle définit quelles autorisations a l'utilisateur⋅rice.
       user_role:
         color: Couleur à attribuer au rôle dans l'interface, au format hexadécimal RVB
@@ -162,7 +144,7 @@ fr-CA:
         url: Là où les événements seront envoyés
     labels:
       account:
-        attribution_domains: Sites web autorisés à vous citer
+        attribution_domains_as_text: Sites web autorisés à vous citer
         discoverable: Autoriser des algorithmes de découverte à mettre en avant votre profil et vos messages
         fields:
           name: Étiquette
@@ -239,10 +221,8 @@ fr-CA:
         setting_display_media_show_all: Montrer tout
         setting_expand_spoilers: Toujours déplier les messages marqués d’un avertissement de contenu
         setting_hide_network: Cacher votre réseau
-        setting_missing_alt_text_modal: Afficher une fenêtre de confirmation avant de poster un média sans texte alternatif
         setting_reduce_motion: Réduire la vitesse des animations
         setting_system_font_ui: Utiliser la police par défaut du système
-        setting_system_scrollbars_ui: Utiliser la barre de défilement par défaut du système
         setting_theme: Thème du site
         setting_trends: Afficher les tendances du jour
         setting_unfollow_modal: Afficher une fenêtre de confirmation avant de vous désabonner d’un compte
@@ -261,7 +241,6 @@ fr-CA:
         name: Mot-clic
       filters:
         actions:
-          blur: Masquer les médias derrière un avertissement
           hide: Cacher complètement
           warn: Cacher derrière un avertissement
       form_admin_settings:
@@ -275,7 +254,6 @@ fr-CA:
         favicon: Favicon
         mascot: Mascotte personnalisée (héritée)
         media_cache_retention_period: Durée de rétention des médias dans le cache
-        min_age: Âge minimum requis
         peers_api_enabled: Publie la liste des serveurs découverts dans l'API
         profile_directory: Activer l’annuaire des profils
         registrations_mode: Qui peut s’inscrire
@@ -339,24 +317,7 @@ fr-CA:
         name: Mot-clic
         trendable: Autoriser ce hashtag à apparaitre dans les tendances
         usable: Autoriser les messages à utiliser ce hashtag localement
-      terms_of_service:
-        changelog: Nouveautés?
-        effective_date: Date effective
-        text: Conditions d'utilisation
-      terms_of_service_generator:
-        admin_email: Adresse électronique pour les notifications légales
-        arbitration_address: Adresse physique pour les notifications d'arbitrage
-        arbitration_website: Site Web pour soumettre les notifications d'arbitrage
-        choice_of_law: Choix de la loi
-        dmca_address: Adresse physique pour les avis DMCA/copyright
-        dmca_email: Adresse e-mail pour les avis DMCA/copyright
-        domain: Domaine
-        jurisdiction: Juridiction
-        min_age: Âge minimum
       user:
-        date_of_birth_1i: Jour
-        date_of_birth_2i: Mois
-        date_of_birth_3i: Année
         role: Rôle
         time_zone: Fuseau horaire
       user_role:
diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml
index a8d6bb86e0..f300f294e7 100644
--- a/config/locales/simple_form.fr.yml
+++ b/config/locales/simple_form.fr.yml
@@ -3,14 +3,14 @@ fr:
   simple_form:
     hints:
       account:
-        attribution_domains: Un par ligne. Protège contre les fausses attributions.
+        attribution_domains_as_text: Un par ligne. Protège contre les fausses attributions.
         discoverable: Vos messages publics et votre profil peuvent être mis en avant ou recommandés dans diverses parties de Mastodon et votre profil peut être suggéré à d’autres utilisateurs.
         display_name: Votre nom complet ou votre nom rigolo.
         fields: Votre page personnelle, vos pronoms, votre âge, ce que vous voulez.
         indexable: Vos messages publics peuvent apparaître dans les résultats de recherche sur Mastodon. Les personnes qui ont interagi avec vos messages peuvent les trouver dans une recherche quoi qu’il arrive.
         note: 'Vous pouvez @mentionner d’autres personnes ou des #hashtags.'
         show_collections: Les gens pourront voir les personnes que vous suivez ou qui vous suivent. Ceux que vous suivez verront que vous les suivez dans tous les cas.
-        unlocked: Les personnes pourront vous suivre sans en demander la permission. Décochez cette case si vous souhaitez examiner les demandes de suivi et choisir d'accepter ou de rejeter les nouveaux followers.
+        unlocked: Les gens pourront vous suivre sans demander d'approbation. Décochez cette option si vous voulez valider les demandes d'abonnement et choisir d'accepter ou de rejeter les nouveaux abonnés.
       account_alias:
         acct: Spécifiez l’identifiant@domaine du compte à partir duquel vous souhaitez migrer
       account_migration:
@@ -43,7 +43,7 @@ fr:
         avatar: WEBP, PNG, GIF ou JPG. Au plus %{size}. Sera réduit à %{dimensions}px
         bot: Signale aux autres que ce compte exécute principalement des actions automatisées et pourrait ne pas être surveillé
         context: Un ou plusieurs contextes où le filtre devrait s’appliquer
-        current_password: Par mesure de sécurité, veuillez saisir le mot de passe de ce compte
+        current_password: Par mesure de sécurité, veuillez saisir le mot de passe du compte actuel
         current_username: Pour confirmer, veuillez saisir l’identifiant de ce compte
         digest: Uniquement envoyé après une longue période d’inactivité en cas de messages personnels reçus pendant votre absence
         email: Vous recevrez un courriel de confirmation
@@ -60,7 +60,6 @@ fr:
         setting_display_media_default: Masquer les médias marqués comme sensibles
         setting_display_media_hide_all: Toujours masquer les médias
         setting_display_media_show_all: Toujours afficher les médias
-        setting_system_scrollbars_ui: S'applique uniquement aux navigateurs basés sur Safari et Chrome
         setting_use_blurhash: Les dégradés sont basés sur les couleurs des images cachées mais n’en montrent pas les détails
         setting_use_pending_items: Cacher les mises à jour des fils d’actualités derrière un clic, au lieu de les afficher automatiquement
         username: Vous pouvez utiliser des lettres, des chiffres, et des tirets bas
@@ -75,7 +74,6 @@ fr:
       filters:
         action: Choisir l'action à effectuer quand un message correspond au filtre
         actions:
-          blur: Cacher les médias derrière un avertissement, sans cacher le texte
           hide: Cacher complètement le contenu filtré, faire comme s'il n'existait pas
           warn: Cacher le contenu filtré derrière un avertissement mentionnant le nom du filtre
       form_admin_settings:
@@ -89,7 +87,6 @@ fr:
         favicon: WEBP, PNG, GIF ou JPG. Remplace la favicon Mastodon par défaut avec une icône personnalisée.
         mascot: Remplace l'illustration dans l'interface Web avancée.
         media_cache_retention_period: Les fichiers médias des messages publiés par des utilisateurs distants sont mis en cache sur votre serveur. Lorsque cette valeur est positive, les médias sont supprimés au terme du nombre de jours spécifié. Si les données des médias sont demandées après leur suppression, elles seront téléchargées à nouveau, dans la mesure où le contenu source est toujours disponible. En raison des restrictions concernant la fréquence à laquelle les cartes de prévisualisation des liens interrogent des sites tiers, il est recommandé de fixer cette valeur à au moins 14 jours, faute de quoi les cartes de prévisualisation des liens ne seront pas mises à jour à la demande avant cette échéance.
-        min_age: Les utilisateurs seront invités à confirmer leur date de naissance lors de l'inscription
         peers_api_enabled: Une liste de noms de domaine que ce serveur a rencontrés dans le fédiverse. Aucune donnée indiquant si vous vous fédérez ou non avec un serveur particulier n'est incluse ici, seulement l'information que votre serveur connaît un autre serveur. Cette option est utilisée par les services qui collectent des statistiques sur la fédération en général.
         profile_directory: L'annuaire des profils répertorie tous les comptes qui choisi d'être découvrables.
         require_invite_text: Lorsque les inscriptions nécessitent une approbation manuelle, rendre le texte de l’invitation "Pourquoi voulez-vous vous inscrire ?" obligatoire plutôt que facultatif
@@ -132,23 +129,8 @@ fr:
         show_application: Vous pourrez toujours voir quelle application vous avez utilisé pour publier un message dans tous les cas.
       tag:
         name: Vous ne pouvez modifier que la casse des lettres, par exemple, pour le rendre plus lisible
-      terms_of_service:
-        changelog: Peut être structuré avec la syntaxe Markdown.
-        effective_date: Un délai raisonnable peut varier entre 10 et 30 jours à compter de la date à laquelle vous informez vos utilisateurs.
-        text: Peut être structuré avec la syntaxe Markdown.
-      terms_of_service_generator:
-        admin_email: Les avis juridiques comprennent les contre-avis, les ordonnances judiciaires, les demandes de retrait et les demandes des forces de l'ordre.
-        arbitration_address: Il peut s'agir de la même que l'adresse physique ci-dessus, ou « N/A » si vous utilisez une adresse e-mail.
-        arbitration_website: Il peut s'agir d'un formulaire web ou de « N/A » s'il s'agit d'un courrier électronique.
-        choice_of_law: Ville, région, territoire ou État dont le droit matériel interne régit toute réclamation.
-        dmca_address: Pour les opérateurs américains, utilisez l'adresse enregistrée dans le répertoire des agents désignés du DMCA Designated Agent Directory. Une boîte postale est disponible sur demande directe. Utilisez le formulaire de demande de dérogation pour l'utilisation d'une boîte postale par un agent désigné du Designated Agent Post Office Box Waiver Request pour envoyer un e-mail au Bureau du droit d'auteur (Copyright Office) et expliquer que vous êtes un modérateur de contenu à domicile qui craint des représailles ou une vengeance pour ses actions et que vous avez besoin d'utiliser une boîte postale afin de masquer votre adresse personnelle au public.
-        dmca_email: Il peut s'agir du même courriel que celui utilisé pour l'« Adresse électronique pour les avis juridiques » ci-dessus.
-        domain: Identification unique du service en ligne que vous offrez.
-        jurisdiction: Indiquez le pays dans lequel réside la personne qui paie les factures. S'il s'agit d'une entreprise ou d'une autre entité, indiquez le pays dans lequel elle est enregistrée, ainsi que la ville, la région, le territoire ou l'État, le cas échéant.
-        min_age: Ne doit pas être en dessous de l’âge minimum requis par les lois de votre juridiction.
       user:
         chosen_languages: Lorsque coché, seuls les messages dans les langues sélectionnées seront affichés sur les fils publics
-        date_of_birth: Nous devons nous assurer que vous avez au moins %{age} pour utiliser Mastodon. Nous ne conserverons pas ces informations.
         role: Le rôle définit quelles autorisations a l'utilisateur⋅rice.
       user_role:
         color: Couleur à attribuer au rôle dans l'interface, au format hexadécimal RVB
@@ -162,7 +144,7 @@ fr:
         url: Là où les événements seront envoyés
     labels:
       account:
-        attribution_domains: Sites web autorisés à vous citer
+        attribution_domains_as_text: Sites web autorisés à vous citer
         discoverable: Autoriser des algorithmes de découverte à mettre en avant votre profil et vos messages
         fields:
           name: Étiquette
@@ -239,10 +221,8 @@ fr:
         setting_display_media_show_all: Montrer tout
         setting_expand_spoilers: Toujours déplier les messages marqués d’un avertissement de contenu
         setting_hide_network: Cacher votre réseau
-        setting_missing_alt_text_modal: Afficher une fenêtre de confirmation avant de poster un média sans texte alternatif
         setting_reduce_motion: Réduire la vitesse des animations
         setting_system_font_ui: Utiliser la police par défaut du système
-        setting_system_scrollbars_ui: Utiliser la barre de défilement par défaut du système
         setting_theme: Thème du site
         setting_trends: Afficher les tendances du jour
         setting_unfollow_modal: Demander confirmation avant de vous désabonner d’un compte
@@ -261,7 +241,6 @@ fr:
         name: Hashtag
       filters:
         actions:
-          blur: Masquer les médias derrière un avertissement
           hide: Cacher complètement
           warn: Cacher derrière un avertissement
       form_admin_settings:
@@ -275,7 +254,6 @@ fr:
         favicon: Favicon
         mascot: Mascotte personnalisée (héritée)
         media_cache_retention_period: Durée de rétention des médias dans le cache
-        min_age: Âge minimum requis
         peers_api_enabled: Publie la liste des serveurs découverts dans l'API
         profile_directory: Activer l’annuaire des profils
         registrations_mode: Qui peut s’inscrire
@@ -339,24 +317,7 @@ fr:
         name: Hashtag
         trendable: Autoriser ce hashtag à apparaitre dans les tendances
         usable: Autoriser les messages à utiliser ce hashtag localement
-      terms_of_service:
-        changelog: Nouveautés?
-        effective_date: Date effective
-        text: Conditions d'utilisation
-      terms_of_service_generator:
-        admin_email: Adresse électronique pour les notifications légales
-        arbitration_address: Adresse physique pour les notifications d'arbitrage
-        arbitration_website: Site Web pour soumettre les notifications d'arbitrage
-        choice_of_law: Choix de la loi
-        dmca_address: Adresse physique pour les avis DMCA/copyright
-        dmca_email: Adresse e-mail pour les avis DMCA/copyright
-        domain: Domaine
-        jurisdiction: Juridiction
-        min_age: Âge minimum
       user:
-        date_of_birth_1i: Jour
-        date_of_birth_2i: Mois
-        date_of_birth_3i: Année
         role: Rôle
         time_zone: Fuseau horaire
       user_role:
diff --git a/config/locales/simple_form.fy.yml b/config/locales/simple_form.fy.yml
index 56f02a1ff4..c840342db5 100644
--- a/config/locales/simple_form.fy.yml
+++ b/config/locales/simple_form.fy.yml
@@ -3,7 +3,7 @@ fy:
   simple_form:
     hints:
       account:
-        attribution_domains: Ien per rigel. Beskermet tsjin falske attribúsjes.
+        attribution_domains_as_text: Ien per rigel. Beskermet tsjin falske attribúsjes.
         discoverable: Jo iepenbiere berjochten kinne útljochte wurde op ferskate plakken binnen Mastodon en jo account kin oanrekommandearre wurde oan oare brûkers.
         display_name: Jo folsleine namme of in aardige bynamme.
         fields: Jo website, persoanlike foarnammewurden, leeftiid, alles wat jo mar kwyt wolle.
@@ -60,7 +60,6 @@ fy:
         setting_display_media_default: As gefoelich markearre media ferstopje
         setting_display_media_hide_all: Media altyd ferstopje
         setting_display_media_show_all: Media altyd toane
-        setting_system_scrollbars_ui: Allinnich fan tapassing op desktopbrowsers basearre op Safari en Chromium
         setting_use_blurhash: Dizige kleuroergongen binne basearre op de kleuren fan de ferstoppe media, wêrmei elk detail ferdwynt
         setting_use_pending_items: De tiidline wurdt bywurke troch op it oantal nije items te klikken, yn stee fan dat dizze automatysk bywurke wurdt
         username: Jo kinne letters, sifers en ûnderstreekjes brûke
@@ -130,14 +129,6 @@ fy:
         show_application: Jo kinne sels altyd sjen mei hokker app jo in berjocht pleatst hawwe.
       tag:
         name: Jo kinne elk wurd mei in haadletter begjinne, om sa bygelyks de tekst mear lêsber te meitsjen
-      terms_of_service:
-        changelog: Kin strukturearre wurde mei Markdown-syntaksis.
-        text: Kin strukturearre wurde mei Markdown-syntaksis.
-      terms_of_service_generator:
-        admin_email: Juridyske meidielingen binne û.o. tsjinoantekeningen, rjochterlike befelen, ferwiderfersiken en fersiken om wetshanthaving.
-        dmca_address: 'Brûk foar behearders yn de FS: it adres dat registrearre is yn de DMCA Designated Agent Directory. Op fersyk is der in postbuslist beskikber. Brûk it DMCA Designated Agent Post Office Box Waiver Request om it Copyright Office te e-mailen en te beskriuwen dat jo in thúsbasearre ynhâldsmoderator binne dy’t wraak of fergelding frezet foar jo dieden en in postbus brûke moat om jo húsadres út it publike domein te hâlden.'
-        domain: In unike identifikaasje fan de online tsjinst dy’t ferliend wurdt.
-        jurisdiction: Fermeld it lân wêr’t de persoan wennet dy’t de rekkeningen betellet. As it in bedriuw of in oare entiteit is, fermeld it lân wêr’t it opnommen is en de stêd, regio, grûngebiet of steat, foar safier fan tapassing.
       user:
         chosen_languages: Allinnich berjochten yn de selektearre talen wurde op de iepenbiere tiidline toand
         role: De rol bepaalt hokker rjochten in brûker hat.
@@ -153,7 +144,7 @@ fy:
         url: Wêr’t eveneminten nei ta stjoerd wurde
     labels:
       account:
-        attribution_domains: Websites dy’t jo wurdearring jaan meie
+        attribution_domains_as_text: Websites dy’t jo wurdearring jaan meie
         discoverable: Profyl en bydragen yn sykalgoritmen opnimme litte
         fields:
           name: Label
@@ -232,7 +223,6 @@ fy:
         setting_hide_network: Jo folgers en wa’t jo folget ferstopje
         setting_reduce_motion: Stadigere animaasjes
         setting_system_font_ui: Standertlettertype fan jo systeem brûke
-        setting_system_scrollbars_ui: Standert skowbalke fan jo systeem brûke
         setting_theme: Websitetema
         setting_trends: Trends fan hjoed toane
         setting_unfollow_modal: Freegje foar it ûntfolgjen fan ien in befêstiging
@@ -327,17 +317,6 @@ fy:
         name: Hashtag
         trendable: Goedkarre dat dizze hashtag ûnder trends te sjen is
         usable: Berjochten tastean dizze hashtag lokaal te brûken
-      terms_of_service:
-        changelog: Wat is wizige?
-        text: Gebrûksbetingsten
-      terms_of_service_generator:
-        admin_email: E-mailadres foar juridyske meldingen
-        arbitration_address: Strjitte foar arbitraazjemeldingen
-        arbitration_website: Website foar it yntsjinjen fan arbitraazjemeldingen
-        dmca_address: Strjitte foar DMCA/auteursrjocht-meidielingen
-        dmca_email: E-mailadres foar DMCA/auteursrjocht-meidielingen
-        domain: Domein
-        jurisdiction: Rjochtsgebiet
       user:
         role: Rol
         time_zone: Tiidsône
diff --git a/config/locales/simple_form.ga.yml b/config/locales/simple_form.ga.yml
index bd8dac2a36..f8257a9da9 100644
--- a/config/locales/simple_form.ga.yml
+++ b/config/locales/simple_form.ga.yml
@@ -3,7 +3,7 @@ ga:
   simple_form:
     hints:
       account:
-        attribution_domains: Ceann in aghaidh an líne. Cosnaíonn sé ó sannadh bréagach.
+        attribution_domains_as_text: Ceann in aghaidh an líne. Cosnaíonn sé ó sannadh bréagach.
         discoverable: Seans go mbeidh do phostálacha poiblí agus do phróifíl le feiceáil nó molta i réimsí éagsúla de Mastodon agus is féidir do phróifíl a mholadh d’úsáideoirí eile.
         display_name: D'ainm iomlán nó d'ainm spraoi.
         fields: Do leathanach baile, forainmneacha, aois, rud ar bith is mian leat.
@@ -60,7 +60,6 @@ ga:
         setting_display_media_default: Folaigh meáin atá marcáilte mar íogair
         setting_display_media_hide_all: Folaigh meáin i gcónaí
         setting_display_media_show_all: Taispeáin meáin i gcónaí
-        setting_system_scrollbars_ui: Ní bhaineann sé ach le brabhsálaithe deisce bunaithe ar Safari agus Chrome
         setting_use_blurhash: Tá grádáin bunaithe ar dhathanna na n-amharcanna ceilte ach cuireann siad salach ar aon mhionsonraí
         setting_use_pending_items: Folaigh nuashonruithe amlíne taobh thiar de chlic seachas an fotha a scrollú go huathoibríoch
         username: Is féidir leat litreacha, uimhreacha, agus béim a úsáid
@@ -75,7 +74,6 @@ ga:
       filters:
         action: Roghnaigh an gníomh ba cheart a dhéanamh nuair a mheaitseálann postáil an scagaire
         actions:
-          blur: Folaigh na meáin taobh thiar de rabhadh, gan an téacs féin a cheilt
           hide: Cuir an t-ábhar scagtha i bhfolach go hiomlán, ag iompar amhail is nach raibh sé ann
           warn: Folaigh an t-ábhar scagtha taobh thiar de rabhadh a luann teideal an scagaire
       form_admin_settings:
@@ -89,7 +87,6 @@ ga:
         favicon: WEBP, PNG, GIF nó JPG. Sáraíonn sé an favicon Mastodon réamhshocraithe le deilbhín saincheaptha.
         mascot: Sáraíonn sé an léaráid san ardchomhéadan gréasáin.
         media_cache_retention_period: Déantar comhaid meán ó phoist a dhéanann cianúsáideoirí a thaisceadh ar do fhreastalaí. Nuair a bheidh luach dearfach socraithe, scriosfar na meáin tar éis an líon sonraithe laethanta. Má iarrtar na sonraí meán tar éis é a scriosadh, déanfar é a ath-íoslódáil, má tá an t-ábhar foinse fós ar fáil. Mar gheall ar shrianta ar cé chomh minic is atá cártaí réamhamhairc ag vótaíocht do shuíomhanna tríú páirtí, moltar an luach seo a shocrú go 14 lá ar a laghad, nó ní dhéanfar cártaí réamhamhairc naisc a nuashonrú ar éileamh roimh an am sin.
-        min_age: Iarrfar ar úsáideoirí a ndáta breithe a dhearbhú le linn clárúcháin
         peers_api_enabled: Liosta de na hainmneacha fearainn ar tháinig an freastalaí seo orthu sa choinbhleacht. Níl aon sonraí san áireamh anseo faoi cé acu an ndéanann tú cónascadh le freastalaí ar leith, díreach go bhfuil a fhios ag do fhreastalaí faoi. Úsáideann seirbhísí a bhailíonn staitisticí ar chónaidhm go ginearálta é seo.
         profile_directory: Liostaíonn an t-eolaire próifíle na húsáideoirí go léir a roghnaigh isteach le bheith in-aimsithe.
         require_invite_text: Nuair a bhíonn faomhadh láimhe ag teastáil le haghaidh clárúcháin, déan an "Cén fáth ar mhaith leat a bheith páirteach?" ionchur téacs éigeantach seachas roghnach
@@ -132,23 +129,8 @@ ga:
         show_application: Beidh tú in ann a fheiceáil i gcónaí cén aip a d’fhoilsigh do phostáil beag beann ar.
       tag:
         name: Ní féidir leat ach cásáil na litreacha a athrú, mar shampla, chun é a dhéanamh níos inléite
-      terms_of_service:
-        changelog: Is féidir é a struchtúrú le comhréir Markdown.
-        effective_date: Féadfaidh tréimhse ama réasúnta a bheith idir 10 agus 30 lá ón dáta a chuireann tú in iúl do d'úsáideoirí.
-        text: Is féidir é a struchtúrú le comhréir Markdown.
-      terms_of_service_generator:
-        admin_email: Áirítear le fógraí dlí frithfhógraí, orduithe cúirte, iarratais éirí anuas, agus iarratais forghníomhaithe dlí.
-        arbitration_address: D’fhéadfadh sé a bheith mar an gcéanna leis an seoladh fisiceach thuas, nó “N/B” má tá ríomhphost in úsáid agat.
-        arbitration_website: D’fhéadfadh gur foirm ghréasáin í, nó “N/B” má tá ríomhphost in úsáid agat.
-        choice_of_law: Cathair, réigiún, críoch nó stát a rialóidh a dhlíthe inmheánacha substaintiúla aon éileamh agus gach éileamh.
-        dmca_address: Maidir le hoibreoirí SAM, bain úsáid as an seoladh atá cláraithe in Eolaire Gníomhairí Ainmnithe DMCA. A P.O. Tá liostú boscaí ar fáil ach é a iarraidh go díreach, bain úsáid as Iarratas Tarscaoilte Bosca Gníomhaire Ainmnithe DMCA chun ríomhphost a chur chuig an Oifig Cóipchirt agus déan cur síos gur modhnóir ábhar baile thú a bhfuil eagla ort díoltas nó aisíocaíocht a fháil mar gheall ar do ghníomhartha agus a dteastaíonn uait úsáid a bhaint as P.O. Bosca chun do sheoladh baile a bhaint den radharc poiblí.
-        dmca_email: Is féidir gurb é an ríomhphost céanna é a úsáidtear le haghaidh “Seoladh ríomhphoist le haghaidh fógraí dlí” thuas.
-        domain: Aitheantas uathúil na seirbhíse ar líne atá á cur ar fáil agat.
-        jurisdiction: Liostaigh an tír ina bhfuil cónaí ar an té a íocann na billí. Más cuideachta nó aonán eile é, liostaigh an tír ina bhfuil sé corpraithe, agus an chathair, an réigiún, an chríoch nó an stát mar is cuí.
-        min_age: Níor chóir go mbeidís faoi bhun na haoise íosta a éilíonn dlíthe do dhlínse.
       user:
         chosen_languages: Nuair a dhéantar iad a sheiceáil, ní thaispeánfar ach postálacha i dteangacha roghnaithe in amlínte poiblí
-        date_of_birth: Ní mór dúinn a chinntiú go bhfuil tú ar a laghad %{age} chun Mastodon a úsáid. Ní stórálfaimid é seo.
         role: Rialaíonn an ról na ceadanna atá ag an úsáideoir.
       user_role:
         color: Dath le húsáid don ról ar fud an Chomhéadain, mar RGB i bhformáid heicsidheachúlach
@@ -162,7 +144,7 @@ ga:
         url: An áit a seolfar imeachtaí chuig
     labels:
       account:
-        attribution_domains: Tá cead ag suíomhanna Gréasáin creidmheas a thabhairt duit
+        attribution_domains_as_text: Tá cead ag suíomhanna Gréasáin creidmheas a thabhairt duit
         discoverable: Próifíl gné agus postálacha in halgartaim fionnachtana
         fields:
           name: Lipéad
@@ -239,10 +221,8 @@ ga:
         setting_display_media_show_all: Taispeáin uile
         setting_expand_spoilers: Méadaigh postálacha atá marcáilte le rabhaidh inneachair i gcónaí
         setting_hide_network: Folaigh do ghraf sóisialta
-        setting_missing_alt_text_modal: Taispeáin dialóg deimhnithe sula bpostálann tú meán gan alt téacs
         setting_reduce_motion: Laghdú ar an tairiscint i beochan
         setting_system_font_ui: Úsáid cló réamhshocraithe an chórais
-        setting_system_scrollbars_ui: Bain úsáid as scrollbharra réamhshocraithe an chórais
         setting_theme: Téama suímh
         setting_trends: Taispeáin treochtaí an lae inniu
         setting_unfollow_modal: Taispeáin dialóg deimhnithe sula ndíleanfaidh tú duine éigin
@@ -261,7 +241,6 @@ ga:
         name: Haischlib
       filters:
         actions:
-          blur: Folaigh na meáin le rabhadh
           hide: Cuir i bhfolach go hiomlán
           warn: Cuir i bhfolach le rabhadh
       form_admin_settings:
@@ -275,7 +254,6 @@ ga:
         favicon: Favicon
         mascot: Mascóg saincheaptha (oidhreacht)
         media_cache_retention_period: Tréimhse choinneála taisce meán
-        min_age: Riachtanas aoise íosta
         peers_api_enabled: Foilsigh liosta de na freastalaithe aimsithe san API
         profile_directory: Cumasaigh eolaire próifíle
         registrations_mode: Cé atá in ann clárú
@@ -339,24 +317,7 @@ ga:
         name: Haischlib
         trendable: Lig don haischlib seo a bheith le feiceáil faoi threochtaí
         usable: Lig do phostálacha an hashchlib seo a úsáid go háitiúil
-      terms_of_service:
-        changelog: Cad atá athraithe?
-        effective_date: Dáta éifeachtach
-        text: Téarmaí Seirbhíse
-      terms_of_service_generator:
-        admin_email: Seoladh ríomhphoist le haghaidh fógraí dlí
-        arbitration_address: Seoladh fisiciúil le haghaidh fógraí eadrána
-        arbitration_website: An láithreán gréasáin chun fógraí eadrána a chur isteach
-        choice_of_law: Rogha an Dlí
-        dmca_address: Seoladh fisiciúil le haghaidh fógraí DMCA/cóipchirt
-        dmca_email: Seoladh ríomhphoist le haghaidh fógraí DMCA/cóipchirt
-        domain: Fearann
-        jurisdiction: Dlínse dhlíthiúil
-        min_age: Aois íosta
       user:
-        date_of_birth_1i: Lá
-        date_of_birth_2i: Mí
-        date_of_birth_3i: Bliain
         role: Ról
         time_zone: Crios ama
       user_role:
diff --git a/config/locales/simple_form.gd.yml b/config/locales/simple_form.gd.yml
index bb05294cfb..af1f06a316 100644
--- a/config/locales/simple_form.gd.yml
+++ b/config/locales/simple_form.gd.yml
@@ -3,13 +3,14 @@ gd:
   simple_form:
     hints:
       account:
+        attribution_domains_as_text: Loidhne fa leth do gach fear. Dìonaidh seo o iomraidhean meallta.
         discoverable: Dh’fhaoidte gun dèid na postaichean poblach ’s a’ phròifil agad a bhrosnachadh no a mholadh ann an caochladh roinnean de Mhastodon agus gun dèid a’ phròifil agad a mholadh do chàch.
         display_name: D’ ainm slàn no spòrsail.
         fields: An duilleag-dhachaigh agad, roimhearan, aois, rud sam bith a thogras tu.
         indexable: Faodaidh na postaichean poblach agad a nochdadh am measg toraidhean luirg air Mastodon. ’S urrainn dhan fheadhainn a rinn eadar-ghabhail leis na postaichean agad lorg annta air a h-uile dòigh.
         note: "’S urrainn dhut @iomradh a thoirt air càch no air #tagaicheanHais."
         show_collections: "’S urrainn do chàch na dàimhean leantainn agad a rùrachadh. Chì daoine a leanas tu gu bheil thu ’gan leantainn air a h-uile dòigh."
-        unlocked: "’S urrainnear do leantainn gun aonta iarraidh. Thoir a’ chromag air falbh ma tha thu airson lèirmheas a dhèanamh air iarrtasan leantainn agus cur romhad an gabh thu ri luchd-leantainn ùr no an diùilt thu iad."
+        unlocked: "’S urrainnear do leantainn gun aontachadh iarraidh. Thoir a’ chromag far a’ bhogsa nam bu mhiann leat lèirmheas a dhèanamh air na h-iarrtasan leantainn agus taghadh an aontaich thu ri neach-leantainn ùr no an dhiùlt thu iad."
       account_alias:
         acct: Sònraich ainm-cleachdaiche@àrainn dhen chunntas a tha thu airson imrich uaithe
       account_migration:
@@ -143,6 +144,7 @@ gd:
         url: Far an dèid na tachartasan a chur
     labels:
       account:
+        attribution_domains_as_text: Na làraichean-lìn a dh’fhaodas iomradh a thoirt ort
         discoverable: Brosnaich a’ phròifil is postaichean agad sna h-algairimean rùrachaidh
         fields:
           name: Leubail
diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml
index 3773123f17..0c256e1b68 100644
--- a/config/locales/simple_form.gl.yml
+++ b/config/locales/simple_form.gl.yml
@@ -3,7 +3,7 @@ gl:
   simple_form:
     hints:
       account:
-        attribution_domains: Un por liña. Como protección para falsas atribucións.
+        attribution_domains_as_text: Un por liña. Protéxete das atribucións falsas.
         discoverable: As túas publicacións públicas e perfil poden mostrarse ou recomendarse en varias zonas de Mastodon e o teu perfil ser suxerido a outras usuarias.
         display_name: O teu nome completo ou un nome divertido.
         fields: Páxina web, pronome, idade, o que ti queiras.
@@ -60,7 +60,6 @@ gl:
         setting_display_media_default: Ocultar medios marcados como sensibles
         setting_display_media_hide_all: Ocultar sempre os medios
         setting_display_media_show_all: Mostrar sempre os medios marcados como sensibles
-        setting_system_scrollbars_ui: Aplícase só en navegadores de escritorio baseados en Safari e Chrome
         setting_use_blurhash: Os gradientes toman as cores da imaxe oculta pero esvaecendo tódolos detalles
         setting_use_pending_items: Agochar actualizacións da cronoloxía tras un click no lugar de desprazar automáticamente os comentarios
         username: Podes usar letras, números e trazos baixos
@@ -75,7 +74,6 @@ gl:
       filters:
         action: Elixe a acción a realizar cando algunha publicación coincida co filtro
         actions:
-          blur: Ocultar multimedia detrás dun aviso, sen ocultar o texto que se inclúa
           hide: Agochar todo o contido filtrado, facer coma se non existise
           warn: Agochar o contido filtrado tras un aviso que conteña o nome do filtro
       form_admin_settings:
@@ -89,7 +87,6 @@ gl:
         favicon: WEBP, PNG, GIF ou JPG. Sobrescribe a icona de favoritos de Mastodon por defecto cunha icona personalizada.
         mascot: Sobrescribe a ilustración na interface web avanzada.
         media_cache_retention_period: Os ficheiros multimedia de publicacións de usuarias remotas están almacenados no teu servidor. Ao establecer un valor positivo, o multimedia vaise eliminar despois do número de días establecido. Se o multimedia fose requerido após ser eliminado entón descargaríase outra vez, se aínda está dispoñible na orixe. Debido a restricións sobre a frecuencia en que o servizo de vista previa trae recursos de terceiras partes, é recomendable establecer este valor polo menos en 14 días, ou as tarxetas de vista previa non se actualizarán baixo demanda para casos anteriores a ese prazo.
-        min_age: Váiselle pedir ás usuarias que confirmen a súa data de nacemento cando creen a conta
         peers_api_enabled: Unha lista dos nomes de dominio que este servidor atopou no fediverso. Non se inclúen aquí datos acerca de se estás a federar con eles ou non, só que o teu servidor os recoñeceu. Ten utilidade para servizos que recollen estatísticas acerca da federación nun amplo senso.
         profile_directory: O directorio de perfís inclúe a tódalas usuarias que optaron por ser descubribles.
         require_invite_text: Cando os rexistros requiren aprobación manual, facer que o texto "Por que te queres rexistrar?" do convite sexa obrigatorio en lugar de optativo
@@ -132,23 +129,8 @@ gl:
         show_application: Independentemente, ti sempre poderás ver a app coa que publicaches a túa publicación.
       tag:
         name: Só podes cambiar maiús/minúsculas, por exemplo, mellorar a lexibilidade
-      terms_of_service:
-        changelog: Pode usarse sintaxe Markdown.
-        effective_date: Un prazo razonable podería estar entre 10 e 30 días desde que envías a notificación ás usuarias.
-        text: Pode usarse sintaxe Markdown.
-      terms_of_service_generator:
-        admin_email: Os avisos legais inclúen ordes xudiciais, solicitudes de retirada de contido e requerimentos do cumprimento da lei.
-        arbitration_address: Pode ser igual ao enderezo Físico de arriba, ou "N/A" se usas correo electrónico.
-        arbitration_website: Pode ser un formulario web, ou "N/A" se usas o correo electrónico.
-        choice_of_law: Cidade, rexión, territorio ou estado cuxas leis rexirán sobre as reclamacións e disputas.
-        dmca_address: Para operadores de USA, utiliza o mesmo enderezo rexistrado no DMCA Designated Agent Directory. Está dispoñible a opción de usar P.O. Box facendo a solicitude baixo a DMCA Designated Agent Post Office Box Waiver Request pedíndoa á Copyright Office e indicando que es un moderador de contidos na túa casa e temes ameazas ou violencia polas medidas que tomas e precisas usar un P.O. Box para que non sexa público o enderezo da túa casa.
-        dmca_email: Pode ser o mesmo correo usado para "Enderezo de correo para avisos legais".
-        domain: Identificador único do servizo en liña que proporcionas.
-        jurisdiction: Informa sobre o país onde vive quen paga as facturas. Se é unha empresa ou outra entidade, indica o país onde está establecida e é axeitado escribir a cidade, rexión, territorio ou estado.
-        min_age: Non debería ser inferior á idade mínima requerida polas leis da túa xurisdición.
       user:
         chosen_languages: Se ten marca, só as publicacións nos idiomas seleccionados serán mostrados en cronoloxías públicas
-        date_of_birth: Temos que ter certeza de que tes %{age} como mínimo para usar Mastodon. Non gardamos este dato.
         role: Os roles establecen os permisos que ten a usuaria.
       user_role:
         color: Cor que se usará para o rol a través da IU, como RGB en formato hex
@@ -162,7 +144,7 @@ gl:
         url: A onde se enviarán os eventos
     labels:
       account:
-        attribution_domains: Sitios web que dan fe sobre ti
+        attribution_domains_as_text: Sitios web que poden acreditarte
         discoverable: Perfil destacado e publicacións nos algoritmos de descubrimento
         fields:
           name: Etiqueta
@@ -208,7 +190,7 @@ gl:
         current_password: Contrasinal actual
         data: Datos
         display_name: Nome mostrado
-        email: Enderezo de correo
+        email: Enderezo de email
         expires_in: Caduca tras
         fields: Metadatos do perfil
         header: Cabeceira
@@ -239,10 +221,8 @@ gl:
         setting_display_media_show_all: Mostrar todo
         setting_expand_spoilers: Despregar sempre as publicacións marcadas con avisos de contido
         setting_hide_network: Non mostrar contactos
-        setting_missing_alt_text_modal: Mostrar mensaxe de confirmación antes de publicar multimedia sen texto descritivo
         setting_reduce_motion: Reducir o movemento nas animacións
         setting_system_font_ui: Utilizar a tipografía por defecto do sistema
-        setting_system_scrollbars_ui: Usar barras de desprazamento predeterminadas no sistema
         setting_theme: Decorado da instancia
         setting_trends: Mostrar as tendencias de hoxe
         setting_unfollow_modal: Solicitar confirmación antes de deixar de seguir alguén
@@ -261,7 +241,6 @@ gl:
         name: Cancelo
       filters:
         actions:
-          blur: Ocultar multimedia cun aviso
           hide: Agochar completamente
           warn: Agochar tras un aviso
       form_admin_settings:
@@ -275,7 +254,6 @@ gl:
         favicon: Favicon
         mascot: Mascota propia (herdado)
         media_cache_retention_period: Período de retención da caché multimedia
-        min_age: Idade mínima requerida
         peers_api_enabled: Publicar na API unha lista dos servidores descubertos
         profile_directory: Activar o directorio de perfís
         registrations_mode: Quen se pode rexistrar
@@ -339,24 +317,7 @@ gl:
         name: Cancelo
         trendable: Permitir que este cancelo apareza en tendencias
         usable: Permitir que as publicacións usen este cancelo localmente
-      terms_of_service:
-        changelog: Que cambios se fixeron?
-        effective_date: Data efectiva
-        text: Termos do Servizo
-      terms_of_service_generator:
-        admin_email: Enderezo de correo para avisos legais
-        arbitration_address: Enderezo físico para avisos de arbitraxe
-        arbitration_website: Páxina web para enviar avisos de arbitraxe
-        choice_of_law: Xurisdición
-        dmca_address: Enderezo físico para avisos sobre DMCA/copyright
-        dmca_email: Enderezo de correo electrónico para avisos sobre DMCA/copyright
-        domain: Dominio
-        jurisdiction: Xurisdición legal
-        min_age: Idade mínima
       user:
-        date_of_birth_1i: Día
-        date_of_birth_2i: Mes
-        date_of_birth_3i: Ano
         role: Rol
         time_zone: Fuso horario
       user_role:
diff --git a/config/locales/simple_form.he.yml b/config/locales/simple_form.he.yml
index 9c01b91638..d8d6af5e99 100644
--- a/config/locales/simple_form.he.yml
+++ b/config/locales/simple_form.he.yml
@@ -3,7 +3,7 @@ he:
   simple_form:
     hints:
       account:
-        attribution_domains: אחד בכל שורה. יגן מפני יחוסים מטעים.
+        attribution_domains_as_text: אחד בכל שורה. יגן מפני יחוס מטעה.
         discoverable: הפוסטים והפרופיל שלך עשויים להיות מוצגים או מומלצים באזורים שונים באתר וייתכן שהפרופיל שלך יוצע למשתמשים אחרים.
         display_name: שמך המלא או שם הכיף שלך.
         fields: עמוד הבית שלך, לשון הפנייה, גיל, וכל מידע אחר לפי העדפתך האישית.
@@ -60,7 +60,6 @@ he:
         setting_display_media_default: הסתרת מדיה המסומנת כרגישה
         setting_display_media_hide_all: הסתר מדיה תמיד
         setting_display_media_show_all: גלה מדיה תמיד
-        setting_system_scrollbars_ui: נוגע רק לגבי דפדפני דסקטופ מבוססים ספארי וכרום
         setting_use_blurhash: הגראדיינטים מבוססים על תוכן התמונה המוסתרת, אבל מסתירים את כל הפרטים
         setting_use_pending_items: הסתר עדכוני פיד מאחורי קליק במקום לגלול את הפיד אוטומטית
         username: ניתן להשתמש בספרות, אותיות לטיניות ומקף תחתון
@@ -75,7 +74,6 @@ he:
       filters:
         action: בחרו איזו פעולה לבצע כאשר הודעה מתאימה למסנן
         actions:
-          blur: החבאת וידאו ותמונות מאחורי אזהרה, ללא החבאת המלל עצמו
           hide: הסתרת התוכן המסונן, כאילו לא היה קיים
           warn: הסתרת התוכן המסונן מאחורי אזהרה עם כותרת המסנן
       form_admin_settings:
@@ -89,7 +87,6 @@ he:
         favicon: WEBP, PNG, GIF או JPG. גובר על "פאבאייקון" ברירת המחדל ומחליף אותו באייקון נבחר בדפדפן.
         mascot: בחירת ציור למנשק הווב המתקדם.
         media_cache_retention_period: קבצי מדיה מהודעות שהגיעו משרתים רחוקים נשמרות על השרת שלך. כאשר יבחר פה מספר חיובי, המדיה תמחק לאחר מספר ימים כמצוין. אם המידע יבוקש שוב לאחר שנמחק, הוא יורד מחדש, אם המידע עדיין זמין בצד הרחוק. עקב מגבלות על תכיפות שליפת כרטיסי קדימון מאתרים מרוחקים, מומלץ לכוון את הערך ל־14 יום לפחות, או שכרטיסי קדימונים לא יעודכנו לפי דרישה לפני חלוף חלון הזמן הזה.
-        min_age: משתמשיםות יתבקשו לאשר את תאריך הלידה בתהליך ההרשמה
         peers_api_enabled: רשימת השרתים ששרת זה פגש בפדיוורס. לא כולל מידע לגבי קשר ישיר עם שרת נתון, אלא רק שידוע לשרת זה על קיומו. מידע זה משמש שירותים האוספים סטטיסטיקות כלליות על הפדרציה.
         profile_directory: ספריית הפרופילים מציגה ברשימה את כל המשתמשים שביקשו להיות ניתנים לגילוי.
         require_invite_text: כאשר הרשמות דורשות אישור ידני, הפיכת טקסט ה"מדוע את/ה רוצה להצטרף" להכרחי במקום אופציונלי
@@ -132,23 +129,8 @@ he:
         show_application: תמיד ניתן לראות איזו אפליקציה פרסמה את הפוסט שלך בכל מקרה.
       tag:
         name: ניתן רק להחליף בין אותיות קטנות וגדולות, למשל כדי לשפר את הקריאות
-      terms_of_service:
-        changelog: ניתן לעצב בעזרת תחביר מארקדאון.
-        effective_date: לוח זמנים סביר יהיה בין 10-30 ימים מיום ההודעה למשתמשיםות שלך.
-        text: ניתן לעצב בעזרת תחביר מארקדאון.
-      terms_of_service_generator:
-        admin_email: מזכרים חוקיים כוללים הוראות בימ"ש, בקשות הסרה, תשובות להתראות ובקשות ציות לאכיפה.
-        arbitration_address: יכול להיות כתובת פיזית זהה לעיל, או “N/A” אם משתמשים בדואל.
-        arbitration_website: יכול להיות טופס בדפדפן או “N/A” אם משתמשים בדואל.
-        choice_of_law: עיר, מחוז, טריטוריה או מדינה שחוקיה חלים על כל התביעות והתלונות.
-        dmca_address: For US operators, use the address registered in the DMCA Designated Agent Directory. A P.O. Box listing is available upon direct request, use the DMCA Designated Agent Post Office Box Waiver Request to email the Copyright Office and describe that you are a home-based content moderator who fears revenge or retribution for your actions and need to use a P.O. Box to remove your home address from public view.
-        dmca_email: ניתן להשתמש באותה כתובת הדואל שעבור "כתובת דואל להודעות משפטיות" לעיל.
-        domain: זיהוי ייחודי של השירות המקוון שאתה מספק.
-        jurisdiction: פרט את המדינה שבה גר משלם החשבונות. אם מדובר בחברה או גוף אחר, פרטו את המדינה שבה הוקם התאגיד, העיר, האזור, או המדינה בהתאם לצורך.
-        min_age: על הערך להיות לפחות בגיל המינימלי הדרוש בחוק באיזור השיפוט שלך.
       user:
         chosen_languages: אם פעיל, רק הודעות בשפות הנבחרות יוצגו לפידים הפומביים
-        date_of_birth: עלינו לוודא שגילך לפחות %{age} כדי להשתמש במסטודון. המידע לא ישמר בשרת שלנו.
         role: התפקיד שולט על אילו הרשאות יש למשתמש.
       user_role:
         color: צבע לתפקיד בממשק המשתמש, כ RGB בפורמט הקסדצימלי
@@ -162,7 +144,7 @@ he:
         url: היעד שאליו יישלחו אירועים
     labels:
       account:
-        attribution_domains: אתרים המורשים לייחס אליך מאמרים
+        attribution_domains_as_text: אתרים המורשים לייחס אליך מאמרים
         discoverable: הצג משתמש ופוסטים בעמוד התגליות
         fields:
           name: תווית
@@ -239,10 +221,8 @@ he:
         setting_display_media_show_all: להציג הכול
         setting_expand_spoilers: להרחיב תמיד הודעות מסומנות באזהרת תוכן
         setting_hide_network: להחביא את הגרף החברתי שלך
-        setting_missing_alt_text_modal: הצג כרטיס אישור לפני פרסום קובץ גרפי ללא תיאור מילולי
         setting_reduce_motion: הפחתת תנועה בהנפשות
         setting_system_font_ui: להשתמש בגופן ברירת המחדל של המערכת
-        setting_system_scrollbars_ui: להשתמש בפס הגלילה שהוא ברירת המחדל של המערכת
         setting_theme: ערכת העיצוב של האתר
         setting_trends: הצגת הנושאים החמים
         setting_unfollow_modal: להראות תיבת אישור לפני הפסקת מעקב אחרי אחרים
@@ -261,7 +241,6 @@ he:
         name: תגית
       filters:
         actions:
-          blur: הסתרת מדיה עם אזהרה
           hide: הסתרה כוללת
           warn: הסתרה עם אזהרה
       form_admin_settings:
@@ -275,7 +254,6 @@ he:
         favicon: סמל מועדפים (Favicon)
         mascot: סמל השרת (ישן)
         media_cache_retention_period: תקופת שמירת מטמון מדיה
-        min_age: דרישת גיל מינימלי
         peers_api_enabled: פרסם רשימה של שרתים שנתגלו באמצעות ה-API
         profile_directory: הפעלת ספריית פרופילים
         registrations_mode: מי יכולים לפתוח חשבון
@@ -339,24 +317,7 @@ he:
         name: תגית
         trendable: הרשה/י לתגית זו להופיע תחת נושאים חמים
         usable: הרשה/י להודעות להכיל תגית זו באופן מקומי
-      terms_of_service:
-        changelog: מה נשתנה?
-        effective_date: תאריך תחולה
-        text: תנאי השירות
-      terms_of_service_generator:
-        admin_email: כתובת דוא"ל להודעות משפטיות
-        arbitration_address: כתובת פיזית להודעות גישור
-        arbitration_website: אתר רשת להגשת הודעות גישור
-        choice_of_law: בחירת אזור שיפוט
-        dmca_address: כתובת פיזית לשליחת התראות זכויות יוצרים/DMCA
-        dmca_email: כתובת דואל לשליחת התראות זכויות יוצרים/DMCA
-        domain: מתחם (דומיין)
-        jurisdiction: איזור השיפוט
-        min_age: גיל מינימלי
       user:
-        date_of_birth_1i: יום
-        date_of_birth_2i: חודש
-        date_of_birth_3i: שנה
         role: תפקיד
         time_zone: אזור זמן
       user_role:
diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml
index adcc3d5789..60922b06b5 100644
--- a/config/locales/simple_form.hu.yml
+++ b/config/locales/simple_form.hu.yml
@@ -3,7 +3,7 @@ hu:
   simple_form:
     hints:
       account:
-        attribution_domains: Soronként egy. Megvéd a hamis forrásmegjelölésektől.
+        attribution_domains_as_text: Megvéd a hamis forrásmegjelölésektől.
         discoverable: A nyilvános bejegyzéseid és a profilod kiemelhető vagy ajánlható a Mastodon különböző területein, a profilod más felhasználóknak is javasolható.
         display_name: Teljes neved vagy vicces neved.
         fields: Weboldalad, megszólításaid, korod, bármi, amit szeretnél.
@@ -60,7 +60,6 @@ hu:
         setting_display_media_default: Kényes tartalomnak jelölt média elrejtése
         setting_display_media_hide_all: Média elrejtése mindig
         setting_display_media_show_all: Média megjelenítése mindig
-        setting_system_scrollbars_ui: Csak Chrome és Safari alapú asztali böngészőkre vonatkozik
         setting_use_blurhash: A kihomályosítás az eredeti képből történik, de minden részletet elrejt
         setting_use_pending_items: Idővonal frissítése csak kattintásra automatikus görgetés helyett
         username: Betűk, számok és alávonások használhatók
@@ -75,7 +74,6 @@ hu:
       filters:
         action: A végrehajtandó műveletet, ha a bejegyzés megfelel a szűrőnek
         actions:
-          blur: Média elrejtése figyelmeztetéssel, a szöveg elrejtése nélkül
           hide: A szűrt tartalom teljes elrejtése, mintha nem is létezne
           warn: A szűrt tartalom a szűrő címét említő figyelmeztetés mögé rejtése
       form_admin_settings:
@@ -89,7 +87,6 @@ hu:
         favicon: WEBP, PNG, GIF vagy JPG. Az alapértelmezett Mastodon favicont felülírja egy egyéni ikonnal.
         mascot: Felülbírálja a speciális webes felületen található illusztrációt.
         media_cache_retention_period: A távoli felhasználók bejegyzéseinek médiatartalmait a kiszolgálód gyorsítótárazza. Ha pozitív értékre állítják, ezek a médiatartalmak a megadott számú nap után törölve lesznek. Ha a médiát újra lekérik, miután törlődött, újra le fogjuk tölteni, ha az eredeti még elérhető. A hivatkozások előnézeti kártyáinak harmadik fél weboldalai felé történő hivatkozásaira alkalmazott megkötései miatt javasolt, hogy ezt az értéket legalább 14 napra állítsuk, ellenkező esetben a hivatkozások előnézeti kártyái szükség esetén nem fognak tudni frissülni ezen idő előtt.
-        min_age: A felhasználók a regisztráció során arra lesznek kérve, hogy erősítsek meg a születési dátumukat
         peers_api_enabled: Azon domainek listája, melyekkel ez a kiszolgáló találkozott a fediverzumban. Nem csatolunk adatot arról, hogy föderált kapcsolatban vagy-e az adott kiszolgálóval, csak arról, hogy a kiszolgálód tud a másikról. Ezt olyan szolgáltatások használják, melyek általában a föderációról készítenek statisztikákat.
         profile_directory: A profilok jegyzéke minden olyan felhasználót felsorol, akik engedélyezték a felfedezhetőségüket.
         require_invite_text: Ha a regisztrációhoz manuális jóváhagyásra van szükség, akkor a „Miért akarsz csatlakozni?” válasz kitöltése legyen kötelező, és ne opcionális
@@ -132,23 +129,8 @@ hu:
         show_application: Ettől függetlenül mindig láthatod, melyik alkalmazás tette közzé a bejegyzésedet.
       tag:
         name: Csak a kis/nagybetűséget változtathatod meg, pl. hogy olvashatóbb legyen
-      terms_of_service:
-        changelog: Markdown szintaxissal adható meg.
-        effective_date: Egy elfogadható időablak a felhasználók értesítésétől számított 10 és 30 nap között bármi lehet.
-        text: Markdown szintaxissal adható meg.
-      terms_of_service_generator:
-        admin_email: A jogi közlemények közé tartoznak az ellenkeresetek, a bírósági végzések, az eltávolítási kérelmek és a bűnüldöző szervek kérelmei is.
-        arbitration_address: Lehet a fent használt valós cím, vagy e-mail használata esetén „N/A”.
-        arbitration_website: Lehet webes űrlap, vagy e-mail használata esetén „N/A”.
-        choice_of_law: Város, régió, terület vagy állam, amely jogszabályai irányadóak az összes követelés esetén.
-        dmca_address: Amerikai üzemeltető esetén használja a DMCA Designated Agent Directory jegyzékében regisztrált címet. Közvetlen kérésre postafiók-jegyzék is rendelkezésre áll, használja a DMCA Designated Agent Directory postafiókról való lemondás iránti kérelmét a Copyright Office-nak küldött e-mailben, és írja le, hogy Ön egy otthoni tartalommoderátor, aki bosszútól vagy megtorlástól tart a tevékenységi miatt, és ezért egy postafiókra van szüksége, hogy ne legyen nyilvános az otthoni címe.
-        dmca_email: Megegyezhet a fenti „E-mail-cím a jogi nyilatkozatok benyújtásához” mezővel.
-        domain: A nyújtott online szolgáltatás egyedi azonosítója.
-        jurisdiction: Adja meg az országot, ahol a számlafizető él. Ha ez egy vállalat vagy más szervezet, adja meg az országot, ahol bejegyezték, valamint adott esetben a várost, régiót, területet vagy államot.
-        min_age: Nem lehet a joghatóság által meghatározott minimális kor alatt.
       user:
         chosen_languages: Ha aktív, csak a kiválasztott nyelvű bejegyzések jelennek majd meg a nyilvános idővonalon
-        date_of_birth: Legalább %{age} évesnek kell lenniük, hogy használhassák a Mastodont. Ezt nem tároljuk.
         role: A szerep szabályozza, hogy a felhasználó milyen jogosultságokkal rendelkezik.
       user_role:
         color: A szerephez használandó szín mindenhol a felhasználói felületen, hexa RGB formátumban
@@ -162,7 +144,7 @@ hu:
         url: Ahová az eseményket küldjük
     labels:
       account:
-        attribution_domains: Weboldalak, melyek szerzőként tüntethetnek fel
+        attribution_domains_as_text: Weboldalak, melyek szerzőként tüntethetnek fel
         discoverable: Profil és bejegyzések szerepeltetése a felfedezési algoritmusokban
         fields:
           name: Címke
@@ -239,10 +221,8 @@ hu:
         setting_display_media_show_all: Mindent mutat
         setting_expand_spoilers: Tartalmi figyelmeztetéssel ellátott bejegyzések automatikus kinyitása
         setting_hide_network: Hálózatod elrejtése
-        setting_missing_alt_text_modal: Megerősítési párbeszédablak megjelenítése a helyettesítő szöveg nélküli média közzététele előtt
         setting_reduce_motion: Animációk mozgásának csökkentése
         setting_system_font_ui: Rendszer betűtípusának használata
-        setting_system_scrollbars_ui: Rendszer alapértelmezett görgetősávjának használata
         setting_theme: Megjelenítési sablon
         setting_trends: Mai trend mutatása
         setting_unfollow_modal: Megerősítés kérése mielőtt abbahagyod valaki követését
@@ -261,7 +241,6 @@ hu:
         name: Hashtag
       filters:
         actions:
-          blur: Média elrejtése figyelmeztetéssel
           hide: Teljes elrejtés
           warn: Elrejtés figyelmeztetéssel
       form_admin_settings:
@@ -275,7 +254,6 @@ hu:
         favicon: Könyvjelzőikon
         mascot: Egyéni kabala (örökölt)
         media_cache_retention_period: Média-gyorsítótár megtartási időszaka
-        min_age: Minimális korhatár
         peers_api_enabled: Felfedezett kiszolgálók listájának közzététele az API-ban
         profile_directory: Profiladatbázis engedélyezése
         registrations_mode: Ki regisztrálhat
@@ -339,24 +317,7 @@ hu:
         name: Hashtag
         trendable: A hashtag megjelenhet a felkapottak között
         usable: A helyi bejegyzések használhatják ezt a hashtaget
-      terms_of_service:
-        changelog: Mi változott?
-        effective_date: A hatálybalépés dátuma
-        text: Felhasználási feltételek
-      terms_of_service_generator:
-        admin_email: E-mail-cím a jogi nyilatkozatok benyújtásához
-        arbitration_address: Fizikai cím a békéltető testületi nyilatkozatok benyújtásához
-        arbitration_website: Weboldal a békéltető testületi nyilatkozatok benyújtásához
-        choice_of_law: Joghatóság
-        dmca_address: Fizikai cím a szerzői jogi nyilatkozatokhoz
-        dmca_email: E-mail-cím a szerzői jogi nyilatkozatokhoz
-        domain: Domain
-        jurisdiction: Joghatóság
-        min_age: Minimális életkor
       user:
-        date_of_birth_1i: Nap
-        date_of_birth_2i: Hónap
-        date_of_birth_3i: Év
         role: Szerep
         time_zone: Időzóna
       user_role:
diff --git a/config/locales/simple_form.ia.yml b/config/locales/simple_form.ia.yml
index cb9b2a3da5..e7da9b1051 100644
--- a/config/locales/simple_form.ia.yml
+++ b/config/locales/simple_form.ia.yml
@@ -3,7 +3,7 @@ ia:
   simple_form:
     hints:
       account:
-        attribution_domains: Un per linea. Protege contra false attributiones.
+        attribution_domains_as_text: Un per linea. Protege contra false attributiones.
         discoverable: Tu messages public e tu profilo pote esser mittite in evidentia o recommendate in varie areas de Mastodon e tu profilo pote esser suggerite a altere usatores.
         display_name: Tu prenomine e nomine de familia o tu pseudonymo.
         fields: Tu pagina principal, pronomines, etate, tote lo que tu vole.
@@ -60,7 +60,6 @@ ia:
         setting_display_media_default: Celar le medios marcate como sensibile
         setting_display_media_hide_all: Sempre celar contento multimedial
         setting_display_media_show_all: Sempre monstrar contento multimedial
-        setting_system_scrollbars_ui: Se applica solmente al navigatores de scriptorio basate sur Safari e Chrome
         setting_use_blurhash: Le imagines degradate se basa sur le colores del visuales celate, ma illos offusca tote le detalios
         setting_use_pending_items: Requirer un clic pro monstrar nove messages in vice de rolar automaticamente le fluxo
         username: Tu pote usar litteras, numeros e tractos de sublineamento
@@ -130,15 +129,6 @@ ia:
         show_application: In omne caso, tu potera sempre vider qual app ha publicate tu message.
       tag:
         name: Tu pote solmente cambiar le litteras inter majusculas e minusculas, per exemplo, pro render lo plus legibile
-      terms_of_service:
-        changelog: Pote esser structurate con le syntaxe Markdown.
-        text: Pote esser structurate con le syntaxe Markdown.
-      terms_of_service_generator:
-        admin_email: Le avisos juridic include le contra-avisos, ordinantias judiciari, demandas de retiro e demandas de application del lege.
-        choice_of_law: Citate, region, territorio o stato cuje leges substantive interne governa omne disputa juridic.
-        dmca_address: Pro operatores in le SUA, usa le adresse registrate in le Directorio de Agentes Designate pro le DMCA (DMCA Designated Agent Directory). Un adresse de cassa postal es disponibile per requesta directe; usa le Requesta de Exemption de Cassa Postal pro Agentes Designate del DMCA (DMCA Designated Agent Post Office Box Waiver Request) pro inviar un message electronic al Officio del Derecto de Autor (Copyright Office) e describer que tu es un moderator de contento que travalia de casa e qui time vengiantias o represalias pro tu actiones, necessitante le uso un cassa postal pro remover tu adresse personal del vista public.
-        domain: Identification unic del servicio in linea que tu forni.
-        jurisdiction: Indica le pais ubi vive le persona qui paga le facturas. Si se tracta de un interprisa o altere organisation, indica le pais ubi illo es incorporate, e le citate, region, territorio o stato del maniera appropriate pro le pais.
       user:
         chosen_languages: Si marcate, solmente le messages in le linguas seligite apparera in chronologias public
         role: Le rolo controla qual permissos le usator ha.
@@ -154,7 +144,7 @@ ia:
         url: A ubi le eventos essera inviate
     labels:
       account:
-        attribution_domains: Sitos web autorisate a accreditar te
+        attribution_domains_as_text: Sitos web autorisate a accreditar te
         discoverable: Evidentiar le profilo e messages in le algorithmos de discoperta
         fields:
           name: Etiquetta
@@ -231,10 +221,8 @@ ia:
         setting_display_media_show_all: Monstrar toto
         setting_expand_spoilers: Sempre expander messages marcate con avisos de contento
         setting_hide_network: Cela tu rete social
-        setting_missing_alt_text_modal: Monstrar un dialogo de confirmation ante de publicar multimedia sin texto alternative
         setting_reduce_motion: Reducer movimento in animationes
         setting_system_font_ui: Usar typo de litteras predefinite del systema
-        setting_system_scrollbars_ui: Usar le barra de rolamento predefinite del systema
         setting_theme: Thema de sito
         setting_trends: Monstrar le tendentias de hodie
         setting_unfollow_modal: Monstrar dialogo de confirmation ante cessar de sequer alcuno
@@ -329,18 +317,6 @@ ia:
         name: Hashtag
         trendable: Permitter a iste hashtag de sub tendentias
         usable: Permitter al messages usar iste hashtag localmente
-      terms_of_service:
-        changelog: Que ha cambiate?
-        text: Conditiones de servicio
-      terms_of_service_generator:
-        admin_email: Adresse de e-mail pro avisos juridic
-        arbitration_address: Adresse physic pro avisos de arbitration
-        arbitration_website: Sito web pro submitter avisos de arbitration
-        choice_of_law: Election del lege applicabile
-        dmca_address: Adresse physic pro avisos DCMA/de derectos de autor
-        dmca_email: Adresse de e-mail pro avisos DCMA/de derectos de autor
-        domain: Dominio
-        jurisdiction: Jurisdiction
       user:
         role: Rolo
         time_zone: Fuso horari
diff --git a/config/locales/simple_form.ie.yml b/config/locales/simple_form.ie.yml
index 56e737172d..771e341616 100644
--- a/config/locales/simple_form.ie.yml
+++ b/config/locales/simple_form.ie.yml
@@ -9,6 +9,7 @@ ie:
         indexable: Tui public postas posse aparir in sercha-resultates sur Mastodon. E in omni casu, tis qui ha interactet con tui postas va posser serchar e trovar les.
         note: 'Tu posse @mentionar altri persones o #hashtags.'
         show_collections: Gente va posser navigar tra tui sequentes e sequitores. Gente quem tu seque va vider que tu seque les sin egarda.
+        unlocked: Persones va posser sequer te sin petir aprobation. Desselecte si tu vole manualmen tractar petitiones de sequer e decider ca acceptar o rejecter nov sequitores.
       account_alias:
         acct: Specificar li usatornomine@dominia del conto ex quel tu vole translocar
       account_migration:
diff --git a/config/locales/simple_form.io.yml b/config/locales/simple_form.io.yml
index 7da9441ba6..1df5008195 100644
--- a/config/locales/simple_form.io.yml
+++ b/config/locales/simple_form.io.yml
@@ -3,14 +3,13 @@ io:
   simple_form:
     hints:
       account:
-        attribution_domains: Un en singla lineo.
         discoverable: Vua publika posti e profilo povas remarkesar o rekomendesar en diferanta parti di Mastodon e vua profilo povas sugestesar ad altra uzanti.
         display_name: Vua tota nomo o vua gaya nomo.
         fields: Vua retsituo, pronomi, evo, irgo quan vu volas.
         indexable: Vua posta publika povos aparar en rezultaji di serchi che Mastodon. Personi qui interagis kun vua posti povos serchar oli irgakaze.
         note: 'Vu povas @mencionar altra personi o #hashtagi.'
         show_collections: Personi povos navigar tra vua sequati e sequanti. Personi quin vu sequas, vidos ke vu sequas li irgakaze.
-        unlocked: Personi povos sequar vu sen demandar aprobo.
+        unlocked: Personi povos sequar vu sen demandar aprobo. Deselektez se vu volas revuar sequadodemandi e selektez aceptar o refuzar nova sequati.
       account_alias:
         acct: Partikulare pozez uzantonomo@domeno di konto quon vua volas ektransferesar
       account_migration:
@@ -21,15 +20,15 @@ io:
       admin_account_action:
         include_statuses: Uzanto vidos quala posti quo kauzigas jero o averto
         send_email_notification: Uzanto ganos expliko pri quo eventas a olia konto
-        text_html: Nemustiga. Vu povas <a href="%{path}">adjuntar avertselektaji</a> por sparar tempo
+        text_html: Neobligata. Vu povas uzar postosintaxo. Vu povas <a href="%{path}">insertar avertofixiti</a> por sparar tempo
         type_html: Selektez ago per <strong>%{acct}</strong>
         types:
           disable: Preventez uzanto de uzar olia konto ma ne efacez o celez olia kontenaji.
           none: Uzez co por sendar averto a la uzanto sen eventigar irga altra ago.
-          sensitive: Igar omna audvidajaddonaji da ca uzanto markesar quale trublema.
+          sensitive: Koaktez omna mediiatachaji da ca uzanto markizesar quale sentoza.
           silence: Preventez la uzanto de povar postar per publika videbleso, celez lua posti e avizi de personi qui ne sequas lu. Co klozas omna raporto kontra ca konto.
           suspend: Preventez irga interago de o a ca konto e efacez lua kontenaji. Inversebla til 30 dii. Co klozas omna raporti kontra ca konto.
-        warning_preset_id: Nemustiga
+        warning_preset_id: Neobligata. Vu povas ankore insertar kustume texto a extremajo di fixito
       announcement:
         all_day: Kande kontrolesas, nur tempoporteodato montresos
         ends_at: Neobligata. Anunco automatika depublikigesos dum ta tempo
@@ -56,13 +55,12 @@ io:
         scopes: Quala API quon softwaro permisesas acesar. Se vu selektas alta skopo, vu ne mustas selektar individui.
         setting_aggregate_reblogs: Ne montrez nova repeti di posti qui ja repetesis recente (nur efektigas repeti recevata nove)
         setting_always_send_emails: Normale retpostoavizi ne sendesas kande vu aktiva uzas Mastodon
-        setting_default_sensitive: Trublema audvidaji originala celesas e povas descelesar per kliko
-        setting_display_media_default: Celez audvidaji qua markesis quale trublema
-        setting_display_media_hide_all: Omnatempe celas audvidaji
-        setting_display_media_show_all: Omnatempe montras audvidaji
-        setting_system_scrollbars_ui: Nur ye tablokomputilretumili qua bazita ye Safario e Kromeo
+        setting_default_sensitive: Sentoza medii originala celesas e povas revelesar per klikto
+        setting_display_media_default: Celez medii quo markizesis quale sentoza
+        setting_display_media_hide_all: Sempre celez medii
+        setting_display_media_show_all: Sempre montrez medii
         setting_use_blurhash: Inklini esas segun kolori di celesis vidaji ma kovras irga detali
-        setting_use_pending_items: Celez tempolinetildatigo dop kliko vice automatike ruligar la fluo
+        setting_use_pending_items: Celez tempolineonovi dop kliktar e ne automatike movigar niuzeto
         username: Vu darfas uzar literi, nombri, e sublinei
         whole_word: Kande klefvorto o fraz esas nur litera e nombra, ol nur aplikesos se ol parigesas la tota vorto
       domain_allow:
@@ -80,17 +78,17 @@ io:
       form_admin_settings:
         activity_api_enabled: Quanto de lokale publikigita posti, aktiva uzanti e nova registri, donita semanope
         app_icon: WEBP, PNG, GIF o JPG. Ol remplas la originala imajeto di softwaro sur poshaparati kun personaligita imajeto.
-        backups_retention_period: Uzanto povas igar arkivi di sua afishi por deskargar pose.
-        bootstrap_timeline_accounts: Ca konti adpinglesos ad super sequorekomendi di nova uzanti.
+        backups_retention_period: Uzanto povas facar arkivi di sua posti por deskargar pose. Se ol esas positiva nombro, ca arkivi automate efacesis de vua konserveyo pos la decidita quanto di dii.
+        bootstrap_timeline_accounts: Ca konti pinglagesos a super sequorekomendi di nova uzanti.
         closed_registrations_message: Montresas kande registradi klozesas
         content_cache_retention_period: Omna posti de altra servili efacesos (anke repeti e respondi) pos decidita quanto di dii, sen ye irga lokala uzantointerago kun ti posti. Privata mencioni inter uzanto de dessanta servili anke desganos e neposible riganesos.
         custom_css: Vu povas pozar kustumizita staili en retverso di Mastodon.
         favicon: WEBP, PNG, GIF o JPG. Ol remplas la originala imajeto di Mastodon kun personaligita imajeto.
         mascot: Remplas montreso en avanca retintervizajo.
-        media_cache_retention_period: Audvidajdosieri di afishi da deslokala uzanti retmemoresis sur vua servilo. Se audvidajdatumo demandesas pos ol forigesis, ol rideskargesos.
+        media_cache_retention_period: Medidoseri de posti quan posti da deslokala uzanti retummemoresis sur vua servilo. Se medidatumo demandesas pos ol efacesas, ol rideskargesos.
         peers_api_enabled: Listo di domeni quin ca servilo trovis en la fediverso. Nula informo inkluzesas hike pri ka vu federas kun partikulara servilo, nur ke vua servilo savas pri lo. Co es uzata da enti qui kolektas statistiki pri federeso generale.
-        profile_directory: La profiluyo listigas omna uzanti qua volunte esar deskovrebla.
-        require_invite_text: Kande registragi bezonas manuala aprobo, "Por quo vu volas adeskar?" textoenpoza esos mustiga
+        profile_directory: La profilcheflisto montras omna uzanti quo voluntale volas esar deskovrebla.
+        require_invite_text: Kande registradi bezonas manuala aprobo, ol kauzigas "Por quo vu volas juntas?" textoenpozo esar obliganta
         site_contact_email: Quale personi povas kontaktar vu por legala o suportquestioni.
         site_contact_username: Quale personi povas kontaktar vu en Mastodon.
         site_extended_description: Irga plusa informi quo forsan esar utila por vizitanti e uzanti. Povas strukturigesar per sintaxo di Markdown.
@@ -101,8 +99,8 @@ io:
         theme: Temo quo videsas da ekirita vizitanti e nova uzanti.
         thumbnail: Cirkum 2:1 imajo montresar kun informo di ca servilo.
         timeline_preview: Ekirita vizitanti videsos maxim recenta publika posti quo esas displonebla en la servilo.
-        trendable_by_default: Ignorez manuala kontrolar di populara enhavajo.
-        trends: Populari montras quala afishi, gretvorti e novaji populareskas en vua servilo.
+        trendable_by_default: Ignorez manuala kontrolar di tendencoza kontenajo. Singla kozi povas ankore efacesar de tendenci pose.
+        trends: Tendenci montras quala posti, hashtagi e niuzrakonti famozeskas en ca servilo.
         trends_as_landing_page: Montrez populara posti a uzanti neeniriti e vizitanti vice deskriptajo pri ca servilo. Bezonas ke populari es aktivita.
       form_challenge:
         current_password: Vu eniras sekura areo
@@ -111,7 +109,7 @@ io:
       invite_request:
         text: Co helpos ni kontrolar vua apliko
       ip_block:
-        comment: Nemustiga.
+        comment: Neobligata. Memorez por quo vu insertas ca regulo.
         expires_in: IP-Adresi esas finita moyeno, oli kelkafoye partigesas e frequenta uzesas da multa personi. Do, nedefinita IP-restrikti ne rekomendesas.
         ip: Tipez adreso di IPv4 o IPv6. Vu povas restrikar tota porteo per sintaxo CIDR. Sorgemez por ke vu ne klefklozas su!
         severities:
@@ -124,21 +122,12 @@ io:
         text: Deskriptez regulo o bezonaj por uzanti en ca servilo. Vu debas lasar lu esar korta e simpla
       sessions:
         otp: Enter the Two-factor code from your phone or use one of your recovery codes.
-        webauthn: Se ol es USB-klefo, certigar ke vu enpozas e se bezonesas, tushetez ol.
+        webauthn: Se ol esas klefo di USB, certigar ke vu insertas e se bezonesas, tushetez.
       settings:
         indexable: Vua profilpagino povas aparar en serchorezultaji che Google, Bing, e altri.
         show_application: Vu sempre povos vidar qua apliko publikigis vua posto irgakaze.
       tag:
         name: Vu povas nur chanjar literkaso, por exemplo, por kauzigar lu divenar plu lektebla
-      terms_of_service:
-        changelog: Povas strukturesita kun Markdown-sintaxo.
-        text: Povas strukturesita kun Markdown-sintaxo.
-      terms_of_service_generator:
-        admin_email: Legala notici inkluzas kontranotici, judicikomandi, forigdemandi e legaddemandi.
-        choice_of_law: Urbo, regiono, teritorio o stato qua havas ca interna inkluziva legi.
-        dmca_address: Por jeranti ek Usa, uzez la adreso en DMCA e demandez forigo di vua hemadreso.
-        domain: Unika identifikeso di enreta servo qua vu provizas.
-        jurisdiction: Listigez lando di paganto di fakturi.
       user:
         chosen_languages: Kande marketigesis, nur posti en selektesis lingui montresos en publika tempolinei
         role: La rolo donas certena permisi a la uzanto.
@@ -154,7 +143,6 @@ io:
         url: Ibe eventi sendesos
     labels:
       account:
-        attribution_domains: Reteyi kua permisesis agnoskar vu
         discoverable: Inkluzar profilo e posti en trovado-algoritmi
         fields:
           name: Etiketo
@@ -203,7 +191,7 @@ io:
         email: Retpost-adreso
         expires_in: Expiras pos
         fields: Profilmetadato
-        header: Fundimajo
+        header: Kapimajo
         honeypot: "%{label} (ne plenigez)"
         inbox_url: URL di relayomesajbuxo
         irreversible: Deslevez e ne celez
@@ -214,31 +202,29 @@ io:
         otp_attempt: Dufaktora identigilo
         password: Pasvorto
         phrase: Klefvorto o frazo
-        setting_advanced_layout: Ebligar altnivela retintervizajo
+        setting_advanced_layout: Aktivigez avancata retintervizajo
         setting_aggregate_reblogs: Grupigar repeti en tempolinei
         setting_always_send_emails: Sempre sendez retpostoavizi
-        setting_auto_play_gif: Autoplear anima GIFi
+        setting_auto_play_gif: Automate pleez animigita GIFi
         setting_boost_modal: Montrez konfirmdialogo ante repetar
         setting_default_language: Postolinguo
         setting_default_privacy: Videbleso di la mesaji
-        setting_default_sensitive: Omnatempe markas audvidaji quale trublema
+        setting_default_sensitive: Sempre markizez medii quale sentoza
         setting_delete_modal: Montrez konfirmdialogo ante efacar posto
-        setting_disable_hover_cards: Desebligar profilprevido dum paso
-        setting_disable_swiping: Desebligar fingromovi
-        setting_display_media: Audvidajmontrajo
+        setting_disable_hover_cards: Desaktivigez profilprevido dum klikpaso
+        setting_disable_swiping: Desaktivigez fingromovi
+        setting_display_media: Mediomontrajo
         setting_display_media_default: Originalo
         setting_display_media_hide_all: Celez omno
         setting_display_media_show_all: Montrez omno
         setting_expand_spoilers: Sempre expansigez posti quo markizesis kun kontenajaverti
         setting_hide_network: Celez vua sociala grafiko
-        setting_missing_alt_text_modal: Montrar konfirmdialogo ante afishar audvidaji sen alternative texto
-        setting_reduce_motion: Despluigar movo di animi
+        setting_reduce_motion: Diminutez moveso di animacii
         setting_system_font_ui: Uzez originala literfonto di sistemo
-        setting_system_scrollbars_ui: Uzar originala rullangeto di sistemo
-        setting_theme: Reteytemo
-        setting_trends: Montrar hodia populari
+        setting_theme: Sittemo
+        setting_trends: Montrez tendenco di hodie
         setting_unfollow_modal: Montrez konfirmdialogo ante desequar ulu
-        setting_use_blurhash: Montrez koloroza inklini por celata audvidaji
+        setting_use_blurhash: Montrez koloroza inklini por celata medii
         setting_use_pending_items: Modo lenta
         severity: Severeso
         sign_in_token_attempt: Sekureskodexo
@@ -265,11 +251,11 @@ io:
         custom_css: Kustumizita CSS
         favicon: Imajeto
         mascot: Kustumizita reprezentimajo (oldo)
-        media_cache_retention_period: Audvidajretmemorretendurtempo
+        media_cache_retention_period: Mediimemorajretendurtempo
         peers_api_enabled: Publikigez listo di deskovrita servili en API
-        profile_directory: Ebligar profiluyo
+        profile_directory: Aktivigez profilcheflisto
         registrations_mode: Qua povas registragar
-        require_invite_text: Bezonas motivo por adeskar
+        require_invite_text: Mustez pozar motivo por juntar
         show_domain_blocks: Montrez domenobstrukti
         show_domain_blocks_rationale: Montrez por quo domeni obstruktesir
         site_contact_email: Kontaktoretposto
@@ -282,8 +268,8 @@ io:
         theme: Originala temo
         thumbnail: Servilimajeto
         timeline_preview: Permisez neyurizita aceso a publika tempolineo
-        trendable_by_default: Permisez populari sen kontrolo
-        trends: Ebligar populari
+        trendable_by_default: Permisez tendenci sen bezonar kontrolo
+        trends: Aktivigez tendenci
         trends_as_landing_page: Uzar populari quale la iniciala pagino
       interactions:
         must_be_follower: Celar la savigi da homi, qui ne sequas tu
@@ -292,7 +278,7 @@ io:
       invite:
         comment: Komento
       invite_request:
-        text: Por quo vu volas adeskar?
+        text: Por quo vu volas juntar?
       ip_block:
         comment: Komento
         ip: IP
@@ -317,7 +303,7 @@ io:
           label: Nova Mastodon-versiono es disponebla
           none: Nultempe notifikar pri aktualigi (ne rekomendata)
           patch: Notifikar pri problemosolvanta aktualigi
-        trending_tag: Nova popularo bezonas kontrolo
+        trending_tag: Nova tendenco bezonas kontrolo
       rule:
         hint: Plusa informo
         text: Regulo
@@ -327,20 +313,8 @@ io:
       tag:
         listable: Permisez ca hashtago aparar en trovaji e sugestaji
         name: Hashtago
-        trendable: Permisez ca gretvorto aparar en populari
+        trendable: Permisez ca hashtago aparar che tendenci
         usable: Permisez posti uzar ca gretiketo lokale
-      terms_of_service:
-        changelog: Quo chanjesis?
-        text: Servtermini
-      terms_of_service_generator:
-        admin_email: Retpostoadreso por legala notici
-        arbitration_address: Fizika adreso por judicinotici
-        arbitration_website: Reteyo por sendar judicinotici
-        choice_of_law: Legselekteso
-        dmca_address: Fizika adreso por autoroyurnotici
-        dmca_email: Retpostoadreso por autoroyurnotici
-        domain: Domeno
-        jurisdiction: Legala seciono
       user:
         role: Rolo
         time_zone: Klokozono
@@ -351,7 +325,7 @@ io:
         permissions_as_keys: Permisi
         position: Prioreso
       webhook:
-        events: Ebligita eventi
+        events: Aktivigita eventi
         template: Pagkargshablono
         url: URL di finpunto
     'no': Ne
diff --git a/config/locales/simple_form.is.yml b/config/locales/simple_form.is.yml
index bc68d14f2a..40ae267a06 100644
--- a/config/locales/simple_form.is.yml
+++ b/config/locales/simple_form.is.yml
@@ -3,7 +3,7 @@ is:
   simple_form:
     hints:
       account:
-        attribution_domains: Eitt á hverja línu. Ver fyrir röngum tilvísunum.
+        attribution_domains_as_text: Eitt á hverja línu. Ver fyrir röngum tilvísunum.
         discoverable: Opinberar færslur og notandasnið þitt geta birst eða verið mælt með á hinum ýmsu svæðum í Mastodon auk þess sem hægt er að mæla með þér við aðra notendur.
         display_name: Fullt nafn þitt eða eitthvað til gamans.
         fields: Heimasíðan þín, fornöfn, aldur eða eitthvað sem þú vilt koma á framfæri.
@@ -60,7 +60,6 @@ is:
         setting_display_media_default: Fela myndefni sem merkt er viðkvæmt
         setting_display_media_hide_all: Alltaf fela allt myndefni
         setting_display_media_show_all: Alltaf birta myndefni sem merkt er viðkvæmt
-        setting_system_scrollbars_ui: Á einungis við um vafra fyrir vinnutölvur sem byggjast á Safari og Chrome
         setting_use_blurhash: Litstiglarnir byggja á litunum í földu myndunum, en gera öll smáatriði óskýr
         setting_use_pending_items: Fela uppfærslur tímalínu þar til smellt er, í stað þess að hún skruni streyminu sjálfvirkt
         username: Þú mátt nota bókstafi, tölur og undirstrik
@@ -75,7 +74,6 @@ is:
       filters:
         action: Veldu hvaða aðgerð á að framkvæma þegar færsla samsvarar síunni
         actions:
-          blur: Fela myndefni á bakvið aðvörun, án þess að fela sjálfann textann
           hide: Fela síað efni algerlega, rétt eins og það sé ekki til staðar
           warn: Fela síað efni á bakvið aðvörun sem tekur fram titil síunnar
       form_admin_settings:
@@ -89,7 +87,6 @@ is:
         favicon: WEBP, PNG, GIF eða JPG. Tekur yfir sjálfgefna Mastodon favicon-táknmynd með sérsniðinni táknmynd.
         mascot: Þetta tekyr yfir myndskreytinguna í ítarlega vefviðmótinu.
         media_cache_retention_period: Myndefnisskrár úr færslum sem gerðar eru af fjartengdum notendum eru geymdar á netþjóninum þínum. Þegar þetta er stillt á jákvætt gildi, verður þessum skrám eytt sjáfkrafa eftir þeim tiltekna fjölda daga. Ef beðið er um myndefnið eftir að því er eytt, mun það verða sótt aftur ef frumgögnin eru ennþá aðgengileg. Vegna takmarkana á hversu oft forskoðunarspjöld tengla eru sótt á utanaðkomandi netþjóna, þá er mælt með því að setja þetta gildi á að minnsta kosti 14 daga, annars gæti mistekist að uppfæra forskoðunarspjöld tengla eftir þörfum fyrir þann tíma.
-        min_age: Notendur verða beðnir um að staðfesta fæðingardag sinn við nýskráningu
         peers_api_enabled: Listi yfir þau lénaheiti sem þessi netþjónn hefur rekist á í skýjasambandinu. Engin gögn eru hér sem gefa til kynna hvort þú sért í sambandi við tiltekinn netþjón, bara að netþjónninn þinn viti um hann. Þetta er notað af þjónustum sem safna tölfræði um skýjasambönd á almennan hátt.
         profile_directory: Notendamappan telur upp alla þá notendur sem hafa valið að vera uppgötvanlegir.
         require_invite_text: Þegar nýskráningar krefjast handvirks samþykkis, þá skal gera textann í “Hvers vegna viltu taka þátt?” að kröfu en ekki valkvæðan
@@ -132,23 +129,8 @@ is:
         show_application: Þú munt alltaf geta séð hvaða forrit birti færsluna þína.
       tag:
         name: Þú getur aðeins breytt stafstöði mill há-/lágstafa, til gæmis til að gera þetta læsilegra
-      terms_of_service:
-        changelog: Er hægt að sníða með Markdown-málskipan.
-        effective_date: Ásættanlegur tímarammi getur verið frá 10 til 30 dagar eftir að þú hefur látið notendurna þína vita.
-        text: Er hægt að sníða með Markdown-málskipan.
-      terms_of_service_generator:
-        admin_email: Löglegar tilkynningar ná yfir andsvör, dómsúrskurði, lokunarbeiðnir og beiðnir frá lögregluembættum.
-        arbitration_address: Má vera það sama og raunverulegt heimilisfang eða “N/A” ef tölvupóstur er notaður.
-        arbitration_website: Má vera innfyllingarform á vefsíðu eða “N/A” ef tölvupóstur er notaður.
-        choice_of_law: Sveitarfélög, héruð eða ríki þar sem ríkjandi lög og reglugerðir skulu stýra meðhöndlun á öllum kröfum.
-        dmca_address: Fyrir rekstraraðila í BNA ætti að nota heimilisfang sem skráð er í DMCA Designated Agent Directory. Hægt er að verða sér úti um A P.O. pósthólfsskráningu með beinni beiðni; notaðu DMCA Designated Agent Post Office Box Waiver Request til að senda tölvupóst á Copyright Office og lýstu því yfir að þú sért heimavinnandi efnismiðlari (home-based content moderator) sem átt á hættu refsingar eða hefndir vegna þess sem þú miðlar og þurfir því á slíku pósthólfi að halda svo þitt eigið heimilisfang sé ekki gert opinbert.
-        dmca_email: Má vera sama tölvupóstfang og það sem notað er í “Tölvupóstfang vegna löglegra tilkynninga” hér að ofan.
-        domain: Einstakt auðkenni á netþjónustunni sem þú býður.
-        jurisdiction: Settu inn landið þar sem sá býr sem borgar reikningana. Ef það er fyrirtæki eða samtök, skaltu hafa það landið þar sem lögheimili þess er, auk borgar, héraðs, svæðis eða fylkis eins og við á.
-        min_age: Ætti ekki að vera lægri en sá lágmarksaldur sek kveðið er á um í lögum þíns lögsagnarumdæmis.
       user:
         chosen_languages: Þegar merkt er við þetta, birtast einungis færslur á völdum tungumálum á opinberum tímalínum
-        date_of_birth: Við verðum að ganga úr skugga um að þú hafir náð %{age} aldri til að nota Mastodon. Við munum ekki geyma þessar upplýsingar.
         role: Hlutverk stýrir hvaða heimildir notandinn hefur.
       user_role:
         color: Litur sem notaður er fyrir hlutverkið allsstaðar í viðmótinu, sem RGB-gildi á hex-sniði
@@ -162,7 +144,7 @@ is:
         url: Hvert atburðir verða sendir
     labels:
       account:
-        attribution_domains: Vefsvæði sem mega vitna í þig
+        attribution_domains_as_text: Vefsvæði sem mega vitna í þig
         discoverable: Hafa notandasnið og færslur með í reikniritum leitar
         fields:
           name: Skýring
@@ -239,10 +221,8 @@ is:
         setting_display_media_show_all: Birta allt
         setting_expand_spoilers: Alltaf útfella færslur sem eru með aðvörun vegna efnisins
         setting_hide_network: Fela félagsnetið þitt
-        setting_missing_alt_text_modal: Birta staðfestingarglugga áður en myndefni án ALT-hjálpartexta er birt
         setting_reduce_motion: Minnka hreyfingu í hreyfimyndum
         setting_system_font_ui: Nota sjálfgefið letur kerfisins
-        setting_system_scrollbars_ui: Nota sjálfgefna skrunstiku kerfisins
         setting_theme: Þema vefsvæðis
         setting_trends: Birta það sem er efst á baugi í dag
         setting_unfollow_modal: Birta staðfestingarglugga áður en hætt er að fylgjast með einhverjum
@@ -261,7 +241,6 @@ is:
         name: Myllumerki
       filters:
         actions:
-          blur: Fela myndefni með aðvörun
           hide: Fela alveg
           warn: Fela með aðvörun
       form_admin_settings:
@@ -275,7 +254,6 @@ is:
         favicon: Auðkennismynd
         mascot: Sérsniðið gæludýr (eldra)
         media_cache_retention_period: Tímalengd sem myndefni haldið
-        min_age: Kröfur um lágmarksaldur
         peers_api_enabled: Birta lista yfir uppgötvaða netþjóna í API-kerfisviðmótinu
         profile_directory: Virkja notendamöppu
         registrations_mode: Hverjir geta nýskráð sig
@@ -339,24 +317,7 @@ is:
         name: Myllumerki
         trendable: Leyfa þessu myllumerki að birtast undir vinsælu efni
         usable: Leyfa færslum að nota þetta myllumerki staðvært
-      terms_of_service:
-        changelog: Hvað breyttist?
-        effective_date: Gildistími
-        text: Þjónustuskilmálar
-      terms_of_service_generator:
-        admin_email: Tölvupóstfang vegna löglegra tilkynninga
-        arbitration_address: Raunverulegt heimilisfang fyrir tilkynningar um úrskurði
-        arbitration_website: Vefsvæði til að senda inn tilkynningar um úrskurði
-        choice_of_law: Val á lagaumgjörð
-        dmca_address: Raunverulegt heimilisfang fyrir kröfur vegna DMCA/höfundarréttar
-        dmca_email: Tölvupóstfang tilkynninga vegna DMCA/höfundaréttar
-        domain: Lén
-        jurisdiction: Lögsagnarumdæmi
-        min_age: Lágmarksaldur
       user:
-        date_of_birth_1i: Dagur
-        date_of_birth_2i: Mánuður
-        date_of_birth_3i: Ár
         role: Hlutverk
         time_zone: Tímabelti
       user_role:
diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml
index da203270fa..c36fce36f7 100644
--- a/config/locales/simple_form.it.yml
+++ b/config/locales/simple_form.it.yml
@@ -3,14 +3,14 @@ it:
   simple_form:
     hints:
       account:
-        attribution_domains: Uno per riga. Protegge da false attribuzioni.
+        attribution_domains_as_text: Uno per riga. Protegge da false attribuzioni.
         discoverable: I tuoi post pubblici e il tuo profilo potrebbero essere presenti o consigliati in varie aree di Mastodon e il tuo profilo potrebbe essere suggerito ad altri utenti.
         display_name: Il tuo nome completo o il tuo soprannome.
         fields: La tua homepage, i pronomi, l'età, tutto quello che vuoi.
         indexable: I tuoi post pubblici potrebbero apparire nei risultati di ricerca su Mastodon. Le persone che hanno interagito con i tuoi post potrebbero essere in grado di cercarli anche se non hai attivato questa impostazione.
         note: 'Puoi @menzionare altre persone o usare gli #hashtags.'
         show_collections: Le persone saranno in grado di navigare attraverso i tuoi seguaci e seguaci. Le persone che segui vedranno che li seguirai indipendentemente dalle tue impostazioni.
-        unlocked: Le persone potranno seguirti senza richiedere l'approvazione. Deseleziona questa opzione, se vuoi rivedere le richieste per poterti seguire e scegliere se accettare o rifiutare i nuovi seguaci.
+        unlocked: Le persone saranno in grado di seguirti senza richiedere l'approvazione. Deseleziona se vuoi controllare le richieste di seguirti e scegli se accettare o rifiutare nuovi follower.
       account_alias:
         acct: Indica il nomeutente@dominio dell'account dal quale vuoi trasferirti
       account_migration:
@@ -60,7 +60,6 @@ it:
         setting_display_media_default: Nascondi media segnati come sensibili
         setting_display_media_hide_all: Nascondi sempre tutti i media
         setting_display_media_show_all: Mostra sempre i media segnati come sensibili
-        setting_system_scrollbars_ui: Si applica solo ai browser desktop basati su Safari e Chrome
         setting_use_blurhash: I gradienti sono basati sui colori delle immagini nascoste ma offuscano tutti i dettagli
         setting_use_pending_items: Fare clic per mostrare i nuovi messaggi invece di aggiornare la timeline automaticamente
         username: Puoi usare lettere, numeri e caratteri di sottolineatura
@@ -75,7 +74,6 @@ it:
       filters:
         action: Scegli quale azione eseguire quando un post corrisponde al filtro
         actions:
-          blur: Nascondi i contenuti multimediali dietro un avviso, senza nascondere il testo stesso
           hide: Nascondi completamente il contenuto filtrato, come se non esistesse
           warn: Nascondi il contenuto filtrato e mostra invece un avviso, citando il titolo del filtro
       form_admin_settings:
@@ -89,7 +87,6 @@ it:
         favicon: WEBP, PNG, GIF o JPG. Sostituisce la favicon predefinita di Mastodon con un'icona personalizzata.
         mascot: Sostituisce l'illustrazione nell'interfaccia web avanzata.
         media_cache_retention_period: I file multimediali da post fatti da utenti remoti sono memorizzati nella cache sul tuo server. Quando impostato a un valore positivo, i media verranno eliminati dopo il numero specificato di giorni. Se i dati multimediali sono richiesti dopo che sono stati eliminati, saranno nuovamente scaricati, se il contenuto sorgente è ancora disponibile. A causa di restrizioni su quanto spesso link anteprima carte sondaggio siti di terze parti, si consiglia di impostare questo valore ad almeno 14 giorni, o le schede di anteprima link non saranno aggiornate su richiesta prima di quel tempo.
-        min_age: Gli utenti saranno invitati a confermare la loro data di nascita durante la registrazione
         peers_api_enabled: Un elenco di nomi di dominio che questo server ha incontrato nel fediverse. Qui non sono inclusi dati sul fatto se si federano con un dato server, solo che il server ne è a conoscenza. Questo viene utilizzato dai servizi che raccolgono statistiche sulla federazione in senso generale.
         profile_directory: La directory del profilo elenca tutti gli utenti che hanno acconsentito ad essere individuabili.
         require_invite_text: 'Quando le iscrizioni richiedono l''approvazione manuale, rendi la domanda: "Perché vuoi unirti?" obbligatoria anziché facoltativa'
@@ -132,23 +129,8 @@ it:
         show_application: Tu sarai sempre in grado di vedere quale app ha pubblicato il tuo post anche se hai attivato questa impostazione.
       tag:
         name: Puoi cambiare solo il minuscolo/maiuscolo delle lettere, ad esempio, per renderlo più leggibile
-      terms_of_service:
-        changelog: Può essere strutturato con la sintassi Markdown.
-        effective_date: Un lasso di tempo ragionevole può variare da 10 a 30 giorni dalla data di notifica agli utenti.
-        text: Può essere strutturato con la sintassi Markdown.
-      terms_of_service_generator:
-        admin_email: Gli avvisi legali includono controavvisi, ordinanze del tribunale, richieste di rimozione e richieste delle forze dell'ordine.
-        arbitration_address: Può essere uguale all'indirizzo fisico sopra indicato oppure "N/D" se si utilizza l'e-mail.
-        arbitration_website: Può essere un modulo web oppure "N/D" se si utilizza l'e-mail.
-        choice_of_law: Città, regione, territorio o Stato le cui leggi sostanziali interne regoleranno tutti i reclami.
-        dmca_address: Per gli operatori statunitensi, utilizzare l'indirizzo registrato nella DMCA Designated Agent Directory. L'elenco delle caselle postali è disponibile su richiesta diretta, utilizzando il DMCA Designated Agent Post Office Box Waiver Request per inviare un'email al Copyright Office e descrivendo di essere un moderatore di contenuti che lavora da casa e che teme vendette o punizioni per le proprie azioni e che hai bisogno di usare una casella postale per rimuovere il tuo indirizzo di casa dalla vista pubblica.
-        dmca_email: Può essere la stessa e-mail utilizzata per "Indirizzo e-mail per avvisi legali" sopra.
-        domain: Identificazione univoca del servizio online che stai fornendo.
-        jurisdiction: Indica il Paese in cui risiede il soggetto che paga le fatture. Se si tratta di un'azienda o di un altro ente, indicare il Paese in cui è costituito, nonché la città, la regione, il territorio o lo Stato, a seconda dei casi.
-        min_age: Non si dovrebbe avere un'età inferiore a quella minima richiesta, dalle leggi della tua giurisdizione.
       user:
         chosen_languages: Quando una o più lingue sono contrassegnate, nelle timeline pubbliche vengono mostrati solo i toot nelle lingue selezionate
-        date_of_birth: Dobbiamo verificare che tu abbia almeno %{age} anni per usare Mastodon. Non archivieremo questa informazione.
         role: Il ruolo controlla quali permessi ha l'utente.
       user_role:
         color: Colore da usare per il ruolo in tutta l'UI, come RGB in formato esadecimale
@@ -162,7 +144,7 @@ it:
         url: Dove gli eventi saranno inviati
     labels:
       account:
-        attribution_domains: Siti web autorizzati ad accreditarti
+        attribution_domains_as_text: Siti web autorizzati ad accreditarti
         discoverable: Include il profilo e i post negli algoritmi di scoperta
         fields:
           name: Etichetta
@@ -239,10 +221,8 @@ it:
         setting_display_media_show_all: Mostra tutti
         setting_expand_spoilers: Espandi sempre post con content warning
         setting_hide_network: Nascondi la tua rete
-        setting_missing_alt_text_modal: Chiedi di confermare prima di pubblicare media senza testo alternativo
         setting_reduce_motion: Riduci movimento nelle animazioni
         setting_system_font_ui: Usa il carattere predefinito del sistema
-        setting_system_scrollbars_ui: Utilizza la barra di scorrimento predefinita del sistema
         setting_theme: Tema del sito
         setting_trends: Mostra tendenze di oggi
         setting_unfollow_modal: Chiedi conferma prima di smettere di seguire qualcuno
@@ -261,7 +241,6 @@ it:
         name: Etichetta
       filters:
         actions:
-          blur: Nascondi i contenuti multimediali con un avviso
           hide: Nascondi completamente
           warn: Nascondi con avviso
       form_admin_settings:
@@ -275,7 +254,6 @@ it:
         favicon: Favicon
         mascot: Personalizza mascotte (legacy)
         media_cache_retention_period: Periodo di conservazione della cache multimediale
-        min_age: Età minima richiesta
         peers_api_enabled: Pubblica l'elenco dei server scoperti nell'API
         profile_directory: Abilita directory del profilo
         registrations_mode: Chi può iscriversi
@@ -339,24 +317,7 @@ it:
         name: Hashtag
         trendable: Permetti a questo hashtag di apparire nelle tendenze
         usable: Permetti ai post di utilizzare questo hashtag localmente
-      terms_of_service:
-        changelog: Cosa è cambiato?
-        effective_date: Data di decorrenza
-        text: Termini di Servizio
-      terms_of_service_generator:
-        admin_email: Indirizzo email per avvisi legali
-        arbitration_address: Indirizzo fisico per avvisi di arbitrato
-        arbitration_website: Sito web per l'invio di avvisi di arbitrato
-        choice_of_law: Scelta della legge
-        dmca_address: Indirizzo fisico per gli avvisi DMCA/copyright
-        dmca_email: Indirizzo email per gli avvisi DMCA/copyright
-        domain: Dominio
-        jurisdiction: Giurisdizione legale
-        min_age: Età minima
       user:
-        date_of_birth_1i: Giorno
-        date_of_birth_2i: Mese
-        date_of_birth_3i: Anno
         role: Ruolo
         time_zone: Fuso orario
       user_role:
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index b8728f63a0..bd8cb11415 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -3,7 +3,7 @@ ja:
   simple_form:
     hints:
       account:
-        attribution_domains: 1行につき1つずつ入力してください。この設定は関わりのないwebサイトに対して虚偽の帰属表示が行われることを防止する役割があります。
+        attribution_domains_as_text: 1行につき1つずつ入力してください。この設定は関わりのないwebサイトに対して虚偽の帰属表示が行われることを防止する役割があります。
         discoverable: プロフィールと公開投稿をMastodonのおすすめやハイライトとしてほかのユーザーに表示することを許可します。
         display_name: フルネーム、ハンドルネームなど
         fields: ホームページ、代名詞、年齢など何でも構いません。
@@ -84,7 +84,6 @@ ja:
         setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります
         setting_stay_privacy: この設定を有効にする場合、ブーストの公開範囲を手動で設定されることもご検討ください
         setting_stop_emoji_reaction_streaming: 通信容量の節約に役立ちます
-        setting_system_scrollbars_ui: Safari/Chromeベースのデスクトップブラウザーでのみ有効です
         setting_use_blurhash: ぼかしはメディアの色を元に生成されますが、細部は見えにくくなっています
         setting_use_pending_items: 新着があってもタイムラインを自動的にスクロールしないようにします
         username: アルファベット大文字と小文字、数字、アンダーバー「_」が使えます
@@ -99,6 +98,7 @@ ja:
       filters:
         action: 投稿がフィルタに一致したときに実行するアクションを選択
         actions:
+          half_warn: フィルターに一致した投稿の本文のみを非表示にし、フィルターのタイトルを含む警告を表示します
           hide: フィルタに一致した投稿を完全に非表示にします
           warn: フィルタに一致した投稿を非表示にし、フィルタのタイトルを含む警告を表示します
       form_admin_settings:
@@ -164,14 +164,6 @@ ja:
         show_application: 自分で自分の投稿の送信元アプリを確認することは、ここのチェック状態にかかわらず可能です。
       tag:
         name: 視認性向上などのためにアルファベット大文字小文字の変更のみ行うことができます
-      terms_of_service:
-        changelog: Markdown 記法を利用できます。
-        text: Markdown 記法を利用できます。
-      terms_of_service_generator:
-        admin_email: 法的通知とは、異議申し立て通知、裁判所命令、削除要請、法執行機関による要請などをいいます。
-        dmca_address: 米国の運営者の場合は、DMCA Designated Agent Directory(DMCA指定代理人ディレクトリ)に登録のある住所を使用してください。申請を行えば記載を私書箱とすることも可能で、その場合はDMCA Designated Agent Post Office Box Waiver Request(DMCA指定代理人の郵便私書箱による免除申請)によって著作権局にメールを送信し、あなたが個人のコンテンツ管理者であって、行った措置に対して報復を受けるおそれがあり、住所を非公開とするために私書箱を使用する必要がある旨を伝えてください。
-        domain: あなたの提供するこのオンラインサービスの識別名です。
-        jurisdiction: 運営責任者が居住する国を記載します。企業や他の団体である場合は、その組織の所在国に加えて、市・区・州などの地域を記載します。
       user:
         chosen_languages: 選択すると、選択した言語の投稿のみが公開タイムラインに表示されるようになります
         role: そのロールは、ユーザーが持つ権限を制御します。
@@ -188,7 +180,7 @@ ja:
     kmyblue: kmyblue
     labels:
       account:
-        attribution_domains: あなたの著者表示を許可するwebサイト
+        attribution_domains_as_text: あなたの著者表示を許可するwebサイト
         discoverable: アカウントを見つけやすくする
         fields:
           examples:
@@ -309,7 +301,6 @@ ja:
         setting_hide_quote_unavailable_server: 引用に対応していないと思われるサーバーの投稿からメニューを隠す
         setting_hide_status_reference_unavailable_server: ひかえめな引用(Fedibirdの参照)に対応していないと思われるサーバーの投稿からメニューを隠す
         setting_lock_follow_from_bot: botからのフォローを承認制にする
-        setting_missing_alt_text_modal: 代替テキストなしでメディアを投稿する前に確認ダイアログを表示する
         setting_public_post_to_unlisted: サードパーティから公開範囲「公開」で投稿した場合、「ローカル公開」に変更する
         setting_reduce_motion: アニメーションの動きを減らす
         setting_reject_public_unlisted_subscription: Misskey系サーバーに「ローカル公開」かつ検索許可「誰でも以外」の投稿を「フォロワーのみ」に変換して配送する
@@ -317,7 +308,6 @@ ja:
         setting_reject_unlisted_subscription: Misskey系サーバーに「非収載」かつ検索許可「誰でも以外」の投稿を「フォロワーのみ」に変換して配送する
         setting_reverse_search_quote: ダブルクオートで囲まず検索した時、単語単位で検索する
         setting_show_application: 送信したアプリを開示する
-        setting_show_avatar_on_filter: フィルター対象投稿の投稿者名やアイコンを表示する
         setting_show_blocking_quote: ブロックしたユーザーの投稿を引用した投稿を表示する
         setting_show_emoji_reaction_count: 投稿につけられた各絵文字の数を表示する
         setting_show_emoji_reaction_on_timeline: タイムライン上の投稿に他の人のつけた絵文字を表示する
@@ -334,16 +324,14 @@ ja:
         setting_stay_privacy: 投稿時に公開範囲を保存する
         setting_stop_emoji_reaction_streaming: 絵文字リアクションのストリーミングを停止する
         setting_system_font_ui: システムのデフォルトフォントを使う
-        setting_system_scrollbars_ui: システムのデフォルトのスクロールバーを使う
         setting_theme: サイトテーマ
         setting_translatable_private: 非公開投稿の翻訳を許可する
         setting_trends: 本日のトレンドタグを表示する
         setting_unfollow_modal: フォローを解除する前に確認ダイアログを表示する
         setting_use_blurhash: 非表示のメディアを色付きのぼかしで表示する
-        setting_use_custom_css: あなた自身が設定する以下のカスタムCSSを有効にする
+        setting_use_custom_css: カスタムCSSを有効にする
         setting_use_pending_items: 手動更新モード
         setting_use_public_index: Mastodonの標準設定によって検索が許可されたアカウントの公開投稿を検索結果に含める
-        setting_use_server_css: 管理者の設定したカスタムCSSを有効にする
         severity: 重大性
         sign_in_token_attempt: セキュリティコード
         title: タイトル
@@ -370,6 +358,7 @@ ja:
         name: ハッシュタグ
       filters:
         actions:
+          half_warn: アカウント名だけを出し、本文は警告で隠す
           hide: 完全に隠す
           warn: 警告付きで隠す
         options:
@@ -470,17 +459,6 @@ ja:
         name: ハッシュタグ
         trendable: トレンドへの表示を許可する
         usable: このサーバーのユーザーがタグをつけて投稿することを許可する
-      terms_of_service:
-        changelog: 変更箇所
-        text: サービス利用規約
-      terms_of_service_generator:
-        admin_email: 法的通知を受け取るメールアドレス
-        arbitration_address: 仲裁通知の送付先住所
-        arbitration_website: 仲裁通知の送信用ウェブサイト
-        dmca_address: DMCA/著作権通知の送付先住所
-        dmca_email: DMCA/著作権通知の送付先メールアドレス
-        domain: ドメイン
-        jurisdiction: 裁判管轄
       user:
         role: ロール
         time_zone: タイムゾーン
diff --git a/config/locales/simple_form.kab.yml b/config/locales/simple_form.kab.yml
index c0ff7e598e..ff041b257b 100644
--- a/config/locales/simple_form.kab.yml
+++ b/config/locales/simple_form.kab.yml
@@ -18,7 +18,7 @@ kab:
         bot: Smekti-d wiyaḍ dakken amiḍan-a ixeddem s wudem amezwer tigawin tiwurmanin yernu ur yezmir ara ad yettwaɛass
         email: Ad n-teṭṭfeḍ imayl i usentem
         irreversible: Tisuffaɣ i tessazedgeḍ ad ttwakksent i lebda, ula ma tekkseḍ imsizdeg-nni ar zdat
-        locale: Tutlayt n ugrudem, imaylen d yilɣa yettudemren
+        locale: Tutlayt n ugrudem, imaylen d walɣuten yettudemren
         password: Seqdec ma drus 8 n yisekkilen
         setting_always_send_emails: S umata, ilɣa s yimayl ur d-ttwaceyyεen ara mi ara tesseqdaceḍ Mastodon s wudem urmid
         setting_display_media_default: Ffer imidyaten yettwacreḍ d infariyen
@@ -115,8 +115,8 @@ kab:
         theme: Asentel amezwer
         thumbnail: Tanfult n uqeddac
       interactions:
-        must_be_follower: Ssewḥel ilɣa sɣur wid akk d tid ur yellin ara d imeḍfaren-ik·im
-        must_be_following: Ssewḥel ilɣa sɣur wid akked tid ur tettḍafareḍ ara
+        must_be_follower: Ssewḥel alɣuten sɣur wid akked tid ur yellin ara d imeḍfaren-ik·im
+        must_be_following: Ssewḥel alɣuten sɣur wid akked tid ur tettḍafareḍ ara
         must_be_following_dm: Sewḥel iznan usriden sɣur wid akked tid ur tettḍafareḍ ara
       invite:
         comment: Awennit
@@ -141,8 +141,6 @@ kab:
         text: Alugen
       tag:
         name: Ahacṭag
-      terms_of_service:
-        text: Tiwtilin n useqdec
       user:
         role: Tamlilt
         time_zone: Tamnaḍt tasragant
diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml
index 661028fff0..1d5da49202 100644
--- a/config/locales/simple_form.ko.yml
+++ b/config/locales/simple_form.ko.yml
@@ -3,7 +3,7 @@ ko:
   simple_form:
     hints:
       account:
-        attribution_domains: 한 줄에 하나씩. 가짜 기여로부터 보호합니다.
+        attribution_domains_as_text: 한 줄에 하나씩. 가짜 기여로부터 보호합니다.
         discoverable: 내 공개 게시물과 프로필이 마스토돈의 다양한 추천 기능에 나타날 수 있고 프로필이 다른 사용자에게 제안될 수 있습니다
         display_name: 진짜 이름 또는 재미난 이름.
         fields: 홈페이지, 호칭, 나이, 뭐든지 적고 싶은 것들.
@@ -60,7 +60,6 @@ ko:
         setting_display_media_default: 민감함으로 표시된 미디어 가리기
         setting_display_media_hide_all: 모든 미디어를 가리기
         setting_display_media_show_all: 모든 미디어를 보이기
-        setting_system_scrollbars_ui: 사파리와 크롬 기반의 데스크탑 브라우저만 적용됩니다
         setting_use_blurhash: 그라디언트는 숨겨진 내용의 색상을 기반으로 하지만 상세 내용은 보이지 않게 합니다
         setting_use_pending_items: 타임라인의 새 게시물을 자동으로 보여 주는 대신, 클릭해서 나타내도록 합니다
         username: 문자, 숫자, 밑줄을 사용할 수 있습니다
@@ -75,7 +74,6 @@ ko:
       filters:
         action: 게시물이 필터에 걸러질 때 어떤 동작을 수행할 지 고르세요
         actions:
-          blur: 텍스트는 숨기지 않고 그대로 둔 채 경고 뒤에 미디어를 숨김니다
           hide: 필터에 걸러진 글을 처음부터 없었던 것처럼 완전히 가리기
           warn: 필터 제목을 언급하는 경고 뒤에 걸러진 내용을 숨기기
       form_admin_settings:
@@ -89,7 +87,6 @@ ko:
         favicon: WEBP, PNG, GIF 또는 JPG. 기본 파비콘을 대체합니다.
         mascot: 고급 웹 인터페이스의 그림을 대체합니다.
         media_cache_retention_period: 원격 사용자가 작성한 글의 미디어 파일은 이 서버에 캐시됩니다. 양수로 설정하면 지정된 일수 후에 미디어가 삭제됩니다. 삭제된 후에 미디어 데이터를 요청하면 원본 콘텐츠를 사용할 수 있는 경우 다시 다운로드됩니다. 링크 미리 보기 카드가 타사 사이트를 폴링하는 빈도에 제한이 있으므로 이 값을 최소 14일로 설정하는 것이 좋으며, 그렇지 않으면 그 이전에는 링크 미리 보기 카드가 제때 업데이트되지 않을 것입니다.
-        min_age: 사용자들은 가입할 때 생일을 확인받게 됩니다
         peers_api_enabled: 이 서버가 연합우주에서 만났던 서버들에 대한 도메인 네임의 목록입니다. 해당 서버와 어떤 연합을 했는지에 대한 정보는 전혀 포함되지 않고, 단순히 그 서버를 알고 있는지에 대한 것입니다. 이것은 일반적으로 연합에 대한 통계를 수집할 때 사용됩니다.
         profile_directory: 프로필 책자는 발견되기를 희망하는 모든 사람들의 목록을 나열합니다.
         require_invite_text: 가입이 수동 승인을 필요로 할 때, "왜 가입하려고 하나요?" 항목을 선택사항으로 두는 것보다는 필수로 두는 것이 낫습니다
@@ -132,21 +129,8 @@ ko:
         show_application: 나 자신은 이 설정과 관계 없이 어떤 앱으로 게시물을 작성했는지 볼 수 있습니다.
       tag:
         name: 읽기 쉽게하기 위한 글자의 대소문자만 변경할 수 있습니다.
-      terms_of_service:
-        changelog: 마크다운 문법을 사용할 수 있습니다.
-        effective_date: 사용자에게 통지한 날로부터 10일에서 30일 사이가 합리적인 기간입니다.
-        text: 마크다운 문법을 사용할 수 있습니다.
-      terms_of_service_generator:
-        admin_email: 법적 고지에는 이의 제기, 법원 명령, 게시 중단 요청, 법 집행 요청이 포함됩니다.
-        arbitration_address: 위의 실제 주소와 같을 수 있으며, 이메일을 사용한다면 "N/A"로 두세요.
-        arbitration_website: 웹 형태를 사용할 수 있습니다. 이메일을 사용한다면 "N/A"로 둘 수 있습니다.
-        dmca_email: 상단의 "법적 통지를 위한 이메일 주소"와 같은 주소를 사용할 수 있습니다.
-        domain: 귀하가 제공하는 온라인 서비스의 고유 식별정보입니다.
-        jurisdiction: 요금을 지불하는 사람이 거주하는 국가를 기재하세요. 회사나 기타 법인인 경우 해당 법인이 설립된 국가와 도시, 지역, 영토 또는 주를 적절히 기재하세요.
-        min_age: 관할지역의 법률에서 요구하는 최저 연령보다 작으면 안 됩니다.
       user:
         chosen_languages: 체크하면, 선택 된 언어로 작성된 게시물들만 공개 타임라인에 보여집니다
-        date_of_birth: 마스토돈을 사용하려면 %{age}세 이상임을 확인해야 합니다. 이 정보는 저장되지 않습니다.
         role: 역할은 사용자가 어떤 권한을 가지게 될 지 결정합니다.
       user_role:
         color: 색상은 사용자 인터페이스에서 역할을 나타내기 위해 사용되며, RGB 16진수 형식입니다
@@ -156,11 +140,11 @@ ko:
         position: 특정 상황에서 충돌이 발생할 경우 더 높은 역할이 충돌을 해결합니다. 특정 작업은 우선순위가 낮은 역할에 대해서만 수행될 수 있습니다
       webhook:
         events: 전송할 이벤트를 선택하세요
-        template: 원하는 JSON 페이로드를 변수와 함께 작성하거나, 그대로 두어 기본 JSON을 사용할 수 있습니다.
+        template: 원하는 JSON 페이로드를 변수와 함께 작성하거나, 그냥 냅둬서 기본 JSON을 사용할 수 있습니다.
         url: 이벤트가 어디로 전송될 지
     labels:
       account:
-        attribution_domains: 나를 기여자로 올릴 수 있도록 허용된 웹사이트들
+        attribution_domains_as_text: 나를 기여자로 올릴 수 있도록 허용된 웹사이트들
         discoverable: 발견하기 알고리즘에 프로필과 게시물을 추천하기
         fields:
           name: 라벨
@@ -237,10 +221,8 @@ ko:
         setting_display_media_show_all: 모두 보이기
         setting_expand_spoilers: 내용 경고로 표시된 게시물을 항상 펼치기
         setting_hide_network: 내 인맥 숨기기
-        setting_missing_alt_text_modal: 대체 텍스트 없이 미디어를 게시하려고 할 때 확인창을 띄웁니다
         setting_reduce_motion: 애니메이션 줄이기
         setting_system_font_ui: 시스템의 기본 글꼴을 사용
-        setting_system_scrollbars_ui: 시스템 기본 스크롤바 사용
         setting_theme: 사이트 테마
         setting_trends: 오늘의 유행 보이기
         setting_unfollow_modal: 누군가를 언팔로우 할 때 확인란 표시하기
@@ -259,7 +241,6 @@ ko:
         name: 해시태그
       filters:
         actions:
-          blur: 경고와 함께 미디어 숨기기
           hide: 완전히 숨기기
           warn: 경고와 함께 숨기기
       form_admin_settings:
@@ -273,7 +254,6 @@ ko:
         favicon: 파비콘
         mascot: 사용자 정의 마스코트 (legacy)
         media_cache_retention_period: 미디어 캐시 유지 기한
-        min_age: 최소 연령 제한
         peers_api_enabled: API에 발견 된 서버들의 목록 발행
         profile_directory: 프로필 책자 활성화
         registrations_mode: 누가 가입할 수 있는지
@@ -337,24 +317,7 @@ ko:
         name: 해시태그
         trendable: 이 해시태그가 유행에 나타날 수 있도록 허용
         usable: 이 해시태그를 로컬 게시물에서 사용 가능하도록 허용
-      terms_of_service:
-        changelog: 무엇이 바뀌었나요?
-        effective_date: 시행일
-        text: 이용 약관
-      terms_of_service_generator:
-        admin_email: 법적 조치를 위한 이메일 주소
-        arbitration_address: 중재 통지를 위한 실제 주소
-        arbitration_website: 중재 통지를 제출하기 위한 웹사이트
-        choice_of_law: 준거법 지정
-        dmca_address: DMCA/저작권 통지를 위한 실제 주소
-        dmca_email: DMCA/저작권 통지를 위한 이메일 주소
-        domain: 도메인
-        jurisdiction: 법적 관할권
-        min_age: 최소 연령
       user:
-        date_of_birth_1i: 일
-        date_of_birth_2i: 월
-        date_of_birth_3i: 년
         role: 역할
         time_zone: 시간대
       user_role:
diff --git a/config/locales/simple_form.la.yml b/config/locales/simple_form.la.yml
index 3a7ba0d445..0ad29b408a 100644
--- a/config/locales/simple_form.la.yml
+++ b/config/locales/simple_form.la.yml
@@ -1 +1,6 @@
+---
 la:
+  simple_form:
+    hints:
+      account:
+        unlocked: Homines tibi sine approbātiōnis postulātiōne sequī poterunt. Dēlēgās, sī rogātiōnēs sequendī recēnseāre vīs et utrum novōs sectātōrēs accipere an repudiāre mālīs.
diff --git a/config/locales/simple_form.lad.yml b/config/locales/simple_form.lad.yml
index a3f70bd361..de37005312 100644
--- a/config/locales/simple_form.lad.yml
+++ b/config/locales/simple_form.lad.yml
@@ -308,12 +308,7 @@ lad:
         name: Etiketa
         trendable: Permite ke esta etiketa apareska en trendes
         usable: Permite ke publikasyones uzen esta etiketa lokalmente
-      terms_of_service_generator:
-        domain: Domeno
       user:
-        date_of_birth_1i: Diya
-        date_of_birth_2i: Mez
-        date_of_birth_3i: Anyo
         role: Rolo
         time_zone: Zona de tiempo
       user_role:
diff --git a/config/locales/simple_form.lt.yml b/config/locales/simple_form.lt.yml
index d3febceea9..6c4b6b3632 100644
--- a/config/locales/simple_form.lt.yml
+++ b/config/locales/simple_form.lt.yml
@@ -3,14 +3,14 @@ lt:
   simple_form:
     hints:
       account:
-        attribution_domains: Po vieną eilutėje. Apsaugo nuo klaidingų atributų.
+        attribution_domains_as_text: Po vieną eilutėje. Apsaugo nuo klaidingų atributų.
         discoverable: Tavo vieši įrašai ir profilis gali būti rodomi arba rekomenduojami įvairiose Mastodon vietose, o profilis gali būti siūlomas kitiems naudotojams.
         display_name: Tavo pilnas vardas arba smagus vardas.
         fields: Tavo pagrindinis puslapis, įvardžiai, amžius, bet kas, ko tik nori.
         indexable: Tavo vieši įrašai gali būti rodomi Mastodon paieškos rezultatuose. Žmonės, kurie bendravo su tavo įrašais, gali jų ieškoti nepriklausomai nuo to.
         note: 'Gali @paminėti kitus žmones arba #saitažodžius.'
         show_collections: Žmonės galės peržiūrėti tavo sekimus ir sekėjus. Žmonės, kuriuos seki, matys, kad juos seki, nepaisant to.
-        unlocked: Žmonės galės jus sekti nepaprašę patvirtinimo. Panaikinkite žymėjimą, jei norite peržiūrėti sekimo prašymus ir pasirinkti, ar priimti, ar atmesti naujus sekėjus.
+        unlocked: Asmenys galės jus sekti nepaprašę patvirtinimo. Panaikinkite žymėjimą, jei norite peržiūrėti sekimo prašymus, ir pasirinkti, ar priimti, ar atmesti naujus sekėjus.
       account_alias:
         acct: Nurodyk paskyros, iš kurios nori perkelti, naudotojo vardą@domeną
       account_migration:
@@ -60,7 +60,6 @@ lt:
         setting_display_media_default: Slėpti mediją, pažymėtą kaip jautrią
         setting_display_media_hide_all: Visada slėpti mediją
         setting_display_media_show_all: Visada rodyti mediją
-        setting_system_scrollbars_ui: Taikoma tik darbalaukio naršyklėms, karkasiniais „Safari“ ir „Chrome“.
         setting_use_blurhash: Gradientai pagrįsti paslėptų vizualizacijų spalvomis, bet užgožia bet kokias detales.
         setting_use_pending_items: Slėpti laiko skalės naujienas po paspaudimo, vietoj automatinio srauto slinkimo.
         username: Gali naudoti raides, skaičius ir pabraukimus
@@ -72,7 +71,6 @@ lt:
       filters:
         action: Pasirink, kokį veiksmą atlikti, kai įrašas atitinka filtrą
         actions:
-          blur: Slėpti mediją po įspėjimu, neslepiant paties teksto
           hide: Visiškai paslėpti filtruotą turinį ir elgtis taip, tarsi jo neegzistuotų
           warn: Slėpti filtruojamą turinį po įspėjimu, paminint filtro pavadinimą
       form_admin_settings:
@@ -83,7 +81,6 @@ lt:
         favicon: WEBP, PNG, GIF arba JPG. Pakeičia numatytąją Mastodon svetaines piktogramą pasirinktine piktograma.
         mascot: Pakeičia išplėstinės žiniatinklio sąsajos iliustraciją.
         media_cache_retention_period: Nuotolinių naudotojų įrašytų įrašų medijos failai talpinami tavo serveryje. Nustačius teigiamą reikšmę, medijos bus ištrinamos po nurodyto dienų skaičiaus. Jei medijos duomenų bus paprašyta po to, kai jie bus ištrinti, jie bus atsiųsti iš naujo, jei šaltinio turinys vis dar prieinamas. Dėl apribojimų, susijusių su nuorodų peržiūros kortelių apklausos dažnumu trečiųjų šalių svetainėse, rekomenduojama nustatyti šią reikšmę ne trumpesnę kaip 14 dienų, kitaip nuorodų peržiūros kortelės nebus atnaujinamos pagal pareikalavimą iki to laiko.
-        min_age: Registracijos metu naudotojai bus paprašyti patvirtinti savo gimimo datą.
         peers_api_enabled: Domenų pavadinimų sąrašas, su kuriais šis serveris susidūrė fediverse. Čia nėra duomenų apie tai, ar tu bendrauji su tam tikru serveriu, tik apie tai, kad tavo serveris apie jį žino. Tai naudojama tarnybose, kurios renka federacijos statistiką bendrąja prasme.
         require_invite_text: Kai registraciją reikia patvirtinti rankiniu būdu, teksto įvesties laukelį „Kodėl nori prisijungti?“ padaryk privalomą, o ne pasirenkamą
         site_contact_email: Kaip žmonės gali su tavimi susisiekti teisiniais ar pagalbos užklausimais.
@@ -105,26 +102,12 @@ lt:
       settings:
         indexable: Tavo profilio puslapis gali būti rodomas paieškos rezultatuose Google, Bing ir kituose.
         show_application: Neatsižvelgiant į tai, visada galėsi matyti, kuri programėlė paskelbė tavo įrašą.
-      terms_of_service:
-        changelog: Gali būti struktūrizuota su ženklinimo sintakse.
-        text: Gali būti struktūrizuota su ženklinimo sintakse.
-      terms_of_service_generator:
-        admin_email: Teisiniai pranešimai įtraukia priešpriešinius pranešimus, teismo įsakymus, pašalinimo prašymus ir teisėsaugos institucijų prašymus.
-        arbitration_address: Gali būti toks pat kaip aukščiau nurodytas fizinis adresas arba „N/A“ (netaikoma), jei naudojamas el. paštas.
-        arbitration_website: Gali būti interneto forma arba „N/A“ (netaikoma), jei naudojamas el. paštas.
-        choice_of_law: Miestas, regionas, teritorija ar valstija, kurių vidaus materialinė teisė reglamentuoja visus reikalavimus.
-        dmca_address: JAV operatoriams naudokite DMCA paskirtojo agento kataloge užregistruotą adresą. Pašto dėžutės sąrašą galima sudaryti pateikus tiesioginį prašymą, naudokite DMCA paskirtojo agento pašto dėžutės atsisakymo prašymą, kad parašytumėte el. laišką Autorinių teisių tarnybai ir aprašytumėte, kad esate namuose įsikūręs turinio moderatorius, kuris baiminasi keršto ar bausmės už savo veiksmus ir kuriam reikia naudoti pašto dėžutę, kad jo namų adresas nebūtų viešai matomas.
-        dmca_email: Gali būti tas pats aukščiau nurodytas el. pašto adresas, naudojamas „El. pašto adresas, skirtas teisiniams pranešimams“.
-        domain: Unikalus jūsų teikiamos internetinės paslaugos identifikavimas.
-        jurisdiction: Nurodykite šalį, kurioje gyvena tas, kas apmoka sąskaitas. Jei tai bendrovė ar kita esybė, nurodykite šalį, kurioje jis įregistruotas, ir atitinkamai miestą, regioną, teritoriją ar valstiją.
-        min_age: Neturėtų būti žemiau mažiausio amžiaus, reikalaujamo pagal jūsų jurisdikcijos įstatymus.
       user:
         chosen_languages: Kai pažymėta, viešose laiko skalėse bus rodomi tik įrašai pasirinktomis kalbomis.
-        date_of_birth: Turime įsitikinti, kad esate bent %{age}, kad naudotumėte „Mastodon“. Mes to neišsaugosime.
         role: Vaidmuo valdo, kokius leidimus naudotojas turi.
     labels:
       account:
-        attribution_domains: Svetainės, kuriuose leidžiama jus įvardyti
+        attribution_domains_as_text: Svetainės, kuriuose leidžiama jus nurodyti
         discoverable: Rekomenduoti profilį ir įrašus į atradimo algoritmus
         indexable: Įtraukti viešus įrašus į paieškos rezultatus
         show_collections: Rodyti sekimus ir sekėjus profilyje
@@ -165,10 +148,8 @@ lt:
         setting_display_media_show_all: Rodyti viską
         setting_expand_spoilers: Visada išplėsti įrašus, pažymėtus turinio įspėjimais
         setting_hide_network: Slėpti savo socialinę diagramą
-        setting_missing_alt_text_modal: Rodyti patvirtinimo dialogo langą prieš skelbiant mediją be alternatyvaus teksto.
         setting_reduce_motion: Sumažinti judėjimą animacijose
         setting_system_font_ui: Naudoti numatytąjį sistemos šriftą
-        setting_system_scrollbars_ui: Naudoti numatytąją sistemos slankjuostę
         setting_theme: Svetainės tema
         setting_trends: Rodyti šiandienos trendus
         setting_use_blurhash: Rodyti spalvingus paslėptos medijos gradientus
@@ -184,7 +165,6 @@ lt:
         name: Saitažodis
       filters:
         actions:
-          blur: Slėpti mediją su įspėjimu
           hide: Slėpti visiškai
           warn: Slėpti su įspėjimu
       form_admin_settings:
@@ -195,7 +175,6 @@ lt:
         custom_css: Pasirinktinis CSS
         favicon: Svetainės piktograma
         mascot: Pasirinktinis talismanas (pasenęs)
-        min_age: Mažiausias amžiaus reikalavimas
         registrations_mode: Kas gali užsiregistruoti
         require_invite_text: Reikalauti priežasties prisijungti
         show_domain_blocks_rationale: Rodyti, kodėl domenai buvo užblokuoti
@@ -234,23 +213,7 @@ lt:
         name: Saitažodis
         trendable: Leisti šį saitažodį rodyti pagal trendus
         usable: Leisti įrašams naudoti šį saitažodį vietoje
-      terms_of_service:
-        changelog: Kas pasikeitė?
-        text: Paslaugų sąlygos
-      terms_of_service_generator:
-        admin_email: El. pašto adresas, skirtas teisiniams pranešimams
-        arbitration_address: Fizinis adresas pranešimams apie arbitražą
-        arbitration_website: Svetainė pranešimams apie arbitražą
-        choice_of_law: Teisės pasirinkimas
-        dmca_address: Fizinis adresas, skirtas DMCA / autorinių teisių pranešimams
-        dmca_email: El. pašto adresas, skirtas DMCA / autorinių teisių pranešimams
-        domain: Domenas
-        jurisdiction: Teisinis teismingumas
-        min_age: Mažiausias amžius
       user:
-        date_of_birth_1i: Diena
-        date_of_birth_2i: Mėnuo
-        date_of_birth_3i: Metai
         role: Vaidmuo
         time_zone: Laiko juosta
       user_role:
diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml
index 287ce36a5d..5293623e3f 100644
--- a/config/locales/simple_form.lv.yml
+++ b/config/locales/simple_form.lv.yml
@@ -3,14 +3,14 @@ lv:
   simple_form:
     hints:
       account:
-        attribution_domains: Viens katrā līnijā. Aizsargā no nepatiesa attiecinājuma.
+        attribution_domains_as_text: Viens katrā līnijā. Aizsargā no nepatiesa attiecinājuma.
         discoverable: Tavas publiskās ziņas un profils var tikt piedāvāti vai ieteikti dažādās Mastodon vietās, un tavs profils var tikt ieteikts citiem lietotājiem.
         display_name: Tavs pilnais vārds vai tavs joku vārds.
         fields: Tava mājas lapa, vietniekvārdi, vecums, viss, ko vēlies.
         indexable: Tavi publiskie ieraksti var tikt parādīti Mastodon meklēšanas iznākumā. Cilvēki, kuri ir mijiedarbojušies ar Taviem ierakstiem, var tos meklēt neatkarīgi no tā.
         note: 'Tu vari @minēt citus cilvēkus vai #mirkļbirkas.'
         show_collections: Cilvēki varēs pārlūkot Tavus sekotājus un sekojamos. Cilvēki, kuriem Tu seko, redzēs, ka Tu seko viņiem neatkarīgi no tā.
-        unlocked: Cilvēki varēs Tev sekot bez apstiprinājuma pieprasīšanas. Jānoņem atzīme, ja vēlies pārskatīt sekošanas pieprasījumus un izvēlēties, vai apstiprināt vai noraidīt jaunus sekotājus.
+        unlocked: Cilvēki varēs sekot jums, neprasot apstiprinājumu. Noņemiet atzīmi no izvēles rūtiņas, ja vēlaties skatīt sekošanas pieprasījumus un izvēlēties, pieņemt vai noraidīt jaunos abonentus.
       account_alias:
         acct: Norādi konta lietotājvārdu@domēnu, no kura vēlies pārvākties
       account_migration:
@@ -25,8 +25,8 @@ lv:
         type_html: Izvēlies, ko darīt ar <strong>%{acct}</strong>
         types:
           disable: Neļauj lietotājam izmantot savu kontu, bet neizdzēs vai neslēp tā saturu.
-          none: Šis ir izmantojams, lai nosūtītu lietotājam brīdinājumu bez jebkādu citu darbību izraisīšanas.
-          sensitive: Visus šī lietotāja informācijas nesēju pielikumus uzspiesti atzīmēt kā jūtīgus.
+          none: Izmanto šo, lai nosūtītu lietotājam brīdinājumu, neradot nekādas citas darbības.
+          sensitive: Piespiest visus šī lietotāja multivides pielikumus atzīmēt kā sensitīvus.
           silence: Neļaut lietotājam veikt ierakstus ar publisku redzamību, paslēpt viņa ierakstus un paziņojumus no cilvēkiem, kas tam neseko. Tiek aizvērti visi ziņojumi par šo kontu.
           suspend: Novērs jebkādu mijiedarbību no šī konta vai uz to un dzēs tā saturu. Atgriežams 30 dienu laikā. Tiek aizvērti visi šī konta pārskati.
         warning_preset_id: Neobligāts. Tu joprojām vari pievienot pielāgotu tekstu sākotnējās iestatīšanas beigās
@@ -45,8 +45,8 @@ lv:
         context: Viens vai vairāki konteksti, kur jāpiemēro filtrs
         current_password: Drošības nolūkos, lūdzu, ievadi pašreizējā konta paroli
         current_username: Lai apstiprinātu, lūdzu, ievadi pašreizējā konta paroli
-        digest: Tiek nosūtīts tikai pēc ilgstošas bezdarbības un tikai tad, ja savas prombūtnes laikā saņēmi jebkādas personīgas ziņas
-        email: Tev tiks nosūtīts apstiprinājuma e-pasta ziņojums
+        digest: Tiek nosūtīts tikai pēc ilgstošas bezdarbības un tikai tad, ja savas prombūtnes laikā esi saņēmis jebkādas personīgas ziņas
+        email: Tev tiks nosūtīts apstiprinājuma e-pasts
         header: WEBP, PNG, GIF vai JPG. Ne vairāk kā %{size}. Tiks samazināts līdz %{dimensions}px
         inbox_url: Nokopē URL no tā releja sākumlapas, kuru vēlies izmantot
         irreversible: Filtrētās ziņas neatgriezeniski pazudīs, pat ja filtrs vēlāk tiks noņemts
@@ -56,11 +56,10 @@ lv:
         scopes: Kuriem API lietotnei būs ļauts piekļūt. Ja atlasa augstākā līmeņa tvērumu, nav nepieciešamas atlasīt atsevišķus.
         setting_aggregate_reblogs: Nerādīt jaunus izcēlumus ziņām, kas nesen tika palielinātas (ietekmē tikai nesen saņemtos palielinājumus)
         setting_always_send_emails: Parasti e-pasta paziņojumi netiek sūtīti, kad aktīvi izmantojat Mastodon
-        setting_default_sensitive: Pēc noklusējuma jūtīgi informācijas nesēji ir paslēpti, un tos var atklāt ar klikšķi
-        setting_display_media_default: Paslēpt informācijas nesējus, kas atzīmēti kā jūtīgi
+        setting_default_sensitive: Sensitīva multivide pēc noklusējuma ir paslēpti, un tos var atklāt, noklikšķinot
+        setting_display_media_default: Paslēpt multividi, kas atzīmēta kā sensitīva
         setting_display_media_hide_all: Vienmēr slēpt multividi
         setting_display_media_show_all: Vienmēr rādīt multividi
-        setting_system_scrollbars_ui: Attiecas tikai uz darbvirsmas pārlūkiem, kuru pamatā ir Safari vai Chrome
         setting_use_blurhash: Pāreju pamatā ir paslēpto uzskatāmo līdzekļu krāsas, bet saturs tiek padarīts neskaidrs
         setting_use_pending_items: Paslēpt laika skalas atjauninājumus aiz klikšķa, nevis ar automātisku plūsmas ritināšanu
         username: Tu vari lietot burtus, ciparus un zemsvītras
@@ -75,21 +74,19 @@ lv:
       filters:
         action: Izvēlies, kuru darbību veikt, ja ziņa atbilst filtram
         actions:
-          blur: Paslēpt informācijas nesējus aiz brīdinājuma, nepaslēpjot tekstu
           hide: Paslēp filtrēto saturu pilnībā, izturoties tā, it kā tas neeksistētu
           warn: Paslēp filtrēto saturu aiz brīdinājuma, kurā minēts filtra nosaukums
       form_admin_settings:
         activity_api_enabled: Vietēji publicēto ziņu, aktīvo lietotāju un jauno reģistrāciju skaits nedēļas kopās
         app_icon: WEBP, PNG, GIF vai JPG. Mobilajās ierīcēs aizstāj noklusējuma lietotnes ikonu ar pielāgotu.
         backups_retention_period: Lietotājiem ir iespēja izveidot savu ierakstu arhīvu lejupielādēšanai vēlāk. Kad iestatīta pozitīva vērtība, šie arhīvi tiks automātiski izdzēsti no krātuves pēc norādītā dienu skaita.
-        bootstrap_timeline_accounts: Šie konti tiks piesprausti jauno lietotāju sekošanas ieteikumu augšdaļā.
+        bootstrap_timeline_accounts: Šie konti tiks piesprausti jauno lietotāju ieteikumu augšdaļā.
         closed_registrations_message: Tiek rādīts, kad reģistrēšanās ir slēgta
         content_cache_retention_period: Visi ieraksti no citiem serveriem (tajā skaitā pastiprinājumi un atbildes) tiks izdzēsti pēc norādītā dienu skaita, neņemot vērā vietēja lietotāja mijiedarbību ar šādiem ierakstiem. Tas ietver ierakstus, kurus vietējs lietotājs ir atzīmējis kā grāmatzīmi vai pievienojis izlasē. Tiks zaudēti arī privāti pieminējumi starp lietotājiem no dažādiem serveriem, un tos nebūs iespējams atgūt. Šī iestatījuma izmantošana ir paredzēta īpašam nolūkam paredzētiem serveriem un neatbilst tam, ko sagaida vairums lietotāju, kad pielietots vispārējas izmantošanas serveros.
         custom_css: Vari lietot pielāgotus stilus Mastodon tīmekļa versijā.
         favicon: WEBP, PNG, GIF vai JPG. Aizstāj noklusējuma Mastodon favikonu ar pielāgotu.
         mascot: Ignorē ilustrāciju uzlabotajā tīmekļa saskarnē.
         media_cache_retention_period: Informācijas nesēju datnes no ierakstiem, kurus ir veikuši attālie lietotāji, tiek kešoti šajā serverī. Kad ir iestatīta apstiprinoša vērtība, informācijas nesēji tiks izdzēsti pēc norādītā dienu skaita. Ja informācijas nesēju dati tiks pieprasīti pēc tam, kad tie tika izdzēsti, tie tiks atkārtoti lejupielādēti, ja avota saturs joprojām būs pieejams. Saišu priekšskatījuma karšu vaicājumu biežuma ierobežojumu dēļ ir ieteicams iestatīt šo vērtību vismaz 14 dienas vai saišu priekšskatījuma kartes netiks atjauninātas pēc pieprasījuma pirms tā laika.
-        min_age: Lietotājiem tiks lūgts apstiprināt viņu dzimšanas datumu reģistrācijas laikā
         peers_api_enabled: Domēna vārdu saraksts, ar kuriem šis serveris ir saskāries fediversā. Šeit nav iekļauti dati par to, vai tu veic federāciju ar noteiktu serveri, tikai tavs serveris par to zina. To izmanto dienesti, kas apkopo statistiku par federāciju vispārīgā nozīmē.
         profile_directory: Profilu direktorijā ir uzskaitīti visi lietotāji, kuri ir izvēlējušies būt atklājami.
         require_invite_text: Ja nepieciešama pašrocīga apstiprināšana, lai pierakstītos, teksta “Kāpēc vēlies pievienoties?” ievade jāpadara par nepieciešamu, nevis izvēles
@@ -125,24 +122,15 @@ lv:
         hint: Izvēles. Sniedz vairāk informācijas par noteikumu
         text: Jāapraksta nosacījums vai prasība šī servera lietotājiem. Jāmēģina to veidot īsu un vienkāršu
       sessions:
-        otp: 'Jāievada tālruņa lietotnes izveidots divpakāpju kods vai jāizmanto viens no saviem atkopes kodiem:'
+        otp: 'Ievadi divfaktoru kodu, ko ģenerējusi tava tālruņa lietotne, vai izmanto kādu no atkopšanas kodiem:'
         webauthn: Ja tā ir USB atslēga, noteikti ievieto to un, ja nepieciešams, pieskaries tai.
       settings:
         indexable: Tava profila lapa var tikt parādīta Google, Bing un citu meklēšanas dzinēju rezultātos.
         show_application: Tu vienmēr varēsi redzēt, kura lietotne publicēja tavu ziņu.
       tag:
         name: Tu vari mainīt tikai burtu lielumu, piemēram, lai tie būtu vieglāk lasāmi
-      terms_of_service:
-        changelog: Var veidot ar Markdown pierakstu.
-        effective_date: Saprātīgs laika logs var būt no 10 līdz 30 dienām no dienas, kad lietotāji tiek apziņoti.
-        text: Var veidot ar Markdown pierakstu.
-      terms_of_service_generator:
-        arbitration_address: Var būt tāda pati kā augstāk esošā fiziskā adrese vai "N/A", ja tiek izmantota e-pasta adrese.
-        arbitration_website: Var būt tīmekļa veidlapa vai "N/A", ja tiek izmantots e-pasts.
-        domain: Sniegtā tiešsaistas pakalpojuma neatkārtojama identifikācija.
       user:
         chosen_languages: Ja ieķeksēts, publiskos laika grafikos tiks parādītas tikai ziņas noteiktajās valodās
-        date_of_birth: Mums jāpārliecinās, ka jums ir vismaz %{age} gadi, lai varētu izmantot Mastodonu. Mēs neuzglabāsim šo informāciju.
         role: Loma nosaka, kādas lietotājam ir atļaujas.
       user_role:
         color: Krāsa, kas jāizmanto lomai visā lietotāja saskarnē, kā RGB hex formātā
@@ -156,7 +144,7 @@ lv:
         url: Kur notikumi tiks nosūtīti
     labels:
       account:
-        attribution_domains: Tīmekļvietnes, kurām ir tiesības uzskaitīt Tevi
+        attribution_domains_as_text: Tīmekļvietnes, kurām ir tiesības uzskaitīt Tevi
         discoverable: Funkcijas profils un ziņas atklāšanas algoritmos
         fields:
           name: Marķējums
@@ -179,7 +167,7 @@ lv:
         types:
           disable: Iesaldēt
           none: Nosūtīt brīdinājumu
-          sensitive: Jūtīgs
+          sensitive: Sensitīvs
           silence: Ierobežot
           suspend: Apturēt
         warning_preset_id: Lietot iepriekš iestatītus brīdinājumus
@@ -213,7 +201,7 @@ lv:
         max_uses: Maksimālais lietojumu skaits
         new_password: Jauna parole
         note: Par sevi
-        otp_attempt: Divpakāpju kods
+        otp_attempt: Divfaktoru kods
         password: Parole
         phrase: Atslēgvārds vai frāze
         setting_advanced_layout: Iespējot paplašināto tīmekļa saskarni
@@ -223,7 +211,7 @@ lv:
         setting_boost_modal: Rādīt apstiprinājuma dialogu pirms izcelšanas
         setting_default_language: Publicēšanas valoda
         setting_default_privacy: Publicēšanas privātums
-        setting_default_sensitive: Vienmēr atzīmēt informācijas nesējus kā jūtīgus
+        setting_default_sensitive: Atļaut atzīmēt multividi kā sensitīvu
         setting_delete_modal: Parādīt apstiprinājuma dialogu pirms ziņas dzēšanas
         setting_disable_hover_cards: Atspējot profila priekšskatījumu pēc kursora novietošanas
         setting_disable_swiping: Atspējot vilkšanas kustības
@@ -233,10 +221,8 @@ lv:
         setting_display_media_show_all: Parādīt visu
         setting_expand_spoilers: Vienmēr izvērst ziņas, kas apzīmētas ar brīdinājumiem par saturu
         setting_hide_network: Slēpt savu sociālo grafu
-        setting_missing_alt_text_modal: Rādīt apstiprināšanas lodziņu pirms informācijas nesēju bez aprakstošā teksta iesūtīšanas
         setting_reduce_motion: Ierobežot kustību animācijās
         setting_system_font_ui: Lietot sistēmas noklusējuma fontu
-        setting_system_scrollbars_ui: Lietot sistēmas noklusējuma ritjoslu
         setting_theme: Vietnes motīvs
         setting_trends: Parādīt šodienas tendences
         setting_unfollow_modal: Parādīt apstiprinājuma dialogu pirms pārtraukt kādam sekot
@@ -268,7 +254,6 @@ lv:
         favicon: Favikona
         mascot: Pielāgots talismans (mantots)
         media_cache_retention_period: Multivides kešatmiņas saglabāšanas periods
-        min_age: Nepieciešamais minimālais vecums
         peers_api_enabled: Publicēt API atklāto serveru sarakstu
         profile_directory: Iespējot profila direktoriju
         registrations_mode: Kurš drīkst pieteikties
@@ -332,19 +317,7 @@ lv:
         name: Tēmturis
         trendable: Atļaut šim tēmturim parādīties zem tendencēm
         usable: Ļaut ierakstos vietēji izmantot šo tēmturi
-      terms_of_service:
-        changelog: Kas ir mainījies?
-        effective_date: Spēkā stāšanās datums
-        text: Pakalpojuma izmantošanas nosacījumi
-      terms_of_service_generator:
-        admin_email: E-pasta adrese juridiskiem paziņojumiem
-        choice_of_law: Likuma izvēle
-        domain: Domēna vārds
-        min_age: Mazākais pieļaujamais vecums
       user:
-        date_of_birth_1i: Diena
-        date_of_birth_2i: Mēnesis
-        date_of_birth_3i: Gads
         role: Loma
         time_zone: Laika josla
       user_role:
diff --git a/config/locales/simple_form.ms.yml b/config/locales/simple_form.ms.yml
index d3670a1c86..4f6dd7c0a1 100644
--- a/config/locales/simple_form.ms.yml
+++ b/config/locales/simple_form.ms.yml
@@ -9,6 +9,7 @@ ms:
         indexable: Kiriman awam anda mungkin muncul dalam hasil carian di Mastodon. Orang yang telah berinteraksi dengan kiriman anda mungkin boleh mencarinya.
         note: 'Anda boleh @menyebut orang lain atau #hashtags.'
         show_collections: Orang akan dapat menyemak imbas ikutan dan pengikut anda. Orang yang anda ikuti akan melihat bahawa anda tetap mengikuti mereka.
+        unlocked: Orang akan dapat mengikuti anda tanpa meminta kelulusan. Nyahtanda jika anda ingin menyemak permintaan ikutan dan pilih sama ada untuk menerima atau menolak pengikut baharu.
       account_alias:
         acct: Tentukan namapengguna@domain akaun yang ingin anda alihkan daripada
       account_migration:
@@ -120,8 +121,6 @@ ms:
         show_application: Anda akan sentiasa dapat melihat apl yang menerbitkan siaran anda tanpa mengira.
       tag:
         name: Anda hanya boleh menukar selongsong huruf, sebagai contoh, untuk menjadikannya lebih mudah dibaca
-      terms_of_service_generator:
-        choice_of_law: City, region, territory or state the internal substantive laws of which shall govern any and all claims.
       user:
         chosen_languages: Apabila disemak, hanya siaran dalam bahasa terpilih akan dipaparkan dalam garis masa awam
       user_role:
diff --git a/config/locales/simple_form.my.yml b/config/locales/simple_form.my.yml
index 474b0f6cb4..abcb11bdaa 100644
--- a/config/locales/simple_form.my.yml
+++ b/config/locales/simple_form.my.yml
@@ -9,6 +9,7 @@ my:
         indexable: သင်၏ အများမြင်ပို့စ်များသည် Mastodon ရှိ ရှာဖွေမှုရလဒ်များတွင် ပေါ်လာနိုင်သည်။ သင့်ပို့စ်များမှတစ်ဆင့် အပြန်အလှန်တုံ့ပြန်ပြီး ရှာဖွေနိုင်ပါမည်။
         note: 'သင်သည် အခြားသူများ သို့မဟုတ် #hashtag များကို @mention ဖြင့် ဖော်ပြနိုင်သည်။'
         show_collections: သင်စောင့်ကြည့်သူများနှင့် သင့်ကိုစောင့်ကြည့်သူများမှတစ်ဆင့် ရှာဖွေနိုင်မည်ဖြစ်သည်။ သင်စောင့်ကြည့်သူများသည် သင်သူတို့ကို မည်သို့စောင့်ကြည့်သည်ကို တွေ့ရလိမ့်မည်။
+        unlocked: ခွင့်ပြုချက်မတောင်းဘဲ လူများက သင့်ကိုစောင့်ကြည့်နိုင်ပါမည်။ စောင့်ကြည့်ရန်အတွက် တောင်းဆိုချက်များထားရှိလိုပါက အမှန်ခြစ်ဖြုတ်ပြီး စောင့်ကြည့်သူသစ်များကို လက်ခံခြင်း သို့မဟုတ် ငြင်းပယ်ခြင်းလည်း အမှန်ခြစ်ဖြုတ်နိုင်ပါသည်။
       account_alias:
         acct: ပြောင်းရွှေ့မည့်အကောင့်မှ username@domain ကို သတ်မှတ်ပါ
       account_migration:
diff --git a/config/locales/simple_form.nan.yml b/config/locales/simple_form.nan.yml
index d9049a784b..aec73b64e1 100644
--- a/config/locales/simple_form.nan.yml
+++ b/config/locales/simple_form.nan.yml
@@ -5,6 +5,7 @@ nan:
       account:
         display_name: Lí ê全名á是別號。
         fields: Lí ê頭頁、代名詞、年歲,kap其他beh分享ê。
+        unlocked: 逐ê m̄免受批准就ē當tuè lí,若是lí想beh審查跟綴ê請求,揀beh准á是拒絕跟tuè ê,請毋通勾。
       defaults:
         password: 用 8 ê字元以上
         setting_display_media_hide_all: 一直khàm掉媒體
diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml
index 6029698bd7..f50e223e8c 100644
--- a/config/locales/simple_form.nl.yml
+++ b/config/locales/simple_form.nl.yml
@@ -3,7 +3,7 @@ nl:
   simple_form:
     hints:
       account:
-        attribution_domains: Eén per regel. Beschermt tegen ongeldige attributies.
+        attribution_domains_as_text: Eén per regel. Beschermt tegen ongeldige attributies.
         discoverable: Jouw openbare berichten kunnen worden uitgelicht op verschillende plekken binnen Mastodon en jouw account kan worden aanbevolen aan andere gebruikers.
         display_name: Jouw volledige naam of een leuke bijnaam.
         fields: Jouw website, persoonlijke voornaamwoorden, leeftijd, alles wat je maar kwijt wilt.
@@ -60,7 +60,6 @@ nl:
         setting_display_media_default: Als gevoelig gemarkeerde media verbergen
         setting_display_media_hide_all: Media altijd verbergen
         setting_display_media_show_all: Media altijd tonen
-        setting_system_scrollbars_ui: Alleen van toepassing op desktopbrowsers gebaseerd op Safari en Chrome
         setting_use_blurhash: Wazige kleurovergangen zijn gebaseerd op de kleuren van de verborgen media, waarmee elk detail verdwijnt
         setting_use_pending_items: De tijdlijn wordt bijgewerkt door op het aantal nieuwe items te klikken, in plaats van dat deze automatisch wordt bijgewerkt
         username: Je kunt letters, cijfers en underscores gebruiken
@@ -75,7 +74,6 @@ nl:
       filters:
         action: Kies welke acties uitgevoerd moeten wanneer een bericht overeenkomt met het filter
         actions:
-          blur: Media verbergen achter een waarschuwing, zonder de tekst zelf te verbergen
           hide: Verberg de gefilterde inhoud volledig, alsof het niet bestaat
           warn: Verberg de gefilterde inhoud achter een waarschuwing, met de titel van het filter als waarschuwingstekst
       form_admin_settings:
@@ -89,7 +87,6 @@ nl:
         favicon: WEBP, PNG, GIF of JPG. Vervangt de standaard Mastodon favicon met een aangepast pictogram.
         mascot: Overschrijft de illustratie in de geavanceerde webomgeving.
         media_cache_retention_period: Mediabestanden van berichten van externe gebruikers worden op jouw server in de cache opgeslagen. Indien ingesteld op een positieve waarde, worden media verwijderd na het opgegeven aantal dagen. Als de mediagegevens worden opgevraagd nadat ze zijn verwijderd, worden ze opnieuw gedownload wanneer de originele inhoud nog steeds beschikbaar is. Vanwege beperkingen op hoe vaak linkvoorbeelden sites van derden raadplegen, wordt aanbevolen om deze waarde in te stellen op ten minste 14 dagen. Anders worden linkvoorbeelden niet op aanvraag bijgewerkt.
-        min_age: Gebruikers krijgen tijdens hun inschrijving de vraag om hun geboortedatum te bevestigen
         peers_api_enabled: Een lijst met domeinnamen die deze server heeft aangetroffen in de fediverse. Er zijn hier geen gegevens inbegrepen over de vraag of je verbonden bent met een bepaalde server, alleen dat je server er van weet. Dit wordt gebruikt door diensten die statistieken over de federatie in algemene zin verzamelen.
         profile_directory: De gebruikersgids bevat een lijst van alle gebruikers die ervoor gekozen hebben om ontdekt te kunnen worden.
         require_invite_text: Maak het invullen van "Waarom wil je je hier registreren?" verplicht in plaats van optioneel, wanneer registraties handmatig moeten worden goedgekeurd
@@ -132,23 +129,8 @@ nl:
         show_application: Je kunt zelf altijd zien met welke app je een bericht hebt geplaatst.
       tag:
         name: Je kunt elk woord met een hoofdletter beginnen, om zo bijvoorbeeld de tekst leesbaarder te maken
-      terms_of_service:
-        changelog: Kan worden opgemaakt met Markdown.
-        effective_date: Een redelijke periode kan variëren van 10 tot 30 dagen vanaf de datum waarop je jouw gebruikers op de hoogte stelt.
-        text: Kan worden opgemaakt met Markdown.
-      terms_of_service_generator:
-        admin_email: Juridische mededelingen zijn o. a. counter-notices, gerechterlijke bevelen, takedown-requests en handhavingsverzoeken.
-        arbitration_address: Kan hetzelfde zijn als bovenstaand vestigingsadres of "N/A" bij gebruik van e-mail.
-        arbitration_website: Kan een webformulier zijn, of "N/A" wanneer e-mail wordt gebruikt.
-        choice_of_law: Stad, regio, grondgebied of staat waar de interne materiële wetten van toepassing zijn op alle aanspraken.
-        dmca_address: 'Gebruik voor beheerders in de VS: het adres dat is geregistreerd in de DMCA Designated Agent Directory. Op verzoek is er een postbuslijst beschikbaar. Gebruik het DMCA Designated Agent Post Office Box Waiver Request om het Copyright Office te e-mailen en te beschrijven dat je een vanaf huis opererende inhoudsmoderator bent, die wraak of vergelding vreest voor je moderator-acties en daarom een postbus moet gebruiken om jouw huisadres uit het publieke domein te houden.'
-        dmca_email: Kan hetzelfde e-mailadres zijn dat gebruikt wordt voor "E-mailadres voor juridische berichten" hierboven.
-        domain: Een unieke identificatie van de online dienst die je levert.
-        jurisdiction: Vermeld het land waar de persoon woont die de rekeningen betaalt. Is het een bedrijf of iets dergelijks, vermeld dan het land waar het ingeschreven staat en de stad, de regio, het grondgebied of de staat, voor zover van toepassing.
-        min_age: Mag niet lager zijn dan de minimale vereiste leeftijd volgens de wetten van jouw jurisdictie.
       user:
         chosen_languages: Alleen berichten in de aangevinkte talen worden op de openbare tijdlijnen getoond
-        date_of_birth: We moeten ervoor zorgen dat je tenminste %{age} bent om Mastodon te gebruiken. Dit wordt niet opgeslagen.
         role: De rol bepaalt welke rechten de gebruiker heeft.
       user_role:
         color: Kleur die gebruikt wordt voor de rol in de UI, als RGB in hexadecimale formaat
@@ -162,7 +144,7 @@ nl:
         url: Waar gebeurtenissen naartoe worden verzonden
     labels:
       account:
-        attribution_domains: Websites die jou credit mogen geven
+        attribution_domains_as_text: Websites die jou credit mogen geven
         discoverable: Jouw account en berichten laten uitlichten door Mastodon
         fields:
           name: Label
@@ -239,10 +221,8 @@ nl:
         setting_display_media_show_all: Alles tonen
         setting_expand_spoilers: Berichten met inhoudswaarschuwingen altijd uitklappen
         setting_hide_network: Jouw volgers en wie je volgt verbergen
-        setting_missing_alt_text_modal: Bevestigingsvenster tonen voor het plaatsen van media zonder alt-tekst
         setting_reduce_motion: Beweging in animaties verminderen
         setting_system_font_ui: Standaardlettertype van het systeem gebruiken
-        setting_system_scrollbars_ui: Standaard scrollbalk van het systeem gebruiken
         setting_theme: Thema website
         setting_trends: Trends van vandaag tonen
         setting_unfollow_modal: Vraag voor het ontvolgen van iemand een bevestiging
@@ -261,7 +241,6 @@ nl:
         name: Hashtag
       filters:
         actions:
-          blur: Media met een waarschuwing verbergen
           hide: Volledig verbergen
           warn: Met een waarschuwing verbergen
       form_admin_settings:
@@ -275,7 +254,6 @@ nl:
         favicon: Favicon
         mascot: Aangepaste mascotte (legacy)
         media_cache_retention_period: Bewaartermijn mediacache
-        min_age: Vereiste minimumleeftijd
         peers_api_enabled: Lijst van bekende servers via de API publiceren
         profile_directory: Gebruikersgids inschakelen
         registrations_mode: Wie kan zich registreren
@@ -339,24 +317,7 @@ nl:
         name: Hashtag
         trendable: Goedkeuren dat deze hashtag onder trends te zien valt
         usable: Berichten toestaan deze hashtag lokaal te gebruiken
-      terms_of_service:
-        changelog: Wat is veranderd?
-        effective_date: Ingangsdatum
-        text: Gebruiksvoorwaarden
-      terms_of_service_generator:
-        admin_email: E-mailadres voor juridische meldingen
-        arbitration_address: Vestigingsadres voor arbitrage-mededelingen
-        arbitration_website: Website voor het indienen van arbitrage-mededelingen
-        choice_of_law: Keuze van rechtsgebied
-        dmca_address: Vestigingsadres voor DMCA/auteursrecht-mededelingen
-        dmca_email: E-mailadres voor DMCA/auteursrecht-mededelingen
-        domain: Domein
-        jurisdiction: Jurisdictie
-        min_age: Minimumleeftijd
       user:
-        date_of_birth_1i: Dag
-        date_of_birth_2i: Maand
-        date_of_birth_3i: Jaar
         role: Rol
         time_zone: Tijdzone
       user_role:
diff --git a/config/locales/simple_form.nn.yml b/config/locales/simple_form.nn.yml
index 1a33a4b91d..fc47c7164c 100644
--- a/config/locales/simple_form.nn.yml
+++ b/config/locales/simple_form.nn.yml
@@ -3,7 +3,7 @@ nn:
   simple_form:
     hints:
       account:
-        attribution_domains: Ein per line. Vernar mot falske krediteringar.
+        attribution_domains_as_text: Ein per line. Vernar mot falske krediteringar.
         discoverable: Dei offentlege innlegga dine og profilen din kan dukka opp i tilrådingar på ulike stader på Mastodon, og profilen din kan bli føreslegen for andre folk.
         display_name: Ditt fulle namn eller ditt tøysenamn.
         fields: Heimesida di, pronomen, alder, eller kva du måtte ynskje.
@@ -60,7 +60,6 @@ nn:
         setting_display_media_default: Gøym media som er merka som sensitive
         setting_display_media_hide_all: Alltid skjul alt media
         setting_display_media_show_all: Vis alltid media
-        setting_system_scrollbars_ui: Gjeld berre skrivebordsnettlesarar som er bygde på Safari og Chrome
         setting_use_blurhash: Overgangar er basert på fargane til skjulte grafikkelement, men gjer detaljar utydelege
         setting_use_pending_items: Gøym tidslineoppdateringar bak eit klikk, i staden for å rulla ned automatisk
         username: Du kan bruka bokstavar, tal og understrekar
@@ -75,7 +74,6 @@ nn:
       filters:
         action: Velg kva som skal gjerast når eit innlegg samsvarar med filteret
         actions:
-          blur: Gøym media bak ei åtvaring utan å gøyme sjølve teksten
           hide: Skjul filtrert innhald fullstendig og lat som om det ikkje finst
           warn: Skjul det filtrerte innhaldet bak ei åtvaring som nemner tittelen på filteret
       form_admin_settings:
@@ -131,18 +129,6 @@ nn:
         show_application: Du vil uansett alltid kunna sjå kva app som la ut innlegga dine.
       tag:
         name: Du kan berre endra bruken av store/små bokstavar, t. d. for å gjera det meir leseleg
-      terms_of_service:
-        changelog: Du kan bruka Markdown-syntaks for struktur.
-        text: Du kan bruka Markdown-syntaks for struktur.
-      terms_of_service_generator:
-        admin_email: Juridiske merknader kan vera motsegner, rettsavgjerder, orskurdar eller førespurnader om sletting.
-        arbitration_address: Kan vere lik den fysiske adressa over, eller "N/A" viss du bruker epost.
-        arbitration_website: Kan vere eit nettskjema eller "N/A" viss du bruker e-post.
-        dmca_address: For US operators, use the address registered in the DMCA Designated Agent Directory. A P.O. Box listing is available upon direct request, use the DMCA Designated Agent Post Office Box Waiver Request to email the Copyright Office and describe that you are a home-based content moderator who fears revenge or retribution for your actions and need to use a P.O. Box to remove your home address from public view.
-        dmca_email: Kan vere same e-post som brukast i "E-postadresse for juridiske meldingar" ovanfor.
-        domain: Noko som identifiserer den nettenesta du tilbyr.
-        jurisdiction: Skriv kva land den som betaler rekningane bur i. Viss det er eit firma eller ein annan organisasjon, skriv du landet der organisasjonen held til, samt adressa som trengst.
-        min_age: Skal ikkje vere under minstealder som krevst av lover i jurisdiksjonen din.
       user:
         chosen_languages: Når merka vil berre tuta på dei valde språka synast på offentlege tidsliner
         role: Rolla kontrollerer kva løyve brukaren har.
@@ -158,7 +144,7 @@ nn:
         url: Kvar hendingar skal sendast
     labels:
       account:
-        attribution_domains: Nettstader som har lov å kreditera deg
+        attribution_domains_as_text: Nettstader som har lov å kreditera deg
         discoverable: Ta med profilen og innlegga i oppdagingsalgoritmar
         fields:
           name: Merkelapp
@@ -235,10 +221,8 @@ nn:
         setting_display_media_show_all: Vis alle
         setting_expand_spoilers: Vid alltid ut tut som er merka med innhaldsåtvaringar
         setting_hide_network: Gøym nettverket ditt
-        setting_missing_alt_text_modal: Vis stadfestingsdialog før du legg ut media utan alt-tekst
         setting_reduce_motion: Minsk rørsle i animasjonar
         setting_system_font_ui: Bruk standardskrifttypen på systemet
-        setting_system_scrollbars_ui: Bruk standardrullefeltet til systemet
         setting_theme: Sidetema
         setting_trends: Vis kva som er populært i dag
         setting_unfollow_modal: Vis stadfesting før du sluttar å fylgja nokon
@@ -257,7 +241,6 @@ nn:
         name: Emneknagg
       filters:
         actions:
-          blur: Gøym media med ei åtvaring
           hide: Gøym heilt
           warn: Gøym med ei åtvaring
       form_admin_settings:
@@ -334,18 +317,6 @@ nn:
         name: Emneknagg
         trendable: Tillat denne emneknaggen til å synast under trendar
         usable: Godta at innlegga kan bruka denne emneknaggen lokalt
-      terms_of_service:
-        changelog: Kva er endra?
-        text: Bruksvilkår
-      terms_of_service_generator:
-        admin_email: Epostadresse for juridiske merknader
-        arbitration_address: Fysisk adresse for skilsdomsvarsel
-        arbitration_website: Nettstad for å senda inn skilsdomsvarsel
-        dmca_address: Fysisk adresse for opphavsrettsvarsel
-        dmca_email: Epostadresse for opphavsrettsvarsel
-        domain: Domene
-        jurisdiction: Rettskrins
-        min_age: Minstealder
       user:
         role: Rolle
         time_zone: Tidssone
diff --git a/config/locales/simple_form.no.yml b/config/locales/simple_form.no.yml
index c93c4d40bc..f1973b1d06 100644
--- a/config/locales/simple_form.no.yml
+++ b/config/locales/simple_form.no.yml
@@ -9,6 +9,7 @@
         indexable: Dine offentlige innlegg kan vises i søkeresultat på Mastodon. Personer som har samhandlet med innleggene dine kan finne de uansett.
         note: 'Du kan @nevne andre eller #emneknagger.'
         show_collections: Folk vil kunne bla gjennom de du følger og dine følgere. Folk du følger vil uansett se at du følger dem.
+        unlocked: Folk vil kunne følge deg uten å be om godkjenning. Fjern markeringen om du vil gjennomgå følge-forespørsler og velge om du vil akseptere eller avvise nye følgere.
       account_alias:
         acct: Spesifiser brukernavn@domene til kontoen du vil flytte fra
       account_migration:
@@ -302,10 +303,6 @@
         listable: Tillat denne emneknaggen å vises i søk og på profilmappen
         name: Emneknagg
         trendable: Tillat denne emneknaggen til å vises under trender
-      terms_of_service:
-        text: Bruksvilkår
-      terms_of_service_generator:
-        domain: Domene
       user:
         role: Rolle
         time_zone: Tidssone
diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml
index c049822b81..866872fb08 100644
--- a/config/locales/simple_form.pl.yml
+++ b/config/locales/simple_form.pl.yml
@@ -3,7 +3,7 @@ pl:
   simple_form:
     hints:
       account:
-        attribution_domains: Jedna na linię.
+        attribution_domains_as_text: Jedna na linię. Chroni przed fałszywym przypisaniem wpisów.
         discoverable: Twój profil i publiczne wpisy mogą być promowane lub polecane na Mastodonie i twój profil może być sugerowany innym użytkownikom.
         display_name: Twoje imię lub pseudonim.
         fields: Co ci się tylko podoba – twoja strona domowa, zaimki, wiek…
@@ -60,7 +60,6 @@ pl:
         setting_display_media_default: Ukrywaj zawartość multimedialną oznaczoną jako wrażliwa
         setting_display_media_hide_all: Zawsze ukrywaj zawartość multimedialną
         setting_display_media_show_all: Zawsze pokazuj zawartość multimedialną
-        setting_system_scrollbars_ui: Stosuje się tylko do przeglądarek komputerowych opartych na Safari i Chrome
         setting_use_blurhash: Gradienty są oparte na kolorach ukrywanej zawartości, ale uniewidaczniają wszystkie szczegóły
         setting_use_pending_items: Ukryj aktualizacje osi czasu za kliknięciem, zamiast automatycznego przewijania strumienia
         username: Możesz używać liter, cyfr i podkreślników
@@ -130,15 +129,6 @@ pl:
         show_application: Ty zawsze widzisz program użyty do zamieszczenia.
       tag:
         name: Możesz zmieniać tylko wielkość liter, np. aby były bardziej widoczne
-      terms_of_service:
-        changelog: Może być stworzony przy pomocy składni Markdown.
-        text: Może być stworzony przy pomocy składni Markdown.
-      terms_of_service_generator:
-        admin_email: Zawiadomienia prawne obejmują środki zapobiegawcze, nakazy sądowe, wnioski o popełnienie sprawy oraz wnioski organów ścigania.
-        choice_of_law: Miasto, region, terytorium lub stan, którego wewnętrzne prawo będzie regulowało wszelkie roszczenia.
-        dmca_address: W przypadku operatorów z USA należy użyć adresu zarejestrowanego w DMCA Designated Agent Directory. Lista skrytek pocztowych dostępna jest na bezpośrednią prośbę użytkownika. Użyj DMCA Agent Post Office Box Waiver Request, aby wysłać email do Copyright Office z informacją, że jesteś domowym administratorm treści i z powodu obawy o zemstę lub odwetu za swoje działania, musisz użyć skrytki pocztowej, żeby usunąć swój adres domowy z dostępu publicznego.
-        domain: Unikalny numer identyfikacji świadczonej przez Ciebie usługi online.
-        jurisdiction: Wymień państwo, w którym mieszkają osoby płacące rachunki. Jeżeli jest to spółka lub inny zarejestrowany podmiot, w zależności od przypadku podaj państwo, w którym jest zarejestrowany, a także miasto, region czy województwo.
       user:
         chosen_languages: Jeżeli zaznaczone, tylko wpisy w wybranych językach będą wyświetlane na publicznych osiach czasu
         role: Rola kontroluje uprawnienia użytkownika.
@@ -154,7 +144,7 @@ pl:
         url: Dokąd będą wysłane zdarzenia
     labels:
       account:
-        attribution_domains: Strony które mogą ci przypisywać autorstwo
+        attribution_domains_as_text: Strony które mogą ci przypisywać autorstwo.
         discoverable: Udostępniaj profil i wpisy funkcjom odkrywania
         fields:
           name: Nazwa
@@ -231,10 +221,8 @@ pl:
         setting_display_media_show_all: Pokaż wszystko
         setting_expand_spoilers: Zawsze rozwijaj wpisy oznaczone ostrzeżeniem o zawartości
         setting_hide_network: Ukryj swoją sieć
-        setting_missing_alt_text_modal: Pokaż okno potwierdzenia przed opublikowaniem materiałów bez pomocniczego opisu obrazów
         setting_reduce_motion: Ogranicz ruch w animacjach
         setting_system_font_ui: Używaj domyślnej czcionki systemu
-        setting_system_scrollbars_ui: Używaj domyślnego paska przewijania systemu
         setting_theme: Motyw strony
         setting_trends: Pokazuj dzisiejsze „Na czasie”
         setting_unfollow_modal: Pytaj o potwierdzenie przed cofnięciem obserwacji
@@ -329,18 +317,6 @@ pl:
         name: Hasztag
         trendable: Pozwól na wyświetlanie tego hashtagu w „Na czasie”
         usable: Pozwól na umieszczanie tego hashtagu w lokalnych wpisach
-      terms_of_service:
-        changelog: Co się zmieniło?
-        text: Warunki korzystania z usługi
-      terms_of_service_generator:
-        admin_email: Adres e-mail przeznaczony do celów prawnych
-        arbitration_address: Adres fizyczny powiadomień arbitrażowych
-        arbitration_website: Strona internetowa do składania zgłoszeń arbitrażowych
-        choice_of_law: Wybór prawa
-        dmca_address: Adres fizyczny dla zgłoszeń naruszenia DMCA/praw autorskich
-        dmca_email: Adres e-mail dla zgłoszeń naruszenia DMCA/praw autorskich
-        domain: Domena
-        jurisdiction: Jurysdykcja
       user:
         role: Rola
         time_zone: Strefa czasowa
diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml
index 64221777d7..16d7cfe445 100644
--- a/config/locales/simple_form.pt-BR.yml
+++ b/config/locales/simple_form.pt-BR.yml
@@ -3,14 +3,14 @@ pt-BR:
   simple_form:
     hints:
       account:
-        attribution_domains: Um por linha. Protege contra atribuições falsas.
+        attribution_domains_as_text: Um por linha. Protege de falsas atribuições.
         discoverable: Suas publicações e perfil públicos podem ser destaques ou recomendados em várias áreas de Mastodon, e seu perfil pode ser sugerido a outros usuários.
         display_name: Seu nome completo ou apelido.
         fields: Sua página inicial, pronomes, idade ou qualquer coisa que quiser.
         indexable: Suas publicações públicas podem aparecer nos resultados da pesquisa em Mastodon. As pessoas que interagiram com suas publicações podem conseguir pesquisá-las independentemente disso.
-        note: 'Você pode @mencionar outras pessoas ou usar #hashtags.'
-        show_collections: As pessoas podem ver seus seguidores e quem você está seguindo. Os perfis que você seguir saberão que você os segue independentemente do que selecionar.
-        unlocked: As pessoas poderão te seguir sem solicitar aprovação. Desmarque caso você queira revisar as solicitações.
+        note: 'Você pode @mencionar outras pessoas ou #hashtags.'
+        show_collections: As pessoas poderão navegar entre os seus seguidores e seguidores. As pessoas que você segue verão que você as segue independentemente disso.
+        unlocked: As pessoas poderão te seguir sem pedir aprovação. Desmarque se você deseja revisar pedidos e escolher se aceita ou rejeita novos seguidores.
       account_alias:
         acct: Especifique o usuário@domínio de onde veio
       account_migration:
@@ -27,8 +27,8 @@ pt-BR:
           disable: Impede o usuário de usar a conta, porém sem excluí-la ou suspendê-la.
           none: Use isto para enviar uma advertência ao usuário, sem nenhuma outra ação.
           sensitive: Marca todas as mídias do usuário como sensível.
-          silence: Impede o usuário de postar publicamente, restringe suas publicações e notificações a somente quem o segue. Encerra todas as denúncias contra esta conta.
-          suspend: Impede qualquer interação de ou para esta conta e exclui seu conteúdo. Reversível dentro de 30 dias. Encerra todas as denúncias contra esta conta.
+          silence: Impede o usuário de enviar postagens visualmente públicas, além de ocultar suas publicações e notificações dos que não o seguem. Ademais, fecha todas as denúncias contra esta conta.
+          suspend: Impede qualquer interação de ou para esta conta e exclui seu conteúdo. Reversível dentro de 30 dias. Ademais, fecha todas as denúncias contra esta conta.
         warning_preset_id: Opcional. Você pode adicionar texto personalizado no final da advertência pré-definida
       announcement:
         all_day: Quando marcada, apenas as datas do período serão mostradas
@@ -37,11 +37,11 @@ pt-BR:
         starts_at: Opcional. Caso o comunicado esteja vinculado a um período específico
         text: Você pode usar a sintaxe do toot. Considere o espaço que o comunicado ocupará na tela do usuário
       appeal:
-        text: Você só pode recorrer uma vez
+        text: Você só pode solicitar uma revisão uma vez
       defaults:
         autofollow: Pessoas que criarem conta através de seu convite te seguirão automaticamente
         avatar: WEBP, PNG, GIF ou JPG. No máximo %{size}. Será reduzido para %{dimensions}px
-        bot: Sinaliza aos outros de que essa conta executa principalmente ações automatizadas e pode não ser monitorada
+        bot: Essa conta executa principalmente ações automatizadas e pode não ser monitorada
         context: Um ou mais contextos onde o filtro deve atuar
         current_password: Para fins de segurança, digite a senha da conta atual
         current_username: Para confirmar, digite o nome de usuário da conta atual
@@ -54,13 +54,12 @@ pt-BR:
         password: Use pelo menos 8 caracteres
         phrase: Corresponderá independente de maiúsculas ou minúsculas, no texto ou no Aviso de Conteúdo de um toot
         scopes: Quais APIs o aplicativo vai ter permissão de acessar. Se você selecionar uma autorização de alto nível, você não precisa selecionar individualmente os outros.
-        setting_aggregate_reblogs: Não mostrar novos impulsos para publicações que já foram impulsionadas recentemente (afeta somente os impulsos mais recentes)
+        setting_aggregate_reblogs: Não mostra novos impulsos para publicações já receberam recentemente (afeta somente os impulsos mais recentes)
         setting_always_send_emails: Normalmente, as notificações por e-mail não serão enviadas enquanto você estiver usando ativamente o Mastodon
         setting_default_sensitive: Mídia sensível está oculta por padrão e pode ser revelada com um clique
         setting_display_media_default: Sempre ocultar mídia sensível
         setting_display_media_hide_all: Sempre ocultar todas as mídias
         setting_display_media_show_all: Sempre mostrar mídia sensível
-        setting_system_scrollbars_ui: Se aplica apenas para navegadores de computador baseado no Safari e Chrome
         setting_use_blurhash: O blur é baseado nas cores da imagem oculta, ofusca a maioria dos detalhes
         setting_use_pending_items: Ocultar atualizações da linha do tempo atrás de um clique ao invés de rolar automaticamente
         username: Você pode usar letras, números e underlines
@@ -75,13 +74,12 @@ pt-BR:
       filters:
         action: Escolher qual ação executar quando uma publicação corresponder ao filtro
         actions:
-          blur: Oculte a mídia com um aviso, porém mantenha o texto visível
           hide: Esconder completamente o conteúdo filtrado, comportando-se como se ele não existisse
           warn: Ocultar o conteúdo filtrado por trás de um aviso mencionando o título do filtro
       form_admin_settings:
         activity_api_enabled: Contagem de publicações locais, usuários ativos e novos usuários semanais
         app_icon: WEBP, PNG, GIF ou JPG. Sobrescrever o ícone padrão do aplicativo em dispositivos móveis com um ícone personalizado.
-        backups_retention_period: Os usuários podem gerar arquivos de suas postagens para baixar mais tarde. Quando definido como um valor positivo, esses arquivos serão automaticamente excluídos do seu armazenamento após o número especificado de dias.
+        backups_retention_period: Os usuários têm a capacidade de gerar arquivos de suas postagens para baixar mais tarde. Quando definido como um valor positivo, esses arquivos serão automaticamente excluídos do seu armazenamento após o número especificado de dias.
         bootstrap_timeline_accounts: Estas contas serão fixadas no topo das recomendações de novos usuários para seguir.
         closed_registrations_message: Exibido quando as inscrições estiverem fechadas
         content_cache_retention_period: Todas as postagens de outros servidores (incluindo boosts e respostas) serão excluídas após o número especificado de dias, sem levar a qualquer interação do usuário local com esses posts. Isto inclui postagens onde um usuário local o marcou como favorito ou favoritos. Menções privadas entre usuários de diferentes instâncias também serão perdidas e impossíveis de restaurar. O uso desta configuração destina-se a instâncias especiais de propósitos e quebra muitas expectativas dos usuários quando implementadas para uso de propósito geral.
@@ -89,7 +87,6 @@ pt-BR:
         favicon: WEBP, PNG, GIF ou JPG. Sobrescreve o favicon padrão do Mastodon com um ícone personalizado.
         mascot: Substitui a ilustração na interface web avançada.
         media_cache_retention_period: Arquivos de mídia de mensagens de usuários remotos são armazenados em cache no seu servidor. Quando definido como valor positivo, a mídia será excluída após o número especificado de dias. Se os dados da mídia forem solicitados depois de excluídos, eles serão baixados novamente, se o conteúdo fonte ainda estiver disponível. Devido a restrições de quantas vezes os cartões de visualização de links sondam sites de terceiros, é recomendado definir este valor em pelo menos 14 dias, ou pré-visualização de links não serão atualizados a pedido antes desse tempo.
-        min_age: Os usuários precisarão confirmar sua data de nascimento no cadastro
         peers_api_enabled: Uma lista de nomes de domínio que este servidor encontrou no "fediverse". Nenhum dado é incluído aqui sobre se você concorda com os padroes operacionais de um determinado servidor, apenas que o seu servidor sabe disso. Esta ferramenta é utilizado por serviços que recolhem estatísticas sob as normas da federação (grupo de empresas que concordam sob paramentros operacionais específicos), em termos gerais.
         profile_directory: O diretório de perfis lista todos os usuários que optaram por permitir que suas contas sejam descobertas.
         require_invite_text: 'Quando o cadastro de novas contas exigir aprovação manual, tornar obrigatório, ao invés de opcional, o texto de solicitação de convite: "Por que você deseja ingressar nessa comunidade?"'
@@ -132,23 +129,8 @@ pt-BR:
         show_application: Você sempre conseguirá ver qual aplicativo realizou sua publicação independentemente disso.
       tag:
         name: Você pode mudar a capitalização das letras, por exemplo, para torná-la mais legível
-      terms_of_service:
-        changelog: Pode ser estruturado com a sintaxe Markdown.
-        effective_date: Um intervalo de tempo razoável pode variar de 10 a 30 dias a partir da data em que notificar seus usuários.
-        text: Pode ser estruturado com a sintaxe Markdown.
-      terms_of_service_generator:
-        admin_email: Avisos legais incluem contra-notificações, ordens judiciais, solicitações de remoção e solicitações de órgãos de fiscalização.
-        arbitration_address: Pode ser o mesmo do endereço colocado cima, ou use "N/A" se estiver utilizando e-mail.
-        arbitration_website: Pode ser um formulário online, ou use "N/A" caso esteja utilizando um e-mail.
-        choice_of_law: Cidade, região, território ou estado cujas leis substantivas internas serão aplicáveis a todas e quaisquer reivindicações.
-        dmca_address: Para operadores dos EUA, utilize o endereço registrado no Diretório de Agentes Designados pela DMCA. Um endereço de Caixa Postal está disponível mediante solicitação direta; use a Solicitação de Isenção de Caixa Postal de Agente Designado pela DMCA para enviar um e-mail ao Escritório de Direitos Autorais, explicando que você é um moderador de conteúdo que trabalha em casa e teme vingança ou retaliação por suas ações, precisando usar uma Caixa Postal para remover seu endereço residencial da visualização pública.
-        dmca_email: Pode ser o mesmo e-mail utilizado acima em "Endereço de e-mail para avisos legais".
-        domain: Identificação única do serviço online que você está fornecendo.
-        jurisdiction: Liste o país onde quem paga as contas reside. Se for uma empresa ou outra entidade, liste o país onde ela está incorporada, e a cidade, região, território ou estado, conforme apropriado.
-        min_age: Não deve ter menos que a idade mínima exigida pelas suas leis locais.
       user:
         chosen_languages: Apenas as publicações dos idiomas selecionados serão exibidas nas linhas públicas
-        date_of_birth: Precisamos ter certeza de que você tem, no mínimo, %{age} anos para usar o Mastodon. Não armazenaremos essa informação.
         role: A função controla quais permissões o usuário tem.
       user_role:
         color: Cor a ser usada para o cargo em toda a interface do usuário, como RGB no formato hexadecimal
@@ -162,7 +144,7 @@ pt-BR:
         url: Aonde os eventos serão enviados
     labels:
       account:
-        attribution_domains: Sites autorizados a creditar você.
+        attribution_domains_as_text: Sites permitidos para credenciar você
         discoverable: Destacar perfil e publicações nos algoritmos de descoberta
         fields:
           name: Rótulo
@@ -239,10 +221,8 @@ pt-BR:
         setting_display_media_show_all: Mostrar tudo
         setting_expand_spoilers: Sempre expandir toots com Aviso de Conteúdo
         setting_hide_network: Ocultar suas relações
-        setting_missing_alt_text_modal: Mostrar caixa de diálogo de confirmação antes de postar mídia sem texto alternativo.
         setting_reduce_motion: Reduzir animações
         setting_system_font_ui: Usar fonte padrão do sistema
-        setting_system_scrollbars_ui: Usar barra de rolagem padrão do sistema
         setting_theme: Tema do site
         setting_trends: Mostrar em alta hoje
         setting_unfollow_modal: Solicitar confirmação antes de deixar de seguir alguém
@@ -261,7 +241,6 @@ pt-BR:
         name: Hashtag
       filters:
         actions:
-          blur: Oculte a mídia com um aviso
           hide: Ocultar completamente
           warn: Ocultar com um aviso
       form_admin_settings:
@@ -275,7 +254,6 @@ pt-BR:
         favicon: Favicon
         mascot: Mascote personalizado (legado)
         media_cache_retention_period: Período de retenção do cachê de mídia
-        min_age: Requisito de idade mínimia
         peers_api_enabled: Publicar lista de instâncias de servidor descobertas na API
         profile_directory: Ativar diretório de perfis
         registrations_mode: Quem pode se inscrever
@@ -339,24 +317,7 @@ pt-BR:
         name: Hashtag
         trendable: Permitir que esta hashtag fique em alta
         usable: Permitir que as publicações usem esta hashtag localmente
-      terms_of_service:
-        changelog: O que mudou?
-        effective_date: Data de vigência
-        text: Termos de Serviço
-      terms_of_service_generator:
-        admin_email: Endereço de e-mail para avisos legais
-        arbitration_address: Endereço físico para avisos de arbitragem.
-        arbitration_website: Site para submissão de notificações de arbitragem
-        choice_of_law: Lei de regência
-        dmca_address: Endereço físico para notificações de DMCA/direitos autorais
-        dmca_email: Endereço de e-mail para notificações de DMCA/direitos autorais
-        domain: Domínio
-        jurisdiction: Jurisdição legal
-        min_age: Idade mínima
       user:
-        date_of_birth_1i: Dia
-        date_of_birth_2i: Mês
-        date_of_birth_3i: Ano
         role: Cargo
         time_zone: Fuso horário
       user_role:
diff --git a/config/locales/simple_form.pt-PT.yml b/config/locales/simple_form.pt-PT.yml
index 5549ef40d6..c2ebfb0c1f 100644
--- a/config/locales/simple_form.pt-PT.yml
+++ b/config/locales/simple_form.pt-PT.yml
@@ -3,14 +3,14 @@ pt-PT:
   simple_form:
     hints:
       account:
-        attribution_domains: Um por linha. Protege contra falsas atribuições.
+        attribution_domains_as_text: Um por linha. Protege contra falsas atribuições.
         discoverable: As suas publicações e perfil públicos podem ser destacados ou recomendados em várias áreas do Mastodon e o seu perfil pode ser sugerido a outros utilizadores.
         display_name: O seu nome completo ou o seu nome divertido.
         fields: A sua página inicial, os seus pronomes, idade e tudo o que quiser.
         indexable: As suas mensagens públicas podem aparecer nos resultados da pesquisa no Mastodon. Independentemente disso, as pessoas que interagiram com as suas publicações podem ser capazes de as pesquisar.
         note: 'Pode @mencionar outras pessoas e usar #etiquetas.'
         show_collections: As pessoas podem navegar pelas listas das pessoas que segue e dos seus seguidores. Independentemente disso, as pessoas que segue verão que você as segue.
-        unlocked: As pessoas poderão segui-lo sem pedir aprovação. Desmarque se pretender rever os pedidos de seguimento e escolha se pretende aceitar ou rejeitar novos seguidores.
+        unlocked: As pessoas podem seguir-te sem pedir a tua aprovação. Desmarca se quiseres rever os pedidos para seguir e escolher se aceitas ou rejeitas os novos seguidores.
       account_alias:
         acct: Especifique o utilizador@domínio da conta de onde você deseja migrar
       account_migration:
@@ -60,7 +60,6 @@ pt-PT:
         setting_display_media_default: Esconder multimédia marcada como sensível
         setting_display_media_hide_all: Esconder sempre toda a multimédia
         setting_display_media_show_all: Mostrar sempre a multimédia
-        setting_system_scrollbars_ui: Aplica-se apenas a navegadores de desktop baseados no Safari e Chrome
         setting_use_blurhash: Os gradientes são baseados nas cores das imagens escondidas, mas ofuscam quaisquer pormenores
         setting_use_pending_items: Ocultar as atualizações da cronologia após um clique em vez de percorrer automaticamente a cronologia
         username: Podes utilizar letras, números e traços inferiores (_)
@@ -75,7 +74,6 @@ pt-PT:
       filters:
         action: Escolha qual a ação a executar quando uma publicação corresponde ao filtro
         actions:
-          blur: Esconder multimédia com um aviso à frente, sem esconder o texto
           hide: Ocultar completamente o conteúdo filtrado, comportando-se como se não existisse
           warn: Ocultar o conteúdo filtrado por trás de um aviso mencionando o título do filtro
       form_admin_settings:
@@ -89,7 +87,6 @@ pt-PT:
         favicon: WEBP, PNG, GIF ou JPG. Substitui o ícone de favorito padrão do Mastodon por um ícone personalizado.
         mascot: Sobrepõe-se à ilustração na interface web avançada.
         media_cache_retention_period: Os ficheiros multimédia de publicações feitas por utilizadores remotos são armazenados em cache no seu servidor. Quando definido para um valor positivo, os ficheiros multimédia serão eliminados após o número de dias especificado. Se os ficheiros multimédia forem solicitados depois de terem sido eliminados, serão transferidos novamente, se o conteúdo de origem ainda estiver disponível. Devido a restrições sobre a frequência com que os cartões de pré-visualização de links pesquisam sites de terceiros, recomenda-se que este valor seja definido para, pelo menos, 14 dias, ou os cartões de pré-visualização de links não serão atualizados a pedido antes desse período.
-        min_age: Os utilizadores serão convidados a confirmar a sua data de nascimento durante o processo de inscrição
         peers_api_enabled: Uma lista de nomes de domínio que este servidor encontrou no fediverso. Nenhum dado é incluído aqui sobre se você federa com um determinado servidor, apenas que o seu servidor o conhece. Este serviço é utilizado por serviços que recolhem estatísticas na federação, em termos gerais.
         profile_directory: O diretório de perfis lista todos os utilizadores que optaram por ter a sua conta a ser sugerida a outros.
         require_invite_text: Quando as incrições exigirem aprovação manual, faça o texto "Por que se quer juntar a nós?" da solicitação de convite ser obrigatório, em vez de opcional
@@ -132,23 +129,8 @@ pt-PT:
         show_application: Independentemente disso será sempre capaz de ver em que aplicação publicou a sua mensagem.
       tag:
         name: Só pode alterar a capitalização das letras, por exemplo, para torná-las mais legíveis
-      terms_of_service:
-        changelog: Pode ser estruturado com sintaxe Markdown.
-        effective_date: Um período razoável pode ir de 10 a 30 dias desde a data em que notifica os seus utilizadores.
-        text: Pode ser estruturado com sintaxe Markdown.
-      terms_of_service_generator:
-        admin_email: Os avisos legais incluem contra-avisos, ordens judiciais, pedidos de remoção e pedidos de aplicação da lei.
-        arbitration_address: Pode ser o mesmo que o endereço físico acima ou “N/A” se utilizar e-mail.
-        arbitration_website: Pode ser um formulário web ou “N/A” se for utilizado o e-mail.
-        choice_of_law: A legislação interna do estado, território, região ou cidade que deve regular qualquer conflito.
-        dmca_address: Para operadores dos EUA, utilize o endereço registado no Diretório de Agentes Designados DMCA. A listagem de uma caixa postal está disponível mediante pedido direto. Utilize o DMCA Designated Agent Post Office Box Waiver Request para enviar uma mensagem de correio eletrónico ao Copyright Office e descreva que é um moderador de conteúdos baseado em casa que receia vingança ou represálias pelas suas ações e que necessita de utilizar uma caixa postal para retirar o seu endereço de casa da vista do público.
-        dmca_email: Pode ser o mesmo e-mail utilizado para “Endereço de e-mail para avisos legais” acima.
-        domain: Identificação única do serviço online que está a prestar.
-        jurisdiction: Indique o país de residência de quem paga as contas. Se se tratar de uma empresa ou outra entidade, indique o país onde está constituída, bem como a cidade, região, território ou estado, consoante o caso.
-        min_age: Não deve ter menos do que a idade mínima exigida pela legislação da sua jurisdição.
       user:
         chosen_languages: Quando selecionado, só serão mostradas nas cronologias públicas as publicações nos idiomas escolhidos
-        date_of_birth: Temos de ter a certeza de que tens pelo menos %{age} para usar o Mastodon. Não vamos guardar esta informação.
         role: A função controla as permissões que o utilizador tem.
       user_role:
         color: Cor a ser utilizada para a função em toda a interface de utilizador, como RGB no formato hexadecimal
@@ -162,7 +144,7 @@ pt-PT:
         url: Para onde os eventos serão enviados
     labels:
       account:
-        attribution_domains: Websites autorizados a atribuir-lhe crédito
+        attribution_domains_as_text: Sites autorizados a atribuir-lhe crédito
         discoverable: Destacar perfil e publicações nos algoritmos de descoberta
         fields:
           name: Rótulo
@@ -239,10 +221,8 @@ pt-PT:
         setting_display_media_show_all: Mostrar todos
         setting_expand_spoilers: Expandir sempre as publicações marcadas com avisos de conteúdo
         setting_hide_network: Esconder a tua rede
-        setting_missing_alt_text_modal: Mostrar janela de confirmação antes de publicar multimédia sem texto alternativo
         setting_reduce_motion: Reduzir movimento em animações
         setting_system_font_ui: Usar o tipo de letra padrão do sistema
-        setting_system_scrollbars_ui: Utilizar a barra de deslocação predefinida do sistema
         setting_theme: Tema do sítio
         setting_trends: Mostrar as tendências de hoje
         setting_unfollow_modal: Solicitar confirmação antes de deixar de seguir alguém
@@ -261,7 +241,6 @@ pt-PT:
         name: Etiqueta
       filters:
         actions:
-          blur: Esconder multimédia com um aviso
           hide: Ocultar por completo
           warn: Ocultar com um aviso
       form_admin_settings:
@@ -275,7 +254,6 @@ pt-PT:
         favicon: Ícone de favoritos
         mascot: Mascote personalizada (legado)
         media_cache_retention_period: Período de retenção de ficheiros multimédia em cache
-        min_age: Idade mínima requerida
         peers_api_enabled: Publicar lista de servidores descobertos na API
         profile_directory: Ativar o diretório de perfis
         registrations_mode: Quem se pode inscrever
@@ -339,24 +317,7 @@ pt-PT:
         name: Etiqueta
         trendable: Permitir que esta etiqueta apareça nas tendências
         usable: Permitir que as publicações usem esta etiqueta localmente
-      terms_of_service:
-        changelog: O que mudou?
-        effective_date: Data de entrada em vigor
-        text: Termos do serviço
-      terms_of_service_generator:
-        admin_email: Endereço de e-mail para avisos legais
-        arbitration_address: Endereço físico para avisos de arbitragem
-        arbitration_website: Website para enviar avisos de arbitragem
-        choice_of_law: Escolha da legislação
-        dmca_address: Endereço físico para avisos DMCA / direitos autorais
-        dmca_email: Endereço de e-mail para avisos DMCA/direitos autorais
-        domain: Domínio
-        jurisdiction: Jurisdição legal
-        min_age: Idade mínima
       user:
-        date_of_birth_1i: Dia
-        date_of_birth_2i: Mês
-        date_of_birth_3i: Ano
         role: Função
         time_zone: Fuso horário
       user_role:
diff --git a/config/locales/simple_form.ro.yml b/config/locales/simple_form.ro.yml
index 5cb4e116a2..73f2f6f5a6 100644
--- a/config/locales/simple_form.ro.yml
+++ b/config/locales/simple_form.ro.yml
@@ -3,13 +3,14 @@ ro:
   simple_form:
     hints:
       account:
+        attribution_domains_as_text: Una pe linie. Protejează împotriva atribuirilor false.
         discoverable: Este posibil ca postările și profilul tău să fie recomandate în diferite zone ale Mastodon, iar profilul tău ar poate fi sugerat altor utilizatori.
         display_name: Numele dvs. complet sau numele dvs. amuzant.
         fields: Pagina ta principală, pronumele tale, vârsta, sau orice îți dorești.
         indexable: Postările tale publice pot apărea în rezultatele căutărilor pe Mastodon. Persoanele care au interacționat cu postările tale vor putea să le caute oricând.
         note: 'Poți @menționa alte persoane sau #hashtag-uri.'
         show_collections: Oamenii vor putea să răsfoiască urmăriți și urmăritorii dvs. Oamenii pe care îi urmăriți vor vedea că îi urmăriți indiferent.
-        unlocked: Alte persoane vă vor putea urmări fără a solicita aprobare. Debifați dacă doriți să revizuiți cererile de urmărire și să alegeți dacă doriți să acceptați sau să respingeți noii urmăritori.
+        unlocked: Alte persoane vă vor putea urmări fără a solicita aprobare. Debifați dacă doriți să revizuiți cererile și să alegeți dacă doriți să acceptați sau să respingeți noii urmăritori.
       account_alias:
         acct: Specificați numele de utilizator@domeniu al contului de la care doriți să treceți
       account_migration:
@@ -135,13 +136,14 @@ ro:
         color: Culoare care va fi folosită pentru rol în întreaga interfață, ca RGB în format hexazecimal
     labels:
       account:
+        attribution_domains_as_text: Site-uri web care vă pot credita
         fields:
           name: Etichetă
           value: Conținut
       account_alias:
-        acct: Identificatorul contului vechi
+        acct: Manipularea contului vechi
       account_migration:
-        acct: Identificatorul contului nou
+        acct: Manipularea contului nou
       account_warning_preset:
         text: Text presetat
         title: Titlu
diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml
index 24f24acadb..37626ff9fd 100644
--- a/config/locales/simple_form.ru.yml
+++ b/config/locales/simple_form.ru.yml
@@ -3,7 +3,7 @@ ru:
   simple_form:
     hints:
       account:
-        attribution_domains: По одному на строку. Защищает от ложных атрибуций.
+        attribution_domains_as_text: По одному на строку. Защищает от ложных атрибуций.
         discoverable: Ваши публичные сообщения и профиль могут быть показаны или рекомендованы в различных разделах Mastodon, и ваш профиль может быть предложен другим пользователям.
         display_name: Ваше полное имя или псевдоним.
         fields: Ваша домашняя страница, местоимения, возраст - все, что угодно.
@@ -60,7 +60,6 @@ ru:
         setting_display_media_default: Скрывать файлы «деликатного характера»
         setting_display_media_hide_all: Всегда скрывать любые медиафайлы
         setting_display_media_show_all: Всегда показывать любые медиафайлы
-        setting_system_scrollbars_ui: Работает только в браузерах для ПК на основе Safari или Chrome
         setting_use_blurhash: Градиенты основаны на цветах скрытых медиа, но скрывают любые детали.
         setting_use_pending_items: Показывать обновления в ленте только после клика вместо автоматической прокрутки.
         username: Вы можете использовать буквы, цифры и знаки подчеркивания
@@ -75,7 +74,6 @@ ru:
       filters:
         action: Выберите действие, которое нужно выполнить, когда сообщение соответствует фильтру
         actions:
-          blur: Скрыть медиа с предупреждением, не скрывая сам текст
           hide: Полностью скрыть отфильтрованный контент так, как будто его не существует
           warn: Скрыть отфильтрованный контент за предупреждением с указанием названия фильтра
       form_admin_settings:
@@ -89,7 +87,6 @@ ru:
         favicon: WEBP, PNG, GIF или JPG. Заменяет стандартный фавикон Mastodon на собственный значок.
         mascot: Заменяет иллюстрацию в расширенном веб-интерфейсе.
         media_cache_retention_period: Медиафайлы из сообщений, сделанных удаленными пользователями, кэшируются на вашем сервере. При положительном значении медиафайлы будут удалены через указанное количество дней. Если медиаданные будут запрошены после удаления, они будут загружены повторно, если исходный контент все еще доступен. В связи с ограничениями на частоту опроса карточек предварительного просмотра ссылок на сторонних сайтах рекомендуется устанавливать значение не менее 14 дней, иначе карточки предварительного просмотра ссылок не будут обновляться по запросу до этого времени.
-        min_age: Пользователям при регистрации будет предложено ввести свою дату рождения
         peers_api_enabled: Список доменных имен, с которыми сервер столкнулся в fediverse. Здесь нет данных о том, федерировались ли вы с данным сервером, только что ваш сервер знает об этом. Это используется службами, которые собирают статистику по федерации в общем смысле.
         profile_directory: В каталоге профилей перечислены все пользователи, которые согласились быть доступными для обнаружения.
         require_invite_text: Когда регистрация требует ручного одобрения, сделайте текстовый ввод "Почему вы хотите присоединиться?" обязательным, а не опциональным
@@ -132,23 +129,8 @@ ru:
         show_application: Вы всегда сможете увидеть, какое приложение опубликовало ваше сообщение.
       tag:
         name: Вы можете изменить только регистр букв чтобы, например, сделать тег более читаемым
-      terms_of_service:
-        changelog: Можно использовать синтаксис языка разметки Markdown.
-        effective_date: Разумные временные рамки могут варьироваться в диапазоне от 10 до 30 дней после уведомления пользователей.
-        text: Можно использовать синтаксис языка разметки Markdown.
-      terms_of_service_generator:
-        admin_email: Юридические уведомления включают в себя встречные уведомления, постановления суда, запросы на удаление и запросы правоохранительных органов.
-        arbitration_address: Может совпадать с почтовым адресом, указанным выше, либо «N/A» в случае электронной почты.
-        arbitration_website: Веб-форма или «N/A» в случае электронной почты.
-        choice_of_law: Город, регион, территория или государство, внутреннее материальное право которого регулирует любые претензии.
-        dmca_address: Находящиеся в США операторы должны использовать адрес, зарегистрированный в DMCA Designated Agent Directory. Использовать абонентский ящик возможно при обращении в соответствующей просьбой, для чего нужно с помощью DMCA Designated Agent Post Office Box Waiver Request написать сообщение в Copyright Office и объяснить, что вы занимаетесь модерацией контента из дома и опасаетесь мести за свои действия, поэтому должны использовать абонентский ящик, чтобы убрать ваш домашний адрес из общего доступа.
-        dmca_email: Может совпадать с адресом электронной почты для юридических уведомлений, указанным выше.
-        domain: Имя, позволяющее уникально идентифицировать ваш онлайн-ресурс.
-        jurisdiction: Впишите страну, где находится лицо, оплачивающее счета. Если это компания либо организация, впишите страну инкорпорации, включая город, регион, территорию или штат, если это необходимо.
-        min_age: Не меньше минимального возраста, требуемого по закону в вашей юрисдикции.
       user:
         chosen_languages: Если выбрано, то в публичных лентах будут показаны только посты на выбранных языках.
-        date_of_birth: Нужно убедиться, что вам не меньше %{age} лет. Мы не храним введённые здесь данные.
         role: Роль определяет, какими правами обладает пользователь.
       user_role:
         color: Цвет, который будет использоваться для роли в интерфейсе (UI), как RGB в формате HEX
@@ -162,7 +144,7 @@ ru:
         url: Куда события будут отправляться
     labels:
       account:
-        attribution_domains: Веб-сайты, которым разрешено ссылаться на вас
+        attribution_domains_as_text: Веб-сайты, которым разрешено ссылаться на вас
         discoverable: Профиль и сообщения в алгоритмах обнаружения
         fields:
           name: Пункт
@@ -239,10 +221,8 @@ ru:
         setting_display_media_show_all: Показывать все
         setting_expand_spoilers: Всегда раскрывать посты, имеющие предупреждение о содержании
         setting_hide_network: Скрыть свои связи
-        setting_missing_alt_text_modal: Всегда спрашивать перед публикацией медиафайлов без альтернативного текста
         setting_reduce_motion: Уменьшить движение в анимации
         setting_system_font_ui: Использовать шрифт системы по умолчанию
-        setting_system_scrollbars_ui: Использовать системные полосы прокрутки
         setting_theme: Тема сайта
         setting_trends: Показывать сегодняшние тренды
         setting_unfollow_modal: Всегда спрашивать перед отпиской от учётной записи
@@ -261,7 +241,6 @@ ru:
         name: Добавить хэштег
       filters:
         actions:
-          blur: Скрыть медиа с предупреждением
           hide: Скрыть полностью
           warn: Скрыть с предупреждением
       form_admin_settings:
@@ -275,7 +254,6 @@ ru:
         favicon: Favicon
         mascot: Пользовательский маскот (устаревшее)
         media_cache_retention_period: Период хранения кэша медиафайлов
-        min_age: Требование минимального возраста
         peers_api_enabled: Публикация списка обнаруженных узлов в API
         profile_directory: Включить каталог профилей
         registrations_mode: Кто может зарегистрироваться
@@ -339,24 +317,7 @@ ru:
         name: Хэштег
         trendable: Разрешить показ хэштега в трендах
         usable: Позволить этот хэштег в локальных сообщениях
-      terms_of_service:
-        changelog: Что изменилось?
-        effective_date: Дата вступления в силу
-        text: Пользовательское соглашение
-      terms_of_service_generator:
-        admin_email: Адрес электронной почты для юридических уведомлений
-        arbitration_address: Почтовый адрес для уведомлений об арбитраже
-        arbitration_website: Вебсайт для подачи уведомления об арбитраже
-        choice_of_law: Юрисдикция
-        dmca_address: Почтовый адрес для обращений правообладателей
-        dmca_email: Адрес электронной почты для обращений правообладателей
-        domain: Доменное имя
-        jurisdiction: Юрисдикция
-        min_age: Минимальный возраст
       user:
-        date_of_birth_1i: День
-        date_of_birth_2i: Месяц
-        date_of_birth_3i: Год
         role: Роль
         time_zone: Часовой пояс
       user_role:
diff --git a/config/locales/simple_form.sl.yml b/config/locales/simple_form.sl.yml
index 5d55844aa9..d1ae553c8c 100644
--- a/config/locales/simple_form.sl.yml
+++ b/config/locales/simple_form.sl.yml
@@ -3,14 +3,13 @@ sl:
   simple_form:
     hints:
       account:
-        attribution_domains: Ena na vrstico. Ščiti pred napačno navedbo avtorstva.
         discoverable: Vaše javne objave in profil so lahko predstavljeni ali priporočeni v različnih delih Mastodona, vaš profil pa je lahko predlagan drugim uporabnikom.
         display_name: Vaše polno ime ali lažno ime.
         fields: Vaša domača stran, starost, kar koli.
         indexable: Vaše javne objave se lahko pojavijo v rezultatih iskanja na Mastodonu. Ljudje, ki so bili v interakciji z vašimi objavami, jih bodo lahko iskali ne glede na to.
         note: 'Druge osebe lahko @omenite ali #ključnite.'
         show_collections: Ljudje bodo lahko brskali po vaših sledilcih in sledenih. Ljudje, ki jim sledite, bodo videli, da jim sledite ne glede na to.
-        unlocked: Ljudje vam bodo lahko sledili, ne da bi zahtevali odobritev. Ne potrdite, če želite pregledati prošnje za sledenje in izbrati, ali želite nove sledilce sprejeti ali zavrniti.
+        unlocked: Ljudje vam bodo lahko sledili, ne da bi zahtevali odobritev. Ne potrdite, če želite pregledati prošnje za sledenje, in izbirajte, ali želite nove sledilce sprejeti ali zavrniti.
       account_alias:
         acct: Določite uporabniškoime@domena računa, od katerega se želite preseliti
       account_migration:
@@ -60,7 +59,6 @@ sl:
         setting_display_media_default: Skrij medij, ki je označen kot občutljiv
         setting_display_media_hide_all: Vedno skrij vse medije
         setting_display_media_show_all: Vedno pokaži medij, ki je označen kot občutljiv
-        setting_system_scrollbars_ui: Velja zgolj za namizne brskalnike, ki temeljijo na Safariju in Chromeu
         setting_use_blurhash: Prelivi temeljijo na barvah skrite vizualne slike, vendar zakrivajo vse podrobnosti
         setting_use_pending_items: Skrij posodobitev časovnice za klikom namesto samodejnega posodabljanja
         username: Uporabite lahko črke, števke in podčrtaje.
@@ -88,7 +86,6 @@ sl:
         favicon: WEBP, PNG, GIF ali JPG. Zamenja privzeto ikono spletne strani Mastodon z ikono po meri.
         mascot: Preglasi ilustracijo v naprednem spletnem vmesniku.
         media_cache_retention_period: Predstavnostne datoteke iz objav uporabnikov na ostalih strežnikih se začasno hranijo na tem strežniku. Ko je nastavljeno na pozitivno vrednost, bodo predstavnostne datoteke izbrisane po nastavljenem številu dni. Če bo predstavnostna datoteka zahtevana po izbrisu, bo ponovno prenešena, če bo vir še vedno na voljo. Zaradi omejitev pogostosti prejemanja predogledov povezav z drugih strani je priporočljivo to vrednost nastaviti na vsaj 14 dni. V nasprotnem predogledi povezav pred tem časom ne bodo osveženi na zahtevo.
-        min_age: Med registracijo bodo morali uporabniki potrditi svoj datum rojstva
         peers_api_enabled: Seznam imen domen, na katere je ta strežnik naletel v fediverzumu. Sem niso vključeni podatki o tem, ali ste v federaciji z danim strežnikom, zgolj to, ali vaš strežnik ve zanj. To uporabljajo storitve, ki zbirajo statistične podatke o federaciji v splošnem smislu.
         profile_directory: Imenik profilov izpiše vse uporabnike, ki so dovolili, da so v njem navedeni.
         require_invite_text: Če registracije zahtevajo ročno potrditev, nastavite vnos besedila pod »Zakaj se želite pridružiti?« za obveznega.
@@ -131,23 +128,8 @@ sl:
         show_application: Ne glede na to boste vedno lahko pogledali, katera aplikacija je objavila vašo objavo.
       tag:
         name: Spremenite lahko le npr. velikost črk (velike/male), da je bolj berljivo
-      terms_of_service:
-        changelog: Uporabite lahko oblikovanje z Markdown.
-        effective_date: Razumen čas do uveljavitve je navadno nekje med 10 in 30 dni od datuma, ko ste obvestili svoje uporabnike.
-        text: Uporabite lahko oblikovanje z Markdown.
-      terms_of_service_generator:
-        admin_email: Pravna obvestila vključujejo odgovore na obvestila, sodne naloge, zahteve za odstranitev in zahteve organov pregona.
-        arbitration_address: Lahko je enak kot fizični naslov zgoraj ali „N/A“, če uporabljate e-pošto.
-        arbitration_website: Lahko je spletni obrazec ali „N/A“, če uporabljate e-pošto.
-        choice_of_law: Mesto, regija, teritorij ali zvezna država, katere področni zakoni urejajo vse tožbene zahtevke.
-        dmca_email: Lahko je enak e-poštni naslov kot v polju „E-poštni naslov za pravna obvestila“ zgoraj.
-        domain: Edinstvena identifikacija spletne storitve, ki jo ponujate.
-        jurisdiction: Navedite državo, kjer živi tisti, ki plačuje račune. Če je to podjetje ali druga entiteta, navedite državo, kjer je bila ustanovljena. Po potrebi dopišite tudi mesto, regijo, teritorij ali zvezno državo.
-        min_age: Ne smete biti mlajši od starostne omejitve, ki jo postavljajo zakoni vašega pravosodnega sistema.
       user:
         chosen_languages: Ko je označeno, bodo v javnih časovnicah prikazane samo objave v izbranih jezikih
-        date_of_birth: Prepričati se moramo, da so uporabniki Mastodona stari vsaj %{age} let. Tega podatka ne bomo shranili.
-        role: Vloga določa, katera dovoljenja ima uporabnik.
       user_role:
         color: Barva, uporabljena za vlogo po celem up. vmesniku, podana v šestnajstiškem zapisu RGB
         highlighted: S tem je vloga javno vidna
@@ -160,7 +142,6 @@ sl:
         url: Kam bodo poslani dogodki
     labels:
       account:
-        attribution_domains: Spletna mesta, ki vas smejo navajati kot avtorja/ico
         discoverable: Izpostavljaj profile in objave v algoritmih odkrivanja
         fields:
           name: Oznaka
@@ -237,10 +218,8 @@ sl:
         setting_display_media_show_all: Prikaži vse
         setting_expand_spoilers: Vedno razširi objave, označene z opozorili o vsebini
         setting_hide_network: Skrij svoje omrežje
-        setting_missing_alt_text_modal: Pred objavo predstavnosti brez nadomestnega besedila pokaži potrditveno okno
         setting_reduce_motion: Zmanjšanje premikanja v animacijah
         setting_system_font_ui: Uporabi privzeto pisavo sistema
-        setting_system_scrollbars_ui: Uporabi privzeti drsni trak sistema
         setting_theme: Tema strani
         setting_trends: Pokaži današnje trende
         setting_unfollow_modal: Pokaži potrditveno okno, preden nekoga prenehamo slediti
@@ -272,7 +251,6 @@ sl:
         favicon: Ikona spletne strani
         mascot: Maskota po meri (opuščeno)
         media_cache_retention_period: Obdobje hrambe predpomnilnika predstavnosti
-        min_age: Spodnja starostna meja
         peers_api_enabled: Objavi seznam odkritih strežnikov v API-ju
         profile_directory: Omogoči imenik profilov
         registrations_mode: Kdo se lahko registrira
@@ -336,24 +314,7 @@ sl:
         name: Ključnik
         trendable: Dovoli, da se ta ključnik pojavi med trendi
         usable: Dovoli, da objave krajevno uporabljajo ta ključnik
-      terms_of_service:
-        changelog: Kaj je novega?
-        effective_date: Datum začetka veljavnosti
-        text: Pogoji uporabe
-      terms_of_service_generator:
-        admin_email: E-poštni naslov za pravna obvestila
-        arbitration_address: Fizični naslov za arbitražna obvestila
-        arbitration_website: Spletišče za vložitev arbitražnih obvestil
-        choice_of_law: Izbira prava
-        dmca_address: Fizični naslov za obvestila DMCA ali o avtorskih pravicah
-        dmca_email: E-poštni naslov za obvestila DMCA ali o avtorskih pravicah
-        domain: Domena
-        jurisdiction: Pravna pristojnost
-        min_age: Najmanjša starost
       user:
-        date_of_birth_1i: Dan
-        date_of_birth_2i: Mesec
-        date_of_birth_3i: Leto
         role: Vloga
         time_zone: Časovni pas
       user_role:
diff --git a/config/locales/simple_form.sq.yml b/config/locales/simple_form.sq.yml
index 9f5ada184c..1ca9037e63 100644
--- a/config/locales/simple_form.sq.yml
+++ b/config/locales/simple_form.sq.yml
@@ -3,7 +3,7 @@ sq:
   simple_form:
     hints:
       account:
-        attribution_domains: Një për rresht. Kjo mbron nga atribuime të rreme.
+        attribution_domains_as_text: Një për rresht. Kjo mbron nga atribuime të rreme.
         discoverable: Postimet dhe profili juaj publik mund të shfaqen, ose rekomandohen në zona të ndryshme të Mastodon-it dhe profili juaj mund të sugjerohet përdoruesve të tjerë.
         display_name: Emri juaj i plotë, ose emri juaj lojcak.
         fields: Faqja juaj hyrëse, përemra, moshë, ç’të keni qejf.
@@ -60,7 +60,6 @@ sq:
         setting_display_media_default: Fshih media me shenjën rezervat
         setting_display_media_hide_all: Fshih përherë mediat
         setting_display_media_show_all: Mediat shfaqi përherë
-        setting_system_scrollbars_ui: Ka vend vetëm për shfletues desktop bazuar në Safari dhe Chrome
         setting_use_blurhash: Gradientët bazohen në ngjyrat e elementëve pamorë të fshehur, por errësojnë çfarëdo hollësie
         setting_use_pending_items: Fshihi përditësimet e rrjedhës kohore pas një klikimi, në vend të rrëshqitjes automatike nëpër prurje
         username: Mund të përdorni shkronja, numra dhe nënvija
@@ -75,7 +74,6 @@ sq:
       filters:
         action: Zgjidhni cili veprim të kryhet, kur një postim ka përputhje me një filtër
         actions:
-          blur: Fshihe median pas një sinjalizimi, pa fshehur vetë tekstin
           hide: Fshihe plotësisht lëndën e filtruar, duke u sjellë sikur të mos ekzistonte
           warn: Fshihe lëndën e filtruar pas një sinjalizimi që përmend titullin e filtrit
       form_admin_settings:
@@ -89,7 +87,6 @@ sq:
         favicon: WEBP, PNG, GIF, ose JPG. Anashkalon favikonën parazgjedhje Mastodon me një ikonë vetjake.
         mascot: Anashkalon ilustrimin te ndërfaqja web e thelluar.
         media_cache_retention_period: Kartela media nga postime të bëra nga përdorues të largët ruhen në një fshehtinë në shërbyesin tuaj. Kur i jepet një vlerë pozitive, media do të fshihet pas numrit të dhënë të ditëve. Nëse të dhënat e medias duhen pas fshirjes, do të rishkarkohen, nëse lënda burim mund të kihet ende. Për shkak kufizimesh mbi sa shpesh skeda paraparjesh lidhjesh ndërveprojnë me sajte palësh të treta, rekomandohet të vihet kjo vlerë të paktën 14 ditë, ose skedat e paraparjes së lidhje s’do të përditësohen duke e kërkuar para asaj kohe.
-        min_age: Përdoruesve do t’ju kërkohet gjatë regjistrimit të ripohojnë datën e lindjes
         peers_api_enabled: Një listë emrash përkatësish që ky shërbyes ka hasur në fedivers. Këtu s’jepen të dhëna nëse jeni i federuar me shërbyesin e dhënë, thjesht tregohet se shërbyesi juaj e njeh. Kjo përdoret nga shërbime që mbledhin statistika mbi federimin në kuptimin e përgjithshëm.
         profile_directory: Drejtoria e profileve paraqet krejt përdoruesit që kanë zgjedhur të jenë të zbulueshëm.
         require_invite_text: Kur regjistrimet lypin miratim dorazi, bëje tekstin “Përse doni të bëheni pjesë?” të detyrueshëm, në vend se opsional
@@ -132,22 +129,8 @@ sq:
         show_application: Pavarësisht nga kjo, do të jeni përherë në gjendje të shihni cili aplikacion botoi postimin tuaj.
       tag:
         name: Mund të ndryshoni shkronjat vetëm nga të mëdha në të vogla ose anasjelltas, për shembull, për t’i bërë më të lexueshme
-      terms_of_service:
-        changelog: Mund të strukturohet me sintaksë Markdown.
-        effective_date: Një kuadër kohor i arsyeshëm mund të shtrihet nga 10 deri në 30 ditë nga data që njoftoni përdoruesit tuaj.
-        text: Mund të strukturohet me sintaksë Markdown.
-      terms_of_service_generator:
-        admin_email: Njoftimet ligjore përfshijnë kundërnjoftime, vendime gjyqi, kërkesa për për nxjerrje sajti jashtë shërbimit dhe kërkesa nga organe ligjore.
-        arbitration_address: Mund të jetë e njëjtë me adresën Fizike më sipër, ose “N/A”, nëse përdoret email.
-        arbitration_website: Mund të jetë një formular web, ose “N/A”, nëse përdoret email.
-        choice_of_law: Qytet, rajon, territor ose shtet, ligjet e brendshme të të cilit do të administrojnë çfarëdo dhe tërë pretendimet.
-        dmca_email: Mund të jetë i njëjti email i përdorur për “Adresë email për njoftime ligjore” më sipër.
-        domain: Identifikues unik për shërbimin internetor që po ofroni.
-        jurisdiction: Vendosni vendin ku jeton cilido që paguan faturat. Nëse është një shoqëri apo tjetër njësi, vendosni vendin ku është regjistruar, si dhe qytetin, rajonin, territorin apo shtetin përkatës.
-        min_age: S’duhet të jetë nën moshën minimum të domosdoshme nga ligjet në juridiksionin tuaj.
       user:
         chosen_languages: Në iu vëntë shenjë, te rrjedha kohore publike do të shfaqen vetëm mesazhe në gjuhët e përzgjedhura
-        date_of_birth: Na duhet të sigurohemi se jeni të paktën %{age}, që të përdorni Mastodon-in. Këtë s’e depozitojmë.
         role: Roli kontrollon cilat leje ka përdoruesi.
       user_role:
         color: Ngjyrë për t’u përdorur për rolin nëpër UI, si RGB në format gjashtëmbëdhjetësh
@@ -161,7 +144,7 @@ sq:
         url: Ku do të dërgohen aktet
     labels:
       account:
-        attribution_domains: Sajte të lejuar t’ju japin hakë
+        attribution_domains_as_text: Sajte të lejuar t’ju japin hakë
         discoverable: Profilin dhe postimet bëji objekt të algoritmeve të zbulimit
         fields:
           name: Etiketë
@@ -238,10 +221,8 @@ sq:
         setting_display_media_show_all: Shfaqi krejt
         setting_expand_spoilers: Mesazhet me sinjalizime mbi lëndën, zgjeroji përherë
         setting_hide_network: Fshiheni rrjetin tuaj
-        setting_missing_alt_text_modal: Shfaq dialog ripohimi, para postimi mediash pa tekst alternativ
         setting_reduce_motion: Zvogëlo lëvizjen në animacione
         setting_system_font_ui: Përdor shkronja parazgjedhje të sistemit
-        setting_system_scrollbars_ui: Përdor shtyllë rrëshqitjesh parazgjedhje të sistemit
         setting_theme: Temë sajti
         setting_trends: Shfaq prirjet sot
         setting_unfollow_modal: Shfaq dialog ripohimi përpara heqjes së ndjekjes për dikë
@@ -260,7 +241,6 @@ sq:
         name: Hashtag
       filters:
         actions:
-          blur: Fshihe median me një sinjalizim
           hide: Fshihe plotësisht
           warn: Fshihe me një sinjalizim
       form_admin_settings:
@@ -274,7 +254,6 @@ sq:
         favicon: Favikonë
         mascot: Simbol vetjak (e dikurshme)
         media_cache_retention_period: Periudhë mbajtjeje lënde media
-        min_age: Domosdosmëri moshe minimum
         peers_api_enabled: Publiko te API listë shërbyesish të zbuluar
         profile_directory: Aktivizo drejtori profilesh
         registrations_mode: Kush mund të regjistrohet
@@ -338,24 +317,7 @@ sq:
         name: Hashtag
         trendable: Lejoje këtë hashtag të shfaqet në prirje
         usable: Lejoji postimet të përdorin lokalisht këtë hashtag
-      terms_of_service:
-        changelog: Ç’ka ndryshuar?
-        effective_date: Datë efektive
-        text: Kushte Shërbimi
-      terms_of_service_generator:
-        admin_email: Adresë email për njoftime ligjore
-        arbitration_address: Adresë fizike për njoftime arbitrazhi
-        arbitration_website: Sajtin për parashtrim njoftime arbitrazhi
-        choice_of_law: Zgjedhje Ligji
-        dmca_address: Adresë fizike për njoftime DMCA/të drejtash kopjimi
-        dmca_email: Adresë email për njoftime DMCA/të drejtash kopjimi
-        domain: Përkatësi
-        jurisdiction: Juridiksion ligjor
-        min_age: Mosha minimale
       user:
-        date_of_birth_1i: Ditë
-        date_of_birth_2i: Muaj
-        date_of_birth_3i: Vit
         role: Rol
         time_zone: Zonë kohore
       user_role:
diff --git a/config/locales/simple_form.sr-Latn.yml b/config/locales/simple_form.sr-Latn.yml
index fee7600074..1dec901340 100644
--- a/config/locales/simple_form.sr-Latn.yml
+++ b/config/locales/simple_form.sr-Latn.yml
@@ -9,6 +9,7 @@ sr-Latn:
         indexable: Vaše javne objave se mogu pojaviti u rezultatima pretrage na Mastodon-u. Ljudi koji su stupili u interakciju sa vašim objavama će možda moći da ih pretražuju.
         note: 'Možete da @pomenete druge ljude ili #heš oznake.'
         show_collections: Ljudi će moći da pregledaju vaše pratioce i pratioce. Ljudi koje pratite videće da ih pratite.
+        unlocked: Ljudi će moći da vas prate bez zahtevanja odobrenja. Opozovite izbor ako želite da pregledate zahteve za praćenje i izaberite da li da prihvatite ili odbijete nove pratioce.
       account_alias:
         acct: Navedite korisničko_ime@domen naloga sa kojeg želite da se preselite
       account_migration:
diff --git a/config/locales/simple_form.sr.yml b/config/locales/simple_form.sr.yml
index 7161627e28..9566e09475 100644
--- a/config/locales/simple_form.sr.yml
+++ b/config/locales/simple_form.sr.yml
@@ -9,6 +9,7 @@ sr:
         indexable: Ваше јавне објаве се могу појавити у резултатима претраге на Mastodon-у. Људи који су ступили у интеракцију са вашим објавама ће можда моћи да их претражују.
         note: 'Можете да @поменете друге људе или #хеш ознаке.'
         show_collections: Људи ће моћи да прегледају ваше пратиоце и пратиоце. Људи које пратите видеће да их пратите.
+        unlocked: Људи ће моћи да вас прате без захтевања одобрења. Опозовите избор ако желите да прегледате захтеве за праћење и изаберите да ли да прихватите или одбијете нове пратиоце.
       account_alias:
         acct: Наведите корисничко_име@домен налога са којег желите да се преселите
       account_migration:
diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml
index 41ac513f39..49ad75e3c6 100644
--- a/config/locales/simple_form.sv.yml
+++ b/config/locales/simple_form.sv.yml
@@ -3,7 +3,7 @@ sv:
   simple_form:
     hints:
       account:
-        attribution_domains: En per rad. Skyddar mot falska attributioner.
+        attribution_domains_as_text: En per rad. Skyddar mot falska attributioner.
         discoverable: Dina offentliga inlägg och din profil kan komma att presenteras eller rekommenderas inom olika områden av Mastodon och din profil kan komma att föreslås till andra användare.
         display_name: Ditt fullständiga namn eller ditt roliga namn.
         fields: Din hemsida, ditt pronomen, din ålder, vadhelst du vill.
@@ -60,7 +60,6 @@ sv:
         setting_display_media_default: Dölj media markerad som känslig
         setting_display_media_hide_all: Dölj alltid all media
         setting_display_media_show_all: Visa alltid media markerad som känslig
-        setting_system_scrollbars_ui: Gäller endast för webbläsare som är baserade på Safari och Chrome
         setting_use_blurhash: Gradienter är baserade på färgerna av de dolda objekten men fördunklar alla detaljer
         setting_use_pending_items: Dölj tidslinjeuppdateringar bakom ett klick istället för att automatiskt bläddra i flödet
         username: Du kan använda bokstäver, siffror och understreck
@@ -130,11 +129,6 @@ sv:
         show_application: Du kommer alltid att kunna se vilken app som publicerat ditt inlägg oavsett.
       tag:
         name: Du kan bara ändra skriftläget av bokstäverna, till exempel, för att göra det mer läsbart
-      terms_of_service:
-        changelog: Kan struktureras med Markdown syntax.
-        text: Kan struktureras med Markdown syntax.
-      terms_of_service_generator:
-        jurisdiction: Lista det land där vem som än betalar räkningarna bor. Om det är ett företag eller annan enhet, lista landet där det är inkorporerat, och staden, regionen, territoriet eller staten på lämpligt sätt.
       user:
         chosen_languages: Vid aktivering visas bara inlägg på dina valda språk i offentliga tidslinjer
         role: Rollen styr vilka behörigheter användaren har.
@@ -150,6 +144,7 @@ sv:
         url: Dit händelser kommer skickas
     labels:
       account:
+        attribution_domains_as_text: Webbplatser som får kreditera dig
         discoverable: Presentera profil och inlägg med upptäcktsalgoritmer
         fields:
           name: Etikett
@@ -228,7 +223,6 @@ sv:
         setting_hide_network: Göm ditt nätverk
         setting_reduce_motion: Minska rörelser i animationer
         setting_system_font_ui: Använd systemets standardfont
-        setting_system_scrollbars_ui: Använd systemets standardrullningsfält
         setting_theme: Sidans tema
         setting_trends: Visa dagens trender
         setting_unfollow_modal: Visa bekräftelse innan du slutar följa någon
@@ -323,19 +317,7 @@ sv:
         name: Hashtagg
         trendable: Tillåt denna hashtagg att visas under trender
         usable: Tillåt inlägg att använda denna fyrkantstagg
-      terms_of_service:
-        changelog: Vad har ändrats?
-        text: Användarvillkor
-      terms_of_service_generator:
-        admin_email: E-postadress för juridiska meddelanden
-        dmca_address: Fysisk adress för meddelanden om DMCA/upphovsrätt
-        dmca_email: Fysisk adress för meddelanden om DMCA/upphovsrätt
-        domain: Domän
-        min_age: Minimiålder
       user:
-        date_of_birth_1i: Dag
-        date_of_birth_2i: Månad
-        date_of_birth_3i: År
         role: Roll
         time_zone: Tidszon
       user_role:
diff --git a/config/locales/simple_form.th.yml b/config/locales/simple_form.th.yml
index 530bd60ad1..f8f4d3f119 100644
--- a/config/locales/simple_form.th.yml
+++ b/config/locales/simple_form.th.yml
@@ -3,7 +3,7 @@ th:
   simple_form:
     hints:
       account:
-        attribution_domains: หนึ่งรายการต่อบรรทัด ปกป้องจากการระบุแหล่งที่มาที่ผิด
+        attribution_domains_as_text: หนึ่งรายการต่อบรรทัด ปกป้องจากการระบุแหล่งที่มาที่ผิด
         discoverable: อาจแสดงหรือแนะนำโพสต์และโปรไฟล์สาธารณะของคุณในพื้นที่ต่าง ๆ ของ Mastodon และอาจเสนอแนะโปรไฟล์ของคุณให้กับผู้ใช้อื่น ๆ
         display_name: ชื่อเต็มของคุณหรือชื่อแบบสนุกสนานของคุณ
         fields: หน้าแรก, สรรพนาม, อายุของคุณ สิ่งใดก็ตามที่คุณต้องการ
@@ -144,7 +144,7 @@ th:
         url: ที่ซึ่งจะส่งเหตุการณ์ไปยัง
     labels:
       account:
-        attribution_domains: เว็บไซต์ที่ได้รับอนุญาตให้ให้เครดิตคุณ
+        attribution_domains_as_text: เว็บไซต์ที่ได้รับอนุญาตให้ให้เครดิตคุณ
         discoverable: แสดงโปรไฟล์และโพสต์ในอัลกอริทึมการค้นพบ
         fields:
           name: ป้ายชื่อ
@@ -221,10 +221,8 @@ th:
         setting_display_media_show_all: แสดงทั้งหมด
         setting_expand_spoilers: ขยายโพสต์ที่มีการทำเครื่องหมายด้วยคำเตือนเนื้อหาเสมอ
         setting_hide_network: ซ่อนกราฟทางสังคมของคุณ
-        setting_missing_alt_text_modal: แสดงกล่องโต้ตอบการยืนยันก่อนที่จะโพสต์สื่อโดยไม่มีข้อความแสดงแทน
         setting_reduce_motion: ลดการเคลื่อนไหวในภาพเคลื่อนไหว
         setting_system_font_ui: ใช้แบบอักษรเริ่มต้นของระบบ
-        setting_system_scrollbars_ui: ใช้แถบเลื่อนเริ่มต้นของระบบ
         setting_theme: ชุดรูปแบบไซต์
         setting_trends: แสดงแนวโน้มของวันนี้
         setting_unfollow_modal: แสดงกล่องโต้ตอบการยืนยันก่อนเลิกติดตามใครสักคน
@@ -319,14 +317,7 @@ th:
         name: แฮชแท็ก
         trendable: อนุญาตให้แฮชแท็กนี้ปรากฏภายใต้แนวโน้ม
         usable: อนุญาตให้โพสต์ใช้แฮชแท็กนี้ในเซิร์ฟเวอร์
-      terms_of_service:
-        text: เงื่อนไขการให้บริการ
-      terms_of_service_generator:
-        domain: โดเมน
       user:
-        date_of_birth_1i: วัน
-        date_of_birth_2i: เดือน
-        date_of_birth_3i: ปี
         role: บทบาท
         time_zone: โซนเวลา
       user_role:
diff --git a/config/locales/simple_form.tok.yml b/config/locales/simple_form.tok.yml
index 81c3121f24..98968ffa93 100644
--- a/config/locales/simple_form.tok.yml
+++ b/config/locales/simple_form.tok.yml
@@ -3,10 +3,12 @@ tok:
   simple_form:
     hints:
       account:
+        attribution_domains_as_text: linja sitelen wan la wan taso o lon. ni la, sona pona pi jan pali li lon.
         display_name: nimi sina ale anu nimi sina musi.
         fields: lipu open sina, en nimi pi kon sina, en suli tenpo sina, en ijo ante ale pi wile sina.
         note: 'sina ken @mu e jan ante, li ken lon e #kulupu toki suli.'
         show_collections: 'jan li ken lukin e ni: sina kute e jan seme, jan seme li kute e sina. ale la, sina kute e jan la ona li sona.'
+        unlocked: ni li lon la, ale la jan li ken kute e sina. sina ken anu ala e ona la, ni li suli ala. sina wile ken e wile kute, li wile ala e ona, la o ala e nasin ni.
       account_alias:
         acct: o toki e nimi@ma pi sijelo tan
       account_migration:
diff --git a/config/locales/simple_form.tr.yml b/config/locales/simple_form.tr.yml
index 22300ccec3..5aed566626 100644
--- a/config/locales/simple_form.tr.yml
+++ b/config/locales/simple_form.tr.yml
@@ -3,7 +3,7 @@ tr:
   simple_form:
     hints:
       account:
-        attribution_domains: Her satırda bir tane. Sahte atıflardan korur.
+        attribution_domains_as_text: Her satırda bir tanesi. Sahte atıflardan korur.
         discoverable: Herkese açık gönderileriniz ve profiliniz Mastodon'un çeşitli kısımlarında öne çıkarılabilir veya önerilebilir ve profiliniz başka kullanıcılara önerilebilir.
         display_name: Tam adınız veya kullanıcı adınız.
         fields: Ana sayfanız, zamirleriniz, yaşınız, istediğiniz herhangi bir şey.
@@ -60,7 +60,6 @@ tr:
         setting_display_media_default: Hassas olarak işaretlenmiş medyayı gizle
         setting_display_media_hide_all: Medyayı her zaman gizle
         setting_display_media_show_all: Medyayı her zaman göster
-        setting_system_scrollbars_ui: Yalnızca Safari ve Chrome tabanlı masaüstü tarayıcılar için geçerlidir
         setting_use_blurhash: Gradyenler gizli görsellerin renklerine dayanır, ancak detayları gizler
         setting_use_pending_items: Akışı otomatik olarak kaydırmak yerine, zaman çizelgesi güncellemelerini tek bir tıklamayla gizleyin
         username: Harfleri, sayıları veya alt çizgi kullanabilirsiniz
@@ -75,7 +74,6 @@ tr:
       filters:
         action: Bir gönderi filtreyle eşleştiğinde hangi eylemin yapılacağını seçin
         actions:
-          blur: Medyayı, metnin kendisini gizlemeden bir uyarı arkasında gizle
           hide: Filtrelenmiş içeriği tamamen gizle, sanki varolmamış gibi
           warn: Süzgeçlenmiş içeriği, süzgecinin başlığından söz eden bir uyarının arkasında gizle
       form_admin_settings:
@@ -89,7 +87,6 @@ tr:
         favicon: WEBP, PNG, GIF veya JPG. Varsayılan Mastodon simgesini isteğe bağlı bir simgeyle değiştirir.
         mascot: Gelişmiş web arayüzündeki illüstrasyonu geçersiz kılar.
         media_cache_retention_period: Uzak kullanıcıların gönderilerindeki ortam dosyaları sunucunuzda önbelleklenir. Pozitif bir değer verildiğinde, ortam dosyaları belirlenen gün sonunda silinecektir. Eğer ortam dosyaları silindikten sonra istenirse, kaynak içerik hala mevcutsa, tekrar indirilecektir. Bağlantı önizleme kartlarının üçüncü parti siteleri yoklamasına ilişkin kısıtlamalar nedeniyle, bu değeri en azından 14 gün olarak ayarlamanız önerilir, yoksa bağlantı önizleme kartları bu süreden önce isteğe bağlı olarak güncellenmeyecektir.
-        min_age: Kullanıcılardan kayıt olurken doğum tarihlerini doğrulamaları istenecektir
         peers_api_enabled: Bu sunucunun fediverse'te karşılaştığı alan adlarının bir listesi. İlgili sunucuyla birleştirme mi yapıyorsunuz yoksa sunucunuz sadece onu biliyor mu hakkında bir bilgi burada yok. Bu blgi genel olarak federasyın hakkında istatistik toplamak isteyen hizmetler tarafından kullanılıyor.
         profile_directory: Profil dizini keşfedilebilir olmayı kabul eden tüm kullanıcıları listeler.
         require_invite_text: Kayıt olmak elle doğrulama gerektiriyorsa, "Neden katılmak istiyorsunuz?" metin girdisini isteğe bağlı yerine zorunlu yapın
@@ -132,23 +129,8 @@ tr:
         show_application: Ne olursa olsun gönderinizi yayınlayan uygulamayı her zaman görebileceksiniz.
       tag:
         name: Harflerin, örneğin daha okunabilir yapmak için, sadece büyük/küçük harf durumlarını değiştirebilirsiniz
-      terms_of_service:
-        changelog: Markdown sözdizimiyle yapılandırılabilir.
-        effective_date: Kullanıcıları bilgilendirmek için mantıklı bir tarih aralığı 10 ile 30 gün arasında olabilir.
-        text: Markdown sözdizimiyle yapılandırılabilir.
-      terms_of_service_generator:
-        admin_email: Yasal bildirimler arasında karşı bildirimler, mahkeme kararları, yayından kaldırma talepleri ve kolluk kuvvetleri talepleri yer alır.
-        arbitration_address: Yukarıdaki fiziksel adresle aynı olabilir, veya e-posta kullanıyorsanız "N/A".
-        arbitration_website: Web formu olabilir, veya e-posta kullanıyorsanız "N/A".
-        choice_of_law: Her türlü iddiayı yönetecek olan iç maddi yasalara ilişkin şehir, bölge, eyalet veya ülke.
-        dmca_address: ABD operatörleri için DMCA Atanmış Temsilci Dizininde kayıtlı adresi kullanın. Doğrudan taleple Posta Kutusu kullanımı mümkündür, Telif Hakkı Ofisine e-posta göndermek için DMCA Atanmış Temsilci Posta Kutusu Feragat Talebini kullanın ve eylemleriniz nedeniyle intikam veya cezalandırılmaktan çekinen ve herkese açık ev adresini kullanmamak için Posta Kutusu kullanması gereken ev tabanlı bir içerik moderatörü olduğunuzu açıklayın.
-        dmca_email: Yukarıdaki "Yasal bildirimler için e-posta adresi" için kullanılan e-posta ile aynı olabilir.
-        domain: Sağladığınız çevrimiçi hizmetin benzersiz tanımlaması.
-        jurisdiction: Faturaları ödeyen kişinin yaşadığı ülkeyi listeleyin. Bir şirket veya başka bir kuruluş ise, kurulduğu ülkeyi ve uygun şekilde şehri, bölgeyi, yöreyi veya eyaleti listeleyin.
-        min_age: Tabi olduğunuz yasaların gerektirdiği yaştan düşük olmamalıdır.
       user:
         chosen_languages: İşaretlendiğinde, yalnızca seçilen dillerdeki gönderiler genel zaman çizelgelerinde görüntülenir
-        date_of_birth: Mastodon kullanmak için en az %{age} yaşında olduğunuzdan emin olmalıyız. Bu bilgiyi saklamıyoruz.
         role: Rol, kullanıcıların sahip olduğu izinleri denetler.
       user_role:
         color: Arayüz boyunca rol için kullanılacak olan renk, hex biçiminde RGB
@@ -162,7 +144,7 @@ tr:
         url: Olayların gönderileceği yer
     labels:
       account:
-        attribution_domains: Size atıf verebilecek websiteleri
+        attribution_domains_as_text: Size atıf verebilecek websiteleri
         discoverable: Profil ve gönderileri keşif algoritmalarında kullan
         fields:
           name: Etiket
@@ -239,10 +221,8 @@ tr:
         setting_display_media_show_all: Tümünü göster
         setting_expand_spoilers: İçerik uyarılarıyla işaretli gönderileri her zaman genişlet
         setting_hide_network: Sosyal grafiğini gizle
-        setting_missing_alt_text_modal: Alternatif metni olmayan medya göndermeden önce onay sorusu göster
         setting_reduce_motion: Animasyonlarda hareketi azalt
         setting_system_font_ui: Sistemin varsayılan yazı tipini kullan
-        setting_system_scrollbars_ui: Sistemin varsayılan kaydırma çubuğunu kullan
         setting_theme: Site teması
         setting_trends: Bugünün gündemini göster
         setting_unfollow_modal: Birini takip etmeden önce onay iletişim kutusu göster
@@ -261,7 +241,6 @@ tr:
         name: Etiket
       filters:
         actions:
-          blur: Medyayı bir uyarıyla gizle
           hide: Tamamen gizle
           warn: Uyarıyla gizle
       form_admin_settings:
@@ -275,7 +254,6 @@ tr:
         favicon: Yer imi simgesi
         mascot: Özel maskot (eski)
         media_cache_retention_period: Medya önbelleği saklama süresi
-        min_age: Azami yaş gereksinimi
         peers_api_enabled: API'de keşfedilen sunucuların listesini yayınla
         profile_directory: Profil dizinini etkinleştir
         registrations_mode: Kim kaydolabilir
@@ -339,24 +317,7 @@ tr:
         name: Etiket
         trendable: Bu etiketin gündem altında görünmesine izin ver
         usable: Gönderilerin yerelde bu etiketi kullanmasına izin ver
-      terms_of_service:
-        changelog: Ne Değişti?
-        effective_date: Yürürlük tarihi
-        text: Hizmet Şartları
-      terms_of_service_generator:
-        admin_email: Yasal bildirimler için e-posta adresi
-        arbitration_address: Tahkim bildirimleri için fiziksel adres
-        arbitration_website: Tahkim bildirimlerini göndermek için website
-        choice_of_law: Yasa Tercihi
-        dmca_address: DMCA/telif hakkı bildirimleri için fiziksel adres
-        dmca_email: DMCA/telif hakkı bildirimleri için e-posta adresi
-        domain: Alan adı
-        jurisdiction: Yasal yetki alanı
-        min_age: Minimum yaş
       user:
-        date_of_birth_1i: Gün
-        date_of_birth_2i: Ay
-        date_of_birth_3i: Yıl
         role: Rol
         time_zone: Zaman dilimi
       user_role:
diff --git a/config/locales/simple_form.uk.yml b/config/locales/simple_form.uk.yml
index b43aaeb234..9b901cbe2c 100644
--- a/config/locales/simple_form.uk.yml
+++ b/config/locales/simple_form.uk.yml
@@ -3,7 +3,7 @@ uk:
   simple_form:
     hints:
       account:
-        attribution_domains: Один на рядок. Захищає від фальшивих атрибутів.
+        attribution_domains_as_text: Один на рядок. Захищає від фальшивих атрибутів.
         discoverable: Ваші дописи та профіль можуть бути рекомендовані в різних частинах Mastodon і ваш профіль може бути запропонований іншим користувачам.
         display_name: Ваше повне ім'я або ваш псевдонім.
         fields: Ваша домашня сторінка, займенники, вік, все, що вам заманеться.
@@ -60,7 +60,6 @@ uk:
         setting_display_media_default: Приховувати медіа, позначені як делікатними
         setting_display_media_hide_all: Завжди приховувати медіа
         setting_display_media_show_all: Завжди показувати медіа
-        setting_system_scrollbars_ui: Застосовується лише для настільних браузерів на основі Safari та Chrome
         setting_use_blurhash: Градієнти, що базуються на кольорах прихованих медіа, але роблять нерозрізненними будь-які деталі
         setting_use_pending_items: Не додавати нові повідомлення до стрічок миттєво, показувати лише після додаткового клацання
         username: Можна використовувати літери, цифри та підкреслення
@@ -75,7 +74,6 @@ uk:
       filters:
         action: Виберіть дію для виконання коли допис збігається з фільтром
         actions:
-          blur: Приховати медіа за попередженням, не приховуючи сам текст
           hide: Повністю сховати фільтрований вміст, ніби його не існує
           warn: Сховати відфільтрований вміст за попередженням, у якому вказано заголовок фільтра
       form_admin_settings:
@@ -89,7 +87,6 @@ uk:
         favicon: WEBP, PNG, GIF або JPG. Замінює стандартну піктограму Mastodon на власну.
         mascot: Змінює ілюстрацію в розширеному вебінтерфейсі.
         media_cache_retention_period: Медіафайли з дописів віддалених користувачів кешуються на вашому сервері. Якщо встановлено додатне значення, медіа буде видалено через вказану кількість днів. Якщо медіадані будуть запитані після видалення, вони будуть завантажені повторно, якщо вихідний вміст все ще доступний. Через обмеження на частоту опитування карток попереднього перегляду посилань на сторонніх сайтах, рекомендується встановити це значення не менше 14 днів, інакше картки попереднього перегляду посилань не будуть оновлюватися на вимогу раніше цього часу.
-        min_age: Користувачам буде запропоновано підтвердити дату народження під час реєстрації
         peers_api_enabled: Список доменів імен цього сервера з'явився у федівсесвіті. Сюди не входять дані чи ви пов'язані федерацією з цим сервером, а лише відомості, що вашому серверу відомо про нього. Його використовують служби, які збирають загальну статистику про федерації.
         profile_directory: У каталозі профілів перераховані всі користувачі, які погодились бути видимими.
         require_invite_text: Якщо реєстрація вимагає власноручного затвердження, зробіть текстове поле «Чому ви хочете приєднатися?» обов'язковим, а не додатковим
@@ -132,22 +129,8 @@ uk:
         show_application: Ви завжди зможете побачити, з якого застосунку опубліковано ваш допис.
       tag:
         name: Тут ви можете лише змінювати регістр літер, щоб підвищити читабельність
-      terms_of_service:
-        changelog: Можна структурувати за допомогою синтаксису Markdown.
-        text: Можна структурувати за допомогою синтаксису Markdown.
-      terms_of_service_generator:
-        admin_email: Юридичні повідомлення містять зустрічні повідомлення, ухвали суду, запити на видалення та запити правоохоронних органів.
-        arbitration_address: Може бути таким самим, як і фізична адреса вище, або "N/A", якщо використано електронну пошту.
-        arbitration_website: Може бути вебформою або "N/A", якщо використано електронну пошту.
-        choice_of_law: Місто, регіон, територія або держава внутрішні змістовні закони яких повинні керувати будь-якими та всіма твердженнями.
-        dmca_address: Для американських операторів використовуйте адресу, зареєстровану в довіднику призначених агентів DMCA. П.О. Перелік скриньок доступний за прямим запитом. Скористайтеся запитом на відмову від поштової скриньки призначеного агента Закону про захист авторських прав у цифрову епоху, щоб надіслати електронний лист до Бюро авторських прав, і опишіть, що ви домашній модератор вмісту, який боїться помсти чи відплати за свої дії та потребує використання P.O. Box, щоб видалити вашу домашню адресу з публічного перегляду.
-        dmca_email: Це може бути та сама адреса електронної пошти, яку використано в розділі «Електронна адреса для юридичних повідомлень» вище.
-        domain: Унікальна ідентифікація онлайн-сервісу, який ви надаєте.
-        jurisdiction: Укажіть країну, де живе той, хто платить за рахунками. Якщо це компанія чи інша організація, вкажіть країну, де вона зареєстрована, а також місто, регіон, територію чи штат відповідно.
-        min_age: Не повинно бути нижче мінімального віку, необхідного законодавством вашої юрисдикції.
       user:
         chosen_languages: У глобальних стрічках будуть показані дописи тільки вибраними мовами
-        date_of_birth: Ми повинні переконатися, що вам принаймні %{age}, щоб використовувати Mastodon. Ми не будемо зберігати це.
         role: Роль визначає, які права має користувач.
       user_role:
         color: Колір, який буде використовуватися для ролі у всьому інтерфейсі, як RGB у форматі hex
@@ -161,6 +144,7 @@ uk:
         url: Куди надсилатимуться події
     labels:
       account:
+        attribution_domains_as_text: Сайти дозволяють вам вказувати ваше авторство
         discoverable: Функції профілю та дописів у алгоритмах виявлення
         fields:
           name: Мітка
@@ -237,10 +221,8 @@ uk:
         setting_display_media_show_all: Показати всі
         setting_expand_spoilers: Завжди розгортати дописи з попередженнями про вміст
         setting_hide_network: Сховати вашу мережу
-        setting_missing_alt_text_modal: Запитувати перед розміщенням медіа без альтернативного тексту
         setting_reduce_motion: Менше руху в анімаціях
         setting_system_font_ui: Використовувати типовий системний шрифт
-        setting_system_scrollbars_ui: Використовувати системну панель гортання
         setting_theme: Тема сайту
         setting_trends: Показувати популярні сьогодні дописи
         setting_unfollow_modal: Показувати діалог підтвердження під час відписки від когось
@@ -259,7 +241,6 @@ uk:
         name: Хештеґ
       filters:
         actions:
-          blur: Приховати медіа з попередженням
           hide: Сховати повністю
           warn: Сховати за попередженням
       form_admin_settings:
@@ -273,7 +254,6 @@ uk:
         favicon: Піктограма сайту
         mascot: Користувацький символ (застарілий)
         media_cache_retention_period: Період збереження кешу медіа
-        min_age: Мінімальна вимога по віку
         peers_api_enabled: Опублікувати список знайдених серверів у API
         profile_directory: Увімкнути каталог профілів
         registrations_mode: Хто може зареєструватися
@@ -337,23 +317,7 @@ uk:
         name: Хештеґ
         trendable: Дозволити появу цього хештеґа у списку популярних хештеґів
         usable: Дозволити дописам використовувати цей гештеґ локально
-      terms_of_service:
-        changelog: Що змінилося?
-        text: Умови використання
-      terms_of_service_generator:
-        admin_email: Адреса електронної пошти для юридичних повідомлень
-        arbitration_address: Фізична адреса для арбітражних повідомлень
-        arbitration_website: Сайт для надсилання арбітражних повідомлень
-        choice_of_law: Вибір права
-        dmca_address: Фізична адреса для сповіщень про DMCA/авторські права
-        dmca_email: Фізична адреса для сповіщень про DMCA/авторські права
-        domain: Домен
-        jurisdiction: Правова юрисдикція
-        min_age: Мінімальний вік
       user:
-        date_of_birth_1i: День
-        date_of_birth_2i: Місяць
-        date_of_birth_3i: Рік
         role: Роль
         time_zone: Часовий пояс
       user_role:
diff --git a/config/locales/simple_form.vi.yml b/config/locales/simple_form.vi.yml
index 8b78787f86..2b1d84f595 100644
--- a/config/locales/simple_form.vi.yml
+++ b/config/locales/simple_form.vi.yml
@@ -3,7 +3,7 @@ vi:
   simple_form:
     hints:
       account:
-        attribution_domains: Bảo vệ khỏi những sự gán ghép sai.
+        attribution_domains_as_text: Mỗi cái một dòng. Bảo vệ khỏi những sự gán ghép sai.
         discoverable: Hồ sơ và tút công khai của bạn được đề xuất cho những người dùng Mastodon khác.
         display_name: Tên đầy đủ hoặc biệt danh đều được.
         fields: Trang blog của bạn, nghề nghiệp, tuổi hoặc bất cứ thứ gì.
@@ -60,7 +60,6 @@ vi:
         setting_display_media_default: Click để xem
         setting_display_media_hide_all: Luôn ẩn
         setting_display_media_show_all: Luôn hiện
-        setting_system_scrollbars_ui: Chỉ áp dụng trình duyệt Chrome và Safari bản desktop
         setting_use_blurhash: Phủ lớp màu làm nhòe đi hình ảnh nhạy cảm
         setting_use_pending_items: Dồn lại toàn bộ tút mới và chỉ hiển thị khi nhấn vào
         username: Chỉ dùng ký tự, số và dấu gạch dưới
@@ -75,7 +74,6 @@ vi:
       filters:
         action: Chọn hành động sẽ thực hiện khi một tút khớp với bộ lọc
         actions:
-          blur: Ẩn sau một cảnh báo, mà không ảnh hưởng nội dung
           hide: Ẩn hoàn toàn, như thể nó không tồn tại
           warn: Hiện cảnh báo và bộ lọc
       form_admin_settings:
@@ -89,7 +87,6 @@ vi:
         favicon: WEBP, PNG, GIF hoặc JPG. Dùng favicon Maston tùy chỉnh.
         mascot: Ghi đè hình minh họa trong giao diện web nâng cao.
         media_cache_retention_period: Các tệp phương tiện từ các tút do người dùng máy chủ khác thực hiện sẽ được lưu vào bộ đệm trên máy chủ của bạn. Khi được đặt thành giá trị dương, phương tiện sẽ bị xóa sau số ngày được chỉ định. Nếu dữ liệu phương tiện được yêu cầu sau khi bị xóa, dữ liệu đó sẽ được tải xuống lại nếu nội dung nguồn vẫn còn. Do những hạn chế về tần suất thẻ xem trước liên kết thăm dò ý kiến ​​các trang web của bên thứ ba, bạn nên đặt giá trị này thành ít nhất 14 ngày, nếu không thẻ xem trước liên kết sẽ không được cập nhật theo yêu cầu trước thời gian đó.
-        min_age: Thành viên sẽ được yêu cầu xác nhận ngày sinh của họ trong quá trình đăng ký
         peers_api_enabled: Danh sách các máy chủ khác mà máy chủ này đã liên hợp. Không có dữ liệu nào được đưa vào đây về việc bạn có liên kết với một máy chủ nhất định hay không, chỉ là máy chủ của bạn biết về nó. Điều này được sử dụng bởi các dịch vụ thu thập số liệu thống kê về liên kết theo nghĩa chung.
         profile_directory: Liệt kê tất cả người đã chọn tham gia để có thể khám phá.
         require_invite_text: Khi đăng ký yêu cầu phê duyệt thủ công, hãy đặt câu hỏi "Tại sao bạn muốn tham gia?" nhập văn bản bắt buộc thay vì tùy chọn
@@ -132,23 +129,8 @@ vi:
         show_application: Bạn sẽ luôn có thể xem ứng dụng nào đã đăng tút của mình.
       tag:
         name: Bạn có thể thay đổi cách viết hoa các chữ cái để giúp nó dễ đọc hơn
-      terms_of_service:
-        changelog: Có thể dùng cú pháp Markdown.
-        effective_date: Khung thời gian hợp lý có thể dao động từ 10 đến 30 ngày kể từ ngày bạn thông báo cho người dùng.
-        text: Có thể dùng cú pháp Markdown.
-      terms_of_service_generator:
-        admin_email: Thông báo pháp lý bao gồm thông báo phản đối, lệnh của tòa án, yêu cầu gỡ bỏ và yêu cầu của cơ quan thực thi pháp luật.
-        arbitration_address: Có thể giống với Địa chỉ liên hệ ở trên hoặc “N/A” nếu sử dụng email.
-        arbitration_website: Có thể dùng biểu mẫu online, hoặc “N/A” nếu dùng email.
-        choice_of_law: Thành phố, khu vực, lãnh thổ hoặc tiểu bang có luật riêng sẽ chi phối mọi khiếu nại.
-        dmca_address: Đối với các quản trị viên tại Hoa Kỳ, hãy sử dụng địa chỉ đã đăng ký trong Danh bạ đại lý được chỉ định của DMCA. Danh sách hộp thư bưu điện có sẵn khi yêu cầu trực tiếp, hãy sử dụng Yêu cầu miễn trừ hộp thư bưu điện của đại lý được chỉ định của DMCA để gửi email đến Văn phòng bản quyền và mô tả rằng bạn là người kiểm duyệt nội dung tại nhà, người sợ bị trả thù hoặc trừng phạt vì hành động của mình và cần sử dụng hộp thư bưu điện để xóa địa chỉ nhà của bạn khỏi chế độ xem công khai.
-        dmca_email: Có thể là cùng một email được sử dụng cho “Địa chỉ email để nhận thông báo pháp lý” ở trên.
-        domain: Mã nhận dạng duy nhất của dịch vụ trực tuyến mà bạn đang cung cấp.
-        jurisdiction: Liệt kê quốc gia nơi người trả hóa đơn sinh sống. Nếu đó là công ty hoặc tổ chức khác, hãy liệt kê quốc gia nơi công ty được thành lập và thành phố, khu vực, lãnh thổ hoặc tiểu bang nếu phù hợp.
-        min_age: Không được dưới độ tuổi tối thiểu theo quy định của luật pháp tại khu vực của bạn.
       user:
         chosen_languages: Chỉ hiển thị những tút viết bằng các ngôn ngữ sau
-        date_of_birth: Chúng tôi phải đảm bảo rằng bạn ít nhất %{age} tuổi để sử dụng Mastodon. Chúng tôi không lưu trữ thông tin này.
         role: Vai trò kiểm soát những quyền mà người dùng có.
       user_role:
         color: Màu được sử dụng cho vai trò trong toàn bộ giao diện người dùng, dưới dạng RGB ở định dạng hex
@@ -162,7 +144,7 @@ vi:
         url: Nơi những sự kiện được gửi đến
     labels:
       account:
-        attribution_domains: Các trang web được phép ghi nhận bạn
+        attribution_domains_as_text: Các trang web được ghi nhận cho bạn
         discoverable: Cho phép khám phá hồ sơ
         fields:
           name: Nhãn
@@ -239,10 +221,8 @@ vi:
         setting_display_media_show_all: Hiện toàn bộ
         setting_expand_spoilers: Luôn mở rộng tút chứa nội dung ẩn
         setting_hide_network: Ẩn quan hệ của bạn
-        setting_missing_alt_text_modal: Hiện xác nhận trước khi đăng media không có văn bản thay thế
         setting_reduce_motion: Giảm chuyển động ảnh GIF
         setting_system_font_ui: Dùng phông chữ mặc định hệ thống
-        setting_system_scrollbars_ui: Dùng scrollbar mặc định hệ thống
         setting_theme: Giao diện
         setting_trends: Hiển thị xu hướng trong ngày
         setting_unfollow_modal: Hỏi trước khi bỏ theo dõi ai đó
@@ -261,7 +241,6 @@ vi:
         name: Hashtag
       filters:
         actions:
-          blur: Ẩn kèm theo cảnh báo
           hide: Ẩn toàn bộ
           warn: Ẩn kèm theo cảnh báo
       form_admin_settings:
@@ -275,7 +254,6 @@ vi:
         favicon: Favicon
         mascot: Tùy chỉnh linh vật (kế thừa)
         media_cache_retention_period: Thời hạn lưu trữ cache media
-        min_age: Độ tuổi tối thiểu
         peers_api_enabled: Công khai danh sách các máy chủ được phát hiện trong API
         profile_directory: Cho phép hiện danh sách thành viên
         registrations_mode: Ai có thể đăng ký
@@ -339,24 +317,7 @@ vi:
         name: Hashtag
         trendable: Cho phép hashtag này lên xu hướng
         usable: Cho phép dùng hashtag này khi soạn tút
-      terms_of_service:
-        changelog: Điểm mới?
-        effective_date: Ngày có hiệu lực
-        text: Điều khoản Dịch vụ
-      terms_of_service_generator:
-        admin_email: Địa chỉ email để nhận thông báo pháp lý
-        arbitration_address: Địa chỉ thực tế để nhận thông báo trọng tài
-        arbitration_website: Trang web để nộp thông báo trọng tài
-        choice_of_law: Lựa chọn Luật pháp
-        dmca_address: Địa chỉ liên lạc để nhận thông báo DMCA/bản quyền
-        dmca_email: Địa chỉ email để nhận thông báo DMCA/bản quyền
-        domain: Tên miền
-        jurisdiction: Quyền tài phán pháp lý
-        min_age: Độ tuổi tối thiểu
       user:
-        date_of_birth_1i: Ngày
-        date_of_birth_2i: Tháng
-        date_of_birth_3i: Năm
         role: Vai trò
         time_zone: Múi giờ
       user_role:
diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml
index 5fd28497af..7e204f6b3b 100644
--- a/config/locales/simple_form.zh-CN.yml
+++ b/config/locales/simple_form.zh-CN.yml
@@ -3,14 +3,14 @@ zh-CN:
   simple_form:
     hints:
       account:
-        attribution_domains: 每行一个域名。这样就可以保护作品免受虚假署名。
+        attribution_domains_as_text: 每行一个域名。这样就可以保护作品免受虚假署名。
         discoverable: 你的公开嘟文和个人资料可能会在 Mastodon 的多个位置展示,你的个人资料可能会被推荐给其他用户。
         display_name: 你的全名或昵称。
         fields: 你的主页、人称代词、年龄,以及任何你想要添加的内容。
         indexable: 你的公开嘟文会出现在 Mastodon 的搜索结果中。无论是否勾选,与你的嘟文有过交互的人都可能通过搜索找到它们。
         note: '你可以提及 @其他人 或 #话题 。'
         show_collections: 人们将能够浏览你的关注和追随者。你关注的人会看到你关注他们。
-        unlocked: 人们将能够在不请求批准的情况下关注你。如果你希望审核关注请求并选择接受或拒绝新的关注者,请取消勾选此项。
+        unlocked: 人们将能够在不请求批准的情况下关注你。如果你希望审核关注请求并选择接受或拒绝新的粉丝,请取消勾选此项。
       account_alias:
         acct: 指定你想要迁移过来的原账号:用户名@站点域名
       account_migration:
@@ -60,7 +60,6 @@ zh-CN:
         setting_display_media_default: 隐藏被标记为敏感内容的媒体
         setting_display_media_hide_all: 始终隐藏媒体
         setting_display_media_show_all: 始终显示媒体
-        setting_system_scrollbars_ui: 仅对基于 Safari 或 Chromium 内核的桌面端浏览器有效
         setting_use_blurhash: 渐变是基于模糊后的隐藏内容生成的
         setting_use_pending_items: 点击查看时间线更新,而非自动滚动更新动态。
         username: 你只能使用字母、数字和下划线
@@ -88,7 +87,6 @@ zh-CN:
         favicon: WEBP、PNG、GIF 或 JPG。使用自定义图标覆盖 Mastodon 的默认图标。
         mascot: 覆盖高级网页界面中的绘图形象。
         media_cache_retention_period: 来自外站用户嘟文的媒体文件将被缓存到你的实例上。当该值被设为正值时,缓存的媒体文件将在指定天数后被清除。如果媒体文件在被清除后重新被请求,且源站内容仍然可用,它将被重新下载。由于链接预览卡拉取第三方站点的频率受到限制,建议将此值设置为至少 14 天,如果小于该值,链接预览卡将不会按需更新。
-        min_age: 用户注册时必须确认出生日期
         peers_api_enabled: 本站在联邦宇宙中遇到的站点列表。 此处不包含关于您是否与给定站点联合的数据,只是您的实例知道它。 这由收集一般意义上的联合统计信息的服务使用。
         profile_directory: 个人资料目录会列出所有选择可被发现的用户。
         require_invite_text: 当注册需要手动批准时,将“你为什么想要加入?”设为必填项
@@ -131,23 +129,8 @@ zh-CN:
         show_application: 无论如何,你始终可以看到是哪个应用发布了你的嘟文。
       tag:
         name: 你只能改变字母的大小写,让它更易读
-      terms_of_service:
-        changelog: 可以使用 Markdown 语法。
-        effective_date: 合理的时间范围可以是从您通知用户之日起 10 到 30 天。
-        text: 可以使用 Markdown 语法。
-      terms_of_service_generator:
-        admin_email: 法务通知包括反通知、法院命令、内容下架要求与执法机关的要求。
-        arbitration_address: 可以与上面的实际地址相同,如果使用电子邮件则为“N/A”。
-        arbitration_website: 可以是网页表单,如果使用电子邮件则为“N/A”。
-        choice_of_law: 适用内部实质法律以管辖任何及所有索赔的城市、地区、领土或州。
-        dmca_address: 如果你是位于美国的运营者,请使用在 DMCA 指定代表名录中注册的地址。如果你需要使用邮政信箱,可以直接申请。请使用 DMCA 指定代表邮政信箱豁免申请表,通过电子邮件联系版权办公室,并声明你是居家内容审核员,因担心审核操作会招致报复或打击报复,需要使用邮政信箱以避免公开家庭住址。
-        dmca_email: 可以与上面“法律声明的电子邮件地址”使用相同的电子邮件地址。
-        domain: 你所提供的在线服务的唯一标识。
-        jurisdiction: 请列出支付运营费用者所在的国家/地区。如果为公司或其他实体,请列出其注册的国家/地区以及相应的城市、地区、领地或州。
-        min_age: 不应低于您所在地法律管辖权要求的最低年龄。
       user:
         chosen_languages: 仅选中语言的嘟文会出现在公共时间线上(全不选则显示所有语言的嘟文)
-        date_of_birth: 我们必须确认%{age}岁以上的用户才能使用Mastodon。我们不会存储该信息。
         role: 角色用于控制用户拥有的权限。
       user_role:
         color: 在界面各处用于标记该角色的颜色,以十六进制 RGB 格式表示
@@ -161,7 +144,7 @@ zh-CN:
         url: 事件将被发往的目的地
     labels:
       account:
-        attribution_domains: 授权展示你的署名的网站
+        attribution_domains_as_text: 授权展示你的署名的网站
         discoverable: 在发现算法中展示你的账号与嘟文
         fields:
           name: 标签
@@ -238,10 +221,8 @@ zh-CN:
         setting_display_media_show_all: 显示全部
         setting_expand_spoilers: 一律展开具有内容警告的嘟文
         setting_hide_network: 隐藏你的社交网络
-        setting_missing_alt_text_modal: 发布媒体时若未为其设置替代文本,则显示确认对话框
         setting_reduce_motion: 降低过渡动画效果
         setting_system_font_ui: 使用系统默认字体
-        setting_system_scrollbars_ui: 使用系统默认样式的滚动条
         setting_theme: 站点主题
         setting_trends: 显示今日热门
         setting_unfollow_modal: 在取消关注前询问我
@@ -260,7 +241,6 @@ zh-CN:
         name: 话题
       filters:
         actions:
-          blur: 隐藏媒体并显示警告
           hide: 完全隐藏
           warn: 隐藏时显示警告
       form_admin_settings:
@@ -274,7 +254,6 @@ zh-CN:
         favicon: Favicon
         mascot: 自定义吉祥物(旧)
         media_cache_retention_period: 媒体缓存保留期
-        min_age: 最低年龄要求
         peers_api_enabled: 在API中公开的已知实例的服务器的列表
         profile_directory: 启用用户目录
         registrations_mode: 谁可以注册
@@ -338,24 +317,7 @@ zh-CN:
         name: 话题
         trendable: 允许在热门下显示此话题
         usable: 允许本站嘟文使用此话题
-      terms_of_service:
-        changelog: 变更说明
-        effective_date: 生效日期
-        text: 服务条款
-      terms_of_service_generator:
-        admin_email: 接收法务通知的邮箱地址
-        arbitration_address: 仲裁通知的实际送达地址
-        arbitration_website: 仲裁通知的在线提交入口
-        choice_of_law: 法律适用选择
-        dmca_address: 接收DMCA/版权通知的实际地址
-        dmca_email: 接收DMCA/版权通知的邮箱地址
-        domain: 域名
-        jurisdiction: 法律管辖区
-        min_age: 最低年龄
       user:
-        date_of_birth_1i: 日
-        date_of_birth_2i: 月
-        date_of_birth_3i: 年
         role: 角色
         time_zone: 时区
       user_role:
diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml
index c77de84e4a..dd134a58fb 100644
--- a/config/locales/simple_form.zh-HK.yml
+++ b/config/locales/simple_form.zh-HK.yml
@@ -9,6 +9,7 @@ zh-HK:
         indexable: 你的公開帖文可能會出現在 Mastodon 的搜尋結果中。無論如何,與你帖文互動過的人都能搜尋到它。
         note: '你可以 @提及他人 或使用 #主題標籤。'
         show_collections: 大家可瀏覽你追蹤的人和你的追蹤者。你追蹤的人無論如何都會看到你追蹤了他們。
+        unlocked: 大家毋須獲得批准即可追蹤你。如果你想審核追蹤請求,來接受或拒絕新追蹤者,請取消勾選。
       account_alias:
         acct: 指定欲移動之帳戶的「使用者名稱@域名」
       account_migration:
diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml
index fc86c77b74..289a24f122 100644
--- a/config/locales/simple_form.zh-TW.yml
+++ b/config/locales/simple_form.zh-TW.yml
@@ -3,7 +3,7 @@ zh-TW:
   simple_form:
     hints:
       account:
-        attribution_domains: 每行一個。以保護偽造署名。
+        attribution_domains_as_text: 每行一個。以保護偽造署名。
         discoverable: 公開嘟文及個人檔案可能於各 Mastodon 功能中被推薦,並且您的個人檔案可能被推薦至其他使用者。
         display_name: 完整名稱或暱稱。
         fields: 烘培雞、自我認同代稱、年齡,及任何您想分享的。
@@ -60,7 +60,6 @@ zh-TW:
         setting_display_media_default: 隱藏標為敏感內容的媒體
         setting_display_media_hide_all: 總是隱藏所有媒體
         setting_display_media_show_all: 總是顯示標為敏感內容的媒體
-        setting_system_scrollbars_ui: 僅套用至基於 Safari 或 Chrome 之桌面瀏覽器
         setting_use_blurhash: 彩色漸層圖樣是基於隱藏媒體內容顏色產生,所有細節將變得模糊
         setting_use_pending_items: 關閉自動捲動更新,時間軸僅於點擊後更新
         username: 您可以使用字幕、數字與底線
@@ -75,7 +74,6 @@ zh-TW:
       filters:
         action: 請選擇當嘟文符合該過濾器時將被執行之動作
         actions:
-          blur: 將多媒體隱藏於警告之後,而不隱藏文字內容
           hide: 完全隱藏過濾內容,當作它似乎不曾存在過
           warn: 隱藏過濾內容於過濾器標題之警告後
       form_admin_settings:
@@ -89,7 +87,6 @@ zh-TW:
         favicon: WEBP、PNG、GIF、或 JPG。使用自訂圖示替代預設 Mastodon favicon 圖示。
         mascot: 覆寫進階網頁介面中的圖例。
         media_cache_retention_period: 來自遠端伺服器嘟文中之多媒體內容將快取於您的伺服器。當設定為正值時,這些多媒體內容將於指定之天數後自您的儲存空間中自動刪除。若多媒體資料於刪除後被請求,且原始內容仍可存取,它們將被重新下載。由於連結預覽中第三方網站查詢頻率限制,建議將其設定為至少 14 日,否則於此之前連結預覽將不被即時更新。
-        min_age: 使用者將於註冊時被要求確認他們的生日
         peers_api_enabled: 浩瀚聯邦宇宙中與此伺服器曾經擦肩而過的網域列表。不包含關於您是否與此伺服器是否有與之串連,僅僅表示您的伺服器已知此網域。這是供收集聯邦宇宙中一般性統計資料服務使用。
         profile_directory: 個人檔案目錄將會列出那些有選擇被發現的使用者。
         require_invite_text: 如果已設定為手動審核註冊,請將「為什麼想要加入呢?」設定為必填項目。
@@ -132,23 +129,8 @@ zh-TW:
         show_application: 將總是顯示您發嘟文之應用程式
       tag:
         name: 您只能變更大小寫,例如,以使其更易讀。
-      terms_of_service:
-        changelog: 能以 Markdown 語法撰寫。
-        effective_date: 合理時間範圍可為自您通知使用者之日起的 10 至 30 天。
-        text: 能以 Markdown 語法撰寫。
-      terms_of_service_generator:
-        admin_email: 法律通知包含反駁通知、法院命令、刪除請求和執法單位請求。
-        arbitration_address: 能與上述相同之實體地址,或「N/A」如使用 email。
-        arbitration_website: 能為網路表單,或「N/A」如使用 email。
-        choice_of_law: 城市、區域、領土或州的內部實體法律應規範任何及所有權利。
-        dmca_address: 位於美國的運營團隊,請使用於 DMCA 指定代理目錄中註冊之地址。可直接請求 PO Box 清單,使用 DMCA 指定代理郵局信箱豁免請求向版權局發送電子郵件,並描述您是一名在家工作之內容管理員,擔心您的行為將遭報復,需要使用 PO Box 保護您的私人住址。
-        dmca_email: 能使用上述用於「法律通知用途電子郵件」之相同電子郵件。
-        domain: 您所提供線上服務之唯一識別。
-        jurisdiction: 列出帳單支付人之居住國家。若為公司或其他實體,請列出其註冊地所在的國家,及城市、地區、領土、或州別等。
-        min_age: 不應低於您所屬法律管轄區要求之最低年齡。
       user:
         chosen_languages: 當選取時,只有選取語言之嘟文會於公開時間軸中顯示
-        date_of_birth: 我們必須確認您至少年滿 %{age} 以使用 Mastodon。我們不會儲存此資料。
         role: 角色控制使用者有哪些權限。
       user_role:
         color: 於整個使用者介面中用於角色的顏色,十六進位格式的 RGB
@@ -162,7 +144,7 @@ zh-TW:
         url: 事件會被傳送至何處
     labels:
       account:
-        attribution_domains: 允許對您予與信譽之網站
+        attribution_domains_as_text: 允許對您予與信譽之網站
         discoverable: 於探索演算法中推薦個人檔案及嘟文
         fields:
           name: 標籤
@@ -239,10 +221,8 @@ zh-TW:
         setting_display_media_show_all: 全部顯示
         setting_expand_spoilers: 永遠展開標有內容警告的嘟文
         setting_hide_network: 隱藏您的社交網路
-        setting_missing_alt_text_modal: 發表未包含說明文字之多媒體嘟文前先詢問我
         setting_reduce_motion: 減少過渡動畫效果
         setting_system_font_ui: 使用系統預設字型
-        setting_system_scrollbars_ui: 使用系統預設捲動軸
         setting_theme: 佈景主題
         setting_trends: 顯示本日熱門趨勢
         setting_unfollow_modal: 取消跟隨某人前先詢問我
@@ -261,7 +241,6 @@ zh-TW:
         name: "「#」主題標籤"
       filters:
         actions:
-          blur: 將多媒體隱藏於警告之後
           hide: 完全隱藏
           warn: 隱藏於警告之後
       form_admin_settings:
@@ -275,7 +254,6 @@ zh-TW:
         favicon: 網站圖示 (Favicon)
         mascot: 自訂吉祥物 (legacy)
         media_cache_retention_period: 多媒體快取資料保留期間
-        min_age: 最低年齡要求
         peers_api_enabled: 於 API 中公開已知伺服器的列表
         profile_directory: 啟用個人檔案目錄
         registrations_mode: 誰能註冊
@@ -339,24 +317,7 @@ zh-TW:
         name: 主題標籤
         trendable: 允許此主題標籤於熱門趨勢下顯示
         usable: 允許嘟文使用此主題標籤
-      terms_of_service:
-        changelog: 有何異動?
-        effective_date: 生效日期
-        text: 服務條款
-      terms_of_service_generator:
-        admin_email: 法律通知用途電子郵件
-        arbitration_address: 仲裁通知之實體地址
-        arbitration_website: 提交仲裁通知之網站
-        choice_of_law: 法律選擇
-        dmca_address: DMCA 或版權通知之實體地址
-        dmca_email: DMCA 或版權通知之電子郵件地址
-        domain: 網域
-        jurisdiction: 司法管轄區
-        min_age: 最低年齡
       user:
-        date_of_birth_1i: 日
-        date_of_birth_2i: 月
-        date_of_birth_3i: 年
         role: 角色
         time_zone: 時區
       user_role:
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 8ae14231ca..c5b5e1950a 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -25,12 +25,10 @@ sk:
       one: Príspevok
       other: Príspevkov
     posts_tab_heading: Príspevky
-    self_follow_error: Nieje povolené nasledovať svoj vlastný účet
   admin:
     account_actions:
       action: Vykonaj
       already_silenced: Tento účet už bol obmedzený.
-      already_suspended: Tento účet už bol vylúčený.
       title: Vykonaj moderovací úkon voči %{acct}
     account_moderation_notes:
       create: Zanechaj poznámku
@@ -207,7 +205,6 @@ sk:
         enable_user: Povoľ užívateľa
         memorialize_account: Zmena na „in memoriam“
         promote_user: Povýš užívateľskú rolu
-        publish_terms_of_service: Zverejni podmienky prevozu
         reject_appeal: Zamietni námietku
         reject_user: Zamietni užívateľa
         remove_avatar_user: Vymaž avatar
@@ -363,7 +360,6 @@ sk:
         cancel: Zruš
         confirm: Vylúč
         preamble_html: Chystáš sa vylúčiť <strong>%{domain}</strong> a jej poddomény.
-        stop_communication: Tvoj server prestane komunikovať s týmito servermi.
         title: Potvrď blokovanie domény %{domain}
       created_msg: Doména je v štádiu blokovania
       destroyed_msg: Blokovanie domény bolo zrušené
@@ -694,6 +690,7 @@ sk:
       open: Otvor príspevok
       original_status: Pôvodný príspevok
       status_changed: Príspevok bol zmenený
+      title: Príspevky na účte
       trending: Populárne
       visibility: Viditeľnosť
       with_media: S médiami
@@ -1204,6 +1201,7 @@ sk:
   scheduled_statuses:
     over_daily_limit: Prekročil/a si denný limit %{limit} predplánovaných príspevkov
     over_total_limit: Prekročil/a si limit %{limit} predplánovaných príspevkov
+    too_soon: Dátum musí byť stanovený do budúcnosti
   sessions:
     activity: Najnovšia aktivita
     browser: Prehliadač
diff --git a/config/locales/sl.yml b/config/locales/sl.yml
index b048dbf728..e7a514c524 100644
--- a/config/locales/sl.yml
+++ b/config/locales/sl.yml
@@ -25,12 +25,9 @@ sl:
       other: Objav
       two: Objavi
     posts_tab_heading: Objave
-    self_follow_error: Ni dovoljeno slediti lastnemu računu
   admin:
     account_actions:
       action: Izvedi dejanje
-      already_silenced: Ta račun je že omejen.
-      already_suspended: Ta račun je že suspendiran.
       title: Izvedi moderirano dejanje za %{acct}
     account_moderation_notes:
       create: Pusti opombo
@@ -52,7 +49,6 @@ sl:
         title: Spremeni e-naslov za %{username}
       change_role:
         changed_msg: Vloga uspešno spremenjena!
-        edit_roles: Upravljaj z uporabniškimi vlogami
         label: Spremeni vlogo
         no_role: Brez vloge
         title: Spremeni vlogo za %{username}
@@ -193,7 +189,6 @@ sl:
         create_domain_block: Ustvari blokado domene
         create_email_domain_block: Ustvari blokado domene e-pošte
         create_ip_block: Ustvari pravilo IP
-        create_relay: Ustvari rele
         create_unavailable_domain: Ustvari domeno, ki ni na voljo
         create_user_role: Ustvari vlogo
         demote_user: Ponižaj uporabnika
@@ -205,22 +200,18 @@ sl:
         destroy_email_domain_block: Izbriši blokado domene e-pošte
         destroy_instance: Očisti domeno
         destroy_ip_block: Izbriši pravilo IP
-        destroy_relay: Izbriši rele
         destroy_status: Izbriši objavo
         destroy_unavailable_domain: Izbriši nedosegljivo domeno
         destroy_user_role: Uniči vlogo
         disable_2fa_user: Onemogoči
         disable_custom_emoji: Onemogoči emotikon po meri
-        disable_relay: Onemogoči rele
         disable_sign_in_token_auth_user: Onemogoči overjanje z žetonom po e-pošti za uporabnika
         disable_user: Onemogoči uporabnika
         enable_custom_emoji: Omogoči emotikon po meri
-        enable_relay: Omogoči rele
         enable_sign_in_token_auth_user: Omogoči overjanje z žetonom po e-pošti za uporabnika
         enable_user: Omogoči uporabnika
         memorialize_account: Spomenificiraj račun
         promote_user: Povišaj uporabnika
-        publish_terms_of_service: Objavi pogoje uporabe
         reject_appeal: Zavrni pritožbo
         reject_user: Zavrni uporabnika
         remove_avatar_user: Odstrani avatar
@@ -258,7 +249,6 @@ sl:
         create_domain_block_html: "%{name} je blokiral/a domeno %{target}"
         create_email_domain_block_html: "%{name} je dal/a na črni seznam e-pošto domene %{target}"
         create_ip_block_html: "%{name} je ustvaril/a pravilo za IP %{target}"
-        create_relay_html: "%{name} je ustvaril/a rele %{target}"
         create_unavailable_domain_html: "%{name} je prekinil/a dostavo v domeno %{target}"
         create_user_role_html: "%{name} je ustvaril/a vlogo %{target}"
         demote_user_html: "%{name} je ponižal/a uporabnika %{target}"
@@ -270,22 +260,18 @@ sl:
         destroy_email_domain_block_html: "%{name} je odblokiral/a e-pošto domene %{target}"
         destroy_instance_html: "%{name} je očistil/a domeno %{target}"
         destroy_ip_block_html: "%{name} je izbrisal/a pravilo za IP %{target}"
-        destroy_relay_html: "%{name} je izbrisal/a rele %{target}"
         destroy_status_html: "%{name} je odstranil/a objavo uporabnika %{target}"
         destroy_unavailable_domain_html: "%{name} je nadaljeval/a dostav v domeno %{target}"
         destroy_user_role_html: "%{name} je izbrisal/a vlogo %{target}"
         disable_2fa_user_html: "%{name} je onemogočil/a dvofaktorsko zahtevo za uporabnika %{target}"
         disable_custom_emoji_html: "%{name} je onemogočil/a emotikone %{target}"
-        disable_relay_html: "%{name} je onemogočil/a rele %{target}"
         disable_sign_in_token_auth_user_html: "%{name} je onemogočil/a overjanje z žetonom po e-pošti za uporabnika %{target}"
         disable_user_html: "%{name} je onemogočil/a prijavo za uporabnika %{target}"
         enable_custom_emoji_html: "%{name} je omogočil/a emotikone %{target}"
-        enable_relay_html: "%{name} je omogočil rele %{target}"
         enable_sign_in_token_auth_user_html: "%{name} je omogočil/a overjanje z žetonom po e-pošti za uporabnika %{target}"
         enable_user_html: "%{name} je omogočil/a prijavo za uporabnika %{target}"
         memorialize_account_html: "%{name} je spremenil/a račun uporabnika %{target} v spominsko stran"
         promote_user_html: "%{name} je povišal/a uporabnika %{target}"
-        publish_terms_of_service_html: "%{name} je posodobil/a pogoje uporabe"
         reject_appeal_html: "%{name} je zavrnil/a pritožbo uporabnika %{target} na moderatorsko odločitev"
         reject_user_html: "%{name} je zavrnil/a registracijo iz %{target}"
         remove_avatar_user_html: "%{name} je odstranil podobo (avatar) uporabnika %{target}"
@@ -315,7 +301,6 @@ sl:
       title: Dnevnik revizije
       unavailable_instance: "(ime domene ni na voljo)"
     announcements:
-      back: Nazaj na oznanila
       destroyed_msg: Obvestilo je bilo uspešno izbrisano!
       edit:
         title: Uredi obvestilo
@@ -324,9 +309,6 @@ sl:
       new:
         create: Ustvari obvestilo
         title: Novo obvestilo
-      preview:
-        explanation_html: 'E-poštno sporočilo bo poslano <strong>%{display_count} uporabnikom</strong>. Priloženo bo naslednje besedilo:'
-        title: Pokaži predogled oznanila
       publish: Objavi
       published_msg: Obvestilo je bilo uspešno objavljeno!
       scheduled_for: Načrtovano ob %{time}
@@ -504,9 +486,6 @@ sl:
       title: Sledi priporočilom
       unsuppress: Obnovi sledenje priporočilom
     instances:
-      audit_log:
-        title: Nedavni revizijski zapisi
-        view_all: Prikaži ves revizijski dnevnik
       availability:
         description_html:
           few: Če dostava v domeno spodleti <strong>%{count} različne dni</strong> brez uspeha, ne bo nadaljnjih poskusov dostopa, razen če je prejeta dostava <em>iz</em> domene.
@@ -643,7 +622,6 @@ sl:
         suspend_description_html: Račun in vsa njegova vsebina ne bo dostopna in bo postopoma izbrisana, interakcija z njim pa ne bo več možna. Dejanje je moč povrniti v roku 30 dni. Zaključi vse prijave zoper ta račun.
       actions_description_html: Odločite se, katere ukrepe boste sprejeli za rešitev te prijave. Če sprejmete kazenski ukrep proti prijavljenemu računu, mu bo poslano e-poštno obvestilo, razen če je izbrana kategorija <strong>Neželena pošta</strong>.
       actions_description_remote_html: Odločite se za dejanje, ki bo odločilo o tej prijavi. To bo vplivalo le na to, kako <strong>vaš</strong> strežnik komunicira s tem oddaljenim računom in obravnava njegovo vsebino.
-      actions_no_posts: To poročilo ni vezano na nobene objave, ki bi jih lahko izbrisali
       add_to_report: Dodaj več v prijavo
       already_suspended_badges:
         local: Že suspendiran na tem strežniku
@@ -860,10 +838,8 @@ sl:
       back_to_account: Nazaj na stran računa
       back_to_report: Nazaj na stran prijave
       batch:
-        add_to_report: 'Dodaj poročilu #%{id}'
         remove_from_report: Odstrani iz prijave
         report: Poročaj
-      contents: Vsebina
       deleted: Izbrisano
       favourites: Priljubljeni
       history: Zgodovina različic
@@ -872,17 +848,13 @@ sl:
       media:
         title: Mediji
       metadata: Metapodatki
-      no_history: Ta objava ni bila spremenjena
       no_status_selected: Nobena objava ni bila spremenjena, ker ni bila nobena izbrana
       open: Odpri objavo
       original_status: Izvorna objava
       reblogs: Ponovljeni blogi
-      replied_to_html: V odgovor %{acct_link}
       status_changed: Objava spremenjena
-      status_title: Avtor/ica objave @%{name}
-      title: Objave računa - @%{name}
+      title: Objave računa
       trending: V trendu
-      view_publicly: Prikaži javno
       visibility: Vidnost
       with_media: Z mediji
     strikes:
@@ -924,9 +896,6 @@ sl:
         message_html: Nobenih pravil strežnika niste določili.
       sidekiq_process_check:
         message_html: Noben proces Sidekiq ne poteka za %{value} vrst. Preglejte svojo prilagoditev Sidekiq
-      software_version_check:
-        action: Oglejte si razpoložljive posodobitve
-        message_html: Na voljo je posodobitev Mastodona.
       software_version_critical_check:
         action: Glejte razpoložljive posodobitve
         message_html: Na voljo je kritična posodobitev Mastodona. Posodobite čim prej.
@@ -953,44 +922,11 @@ sl:
       name: Ime
       newest: Najnovejše
       oldest: Najstarejše
-      open: Prikaži javno
       reset: Ponastavi
       review: Stanje pregleda
       search: Išči
       title: Ključniki
       updated_msg: Nastavitve ključnikov uspešno posodobljene
-    terms_of_service:
-      back: Nazaj na pogoje uporabe
-      changelog: Kaj je novega
-      create: Uporabi svoje
-      current: Trenutni
-      draft: Osnutek
-      generate: Uporabi predlogo
-      generates:
-        action: Generiraj
-        chance_to_review_html: "<strong>Generirani pogoji uporabe ne bodo objavljeni samodejno,</strong> tako da jih boste imeli čas preveriti. Vnesite vse potrebne podatke."
-        explanation_html: Predloga pogojev uporabe je informativne narave in naj ne služi kot pravno vodilo. O pravnih vprašanjih in specifikah se posvetujte s pravnim strokovnjakom.
-        title: Postavitev pogojev uporabe
-      going_live_on_html: Objavljeni, začnejo veljati %{date}
-      history: Zgodovina
-      live: Objavljeni
-      no_history: V pogojih uporabe še ni zabeleženih sprememb.
-      no_terms_of_service_html: Trenutno nimate nastavljenih pogojev uporabe. Ti naj bi razjasnili pravno razmerje in dodeljevanje odgovornosti med vami in vašimi uporabniki v primeru spora.
-      notified_on_html: Uporabniki so obveščeni %{date}
-      notify_users: Obvesti uporabnike
-      preview:
-        explanation_html: 'E-poštno sporočilo bo poslano <strong>%{display_count} uporabnikom</strong>, ki so se registrirali pred %{date}. Priloženo bo naslednje besedilo:'
-        send_preview: Pošlji predogled na %{email}
-        send_to_all:
-          few: Pošlji %{display_count} e-poštna sporočila
-          one: Pošlji %{display_count} e-poštno sporočilo
-          other: Pošlji %{display_count} e-poštnih sporočil
-          two: Pošlji %{display_count} e-poštni sporočili
-        title: Prikaži predogled obvestila pogojev uporabe
-      publish: Objavi
-      published_on_html: Objavljeno %{date}
-      save_draft: Shrani osnutek
-      title: Pogoji uporabe
     title: Upravljanje
     trends:
       allow: Dovoli
@@ -1206,6 +1142,7 @@ sl:
     migrate_account: Premakni se na drug račun
     migrate_account_html: Če želite ta račun preusmeriti na drugega, ga lahko <a href="%{path}">nastavite tukaj</a>.
     or_log_in_with: Ali se prijavite z
+    privacy_policy_agreement_html: Prebral_a sem in se strinjam s <a href="%{privacy_policy_path}" target="_blank">pravilnikom o zasebnosti</a>.
     progress:
       confirm: Potrdi e-pošto
       details: Vaši podatki
@@ -1230,7 +1167,7 @@ sl:
     set_new_password: Nastavi novo geslo
     setup:
       email_below_hint_html: Poglejte v mapo neželene pošte ali zaprosite za novega. Če ste podali napačen e-naslov, ga lahko popravite.
-      email_settings_hint_html: Kliknite na povezavo, ki smo vam jo poslali na %{email}, pa boste lahko začeli uporabljati Mastodon. Tukajle bomo počakali.
+      email_settings_hint_html: Kliknite povezavo, ki smo vam jo poslali, da overite %{email}. Počakali bomo.
       link_not_received: Ali ste prejeli povezavo?
       new_confirmation_instructions_sent: Čez nekaj minut boste prejeli novo e-sporočilo s potrditveno povezavo!
       title: Preverite svojo dohodno e-pošto
@@ -1239,7 +1176,7 @@ sl:
       title: Vpiši se v %{domain}
     sign_up:
       manual_review: Registracije na %{domain} ročno pregledajo naši moderatorji. Da nam olajšate obdelavo vaše prijave, zapišite kaj o sebi in zakaj si želite račun na %{domain}.
-      preamble: Če ustvarite račun na tem strežniku Mastodona, boste lahko sledili komur koli v fediverzumu, ne glede na to, kje gostuje njegov/njen račun.
+      preamble: Z računom na strežniku Mastodon boste lahko sledili vsem drugim v tem omrežju, ne glede na to, kje gostuje njihov račun.
       title: Naj vas namestimo na %{domain}.
     status:
       account_status: Stanje računa
@@ -1251,16 +1188,8 @@ sl:
       view_strikes: Pokaži pretekle ukrepe proti mojemu računu
     too_fast: Obrazec oddan prehitro, poskusite znova.
     use_security_key: Uporabi varnostni ključ
-    user_agreement_html: Prebral/a sem <a href="%{terms_of_service_path}" target="_blank">pogoje uporabe</a> in <a href="%{privacy_policy_path}" target="_blank">politiko zasebnosti</a> in z obojim soglašam
-    user_privacy_agreement_html: Prebral sem <a href="%{privacy_policy_path}" target="_blank">politiko zasebnosti</a> in soglašam z njo
   author_attribution:
     example_title: Vzorčno besedilo
-    hint_html: Ali pišete novičke ali spletni dnevnik kje drugje poleg Mastodona? Poskrbite, da bo vaše avtorstvo pravilno navedeno, ko bo kdo delil vaše delo na Mastodonu.
-    instructions: 'Poskrbite, da bo v dokumentu HTML vašega prispevka naslednja koda:'
-    more_from_html: Več od %{name}
-    s_blog: Spletni dnevnik %{name}
-    then_instructions: Nato dodajte ime domene, kamor objavljate, v spodnje polje.
-    title: Priznanje avtorstva
   challenge:
     confirm: Nadaljuj
     hint_html: "<strong>Namig:</strong> naslednjo uro vas ne bomo več vprašali po vašem geslu."
@@ -1472,67 +1401,19 @@ sl:
       overwrite: Prepiši
       overwrite_long: Zamenjaj trenutne zapise z novimi
     overwrite_preambles:
-      blocking_html:
-        few: Kaže, da želite <strong>zamenjati svoj seznam blokiranih</strong> z do <strong>%{count} računi</strong> iz <strong>%{filename}</strong>.
-        one: Kaže, da želite <strong>zamenjati svoj seznam blokiranih</strong> z do <strong>%{count} računom</strong> iz <strong>%{filename}</strong>.
-        other: Kaže, da želite <strong>zamenjati svoj seznam blokiranih</strong> z do <strong>%{count} računi</strong> iz <strong>%{filename}</strong>.
-        two: Kaže, da želite <strong>zamenjati svoj seznam blokiranih</strong> z do <strong>%{count} računoma</strong> iz <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: Kaže, da želite <strong>zamenjati svoje zaznamke</strong> z do <strong>%{count} objavami</strong> iz <strong>%{filename}</strong>.
-        one: Kaže, da želite <strong>zamenjati svoje zaznamke</strong> z do <strong>%{count} objavo</strong> iz <strong>%{filename}</strong>.
-        other: Kaže, da želite <strong>zamenjati svoje zaznamke</strong> z do <strong>%{count} objavami</strong> iz <strong>%{filename}</strong>.
-        two: Kaže, da želite <strong>zamenjati svoje zaznamke</strong> z do <strong>%{count} objavama</strong> iz <strong>%{filename}</strong>.
-      domain_blocking_html:
-        few: Kaže, da želite <strong>zamenjati svoj seznam blokiranih domen</strong> z do <strong>%{count} domenami</strong> iz <strong>%{filename}</strong>.
-        one: Kaže, da želite <strong>zamenjati svoj seznam blokiranih domen</strong> z do <strong>%{count} domeno</strong> iz <strong>%{filename}</strong>.
-        other: Kaže, da želite <strong>zamenjati svoj seznam blokiranih domen</strong> z do <strong>%{count} domenami</strong> iz <strong>%{filename}</strong>.
-        two: Kaže, da želite <strong>zamenjati svoj seznam blokiranih domen</strong> z do <strong>%{count} domenama</strong> iz <strong>%{filename}</strong>.
-      following_html:
-        few: Kaže, da želite <strong>slediti</strong> do <strong>%{count} računom</strong> iz <strong>%{filename}</strong> in <strong>nehati slediti komur koli drugemu</strong>.
-        one: Kaže, da želite <strong>slediti</strong> do <strong>%{count} računu</strong> iz <strong>%{filename}</strong> in <strong>nehati slediti komur koli drugemu</strong>.
-        other: Kaže, da želite <strong>slediti</strong> do <strong>%{count} računom</strong> iz <strong>%{filename}</strong> in <strong>nehati slediti komur koli drugemu</strong>.
-        two: Kaže, da želite <strong>slediti</strong> do <strong>%{count} računoma</strong> iz <strong>%{filename}</strong> in <strong>nehati slediti komur koli drugemu</strong>.
-      lists_html:
-        few: Kaže, da nameravate <strong>zamenjati svoje sezname</strong> z vsebino iz <strong>%{filename}</strong>. V nove sezname bodo dodani do <strong>%{count} računi</strong>.
-        one: Kaže, da nameravate <strong>zamenjati svoje sezname</strong> z vsebino iz <strong>%{filename}</strong>. V nove sezname bo dodan do <strong>%{count} račun</strong>.
-        other: Kaže, da nameravate <strong>zamenjati svoje sezname</strong> z vsebino iz <strong>%{filename}</strong>. V nove sezname bo dodanih do <strong>%{count} računov</strong>.
-        two: Kaže, da nameravate <strong>zamenjati svoje sezname</strong> z vsebino iz <strong>%{filename}</strong>. V nove sezname bosta dodana do <strong>%{count} računa</strong>.
-      muting_html:
-        few: Kaže, da nameravate <strong>zamenjati svoj seznam utišanih računov</strong> z do <strong>%{count} računi</strong> iz <strong>%{filename}</strong>.
-        one: Kaže, da nameravate <strong>zamenjati svoj seznam utišanih računov</strong> z do <strong>%{count} računom</strong> iz <strong>%{filename}</strong>.
-        other: Kaže, da nameravate <strong>zamenjati svoj seznam utišanih računov</strong> z do <strong>%{count} računi</strong> iz <strong>%{filename}</strong>.
-        two: Kaže, da nameravate <strong>zamenjati svoj seznam utišanih računov</strong> z do <strong>%{count} računoma</strong> iz <strong>%{filename}</strong>.
+      blocking_html: Svoj <strong>seznam blokiranih računov boste nadomestili</strong> z največ <strong>%{total_items} računi</strong> iz <strong>%{filename}</strong>.
+      bookmarks_html: Svoje <strong>zaznamke boste nadomestili</strong> z največ <strong>%{total_items} objavami</strong> iz <strong>%{filename}</strong>.
+      domain_blocking_html: Svoj <strong>seznam blokiranih domen boste nadomestili</strong> z največ <strong>%{total_items} domenami</strong> iz <strong>%{filename}</strong>.
+      following_html: "<strong>Začeli boste slediti</strong> največ <strong>%{total_items} računom</strong> iz <strong>%{filename}</strong> in <strong>prenehali slediti vsem ostalim</strong>."
+      lists_html: Svoje <strong>sezname boste nadomestili</strong> z vsebino datoteke <strong>%{filename}</strong>. Največ <strong>%{total_items} računov</strong> bo dodanih na nove sezname.
+      muting_html: Svoj <strong>seznam utišanih računov boste nadomestili</strong> z največ <strong>%{total_items} računi</strong> iz <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        few: Kaže, da nameravate <strong>blokirati</strong> <strong>%{count} račune</strong> iz <strong>%{filename}</strong>.
-        one: Kaže, da nameravate <strong>blokirati</strong> <strong>%{count} račun</strong> iz <strong>%{filename}</strong>.
-        other: Kaže, da nameravate <strong>blokirati</strong> <strong>%{count} računov</strong> iz <strong>%{filename}</strong>.
-        two: Kaže, da nameravate <strong>blokirati</strong> <strong>%{count} računa</strong> iz <strong>%{filename}</strong>.
-      bookmarks_html:
-        few: V svoje <strong>zaznamke</strong> boste dodali <strong>%{count} objave</strong> iz <strong>%{filename}</strong>.
-        one: V svoje <strong>zaznamke</strong> boste dodali <strong>%{count} objavo</strong> iz <strong>%{filename}</strong>.
-        other: V svoje <strong>zaznamke</strong> boste dodali <strong>%{count} objav</strong> iz <strong>%{filename}</strong>.
-        two: V svoje <strong>zaznamke</strong> boste dodali <strong>%{count} objavi</strong> iz <strong>%{filename}</strong>.
-      domain_blocking_html:
-        few: "<strong>Blokirali</strong> boste <strong>%{count} domene</strong> iz <strong>%{filename}</strong>."
-        one: "<strong>Blokirali</strong> boste <strong>%{count} domeno</strong> iz <strong>%{filename}</strong>."
-        other: "<strong>Blokirali</strong> boste <strong>%{count} domen</strong> iz <strong>%{filename}</strong>."
-        two: "<strong>Blokirali</strong> boste <strong>%{count} domeni</strong> iz <strong>%{filename}</strong>."
-      following_html:
-        few: Začeli boste <strong>slediti</strong> <strong>%{count} računom</strong> iz <strong>%{filename}</strong>.
-        one: Začeli boste <strong>slediti</strong> <strong>%{count} računu</strong> iz <strong>%{filename}</strong>.
-        other: Začeli boste <strong>slediti</strong> <strong>%{count} računom</strong> iz <strong>%{filename}</strong>.
-        two: Začeli boste <strong>slediti</strong> <strong>%{count} računoma</strong> iz <strong>%{filename}</strong>.
-      lists_html:
-        few: V svoje <strong>sezname</strong> boste dodali <strong>%{count} račune</strong> iz <strong>%{filename}</strong>. Če seznami manjkajo, bodo ustvarjeni.
-        one: V svoje <strong>sezname</strong> boste dodali <strong>%{count} račun</strong> iz <strong>%{filename}</strong>. Če seznami manjkajo, bodo ustvarjeni.
-        other: V svoje <strong>sezname</strong> boste dodali <strong>%{count} računov</strong> iz <strong>%{filename}</strong>. Če seznami manjkajo, bodo ustvarjeni.
-        two: V svoje <strong>sezname</strong> boste dodali <strong>%{count} računa</strong> iz <strong>%{filename}</strong>. Če seznami manjkajo, bodo ustvarjeni.
-      muting_html:
-        few: "<strong>Utišali</strong> boste <strong>%{count} račune</strong> iz <strong>%{filename}</strong>."
-        one: "<strong>Utišali</strong> boste <strong>%{count} račun</strong> iz <strong>%{filename}</strong>."
-        other: "<strong>Utišali</strong> boste <strong>%{count} računov</strong> iz <strong>%{filename}</strong>."
-        two: "<strong>Utišali</strong> boste <strong>%{count} računa</strong> iz <strong>%{filename}</strong>."
+      blocking_html: "<strong>Blokirali boste</strong> največ <strong>%{total_items} računov</strong> iz <strong>%{filename}</strong>."
+      bookmarks_html: "<strong>Med zaznamke</strong> boste dodali boste največ <strong>%{total_items} objav</strong> iz <strong>%{filename}</strong>."
+      domain_blocking_html: "<strong>Blokirali boste</strong> največ <strong>%{total_items} domen</strong> iz <strong>%{filename}</strong>."
+      following_html: "<strong>Začeli boste slediti</strong> največ <strong>%{total_items} računom</strong> iz <strong>%{filename}</strong>."
+      lists_html: Dodali boste največ <strong>%{total_items} računov</strong> iz <strong>%{filename}</strong> na svoje <strong>sezname</strong>. Po potrebi bodo ustvarjeni novi seznami.
+      muting_html: "<strong>Utišali boste</strong> največ <strong>%{total_items} računov</strong> iz <strong>%{filename}</strong>."
     preface: Podatke, ki ste jih izvozili iz drugega strežnika, lahko uvozite. Na primer seznam oseb, ki jih spremljate ali blokirate.
     recent_imports: Nedavni uvozi
     states:
@@ -1619,7 +1500,6 @@ sl:
   media_attachments:
     validations:
       images_and_video: Videoposnetka ni mogoče priložiti objavi, ki že vsebuje slike
-      not_found: Predstavnosti %{ids} ne najdem ali pa je že pripeta k drugi objavi
       not_ready: Datotek, katerih obdelava ni dokončana, ni mogoče pripeti. Poskusite znova kmalu!
       too_many: Ni možno priložiti več kot 4 datoteke
   migrations:
@@ -1791,7 +1671,7 @@ sl:
   scheduled_statuses:
     over_daily_limit: Za ta dan ste presegli omejitev %{limit} načrtovanih objav
     over_total_limit: Presegli ste omejitev %{limit} načrtovanih objav
-    too_soon: datum mora biti v prihodnosti
+    too_soon: Načrtovani datum mora biti v prihodnosti
   self_destruct:
     lead_html: Na žalost se <strong>%{domain}</strong> za vedno zapira. Če ste tu imeli svoj račun, ga v prihodnje ne boste mogli več uporabljati. Zahtevate lahko kopijo svojih podatkov.
     title: Ta strežnik se zapira
@@ -1962,8 +1842,6 @@ sl:
       too_late: Prepozno je, da bi se pritožili na ta ukrep
   tags:
     does_not_match_previous_name: se ne ujema s prejšnjim imenom
-  terms_of_service:
-    title: Pogoji uporabe
   themes:
     contrast: Mastodon (Visok kontrast)
     default: Mastodon (Temna)
@@ -1995,10 +1873,6 @@ sl:
     recovery_instructions_html: Če kdaj izgubite dostop do telefona, lahko uporabite eno od spodnjih obnovitvenih kod, da ponovno pridobite dostop do svojega računa. <strong>Shranite obnovitvene kode</strong>. Lahko jih natisnete in shranite z drugimi pomembnimi dokumenti.
     webauthn: Varnostni ključi
   user_mailer:
-    announcement_published:
-      description: 'Skrbniki domene %{domain} oznanjajo:'
-      subject: Storitveno oznanilo
-      title: Storitveno oznanilo %{domain}
     appeal_approved:
       action: Nastavitve računa
       explanation: Pritožbi na ukrep proti vašemu računu z dne %{strike_date}, ki ste jo oddali dne %{appeal_date}, je bilo ugodeno. Vaš račun je znova nesporen.
@@ -2028,15 +1902,6 @@ sl:
       further_actions_html: Če to niste bili vi, priporočamo da takoj ukrepate (%{action}) in omogočite dvo-ravensko overjanje (2FA), da ohranite račun varen.
       subject: Do vašega računa je bil opravljen dostop z novega naslova IP
       title: Nova prijava
-    terms_of_service_changed:
-      agreement: Če boste še naprej uporabljali %{domain}, se strinjate s temi pogoji. Če se ne, lahko kadarkoli odstopite od dogovora z domeno %{domain} tako, da izbrišete svoj račun.
-      changelog: 'Kratek povzetek tega, kaj ta posodobitev pomeni za vas:'
-      description: 'To e-poštno sporočilo ste prejeli, ker smo spremenili pogoje uporabe v domeni %{domain}. Posodobitve bodo začele veljati %{date}. Vabimo vas, da si posodobljene pogoje preberete tukaj:'
-      description_html: To e-poštno sporočilo ste prejeli, ker smo spremenili pogoje uporabe v domeni %{domain}. Posodobitve bodo začele veljati <strong>%{date}</strong>. Vabimo vas, da si posodobljene pogoje preberete <a href="%{path}" target="_blank">na tej povezavi</a>.
-      sign_off: Ekipa %{domain}
-      subject: Posodobitve naših pogojev uporabe
-      subtitle: Spreminjajo se pogoji uporabe domene %{domain}
-      title: Pomembna posodobitev
     warning:
       appeal: Pošlji pritožbo
       appeal_description: Če menite, da gre za napako, lahko pošljete pritožbo osebju %{instance}.
@@ -2126,7 +1991,6 @@ sl:
     instructions_html: Spodnjo kodo kopirajte in prilepite v HTML svojega spletnega mesta. Nato dodajte naslov svoje spletne strani v eno od dodatnih polj v svojem profilu v zavihku »Uredi profil« in shranite spremembe.
     verification: Potrditev
     verified_links: Vaše preverjene povezave
-    website_verification: Overitev spletišča
   webauthn_credentials:
     add: Dodaj nov varnostni ključ
     create:
diff --git a/config/locales/sq.yml b/config/locales/sq.yml
index 01884c10e9..7d7df6d4c2 100644
--- a/config/locales/sq.yml
+++ b/config/locales/sq.yml
@@ -187,7 +187,6 @@ sq:
         create_domain_block: Krijo Bllokim Përkatësie
         create_email_domain_block: Krijoni Bllokim Përkatësie Email-esh
         create_ip_block: Krijoni Rregull IP
-        create_relay: Krijoni Rele
         create_unavailable_domain: Krijo Përkatësi të Papërdorshme
         create_user_role: Krijoni Rol
         demote_user: Zhgradoje Përdoruesin
@@ -199,22 +198,18 @@ sq:
         destroy_email_domain_block: Fshini Bllokim Përkatësie Email-esh
         destroy_instance: Spastroje Përkatësinë
         destroy_ip_block: Fshini Rregull IP
-        destroy_relay: Fshije Relenë
         destroy_status: Fshi Gjendje
         destroy_unavailable_domain: Fshi Përkatësi të Papërdorshme
         destroy_user_role: Asgjësoje Rolin
         disable_2fa_user: Çaktivizo 2FA-në
         disable_custom_emoji: Çaktivizo Emotikon Vetjak
-        disable_relay: Çaktivizoje Relenë
         disable_sign_in_token_auth_user: Çaktivizoni për Përdoruesin Mirëfilltësim Me Token Email-i
         disable_user: Çaktivizo Përdorues
         enable_custom_emoji: Aktivizo Emotikon Vetjak
-        enable_relay: Aktivizoje Relenë
         enable_sign_in_token_auth_user: Aktivizoni për Përdoruesin Mirëfilltësim Me Token Email-i
         enable_user: Aktivizo Përdorues
         memorialize_account: Bëje Llogari Përkujtimore
         promote_user: Promovojeni Përdoruesin
-        publish_terms_of_service: Boto Kushte Shërbimi
         reject_appeal: Hidheni Poshtë Apelimin
         reject_user: Hidhe Poshtë Përdoruesin
         remove_avatar_user: Hiqe Avatarin
@@ -252,7 +247,6 @@ sq:
         create_domain_block_html: "%{name} bllokoi përkatësinë %{target}"
         create_email_domain_block_html: "%{name} bllokoi përkatësinë email %{target}"
         create_ip_block_html: "%{name} krijoi rregull për IP-në %{target}"
-        create_relay_html: "%{name} krijoi një rele %{target}"
         create_unavailable_domain_html: "%{name} ndali dërgimin drejt përkatësisë %{target}"
         create_user_role_html: "%{name} krijoi rolin %{target}"
         demote_user_html: "%{name} zhgradoi përdoruesin %{target}"
@@ -264,22 +258,18 @@ sq:
         destroy_email_domain_block_html: "%{name} zhbllokoi përkatësi email %{target}"
         destroy_instance_html: "%{name} spastroi përkatësinë %{target}"
         destroy_ip_block_html: "%{name} fshiu rregull për IP-në %{target}"
-        destroy_relay_html: "%{name} fshiu relenë %{target}"
         destroy_status_html: "%{name} hoqi gjendje nga %{target}"
         destroy_unavailable_domain_html: "%{name} rinisi dërgimin drejt përkatësisë %{target}"
         destroy_user_role_html: "%{name} fshiu rolin %{target}"
         disable_2fa_user_html: "%{name} çaktivizoi domosdoshmërinë për dyfaktorësh për përdoruesin %{target}"
         disable_custom_emoji_html: "%{name} çaktivizoi emoxhin %{target}"
-        disable_relay_html: "%{name} çaktivizoi relenë %{target}"
         disable_sign_in_token_auth_user_html: "%{name} çaktivizoi mirëfilltësim me token email për %{target}"
         disable_user_html: "%{name} çaktivizoi hyrje për përdoruesin %{target}"
         enable_custom_emoji_html: "%{name} aktivizoi emoxhin %{target}"
-        enable_relay_html: "%{name} aktivizoi relenë %{target}"
         enable_sign_in_token_auth_user_html: "%{name} aktivizoi mirëfilltësim me token email për %{target}"
         enable_user_html: "%{name} aktivizoi hyrje për përdoruesin %{target}"
         memorialize_account_html: "%{name} e shndërroi llogarinë e %{target} në një faqe përkujtimore"
         promote_user_html: "%{name} gradoi përdoruesin %{target}"
-        publish_terms_of_service_html: "%{name} botoi përditësime të kushteve të shërbimit"
         reject_appeal_html: "%{name} hodhi poshtë apelim vendimi moderimi nga %{target}"
         reject_user_html: "%{name} hodhi poshtë regjistrimin nga %{target}"
         remove_avatar_user_html: "%{name} hoqi avatarin e %{target}"
@@ -309,7 +299,6 @@ sq:
       title: Regjistër auditimesh
       unavailable_instance: "(emër përkatësie jo i passhëm)"
     announcements:
-      back: Mbrapsht te njoftimet
       destroyed_msg: Lajmërimi u fshi me sukses!
       edit:
         title: Përpunoni lajmërimin
@@ -318,10 +307,6 @@ sq:
       new:
         create: Krijoni lajmërim
         title: Lajmërim i ri
-      preview:
-        disclaimer: Ngaqë përdoruesit s’mund të zgjedhin lënien jashtë tyre, njoftimet me email do të kufizohen te njoftime të rëndësishme, të tilla si cenim të dhënash personale, ose njoftime mbylljesh shërbyesish.
-        explanation_html: 'Email-i do të dërgohet te <strong>%{display_count} përdorues</strong>. Te email-i do të përfshihet teksti vijues:'
-        title: Bëni paraparje të shënimit për njoftimin
       publish: Publikoje
       published_msg: Lajmërimi u botua me sukses!
       scheduled_for: Vënë në plan për më %{time}
@@ -480,32 +465,6 @@ sq:
       new:
         title: Importoni bllokime përkatësish
       no_file: S’u përzgjodh kartelë
-    fasp:
-      debug:
-        callbacks:
-          created_at: Krijuar më
-          delete: Fshije
-          ip: Adresë IP
-          request_body: Lëndë kërkese
-      providers:
-        active: Aktivë
-        delete: Fshije
-        edit: Përpunoni Shërbim
-        finish_registration: Përfundoje regjistrimin
-        name: Emër
-        providers: Shërbime
-        public_key_fingerprint: Shenja gishtash kyçi publik
-        registration_requested: Lyp regjistrim
-        registrations:
-          confirm: Ripohojeni
-          description: Morët një regjistrim nga një FASP. Nëse s’e filluat ju, hidheni tej. Nëse e filluat ju, krahasoni me kujdes emrin dhe shenjat e gishtave, para se të ripohoni regjistrimin.
-          reject: Hidhe tej
-          title: Ripohoni Regjistrim në FASP
-        save: Ruaje
-        select_capabilities: Përzgjidhni Aftësi
-        sign_in: Hyni
-        status: Gjendje
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Rekomandimet për ndjekje ndihmojnë përdoruesit e rinj të gjejnë shpejt lëndë me interes</strong>. Kur një përdorues nuk ka ndërvepruar mjaftueshëm me të tjerët, që të formohen rekomandime të personalizuara ndjekjeje, rekomandohen këto llogari. Ato përzgjidhen çdo ditë, prej një përzierje llogarish me shkallën më të lartë të angazhimit dhe numrin më të lartë të ndjekësve vendorë për një gjuhë të dhënë."
       language: Për gjuhën
@@ -856,10 +815,8 @@ sq:
       back_to_account: Mbrapsht te faqja e llogarisë
       back_to_report: Mbrapsht te faqja e raportimit
       batch:
-        add_to_report: 'Shtoje te raportimi #%{id}'
         remove_from_report: Hiqe prej raportimit
         report: Raportojeni
-      contents: Lëndë
       deleted: E fshirë
       favourites: Të parapëlqyer
       history: Historik versioni
@@ -868,17 +825,13 @@ sq:
       media:
         title: Media
       metadata: Tejtëdhëna
-      no_history: Ky postim s’është përpunuar
       no_status_selected: S’u ndryshua ndonjë gjendje, ngaqë s’u përzgjodh ndonjë e tillë
       open: Hape postimin
       original_status: Postim origjinal
       reblogs: Riblogime
-      replied_to_html: Iu përgjigj %{acct_link}
       status_changed: Postimi ndryshoi
-      status_title: Postim nga @%{name}
-      title: Postime llogarie - @%{name}
+      title: Gjendje llogarish
       trending: Në modë
-      view_publicly: Shiheni publikisht
       visibility: Dukshmëri
       with_media: Me media
     strikes:
@@ -953,36 +906,6 @@ sq:
       search: Kërkim
       title: Hashtag-ë
       updated_msg: Rregullimet për hashtag-ët u përditësuan me sukses
-    terms_of_service:
-      back: Mbrapsht te kushte shërbimi
-      changelog: Ç’ka ndryshuar
-      create: Përdorni tuajat
-      current: Të tanishmet
-      draft: Skicë
-      generate: Përdorni gjedhe
-      generates:
-        action: Prodhoji
-        chance_to_review_html: "<strong>Termat e shërbimit të prodhuar s’do të botohen automatikisht.</strong> Do të keni një mundësi të shqyrtoni përfundimet. Ju lutemi, që të vazhdohet, plotësoni hollësitë e nevojshme."
-        explanation_html: Gjedhja e dhënë për kushtet e shërbimit është vetëm për qëllime njohjeje dhe s’duhet marrë si këshillë ligjore, për çfarëdo çështje. Ju lutemi, për situatën tuaj dhe pyetje specifike ligjore, lidhuni me juristin tuaj.
-        title: Ujdisje Termash Shërbimi
-      going_live_on_html: Funksionalë, në fuqi që prej %{date}
-      history: Historik
-      live: Drejtpërdrejt
-      no_history: Ende s’ka ndryshime të regjistruara të kushteve të shërbimit.
-      no_terms_of_service_html: Aktualisht s’keni të formësuar terma shërbimit. Termat e shërbimit janë menduar të japin qartësi dhe t’ju mbrojnë nga penalitete potenciale në çështje gjyqësore me përdoruesit tuaj.
-      notified_on_html: Përdoruesi u njoftuan më %{date}
-      notify_users: Njoftoji përdoruesit
-      preview:
-        explanation_html: 'Email-i do t’u dërgohet <strong>%{display_count} përdoruesve</strong> që janë regjistruar para %{date}. Te email-i do të përfshihet teksti vijues:'
-        send_preview: Dërgo paraparje te %{email}
-        send_to_all:
-          one: Dërgo %{display_count} email
-          other: Dërgo %{display_count} email-e
-        title: Bëni paraparje të njoftimit të shërbimt
-      publish: Botoje
-      published_on_html: Botuar më %{date}
-      save_draft: Ruaje skicën
-      title: Kushte Shërbimi
     title: Administrim
     trends:
       allow: Lejojeni
@@ -1189,6 +1112,7 @@ sq:
     migrate_account: Kaloni në një tjetër llogari
     migrate_account_html: Nëse doni ta ridrejtoni këtë llogari te një tjetër, këtë mund <a href="%{path}">ta formësoni këtu</a>.
     or_log_in_with: Ose bëni hyrjen me
+    privacy_policy_agreement_html: I kam lexuar dhe pajtohem me <a href="%{privacy_policy_path}" target="_blank">rregullat e privatësisë</a>
     progress:
       confirm: Ripohoni email-in
       details: Hollësitë tuaja
@@ -1213,7 +1137,7 @@ sq:
     set_new_password: Caktoni fjalëkalim të ri
     setup:
       email_below_hint_html: Shihni te dosja juaj e të padëshiruarve, ose kërkoni një tjetër. Mundeni të saktësoni adresën tuaj email, nëse është gabim.
-      email_settings_hint_html: Që të filloni të përdorni Mastodon-in, klikoni mbi lidhjen që dërguam te%{email}. Do të presim këtu.
+      email_settings_hint_html: Që të verifikoni %{email}, klikoni lidhjen që ju dërguam. Do të presim këtu.
       link_not_received: S’morët lidhje?
       new_confirmation_instructions_sent: Brenda pak mintuash do të merrni një email të ri me lidhjen e ripohimit!
       title: Shihni te email-et tuaj
@@ -1222,7 +1146,7 @@ sq:
       title: Bëni hyrjen te %{domain}
     sign_up:
       manual_review: Regjistrimet te %{domain} kalojnë një shqyrtim dorazi nga moderatorët tanë. Që të na ndihmoni të përfundojmë regjistrimin tuaj, na shkruani pakëz mbi veten dhe pse doni një llogari në %{domain}.
-      preamble: Me një llogari te ky shërbyes Mastodon do të jeni në gjendje të ndiqni cilindo person në fedivers, pavarësisht se ku strehohet llogaria e tij.
+      preamble: Me një llogari në këtë shërbyes Mastodon, do të jeni në gjendje të ndiqni cilindo person tjetër në rrjet, pavarësisht se ku strehohet llogaria e tyre.
       title: Le të ujdisim llogarinë tuaj në %{domain}.
     status:
       account_status: Gjendje llogarie
@@ -1234,8 +1158,6 @@ sq:
       view_strikes: Shihni paralajmërime të dikurshme kundër llogarisë tuaj
     too_fast: Formulari u parashtrua shumë shpejt, riprovoni.
     use_security_key: Përdor kyç sigurie
-    user_agreement_html: I kam lexuar dhe pajtohen me <a href="%{terms_of_service_path}" target="_blank">kushtet e shërbimit</a> dhe <a href="%{privacy_policy_path}" target="_blank">rregullat e privatësisë</a>
-    user_privacy_agreement_html: I kam lexuar dhe pajtohem me <a href="%{privacy_policy_path}" target="_blank">rregullat e privatësisë</a>
   author_attribution:
     example_title: Tekst shembull
     hint_html: Shkruani lajme, apo artikuj blogu jashtë Mastodon-it? Kontrolloni se si ju jepet hakë, kur ndahen me të tjerët në Mastodon.
@@ -1441,43 +1363,19 @@ sq:
       overwrite: Mbishkruaje
       overwrite_long: Zëvendësoji zërat ekzistues me të rinjtë
     overwrite_preambles:
-      blocking_html:
-        one: Ju ndan një hap nga <strong>zëvendësimi i listës tuaj të bllokimeve</strong>, me <strong>%{count} llogari</strong> nga <strong>%{filename}</strong>.
-        other: Ju ndan një hap nga <strong>zëvendësimi i listës tuaj të bllokimeve</strong>, me <strong>%{count} llogari</strong> nga <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Ju ndan një hap nga <strong>zëvendësimi i faqerojtësve tuaj</strong>, me <strong>%{count} postim</strong> nga <strong>%{filename}</strong>.
-        other: Ju ndan një hap nga <strong>zëvendësimi i faqerojtësve tuaj</strong>, me <strong>%{count} postime</strong> nga <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Ju ndan një hap nga <strong>zëvendësimi i listës tuaj të bllokimeve të përkatësive</strong>, me <strong>%{count} përkatësi</strong> nga <strong>%{filename}</strong>.
-        other: Ju ndan një hap nga <strong>zëvendësimi i listës tuaj të bllokimeve të përkatësive</strong>, me <strong>%{count} përkatësi</strong> nga <strong>%{filename}</strong>.
-      following_html:
-        one: Ju ndan një hap nga <strong>ndjekja</strong> e <strong>%{count} llogarie</strong> nga <strong>%{filename}</strong> dhe <strong>reshtja e ndjekjes së gjithkujt tjetër</strong>.
-        other: Ju ndan një hap nga <strong>ndjekja</strong> e deri <strong>%{count} llogarish</strong> nga <strong>%{filename}</strong> dhe <strong>reshtja e ndjekjes së gjithkujt tjetër</strong>.
-      lists_html:
-        one: Ju ndan një hap nga <strong>zëvendësimi i listave tuaja</strong> me lëndën e <strong>%{filename}</strong>. Te listat e reja do të shtohet deri <strong>%{count} llogari</strong>.
-        other: Ju ndan një hap nga <strong>zëvendësimi i listave tuaja</strong> me lëndën e <strong>%{filename}</strong>. Te listat e reja do të shtohet deri <strong>%{count} llogari</strong>.
-      muting_html:
-        one: Ju ndan një hap nga <strong>zëvendësimi i listës tuaj të llogarisë së heshtuar</strong> me <strong>%{count} llogari nga</strong> from <strong>%{filename}</strong>.
-        other: Ju ndan një hap nga <strong>zëvendësimi i listës tuaj të llogarive të heshtuara</strong> me <strong>%{count} llogari nga</strong> from <strong>%{filename}</strong>.
+      blocking_html: Ju ndan një hap nga <strong>zëvendësimi i listës tuaj të bllokimeve</strong> me <strong>%{total_items} llogari</strong> nga <strong>%{filename}</strong>.
+      bookmarks_html: Ju ndan një hap nga <strong>zëvendësimi i faqerojtësve tuaj</strong> me <strong>%{total_items} postime</strong> nga <strong>%{filename}</strong>.
+      domain_blocking_html: Ju ndan një hap nga <strong>zëvendësimi i listës tuaj të bllokimit të përkatësive</strong> me <strong>%{total_items} përkatësi</strong> nga <strong>%{filename}</strong>.
+      following_html: Ju ndan një hap nga <strong>ndjekja</strong> e <strong>%{total_items} llogarive</strong> nga <strong>%{filename}</strong> dhe <strong>reshtja së ndjekuri këdo tjetër</strong>.
+      lists_html: Ju ndan një hap nga <strong>zëvendësimi i listave tuaja</strong> me lëndë nga <strong>%{filename}</strong>. Te listat e reja do të shtohen deri në <strong>%{total_items} llogari</strong>.
+      muting_html: Ju ndan një hpa nga <strong>zëvendësimi i listës suaj të llogarive të heshtuara</strong> me <strong>%{total_items} llogari</strong> nga <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Ju ndan një hap nga <strong>bllokimi</strong> i deri <strong>%{count} llogarie</strong> nga <strong>%{filename}</strong>.
-        other: Ju ndan një hap nga <strong>bllokimi</strong> i deri <strong>%{count} llogarive</strong> nga <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Ju ndan një hap nga shtimi te <strong>faqerojtësit</strong> tuaj i deri <strong>%{count} postimi</strong> nga <strong>%{filename}</strong>.
-        other: Ju ndan një hap nga shtimi te <strong>faqerojtësit</strong> tuaj i deri <strong>%{count} postimeve</strong> nga <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Ju ndan një hap nga <strong>bllokimi</strong> i deri <strong>%{count} përkatësie</strong> nga <strong>%{filename}</strong>.
-        other: Ju ndan një hap nga <strong>bllokimi</strong> i deri <strong>%{count} përkatësive</strong> nga <strong>%{filename}</strong>.
-      following_html:
-        one: Ju ndan një hap nga <strong>ndjekja</strong> e deri <strong>%{count} llogarie</strong> nga <strong>%{filename}</strong>.
-        other: Ju ndan një hap nga <strong>ndjekja</strong> e deri <strong>%{count} llogarive</strong> nga <strong>%{filename}</strong>.
-      lists_html:
-        one: Ju ndan një hap nga shtimi i deri <strong>%{count} llogarie</strong> nga <strong>%{filename}</strong> te <strong>listat</strong> tuaja. Nëse s’ka listë ku të shtohen, do të krijohen lista të reja.
-        other: Ju ndan një hap nga shtimi i deri <strong>%{count} llogarive</strong> nga <strong>%{filename}</strong> te <strong>listat</strong> tuaja. Nëse s’ka listë ku të shtohen, do të krijohen lista të reja.
-      muting_html:
-        one: Ju ndan një hap nga <strong>heshtimi</strong> i deri <strong>%{count} llogarie</strong> nga <strong>%{filename}</strong>.
-        other: Ju ndan një hap nga <strong>heshtimi</strong> i deri <strong>%{count} llogarive</strong> nga <strong>%{filename}</strong>.
+      blocking_html: Ju ndan një hap nga <strong>bllokimi</strong> i <strong>%{total_items} llogarive</strong> nga <strong>%{filename}</strong>.
+      bookmarks_html: Ju ndan një hap nga shtimi te <strong>faqerojtësit</strong> tuaj i <strong>%{total_items} postimeve</strong> nga <strong>%{filename}</strong>.
+      domain_blocking_html: Ju ndan një hap nga <strong>bllokimi</strong> i <strong>%{total_items} përkatësive</strong> nga <strong>%{filename}</strong>.
+      following_html: Ju ndan një hap nga <strong>ndjekja</strong> e <strong>%{total_items} llogarive</strong> nga <strong>%{filename}</strong>.
+      lists_html: Jui nda një hap nga shtimi te <strong>listat</strong> tuaja i deri <strong>%{total_items} llogarive</strong> nga <strong>%{filename}</strong>. Nëse s’ka listë ku të shtohen, do të krijohen lista të reja.
+      muting_html: Ju ndan një hap nga <strong>heshtimi</strong> i <strong>%{total_items} llogarive</strong> nga <strong>%{filename}</strong>.
     preface: Mund të importoni të dhëna që keni eksportuar nga një shërbyes tjetër, bie fjala, një listë të personave që ndiqni ose bllokoni.
     recent_imports: Importime së fundi
     states:
@@ -1734,7 +1632,7 @@ sq:
   scheduled_statuses:
     over_daily_limit: Keni tejkaluar kufirin e %{limit} mesazheve të planifikuara për atë ditë
     over_total_limit: Keni tejkaluar kufirin prej %{limit} mesazhesh të planifikuara
-    too_soon: data duhet të jetë në të ardhmen
+    too_soon: Data e planifikimit duhet të bjerë në të ardhmen
   self_destruct:
     lead_html: Mjerisht, <strong>%{domain}</strong> po mbyllet përgjithmonë. Nëse patët një llogari këtu, s’do të jeni në gjendje të vazhdoni ta përdorni, por mundeni ende të kërkoni një kopjeruajtje të të dhënave tuaja.
     title: Ky shërbyes po mbyllet
@@ -1897,8 +1795,6 @@ sq:
       too_late: Është shumë vonë për apelim të këtij paralajmërimi
   tags:
     does_not_match_previous_name: s’përputhet me emrin e mëparshëm
-  terms_of_service:
-    title: Kushte Shërbimi
   themes:
     contrast: Mastodon (Me shumë kontrast)
     default: Mastodon (I errët)
@@ -1930,10 +1826,6 @@ sq:
     recovery_instructions_html: Në ndodhtë që të humbni hyrje te telefoni juaj, mund të përdorni një nga kodet e rikthimit më poshtë, që të rifitoni hyrje te llogaria juaj. <strong>Mbajini të parrezik kodet e rikthimeve</strong>. Për shembull, mund t’i shtypni dhe t’i ruani tok me dokumente të tjerë të rëndësishëm.
     webauthn: Kyçe sigurie
   user_mailer:
-    announcement_published:
-      description: 'Përgjegjësit e %{domain} po bëjnë një njoftim:'
-      subject: Njoftim shërbimi
-      title: Njoftim shërbimi %{domain}
     appeal_approved:
       action: Rregullime Llogarie
       explanation: Apelimi i paralajmërimit kundër llogarisë tuaj më %{strike_date}, të cilin e parashtruar më %{appeal_date} është miratuar. Llogaria juaj është sërish në pozita të mira.
@@ -1963,15 +1855,6 @@ sq:
       further_actions_html: Nëse s’ishit ju, këshillojmë të %{action} menjëherë dhe të aktivizoni mirëfilltësim dyfaktorësh, për ta mbajtur llogarinë tuaj të sigurt.
       subject: Llogaria juaj është përdorur që nga një adresë e re IP
       title: Hyrje e re
-    terms_of_service_changed:
-      agreement: Duke vazhduar të përdorni %{domain}, pajtoheni më këto terma. Nëse s’pajtoheni me termat e përditësuar, mund të përfundoni pajtimin tuaj me %{domain} në çfarëdo kohe, përmes fshirjes së llogarisë tuaj.
-      changelog: 'Me një vështrim, ja se ç’do të thotë ky përditësim për ju:'
-      description: 'Po e merrni këtë email ngaqë po bëjmë disa ndryshime te kushtet tona të shërbimit te %{domain}. Këto përditësime do të hyjnë në fuqi më %{date}. Ju nxisim të shqyrtoni kushtet e përditësuara të plota këtu:'
-      description_html: Po e merrni këtë email ngaqë po bëjmë disa ndryshime te kushtet tona të shërbimit te %{domain}. Këto përditësime do të hyjnë në fuqi më <strong>%{date}</strong>. Ju nxisim t’i shqyrtoni <a href="%{path}" target="_blank">të plota kushtet e përditësuara këtu</a>.
-      sign_off: Ekipi i %{domain}
-      subject: Përditësime të termave tanë të shërbimit
-      subtitle: Termat e shërbimit të %{domain} po ndryshojnë
-      title: Përditësim i rëndësishëm
     warning:
       appeal: Parashtroni një apelim
       appeal_description: Nëse besoni se është gabim, mund t’i parashtroni një apelim stafit të %{instance}.
diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml
index 700e588550..ad14d9d131 100644
--- a/config/locales/sr-Latn.yml
+++ b/config/locales/sr-Latn.yml
@@ -807,6 +807,7 @@ sr-Latn:
       original_status: Originalna objava
       reblogs: Deljenja
       status_changed: Objava promenjena
+      title: Statusi naloga
       trending: U trendu
       visibility: Vidljivost
       with_media: Sa multimedijom
@@ -1060,6 +1061,7 @@ sr-Latn:
     migrate_account: Premeštanje u drugi nalog
     migrate_account_html: Ako želite da preusmerite ovaj nalog na neki drugi, možete to <a href="%{path}">podesiti ovde</a>.
     or_log_in_with: Ili se prijavite sa
+    privacy_policy_agreement_html: Pročitao/-la sam i saglasan/-a sam sa <a href="%{privacy_policy_path}" target="_blank">politikom privatnosti</a>
     progress:
       details: Vaši detalji
       review: Naš pregled
@@ -1082,6 +1084,7 @@ sr-Latn:
     security: Bezbednost
     set_new_password: Postavi novu lozinku
     setup:
+      email_settings_hint_html: Kliknite na vezu koji smo vam poslali da verifikujete %{email}. Čekaćemo ovde.
       link_not_received: Niste dobili vezu?
       title: Proverite svoje prijemno sanduče
     sign_in:
@@ -1089,6 +1092,7 @@ sr-Latn:
       title: Prijavite se na %{domain}
     sign_up:
       manual_review: Naši moderatori ručno pregledaju registracije na %{domain}. Da biste nam pomogli da obradimo vašu registraciju, napišite nešto o sebi i zašto želite nalog na %{domain}.
+      preamble: Sa nalogom na ovom Mastodon serveru, moći ćete da pratite bilo koga sa mreže, bez obzira na to na kom serveru se njegov/njen nalog nalazi.
       title: Hajde da Vam namestimo nalog na %{domain}.
     status:
       account_status: Status naloga
@@ -1298,6 +1302,20 @@ sr-Latn:
       merge_long: Zadržite postojeće zapise i dodajte nove
       overwrite: Zameni
       overwrite_long: Zameni trenutne zapise novima
+    overwrite_preambles:
+      blocking_html: Upravo ćete <strong>zameniti svoju listu blokiranih</strong> sa do <strong>%{total_items} naloga</strong> iz <strong>%{filename}</strong>.
+      bookmarks_html: Upravo ćete <strong>zameniti svoje obeleživače</strong> sa do <strong>%{total_items} objava</strong> iz <strong>%{filename}</strong>.
+      domain_blocking_html: Upravo ćete <strong>zameniti svoju listu blokiranih domena</strong> sa do <strong>%{total_items} domena</strong> iz <strong>%{filename}</strong>.
+      following_html: Upravo ćete <strong>pratiti</strong> do <strong>%{total_items} naloga</strong> iz <strong>%{filename}</strong> and <strong>i prestati sa praćenjem bilo koga drugog</strong>.
+      lists_html: Spremate se da <strong>zamenite svoje liste</strong> sadržajem od <strong>%{filename}</strong>. Do <strong>%{total_items} naloga</strong> će biti dodato na nove liste.
+      muting_html: Upravo ćete <strong>zameniti svoju listu ignorisanih naloga</strong> sa do <strong>%{total_items} naloga</strong> iz <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Upravo ćete <strong>blokirati</strong> do <strong>%{total_items} naloga</strong> iz <strong>%{filename}</strong>.
+      bookmarks_html: Upravo ćete dodati do <strong>%{total_items} objava</strong> iz <strong>%{filename}</strong> u vaše <strong>obeleživače</strong>.
+      domain_blocking_html: Upravo ćete <strong>blokirati</strong> do <strong>%{total_items} domena</strong> iz <strong>%{filename}</strong>.
+      following_html: Upravo ćete <strong>pratiti</strong> do <strong>%{total_items} naloga</strong> iz <strong>%{filename}</strong>.
+      lists_html: Spremate se da dodate do <strong>%{total_items} naloga</strong> od <strong>%{filename}</strong> na svoje <strong>liste</strong>. Nove liste će biti kreirane ako ne postoji lista za dodavanje.
+      muting_html: Upravo ćete <strong>ignorisati</strong> do <strong>%{total_items} naloga</strong> iz <strong>%{filename}</strong>.
     preface: Možete uvesti podatke koje ste izvezli sa druge instance, kao što su liste ljudi koje ste pratili ili blokirali.
     recent_imports: Nedavni uvozi
     states:
@@ -1541,6 +1559,7 @@ sr-Latn:
   scheduled_statuses:
     over_daily_limit: Prekoračili ste granicu od %{limit} planiranih objava za danas
     over_total_limit: Prekoračili ste granicu od %{limit} planiranih objava
+    too_soon: Planirani datum mora biti u budućnosti
   self_destruct:
     lead_html: Nažalost, <strong>%{domain}</strong> se trajno zatvara. Ako ste tamo imali nalog, nećete moći da nastavite da ga koristite, ali i dalje možete da zatražite rezervnu kopiju svojih podataka.
     title: Ovaj server se zatvara
diff --git a/config/locales/sr.yml b/config/locales/sr.yml
index 28abe3b46e..fc92b98176 100644
--- a/config/locales/sr.yml
+++ b/config/locales/sr.yml
@@ -837,6 +837,7 @@ sr:
       original_status: Оригинална објава
       reblogs: Дељења
       status_changed: Објава промењена
+      title: Статуси налога
       trending: У тренду
       visibility: Видљивост
       with_media: Са мултимедијом
@@ -1090,6 +1091,7 @@ sr:
     migrate_account: Премештање у други налог
     migrate_account_html: Ако желите да преусмерите овај налог на неки други, можете то <a href="%{path}">подесити овде</a>.
     or_log_in_with: Или се пријавите са
+    privacy_policy_agreement_html: Прочитао/-ла сам и сагласан/-а сам са <a href="%{privacy_policy_path}" target="_blank">политиком приватности</a>
     progress:
       details: Ваши детаљи
       review: Наш преглед
@@ -1112,6 +1114,7 @@ sr:
     security: Безбедност
     set_new_password: Постави нову лозинку
     setup:
+      email_settings_hint_html: Кликните на везу који смо вам послали да верификујете %{email}. Чекаћемо овде.
       link_not_received: Нисте добили везу?
       title: Проверите своје пријемно сандуче
     sign_in:
@@ -1119,6 +1122,7 @@ sr:
       title: Пријавите се на %{domain}
     sign_up:
       manual_review: Наши модератори ручно прегледају регистрације на %{domain}. Да бисте нам помогли да обрадимо вашу регистрацију, напишите нешто о себи и зашто желите налог на %{domain}.
+      preamble: Са налогом на овом Mastodon серверу, моћи ћете да пратите било кога са мреже, без обзира на то на ком серверу се његов/њен налог налази.
       title: Хајде да Вам наместимо налог на %{domain}.
     status:
       account_status: Статус налога
@@ -1328,6 +1332,20 @@ sr:
       merge_long: Задржите постојеће записе и додајте нове
       overwrite: Замени
       overwrite_long: Замени тренутне записе новима
+    overwrite_preambles:
+      blocking_html: Управо ћете <strong>заменити своју листу блокираних</strong> са до <strong>%{total_items} налога</strong> из <strong>%{filename}</strong>.
+      bookmarks_html: Управо ћете <strong>заменити своје обележиваче</strong> са до <strong>%{total_items} објава</strong> из <strong>%{filename}</strong>.
+      domain_blocking_html: Управо ћете <strong>заменити своју листу блокираних домена</strong> са до <strong>%{total_items} домена</strong> из <strong>%{filename}</strong>.
+      following_html: Управо ћете <strong>пратити</strong> до <strong>%{total_items} налога</strong> из <strong>%{filename}</strong> and <strong>и престати са праћењем било кога другог</strong>.
+      lists_html: Спремате се да <strong>замените своје листе</strong> садржајем од <strong>%{filename}</strong>. До <strong>%{total_items} налога</strong> ће бити додато на нове листе.
+      muting_html: Управо ћете <strong>заменити своју листу игнорисаних налога</strong> са до <strong>%{total_items} налога</strong> из <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Управо ћете <strong>блокирати</strong> до <strong>%{total_items} налога</strong> из <strong>%{filename}</strong>.
+      bookmarks_html: Управо ћете додати до <strong>%{total_items} објава</strong> из <strong>%{filename}</strong> у ваше <strong>обележиваче</strong>.
+      domain_blocking_html: Управо ћете <strong>блокирати</strong> до <strong>%{total_items} домена</strong> из <strong>%{filename}</strong>.
+      following_html: Управо ћете <strong>пратити</strong> до <strong>%{total_items} налога</strong> из <strong>%{filename}</strong>.
+      lists_html: Спремате се да додате до <strong>%{total_items} налога</strong> од <strong>%{filename}</strong> на своје <strong>листе</strong>. Нове листе ће бити креиране ако не постоји листа за додавање.
+      muting_html: Управо ћете <strong>игнорисати</strong> до <strong>%{total_items} налога</strong> из <strong>%{filename}</strong>.
     preface: Можете увести податке које сте извезли са друге инстанце, као што су листе људи које сте пратили или блокирали.
     recent_imports: Недавни увози
     states:
@@ -1571,6 +1589,7 @@ sr:
   scheduled_statuses:
     over_daily_limit: Прекорачили сте границу од %{limit} планираних објава за данас
     over_total_limit: Прекорачили сте границу од %{limit} планираних објава
+    too_soon: Планирани датум мора бити у будућности
   self_destruct:
     lead_html: Нажалост, <strong>%{domain}</strong> се трајно затвара. Ако сте тамо имали налог, нећете моћи да наставите да га користите, али и даље можете да затражите резервну копију својих података.
     title: Овај сервер се затвара
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index bc351a8f3b..9e15ec87cd 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -187,7 +187,6 @@ sv:
         create_domain_block: Skapa blockerad domän
         create_email_domain_block: Skapa E-post domän block
         create_ip_block: Skapa IP-regel
-        create_relay: Skapa ombud
         create_unavailable_domain: Skapa otillgänglig domän
         create_user_role: Skapa roll
         demote_user: Degradera användare
@@ -199,22 +198,18 @@ sv:
         destroy_email_domain_block: Ta bort E-post domän block
         destroy_instance: Rensa domänen
         destroy_ip_block: Radera IP-regel
-        destroy_relay: Radera ombud
         destroy_status: Radera inlägg
         destroy_unavailable_domain: Ta bort otillgänglig domän
         destroy_user_role: Förstör roll
         disable_2fa_user: Inaktivera 2FA
         disable_custom_emoji: Inaktivera egna emojis
-        disable_relay: Inaktivera ombud
         disable_sign_in_token_auth_user: Inaktivera autentisering med pollett via e-post för användare
         disable_user: Inaktivera användare
         enable_custom_emoji: Aktivera egna emojis
-        enable_relay: Aktivera ombud
         enable_sign_in_token_auth_user: Aktivera autentisering med pollett via e-post för användare
         enable_user: Aktivera användare
         memorialize_account: Minnesmärk konto
         promote_user: Befordra användare
-        publish_terms_of_service: Publicera användarvillkor
         reject_appeal: Avvisa överklagande
         reject_user: Avvisa användare
         remove_avatar_user: Ta bort avatar
@@ -252,7 +247,6 @@ sv:
         create_domain_block_html: "%{name} blockerade domänen %{target}"
         create_email_domain_block_html: "%{name} blockerade e-post domänet%{target}"
         create_ip_block_html: "%{name} skapade regel för IP %{target}"
-        create_relay_html: "%{name} skapade ombudet %{target}"
         create_unavailable_domain_html: "%{name} stoppade leverans till domänen %{target}"
         create_user_role_html: "%{name} skapade rollen %{target}"
         demote_user_html: "%{name} nedgraderade användare %{target}"
@@ -264,22 +258,18 @@ sv:
         destroy_email_domain_block_html: "%{name} avblockerade e-post domänet %{target}"
         destroy_instance_html: "%{name} rensade domän %{target}"
         destroy_ip_block_html: "%{name} tog bort regel för IP %{target}"
-        destroy_relay_html: "%{name} tog bort ombudet %{target}"
         destroy_status_html: "%{name} tog bort inlägget av %{target}"
         destroy_unavailable_domain_html: "%{name} återupptog leverans till domänen %{target}"
         destroy_user_role_html: "%{name} raderade rollen %{target}"
         disable_2fa_user_html: "%{name} inaktiverade tvåfaktorsautentiseringskrav för användaren %{target}"
         disable_custom_emoji_html: "%{name} inaktiverade emoji %{target}"
-        disable_relay_html: "%{name} inaktiverade ombudet %{target}"
         disable_sign_in_token_auth_user_html: "%{name} inaktiverade e-posttokenautentisering för %{target}"
         disable_user_html: "%{name} stängde av inloggning för användaren %{target}"
         enable_custom_emoji_html: "%{name} aktiverade emoji %{target}"
-        enable_relay_html: "%{name} aktiverat ombudet %{target}"
         enable_sign_in_token_auth_user_html: "%{name} aktiverade e-posttokenautentisering för %{target}"
         enable_user_html: "%{name} aktiverade inloggning för användaren %{target}"
         memorialize_account_html: "%{name} gjorde %{target}s konto till en minnessida"
         promote_user_html: "%{name} befordrade användaren %{target}"
-        publish_terms_of_service_html: "%{name} publicerade uppdateringar till användarvillkoren"
         reject_appeal_html: "%{name} avvisade överklagande av modereringsbeslut från %{target}"
         reject_user_html: "%{name} avvisade registrering från %{target}"
         remove_avatar_user_html: "%{name} tog bort %{target}s avatar"
@@ -475,19 +465,6 @@ sv:
       new:
         title: Importera domänblockeringar
       no_file: Ingen fil vald
-    fasp:
-      debug:
-        callbacks:
-          ip: IP-adress
-      providers:
-        base_url: Bas-URL
-        delete: Ta bort
-        finish_registration: Slutför registrering
-        name: Namn
-        registrations:
-          confirm: Bekräfta
-          reject: Avvisa
-        save: Spara
     follow_recommendations:
       description_html: "<strong>Följrekommendationer hjälper nya användare att snabbt hitta intressant innehåll</strong>. När en användare inte har interagerat med andra tillräckligt mycket för att forma personliga följrekommendationer, rekommenderas istället dessa konton. De beräknas om varje dag från en mix av konton med nylig aktivitet och högst antal följare för ett givet språk."
       language: För språket
@@ -841,10 +818,8 @@ sv:
       back_to_account: Tillbaka till kontosidan
       back_to_report: Tillbaka till rapportsidan
       batch:
-        add_to_report: 'Lägg till i rapport #%{id}'
         remove_from_report: Ta bort från rapport
         report: Rapportera
-      contents: Innehåll
       deleted: Raderad
       favourites: Favoriter
       history: Versionshistorik
@@ -853,17 +828,13 @@ sv:
       media:
         title: Media
       metadata: Metadata
-      no_history: Detta inlägg har inte redigerats
       no_status_selected: Inga inlägg ändrades eftersom inga valdes
       open: Öppna inlägg
       original_status: Ursprungligt inlägg
       reblogs: Ombloggningar
-      replied_to_html: Svarade på %{acct_link}
       status_changed: Inlägg ändrat
-      status_title: Inlägg av @%{name}
-      title: Kontoinlägg - @%{name}
+      title: Kontoinlägg
       trending: Trendande
-      view_publicly: Visa offentligt
       visibility: Synlighet
       with_media: Med media
     strikes:
@@ -940,35 +911,6 @@ sv:
       search: Sök
       title: Hashtaggar
       updated_msg: Hashtagg-inställningarna har uppdaterats
-    terms_of_service:
-      back: Tillbaka till användarvillkoren
-      changelog: Vad har ändrats
-      create: Använd dina egna
-      current: Nuvarande
-      draft: Utkast
-      generate: Använd mall
-      generates:
-        action: Generera
-        chance_to_review_html: "<strong>De genererade villkoren för tjänsten kommer inte att publiceras automatiskt.</strong> Du kommer att ha en chans att granska resultatet. Vänligen fyll i nödvändig information för att fortsätta."
-        explanation_html: Användarvillkorsmallen tillhandahålls endast i informationssyfte, och skall inte tolkas som juridisk rådgivning i något ämne. Rådgör med ditt eget juridiska ombud om din situation och specifika juridiska frågor du har.
-        title: Inställningar för användarvillkor
-      history: Historik
-      live: Aktuella
-      no_history: Det finns inga sparade ändringar i användarvillkoren ännu.
-      no_terms_of_service_html: Du har för närvarande inte några användarvillkor. Användarvillkoren är avsedda att ge klarhet och skydda dig i tvister med dina användare.
-      notified_on_html: Användare meddelade på %{date}
-      notify_users: Meddela användare
-      preview:
-        explanation_html: 'E-postmeddelandet kommer att skickas till <strong>%{display_count} användare</strong> som har registrerat sig före %{date}. Följande text kommer att inkluderas i meddelandet:'
-        send_preview: Skicka till %{email} för förhandsgranskning
-        send_to_all:
-          one: Skicka %{display_count} meddelande
-          other: Skicka %{display_count} meddelanden
-        title: Förhandsgranska användarvillkorsmeddelande
-      publish: Publicera
-      published_on_html: Publicerade den %{date}
-      save_draft: Spara utkast
-      title: Användarvillkor
     title: Administration
     trends:
       allow: Tillåt
@@ -1176,6 +1118,7 @@ sv:
     migrate_account: Flytta till ett annat konto
     migrate_account_html: Om du vill omdirigera detta konto till ett annat, kan du <a href="%{path}">konfigurera det här</a>.
     or_log_in_with: Eller logga in med
+    privacy_policy_agreement_html: Jag har läst och godkänner <a href="%{privacy_policy_path}" target="_blank">integritetspolicyn</a>
     progress:
       confirm: Bekräfta e-postadress
       details: Dina uppgifter
@@ -1200,7 +1143,7 @@ sv:
     set_new_password: Skriv in nytt lösenord
     setup:
       email_below_hint_html: Kolla din skräppost-mapp eller begär en ny. Du kan korrigera din e-postadress om den är fel.
-      email_settings_hint_html: Klicka på länken vi skickade till %{email} för att börja använda Mastodon. Vi väntar här.
+      email_settings_hint_html: Klicka på länken som vi har skickat till dig för att bekräfta %{email}. Vi väntar här.
       link_not_received: Fick du ingen länk?
       new_confirmation_instructions_sent: Du kommer att få ett nytt e-postmeddelande med bekräftelselänken om några minuter!
       title: Kolla din inkorg
@@ -1209,7 +1152,7 @@ sv:
       title: Logga in på %{domain}
     sign_up:
       manual_review: Registreringar på %{domain} går igenom manuell granskning av våra moderatorer. För att hjälpa oss att hantera din registrering, skriv lite om dig själv och varför du vill ha ett konto på %{domain}.
-      preamble: Med ett konto på denna Mastodon-server kan du följa alla andra personer i fediversum, oavsett vilken server deras konto tillhör.
+      preamble: Med ett konto på denna Mastodon-server kan du följa alla andra personer på nätverket, oavsett vilken server deras konto tillhör.
       title: Låt oss få igång dig på %{domain}.
     status:
       account_status: Kontostatus
@@ -1221,8 +1164,6 @@ sv:
       view_strikes: Visa tidigare prickar på ditt konto
     too_fast: Formuläret har skickats för snabbt, försök igen.
     use_security_key: Använd säkerhetsnyckel
-    user_agreement_html: Jag har läst och godkänner <a href="%{terms_of_service_path}" target="_blank">användarvillkoren</a> och <a href="%{privacy_policy_path}" target="_blank">integritetspolicy</a>
-    user_privacy_agreement_html: Jag har läst och godkänner <a href="%{privacy_policy_path}" target="_blank">integritetspolicyn</a>
   author_attribution:
     example_title: Exempeltext
     hint_html: Skriver du nyheter eller bloggartiklar utanför Mastodon? Kontrollera hur du får krediteras när de delas på Mastodon.
@@ -1428,43 +1369,19 @@ sv:
       overwrite: Skriv över
       overwrite_long: Ersätt de nuvarande uppgifterna med de nya
     overwrite_preambles:
-      blocking_html:
-        one: Du är på väg att <strong>ersätta din blockeringslista</strong> med upp till <strong>%{count} konto</strong> från <strong>%{filename}</strong>.
-        other: Du är på väg att <strong>ersätta din blockeringslista</strong> med upp till <strong>%{count} konton</strong> från <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Du är på väg att <strong>ersätta din blockeringslista</strong> med upp till <strong>%{count} inlägg</strong> från <strong>%{filename}</strong>.
-        other: Du är på väg att <strong>ersätta din blockeringslista</strong> med upp till <strong>%{count} inlägg</strong> från <strong>%{filename}</strong>.
-      domain_blocking_html:
-        one: Du är på väg att <strong>ersätta din blockeringslista</strong> med upp till <strong>%{count} domän</strong> från <strong>%{filename}</strong>.
-        other: Du är på väg att <strong>ersätta din blockeringslista</strong> med upp till <strong>%{count} domäner</strong> från <strong>%{filename}</strong>.
-      following_html:
-        one: Du är på väg till <strong>följ</strong> upp till <strong>%{count} konto</strong> från <strong>%{filename}</strong> och <strong>sluta följa någon annan</strong>.
-        other: Du är på väg till <strong>följ</strong> upp till <strong>%{count} konton</strong> från <strong>%{filename}</strong> och <strong>sluta följa någon annan</strong>.
-      lists_html:
-        one: Du är på väg att <strong>ersätta dina listor</strong> med innehållet i <strong>%{filename}</strong>. Upp till <strong>%{count} konto</strong> kommer att läggas till i nya listor.
-        other: Du är på väg att <strong>ersätta dina listor</strong> med innehållet i <strong>%{filename}</strong>. Upp till <strong>%{count} konton</strong> kommer att läggas till i nya listor.
-      muting_html:
-        one: Du är på väg att <strong>ersätta din lista med tystade konton</strong> med upp till <strong>%{count} konto</strong> från <strong>%{filename}</strong>.
-        other: Du är på väg att <strong>ersätta din lista med tystade konton</strong> med upp till <strong>%{count} konton</strong> från <strong>%{filename}</strong>.
+      blocking_html: Du är på väg att <strong>ersätta din blockeringslista</strong> med upp till <strong>%{total_items} konton</strong> från <strong>%{filename}</strong>.
+      bookmarks_html: Du är på väg att <strong>ersätta din blockeringslista</strong> med upp till <strong>%{total_items} inlägg</strong> från <strong>%{filename}</strong>.
+      domain_blocking_html: Du är på väg att <strong>ersätta din blockeringslista</strong> med upp till <strong>%{total_items} domäner</strong> från <strong>%{filename}</strong>.
+      following_html: Du är på väg till <strong>följ</strong> upp till <strong>%{total_items} konton</strong> från <strong>%{filename}</strong> och <strong>sluta följa någon annan</strong>.
+      lists_html: Du är på väg att <strong>ersätta dina listor</strong> med innehållet i <strong>%{filename}</strong>. Upp till <strong>%{total_items} konton</strong> kommer att läggas till i nya listor.
+      muting_html: Du är på väg att <strong>ersätta din lista med tystade konton</strong> med upp till <strong>%{total_items} konton</strong> från <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        one: Du är på väg att <strong>blockera</strong> med upp till <strong>%{count} konto</strong> från <strong>%{filename}</strong>.
-        other: Du är på väg att <strong>blockera</strong> med upp till <strong>%{count} konton</strong> från <strong>%{filename}</strong>.
-      bookmarks_html:
-        one: Du håller på att lägga upp till <strong>%{count} inlägg</strong> från <strong>%{filename}</strong> till dina <strong>bokmärken</strong>.
-        other: Du håller på att lägga upp till <strong>%{count} inlägg</strong> från <strong>%{filename}</strong> till dina <strong>bokmärken</strong>.
-      domain_blocking_html:
-        one: Du är på väg att <strong>blockera</strong> upp till <strong>%{count} domän</strong> från <strong>%{filename}</strong>.
-        other: Du är på väg att <strong>blockera</strong> upp till <strong>%{count} domäner</strong> från <strong>%{filename}</strong>.
-      following_html:
-        one: Du är på väg att <strong>följa</strong> upp till <strong>%{count} konto</strong> från <strong>%{filename}</strong>.
-        other: Du är på väg att <strong>följa</strong> upp till <strong>%{count} konton</strong> från <strong>%{filename}</strong>.
-      lists_html:
-        one: Du håller på att lägga upp till <strong>%{count} konto</strong> från <strong>%{filename}</strong> till dina <strong>listor</strong>. Nya listor kommer att skapas om det inte finns någon lista att lägga till.
-        other: Du håller på att lägga upp till <strong>%{count} konton</strong> från <strong>%{filename}</strong> till dina <strong>listor</strong>. Nya listor kommer att skapas om det inte finns någon lista att lägga till.
-      muting_html:
-        one: Du är på väg att <strong>tysta</strong> upp till <strong>%{count} konto</strong> från <strong>%{filename}</strong>.
-        other: Du är på väg att <strong>tysta</strong> upp till <strong>%{count} konton</strong> från <strong>%{filename}</strong>.
+      blocking_html: Du är på väg att <strong>blockera</strong> med upp till <strong>%{total_items} konton</strong> från <strong>%{filename}</strong>.
+      bookmarks_html: Du håller på att lägga upp till <strong>%{total_items} inlägg</strong> från <strong>%{filename}</strong> till dina <strong>bokmärken</strong>.
+      domain_blocking_html: Du är på väg att <strong>blockera</strong> upp till <strong>%{total_items} domäner</strong> från <strong>%{filename}</strong>.
+      following_html: Du är på väg att <strong>följa</strong> upp till <strong>%{total_items} konton</strong> från <strong>%{filename}</strong>.
+      lists_html: Du håller på att lägga upp till <strong>%{total_items} konton</strong> från <strong>%{filename}</strong> till dina <strong>listor</strong>. Nya listor kommer att skapas om det inte finns någon lista att lägga till.
+      muting_html: Du är på väg att <strong>tysta</strong> upp till <strong>%{total_items} konton</strong> från <strong>%{filename}</strong>.
     preface: Du kan importera data som du exporterat från en annan instans, till exempel en lista över personer du följer eller blockerar.
     recent_imports: Nyligen importerade
     states:
@@ -1721,7 +1638,7 @@ sv:
   scheduled_statuses:
     over_daily_limit: Du har överskridit dygnsgränsen på %{limit} schemalagda inlägg
     over_total_limit: Du har överskridit gränsen på %{limit} schemalagda inlägg
-    too_soon: datumet måste vara i framtiden
+    too_soon: Schemaläggningsdatumet måste vara i framtiden
   self_destruct:
     lead_html: Tyvärr stänger <strong>%{domain}</strong> för gott. Om du hade ett konto där kommer du inte längre kunna använda det, men du kan fortfarande begära en säkerhetskopia av din data.
     title: Denna server stänger ned
@@ -1884,8 +1801,6 @@ sv:
       too_late: Det är för sent att överklaga denna strejk
   tags:
     does_not_match_previous_name: matchar inte det föregående namnet
-  terms_of_service:
-    title: Användarvillkor
   themes:
     contrast: Mastodon (Hög kontrast)
     default: Mastodon (Mörk)
@@ -1946,13 +1861,6 @@ sv:
       further_actions_html: Om detta inte var du, rekommenderar vi att du snarast %{action} och aktiverar tvåfaktorsautentisering för att hålla ditt konto säkert.
       subject: Ditt konto har nåtts från en ny IP-adress
       title: En ny inloggning
-    terms_of_service_changed:
-      agreement: Genom att fortsätta använda %{domain} godkänner du dessa villkor. Om du inte håller med om de uppdaterade villkoren kan du när som helst säga upp ditt avtal med %{domain} genom att radera ditt konto.
-      changelog: 'I korthet, här är vad denna uppdatering innebär för dig:'
-      sign_off: "%{domain} teamet"
-      subject: Uppdateringar till våra användarvillkor
-      subtitle: Villkoren för tjänsten på %{domain} ändras
-      title: Viktig uppdatering
     warning:
       appeal: Skicka överklagan
       appeal_description: Om du anser detta felaktigt kan du skicka överklagan till administratörerna av %{instance}.
diff --git a/config/locales/th.yml b/config/locales/th.yml
index 19f9e4da1f..1a580ad6f2 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -184,7 +184,6 @@ th:
         create_domain_block: สร้างการปิดกั้นโดเมน
         create_email_domain_block: สร้างการปิดกั้นโดเมนอีเมล
         create_ip_block: สร้างกฎ IP
-        create_relay: สร้างรีเลย์
         create_unavailable_domain: สร้างโดเมนที่ไม่พร้อมใช้งาน
         create_user_role: สร้างบทบาท
         demote_user: ลดขั้นผู้ใช้
@@ -196,22 +195,18 @@ th:
         destroy_email_domain_block: ลบการปิดกั้นโดเมนอีเมล
         destroy_instance: ล้างข้อมูลโดเมน
         destroy_ip_block: ลบกฎ IP
-        destroy_relay: ลบรีเลย์
         destroy_status: ลบโพสต์
         destroy_unavailable_domain: ลบโดเมนที่ไม่พร้อมใช้งาน
         destroy_user_role: ทำลายบทบาท
         disable_2fa_user: ปิดใช้งาน 2FA
         disable_custom_emoji: ปิดใช้งานอีโมจิที่กำหนดเอง
-        disable_relay: ปิดใช้งานรีเลย์
         disable_sign_in_token_auth_user: ปิดใช้งานการรับรองความถูกต้องด้วยโทเคนอีเมลสำหรับผู้ใช้
         disable_user: ปิดใช้งานผู้ใช้
         enable_custom_emoji: เปิดใช้งานอีโมจิที่กำหนดเอง
-        enable_relay: เปิดใช้งานรีเลย์
         enable_sign_in_token_auth_user: เปิดใช้งานการรับรองความถูกต้องด้วยโทเคนอีเมลสำหรับผู้ใช้
         enable_user: เปิดใช้งานผู้ใช้
         memorialize_account: ทำให้บัญชีเป็นอนุสรณ์
         promote_user: เลื่อนขั้นผู้ใช้
-        publish_terms_of_service: เผยแพร่เงื่อนไขการให้บริการ
         reject_appeal: ปฏิเสธการอุทธรณ์
         reject_user: ปฏิเสธผู้ใช้
         remove_avatar_user: เอาภาพประจำตัวออก
@@ -249,7 +244,6 @@ th:
         create_domain_block_html: "%{name} ได้ปิดกั้นโดเมน %{target}"
         create_email_domain_block_html: "%{name} ได้ปิดกั้นโดเมนอีเมล %{target}"
         create_ip_block_html: "%{name} ได้สร้างกฎสำหรับ IP %{target}"
-        create_relay_html: "%{name} ได้สร้างรีเลย์ %{target}"
         create_unavailable_domain_html: "%{name} ได้หยุดการจัดส่งไปยังโดเมน %{target}"
         create_user_role_html: "%{name} ได้สร้างบทบาท %{target}"
         demote_user_html: "%{name} ได้ลดขั้นผู้ใช้ %{target}"
@@ -261,17 +255,14 @@ th:
         destroy_email_domain_block_html: "%{name} ได้เลิกปิดกั้นโดเมนอีเมล %{target}"
         destroy_instance_html: "%{name} ได้ล้างข้อมูลโดเมน %{target}"
         destroy_ip_block_html: "%{name} ได้ลบกฎสำหรับ IP %{target}"
-        destroy_relay_html: "%{name} ได้ลบรีเลย์ %{target}"
         destroy_status_html: "%{name} ได้เอาโพสต์โดย %{target} ออก"
         destroy_unavailable_domain_html: "%{name} ได้ทำการจัดส่งไปยังโดเมน %{target} ต่อ"
         destroy_user_role_html: "%{name} ได้ลบบทบาท %{target}"
         disable_2fa_user_html: "%{name} ได้ปิดใช้งานความต้องการสองปัจจัยสำหรับผู้ใช้ %{target}"
         disable_custom_emoji_html: "%{name} ได้ปิดใช้งานอีโมจิ %{target}"
-        disable_relay_html: "%{name} ได้ปิดใช้งานรีเลย์ %{target}"
         disable_sign_in_token_auth_user_html: "%{name} ได้ปิดใช้งานการรับรองความถูกต้องด้วยโทเคนอีเมลสำหรับ %{target}"
         disable_user_html: "%{name} ได้ปิดใช้งานการเข้าสู่ระบบสำหรับผู้ใช้ %{target}"
         enable_custom_emoji_html: "%{name} ได้เปิดใช้งานอีโมจิ %{target}"
-        enable_relay_html: "%{name} ได้เปิดใช้งานรีเลย์ %{target}"
         enable_sign_in_token_auth_user_html: "%{name} ได้เปิดใช้งานการรับรองความถูกต้องด้วยโทเคนอีเมลสำหรับ %{target}"
         enable_user_html: "%{name} ได้เปิดใช้งานการเข้าสู่ระบบสำหรับผู้ใช้ %{target}"
         memorialize_account_html: "%{name} ได้เปลี่ยนบัญชีของ %{target} เป็นหน้าอนุสรณ์"
@@ -466,12 +457,6 @@ th:
       new:
         title: นำเข้าการปิดกั้นโดเมน
       no_file: ไม่ได้เลือกไฟล์
-    fasp:
-      debug:
-        callbacks:
-          created_at: สร้างเมื่อ
-          delete: ลบ
-          ip: ที่อยู่ IP
     follow_recommendations:
       description_html: "<strong>คำแนะนำการติดตามช่วยให้ผู้ใช้ใหม่ค้นหาเนื้อหาที่น่าสนใจได้อย่างรวดเร็ว</strong> เมื่อผู้ใช้ไม่ได้โต้ตอบกับผู้อื่นมากพอที่จะสร้างคำแนะนำการติดตามเฉพาะบุคคล จะแนะนำบัญชีเหล่านี้แทน จะคำนวณคำแนะนำใหม่เป็นประจำทุกวันจากบัญชีต่าง ๆ ที่มีการมีส่วนร่วมล่าสุดสูงสุดและจำนวนผู้ติดตามในเซิร์ฟเวอร์สูงสุดสำหรับภาษาที่กำหนด"
       language: สำหรับภาษา
@@ -819,10 +804,8 @@ th:
       back_to_account: กลับไปที่หน้าบัญชี
       back_to_report: กลับไปที่หน้ารายงาน
       batch:
-        add_to_report: 'เพิ่มไปยังรายงาน #%{id}'
         remove_from_report: เอาออกจากรายงาน
         report: รายงาน
-      contents: เนื้อหา
       deleted: ลบแล้ว
       favourites: รายการโปรด
       history: ประวัติรุ่น
@@ -831,17 +814,13 @@ th:
       media:
         title: สื่อ
       metadata: ข้อมูลอภิพันธุ์
-      no_history: ไม่มีการแก้ไขโพสต์นี้
       no_status_selected: ไม่มีการเปลี่ยนแปลงโพสต์เนื่องจากไม่มีการเลือก
       open: เปิดโพสต์
       original_status: โพสต์ดั้งเดิม
       reblogs: การดัน
-      replied_to_html: ตอบกลับ %{acct_link}
       status_changed: เปลี่ยนโพสต์แล้ว
-      status_title: โพสต์โดย @%{name}
-      title: โพสต์ของบัญชี - @%{name}
+      title: โพสต์ของบัญชี
       trending: กำลังนิยม
-      view_publicly: ดูแบบสาธารณะ
       visibility: การมองเห็น
       with_media: มีสื่อ
     strikes:
@@ -918,25 +897,6 @@ th:
       search: ค้นหา
       title: แฮชแท็ก
       updated_msg: อัปเดตการตั้งค่าแฮชแท็กสำเร็จ
-    terms_of_service:
-      back: กลับไปที่เงื่อนไขการให้บริการ
-      create: ใช้ของคุณเอง
-      current: ปัจจุบัน
-      draft: แบบร่าง
-      generate: ใช้แม่แบบ
-      generates:
-        action: สร้าง
-      history: ประวัติ
-      live: สด
-      notify_users: แจ้งเตือนผู้ใช้
-      preview:
-        send_preview: ส่งตัวอย่างไปยัง %{email}
-        send_to_all:
-          other: ส่ง %{display_count} อีเมล
-      publish: เผยแพร่
-      published_on_html: เผยแพร่เมื่อ %{date}
-      save_draft: บันทึกแบบร่าง
-      title: เงื่อนไขการให้บริการ
     title: การดูแล
     trends:
       allow: อนุญาต
@@ -1095,7 +1055,7 @@ th:
     salutation: "%{name},"
     settings: 'เปลี่ยนการกำหนดลักษณะอีเมล: %{link}'
     unsubscribe: เลิกบอกรับ
-    view: 'ดู:'
+    view: 'มุมมอง:'
     view_profile: ดูโปรไฟล์
     view_status: ดูโพสต์
   applications:
@@ -1140,6 +1100,7 @@ th:
     migrate_account: ย้ายไปยังบัญชีอื่น
     migrate_account_html: หากคุณต้องการเปลี่ยนเส้นทางบัญชีนี้ไปยังบัญชีอื่น คุณสามารถ <a href="%{path}">กำหนดค่าบัญชีที่นี่</a>
     or_log_in_with: หรือเข้าสู่ระบบด้วย
+    privacy_policy_agreement_html: ฉันได้อ่านและเห็นด้วยกับ <a href="%{privacy_policy_path}" target="_blank">นโยบายความเป็นส่วนตัว</a>
     progress:
       confirm: ยืนยันอีเมล
       details: รายละเอียดของคุณ
@@ -1164,6 +1125,7 @@ th:
     set_new_password: ตั้งรหัสผ่านใหม่
     setup:
       email_below_hint_html: ตรวจสอบโฟลเดอร์สแปมของคุณ หรือขออีเมลอื่น คุณสามารถแก้ไขที่อยู่อีเมลของคุณหากที่อยู่อีเมลผิด
+      email_settings_hint_html: คลิกลิงก์ที่เราส่งถึงคุณเพื่อยืนยัน %{email} เราจะรออยู่ตรงนี้
       link_not_received: ไม่ได้รับลิงก์?
       new_confirmation_instructions_sent: คุณจะได้รับอีเมลใหม่พร้อมลิงก์การยืนยันในไม่กี่นาที!
       title: ตรวจสอบกล่องขาเข้าของคุณ
@@ -1172,6 +1134,7 @@ th:
       title: เข้าสู่ระบบ %{domain}
     sign_up:
       manual_review: การลงทะเบียนใน %{domain} จะผ่านการตรวจทานด้วยตนเองโดยผู้กลั่นกรองของเรา เพื่อช่วยให้เราประมวลผลการลงทะเบียนของคุณ เขียนสักนิดเกี่ยวกับตัวคุณเองและเหตุผลที่คุณต้องการบัญชีใน %{domain}
+      preamble: ด้วยบัญชีในเซิร์ฟเวอร์ Mastodon นี้ คุณจะสามารถติดตามบุคคลอื่นใดในเครือข่าย โดยไม่คำนึงถึงที่ซึ่งบัญชีของเขาได้รับการโฮสต์
       title: มาตั้งค่าของคุณใน %{domain} กันเลย
     status:
       account_status: สถานะบัญชี
@@ -1183,7 +1146,6 @@ th:
       view_strikes: ดูการดำเนินการที่ผ่านมาต่อบัญชีของคุณ
     too_fast: ส่งแบบฟอร์มเร็วเกินไป ลองอีกครั้ง
     use_security_key: ใช้กุญแจความปลอดภัย
-    user_privacy_agreement_html: ฉันได้อ่านและเห็นด้วยกับ <a href="%{privacy_policy_path}" target="_blank">นโยบายความเป็นส่วนตัว</a>
   author_attribution:
     example_title: ข้อความตัวอย่าง
     hint_html: คุณกำลังเขียนข่าวหรือบทความบล็อกภายนอก Mastodon หรือไม่? ควบคุมวิธีที่คุณได้รับเครดิตเมื่อมีการแบ่งปันข่าวหรือบทความบล็อกใน Mastodon
@@ -1382,31 +1344,19 @@ th:
       overwrite: เขียนทับ
       overwrite_long: แทนที่ระเบียนปัจจุบันด้วยระเบียนใหม่
     overwrite_preambles:
-      blocking_html:
-        other: คุณกำลังจะ <strong>แทนที่รายการการปิดกั้นของคุณ</strong> ด้วยมากถึง <strong>%{count} บัญชี</strong> จาก <strong>%{filename}</strong>
-      bookmarks_html:
-        other: คุณกำลังจะ <strong>แทนที่ที่คั่นหน้าของคุณ</strong> ด้วยมากถึง <strong>%{count} โพสต์</strong> จาก <strong>%{filename}</strong>
-      domain_blocking_html:
-        other: คุณกำลังจะ <strong>แทนที่รายการการปิดกั้นโดเมนของคุณ</strong> ด้วยมากถึง <strong>%{count} โดเมน</strong> จาก <strong>%{filename}</strong>
-      following_html:
-        other: คุณกำลังจะ <strong>ติดตาม</strong> มากถึง <strong>%{count} บัญชี</strong> จาก <strong>%{filename}</strong> และ <strong>หยุดการติดตามคนอื่นใด</strong>
-      lists_html:
-        other: คุณกำลังจะ <strong>แทนที่รายการของคุณ</strong> ด้วยเนื้อหาของ <strong>%{filename}</strong> จะเพิ่มมากถึง <strong>%{count} บัญชี</strong> ไปยังรายการใหม่
-      muting_html:
-        other: คุณกำลังจะ <strong>แทนที่รายการบัญชีที่ซ่อนอยู่ของคุณ</strong> ด้วยมากถึง <strong>%{count} บัญชี</strong> จาก <strong>%{filename}</strong>
+      blocking_html: คุณกำลังจะ <strong>แทนที่รายการการปิดกั้นของคุณ</strong> ด้วยมากถึง <strong>%{total_items} บัญชี</strong> จาก <strong>%{filename}</strong>
+      bookmarks_html: คุณกำลังจะ <strong>แทนที่ที่คั่นหน้าของคุณ</strong> ด้วยมากถึง <strong>%{total_items} โพสต์</strong> จาก <strong>%{filename}</strong>
+      domain_blocking_html: คุณกำลังจะ <strong>แทนที่รายการการปิดกั้นโดเมนของคุณ</strong> ด้วยมากถึง <strong>%{total_items} โดเมน</strong> จาก <strong>%{filename}</strong>
+      following_html: คุณกำลังจะ <strong>ติดตาม</strong> มากถึง <strong>%{total_items} บัญชี</strong> จาก <strong>%{filename}</strong> และ <strong>หยุดการติดตามคนอื่นใด</strong>
+      lists_html: คุณกำลังจะ <strong>แทนที่รายการของคุณ</strong> ด้วยเนื้อหาของ <strong>%{filename}</strong> จะเพิ่มมากถึง <strong>%{total_items} บัญชี</strong> ไปยังรายการใหม่
+      muting_html: คุณกำลังจะ <strong>แทนที่รายการบัญชีที่ซ่อนอยู่ของคุณ</strong> ด้วยมากถึง <strong>%{total_items} บัญชี</strong> จาก <strong>%{filename}</strong>
     preambles:
-      blocking_html:
-        other: คุณกำลังจะ <strong>ปิดกั้น</strong> มากถึง <strong>%{count} บัญชี</strong> จาก <strong>%{filename}</strong>
-      bookmarks_html:
-        other: คุณกำลังจะเพิ่มมากถึง <strong>%{count} โพสต์</strong> จาก <strong>%{filename}</strong> ไปยัง <strong>ที่คั่นหน้า</strong> ของคุณ
-      domain_blocking_html:
-        other: คุณกำลังจะ <strong>ปิดกั้น</strong> มากถึง <strong>%{count} โดเมน</strong> จาก <strong>%{filename}</strong>
-      following_html:
-        other: คุณกำลังจะ <strong>ติดตาม</strong> มากถึง <strong>%{count} บัญชี</strong> จาก <strong>%{filename}</strong>
-      lists_html:
-        other: คุณกำลังจะเพิ่มมากถึง <strong>%{count} บัญชี</strong> จาก <strong>%{filename}</strong> ไปยัง <strong>รายการ</strong> ของคุณ จะสร้างรายการใหม่หากไม่มีรายการที่จะเพิ่มไปยัง
-      muting_html:
-        other: คุณกำลังจะ <strong>ซ่อน</strong> มากถึง <strong>%{count} บัญชี</strong> จาก <strong>%{filename}</strong>
+      blocking_html: คุณกำลังจะ <strong>ปิดกั้น</strong> มากถึง <strong>%{total_items} บัญชี</strong> จาก <strong>%{filename}</strong>
+      bookmarks_html: คุณกำลังจะเพิ่มมากถึง <strong>%{total_items} โพสต์</strong> จาก <strong>%{filename}</strong> ไปยัง <strong>ที่คั่นหน้า</strong> ของคุณ
+      domain_blocking_html: คุณกำลังจะ <strong>ปิดกั้น</strong> มากถึง <strong>%{total_items} โดเมน</strong> จาก <strong>%{filename}</strong>
+      following_html: คุณกำลังจะ <strong>ติดตาม</strong> มากถึง <strong>%{total_items} บัญชี</strong> จาก <strong>%{filename}</strong>
+      lists_html: คุณกำลังจะเพิ่มมากถึง <strong>%{total_items} บัญชี</strong> จาก <strong>%{filename}</strong> ไปยัง <strong>รายการ</strong> ของคุณ จะสร้างรายการใหม่หากไม่มีรายการที่จะเพิ่มไปยัง
+      muting_html: คุณกำลังจะ <strong>ซ่อน</strong> มากถึง <strong>%{total_items} บัญชี</strong> จาก <strong>%{filename}</strong>
     preface: คุณสามารถนำเข้าข้อมูลที่คุณได้ส่งออกจากเซิร์ฟเวอร์อื่น เช่น รายการผู้คนที่คุณกำลังติดตามหรือกำลังปิดกั้น
     recent_imports: การนำเข้าล่าสุด
     states:
@@ -1642,7 +1592,7 @@ th:
     last_active: ใช้งานล่าสุด
     most_recent: ล่าสุด
     moved: ย้ายแล้ว
-    mutual: คนที่มีร่วมกัน
+    mutual: ร่วมกัน
     primary: หลัก
     relationship: ความสัมพันธ์
     remove_selected_domains: เอาผู้ติดตามทั้งหมดออกจากโดเมนที่เลือก
@@ -1662,7 +1612,7 @@ th:
   scheduled_statuses:
     over_daily_limit: คุณมีโพสต์ที่จัดกำหนดการไว้เกินขีดจำกัดที่ %{limit} สำหรับวันนี้แล้ว
     over_total_limit: คุณมีโพสต์ที่จัดกำหนดการไว้เกินขีดจำกัดที่ %{limit} แล้ว
-    too_soon: วันที่ต้องอยู่ในอนาคต
+    too_soon: วันที่จัดกำหนดการต้องอยู่ในอนาคต
   self_destruct:
     lead_html: น่าเสียดาย <strong>%{domain}</strong> กำลังปิดตัวลงอย่างถาวร หากคุณมีบัญชีที่นั่น คุณจะไม่สามารถใช้บัญชีต่อไปได้ แต่คุณยังคงสามารถขอข้อมูลสำรองของข้อมูลของคุณ
     title: เซิร์ฟเวอร์นี้กำลังปิดตัวลง
@@ -1821,8 +1771,6 @@ th:
       too_late: สายเกินไปที่จะอุทธรณ์การดำเนินการนี้
   tags:
     does_not_match_previous_name: ไม่ตรงกับชื่อก่อนหน้านี้
-  terms_of_service:
-    title: เงื่อนไขการให้บริการ
   themes:
     contrast: Mastodon (ความคมชัดสูง)
     default: Mastodon (มืด)
@@ -1883,8 +1831,6 @@ th:
       further_actions_html: หากนี่ไม่ใช่คุณ เราแนะนำให้คุณ %{action} ทันทีและเปิดใช้งานการรับรองความถูกต้องด้วยสองปัจจัยเพื่อรักษาบัญชีของคุณให้ปลอดภัย
       subject: มีการเข้าถึงบัญชีของคุณจากที่อยู่ IP ใหม่
       title: การลงชื่อเข้าใหม่
-    terms_of_service_changed:
-      sign_off: ทีม %{domain}
     warning:
       appeal: ส่งการอุทธรณ์
       appeal_description: หากคุณเชื่อว่านี่เป็นข้อผิดพลาด คุณสามารถส่งการอุทธรณ์ไปยังพนักงานของ %{instance}
diff --git a/config/locales/tok.yml b/config/locales/tok.yml
index 48b5406147..e07e29f60a 100644
--- a/config/locales/tok.yml
+++ b/config/locales/tok.yml
@@ -27,16 +27,20 @@ tok:
     accounts:
       approve: o wile
       are_you_sure: ni li pona ala pona?
-      avatar: sitelen
       delete: o ala e sona
       deleted: jan li ala e ni
       demote: o lili e ken
-      edit: ante toki
-      invite_request_text: nasin kama
-      ip: nanpa IP
-      joined: tenpo kama
       search: o alasa
+    statuses:
+      title: toki suli pi jan ni
     tags:
       search: o alasa
+  imports:
+    overwrite_preambles:
+      blocking_html: "<strong>jan %{total_items}</strong> li lon lipu <strong>%{filename}</strong>. sina wile ala lukin e toki jan la, sina ni la <strong>kulupu pi lukin ala li kama</strong> kulupu lipu."
+      bookmarks_html: "<strong>toki %{total_items}</strong> li lon lipu <strong>%{filename}</strong>. toki li suli tawa sina la, sina ni la <strong>kulupu pi toki suli li kama</strong> kulupu lipu."
+      domain_blocking_html: "<strong>ma %{total_items}</strong> li lon lipu <strong>%{filename}</strong>. sina wile ala lukin e toki tan ma la, sina ni la <strong>kulupu pi lukin ala li kama</strong> kulupu lipu."
+      following_html: "<strong>jan %{total_items}</strong> li lon lipu <strong>%{filename}</strong>. sina wile <strong>kute</strong> e jan la, sina ni la <strong>kulupu kute li kama</strong> kulupu lipu."
+      lists_html: "<strong>jan %{total_items}</strong> li lon lipu <strong>%{filename}</strong>. sina jo e kulupu jan. sina ni la <strong>kulupu sina li weka</strong>, sina kama jo e kulupu lipu."
   privacy:
     search: o alasa
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 751e6ae22f..aa697db37e 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -187,7 +187,6 @@ tr:
         create_domain_block: Engellenen Alan Adı Oluştur
         create_email_domain_block: E-Posta Alan Adı Engeli Oluştur
         create_ip_block: IP kuralı oluştur
-        create_relay: Aktarıcı Oluştur
         create_unavailable_domain: Mevcut Olmayan Alan Adı Oluştur
         create_user_role: Rol Oluştur
         demote_user: Kullanıcıyı Düşür
@@ -199,22 +198,18 @@ tr:
         destroy_email_domain_block: E-posta Alan Adı Engelini Sil
         destroy_instance: Alan adını temizle
         destroy_ip_block: IP kuralını sil
-        destroy_relay: Aktarıcı Sil
         destroy_status: Durumu Sil
         destroy_unavailable_domain: Mevcut Olmayan Alan Adı Sil
         destroy_user_role: Rolü Kaldır
         disable_2fa_user: 2AD Kapat
         disable_custom_emoji: Özel İfadeyi Devre Dışı Bırak
-        disable_relay: Aktarıcıyı Devre Dışı Bırak
         disable_sign_in_token_auth_user: Kullanıcı için E-posta Token Doğrulamayı Devre Dışı Bırak
         disable_user: Kullanıcıyı Devre Dışı Bırak
         enable_custom_emoji: Özel İfadeyi Etkinleştir
-        enable_relay: Aktarıcıyı Etkinleştir
         enable_sign_in_token_auth_user: Kullanıcı için E-posta Token Doğrulamayı Etkinleştir
         enable_user: Kullanıcıyı Etkinleştir
         memorialize_account: Hesabı Anıtlaştır
         promote_user: Kullanıcıyı Yükselt
-        publish_terms_of_service: Hizmet Şartlarını Yayınla
         reject_appeal: İtirazı Reddet
         reject_user: Kullanıcıyı Reddet
         remove_avatar_user: Profil Resmini Kaldır
@@ -252,7 +247,6 @@ tr:
         create_domain_block_html: "%{name}, %{target} alan adını engelledi"
         create_email_domain_block_html: "%{name}, %{target} e-posta alan adını engelledi"
         create_ip_block_html: "%{name}, %{target} IP adresi için kural oluşturdu"
-        create_relay_html: "%{name}, %{target} aktarıcısını oluşturdu"
         create_unavailable_domain_html: "%{name}, %{target} alan adına teslimatı durdurdu"
         create_user_role_html: "%{name}, %{target} rolünü oluşturdu"
         demote_user_html: "%{name}, %{target} kullanıcısını düşürdü"
@@ -264,22 +258,18 @@ tr:
         destroy_email_domain_block_html: "%{name}, %{target} e-posta alan adı engelini kaldırdı"
         destroy_instance_html: "%{name}, %{target} alan adını temizledi"
         destroy_ip_block_html: "%{name}, %{target} IP adresi kuralını sildi"
-        destroy_relay_html: "%{name}, %{target} aktarıcısını sildi"
         destroy_status_html: "%{name}, %{target} kullanıcısının gönderisini kaldırdı"
         destroy_unavailable_domain_html: "%{name}, %{target} alan adına teslimatı sürdürdü"
         destroy_user_role_html: "%{name}, %{target} rolünü sildi"
         disable_2fa_user_html: "%{name}, %{target} kullanıcısının iki aşamalı doğrulama gereksinimini kapattı"
         disable_custom_emoji_html: "%{name}, %{target} emojisini devre dışı bıraktı"
-        disable_relay_html: "%{name}, %{target} aktarıcısını devre dışı bıraktı"
         disable_sign_in_token_auth_user_html: "%{name}, %{target} için e-posta token doğrulamayı devre dışı bıraktı"
         disable_user_html: "%{name}, %{target} kullanıcısı için oturum açmayı devre dışı bıraktı"
         enable_custom_emoji_html: "%{name}, %{target} emojisini etkinleştirdi"
-        enable_relay_html: "%{name}, %{target} aktarıcısını etkinleştirdi"
         enable_sign_in_token_auth_user_html: "%{name}, %{target} için e-posta token doğrulamayı etkinleştirdi"
         enable_user_html: "%{name}, %{target} kullanıcısı için oturum açmayı etkinleştirdi"
         memorialize_account_html: "%{name}, %{target} kullanıcısının hesabını bir anıt sayfaya dönüştürdü"
         promote_user_html: "%{name}, %{target} kullanıcısını yükseltti"
-        publish_terms_of_service_html: "%{name} hizmet şartları güncellemelerini yayınladı"
         reject_appeal_html: "%{name}, %{target} kullanıcısının yönetim kararına itirazını reddetti"
         reject_user_html: "%{name}, %{target} konumundan kaydı reddetti"
         remove_avatar_user_html: "%{name}, %{target} kullanıcısının avatarını kaldırdı"
@@ -309,7 +299,6 @@ tr:
       title: Denetim günlüğü
       unavailable_instance: "(alan adı mevcut değil)"
     announcements:
-      back: Duyurulara geri dön
       destroyed_msg: Duyuru başarıyla silindi!
       edit:
         title: Duyuruyu düzenle
@@ -318,10 +307,6 @@ tr:
       new:
         create: Duyuru oluştur
         title: Yeni duyuru
-      preview:
-        disclaimer: Kullanıcılar bu bildirimleri almayı iptal edemediği için, e-posta bildirimleri kişisel veri ihlali veya sunucu kapatma bildirimleri gibi önemli duyurularla sınırlandırılmalıdır.
-        explanation_html: 'E-posta, <strong>%{display_count} kullanıcıya</strong> gönderilecektir. E-posta içerisinde aşağıdaki metin yer alacaktır:'
-        title: Duyuru bildiriminin önizlemesine bak
       publish: Yayınla
       published_msg: Duyuru başarıyla yayınlandı!
       scheduled_for: "%{time} için zamanlandı"
@@ -480,36 +465,6 @@ tr:
       new:
         title: Domain bloklarını içe aktar
       no_file: Dosya seçilmedi
-    fasp:
-      debug:
-        callbacks:
-          created_at: Oluşturulma tarihi
-          delete: Sil
-          ip: IP adresi
-          request_body: İstek gövdesi
-          title: Hata ayıklama geri çağrıları
-      providers:
-        active: Etkin
-        base_url: Temel URL
-        callback: Geri çağırma
-        delete: Sil
-        edit: Sağlayıcıyı Düzenle
-        finish_registration: Kaydı tamamla
-        name: Ad
-        providers: Sağlayıcılar
-        public_key_fingerprint: Açık anahtar parmak izi
-        registration_requested: Kayıt istendi
-        registrations:
-          confirm: Onayla
-          description: FASP'tan bir kayıt aldınız. Eğer bunu siz başlatmadıysanız reddedin. Eğer siz başlattıysanız, kaydı onaylamadan önce ad ve anahtar parmak izini dikkatlice karşılaştırın.
-          reject: Reddet
-          title: FASP Kaydını Onayla
-        save: Kaydet
-        select_capabilities: Yetenekleri Seç
-        sign_in: Oturum Aç
-        status: Durum
-        title: Fediverse Yardımcı Hizmet Sağlayıcıları (FASP)
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Takip önerileri yeni kullanıcıların hızlı bir şekilde ilginç içerik bulmalarını sağlar</strong>. Eğer bir kullanıcı, kişisel takip önerileri almaya yetecek kadar başkalarıyla etkileşime girmediğinde, onun yerine bu hesaplar önerilir. Bu öneriler, verili bir dil için en yüksek takipçi sayısına ve en yüksek güncel meşguliyete sahip hesapların bir karışımdan günlük olarak hesaplanıyorlar."
       language: Dil için
@@ -863,10 +818,8 @@ tr:
       back_to_account: Hesap sayfasına geri dön
       back_to_report: Bildirim sayfasına geri dön
       batch:
-        add_to_report: "#%{id} raporuna ekle"
         remove_from_report: Bildirimden kaldır
         report: Bildirim
-      contents: İçerikler
       deleted: Silindi
       favourites: Favoriler
       history: Sürüm geçmişi
@@ -875,17 +828,13 @@ tr:
       media:
         title: Medya
       metadata: Üstveri
-      no_history: Bu gönderi düzenlenmemiş
       no_status_selected: Hiçbiri seçilmediğinden hiçbir durum değiştirilmedi
       open: Gönderiyi aç
       original_status: Özgün gönderi
       reblogs: Yeniden Paylaşımlar
-      replied_to_html: Yanıtladı %{acct_link}
       status_changed: Gönderi değişti
-      status_title: "@%{name} gönderisi"
-      title: Hesap gönderileri - @%{name}
+      title: Hesap durumları
       trending: Öne çıkanlar
-      view_publicly: Herkese açık görüntüle
       visibility: Görünürlük
       with_media: Medya ile
     strikes:
@@ -962,36 +911,6 @@ tr:
       search: Ara
       title: Etiketler
       updated_msg: Etiket ayarları başarıyla güncellendi
-    terms_of_service:
-      back: Hizmet şartlarına dön
-      changelog: Ne Değişti
-      create: Kendinizinkini kullanın
-      current: Şimdiki
-      draft: Taslak
-      generate: Şablon kullan
-      generates:
-        action: Oluştur
-        chance_to_review_html: "<strong>Üretilmiş hizmet şartları otomatik olarak yayınlanmayacaktır.</strong> Sonucu gözden geçirme şansınız olacaktır. Devam etmek için lütfen gerekli ayrıntıları doldurun."
-        explanation_html: Sağlanan hizmet şartları şablonu yalnızca bilgilendirme amaçlıdır ve herhangi bir konuda yasal tavsiye olarak yorumlanmamalıdır. Lütfen durumunuz ve belirli yasal sorularınız hakkında kendi hukuk müşavirinize danışın.
-        title: Hizmet Şartları Kurulumu
-      going_live_on_html: Canlı, %{date} tarihinden beri yürürlükte
-      history: Tarihçe
-      live: Canlı
-      no_history: Henüz kaydedilmiş hizmet şartları değişikliği yok.
-      no_terms_of_service_html: Şu anda yapılandırılmış herhangi bir hizmet şartınız yok. Hizmet şartları, kullanıcılarınızla olan anlaşmazlıklarda netlik sağlamak ve sizi olası yükümlülüklerden korumak içindir.
-      notified_on_html: Kullanıcılar %{date} tarihinde bilgilendirildi
-      notify_users: Kullanıcılara bildir
-      preview:
-        explanation_html: 'E-posta, %{date} tarihinden önce kaydolan <strong>%{display_count} kullanıcıya</strong> gönderilecektir. E-posta içerisinde aşağıdaki metin yer alacaktır:'
-        send_preview: Ön izlemeyi %{email} adresine gönder
-        send_to_all:
-          one: "%{display_count} e-posta gönder"
-          other: "%{display_count} e-posta gönder"
-        title: Hizmet şartları bildirimini öz izle
-      publish: Yayınla
-      published_on_html: "%{date} tarihinde yayınlandı"
-      save_draft: Taslağı kaydet
-      title: Hizmet Şartları
     title: Yönetim
     trends:
       allow: İzin ver
@@ -1199,6 +1118,7 @@ tr:
     migrate_account: Farklı bir hesaba taşıyın
     migrate_account_html: Bu hesabı başka bir hesaba yönlendirmek istiyorsan, <a href="%{path}">buradan yapılandırabilirsin</a>.
     or_log_in_with: 'Veya şununla oturum açın:'
+    privacy_policy_agreement_html: <a href="%{privacy_policy_path}" target="_blank">Gizlilik politikasını</a> okudum ve kabul ettim
     progress:
       confirm: E-postanızı onaylayın
       details: Ayrıntılarınız
@@ -1223,7 +1143,7 @@ tr:
     set_new_password: Yeni parola belirle
     setup:
       email_below_hint_html: İstenmeyenler dizininize bakın veya başka bir onay bağlantısı isteyin. Eğer yanlışsa e-posta adresinizi de düzeltebilirsiniz.
-      email_settings_hint_html: Mastodon kullanmaya başlamak %{email} adresine gönderdiğimiz bağlantıya tıklayın. Sizi burada bekliyoruz.
+      email_settings_hint_html: "%{email} adresinizi doğrulamak için size gönderdiğimiz bağlantıya tıklayın. Biz burada bekliyoruz."
       link_not_received: Bağlantı gelmedi mi?
       new_confirmation_instructions_sent: Birkaç dakika içerisinde onaylama bağlantısını içeren yeni bir e-posta alacaksınız!
       title: Gelen kutunuzu kontrol edin
@@ -1232,7 +1152,7 @@ tr:
       title: "%{domain} giriş yapın"
     sign_up:
       manual_review: "%{domain} kayıtları moderatörler tarafından manuel olarak inceleniyor. Kaydınızı işlememizi kolaylaştırmak için kendiniz ve %{domain} sunucusundan neden hesap istediğiniz hakkında biraz bilgi verin."
-      preamble: Bu Mastodon sunucusu üzerinden bir hesap ile fediverseteki herhangi bir kişiyi, hangi sunucuda olursa olsun, takip edebilirsiniz.
+      preamble: Bu Mastodon sunucusu üzerinden bir hesap ile ağdaki herhangi bir kişiyi, hesabı hangi sunucuda saklanırsa saklansın, takip edebilirsiniz.
       title: "%{domain} için kurulumunuzu yapalım."
     status:
       account_status: Hesap durumu
@@ -1244,8 +1164,6 @@ tr:
       view_strikes: Hesabınıza yönelik eski eylemleri görüntüleyin
     too_fast: Form çok hızlı gönderildi, tekrar deneyin.
     use_security_key: Güvenlik anahtarını kullan
-    user_agreement_html: <a href="%{terms_of_service_path}" target="_blank">hizmet şartları</a> ve <a href="%{privacy_policy_path}" target="_blank">gizlilik politikasını</a> okudum ve kabul ediyorum
-    user_privacy_agreement_html: <a href="%{privacy_policy_path}" target="_blank">Gizlilik politikasını</a> okudum ve kabul ettim
   author_attribution:
     example_title: Örnek metin
     hint_html: Mastodon dışında haber veya günlük yazıları mı yazıyorsunuz? Bu yazılar Mastodon'da paylaşıldığında size nasıl atıf yapılacağını denetleyin.
@@ -1451,43 +1369,19 @@ tr:
       overwrite: Üzerine yaz
       overwrite_long: Mevcut kayıtları yenileriyle değiştir
     overwrite_preambles:
-      blocking_html:
-        one: "<strong>Engelleme listenizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} hesap</strong> ile değiştirmek üzeresiniz."
-        other: "<strong>Engelleme listenizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} hesap</strong> ile değiştirmek üzeresiniz."
-      bookmarks_html:
-        one: "<strong>Yerimlerinizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} gönderi</strong> ile değiştirmek üzeresiniz."
-        other: "<strong>Yerimlerinizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} gönderi</strong> ile değiştirmek üzeresiniz."
-      domain_blocking_html:
-        one: "<strong>Alan adı engelleme listenizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} alan adı</strong> ile değiştirmek üzeresiniz."
-        other: "<strong>Alan adı engelleme listenizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} alan adı</strong> ile değiştirmek üzeresiniz."
-      following_html:
-        one: "<strong>%{filename}</strong> dosyasından <strong>%{count} hesabı</strong> <strong>takip etmek</strong> ve <strong>diğer herkesin takibini bırakmak</strong> üzeresiniz."
-        other: "<strong>%{filename}</strong> dosyasından <strong>%{count} hesabı</strong> <strong>takip etmek</strong> ve <strong>diğer herkesin takibini bırakmak</strong> üzeresiniz."
-      lists_html:
-        one: "<strong>Listelerinizi</strong>, <strong>%{filename}</strong> dosya içeriğiyle değiştirmek üzeresiniz. <strong>%{count} hesap</strong> yeni listelere eklenecektir."
-        other: "<strong>Listelerinizi</strong>, <strong>%{filename}</strong> dosya içeriğiyle değiştirmek üzeresiniz. <strong>%{count} hesap</strong> yeni listelere eklenecektir."
-      muting_html:
-        one: "<strong>Sessize alınmış hesap listenizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} hesap</strong> ile değiştirmek üzeresiniz."
-        other: "<strong>Sessize alınmış hesap listenizi</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} hesap</strong> ile değiştirmek üzeresiniz."
+      blocking_html: "<strong>Engel listenizi</strong>, <strong>%{filename}</strong> dosyasından, <strong>%{total_items} hesapla</strong> değiştirmek üzeresiniz."
+      bookmarks_html: "<strong>Yerimlerinizi</strong>, <strong>%{filename}</strong> dosyasından, <strong>%{total_items} gönderiyle</strong> değiştirmek üzeresiniz."
+      domain_blocking_html: "<strong>Alan adı engel listenizi</strong>, <strong>%{filename}</strong> dosyasından, <strong>%{total_items} alan adıyla</strong> değiştirmek üzeresiniz."
+      following_html: "<strong>%{filename}</strong> dosyasından <strong>%{total_items} hesabı</strong> <strong>takip etmeye</strong> başlamak ve <strong>diğer herkesi takipten çıkmak</strong> üzeresiniz."
+      lists_html: Listelerinizi <strong>%{filename}</strong> içeriğiyle <strong>değiştirmek</strong> üzeresiniz. Yeni listelere en fazla <strong>%{total_items} hesap</strong> eklenecek.
+      muting_html: "<strong>Sessize alınmış hesaplar listenizi</strong>, <strong>%{filename}</strong> dosyasından, <strong>%{total_items} hesapla</strong> değiştirmek üzeresiniz."
     preambles:
-      blocking_html:
-        one: "<strong>%{filename}</strong> dosyasından <strong>%{count} hesap</strong> <strong>engellemek</strong> üzeresiniz."
-        other: "<strong>%{filename}</strong> dosyasından <strong>%{count} hesap</strong> <strong>engellemek</strong> üzeresiniz."
-      bookmarks_html:
-        one: "<strong>Yerimlerinize</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} gönderi</strong> eklemek üzeresiniz."
-        other: "<strong>Yerimlerinize</strong>, <strong>%{filename}</strong> dosyasından <strong>%{count} gönderi</strong> eklemek üzeresiniz."
-      domain_blocking_html:
-        one: "<strong>%{filename}</strong> dosyasından <strong>%{count} alan adı</strong> <strong>engellemek</strong> üzeresiniz."
-        other: "<strong>%{filename}</strong> dosyasından <strong>%{count} alan adı</strong> <strong>engellemek</strong> üzeresiniz."
-      following_html:
-        one: "<strong>%{filename}</strong> dosyasından <strong>%{count} hesap</strong> <strong>takip etmek</strong> üzeresiniz."
-        other: "<strong>%{filename}</strong> dosyasından <strong>%{count} hesap</strong> <strong>takip etmek</strong> üzeresiniz."
-      lists_html:
-        one: "<strong>Listelerinize</strong>, <strong>%{filename}</strong> <strong>%{count} hesap</strong> eklemek üzeresiniz. Eklenecek liste yoksa yeni listeler oluşturulacaktır."
-        other: "<strong>Listelerinize</strong>, <strong>%{filename}</strong> <strong>%{count} hesap</strong> eklemek üzeresiniz. Eklenecek liste yoksa yeni listeler oluşturulacaktır."
-      muting_html:
-        one: "<strong>%{filename}</strong> dosyasından <strong>%{count} hesabı</strong> <strong>sessize almak</strong> üzeresiniz."
-        other: "<strong>%{filename}</strong> dosyasından <strong>%{count} hesabı</strong> <strong>sessize almak</strong> üzeresiniz."
+      blocking_html: "<strong>%{filename}</strong> dosyasından <strong>%{total_items} hesabı</strong> <strong>engellemek</strong> üzeresiniz."
+      bookmarks_html: "<strong>%{filename}</strong> dosyasından <strong>%{total_items} gönderiyi</strong> <strong>yerimlerinize</strong> eklemek üzeresiniz."
+      domain_blocking_html: "<strong>%{filename}</strong> dosyasından <strong>%{total_items} alan adını</strong> <strong>engellemek</strong> üzeresiniz."
+      following_html: "<strong>%{filename}</strong> dosyasından <strong>%{total_items} hesabı</strong> <strong>takip etmek</strong> üzeresiniz."
+      lists_html: "<strong>Listelerinize</strong> <strong>%{filename}</strong> dosyasından en fazla <strong>%{total_items} hesap</strong> eklemek üzeresiniz. Eklenecek bir liste olmaması durumunda yeni listeler oluşturulacaktır."
+      muting_html: "<strong>%{filename}</strong> dosyasından <strong>%{total_items} hesabı</strong> <strong>sessize almak</strong> üzeresiniz."
     preface: Diğer sunucudan alarak oluşturduğunuz dosyalar sayesinde, bu sunucudaki hesabınıza takipçilerinizi aktarabilir veya istemediğiniz kişileri otomatik olarak engelleyebilirsiniz.
     recent_imports: Son içe aktarmalar
     states:
@@ -1744,7 +1638,7 @@ tr:
   scheduled_statuses:
     over_daily_limit: Bugün için %{limit} zamanlanmış gönderi sınırını aştınız
     over_total_limit: "%{limit} zamanlanmış gönderi sınırını aştınız"
-    too_soon: tarih ileri bir tarih olmalıdır
+    too_soon: Programlanan tarih bugünden ileri bir tarihte olmalıdır
   self_destruct:
     lead_html: Maalesef <strong>%{domain}</strong> kalıcı olarak kapanıyor. Eğer orada hesabınız varsa, onu kullanmaya devam edemeyeceksiniz, ancak yine de verinizin bir yedeğini isteyebilirsiniz.
     title: Bu sunucu kapanıyor
@@ -1907,8 +1801,6 @@ tr:
       too_late: Bu eyleme itiraz etmek için çok geç
   tags:
     does_not_match_previous_name: önceki adla eşleşmiyor
-  terms_of_service:
-    title: Hizmet Şartları
   themes:
     contrast: Mastodon (Yüksek karşıtlık)
     default: Mastodon (Karanlık)
@@ -1940,10 +1832,6 @@ tr:
     recovery_instructions_html: 'Eğer telefonunuza erişiminizi kaybederseniz, aşağıdaki kurtarma kodlarından birini kullanarak hesabınıza giriş yapabilirsiniz. <strong>Kurtarma kodlarınızı güvenli halde tutunuz.</strong> Örneğin: kodların çıktısını alıp diğer önemli belgeleriniz ile birlikte saklayabilirsiniz.'
     webauthn: Güvenlik anahtarları
   user_mailer:
-    announcement_published:
-      description: "%{domain} yöneticileri bir duyuru yapıyorlar:"
-      subject: Hizmet duyurusu
-      title: "%{domain} hizmet duyurusu"
     appeal_approved:
       action: Hesap Ayarları
       explanation: "%{appeal_date} tarihinde gönderdiğiniz, hesabınıza yönelik %{strike_date} tarihli eyleme itirazınız onaylandı. Hesabınız artık tekrar iyi durumda."
@@ -1973,15 +1861,6 @@ tr:
       further_actions_html: Eğer oturum açan siz değildiyseniz, hesabınızı güvenli tutmanız için hemen %{action} yapmanızı ve iki aşamalı yetkilendirmeyi etkinleştirmenizi öneriyoruz.
       subject: Hesabınıza yeni bir IP adresinden erişim oldu
       title: Yeni bir oturum açma
-    terms_of_service_changed:
-      agreement: "%{domain} sunucusunu kullanmaya devam ederek bu şartları kabul etmiş olursunuz. Güncellenen şartları kabul etmiyorsanız, %{domain} ile olan sözleşmenizi istediğiniz zaman hesabınızı silerek feshedebilirsiniz."
-      changelog: 'Bir bakışta, bu güncellemenin sizin için anlamı şudur:'
-      description: 'Bu e-postayı alıyorsunuz çünkü %{domain} adresindeki hizmet şartlarımızda bazı değişiklikler yapıyoruz. Bu değişiklikler %{date} tarihinde geçerli olacak. Aşağıda tümü yer alan güncellenen şartları incelemenizi öneriyoruz:'
-      description_html: Bu e-postayı alıyorsunuz çünkü %{domain} adresindeki hizmet şartlarımızda bazı değişiklikler yapıyoruz. Bu değişiklikler <strong>%{date}</strong> tarihinde geçerli olacak. <a href="%{path}" target="_blank">Tüm güncellenen şartları</a> incelemenizi öneriyoruz.
-      sign_off: "%{domain} Ekibi"
-      subject: Şimdiki hizmet şartlarımıza güncellemeler
-      subtitle: "%{domain} adresindeki hizmet şartları değişiyor"
-      title: Önemli güncelleme
     warning:
       appeal: Bir itiraz gönder
       appeal_description: Bunun bir hata olduğunu düşünüyorsanız, %{instance} sunucusunun personeline bir itiraz gönderebilirsiniz.
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index 0256773d32..f941bfac79 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -193,7 +193,6 @@ uk:
         create_domain_block: Створити блокування домену
         create_email_domain_block: Створити блокування домену е-пошти
         create_ip_block: Створити правило IP
-        create_relay: Створити реле
         create_unavailable_domain: Створити недоступний домен
         create_user_role: Створити роль
         demote_user: Понизити користувача
@@ -205,22 +204,18 @@ uk:
         destroy_email_domain_block: Видалити блокування домену е-пошти
         destroy_instance: Очистити домен
         destroy_ip_block: Видалити правило IP
-        destroy_relay: Видалити реле
         destroy_status: Видалити допис
         destroy_unavailable_domain: Видалити недоступний домен
         destroy_user_role: Знищити роль
         disable_2fa_user: Вимкнути 2FA
         disable_custom_emoji: Вимкнути користувацькі емодзі
-        disable_relay: Вимкнути реле
         disable_sign_in_token_auth_user: Вимкнути автентифікацію за допомогою токена е-пошти для користувача
         disable_user: Відключити користувача
         enable_custom_emoji: Увімкнути користувацькі емодзі
-        enable_relay: Увімкнути реле
         enable_sign_in_token_auth_user: Увімкнути автентифікацію за допомогою токена е-пошти для користувача
         enable_user: Активувати користувача
         memorialize_account: Меморіалізувати акаунт
         promote_user: Підвищити користувача
-        publish_terms_of_service: Опублікувати Умови використання
         reject_appeal: Відхилити апеляцію
         reject_user: Відхилити користувача
         remove_avatar_user: Видалити аватар
@@ -258,7 +253,6 @@ uk:
         create_domain_block_html: "%{name} блокує домен %{target}"
         create_email_domain_block_html: "%{name} блокує домен електронної пошти %{target}"
         create_ip_block_html: "%{name} створює правило для IP %{target}"
-        create_relay_html: "%{name} створив реле %{target}"
         create_unavailable_domain_html: "%{name} зупиняє доставляння на домен %{target}"
         create_user_role_html: "%{name} створює роль %{target}"
         demote_user_html: "%{name} понижує користувача %{target}"
@@ -270,22 +264,18 @@ uk:
         destroy_email_domain_block_html: "%{name} розблоковує домен електронної пошти %{target}"
         destroy_instance_html: "%{name} очищує домен %{target}"
         destroy_ip_block_html: "%{name} видаляє правило для IP %{target}"
-        destroy_relay_html: "%{name} видалив реле %{target}"
         destroy_status_html: "%{name} вилучає допис %{target}"
         destroy_unavailable_domain_html: "%{name} відновлює доставляння на домен %{target}"
         destroy_user_role_html: "%{name} видаляє роль %{target}"
         disable_2fa_user_html: "%{name} вимикає двоетапну перевірку для користувача %{target}"
         disable_custom_emoji_html: "%{name} вимикає емодзі %{target}"
-        disable_relay_html: "%{name} вимкнув реле %{target}"
         disable_sign_in_token_auth_user_html: "%{name} вимикає автентифікацію через токен е-пошти для %{target}"
         disable_user_html: "%{name} вимикає вхід для користувача %{target}"
         enable_custom_emoji_html: "%{name} вмикає емодзі %{target}"
-        enable_relay_html: "%{name} увімкнув реле %{target}"
         enable_sign_in_token_auth_user_html: "%{name} вмикає автентифікацію через токен е-пошти для %{target}"
         enable_user_html: "%{name} вмикає вхід для користувача %{target}"
         memorialize_account_html: "%{name} перетворює обліковий запис %{target} на сторінку пам'яті"
         promote_user_html: "%{name} підвищує користувача %{target}"
-        publish_terms_of_service_html: "%{name} опублікував оновлення умов використання"
         reject_appeal_html: "%{name} відхилили звернення на оскарження рішення від %{target}"
         reject_user_html: "%{name} відхиляє реєстрацію від %{target}"
         remove_avatar_user_html: "%{name} прибирає аватар %{target}"
@@ -315,7 +305,6 @@ uk:
       title: Журнал подій
       unavailable_instance: "(ім'я домену недоступне)"
     announcements:
-      back: Назад до анонсів
       destroyed_msg: Оголошення успішно видалено!
       edit:
         title: Редагувати оголошення
@@ -324,10 +313,6 @@ uk:
       new:
         create: Створити оголошення
         title: Нове оголошення
-      preview:
-        disclaimer: Оскільки користувачі не можуть відмовитися від них, сповіщення по електронній пошті повинні обмежуватися важливими оголошеннями, такими як порушення особистих даних або повідомлення про закриття серверу.
-        explanation_html: 'Електронний лист буде надіслано <strong>%{display_count} користувачам</strong>. До електронного листа буде включено такий текст:'
-        title: Попередній перегляд сповіщення
       publish: Опублікувати
       published_msg: Оголошення успішно опубліковано!
       scheduled_for: Заплановано на %{time}
@@ -861,10 +846,8 @@ uk:
       back_to_account: Назад до сторінки облікового запису
       back_to_report: Повернутися до сторінки скарги
       batch:
-        add_to_report: 'Додати до звіту #%{id}'
         remove_from_report: Вилучити зі скарги
         report: Скарга
-      contents: Вміст
       deleted: Видалено
       favourites: Вподобане
       history: Історія версій
@@ -873,17 +856,13 @@ uk:
       media:
         title: Медіа
       metadata: Метадані
-      no_history: Цей допис ще не редагували
       no_status_selected: Жодного допису не було змінено, оскільки жодного з них не було вибрано
       open: Відкрити допис
       original_status: Оригінальний допис
       reblogs: Поширення
-      replied_to_html: Відповів %{acct_link}
       status_changed: Допис змінено
-      status_title: "@%{name} опублікував допис"
-      title: Дописи облікового запису - @%{name}
+      title: Дописи облікових записів
       trending: Популярне
-      view_publicly: Переглянути привселюдно
       visibility: Видимість
       with_media: З медіа
     strikes:
@@ -960,32 +939,6 @@ uk:
       search: Пошук
       title: Гештеґи
       updated_msg: Параметри хештеґів успішно оновлені
-    terms_of_service:
-      back: Назад до умов використання
-      changelog: Що змінилося
-      create: Використовувати власний
-      current: Поточний
-      draft: Чернетка
-      generate: Використовувати шаблон
-      generates:
-        action: Згенерувати
-        chance_to_review_html: "<strong>Створені умови використання не опублікуються автоматично.</strong> Ви матимете змогу переглянути результати. Будь ласка, заповніть потрібні дані, щоб перейти далі."
-        explanation_html: Наданий шаблон умов надання послуг призначений лише для інформаційних цілей і не має розглядатися як юридична консультація з будь-якого питання. Проконсультуйтеся зі своїм юридичним радником щодо вашої ситуації та конкретних юридичних питань, які у вас постали.
-        title: Налаштування Умов обслуговування
-      history: Історія
-      live: Наживо
-      no_history: Змін умов обслуговування поки що не зафіксовано.
-      no_terms_of_service_html: Наразі у вас не налаштовано умов використання. Умови використання призначені для забезпечення ясності та захисту від потенційних зобов’язань у суперечках із користувачами.
-      notified_on_html: Користувачі сповіщені %{date}
-      notify_users: Сповістити користувачів
-      preview:
-        explanation_html: 'Електронний лист буде надіслано <strong>%{display_count} користувачам</strong>, які зареєструвалися до %{date}. Розміщений нижче текст додасться до електронного листа:'
-        send_preview: Надіслати попередній перегляд на %{email}
-        title: Попередній перегляд сповіщення про умови використання
-      publish: Опублікувати
-      published_on_html: Опубліковано %{date}
-      save_draft: Зберегти чернетку
-      title: Умови використання
     title: Адміністрування
     trends:
       allow: Дозволити
@@ -1201,6 +1154,7 @@ uk:
     migrate_account: Переїхати на інший обліковий запис
     migrate_account_html: Якщо ви бажаєте переспрямувати цей обліковий запис на інший, ви можете <a href="%{path}">налаштувати це тут</a>.
     or_log_in_with: Або увійдіть з
+    privacy_policy_agreement_html: Мною прочитано і я погоджуюся з <a href="%{privacy_policy_path}" target="_blank">політикою приватності</a>
     progress:
       confirm: Підтвердити електронну адресу
       details: Ваші дані
@@ -1225,7 +1179,7 @@ uk:
     set_new_password: Встановити новий пароль
     setup:
       email_below_hint_html: Перевірте теку "Спам", або зробіть ще один запит. Ви можете виправити свою електронну адресу, якщо вона неправильна.
-      email_settings_hint_html: Натисніть посилання, надіслане на %{email}, щоб почати користуватися Mastodon. Ми чекатимемо тут.
+      email_settings_hint_html: Натисніть на посилання, яке ми надіслали вам, щоб підтвердити %{email}. Ми чекатимемо прямо тут.
       link_not_received: Не отримали посилання?
       new_confirmation_instructions_sent: Ви отримаєте новий лист із посиланням для підтвердження протягом кількох хвилин!
       title: Перевірте вашу поштову скриньку
@@ -1234,7 +1188,7 @@ uk:
       title: Увійти до %{domain}
     sign_up:
       manual_review: Реєстрація на %{domain} проходить через ручний розгляд нашими модераторами. Щоб допомогти нам завершити вашу реєстрацію, напишіть трохи про себе і чому ви хочете зареєструватися на %{domain}.
-      preamble: За допомогою облікового запису на цьому сервері Mastodon ви зможете стежити за будь-якою іншою людиною у федіверсі незалежно від того, де розміщений обліковий запис.
+      preamble: За допомогою облікового запису на цьому сервері Mastodon, ви зможете слідкувати за будь-якою іншою людиною в мережі, не зважаючи на те, де розміщений обліковий запис.
       title: Налаштуймо вас на %{domain}.
     status:
       account_status: Стан облікового запису
@@ -1246,8 +1200,6 @@ uk:
       view_strikes: Переглянути попередні попередження вашому обліковому запису
     too_fast: Форму подано занадто швидко, спробуйте ще раз.
     use_security_key: Використовувати ключ безпеки
-    user_agreement_html: Я прочитав і приймаю <a href="%{terms_of_service_path}" target="_blank">умови використання</a> та <a href="%{privacy_policy_path}" target="_blank">політику конфіденційності< /a>
-    user_privacy_agreement_html: Мною прочитано і я погоджуюся з <a href="%{privacy_policy_path}" target="_blank">політикою приватності</a>
   author_attribution:
     example_title: Зразок тексту
     hint_html: Ви пишете новини чи статті в блозі за межами Mastodon? Контролюйте, як вони підписуються, коли ними діляться на Mastodon.
@@ -1466,6 +1418,20 @@ uk:
       merge_long: Зберегти наявні записи та додати нові
       overwrite: Перезаписувати
       overwrite_long: Замінити наявні записи новими
+    overwrite_preambles:
+      blocking_html: Ви збираєтеся <strong>замінити ваш список блокування</strong> з <strong>%{total_items} обліковими записами</strong> з <strong>%{filename}</strong>.
+      bookmarks_html: Ви збираєтеся <strong>замінити ваші закладки</strong> з <strong>%{total_items} дописами</strong> з <strong>%{filename}</strong>.
+      domain_blocking_html: Ви збираєтеся <strong>замінити ваш список блокування доменів</strong> з <strong>%{total_items} доменами</strong> з <strong>%{filename}</strong>.
+      following_html: Ви збираєтеся <strong>слідкувати</strong> за <strong>%{total_items} обліковими записами</strong> з <strong>%{filename}</strong> і <strong>перестати слідкувати за всіма іншими</strong>.
+      lists_html: Ви збираєтеся <strong>замінити ваші списки</strong> вмістом з <strong>%{filename}</strong>. До нових списків буде додано до <strong>%{total_items} облікових записів</strong>.
+      muting_html: Ви збираєтеся <strong>замінити ваш список ігнорованих облікових записів</strong> з <strong>%{total_items} обліковими записами</strong> з <strong>%{filename}</strong>.
+    preambles:
+      blocking_html: Ви збираєтеся <strong>заблокувати</strong> <strong>%{total_items} облікових записів</strong> з <strong>%{filename}</strong>.
+      bookmarks_html: Ви збираєтеся додати <strong>%{total_items} дописів</strong> з <strong>%{filename}</strong> до ваших <strong>закладок</strong>.
+      domain_blocking_html: Ви збираєтеся <strong>заблокувати</strong> <strong>%{total_items} доменів</strong> з <strong>%{filename}</strong>.
+      following_html: Ви збираєтеся <strong>слідкувати</strong> за <strong>%{total_items} обліковими записами</strong> з <strong>%{filename}</strong>.
+      lists_html: Ви збираєтеся додати до <strong>%{total_items} облікових записів</strong> з <strong>%{filename}</strong> до ваших <strong>списків</strong>. Нові списки будуть створені, якщо немає списку для додавання.
+      muting_html: Ви збираєтеся <strong>ігнорувати</strong> <strong>%{total_items} облікових записів</strong> з <strong>%{filename}</strong>.
     preface: Ви можете імпортувати дані, які ви експортували з іншого сервера, наприклад, список людей, яких ви відстежуєте або блокуєте.
     recent_imports: Останні імпорти
     states:
@@ -1724,7 +1690,7 @@ uk:
   scheduled_statuses:
     over_daily_limit: Ви перевищили ліміт в %{limit} запланованих дописів на сьогодні
     over_total_limit: Ви перевищили ліміт в %{limit} запланованих дописів
-    too_soon: дата повинна бути в майбутньому
+    too_soon: Запланована дата має бути в майбутньому
   self_destruct:
     lead_html: На жаль, <strong>%{domain}</strong> остаточно закривається. Якщо у вас є обліковий запис там, ви не зможете продовжити його використання, але ви все ще можете надіслати запит на резервну копію даних.
     title: Сервер закривається
@@ -1895,8 +1861,6 @@ uk:
       too_late: Запізно оскаржувати це попередження
   tags:
     does_not_match_previous_name: не збігається з попереднім ім'ям
-  terms_of_service:
-    title: Умови використання
   themes:
     contrast: Mastodon (Висока контрастність)
     default: Mastodon (Темна)
@@ -1928,10 +1892,6 @@ uk:
     recovery_instructions_html: У випадку втрати доступу до вашого телефону ви можете використати один з нижчевказаних кодів відновлення, щоб повернути доступ до вашого облікового запису. <strong>Тримайте коди відновлення у безпеці</strong>, наприклад, роздрукуйте їх та зберігайте разом з іншими важливими документами.
     webauthn: Ключі безпеки
   user_mailer:
-    announcement_published:
-      description: 'Адміністратори %{domain} роблять оголошення:'
-      subject: Службове оголошення
-      title: Службове оголошення сервісу %{domain}
     appeal_approved:
       action: Налаштування облікового запису
       explanation: Оскарження попередження вашому обліковому запису %{strike_date}, яке ви надіслали %{appeal_date} було схвалено. Ваш обліковий запис знову вважається добропорядним.
@@ -1961,13 +1921,6 @@ uk:
       further_actions_html: Якщо це були не ви. Радимо вам негайно %{action} й увімкнути двоетапну перевірку, щоб уберегти свій обліковий запис.
       subject: До вашого облікового запису отримано доступ з нової IP-адреси
       title: Новий вхід
-    terms_of_service_changed:
-      agreement: Далі використовуючи %{domain}, ви погоджуєтеся з цими умовами. Якщо ви не згодні з оновленими умовами, ви можете припинити свою угоду з %{domain} будь-якої миті, видаливши ваш обліковий запис.
-      changelog: 'Коротко, ось що це оновлення означає для вас:'
-      sign_off: Команда %{domain}
-      subject: Оновлення до наших умов обслуговування
-      subtitle: Умови використання %{domain} змінюються
-      title: Важливе оновлення
     warning:
       appeal: Подати апеляцію
       appeal_description: Якщо ви вважаєте, що це помилка, ви можете надіслати оскаржити дії персоналу %{instance}.
diff --git a/config/locales/vi.yml b/config/locales/vi.yml
index a255973a10..560d1282b4 100644
--- a/config/locales/vi.yml
+++ b/config/locales/vi.yml
@@ -184,7 +184,6 @@ vi:
         create_domain_block: Chặn máy chủ
         create_email_domain_block: Chặn tên miền email
         create_ip_block: Chặn IP
-        create_relay: Tạo relay
         create_unavailable_domain: Bỏ liên hợp
         create_user_role: Tạo vai trò
         demote_user: Hạ vai trò
@@ -196,22 +195,18 @@ vi:
         destroy_email_domain_block: Bỏ chặn tên miền email
         destroy_instance: Thanh trừng máy chủ
         destroy_ip_block: Bỏ chặn IP
-        destroy_relay: Xóa relay
         destroy_status: Xóa tút
         destroy_unavailable_domain: Cho phép liên hợp
         destroy_user_role: Xóa vai trò
         disable_2fa_user: Vô hiệu hóa 2FA
         disable_custom_emoji: Vô hiệu hóa emoji
-        disable_relay: Tắt relay
         disable_sign_in_token_auth_user: Tắt xác minh bằng email cho người dùng
         disable_user: Vô hiệu hóa đăng nhập
         enable_custom_emoji: Cho phép emoji
-        enable_relay: Bật relay
         enable_sign_in_token_auth_user: Bật xác minh bằng email cho người dùng
         enable_user: Cho phép đăng nhập
         memorialize_account: Gán tưởng niệm
         promote_user: Nâng vai trò
-        publish_terms_of_service: Đăng Điều khoản Dịch vụ
         reject_appeal: Từ chối khiếu nại
         reject_user: Từ chối đăng ký
         remove_avatar_user: Xóa ảnh đại diện
@@ -249,7 +244,6 @@ vi:
         create_domain_block_html: "%{name} đã chặn máy chủ %{target}"
         create_email_domain_block_html: "%{name} đã chặn tên miền email %{target}"
         create_ip_block_html: "%{name} đã chặn IP %{target}"
-        create_relay_html: "%{name} đã tạo relay %{target}"
         create_unavailable_domain_html: "%{name} đã bỏ liên hợp với máy chủ %{target}"
         create_user_role_html: "%{name} đã tạo vai trò %{target}"
         demote_user_html: "%{name} đã hạ vai trò của %{target}"
@@ -261,22 +255,18 @@ vi:
         destroy_email_domain_block_html: "%{name} đã bỏ chặn email %{target}"
         destroy_instance_html: "%{name} đã thanh trừng máy chủ %{target}"
         destroy_ip_block_html: "%{name} đã bỏ chặn IP %{target}"
-        destroy_relay_html: "%{name} đã xóa relay %{target}"
         destroy_status_html: "%{name} đã xóa tút của %{target}"
         destroy_unavailable_domain_html: "%{name} tiếp tục liên hợp với máy chủ %{target}"
         destroy_user_role_html: "%{name} đã xóa vai trò %{target}"
         disable_2fa_user_html: "%{name} đã vô hiệu hóa xác minh hai bước của %{target}"
         disable_custom_emoji_html: "%{name} đã ẩn emoji %{target}"
-        disable_relay_html: "%{name} đã tắt relay %{target}"
         disable_sign_in_token_auth_user_html: "%{name} đã tắt xác minh email của %{target}"
         disable_user_html: "%{name} đã vô hiệu hóa đăng nhập %{target}"
         enable_custom_emoji_html: "%{name} đã cho phép emoji %{target}"
-        enable_relay_html: "%{name} đã bật relay %{target}"
         enable_sign_in_token_auth_user_html: "%{name} đã bật xác minh email của %{target}"
         enable_user_html: "%{name} đã bỏ vô hiệu hóa đăng nhập %{target}"
         memorialize_account_html: "%{name} đã biến tài khoản %{target} thành một trang tưởng niệm"
         promote_user_html: "%{name} đã nâng vai trò của %{target}"
-        publish_terms_of_service_html: "%{name} đã cập nhật điều khoản dịch vụ"
         reject_appeal_html: "%{name} đã từ chối khiếu nại từ %{target}"
         reject_user_html: "%{name} đã từ chối đăng ký từ %{target}"
         remove_avatar_user_html: "%{name} đã xóa ảnh đại diện của %{target}"
@@ -306,7 +296,6 @@ vi:
       title: Nhật ký kiểm duyệt
       unavailable_instance: "(tên máy chủ không khả dụng)"
     announcements:
-      back: Quay về thông báo
       destroyed_msg: Xóa thông báo thành công!
       edit:
         title: Sửa thông báo
@@ -315,10 +304,6 @@ vi:
       new:
         create: Tạo thông báo
         title: Tạo thông báo mới
-      preview:
-        disclaimer: Vì người dùng không thể chọn không nhận thông báo qua email nên thông báo qua email chỉ nên giới hạn ở những thông báo quan trọng như thông báo vi phạm dữ liệu cá nhân hoặc thông báo đóng máy chủ.
-        explanation_html: 'Gửi email tới <strong>%{display_count} thành viên</strong>. Nội dung sau đây sẽ được đưa vào email:'
-        title: Xem trước thông báo sẽ gửi
       publish: Đăng
       published_msg: Truyền đi thông báo thành công!
       scheduled_for: Đã lên lịch %{time}
@@ -472,36 +457,6 @@ vi:
       new:
         title: Nhập máy chủ chặn
       no_file: Không có tập tin nào được chọn
-    fasp:
-      debug:
-        callbacks:
-          created_at: Đã tạo lúc
-          delete: Xóa
-          ip: Địa chỉ IP
-          request_body: Nội dung yêu cầu
-          title: Debug Callback
-      providers:
-        active: Hoạt động
-        base_url: URL cơ sở
-        callback: Callback
-        delete: Xóa
-        edit: Chỉnh sửa nhà cung cấp
-        finish_registration: Hoàn tất đăng ký
-        name: Tên
-        providers: Nhà cung cấp
-        public_key_fingerprint: Public key fingerprint
-        registration_requested: Yêu cầu đăng ký
-        registrations:
-          confirm: Xác nhận
-          description: Bạn đã nhận được đăng ký từ FASP. Hãy từ chối nếu bạn không phải là người khởi tạo. Nếu bạn đã khởi tạo, hãy so sánh cẩn thận tên và dấu vân tay khóa trước khi xác nhận đăng ký.
-          reject: Từ chối
-          title: Xác nhận đăng ký FASP
-        save: Lưu
-        select_capabilities: Chọn khả năng
-        sign_in: Đăng nhập
-        status: Trạng thái
-        title: Fediverse Auxiliary Service Providers
-      title: FASP
     follow_recommendations:
       description_html: "<strong>Gợi ý theo dõi giúp những người mới nhanh chóng tìm thấy những nội dung thú vị</strong>. Khi một người chưa đủ tương tác với những người khác để hình thành các đề xuất theo dõi được cá nhân hóa, thì những người này sẽ được đề xuất. Nó bao gồm những người có số lượt tương tác gần đây cao nhất và số lượng người theo dõi cao nhất cho một ngôn ngữ nhất định trong máy chủ."
       language: Theo ngôn ngữ
@@ -849,10 +804,8 @@ vi:
       back_to_account: Quay lại trang tài khoản
       back_to_report: Quay lại trang báo cáo
       batch:
-        add_to_report: 'Thêm vào báo cáo #%{id}'
         remove_from_report: Xóa khỏi báo cáo
         report: Báo cáo
-      contents: Nội dung
       deleted: Đã xóa
       favourites: Lượt thích
       history: Lịch sử phiên bản
@@ -861,17 +814,13 @@ vi:
       media:
         title: Media
       metadata: Metadata
-      no_history: Tút này chưa được chỉnh sửa
       no_status_selected: Bạn chưa chọn bất kỳ tút nào
       open: Mở tút
       original_status: Tút gốc
       reblogs: Lượt đăng lại
-      replied_to_html: Trả lời đến %{acct_link}
       status_changed: Tút đã sửa
-      status_title: Đăng bởi @%{name}
-      title: Tút từ tài khoản - @%{name}
+      title: Tất cả tút
       trending: Xu hướng
-      view_publicly: Xem công khai
       visibility: Hiển thị
       with_media: Có media
     strikes:
@@ -948,35 +897,6 @@ vi:
       search: Tìm kiếm
       title: Hashtag
       updated_msg: Hashtag đã được cập nhật thành công
-    terms_of_service:
-      back: Trở về điều khoản dịch vụ
-      changelog: Điểm mới
-      create: Dùng của bạn
-      current: Hiện tại
-      draft: Bản nháp
-      generate: Dùng mẫu
-      generates:
-        action: Tạo
-        chance_to_review_html: "<strong>Các điều khoản dịch vụ đã tạo sẽ không được tự động công bố.</strong> Bạn sẽ cần xem lại trước. Vui lòng điền thông tin cần thiết để tiếp tục."
-        explanation_html: Mẫu điều khoản dịch vụ được cung cấp chỉ nhằm mục đích cung cấp thông tin và không được hiểu là tư vấn pháp lý về bất kỳ vấn đề nào. Vui lòng tham khảo ý kiến ​​cố vấn pháp lý của riêng bạn về tình huống của bạn và các câu hỏi pháp lý cụ thể mà bạn có.
-        title: Thiết lập Điều khoản Dịch vụ
-      going_live_on_html: Chính thức áp dụng từ %{date}
-      history: Lịch sử
-      live: Đang hiển thị
-      no_history: Hiện tại chưa có ghi nhận thay đổi nào về các điều khoản dịch vụ.
-      no_terms_of_service_html: Hiện tại bạn chưa cấu hình bất kỳ điều khoản dịch vụ nào. Điều khoản dịch vụ nhằm mục đích cung cấp sự rõ ràng và bảo vệ bạn khỏi các trách nhiệm pháp lý tiềm ẩn trong các tranh chấp với người dùng của bạn.
-      notified_on_html: Đã thông báo tới thành viên vào %{date}
-      notify_users: Thông báo tới thành viên
-      preview:
-        explanation_html: 'Gửi email tới <strong>%{display_count} thành viên</strong> đã đăng ký trước %{date}. Nội dung sau đây sẽ được đưa vào email:'
-        send_preview: Gửi bản xem thử tới %{email}
-        send_to_all:
-          other: Gửi tới %{display_count} email
-        title: Xem trước thông báo về điều khoản dịch vụ
-      publish: Đăng
-      published_on_html: Đăng vào %{date}
-      save_draft: Lưu bản nháp
-      title: Điều khoản Dịch vụ
     title: Quản trị
     trends:
       allow: Cho phép
@@ -1180,6 +1100,7 @@ vi:
     migrate_account: Chuyển sang tài khoản khác
     migrate_account_html: Nếu bạn muốn bỏ tài khoản này để dùng một tài khoản khác, bạn có thể <a href="%{path}">thiết lập tại đây</a>.
     or_log_in_with: Hoặc đăng nhập bằng
+    privacy_policy_agreement_html: Tôi đã đọc và đồng ý <a href="%{privacy_policy_path}" target="_blank">chính sách bảo mật</a>
     progress:
       confirm: Xác nhận email
       details: Điền thông tin
@@ -1204,7 +1125,7 @@ vi:
     set_new_password: Đặt mật khẩu mới
     setup:
       email_below_hint_html: Kiểm tra hộp thư rác hoặc yêu cầu gửi lại. Bạn có thể sửa địa chỉ email của mình nếu sai.
-      email_settings_hint_html: Nhấn vào link được gửi đến %{email} để bắt đầu dùng Mastodon. Chúng tôi đợi bạn.
+      email_settings_hint_html: Nhấn vào liên kết chúng tôi vừa gửi để xác minh %{email}. Nhanh nhé bạn.
       link_not_received: Không nhận được mã?
       new_confirmation_instructions_sent: Bạn sẽ nhận được một email mới với liên kết xác minh sau vài phút!
       title: Kiểm tra email của bạn
@@ -1213,7 +1134,7 @@ vi:
       title: Đăng nhập %{domain}
     sign_up:
       manual_review: "%{domain} sẽ duyệt đăng ký thủ công. Để giúp chúng tôi duyệt nhanh, hãy viết một chút về bản thân và lý do bạn muốn có một tài khoản trên %{domain}."
-      preamble: Với tài khoản trên máy chủ Mastodon này, bạn sẽ có thể theo dõi bất kỳ người nào trên các máy chủ khác, bất kể tài khoản của họ ở đâu.
+      preamble: Với tài khoản trên máy chủ Mastodon này, bạn sẽ có thể theo dõi bất kỳ người nào thuộc các máy chủ khác.
       title: Đang đăng ký trên %{domain}.
     status:
       account_status: Trạng thái tài khoản
@@ -1225,8 +1146,6 @@ vi:
       view_strikes: Xem những lần cảnh cáo cũ
     too_fast: Nghi vấn đăng ký spam, xin thử lại.
     use_security_key: Dùng khóa bảo mật
-    user_agreement_html: Tôi đã đọc và đồng ý với <a href="%{terms_of_service_path}" target="_blank">điều khoản dịch vụ</a> và <a href="%{privacy_policy_path}" target="_blank">chính sách bảo mật</a>
-    user_privacy_agreement_html: Tôi đã đọc và đồng ý <a href="%{privacy_policy_path}" target="_blank">chính sách bảo mật</a>
   author_attribution:
     example_title: Văn bản mẫu
     hint_html: Bạn là nhà báo hoặc blogger bên ngoài Mastodon? Kiểm soát cách bài viết của bạn được ghi nhận khi chia sẻ trên Mastodon.
@@ -1425,31 +1344,19 @@ vi:
       overwrite: Ghi đè
       overwrite_long: Thay thế các bản ghi hiện tại bằng các bản ghi mới
     overwrite_preambles:
-      blocking_html:
-        other: Bạn sắp <strong>thay thế danh sách chặn</strong> với <strong>%{count} người</strong> từ <strong>%{filename}</strong>.
-      bookmarks_html:
-        other: Bạn sắp <strong>thay thế danh sách tút đã lưu</strong> với <strong>%{count} tút</strong> từ <strong>%{filename}</strong>.
-      domain_blocking_html:
-        other: Bạn sắp <strong>thay thế danh sách máy chủ chặn</strong> với <strong>%{count} máy chủ</strong> từ <strong>%{filename}</strong>.
-      following_html:
-        other: Bạn sắp <strong>theo dõi</strong> tới <strong>%{count} người</strong> từ <strong>%{filename}</strong> và <strong>ngừng theo dõi mọi người khác</strong>.
-      lists_html:
-        other: Bạn sắp <strong>thay thế danh sách</strong> với nội dung của <strong>%{filename}</strong>. Có <strong>%{count} người</strong> sẽ được thêm vào những danh sách mới.
-      muting_html:
-        other: Bạn sắp <strong>they thế danh sách người bạn chặn</strong> với <strong>%{count} người</strong> từ <strong>%{filename}</strong>.
+      blocking_html: Bạn sắp <strong>thay thế danh sách chặn</strong> với <strong>%{total_items} tài khoản</strong> từ <strong>%{filename}</strong>.
+      bookmarks_html: Bạn sắp <strong>thay thế lượt lưu</strong> với <strong>%{total_items} tút</strong> từ <strong>%{filename}</strong>.
+      domain_blocking_html: Bạn sắp <strong>thay thế danh sách máy chủ chặn</strong> với <strong>%{total_items} máy chủ</strong> từ <strong>%{filename}</strong>.
+      following_html: Bạn sắp <strong>theo dõi</strong> với <strong>%{total_items} người</strong> từ <strong>%{filename}</strong> và <strong>ngừng theo dõi bất kỳ ai</strong>.
+      lists_html: Bạn sắp <strong>thay thế các danh sách</strong> bằng nội dung từ <strong>%{filename}</strong>. Hơn <strong>%{total_items} tài khoản</strong> sẽ được thêm vào những danh sách mới.
+      muting_html: Bạn sắp <strong>thay thế danh sách ẩn</strong> với <strong>%{total_items} người</strong> từ <strong>%{filename}</strong>.
     preambles:
-      blocking_html:
-        other: Bạn sắp <strong>chặn</strong> tới <strong>%{count} người</strong> từ <strong>%{filename}</strong>.
-      bookmarks_html:
-        other: Bạn sắp thêm tới <strong>%{count} tút</strong> từ <strong>%{filename}</strong> vào <strong>danh sách tút đã lưu</strong>.
-      domain_blocking_html:
-        other: Bạn sắp <strong>chặn</strong> tới <strong>%{count} máy chủ</strong> từ <strong>%{filename}</strong>.
-      following_html:
-        other: Bạn sắp <strong>theo dõi</strong> tới <strong>%{count} người</strong> từ <strong>%{filename}</strong>.
-      lists_html:
-        other: Bạn sắp thêm tới <strong>%{count} người</strong> từ <strong>%{filename}</strong> vào <strong>danh sách của bạn</strong>. Những danh sách mới sẽ được tạo nếu không có danh sách để thêm.
-      muting_html:
-        other: Bạn sắp <strong>ẩn</strong> tới <strong>%{count} người</strong> từ <strong>%{filename}</strong>.
+      blocking_html: Bạn sắp <strong>chặn</strong> tới <strong>%{total_items} người</strong> từ <strong>%{filename}</strong>.
+      bookmarks_html: Bạn sắp thêm vào <strong>%{total_items} tút</strong> từ <strong>%{filename}</strong> vào <strong>lượt lưu</strong>.
+      domain_blocking_html: Bạn sắp <strong>chặn</strong> tới <strong>%{total_items} máy chủ</strong> từ <strong>%{filename}</strong>.
+      following_html: Bạn sắp <strong>theo dõi</strong> tới <strong>%{total_items} người</strong> từ <strong>%{filename}</strong>.
+      lists_html: Bạn sắp thêm <strong>%{total_items} tài khoản</strong> từ <strong>%{filename}</strong> vào <strong>danh sách của bạn</strong>. Những danh sách mới sẽ được tạo nếu bạn chưa có danh sách nào.
+      muting_html: Bạn sắp <strong>ẩn</strong> lên tới <strong>%{total_items} người</strong> từ <strong>%{filename}</strong>.
     preface: Bạn có thể nhập dữ liệu mà bạn đã xuất từ một máy chủ khác, chẳng hạn danh sách những người đang theo dõi hoặc chặn.
     recent_imports: Đã nhập gần đây
     states:
@@ -1705,7 +1612,7 @@ vi:
   scheduled_statuses:
     over_daily_limit: Bạn đã vượt qua giới hạn được lên lịch đăng tút %{limit} hôm nay
     over_total_limit: Bạn đã vượt quá giới hạn %{limit} của các tút được lên lịch
-    too_soon: ngày phải ở tương lai
+    too_soon: Ngày lên lịch phải trong tương lai
   self_destruct:
     lead_html: Rất tiếc, <strong>%{domain}</strong>đã đóng cửa vĩnh viễn. Nếu bạn có tài khoản ở đó, bạn sẽ không thể tiếp tục sử dụng tài khoản đó nhưng bạn vẫn có thể yêu cầu bản sao lưu dữ liệu của mình.
     title: Máy chủ đang đóng cửa
@@ -1864,8 +1771,6 @@ vi:
       too_late: Đã quá trễ để khiếu nại
   tags:
     does_not_match_previous_name: không khớp với tên trước
-  terms_of_service:
-    title: Điều khoản Dịch vụ
   themes:
     contrast: Mastodon (Tương phản)
     default: Mastodon (Tối)
@@ -1897,10 +1802,6 @@ vi:
     recovery_instructions_html: Nếu bạn bị mất điện thoại, hãy dùng một trong các mã khôi phục bên dưới để lấy lại quyền truy cập vào tài khoản của mình. <strong>Giữ mã khôi phục an toàn</strong>. Ví dụ, bạn có thể in chúng ra giấy.
     webauthn: Khóa bảo mật
   user_mailer:
-    announcement_published:
-      description: 'Quản trị viên %{domain} gửi một thông báo:'
-      subject: Thông báo dịch vụ
-      title: "%{domain} thông báo dịch vụ"
     appeal_approved:
       action: Cài đặt tài khoản
       explanation: Khiếu nại về tài khoản của bạn vào %{strike_date}, được gửi lúc %{appeal_date} đã được chấp nhận. Tài khoản của bạn đã có thể sử dụng bình thường.
@@ -1930,15 +1831,6 @@ vi:
       further_actions_html: Nếu đây không phải là bạn, hãy %{action} lập tức và bật xác minh hai bước để giữ tài khoản được an toàn.
       subject: Đăng nhập tài khoản từ địa chỉ IP mới
       title: Lần đăng nhập mới
-    terms_of_service_changed:
-      agreement: Tiếp tục sử dụng %{domain}, đồng nghĩa bạn đồng ý điều khoản dịch vụ. Nếu bạn không đồng ý với các điều khoản đã cập nhật, hãy xóa tài khoản %{domain} của bạn.
-      changelog: 'Nhìn sơ qua, bản cập nhật này:'
-      description: 'Bạn nhận được email này vì chúng tôi đang thực hiện một số thay đổi đối với các điều khoản dịch vụ tại %{domain}. Thay đổi sẽ có hiệu lực từ %{date}. Hãy xem lại đầy đủ các điều khoản đã cập nhật tại đây:'
-      description_html: Bạn nhận được email này vì chúng tôi đang thực hiện một số thay đổi đối với các điều khoản dịch vụ tại %{domain}. Thay đổi sẽ có hiệu lực từ <strong>%{date}</strong>. Hãy xem lại <a href="%{path}" target="_blank">đầy đủ các điều khoản được cập nhật ở đây</a>.
-      sign_off: Đội ngũ %{domain}
-      subject: Cập nhật điều khoản dịch vụ
-      subtitle: Điều khoản dịch vụ tại %{domain} đã thay đổi
-      title: Cập nhật quan trọng
     warning:
       appeal: Gửi khiếu nại
       appeal_description: Nếu bạn nghĩ đây chỉ là nhầm lẫn, hãy gửi một khiếu nại cho %{instance}.
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index 3b87654041..806024b110 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -184,7 +184,6 @@ zh-CN:
         create_domain_block: 屏蔽站点
         create_email_domain_block: 封禁新的邮箱域名
         create_ip_block: 新建 IP 规则
-        create_relay: 创建中继站
         create_unavailable_domain: 创建不可用域名
         create_user_role: 创建角色
         demote_user: 取消管理员
@@ -196,22 +195,18 @@ zh-CN:
         destroy_email_domain_block: 解除邮箱域名封禁
         destroy_instance: 清除实例
         destroy_ip_block: 删除 IP 规则
-        destroy_relay: 删除中继站
         destroy_status: 删除嘟文
         destroy_unavailable_domain: 删除不可用域名
         destroy_user_role: 删除角色
         disable_2fa_user: 停用双因素认证
         disable_custom_emoji: 禁用自定义表情符号
-        disable_relay: 禁用中继站
         disable_sign_in_token_auth_user: 为用户禁用邮件令牌身份验证
         disable_user: 禁用用户
         enable_custom_emoji: 启用自定义表情符号
-        enable_relay: 启用中继站
         enable_sign_in_token_auth_user: 为用户启用邮件令牌身份验证
         enable_user: 启用用户
         memorialize_account: 设为追悼账号
         promote_user: 指派管理员
-        publish_terms_of_service: 发布服务条款
         reject_appeal: 驳回申诉
         reject_user: 拒绝用户
         remove_avatar_user: 移除头像
@@ -249,7 +244,6 @@ zh-CN:
         create_domain_block_html: "%{name} 屏蔽了站点 %{target}"
         create_email_domain_block_html: "%{name} 封禁了邮箱域名 %{target}"
         create_ip_block_html: "%{name} 为 IP %{target} 创建了规则"
-        create_relay_html: "%{name} 添加了中继站 %{target}"
         create_unavailable_domain_html: "%{name} 停止了向域名 %{target} 的投递"
         create_user_role_html: "%{name} 创建了角色 %{target}"
         demote_user_html: "%{name} 撤销了用户 %{target} 的管理权限"
@@ -261,22 +255,18 @@ zh-CN:
         destroy_email_domain_block_html: "%{name} 解封了邮箱域名 %{target}"
         destroy_instance_html: "%{name} 清除了实例 %{target}"
         destroy_ip_block_html: "%{name} 删除了 IP %{target} 的规则"
-        destroy_relay_html: "%{name} 删除了中继站 %{target}"
         destroy_status_html: "%{name} 删除了 %{target} 的嘟文"
         destroy_unavailable_domain_html: "%{name} 恢复了向域名 %{target} 的投递"
         destroy_user_role_html: "%{name} 删除了角色 %{target}"
         disable_2fa_user_html: "%{name} 停用了用户 %{target} 的双因素认证"
         disable_custom_emoji_html: "%{name} 停用了自定义表情 %{target}"
-        disable_relay_html: "%{name} 停用了中继站 %{target}"
         disable_sign_in_token_auth_user_html: "%{name} 为 %{target} 禁用了邮件令牌身份验证"
         disable_user_html: "%{name} 将用户 %{target} 设置为禁止登录"
         enable_custom_emoji_html: "%{name} 启用了自定义表情 %{target}"
-        enable_relay_html: "%{name} 启用了中继站 %{target}"
         enable_sign_in_token_auth_user_html: "%{name} 为 %{target} 启用了邮件令牌身份验证"
         enable_user_html: "%{name} 将用户 %{target} 设置为允许登录"
         memorialize_account_html: "%{name} 将 %{target} 设置为追悼账号"
         promote_user_html: "%{name} 将用户 %{target} 设为管理员"
-        publish_terms_of_service_html: "%{name} 更新了服务条款"
         reject_appeal_html: "%{name} 驳回了 %{target} 对审核结果的申诉"
         reject_user_html: "%{name} 拒绝了用户 %{target} 的注册"
         remove_avatar_user_html: "%{name} 删除了 %{target} 的头像"
@@ -306,7 +296,6 @@ zh-CN:
       title: 审核日志
       unavailable_instance: "(域名不可用)"
     announcements:
-      back: 回到公告
       destroyed_msg: 公告已删除!
       edit:
         title: 编辑公告
@@ -315,10 +304,6 @@ zh-CN:
       new:
         create: 创建公告
         title: 新公告
-      preview:
-        disclaimer: 由于用户无法选择退出,电子邮件通知应仅限于重要公告,例如个人数据泄露或服务器关闭通知。
-        explanation_html: 此电子邮件将发送给 <strong>%{display_count} 用户</strong>。电子邮件将包含以下文本:
-        title: 预览公告通知
       publish: 发布
       published_msg: 公告已发布!
       scheduled_for: 定时在 %{time}
@@ -472,30 +457,6 @@ zh-CN:
       new:
         title: 导入域名列表
       no_file: 没有选择文件
-    fasp:
-      debug:
-        callbacks:
-          created_at: 创建于
-          delete: 刪除
-          ip: IP 地址
-          request_body: 请求正文
-          title: 调试回调
-      providers:
-        active: 有效
-        base_url: 基础 URL
-        callback: 回调
-        delete: 刪除
-        edit: 编辑提供商
-        finish_registration: 完成注册
-        name: 名称
-        providers: 提供商
-        public_key_fingerprint: 公钥指纹
-        registrations:
-          confirm: 确认
-          reject: 拒绝
-        save: 保存
-        sign_in: 登录
-        status: 状态
     follow_recommendations:
       description_html: "<strong>“关注推荐”可帮助新用户快速找到有趣的内容</strong>。 当用户与他人的互动不足以形成个性化的建议时,就会推荐关注这些账号。推荐会每日更新,基于选定语言的近期最高互动数和最多本站关注者数综合评估得出。"
       language: 选择语言
@@ -843,10 +804,8 @@ zh-CN:
       back_to_account: 返回账号信息页
       back_to_report: 返回举报页
       batch:
-        add_to_report: '添加到举报 #%{id}'
         remove_from_report: 从举报中移除
         report: 举报
-      contents: 内容
       deleted: 已删除
       favourites: 喜欢
       history: 版本历史记录
@@ -855,17 +814,13 @@ zh-CN:
       media:
         title: 媒体文件
       metadata: 元数据
-      no_history: 此嘟文没有编辑历史
       no_status_selected: 因为没有嘟文被选中,所以没有更改
       open: 展开嘟文
       original_status: 原始嘟文
       reblogs: 转发
-      replied_to_html: 回复 %{acct_link}
       status_changed: 嘟文已编辑
-      status_title: "@%{name} 的嘟文"
-      title: 该账号的嘟文 - @%{name}
+      title: 账号嘟文
       trending: 当前热门
-      view_publicly: 以公开身份查看
       visibility: 可见性
       with_media: 含有媒体文件
     strikes:
@@ -942,34 +897,6 @@ zh-CN:
       search: 搜索
       title: 话题
       updated_msg: 话题设置更新成功
-    terms_of_service:
-      back: 返回服务条款页
-      changelog: 变更说明
-      create: 自定义
-      current: 当前版本
-      draft: 草稿
-      generate: 使用模板
-      generates:
-        action: 生成
-        chance_to_review_html: "<strong>服务条款生成后不会自动发布。</strong>你可以审核生成的草稿,填写必要的信息后继续操作。"
-        explanation_html: 此服务条款模板仅供参考,不构成法律意见。如有任何法律问题,请咨询法律顾问。
-        title: 设置服务条款
-      history: 历史记录
-      live: 生效中
-      no_history: 尚无服务条款变更记录。
-      no_terms_of_service_html: 你还没有设置任何服务条款。设置服务条款可以清晰地界定责任,并在与用户发生争议时有效保护您的权益。
-      notified_on_html: 已于 %{date} 通知用户
-      notify_users: 通知用户
-      preview:
-        explanation_html: 即将向在 %{date} 注册之前的<strong> %{display_count} 名用户</strong>发送邮件。下述文本将会包含在邮件中:
-        send_preview: 向 %{email} 发送预览
-        send_to_all:
-          other: 发送 %{display_count} 封邮件
-        title: 服务条款变更通知预览
-      publish: 发布
-      published_on_html: 发表于 %{date}
-      save_draft: 保存草稿
-      title: 服务条款
     title: 管理
     trends:
       allow: 允许
@@ -1173,6 +1100,7 @@ zh-CN:
     migrate_account: 迁移到另一个账号
     migrate_account_html: 如果你希望引导他人关注另一个账号,请<a href="%{path}">点击这里进行设置</a>。
     or_log_in_with: 或通过外部服务登录
+    privacy_policy_agreement_html: 我已阅读并同意 <a href="%{privacy_policy_path}" target="_blank">隐私政策</a>
     progress:
       confirm: 确认邮箱
       details: 你的详细信息
@@ -1197,7 +1125,7 @@ zh-CN:
     set_new_password: 设置新密码
     setup:
       email_below_hint_html: 请检查你的垃圾邮件文件夹,或请求重新发送邮件。如果你填写的邮箱地址有误,请更正。
-      email_settings_hint_html: 点击发送到 %{email} 的链接,即可开始使用 Mastodon。
+      email_settings_hint_html: 请点击我们发送给 %{email} 地址中的确认链接。我在这儿等着你。
       link_not_received: 没有收到链接?
       new_confirmation_instructions_sent: 你将在几分钟内收到一封带有确认链接的新邮件!
       title: 请检查你的收件箱
@@ -1206,7 +1134,7 @@ zh-CN:
       title: 登录到 %{domain}
     sign_up:
       manual_review: 你在 %{domain} 上的注册需要经由管理人员手动审核。 为了帮助我们处理你的注册,请简要说明你为什么想在 %{domain} 上注册。
-      preamble: 在这个 Mastodon 站点上注册一个账号,你就可以关注联邦宇宙中的任何人,无论他们的账号在哪里。
+      preamble: 有了这个Mastodon服务器上的账号,你就可以关注Mastodon网络上的任何其他人,无论他们的账号在哪里。
       title: 让我们在 %{domain} 上开始。
     status:
       account_status: 账号状态
@@ -1218,8 +1146,6 @@ zh-CN:
       view_strikes: 查看针对你账号的处罚记录
     too_fast: 表单提交过快,请重试。
     use_security_key: 使用安全密钥
-    user_agreement_html: 我已阅读并同意 <a href="%{terms_of_service_path}" target="_blank">服务条款</a> 与 <a href="%{privacy_policy_path}" target="_blank">隐私政策</a>
-    user_privacy_agreement_html: 我已阅读并同意<a href="%{privacy_policy_path}" target="_blank">隐私政策</a>
   author_attribution:
     example_title: 示例文本
     hint_html: 你是否在 Mastodon 之外撰写新闻或博客文章?控制它们被分享到 Mastodon 时的署名方式。
@@ -1418,31 +1344,19 @@ zh-CN:
       overwrite: 覆盖
       overwrite_long: 将当前记录替换为新记录
     overwrite_preambles:
-      blocking_html:
-        other: 你即将使用来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个</strong>账号<strong>替换你的屏蔽列表</strong>。
-      bookmarks_html:
-        other: 你即将使用来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 条</strong>嘟文<strong>替换你的收藏列表</strong>。
-      domain_blocking_html:
-        other: 你即将使用来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个</strong>站点域名<strong>替换你的站点屏蔽列表</strong>。
-      following_html:
-        other: 你即将<strong>关注</strong>来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个账号</strong>,并<strong>停止关注其他所有人</strong>。
-      lists_html:
-        other: 你即将使用来自 <strong>%{filename}</strong> 的内容<strong>替换你的列表</strong>。最多将会有 <strong>%{count} 个账号</strong> 被添加到新列表。
-      muting_html:
-        other: 你即将使用来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个</strong>账号<strong>替换你的已隐藏账号列表</strong>。
+      blocking_html: 你即将使用来自<strong> %{filename} </strong>的最多<strong> %{total_items} 个账号</strong>替换你的屏蔽列表。
+      bookmarks_html: 你即将使用来自<strong> %{filename} </strong>的<strong> %{total_items} 篇嘟文</strong>替换你的收藏夹。
+      domain_blocking_html: 你即将使用来自<strong> %{filename} </strong>的最多<strong> %{total_items} 个站点域名</strong><strong>替换你的站点屏蔽列表</strong>。
+      following_html: 你即将从<strong> %{filename} </strong>关注<strong> %{total_items} 个账号</strong>,并停止关注其他任何人。
+      lists_html: 你即将用<strong> %{filename} </strong>的内容<strong>替换你的列表</strong>。新列表中将添加<strong> %{total_items} 个账号</strong>。
+      muting_html: 你即将使用来自<strong> %{filename} </strong>的最多<strong> %{total_items} 个账号</strong>替换你已隐藏的账号列表。
     preambles:
-      blocking_html:
-        other: 你即将<strong>屏蔽</strong>来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个账号</strong>。
-      bookmarks_html:
-        other: 你即将把来自<strong> %{filename} </strong>的<strong> %{count} 篇嘟文</strong>添加到你的<strong>收藏夹</strong>中。
-      domain_blocking_html:
-        other: 你即将<strong>屏蔽</strong>来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个站点</strong>。
-      following_html:
-        other: 你即将<strong>关注</strong>来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个账号</strong>。
-      lists_html:
-        other: 你即将从<strong> %{filename} </strong>中添加最多<strong> %{count} 个账号</strong>到你的<strong>列表</strong>中。如果没有可用列表,将创建新的列表。
-      muting_html:
-        other: 你即将<strong>隐藏</strong>来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个账号</strong>。
+      blocking_html: 你即将从<strong> %{filename} </strong>中<strong>封锁</strong>多达<strong> %{total_items} </strong>个账号。
+      bookmarks_html: 你即将把来自<strong> %{filename} </strong>的<strong> %{total_items} 篇嘟文</strong>添加到你的<strong>书签</strong>中。
+      domain_blocking_html: 你即将屏蔽<strong> %{filename} </strong>中<strong>列出的</strong><strong> %{total_items} 个站点域名</strong>。
+      following_html: 你即将从<strong> %{filename} </strong><strong>关注</strong>最多<strong> %{total_items} 个账号</strong>。
+      lists_html: 你即将从<strong> %{filename} </strong>中添加最多<strong> %{total_items} 个账号</strong>到你的<strong>列表</strong>中。如果没有可用列表,将创建新的列表。
+      muting_html: 你即将从<strong> %{filename} </strong>中<strong>隐藏</strong><strong> %{total_items} 个账号</strong>。
     preface: 你可以在此导入你在其他实例导出的数据,比如你所关注或屏蔽的用户列表。
     recent_imports: 最近导入
     states:
@@ -1698,7 +1612,7 @@ zh-CN:
   scheduled_statuses:
     over_daily_limit: 你已超出每日定时嘟文的上限(%{limit} 条)
     over_total_limit: 你已超出定时嘟文的上限(%{limit} 条)
-    too_soon: 日期必须晚于当前时间
+    too_soon: 所定的时间必须在未来
   self_destruct:
     lead_html: 很遗憾,<strong>%{domain}</strong> 即将永久关闭。 如果你在其中设有账号,那么你将无法再继续使用,但你仍可以请求获得本人数据的备份。
     title: 此服务器即将关闭
@@ -1857,8 +1771,6 @@ zh-CN:
       too_late: 已来不及对此次处罚提出申诉
   tags:
     does_not_match_previous_name: 和之前的名称不匹配
-  terms_of_service:
-    title: 服务条款
   themes:
     contrast: Mastodon(高对比度)
     default: Mastodon(暗色主题)
@@ -1890,10 +1802,6 @@ zh-CN:
     recovery_instructions_html: 如果你的手机无法使用,你可以使用下列任意一个恢复代码来重新获得对账号的访问权。<strong>请妥善保管好你的恢复代码</strong>(例如,你可以将它们打印出来,然后和其他重要的文件放在一起)。
     webauthn: 安全密钥
   user_mailer:
-    announcement_published:
-      description: "%{domain}管理员发布了一则公告:"
-      subject: 服务公告
-      title: "%{domain}服务公告"
     appeal_approved:
       action: 账号设置
       explanation: 你于 %{appeal_date} 对 %{strike_date} 在你账号上做出的处罚提出的申诉已被批准,你的账号已回到正常状态。
@@ -1923,13 +1831,6 @@ zh-CN:
       further_actions_html: 如果不是你本人操作,我们建议你立即 %{action} 并启用双因素认证,以确保账号安全。
       subject: 已有新 IP 地址访问了你的账号
       title: 一次新登录
-    terms_of_service_changed:
-      agreement: 继续使用你在 %{domain} 的账号即表示您同意这些条款。如果你不同意更新后的条款,你可以随时删除账号以终止与 %{domain} 的协议。
-      changelog: 本次更新的要点如下:
-      sign_off: "%{domain} 运营团队"
-      subject: 服务条款变更
-      subtitle: "%{domain} 更新了服务条款"
-      title: 重要变更
     warning:
       appeal: 提交申诉
       appeal_description: 如果你认为此结果有误,可以向 %{instance} 的工作人员提交申诉。
diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml
index 43f4f3ecba..c0726fc2f7 100644
--- a/config/locales/zh-HK.yml
+++ b/config/locales/zh-HK.yml
@@ -38,7 +38,7 @@ zh-HK:
         current_email: 現時電郵
         label: 更改電郵
         new_email: 新的電郵
-        submit: 更改電郵
+        submit: 改變電郵
         title: 改變 %{username} 的電郵
       change_role:
         changed_msg: 成功更改身份!
@@ -182,7 +182,6 @@ zh-HK:
         destroy_domain_block: 刪除已封鎖的域名
         destroy_instance: 清除網域
         destroy_ip_block: 刪除 IP 規則
-        destroy_relay: 刪除中繼
         destroy_status: 刪除文章
         destroy_unavailable_domain: 刪除無效域名
         destroy_user_role: 刪除身份
@@ -426,12 +425,6 @@ zh-HK:
       new:
         title: 匯入封鎖的網域
       no_file: 未選擇檔案
-    fasp:
-      debug:
-        callbacks:
-          delete: 刪除
-      providers:
-        delete: 刪除
     follow_recommendations:
       description_html: "<strong>跟隨建議幫助新使用者快速找到有趣內容。</strong> 當使用者尚未和其他帳號足夠多的互動以產生個人化建議時,以下帳號將被推荐。這些是一句指定語言的近期參與度和本地粉絲數最高之帳戶組合每日重新計算。"
       language: 按語言
@@ -784,6 +777,7 @@ zh-HK:
       original_status: 原始帖文
       reblogs: 轉發
       status_changed: 帖文已變更
+      title: 帳戶文章
       trending: 熱門
       visibility: 可見性
       with_media: 含有媒體檔案
@@ -1028,6 +1022,7 @@ zh-HK:
     migrate_account: 轉移到另一個帳號
     migrate_account_html: 想要將這個帳號指向另一個帳號可<a href="%{path}">到這裡設定</a>。
     or_log_in_with: 或登入於
+    privacy_policy_agreement_html: 我已閱讀且同意<a href="%{privacy_policy_path}" target="_blank">私隱政策</a>
     progress:
       details: 你的資料
       review: 我們的審核
@@ -1050,6 +1045,7 @@ zh-HK:
     security: 登入資訊
     set_new_password: 設定新密碼
     setup:
+      email_settings_hint_html: 請點擊我們發送給你的連結來驗證 %{email}。我們會等着你。
       link_not_received: 收不到連結嗎?
       title: 檢查你的信箱
     sign_in:
@@ -1057,6 +1053,7 @@ zh-HK:
       title: 登入 %{domain}
     sign_up:
       manual_review: "%{domain} 的註冊均需要我們的管理員手動審核。為了幫助我們處理你的註冊,請填寫一些關於你的資訊,以及為何想在 %{domain} 上開設帳號。"
+      preamble: 有了這個 Mastodon 伺服器的帳號,無論將帳號託管在何處,你都可以追蹤網絡上任何人。
       title: 讓我為你在 %{domain} 上設定好。
     status:
       account_status: 帳戶文章
@@ -1252,6 +1249,20 @@ zh-HK:
       merge_long: 留下舊有記錄並添加新的資訊
       overwrite: 覆蓋
       overwrite_long: 用新記錄覆蓋當前記錄
+    overwrite_preambles:
+      blocking_html: 你將以<strong>%{filename}</strong>的最多<strong>%{total_items}個帳號</strong>,來<strong>取代你現有的封鎖列表</strong>。
+      bookmarks_html: 你將以<strong>%{filename}</strong>的最多<strong>%{total_items}個帳號</strong>,來<strong>取代你現有的書籤</strong>。
+      domain_blocking_html: 你將以<strong>%{filename}</strong>的最多<strong>%{total_items} 個帳號</strong>,來<strong>取代你現有的網域封鎖列表</strong>。
+      following_html: 你將從<strong>%{filename}</strong><strong>追蹤</strong>最多<strong>%{total_items} 個帳號</strong>,並<strong>取消追蹤其他人</strong>。
+      lists_html: 你將根據<strong>%{filename}</strong>的內容<strong>取代的你列表</strong>。最多新增 <strong>%{total_items} 個帳號</strong> 到新列表。
+      muting_html: 你將根據<strong>%{filename}</strong>中最多 <strong>%{total_items} 個帳號</strong><strong>取代你的靜音帳號列表</strong>。
+    preambles:
+      blocking_html: 你將根據<strong>%{filename}</strong><strong>封鎖</strong>最多 <strong>%{total_items} 個帳號</strong>。
+      bookmarks_html: 你將加入 <strong>%{filename}</strong> 中最多 <strong>%{total_items} 篇帖文</strong>到你的<strong>書籤</strong>中。
+      domain_blocking_html: 你將<strong>封鎖</strong>來自<strong>%{filename}</strong>的最多 <strong>%{total_items} 個網域</strong>。
+      following_html: 你將<strong>追蹤</strong> 來自 <strong>%{filename}</strong> 最多 <strong>%{total_items} 個帳號</strong>。
+      lists_html: 你將加入來自<strong>%{filename}</strong>最多 <strong>%{total_items} 個帳號</strong>到你的<strong>列表</strong>中。如果現時沒有列表,將會建立新列表。
+      muting_html: 你將<strong>靜音</strong>來自<strong>%{filename}</strong>的最多 <strong>%{total_items} 個帳號</strong>。
     preface: 你可以在此匯入你在其他服務站所匯出的資料檔,包括︰你所關注的用戶,被你封鎖的用戶。
     recent_imports: 最近的匯入
     states:
@@ -1493,6 +1504,7 @@ zh-HK:
   scheduled_statuses:
     over_daily_limit: 你已經超越了當天排定發文的限額 (%{limit})
     over_total_limit: 你已經超越了排定發文的限額 (%{limit})
+    too_soon: 不可以改變過去哦,嘟文只可以排定在未來
   self_destruct:
     lead_html: 很遺憾,<strong>%{domain}</strong> 即將永久停止服務。如果你在該處擁有帳號,你將無法繼續使用它,但你仍然可以要求備份你的數據。
     title: 這個伺服器即將停止服務
@@ -1556,7 +1568,6 @@ zh-HK:
     import: 匯入
     import_and_export: 匯入及匯出
     migrate: 帳戶遷移
-    notifications: 電郵通知
     preferences: 偏好設定
     profile: 個人資料
     relationships: 關注及追隨者
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index fae08b3216..14b727cf1d 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -184,7 +184,6 @@ zh-TW:
         create_domain_block: 新增網域封鎖
         create_email_domain_block: 新增電子郵件網域封鎖
         create_ip_block: 新增IP規則
-        create_relay: 新增中繼
         create_unavailable_domain: 新增無法存取的網域
         create_user_role: 新增角色
         demote_user: 將用戶降級
@@ -196,22 +195,18 @@ zh-TW:
         destroy_email_domain_block: 刪除電子郵件網域封鎖
         destroy_instance: 清除網域
         destroy_ip_block: 刪除 IP 規則
-        destroy_relay: 刪除中繼
         destroy_status: 刪除狀態
         destroy_unavailable_domain: 刪除無法存取的網域
         destroy_user_role: 移除角色
         disable_2fa_user: 停用兩階段驗證
         disable_custom_emoji: 停用自訂 emoji 表情符號
-        disable_relay: 停用中繼
         disable_sign_in_token_auth_user: 停用使用者電子郵件 token 驗證
         disable_user: 停用帳號
         enable_custom_emoji: 啟用自訂 emoji 表情符號
-        enable_relay: 啟用中繼
         enable_sign_in_token_auth_user: 啟用使用者電子郵件 token 驗證
         enable_user: 啓用帳號
         memorialize_account: 設定成追悼帳號
         promote_user: 將用戶升級
-        publish_terms_of_service: 發佈服務條款
         reject_appeal: 駁回申訴
         reject_user: 回絕使用者
         remove_avatar_user: 刪除大頭貼
@@ -249,7 +244,6 @@ zh-TW:
         create_domain_block_html: "%{name} 已封鎖網域 %{target}"
         create_email_domain_block_html: "%{name} 已封鎖電子郵件網域 %{target}"
         create_ip_block_html: "%{name} 已設定 IP %{target} 的規則"
-        create_relay_html: "%{name} 已新增中繼 %{target}"
         create_unavailable_domain_html: "%{name} 停止發送至網域 %{target}"
         create_user_role_html: "%{name} 已新增 %{target} 角色"
         demote_user_html: "%{name} 將使用者 %{target} 降級"
@@ -261,22 +255,18 @@ zh-TW:
         destroy_email_domain_block_html: "%{name} 已解除封鎖電子郵件網域 %{target}"
         destroy_instance_html: "%{name} 已清除網域 %{target}"
         destroy_ip_block_html: "%{name} 已刪除 IP %{target} 的規則"
-        destroy_relay_html: "%{name} 已刪除中繼 %{target}"
         destroy_status_html: "%{name} 已刪除 %{target} 的嘟文"
         destroy_unavailable_domain_html: "%{name} 已恢復對網域 %{target} 的發送"
         destroy_user_role_html: "%{name} 已刪除 %{target} 角色"
         disable_2fa_user_html: "%{name} 已停用使用者 %{target} 的兩階段驗證 (2FA) "
         disable_custom_emoji_html: "%{name} 已停用自訂 emoji 表情符號 %{target}"
-        disable_relay_html: "%{name} 已停用中繼 %{target}"
         disable_sign_in_token_auth_user_html: "%{name} 已停用 %{target} 之使用者電子郵件 token 驗證"
         disable_user_html: "%{name} 將使用者 %{target} 設定為禁止登入"
         enable_custom_emoji_html: "%{name} 已啟用自訂 emoji 表情符號 %{target}"
-        enable_relay_html: "%{name} 已啟用中繼 %{target}"
         enable_sign_in_token_auth_user_html: "%{name} 已啟用 %{target} 之使用者電子郵件 token 驗證"
         enable_user_html: "%{name} 將使用者 %{target} 設定為允許登入"
         memorialize_account_html: "%{name} 將 %{target} 設定為追悼帳號"
         promote_user_html: "%{name} 對使用者 %{target} 已進行晉級操作"
-        publish_terms_of_service_html: "%{name} 已發佈服務條款更新"
         reject_appeal_html: "%{name} 已回絕來自 %{target} 的審核決定申訴"
         reject_user_html: "%{name} 已回絕自 %{target} 而來的註冊"
         remove_avatar_user_html: "%{name} 已移除 %{target} 的大頭貼"
@@ -306,7 +296,6 @@ zh-TW:
       title: 營運日誌
       unavailable_instance: "(該域名無法使用)"
     announcements:
-      back: 回到公告
       destroyed_msg: 成功刪除公告!
       edit:
         title: 編輯公告
@@ -315,10 +304,6 @@ zh-TW:
       new:
         create: 新增公告
         title: 新增公告
-      preview:
-        disclaimer: 由於使用者無法選擇退出,電子郵件通知應僅限於重要公告,例如個人資料洩露或伺服器關閉通知。
-        explanation_html: 此 email 將寄至 <strong>%{display_count} 名使用者</strong>。以下文字將被包含於 e-mail 中:
-        title: 預覽公告通知
       publish: 發布
       published_msg: 成功發布公告!
       scheduled_for: 排定 %{time}
@@ -472,36 +457,6 @@ zh-TW:
       new:
         title: 匯入網域黑名單
       no_file: 尚未選擇檔案
-    fasp:
-      debug:
-        callbacks:
-          created_at: 建立於
-          delete: 刪除
-          ip: IP 地址
-          request_body: Request body
-          title: 除錯 callback
-      providers:
-        active: 生效
-        base_url: Base URL
-        callback: Callback
-        delete: 刪除
-        edit: 編輯提供商
-        finish_registration: 完成註冊
-        name: 名稱
-        providers: 提供商
-        public_key_fingerprint: 公鑰指紋
-        registration_requested: 已請求註冊
-        registrations:
-          confirm: 確認
-          description: 您收到 FASP的註冊。若您未發起此請求,請拒絕它。若您發起了此請求,請仔細比較名稱與金鑰指紋再確認註冊。
-          reject: 拒絕
-          title: 確認 FASP 註冊
-        save: 儲存
-        select_capabilities: 選擇功能
-        sign_in: 登入
-        status: 狀態
-        title: 聯邦宇宙輔助服務提供商 (Fediverse Auxiliary Service Providers)
-      title: FASP
     follow_recommendations:
       description_html: "<strong>跟隨建議幫助新使用者們快速找到有趣的內容</strong>。當使用者沒有與其他帳號有足夠多的互動以建立個人化跟隨建議時,這些帳號將會被推薦。這些帳號將基於某選定語言之高互動與高本地跟隨者數量帳號而每日重新更新。"
       language: 對於語言
@@ -851,10 +806,8 @@ zh-TW:
       back_to_account: 返回帳號資訊頁面
       back_to_report: 回到檢舉報告頁面
       batch:
-        add_to_report: '新增至報告 #%{id}'
         remove_from_report: 自檢舉報告中移除
         report: 檢舉報告
-      contents: 內容
       deleted: 已刪除
       favourites: 最愛
       history: 版本紀錄
@@ -863,17 +816,13 @@ zh-TW:
       media:
         title: 媒體檔案
       metadata: 詮釋資料
-      no_history: 此嘟文未曾被編輯
       no_status_selected: 因未選取嘟文,所以什麼事都沒發生。
       open: 公開嘟文
       original_status: 原始嘟文
       reblogs: 轉嘟
-      replied_to_html: 回覆給 %{acct_link}
       status_changed: 嘟文已編輯
-      status_title: 由 @%{name} 發嘟
-      title: 嘟文帳號 - @%{name}
+      title: 帳號嘟文
       trending: 熱門
-      view_publicly: 公開檢視
       visibility: 可見性
       with_media: 含有媒體檔案
     strikes:
@@ -950,35 +899,6 @@ zh-TW:
       search: 搜尋
       title: 主題標籤
       updated_msg: 成功更新主題標籤設定
-    terms_of_service:
-      back: 回到服務條款
-      changelog: 有何異動
-      create: 使用您自己的
-      current: 目前
-      draft: 草稿
-      generate: 使用模板
-      generates:
-        action: 產生
-        chance_to_review_html: "<strong>所產生之服務條款將不會自動發佈。</strong> 您將能檢視所產生之結果。請填寫必要細節以繼續。"
-        explanation_html: 提供之服務條款模板僅供參考資訊使用,不應將其作為任何法律建議。請依照您的具體情形與特定法律議題諮詢您的法律顧問。
-        title: 設定服務條款
-      going_live_on_html: 目前條款,自 %{date} 起生效
-      history: 歷史
-      live: 目前版本
-      no_history: 未有任何服務條款變更紀錄。
-      no_terms_of_service_html: 您目前尚未設定任何服務條款。服務條款旨在提供明確性,並保護您與使用者的爭議中免受潛在責任。
-      notified_on_html: 於 %{date} 通知之使用者
-      notify_users: 通知使用者
-      preview:
-        explanation_html: 此 email 將寄至於 %{date} 前註冊之 <strong>%{display_count} 名使用者</strong>。以下文字將被包含於 e-mail 中:
-        send_preview: 將預覽寄至 %{email}
-        send_to_all:
-          other: 寄出 %{display_count} 封 email
-        title: 預覽服務條款通知
-      publish: 發佈
-      published_on_html: 發佈於 %{date}
-      save_draft: 儲存草稿
-      title: 服務條款
     title: 管理介面
     trends:
       allow: 允許
@@ -1182,6 +1102,7 @@ zh-TW:
     migrate_account: 轉移至另一個帳號
     migrate_account_html: 如果您希望引導他人跟隨另一個帳號,請<a href="%{path}">至這裡設定</a>。
     or_log_in_with: 或透過其他方式登入
+    privacy_policy_agreement_html: 我已閱讀且同意 <a href="%{privacy_policy_path}" target="_blank">隱私權政策</a>
     progress:
       confirm: 驗證電子郵件地址
       details: 您的個人資料
@@ -1206,7 +1127,7 @@ zh-TW:
     set_new_password: 設定新密碼
     setup:
       email_below_hint_html: 請檢查您的垃圾郵件資料夾,或是請求另一封驗證信。若不正確,您可以更正您的電子郵件地址。
-      email_settings_hint_html: 請點擊我們寄至 %{email} 之連結以開始使用 Mastodon。我們將於此稍候。
+      email_settings_hint_html: 請點擊我們寄給您連結以驗證 %{email}。我們將於此稍候。
       link_not_received: 無法取得連結嗎?
       new_confirmation_instructions_sent: 您將於幾分鐘之內收到新的包含驗證連結之電子郵件!
       title: 請檢查您的收件匣
@@ -1215,7 +1136,7 @@ zh-TW:
       title: 登入 %{domain}
     sign_up:
       manual_review: "%{domain} 上的註冊由我們的管理員進行人工審核。為協助我們處理您的註冊,請寫一些關於您自己的資訊以及您欲於 %{domain} 上註冊帳號之原因。"
-      preamble: 若於此 Mastodon 伺服器擁有帳號,您將能跟隨聯邦宇宙中任何一份子,無論他們的帳號託管於何處。
+      preamble: 若於此 Mastodon 伺服器擁有帳號,您將能跟隨聯邦宇宙網路中任何一份子,無論他們的帳號託管於何處。
       title: 讓我們一起設定 %{domain} 吧!
     status:
       account_status: 帳號狀態
@@ -1227,8 +1148,6 @@ zh-TW:
       view_strikes: 檢視針對您帳號過去的警示
     too_fast: 送出表單的速度太快跟不上,請稍後再試。
     use_security_key: 使用安全金鑰
-    user_agreement_html: 我已閱讀並同意 <a href="%{terms_of_service_path}" target="_blank">服務條款</a> 與 <a href="%{privacy_policy_path}" target="_blank">隱私權政策</a>
-    user_privacy_agreement_html: 我已閱讀並同意 <a href="%{privacy_policy_path}" target="_blank">隱私權政策</a>
   author_attribution:
     example_title: 範例文字
     hint_html: 您是否正於 Mastodon 之外撰寫新聞或部落格文章?控制當它們於 Mastodon 上分享時您如何獲得信譽。
@@ -1427,31 +1346,19 @@ zh-TW:
       overwrite: 覆蓋
       overwrite_long: 以新的紀錄覆蓋目前紀錄
     overwrite_preambles:
-      blocking_html:
-        other: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{count} 個帳號</strong> 以 <strong>取代您的封鎖帳號列表</strong>。
-      bookmarks_html:
-        other: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{count} 個嘟文</strong> 以 <strong>取代您的書籤</strong>。
-      domain_blocking_html:
-        other: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{count} 個網域</strong> 以 <strong>取代您的封鎖網域列表</strong>。
-      following_html:
-        other: 您將要 <strong>跟隨</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{count} 個帳號</strong> 並且 <strong>取消跟隨其他所有人</strong>。
-      lists_html:
-        other: 您將以 <strong>%{filename}</strong> 之內容<strong>取代您的列表</strong>。這將會新增 <strong>%{count} 個帳號</strong>至新列表。
-      muting_html:
-        other: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{count} 個帳號</strong> 以 <strong>取代您的靜音帳號列表</strong>。
+      blocking_html: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{total_items} 個帳號</strong> 以 <strong>取代您的封鎖帳號列表</strong>。
+      bookmarks_html: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{total_items} 個嘟文</strong> 以 <strong>取代您的書籤</strong>。
+      domain_blocking_html: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{total_items} 個網域</strong> 以 <strong>取代您的封鎖網域列表</strong>。
+      following_html: 您將要 <strong>跟隨</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{total_items} 個帳號</strong> 並且 <strong>取消跟隨其他所有人</strong>。
+      lists_html: 您將以 <strong>%{filename}</strong> 之內容<strong>取代您的列表</strong>。這將會新增 <strong>%{total_items} 個帳號</strong>至新列表。
+      muting_html: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{total_items} 個帳號</strong> 以 <strong>取代您的靜音帳號列表</strong>。
     preambles:
-      blocking_html:
-        other: 您將要 <strong>封鎖</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{count} 個帳號</strong>。
-      bookmarks_html:
-        other: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{count} 個嘟文</strong> 以 <strong>加入至您的書籤</strong>。
-      domain_blocking_html:
-        other: 您將要 <strong>封鎖</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{count} 個網域</strong>。
-      following_html:
-        other: 您將要 <strong>跟隨</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{count} 個帳號</strong>。
-      lists_html:
-        other: 您將自 <strong>%{filename}</strong> 新增 <strong>%{count} 個帳號</strong>至您的<strong>列表</strong>。若不存在列表用以新增帳號,則會建立新列表。
-      muting_html:
-        other: 您將要 <strong>靜音</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{count} 個帳號</strong>。
+      blocking_html: 您將要 <strong>封鎖</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{total_items} 個帳號</strong>。
+      bookmarks_html: 您將要自 <strong>%{filename}</strong> 中之 <strong>%{total_items} 個嘟文</strong> 以 <strong>加入至您的書籤</strong>。
+      domain_blocking_html: 您將要 <strong>封鎖</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{total_items} 個網域</strong>。
+      following_html: 您將要 <strong>跟隨</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{total_items} 個帳號</strong>。
+      lists_html: 您將自 <strong>%{filename}</strong> 新增 <strong>%{total_items} 個帳號</strong>至您的<strong>列表</strong>。若不存在列表用以新增帳號,則會建立新列表。
+      muting_html: 您將要 <strong>靜音</strong> 自 <strong>%{filename}</strong> 中之 <strong>%{total_items} 個帳號</strong>。
     preface: 您能於此匯入您於其他伺服器所匯出的資料檔,包括跟隨中的使用者、封鎖的使用者名單等。
     recent_imports: 最近匯入的
     states:
@@ -1707,7 +1614,7 @@ zh-TW:
   scheduled_statuses:
     over_daily_limit: 您已經超過了本日排定發嘟的限額 (%{limit})
     over_total_limit: 您已經超過排程發嘟的限額 (%{limit})
-    too_soon: 日期必須為未來
+    too_soon: 嘟文不可以改變過去哦,只能預定未來 (咦)
   self_destruct:
     lead_html: 很遺憾,<strong>%{domain}</strong> 即將永久停止服務。如果您於該伺服器擁有帳號,您將無法繼續使用它,但您仍然可以請求您的資料備份。
     title: 這個伺服器即將停止服務
@@ -1866,8 +1773,6 @@ zh-TW:
       too_late: 您太晚申訴這個警示了
   tags:
     does_not_match_previous_name: 與先前的名稱不吻合
-  terms_of_service:
-    title: 服務條款
   themes:
     contrast: Mastodon(高對比)
     default: Mastodon(深色)
@@ -1899,10 +1804,6 @@ zh-TW:
     recovery_instructions_html: 若您的手機無法使用,您可以使用下列任意一個備用驗證碼來重新獲得帳號的存取權。<strong>請妥善保管好您的備用驗證碼</strong>(例如,可以將它們列印出來,與您的其他重要文件放在一起)。
     webauthn: 安全金鑰
   user_mailer:
-    announcement_published:
-      description: "%{domain} 之管理員正在進行公告:"
-      subject: 服務公告
-      title: "%{domain} 服務公告"
     appeal_approved:
       action: 帳號設定
       explanation: 您於 %{appeal_date} 遞交的針對您帳號的 %{strike_date} 警示之申訴已獲批准。您的帳號再次享有良好的信譽。
@@ -1932,19 +1833,6 @@ zh-TW:
       further_actions_html: 如果這個不是您,我們建議您立即 %{action} ,並且啟用二階段驗證 (2FA) 以確保帳號安全。
       subject: 您的帳號已被新 IP 位址存取
       title: 新登入
-    terms_of_service_changed:
-      agreement: 透過繼續使用 %{domain},您將同意這些條款。若您不同意此條款異動,您能隨時終止與 %{domain} 之協議並刪除您的帳號。
-      changelog: 簡而言之,此次更新對您將意味著:
-      description: |-
-        您收到此 e-mail 係因我們正在更新 %{domain} 之服務條款。
-        這些異動將於 %{date} 起生效。我們鼓勵您審視此處之服務條款更新全文:
-      description_html: |-
-        您收到此 e-mail 係因我們正在更新 %{domain} 之服務條款。
-        這些異動將於 <strong>%{date} 起</strong> 生效。我們鼓勵您審視 <a href="%{path}" target="_blank">此處之服務條款更新全文</a>。
-      sign_off: "%{domain} 團隊"
-      subject: 我們的服務條款更新
-      subtitle: "%{domain} 之服務條款正在悄悄發生變化"
-      title: 重要更新
     warning:
       appeal: 遞交申訴
       appeal_description: 若您認為這是錯誤,您可以向 %{instance} 的工作人員提出申訴。
diff --git a/config/mastodon.yml b/config/mastodon.yml
deleted file mode 100644
index 3f110d51cb..0000000000
--- a/config/mastodon.yml
+++ /dev/null
@@ -1,14 +0,0 @@
----
-shared:
-  experimental_features: <%= ENV.fetch('EXPERIMENTAL_FEATURES', nil) %>
-  self_destruct_value: <%= ENV.fetch('SELF_DESTRUCT', nil) %>
-  software_update_url: <%= ENV.fetch('UPDATE_CHECK_URL', 'https://kmy.blue/update-check') %>
-  source:
-    base_url: <%= ENV.fetch('SOURCE_BASE_URL', nil) %>
-    repository: <%= ENV.fetch('GITHUB_REPOSITORY', 'kmycode/mastodon') %>
-    tag: <%= ENV.fetch('SOURCE_TAG', nil) %>
-  version:
-    metadata: <%= ENV.fetch('MASTODON_VERSION_METADATA', nil) %>
-    prerelease: <%= ENV.fetch('MASTODON_VERSION_PRERELEASE', nil) %>
-test:
-  experimental_features: <%= [ENV.fetch('EXPERIMENTAL_FEATURES', nil), 'testing_only'].compact.join(',') %>
diff --git a/config/navigation.rb b/config/navigation.rb
index ac9b42c661..ebcbf10275 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -6,7 +6,7 @@ SimpleNavigation::Configuration.run do |navigation|
   navigation.items do |n|
     n.item :web, safe_join([material_symbol('chevron_left'), t('settings.back')]), root_path
 
-    if Rails.configuration.x.mastodon.software_update_url.present? && current_user.can?(:view_devops)
+    if ENV['UPDATE_CHECK_URL'] != '' && current_user.can?(:view_devops)
       n.item :software_updates, safe_join([material_symbol('report'), t('admin.critical_update_pending')]), admin_software_updates_path, if: -> { SoftwareUpdate.urgent_pending? }, html: { class: 'warning' }
       n.item :software_updates, safe_join([material_symbol('report'), t('admin.update_pendings.major')]), admin_software_updates_path, if: -> { !SoftwareUpdate.urgent_pending? && SoftwareUpdate.major_pending? }, html: { class: 'warning' }
       n.item :software_updates, safe_join([material_symbol('report'), t('admin.update_pendings.patch')]), admin_software_updates_path, if: -> { !SoftwareUpdate.urgent_pending? && SoftwareUpdate.patch_pending? }, html: { class: 'warning' }
@@ -19,7 +19,7 @@ SimpleNavigation::Configuration.run do |navigation|
       s.item :notifications, safe_join([material_symbol('mail'), t('settings.notifications')]), settings_preferences_notifications_path
       s.item :reaching, safe_join([material_symbol('search'), t('preferences.reaching')]), settings_preferences_reaching_path
       s.item :custom_css, safe_join([material_symbol('inbox'), t('preferences.custom_css')]), settings_preferences_custom_css_path
-      s.item :other, safe_join([material_symbol('tune'), t('preferences.other')]), settings_preferences_other_path
+      s.item :other, safe_join([material_symbol('settings'), t('preferences.other')]), settings_preferences_other_path
     end
 
     n.item :relationships, safe_join([material_symbol('groups'), t('settings.relationships')]), relationships_path, if: -> { current_user.functional? && !self_destruct } do |s|
@@ -74,15 +74,13 @@ SimpleNavigation::Configuration.run do |navigation|
 
     n.item :admin, safe_join([material_symbol('manufacturing'), t('admin.title')]), nil, if: -> { current_user.can?(:view_dashboard, :manage_settings, :manage_rules, :manage_announcements, :manage_custom_emojis, :manage_webhooks, :manage_federation) && !self_destruct } do |s|
       s.item :dashboard, safe_join([material_symbol('speed'), t('admin.dashboard.title')]), admin_dashboard_path, if: -> { current_user.can?(:view_dashboard) }
-      s.item :settings, safe_join([material_symbol('tune'), t('admin.settings.title')]), admin_settings_path, if: -> { current_user.can?(:manage_settings) }, highlights_on: %r{/admin/settings}
-      s.item :terms_of_service, safe_join([material_symbol('description'), t('admin.terms_of_service.title')]), admin_terms_of_service_index_path, highlights_on: %r{/admin/terms_of_service}, if: -> { current_user.can?(:manage_rules) }
+      s.item :settings, safe_join([material_symbol('manufacturing'), t('admin.settings.title')]), admin_settings_path, if: -> { current_user.can?(:manage_settings) }, highlights_on: %r{/admin/settings}
       s.item :rules, safe_join([material_symbol('gavel'), t('admin.rules.title')]), admin_rules_path, highlights_on: %r{/admin/rules}, if: -> { current_user.can?(:manage_rules) }
       s.item :warning_presets, safe_join([material_symbol('warning'), t('admin.warning_presets.title')]), admin_warning_presets_path, highlights_on: %r{/admin/warning_presets}, if: -> { current_user.can?(:manage_settings) }
       s.item :roles, safe_join([material_symbol('contact_mail'), t('admin.roles.title')]), admin_roles_path, highlights_on: %r{/admin/roles}, if: -> { current_user.can?(:manage_roles) }
       s.item :announcements, safe_join([material_symbol('campaign'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements}, if: -> { current_user.can?(:manage_announcements) }
       s.item :custom_emojis, safe_join([material_symbol('mood'), t('admin.custom_emojis.title')]), admin_custom_emojis_path, highlights_on: %r{/admin/custom_emojis}, if: -> { current_user.can?(:manage_custom_emojis) }
       s.item :webhooks, safe_join([material_symbol('inbox'), t('admin.webhooks.title')]), admin_webhooks_path, highlights_on: %r{/admin/webhooks}, if: -> { current_user.can?(:manage_webhooks) }
-      s.item :fasp, safe_join([material_symbol('extension'), t('admin.fasp.title')]), admin_fasp_providers_path, highlights_on: %r{/admin/fasp}, if: -> { current_user.can?(:manage_federation) } if Mastodon::Feature.fasp_enabled?
       s.item :relays, safe_join([material_symbol('captive_portal'), t('admin.relays.title')]), admin_relays_path, highlights_on: %r{/admin/relays}, if: -> { !limited_federation_mode? && current_user.can?(:manage_federation) }
       s.item :friend_servers, safe_join([material_symbol('captive_portal'), t('admin.friend_servers.title')]), admin_friend_servers_path, highlights_on: %r{/admin/friend_servers}, if: -> { current_user.can?(:manage_federation) }
     end
diff --git a/config/puma.rb b/config/puma.rb
index 4fe8f802b9..ac9ccac209 100644
--- a/config/puma.rb
+++ b/config/puma.rb
@@ -17,27 +17,6 @@ workers     ENV.fetch('WEB_CONCURRENCY') { 2 }.to_i
 
 preload_app!
 
-if ENV['MASTODON_PROMETHEUS_EXPORTER_ENABLED'] == 'true'
-  require 'prometheus_exporter'
-  require 'prometheus_exporter/instrumentation'
-
-  on_worker_boot do
-    # Ruby process metrics (memory, GC, etc)
-    PrometheusExporter::Instrumentation::Process.start(type: 'puma')
-
-    # ActiveRecord metrics (connection pool usage)
-    PrometheusExporter::Instrumentation::ActiveRecord.start(
-      custom_labels: { type: 'puma' }, # optional params
-      config_labels: [:database, :host] # optional params
-    )
-  end
-
-  after_worker_boot do
-    # Puma metrics
-    PrometheusExporter::Instrumentation::Puma.start unless PrometheusExporter::Instrumentation::Puma.started?
-  end
-end
-
 on_worker_boot do
   ActiveSupport.on_load(:active_record) do
     ActiveRecord::Base.establish_connection
diff --git a/config/routes.rb b/config/routes.rb
index 1f97ddaaa4..f6d362c772 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,6 +16,44 @@ def redirect_with_vary(path)
 end
 
 Rails.application.routes.draw do
+  # Paths of routes on the web app that to not require to be indexed or
+  # have alternative format representations requiring separate controllers
+  web_app_paths = %w(
+    /getting-started
+    /keyboard-shortcuts
+    /home
+    /public
+    /public/local
+    /public/local/fixed
+    /public/remote
+    /conversations
+    /lists/(*any)
+    /antennasw/(*any)
+    /antennast/(*any)
+    /circles/(*any)
+    /links/(*any)
+    /notifications/(*any)
+    /notifications_v2/(*any)
+    /favourites
+    /emoji_reactions
+    /bookmarks
+    /bookmark_categories/(*any)
+    /pinned
+    /reaction_deck
+    /start/(*any)
+    /directory
+    /explore/(*any)
+    /search
+    /publish
+    /follow_requests
+    /blocks
+    /domain_blocks
+    /mutes
+    /followed_tags
+    /statuses/(*any)
+    /deck/(*any)
+  ).freeze
+
   root 'home#index'
 
   mount LetterOpenerWeb::Engine, at: 'letter_opener' if Rails.env.development?
@@ -33,13 +71,6 @@ Rails.application.routes.draw do
                 tokens: 'oauth/tokens'
   end
 
-  namespace :oauth do
-    # As this is borrowed from OpenID, the specification says we must also support
-    # POST for the userinfo endpoint:
-    # https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
-    match 'userinfo', via: [:get, :post], to: 'userinfo#show', defaults: { format: 'json' }
-  end
-
   scope path: '.well-known' do
     scope module: :well_known do
       get 'oauth-authorization-server', to: 'oauth_metadata#show', as: :oauth_metadata, defaults: { format: 'json' }
@@ -55,9 +86,7 @@ Rails.application.routes.draw do
 
   get 'manifest', to: 'manifests#show', defaults: { format: 'json' }
   get 'intent', to: 'intents#show'
-  get 'custom.css', to: 'custom_css#show'
-  resources :custom_css, only: :show, path: :css
-  get 'system.css', to: 'system_css#show', as: :system_css
+  get 'custom.css', to: 'custom_css#show', as: :custom_css
   get 'user_custom.css', to: 'user_custom_css#show', as: :user_custom_css
 
   get 'remote_interaction_helper', to: 'remote_interaction_helper#index'
@@ -133,7 +162,6 @@ Rails.application.routes.draw do
   constraints(username: %r{[^@/.]+}) do
     with_options to: 'accounts#show' do
       get '/@:username', as: :short_account
-      get '/@:username/featured'
       get '/@:username/with_replies', as: :short_account_with_replies
       get '/@:username/media', as: :short_account_media
       get '/@:username/tagged/:tag', as: :short_account_tag
@@ -177,6 +205,7 @@ Rails.application.routes.draw do
       end
     end
   end
+  resources :antennas, except: [:show]
 
   resource :relationships, only: [:show, :update]
   resources :severed_relationships, only: [:index] do
@@ -201,18 +230,16 @@ Rails.application.routes.draw do
 
   draw(:api)
 
-  draw(:fasp)
-
-  draw(:web_app)
+  web_app_paths.each do |path|
+    get path, to: 'home#index'
+  end
 
   get '/web/(*any)', to: redirect('/%{any}', status: 302), as: :web, defaults: { any: '' }, format: false
   get '/about',      to: 'about#show'
   get '/about/more', to: redirect('/about')
 
-  get '/privacy-policy',   to: 'privacy#show', as: :privacy_policy
-  get '/terms-of-service', to: 'terms_of_service#show', as: :terms_of_service
-  get '/terms-of-service/:date', to: 'terms_of_service#show', as: :terms_of_service_version
-  get '/terms', to: redirect('/terms-of-service')
+  get '/privacy-policy', to: 'privacy#show', as: :privacy_policy
+  get '/terms',          to: redirect('/privacy-policy')
 
   match '/', via: [:post, :put, :patch, :delete], to: 'application#raise_not_found', format: false
   match '*unmatched_route', via: :all, to: 'application#raise_not_found', format: false
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index b829d0b9d8..9f6da976dd 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -48,33 +48,15 @@ namespace :admin do
   resource :special_instances, only: [:show, :create]
   resource :special_domains, only: [:show, :create]
 
-  namespace :terms_of_service do
-    resource :generate, only: [:show, :create]
-    resource :history, only: [:show]
-    resource :draft, only: [:show, :update]
-  end
-
-  resources :terms_of_service, only: [:index] do
-    resource :preview, only: [:show], module: :terms_of_service
-    resource :test, only: [:create], module: :terms_of_service
-    resource :distribution, only: [:create], module: :terms_of_service
-  end
-
   resources :announcements, except: [:show] do
     member do
       post :publish
       post :unpublish
     end
-
-    resource :preview, only: [:show], module: :announcements
-    resource :test, only: [:create], module: :announcements
-    resource :distribution, only: [:create], module: :announcements
   end
 
-  with_options to: redirect('/admin/settings/branding') do
-    get '/settings'
-    get '/settings/edit'
-  end
+  get '/settings', to: redirect('/admin/settings/branding')
+  get '/settings/edit', to: redirect('/admin/settings/branding')
 
   namespace :settings do
     resource :branding, only: [:show, :update], controller: 'branding'
diff --git a/config/routes/api.rb b/config/routes/api.rb
index c8233bb01d..23868652dd 100644
--- a/config/routes/api.rb
+++ b/config/routes/api.rb
@@ -54,10 +54,8 @@ namespace :api, format: false do
       resources :antenna, only: :show
     end
 
-    with_options to: 'streaming#index' do
-      get '/streaming'
-      get '/streaming/(*any)'
-    end
+    get '/streaming', to: 'streaming#index'
+    get '/streaming/(*any)', to: 'streaming#index'
 
     resources :custom_emojis, only: [:index]
     resources :reaction_deck, only: [:index, :create]
@@ -65,7 +63,7 @@ namespace :api, format: false do
     resources :scheduled_statuses, only: [:index, :show, :update, :destroy]
     resources :preferences, only: [:index]
 
-    resources :annual_reports, only: [:index, :show] do
+    resources :annual_reports, only: [:index] do
       member do
         post :read
       end
@@ -88,7 +86,7 @@ namespace :api, format: false do
       end
     end
 
-    resources :media, only: [:create, :update, :show, :destroy]
+    resources :media, only: [:create, :update, :show]
     resources :blocks, only: [:index]
     resources :mutes, only: [:index]
     resources :favourites, only: [:index]
@@ -128,13 +126,10 @@ namespace :api, format: false do
         resources :rules, only: [:index]
         resources :domain_blocks, only: [:index]
         resource :privacy_policy, only: [:show]
-        resource :terms_of_service, only: [:show]
         resource :extended_description, only: [:show]
         resource :translation_languages, only: [:show]
         resource :languages, only: [:show]
         resource :activity, only: [:show], controller: :activity
-
-        get '/terms_of_service/:date', to: 'terms_of_services#show'
       end
     end
 
@@ -205,7 +200,6 @@ namespace :api, format: false do
         resources :circles, only: :index
         resources :identity_proofs, only: :index
         resources :featured_tags, only: :index
-        resources :endorsements, only: :index
       end
 
       member do
@@ -219,10 +213,8 @@ namespace :api, format: false do
       end
 
       scope module: :accounts do
-        post :pin, to: 'endorsements#create'
-        post :endorse, to: 'endorsements#create'
-        post :unpin, to: 'endorsements#destroy'
-        post :unendorse, to: 'endorsements#destroy'
+        resource :pin, only: :create
+        post :unpin, to: 'pins#destroy'
         resource :note, only: :create
       end
     end
@@ -238,11 +230,6 @@ namespace :api, format: false do
 
     resources :lists, only: [:index, :create, :show, :update, :destroy] do
       resource :accounts, only: [:show, :create, :destroy], module: :lists
-
-      member do
-        post :favourite
-        post :unfavourite
-      end
     end
 
     resources :antennas, only: [:index, :create, :show, :update, :destroy] do
@@ -271,7 +258,7 @@ namespace :api, format: false do
 
     resources :featured_tags, only: [:index, :create, :destroy]
 
-    resources :polls, only: [:show] do
+    resources :polls, only: [:create, :show] do
       resources :votes, only: :create, module: :polls
     end
 
@@ -394,7 +381,7 @@ namespace :api, format: false do
   namespace :web do
     resource :settings, only: [:update]
     resources :embeds, only: [:show]
-    resources :push_subscriptions, only: [:create, :destroy] do
+    resources :push_subscriptions, only: [:create] do
       member do
         put :update
       end
diff --git a/config/routes/fasp.rb b/config/routes/fasp.rb
deleted file mode 100644
index 9d052526de..0000000000
--- a/config/routes/fasp.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-namespace :api, format: false do
-  namespace :fasp do
-    namespace :debug do
-      namespace :v0 do
-        namespace :callback do
-          resources :responses, only: [:create]
-        end
-      end
-    end
-
-    resource :registration, only: [:create]
-  end
-end
-
-namespace :admin do
-  namespace :fasp do
-    namespace :debug do
-      resources :callbacks, only: [:index, :destroy]
-    end
-
-    resources :providers, only: [:index, :show, :edit, :update, :destroy] do
-      resources :debug_calls, only: [:create]
-
-      resource :registration, only: [:new, :create]
-    end
-  end
-end
diff --git a/config/routes/web_app.rb b/config/routes/web_app.rb
deleted file mode 100644
index 84a7394760..0000000000
--- a/config/routes/web_app.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-# Paths handled by the React application, which do not:
-# - Require indexing
-# - Have alternative format representations
-
-%w(
-  /antennas/(*any)
-  /blocks
-  /bookmarks
-  /bookmark_categories/(*any)
-  /circles/(*any)
-  /conversations
-  /deck/(*any)
-  /directory
-  /domain_blocks
-  /emoji_reactions
-  /explore/(*any)
-  /favourites
-  /follow_requests
-  /followed_tags
-  /getting-started
-  /home
-  /keyboard-shortcuts
-  /links/(*any)
-  /lists/(*any)
-  /mutes
-  /notifications_v2/(*any)
-  /notifications/(*any)
-  /pinned
-  /public
-  /public/local
-  /public/local/fixed
-  /public/remote
-  /publish
-  /reaction_deck
-  /search
-  /start/(*any)
-  /statuses/(*any)
-).each { |path| get path, to: 'home#index' }
diff --git a/config/settings.yml b/config/settings.yml
index 2d8b75148f..3b8a477a26 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -27,27 +27,14 @@ defaults: &defaults
   trends_as_landing_page: true
   trendable_by_default: false
   reserved_usernames:
-    - abuse
-    - account
-    - accounts
     - admin
-    - administration
-    - administrator
-    - admins
+    - support
     - help
-    - helpdesk
-    - instance
+    - root
+    - webmaster
+    - administrator
     - mod
     - moderator
-    - moderators
-    - mods
-    - owner
-    - root
-    - security
-    - server
-    - staff
-    - support
-    - webmaster
   disallowed_hashtags: # space separated string or list of hashtags without the hash
   bootstrap_timeline_accounts: ''
   activity_api_enabled: true
@@ -69,7 +56,6 @@ defaults: &defaults
   stranger_mention_from_local_ng: false
   enable_local_timeline: true
   emoji_reaction_disallow_domains: []
-  allow_referer_origin: false
 
 development:
   <<: *defaults
diff --git a/app/views/custom_css/show_system.css.erb b/config/storage.yml
similarity index 100%
rename from app/views/custom_css/show_system.css.erb
rename to config/storage.yml
diff --git a/config/templates/terms-of-service.md b/config/templates/terms-of-service.md
deleted file mode 100644
index b2e32f76b9..0000000000
--- a/config/templates/terms-of-service.md
+++ /dev/null
@@ -1,278 +0,0 @@
-## Introduction
-
-These terms of service (the "Terms") cover your access and use of Server
-Operator's ("Administrator", "we", or "us") instance, located at %{domain} (the
-"Instance"). These Terms apply solely to your use of the Instance as operated
-by the Administrator. Please note that we have no affiliation with Mastodon
-gGmbH (“Mastodon”) and these Terms do not contain any representations or
-warranties or other promises from Mastodon about your use of the Instance. If
-you would like to contact us for any reason, please direct all questions,
-comments, concerns and notices to us by following the instructions provided in
-the Notice section below.
-
-Please read these Terms carefully before using the Instance as they contain
-important information about your interactions with the Instance. We may have
-other policies that apply to your use of the Instance and that are incorporated
-into these Terms. You should also read these policies before using the Instance.
-
-## Age Requirements and Responsibility of Parents and Legal Guardians
-
-By accessing the Instance, you signify that you are at least %{min_age} years old
-and that you meet the minimum age required by the laws in your country. If you
-are old enough to access the Instance in your country, but are not old enough to
-have the legal authority to consent to our Terms, please ask your parent or
-legal guardian to read these Terms with you, as they must agree to the Terms on
-your behalf. If you are a parent or legal guardian who has accepted these terms
-on your child's behalf, these terms apply to you and you are responsible for
-your child's activities on the Instance.
-
-## Prohibited Uses
-
-You are fully responsible for your activities while using the Instance,
-including any content, information or other materials you post or upload to the
-Instance, and you bear all risks associated with use of the Instance. By
-agreeing to these Terms, you agree to comply with all applicable federal, state,
-and local laws and regulations in connection with your use of the Instance. You
-also agree not to use the Instance to engage in any prohibited conduct, or to
-assist any other person or entity in engaging in any prohibited conduct.
-
-We reserve the right (but do not have the obligation) in our sole discretion to:
-(1) monitor the Instance for violations of these Terms; (2) take appropriate
-legal action against anyone who uses or accesses the Instance in a manner that
-we believe violates the law or these Terms, including without limitation,
-reporting such user to law enforcement authorities; (3) deny access to the
-Instance or any features of the Instance to anyone who violates these Terms or
-who we believe interferes with the ability of others to enjoy our Instance or
-infringes the rights of others; and (4) otherwise manage the Instance in a
-manner designed to protect our rights and property and to facilitate the proper
-functioning of the Instance.
-
-You are prohibited from using the Instance for the commission of harmful or
-illegal activities. Accordingly, you may not, or assist any other person to (or
-attempt to):
-
-- Violate these Terms or other policies and terms posted on, or otherwise
-  applicable to, the Instance;
-- Upload any material, program, or software that contains any virus, worm,
-  spyware, Trojan horse or other program or code designed to interrupt, destroy
-  or limit the functionality of the Instance, launch a denial of service attack,
-  or in any other way attempt to interfere with the functioning and availability
-  of the Instance;
-- Use, launch, develop, or distribute any automated system, including without
-  limitation, any spider, robot, cheat utility, scraper, offline reader, or any
-  data mining or similar data gathering extraction tools to access the Instance,
-  except in each case as may be the result of standard search engine or Internet
-  browser and local caching or for human review and interaction with Content on
-  the Instance;
-- Use or launch any unauthorized script or other software;
-- Interfere with, disable, vandalize or disrupt the Instance or servers or
-  networks connected to the Instance;
-- Hack into, penetrate, disable, or otherwise circumvent the security measures
-  of the Instance or servers or networks connected to the Instance;
-- Otherwise use the Instance in any way that violates any applicable
-  national, federal, state, local or international law or regulation.
-
-## Intellectual Property
-
-The Instance contains content provided by its users, including you, such as
-text, photos, videos, audio, links, and streams (“Content”). When you submit
-Content to the Instance, you represent and warrant that you have all of the
-rights, power, and authority necessary to grant the rights to the Content
-contained within these Terms. Because you alone are responsible for the Content
-that you submit to the Instance, you may expose yourself to liability from third
-parties if you post or share such Content without all necessary rights.
-
-You retain all ownership rights you have in the Content that you submit to the
-Instance, but you grant us a limited, non-exclusive, irrevocable, transferable,
-royalty-free, perpetual license to use, copy, store, display, share, distribute,
-communicate and transfer the Content in ways that are consistent with your use
-of the Instance. To the fullest extent possible, you agree to waive or promise
-not to assert against the Administrator all moral rights you may have in the
-Content to the extent those rights are necessary for the Administrator to host
-the Content on the Instance.
-
-## DMCA Copyright Infringement Notice
-
-We have implemented the procedures described in the Digital Millennium Copyright
-Act of 1998 ("DMCA"), 17 U.S.C. § 512 , regarding the reporting of alleged
-copyright infringement and the removal of or disabling access to infringing
-material. If you have a good faith belief that copyrighted material on the
-Instance is being used in a way that infringes a copyright over which you are
-authorized to act, you may make a Notice of Infringing Material. If you have a
-good faith belief that copyrighted material that was removed or access to which
-was disabled was a result of a mistake or misidentification, then you may make a
-Notice of Counter-Notification.
-
-Before serving a Notice of Infringing Material or Counter-Notification, you may
-wish to contact a lawyer to better understand your rights and obligations under
-the DMCA and other applicable laws. For example, if your Notice or
-Counter-Notifications fails to comply with all requirements of sections
-512(c)(3) or 512(g)(3), respectively, your Notice or Counter-Notification may
-not be effective.
-
-### Termination of Repeat Infringers
-
-We will terminate or disable your use of the Instance in appropriate
-circumstances if you are deemed by us to be a repeat copyright infringer.
-
-### Notices and Counter-Notifications must be sent to:
-
-DMCA Agent: Copyright Manager
-
-Address: %{dmca_address}
-
-Email: %{dmca_email}
-
-## Disclaimer
-
-Administrator reserves the right in our sole discretion to modify or
-discontinue, temporarily or permanently, the Instance (or any part thereof) with
-or without notice to you. You agree that Administrator will not be liable to
-you or to any third party for any modification or discontinuance of the
-Instance, except as set forth in the "Limitation of Liability" section below.
-
-You understand that we are not responsible for any activities or legal
-consequences of your use of the Instance. Users are responsible for using the
-Instance in compliance with all applicable laws and regulations of the
-jurisdictions in which such users are domiciled, reside, or are located at the
-time of such access or use, as well as these Terms. Any violation of these
-Terms may result in the suspension or termination by us, in our sole discretion,
-of your access to and use of the Instance.
-
-## Limitation of Liability
-
-In no event will Administrator's total liability to you for all damages, losses,
-or causes of action exceed one hundred dollars ($100). If you are dissatisfied
-with the Instance or with these Terms, your sole remedy is to discontinue your
-use of the Instance.
-
-## Links to and From Other Websites
-
-You may gain access to other websites and Instances via links on the Instance.
-These Terms apply to the Instance only and do not apply to Mastodon, other
-Instances, or other parties' websites. Similarly, you may have come to the
-Instance via a link from another website or Instance. The terms of use of other
-websites and Instances do not apply to the Instance. Administrator assumes no
-responsibility for any terms of use or material outside of the Instance accessed
-via any link. You are free to establish a hypertext link to the Instance so
-long as the link does not state or imply any sponsorship of your website,
-instance or service by Administrator or the Instance. Unless expressly agreed
-to by us in writing, reference to any of our products, services, processes or
-other information, by trade name, trademark, logo, or otherwise by you or any
-third party does not constitute or imply endorsement, sponsorship or
-recommendation thereof by us. You may not, without our prior written
-permission, scrape the Instance or incorporate into another website or other
-service any of our material, content or intellectual property, unless you are
-otherwise permitted by us to do so in accordance with a license or subject to
-separate terms.
-
-## Dispute Resolution by Binding Arbitration
-
-### Agreement to Arbitrate:
-
-This Dispute Resolution by Binding Arbitration section is referred to in these
-Terms as the “Arbitration Agreement.” You and the Instance agree that any and
-all disputes, claims, demands, or causes of action (“Claims”) that have arisen
-or may arise between you and us, whether arising out of or relating to these
-Terms, the website, or any aspect of the relationship or transactions between
-us, will be resolved exclusively through final and binding arbitration before a
-neutral arbitrator, rather than in a court by a judge or jury, in accordance
-with the terms of this Arbitration Agreement, except that, where available, you
-or we may (but are not required to) assert individual Claims in small claims
-court if such Claims are within the scope of such court's jurisdiction. Further,
-this Arbitration Agreement does not preclude you from bringing issues to the
-attention of federal, state/provincial, or local agencies, and such agencies
-can, if the law allows, seek relief against us on your behalf. You agree that,
-by entering into these Terms, you and we are each waiving the right to a trial
-by jury or to participate in a class action and that our respective rights will
-be determined by a neutral arbitrator, not a judge or jury.
-
-### Prohibition of Class and Representative Actions and Non-Individualized
-
-### Relief
-
-You and we agree that each of us may bring claims against the other only on an
-individual basis and not as a plaintiff or class member in any purported class
-or representative action or proceeding.
-
-### Pre-Arbitration Dispute Resolution
-
-Before commencing any arbitration (or suit in small claims court, if available),
-you agree to provide the Instance with a written notice of Claim, and the
-Instance agrees to provide you with a written notice of Claim to the extent
-reasonably possible based on the availability of your contact information to the
-Instance (“Notice”). The Notice to the Instance shall be sent to
-%{arbitration_website} with a paper copy to %{arbitration_address}. Where the
-Instance has your contact information, the Instance will send its Notice to you
-using the last email address we have on file for you if you have provided us
-with an email address (each, a “Notice Address”). The Notice must (i) describe
-the nature and basis of the Claim in sufficient detail to evaluate the merits of
-the claiming party's Claim and (ii) set forth the specific relief sought,
-including the amount of money (if any) that is demanded and the means by which
-the demanding party calculated the claimed amount. Both parties agree that they
-will attempt to resolve a Claim through informal negotiation within sixty (60)
-calendar days from the date the Notice is received. If the Claim is not resolved
-within sixty (60) calendar days after the Notice is received, you or we may
-commence an arbitration proceeding. Each party agrees that %{jurisdiction}
-courts may enter injunctive relief to enforce the pre-filing requirements of
-this paragraph, including an injunction to stay an arbitration that has been
-commenced in violation of this paragraph.
-
-### Arbitration Procedures
-
-The relevant arbitration rules of %{jurisdiction} fully applies to the
-Arbitration Agreement. The arbitration will be conducted by a neutral arbitrator
-in accordance with %{jurisdiction} rules (the “Rules”), as modified by this
-Arbitration Agreement. If there is any inconsistency between any term of the
-Rules and any term of this Arbitration Agreement, the applicable terms of this
-Arbitration Agreement will control. The arbitrator must also follow the
-provisions of these Terms as a court would. Except as set forth above, all
-issues are for the arbitrator to decide, including, but not limited to,
-threshold issues relating to the scope, enforceability, and arbitrability of
-this Arbitration Agreement and issues relating to (a) whether the terms of these
-Terms (or any aspect thereof) are enforceable, unconscionable, or illusory and
-(b) any defense to arbitration, including waiver, delay, laches, or estoppel.
-Regardless of the manner in which the arbitration is conducted, the arbitrator
-will issue a reasoned written decision sufficient to explain the essential
-findings and conclusions on which the award is based. Payment of all filing,
-administration and arbitrator fees (collectively, the “Arbitration Fees”) will
-be governed by the Rules unless otherwise provided in this Arbitration
-Agreement.
-
-### Small Claims Court
-
-Subject to applicable jurisdictional requirements, either party may elect to
-pursue a Claim in a local small claims court rather than through arbitration so
-long as the matter remains in a small claims court and proceeds only on an
-individual basis.
-
-## Choice of Law
-
-Any and all claims related to or arising out of your use of, or access to the
-Instance shall be governed by internal substantive laws of %{choice_of_law} in all
-respects, without regard for the jurisdiction or forum in which you are
-domiciled, reside, or located at the time of such access or use.
-
-## Waiver and Severability
-
-If you do not comply with a portion of these Terms and we do not take action
-right away, this does not mean we are giving up any of our rights under these
-Terms. If any part of these Terms is determined to be invalid or unenforceable
-by a court of competent jurisdiction or arbitrator, the remainder of the Terms
-shall be enforced to the maximum extent permitted by law.
-
-## Notices
-
-All notices to Administrator under these Terms, unless otherwise specified shall
-be sent to %{admin_email}. Service of any notice will be deemed given on the
-date of receipt delivered by email.
-
-## Changes to these Terms
-
-We may change or modify these Terms by posting a revised version on the
-Instance, or by otherwise providing notice to you, and will state at the top of
-the revised Terms the date they were last revised. Changes will not apply
-retroactively and will become effective no earlier than fourteen (14) calendar
-days after they are posted, except for changes addressing changes made for legal
-reasons, which will be effective immediately. Your continued use of the
-Instance after any change means you agree to the new Terms.
diff --git a/config/translation.yml b/config/translation.yml
deleted file mode 100644
index e074c5d0f2..0000000000
--- a/config/translation.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-shared:
-  deepl:
-    api_key: <%= ENV.fetch('DEEPL_API_KEY', nil) %>
-    plan: <%= ENV.fetch('DEEPL_PLAN', 'free') %>
-  libre_translate:
-    api_key: <%= ENV.fetch('LIBRE_TRANSLATE_API_KEY', nil) %>
-    endpoint: <%= ENV.fetch('LIBRE_TRANSLATE_ENDPOINT', nil) %>
diff --git a/config/webpack/production.js b/config/webpack/production.js
index c9c607c68e..7f1ee4a8f9 100644
--- a/config/webpack/production.js
+++ b/config/webpack/production.js
@@ -48,7 +48,7 @@ module.exports = merge(sharedConfig, {
       logLevel: 'silent', // do not bother Webpacker, who runs with --json and parses stdout
     }),
     new InjectManifest({
-      additionalManifestEntries: ['1f602.svg', 'sheet_15.png'].map((filename) => {
+      additionalManifestEntries: ['1f602.svg', 'sheet_13.png'].map((filename) => {
         const path = resolve(root, 'public', 'emoji', filename);
         const body = readFileSync(path);
         const md5  = createHash('md5');
diff --git a/config/webpack/rules/babel.js b/config/webpack/rules/babel.js
index 76e41f3df0..902b823e1f 100644
--- a/config/webpack/rules/babel.js
+++ b/config/webpack/rules/babel.js
@@ -4,7 +4,7 @@ const { env, settings } = require('../configuration');
 
 // Those modules contain modern ES code that need to be transpiled for Webpack to process it
 const nodeModulesToProcess = [
-  '@reduxjs', 'fuzzysort', 'toygrad', '@react-spring'
+  '@reduxjs', 'fuzzysort'
 ];
 
 module.exports = {
diff --git a/config/webpack/rules/tesseract.js b/config/webpack/rules/tesseract.js
index 746fa55dfe..de647c8d10 100644
--- a/config/webpack/rules/tesseract.js
+++ b/config/webpack/rules/tesseract.js
@@ -1,8 +1,9 @@
 module.exports = {
   test: [
     /tesseract\.js\/dist\/worker\.min\.js$/,
-    /tesseract\.js\/dist\/worker\.min\.js\.map$/,
-    /tesseract\.js-core\/tesseract-core\.wasm\.js$/,
+    /tesseract\.js\/dist\/worker\.min\.js.map$/,
+    /tesseract\.js-core\/tesseract-core\.wasm$/,
+    /tesseract\.js-core\/tesseract-core\.wasm.js$/,
   ],
   use: {
     loader: 'file-loader',
diff --git a/config/webpacker.yml b/config/webpacker.yml
index ac4bca2916..e07f577c5e 100644
--- a/config/webpacker.yml
+++ b/config/webpacker.yml
@@ -13,8 +13,8 @@ default: &default
   # ['app/assets', 'engine/foo/app/assets']
   resolved_paths: []
 
-  # Cache manifest.json for performance
-  cache_manifest: true
+  # Reload manifest.json on all requests so we reload latest compiled packs
+  cache_manifest: false
 
   # Extract and emit a css file
   extract_css: true
@@ -55,9 +55,6 @@ development:
 
   compile: true
 
-  # Reload manifest in development environment so we pick up changes
-  cache_manifest: false
-
   # Reference: https://webpack.js.org/configuration/dev-server/
   dev_server:
     https: false
@@ -92,3 +89,6 @@ production:
 
   # Production depends on precompilation of packs prior to booting for performance.
   compile: false
+
+  # Cache manifest.json for performance
+  cache_manifest: true
diff --git a/db/migrate/.rubocop.yml b/db/migrate/.rubocop.yml
new file mode 100644
index 0000000000..6f8b6cc60d
--- /dev/null
+++ b/db/migrate/.rubocop.yml
@@ -0,0 +1,9 @@
+inherit_from: ../../.rubocop.yml
+
+Naming/VariableNumber:
+  CheckSymbols: false
+
+# Enabled here as workaround for https://docs.rubocop.org/rubocop/configuration.html#path-relativity
+Rails/CreateTableWithTimestamps:
+  Include:
+    - '*.rb'
diff --git a/db/migrate/20160325130944_add_admin_to_users.rb b/db/migrate/20160325130944_add_admin_to_users.rb
index a78d08a156..6b701ebcc8 100644
--- a/db/migrate/20160325130944_add_admin_to_users.rb
+++ b/db/migrate/20160325130944_add_admin_to_users.rb
@@ -2,6 +2,6 @@
 
 class AddAdminToUsers < ActiveRecord::Migration[4.2]
   def change
-    add_column :users, :admin, :boolean, default: false # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :users, :admin, :boolean, default: false
   end
 end
diff --git a/db/migrate/20161123093447_add_sensitive_to_statuses.rb b/db/migrate/20161123093447_add_sensitive_to_statuses.rb
index 93388126e9..7487b2ee33 100644
--- a/db/migrate/20161123093447_add_sensitive_to_statuses.rb
+++ b/db/migrate/20161123093447_add_sensitive_to_statuses.rb
@@ -2,6 +2,6 @@
 
 class AddSensitiveToStatuses < ActiveRecord::Migration[5.0]
   def change
-    add_column :statuses, :sensitive, :boolean, default: false # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :statuses, :sensitive, :boolean, default: false
   end
 end
diff --git a/db/migrate/20170112154826_migrate_settings.rb b/db/migrate/20170112154826_migrate_settings.rb
index 166d90eab7..d1faa81f5d 100644
--- a/db/migrate/20170112154826_migrate_settings.rb
+++ b/db/migrate/20170112154826_migrate_settings.rb
@@ -17,7 +17,7 @@ class MigrateSettings < ActiveRecord::Migration[4.2]
       t.remove_index [:thing_type, :thing_id, :var]
       t.rename :thing_id, :target_id
       t.rename :thing_type, :target_type
-      t.column :target_id, :integer, null: false # rubocop:disable Rails/NotNullColumn
+      t.column :target_id, :integer, null: false
       t.column :target_type, :string, null: false, default: ''
       t.index [:target_type, :target_id, :var], unique: true
     end
diff --git a/db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb b/db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb
index d05be7673e..5282602ce9 100644
--- a/db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb
+++ b/db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb
@@ -2,6 +2,6 @@
 
 class AddRejectMediaToDomainBlocks < ActiveRecord::Migration[5.0]
   def change
-    add_column :domain_blocks, :reject_media, :boolean # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :domain_blocks, :reject_media, :boolean
   end
 end
diff --git a/db/migrate/20170127165745_add_devise_two_factor_to_users.rb b/db/migrate/20170127165745_add_devise_two_factor_to_users.rb
index 5e29e2f003..0f60758d3b 100644
--- a/db/migrate/20170127165745_add_devise_two_factor_to_users.rb
+++ b/db/migrate/20170127165745_add_devise_two_factor_to_users.rb
@@ -7,7 +7,7 @@ class AddDeviseTwoFactorToUsers < ActiveRecord::Migration[5.0]
       t.column :encrypted_otp_secret_iv, :string
       t.column :encrypted_otp_secret_salt, :string
       t.column :consumed_timestep, :integer
-      t.column :otp_required_for_login, :boolean # rubocop:disable Rails/ThreeStateBooleanColumn
+      t.column :otp_required_for_login, :boolean
     end
   end
 end
diff --git a/db/migrate/20170205175257_remove_devices.rb b/db/migrate/20170205175257_remove_devices.rb
index d4af5ab613..643e196bf3 100644
--- a/db/migrate/20170205175257_remove_devices.rb
+++ b/db/migrate/20170205175257_remove_devices.rb
@@ -1,11 +1,7 @@
 # frozen_string_literal: true
 
 class RemoveDevices < ActiveRecord::Migration[5.0]
-  def up
+  def change
     drop_table :devices if table_exists?(:devices)
   end
-
-  def down
-    raise ActiveRecord::IrreversibleMigration
-  end
 end
diff --git a/db/migrate/20170209184350_add_reply_to_statuses.rb b/db/migrate/20170209184350_add_reply_to_statuses.rb
index 030ceb4db0..ffef97609d 100644
--- a/db/migrate/20170209184350_add_reply_to_statuses.rb
+++ b/db/migrate/20170209184350_add_reply_to_statuses.rb
@@ -2,7 +2,7 @@
 
 class AddReplyToStatuses < ActiveRecord::Migration[5.0]
   def up
-    add_column :statuses, :reply, :boolean, default: false # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :statuses, :reply, :boolean, nil: false, default: false
     Status.unscoped.update_all('reply = (in_reply_to_id IS NOT NULL)')
   end
 
diff --git a/db/migrate/20170304202101_add_type_to_media_attachments.rb b/db/migrate/20170304202101_add_type_to_media_attachments.rb
index 8f863fba40..e49d87fc74 100644
--- a/db/migrate/20170304202101_add_type_to_media_attachments.rb
+++ b/db/migrate/20170304202101_add_type_to_media_attachments.rb
@@ -3,7 +3,7 @@
 class AddTypeToMediaAttachments < ActiveRecord::Migration[5.0]
   class MigrationMediaAttachment < ApplicationRecord
     self.table_name = :media_attachments
-    enum :type, [:image, :gifv, :video]
+    enum type: [:image, :gifv, :video]
     IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
     VIDEO_MIME_TYPES = ['video/webm', 'video/mp4'].freeze
   end
diff --git a/db/migrate/20170330163835_create_imports.rb b/db/migrate/20170330163835_create_imports.rb
index 7548a376d4..6bd9e28b5b 100644
--- a/db/migrate/20170330163835_create_imports.rb
+++ b/db/migrate/20170330163835_create_imports.rb
@@ -5,7 +5,7 @@ class CreateImports < ActiveRecord::Migration[5.0]
     create_table :imports do |t|
       t.integer :account_id, null: false
       t.integer :type, null: false
-      t.boolean :approved # rubocop:disable Rails/ThreeStateBooleanColumn
+      t.boolean :approved
 
       t.timestamps
     end
diff --git a/db/migrate/20170711225116_fix_null_booleans.rb b/db/migrate/20170711225116_fix_null_booleans.rb
index 7b0ee32293..12cc9b8b86 100644
--- a/db/migrate/20170711225116_fix_null_booleans.rb
+++ b/db/migrate/20170711225116_fix_null_booleans.rb
@@ -3,10 +3,10 @@
 class FixNullBooleans < ActiveRecord::Migration[5.1]
   def change
     safety_assured do
-      change_column_default :domain_blocks, :reject_media, false # rubocop:disable Rails/ReversibleMigration
+      change_column_default :domain_blocks, :reject_media, false
       change_column_null :domain_blocks, :reject_media, false, false
 
-      change_column_default :imports, :approved, false # rubocop:disable Rails/ReversibleMigration
+      change_column_default :imports, :approved, false
       change_column_null :imports, :approved, false, false
 
       change_column_null :statuses, :sensitive, false, false
@@ -14,7 +14,7 @@ class FixNullBooleans < ActiveRecord::Migration[5.1]
 
       change_column_null :users, :admin, false, false
 
-      change_column_default :users, :otp_required_for_login, false # rubocop:disable Rails/ReversibleMigration
+      change_column_default :users, :otp_required_for_login, false
       change_column_null :users, :otp_required_for_login, false, false
     end
   end
diff --git a/db/migrate/20170905165803_add_local_to_statuses.rb b/db/migrate/20170905165803_add_local_to_statuses.rb
index 90f01cb373..cb6307c265 100644
--- a/db/migrate/20170905165803_add_local_to_statuses.rb
+++ b/db/migrate/20170905165803_add_local_to_statuses.rb
@@ -2,6 +2,6 @@
 
 class AddLocalToStatuses < ActiveRecord::Migration[5.1]
   def change
-    add_column :statuses, :local, :boolean, null: true, default: nil # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :statuses, :local, :boolean, null: true, default: nil
   end
 end
diff --git a/db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb b/db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb
index 34b7951551..4c3a25e838 100644
--- a/db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb
+++ b/db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb
@@ -5,7 +5,7 @@ class AddIndexOnStatusesForApiV1AccountsAccountIdStatuses < ActiveRecord::Migrat
 
   def change
     safety_assured do
-      add_index :statuses, [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106 # rubocop:disable Naming/VariableNumber
+      add_index :statuses, [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106
     end
     remove_index :statuses, [:account_id, :id], name: :index_statuses_on_account_id_id
   end
diff --git a/db/migrate/20180514140000_revert_index_change_on_statuses_for_api_v1_accounts_account_id_statuses.rb b/db/migrate/20180514140000_revert_index_change_on_statuses_for_api_v1_accounts_account_id_statuses.rb
index 317c48bb69..242ae74107 100644
--- a/db/migrate/20180514140000_revert_index_change_on_statuses_for_api_v1_accounts_account_id_statuses.rb
+++ b/db/migrate/20180514140000_revert_index_change_on_statuses_for_api_v1_accounts_account_id_statuses.rb
@@ -5,7 +5,7 @@ class RevertIndexChangeOnStatusesForApiV1AccountsAccountIdStatuses < ActiveRecor
 
   def change
     safety_assured do
-      add_index :statuses, [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106 unless index_name_exists?(:statuses, 'index_statuses_20180106') # rubocop:disable Naming/VariableNumber
+      add_index :statuses, [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106 unless index_name_exists?(:statuses, 'index_statuses_20180106')
     end
 
     # These index may not exists (see migration 20180514130000)
diff --git a/db/migrate/20181024224956_migrate_account_conversations.rb b/db/migrate/20181024224956_migrate_account_conversations.rb
index 643b3d46c4..d879fd88a2 100644
--- a/db/migrate/20181024224956_migrate_account_conversations.rb
+++ b/db/migrate/20181024224956_migrate_account_conversations.rb
@@ -21,7 +21,7 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2]
     belongs_to :account, class_name: 'MigrationAccount'
     has_many :mentions, dependent: :destroy, inverse_of: :status, class_name: 'MigrationMention', foreign_key: :status_id
     scope :local, -> { where(local: true).or(where(uri: nil)) }
-    enum :visibility, { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4 }, suffix: :visibility
+    enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4 }, _suffix: :visibility
     has_many :active_mentions, -> { active }, class_name: 'MigrationMention', inverse_of: :status, foreign_key: :status_id
   end
 
diff --git a/db/migrate/20181203021853_add_discoverable_to_accounts.rb b/db/migrate/20181203021853_add_discoverable_to_accounts.rb
index 333a2ce210..16576aa043 100644
--- a/db/migrate/20181203021853_add_discoverable_to_accounts.rb
+++ b/db/migrate/20181203021853_add_discoverable_to_accounts.rb
@@ -2,6 +2,6 @@
 
 class AddDiscoverableToAccounts < ActiveRecord::Migration[5.2]
   def change
-    add_column :accounts, :discoverable, :boolean # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :accounts, :discoverable, :boolean
   end
 end
diff --git a/db/migrate/20190509164208_add_by_moderator_to_tombstone.rb b/db/migrate/20190509164208_add_by_moderator_to_tombstone.rb
index 8dfa4cca4a..656cd0af58 100644
--- a/db/migrate/20190509164208_add_by_moderator_to_tombstone.rb
+++ b/db/migrate/20190509164208_add_by_moderator_to_tombstone.rb
@@ -2,6 +2,6 @@
 
 class AddByModeratorToTombstone < ActiveRecord::Migration[5.2]
   def change
-    add_column :tombstones, :by_moderator, :boolean # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :tombstones, :by_moderator, :boolean
   end
 end
diff --git a/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb b/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb
index 37812f2b54..cbb5fc67b4 100644
--- a/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb
+++ b/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb
@@ -7,7 +7,7 @@ class AddSilencedAtSuspendedAtToAccounts < ActiveRecord::Migration[5.2]
 
   class DomainBlock < ApplicationRecord
     # Dummy class, to make migration possible across version changes
-    enum :severity, [:silence, :suspend, :noop]
+    enum severity: [:silence, :suspend, :noop]
 
     has_many :accounts, foreign_key: :domain, primary_key: :domain
   end
diff --git a/db/migrate/20190805123746_add_capabilities_to_tags.rb b/db/migrate/20190805123746_add_capabilities_to_tags.rb
index 628617cb7e..949938bb1e 100644
--- a/db/migrate/20190805123746_add_capabilities_to_tags.rb
+++ b/db/migrate/20190805123746_add_capabilities_to_tags.rb
@@ -4,11 +4,9 @@ class AddCapabilitiesToTags < ActiveRecord::Migration[5.2]
   def change
     safety_assured do
       change_table(:tags, bulk: true) do |t|
-        # rubocop:disable Rails/ThreeStateBooleanColumn
         t.column :usable, :boolean
         t.column :trendable, :boolean
         t.column :listable, :boolean
-        # rubocop:enable Rails/ThreeStateBooleanColumn
         t.column :reviewed_at, :datetime
         t.column :requested_review_at, :datetime
       end
diff --git a/db/migrate/20190820003045_update_statuses_index.rb b/db/migrate/20190820003045_update_statuses_index.rb
index 4bd9c156db..df9ce0638f 100644
--- a/db/migrate/20190820003045_update_statuses_index.rb
+++ b/db/migrate/20190820003045_update_statuses_index.rb
@@ -4,12 +4,12 @@ class UpdateStatusesIndex < ActiveRecord::Migration[5.2]
   disable_ddl_transaction!
 
   def up
-    safety_assured { add_index :statuses, [:account_id, :id, :visibility, :updated_at], where: 'deleted_at IS NULL', order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20190820 } # rubocop:disable Naming/VariableNumber
-    remove_index :statuses, name: :index_statuses_20180106 # rubocop:disable Naming/VariableNumber
+    safety_assured { add_index :statuses, [:account_id, :id, :visibility, :updated_at], where: 'deleted_at IS NULL', order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20190820 }
+    remove_index :statuses, name: :index_statuses_20180106
   end
 
   def down
-    safety_assured { add_index :statuses, [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106 } # rubocop:disable Naming/VariableNumber
-    remove_index :statuses, name: :index_statuses_20190820 # rubocop:disable Naming/VariableNumber
+    safety_assured { add_index :statuses, [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106 }
+    remove_index :statuses, name: :index_statuses_20190820
   end
 end
diff --git a/db/migrate/20190823221802_add_local_index_to_statuses.rb b/db/migrate/20190823221802_add_local_index_to_statuses.rb
index 5cab5547e5..b5baa30287 100644
--- a/db/migrate/20190823221802_add_local_index_to_statuses.rb
+++ b/db/migrate/20190823221802_add_local_index_to_statuses.rb
@@ -4,10 +4,10 @@ class AddLocalIndexToStatuses < ActiveRecord::Migration[5.2]
   disable_ddl_transaction!
 
   def up
-    add_index :statuses, [:id, :account_id], name: :index_statuses_local_20190824, algorithm: :concurrently, order: { id: :desc }, where: '(local OR (uri IS NULL)) AND deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' # rubocop:disable Naming/VariableNumber
+    add_index :statuses, [:id, :account_id], name: :index_statuses_local_20190824, algorithm: :concurrently, order: { id: :desc }, where: '(local OR (uri IS NULL)) AND deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))'
   end
 
   def down
-    remove_index :statuses, name: :index_statuses_local_20190824 # rubocop:disable Naming/VariableNumber
+    remove_index :statuses, name: :index_statuses_local_20190824
   end
 end
diff --git a/db/migrate/20191212163405_add_hide_collections_to_accounts.rb b/db/migrate/20191212163405_add_hide_collections_to_accounts.rb
index 0cf1e9fee7..d6740f19b8 100644
--- a/db/migrate/20191212163405_add_hide_collections_to_accounts.rb
+++ b/db/migrate/20191212163405_add_hide_collections_to_accounts.rb
@@ -2,6 +2,6 @@
 
 class AddHideCollectionsToAccounts < ActiveRecord::Migration[5.2]
   def change
-    add_column :accounts, :hide_collections, :boolean # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :accounts, :hide_collections, :boolean
   end
 end
diff --git a/db/migrate/20200119112504_add_public_index_to_statuses.rb b/db/migrate/20200119112504_add_public_index_to_statuses.rb
index 21a361d5b6..6451b9254e 100644
--- a/db/migrate/20200119112504_add_public_index_to_statuses.rb
+++ b/db/migrate/20200119112504_add_public_index_to_statuses.rb
@@ -4,10 +4,10 @@ class AddPublicIndexToStatuses < ActiveRecord::Migration[5.2]
   disable_ddl_transaction!
 
   def up
-    add_index :statuses, [:id, :account_id], name: :index_statuses_public_20200119, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' # rubocop:disable Naming/VariableNumber
+    add_index :statuses, [:id, :account_id], name: :index_statuses_public_20200119, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))'
   end
 
   def down
-    remove_index :statuses, name: :index_statuses_public_20200119 # rubocop:disable Naming/VariableNumber
+    remove_index :statuses, name: :index_statuses_public_20200119
   end
 end
diff --git a/db/migrate/20200309150742_add_forwarded_to_reports.rb b/db/migrate/20200309150742_add_forwarded_to_reports.rb
index ba835ded3a..60db0167e3 100644
--- a/db/migrate/20200309150742_add_forwarded_to_reports.rb
+++ b/db/migrate/20200309150742_add_forwarded_to_reports.rb
@@ -2,6 +2,6 @@
 
 class AddForwardedToReports < ActiveRecord::Migration[5.2]
   def change
-    add_column :reports, :forwarded, :boolean # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :reports, :forwarded, :boolean
   end
 end
diff --git a/db/migrate/20210609202149_create_login_activities.rb b/db/migrate/20210609202149_create_login_activities.rb
index 4add47a725..f2da335997 100644
--- a/db/migrate/20210609202149_create_login_activities.rb
+++ b/db/migrate/20210609202149_create_login_activities.rb
@@ -6,7 +6,7 @@ class CreateLoginActivities < ActiveRecord::Migration[6.1]
       t.belongs_to :user, null: false, foreign_key: { on_delete: :cascade }
       t.string :authentication_method
       t.string :provider
-      t.boolean :success # rubocop:disable Rails/ThreeStateBooleanColumn
+      t.boolean :success
       t.string :failure_reason
       t.inet :ip
       t.string :user_agent
diff --git a/db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb b/db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb
index eaeba54473..8c5d9c368a 100644
--- a/db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb
+++ b/db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb
@@ -2,6 +2,6 @@
 
 class AddSkipSignInTokenToUsers < ActiveRecord::Migration[6.1]
   def change
-    add_column :users, :skip_sign_in_token, :boolean # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :users, :skip_sign_in_token, :boolean
   end
 end
diff --git a/db/migrate/20211031031021_create_preview_card_providers.rb b/db/migrate/20211031031021_create_preview_card_providers.rb
index 577cfc53b8..83255614b6 100644
--- a/db/migrate/20211031031021_create_preview_card_providers.rb
+++ b/db/migrate/20211031031021_create_preview_card_providers.rb
@@ -5,7 +5,7 @@ class CreatePreviewCardProviders < ActiveRecord::Migration[6.1]
     create_table :preview_card_providers do |t|
       t.string :domain, null: false, default: '', index: { unique: true }
       t.attachment :icon
-      t.boolean :trendable # rubocop:disable Rails/ThreeStateBooleanColumn
+      t.boolean :trendable
       t.datetime :reviewed_at
       t.datetime :requested_review_at
       t.timestamps
diff --git a/db/migrate/20211115032527_add_trendable_to_preview_cards.rb b/db/migrate/20211115032527_add_trendable_to_preview_cards.rb
index 561a4d91c0..21fc4ecf46 100644
--- a/db/migrate/20211115032527_add_trendable_to_preview_cards.rb
+++ b/db/migrate/20211115032527_add_trendable_to_preview_cards.rb
@@ -2,6 +2,6 @@
 
 class AddTrendableToPreviewCards < ActiveRecord::Migration[6.1]
   def change
-    add_column :preview_cards, :trendable, :boolean # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :preview_cards, :trendable, :boolean
   end
 end
diff --git a/db/migrate/20220202200743_add_trendable_to_accounts.rb b/db/migrate/20220202200743_add_trendable_to_accounts.rb
index ad97e1fd1e..539717b751 100644
--- a/db/migrate/20220202200743_add_trendable_to_accounts.rb
+++ b/db/migrate/20220202200743_add_trendable_to_accounts.rb
@@ -4,7 +4,7 @@ class AddTrendableToAccounts < ActiveRecord::Migration[6.1]
   def change
     safety_assured do
       change_table(:accounts, bulk: true) do |t|
-        t.column :trendable, :boolean # rubocop:disable Rails/ThreeStateBooleanColumn
+        t.column :trendable, :boolean
         t.column :reviewed_at, :datetime
         t.column :requested_review_at, :datetime
       end
diff --git a/db/migrate/20220202200926_add_trendable_to_statuses.rb b/db/migrate/20220202200926_add_trendable_to_statuses.rb
index 3eaf2d0bb0..5d101132b8 100644
--- a/db/migrate/20220202200926_add_trendable_to_statuses.rb
+++ b/db/migrate/20220202200926_add_trendable_to_statuses.rb
@@ -2,6 +2,6 @@
 
 class AddTrendableToStatuses < ActiveRecord::Migration[6.1]
   def change
-    add_column :statuses, :trendable, :boolean # rubocop:disable Rails/ThreeStateBooleanColumn
+    add_column :statuses, :trendable, :boolean
   end
 end
diff --git a/db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb b/db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb
index c3dfc786a5..55567d62e1 100644
--- a/db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb
+++ b/db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb
@@ -7,7 +7,7 @@ class AddOrderedMediaAttachmentIdsToStatusEdits < ActiveRecord::Migration[6.1]
         t.column :ordered_media_attachment_ids, :bigint, array: true
         t.column :media_descriptions, :text, array: true
         t.column :poll_options, :string, array: true
-        t.column :sensitive, :boolean # rubocop:disable Rails/ThreeStateBooleanColumn
+        t.column :sensitive, :boolean
       end
     end
   end
diff --git a/db/migrate/20220304195405_migrate_hide_network_preference.rb b/db/migrate/20220304195405_migrate_hide_network_preference.rb
index 996b7d78ab..0083e0422a 100644
--- a/db/migrate/20220304195405_migrate_hide_network_preference.rb
+++ b/db/migrate/20220304195405_migrate_hide_network_preference.rb
@@ -13,13 +13,6 @@ class MigrateHideNetworkPreference < ActiveRecord::Migration[6.1]
     belongs_to :account
   end
 
-  class Setting < ApplicationRecord
-    # Mirror the behavior of the `Setting` model at this point in db history
-    def value
-      YAML.safe_load(self[:value], permitted_classes: [ActiveSupport::HashWithIndifferentAccess, Symbol]) if self[:value].present?
-    end
-  end
-
   def up
     Account.reset_column_information
 
diff --git a/db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb b/db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb
index b3b5ed3894..e794824afc 100644
--- a/db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb
+++ b/db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb
@@ -2,6 +2,6 @@
 
 class ChangeCanonicalEmailBlocksNullable < ActiveRecord::Migration[6.1]
   def change
-    safety_assured { change_column :canonical_email_blocks, :reference_account_id, :bigint, null: true, default: nil } # rubocop:disable Rails/ReversibleMigration
+    safety_assured { change_column :canonical_email_blocks, :reference_account_id, :bigint, null: true, default: nil }
   end
 end
diff --git a/db/migrate/20230314021909_add_group_message_following_only_to_accounts.rb b/db/migrate/20230314021909_add_group_message_following_only_to_accounts.rb
index 1f59c7c044..4712e9dd3e 100644
--- a/db/migrate/20230314021909_add_group_message_following_only_to_accounts.rb
+++ b/db/migrate/20230314021909_add_group_message_following_only_to_accounts.rb
@@ -2,6 +2,6 @@
 
 class AddGroupMessageFollowingOnlyToAccounts < ActiveRecord::Migration[6.1]
   def change
-    add_column :accounts, :group_message_following_only, :boolean, null: false, default: false
+    add_column :accounts, :group_message_following_only, :boolean
   end
 end
diff --git a/db/migrate/20230314081013_add_group_allow_private_message_to_accounts.rb b/db/migrate/20230314081013_add_group_allow_private_message_to_accounts.rb
index adba09a942..367e1c1d0a 100644
--- a/db/migrate/20230314081013_add_group_allow_private_message_to_accounts.rb
+++ b/db/migrate/20230314081013_add_group_allow_private_message_to_accounts.rb
@@ -2,6 +2,6 @@
 
 class AddGroupAllowPrivateMessageToAccounts < ActiveRecord::Migration[6.1]
   def change
-    add_column :accounts, :group_allow_private_message, :boolean, null: false, default: false
+    add_column :accounts, :group_allow_private_message, :boolean
   end
 end
diff --git a/db/migrate/20230412005311_add_markdown_to_statuses.rb b/db/migrate/20230412005311_add_markdown_to_statuses.rb
index de4fadcb78..a9730796e5 100644
--- a/db/migrate/20230412005311_add_markdown_to_statuses.rb
+++ b/db/migrate/20230412005311_add_markdown_to_statuses.rb
@@ -3,7 +3,7 @@
 class AddMarkdownToStatuses < ActiveRecord::Migration[6.1]
   def change
     safety_assured do
-      add_column :statuses, :markdown, :boolean, default: false, null: false
+      add_column :statuses, :markdown, :boolean, default: false
     end
   end
 end
diff --git a/db/migrate/20230412073021_add_markdown_to_status_edits.rb b/db/migrate/20230412073021_add_markdown_to_status_edits.rb
index 72a10c1795..8dd5711487 100644
--- a/db/migrate/20230412073021_add_markdown_to_status_edits.rb
+++ b/db/migrate/20230412073021_add_markdown_to_status_edits.rb
@@ -3,7 +3,7 @@
 class AddMarkdownToStatusEdits < ActiveRecord::Migration[6.1]
   def change
     safety_assured do
-      add_column :status_edits, :markdown, :boolean, default: false, null: false
+      add_column :status_edits, :markdown, :boolean, default: false
     end
   end
 end
diff --git a/db/migrate/20230428111230_add_emoji_reaction_streaming_to_accounts.rb b/db/migrate/20230428111230_add_emoji_reaction_streaming_to_accounts.rb
index 845a8c46ac..43c7df805d 100644
--- a/db/migrate/20230428111230_add_emoji_reaction_streaming_to_accounts.rb
+++ b/db/migrate/20230428111230_add_emoji_reaction_streaming_to_accounts.rb
@@ -3,7 +3,7 @@
 class AddEmojiReactionStreamingToAccounts < ActiveRecord::Migration[6.1]
   def change
     safety_assured do
-      add_column :accounts, :stop_emoji_reaction_streaming, :boolean, default: false, null: false
+      add_column :accounts, :stop_emoji_reaction_streaming, :boolean, default: false
     end
   end
 end
diff --git a/db/migrate/20230510004621_remove_stop_emoji_reaction_streaming_from_accounts.rb b/db/migrate/20230510004621_remove_stop_emoji_reaction_streaming_from_accounts.rb
index 2b21c554ff..3bc96d3893 100644
--- a/db/migrate/20230510004621_remove_stop_emoji_reaction_streaming_from_accounts.rb
+++ b/db/migrate/20230510004621_remove_stop_emoji_reaction_streaming_from_accounts.rb
@@ -9,7 +9,7 @@ class RemoveStopEmojiReactionStreamingFromAccounts < ActiveRecord::Migration[6.1
 
   def down
     safety_assured do
-      add_column :accounts, :stop_emoji_reaction_streaming, :boolean, null: false, default: false
+      add_column :accounts, :stop_emoji_reaction_streaming, :boolean, null: true, default: false
     end
   end
 end
diff --git a/db/migrate/20231212225737_improve_index_for_public_timeline_speed.rb b/db/migrate/20231212225737_improve_index_for_public_timeline_speed.rb
index 8bea8d5371..5902df0802 100644
--- a/db/migrate/20231212225737_improve_index_for_public_timeline_speed.rb
+++ b/db/migrate/20231212225737_improve_index_for_public_timeline_speed.rb
@@ -4,17 +4,17 @@ class ImproveIndexForPublicTimelineSpeed < ActiveRecord::Migration[7.0]
   disable_ddl_transaction!
 
   def up
-    add_index :statuses, [:id, :account_id], name: :index_statuses_local_20231213, algorithm: :concurrently, order: { id: :desc }, # rubocop:disable Naming/VariableNumber
+    add_index :statuses, [:id, :account_id], name: :index_statuses_local_20231213, algorithm: :concurrently, order: { id: :desc },
                                              where: '(local OR (uri IS NULL)) AND deleted_at IS NULL AND visibility IN (0, 10, 11) AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))'
-    add_index :statuses, [:id, :account_id], name: :index_statuses_public_20231213, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility IN (0, 10, 11) AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' # rubocop:disable Naming/VariableNumber
-    remove_index :statuses, name: :index_statuses_local_20190824 # rubocop:disable Naming/VariableNumber
-    remove_index :statuses, if_exists: true, name: :index_statuses_public_20200119 # rubocop:disable Naming/VariableNumber
+    add_index :statuses, [:id, :account_id], name: :index_statuses_public_20231213, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility IN (0, 10, 11) AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))'
+    remove_index :statuses, name: :index_statuses_local_20190824
+    remove_index :statuses, name: :index_statuses_public_20200119
   end
 
   def down
-    add_index :statuses, [:id, :account_id], name: :index_statuses_local_20190824, algorithm: :concurrently, order: { id: :desc }, where: '(local OR (uri IS NULL)) AND deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' # rubocop:disable Naming/VariableNumber
-    add_index :statuses, [:id, :account_id], name: :index_statuses_public_20200119, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' # rubocop:disable Naming/VariableNumber
-    remove_index :statuses, name: :index_statuses_local_20231213 # rubocop:disable Naming/VariableNumber
-    remove_index :statuses, if_exists: true, name: :index_statuses_public_20231213 # rubocop:disable Naming/VariableNumber
+    add_index :statuses, [:id, :account_id], name: :index_statuses_local_20190824, algorithm: :concurrently, order: { id: :desc }, where: '(local OR (uri IS NULL)) AND deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))'
+    add_index :statuses, [:id, :account_id], name: :index_statuses_public_20200119, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))'
+    remove_index :statuses, name: :index_statuses_local_20231213
+    remove_index :statuses, name: :index_statuses_public_20231213
   end
 end
diff --git a/db/migrate/20240312105620_create_severed_relationships.rb b/db/migrate/20240312105620_create_severed_relationships.rb
index 3269298e80..1ed911cd55 100644
--- a/db/migrate/20240312105620_create_severed_relationships.rb
+++ b/db/migrate/20240312105620_create_severed_relationships.rb
@@ -14,10 +14,8 @@ class CreateSeveredRelationships < ActiveRecord::Migration[7.0]
       t.integer :direction, null: false
 
       # Those attributes are carried over from the `follows` table
-      # rubocop:disable Rails/ThreeStateBooleanColumn
       t.boolean :show_reblogs
       t.boolean :notify
-      # rubocop:enable Rails/ThreeStateBooleanColumn
       t.string :languages, array: true
 
       t.timestamps
diff --git a/db/migrate/20240918233930_add_fetched_replies_at_to_status.rb b/db/migrate/20240918233930_add_fetched_replies_at_to_status.rb
deleted file mode 100644
index 229e43d978..0000000000
--- a/db/migrate/20240918233930_add_fetched_replies_at_to_status.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddFetchedRepliesAtToStatus < ActiveRecord::Migration[7.1]
-  def change
-    add_column :statuses, :fetched_replies_at, :datetime, null: true
-  end
-end
diff --git a/db/migrate/20241014010506_remove_duplicate_indexes.rb b/db/migrate/20241014010506_remove_duplicate_indexes.rb
deleted file mode 100644
index 50e0e6ffcf..0000000000
--- a/db/migrate/20241014010506_remove_duplicate_indexes.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-class RemoveDuplicateIndexes < ActiveRecord::Migration[7.1]
-  def change
-    remove_index :account_aliases, :account_id
-    remove_index :account_relationship_severance_events, :account_id
-    remove_index :custom_filter_statuses, :status_id
-    remove_index :webauthn_credentials, :user_id
-  end
-end
diff --git a/db/migrate/20241022214312_add_untrusted_favourites_count_and_untrusted_reblogs_count_to_status_stat.rb b/db/migrate/20241022214312_add_untrusted_favourites_count_and_untrusted_reblogs_count_to_status_stat.rb
deleted file mode 100644
index e34caff240..0000000000
--- a/db/migrate/20241022214312_add_untrusted_favourites_count_and_untrusted_reblogs_count_to_status_stat.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-class AddUntrustedFavouritesCountAndUntrustedReblogsCountToStatusStat < ActiveRecord::Migration[7.1]
-  def change
-    add_column :status_stats, :untrusted_favourites_count, :bigint, null: true
-    add_column :status_stats, :untrusted_reblogs_count, :bigint, null: true
-  end
-end
diff --git a/db/migrate/20241104082851_create_annual_report_statuses_per_account_counts.rb b/db/migrate/20241104082851_create_annual_report_statuses_per_account_counts.rb
deleted file mode 100644
index c8ec30ad57..0000000000
--- a/db/migrate/20241104082851_create_annual_report_statuses_per_account_counts.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-class CreateAnnualReportStatusesPerAccountCounts < ActiveRecord::Migration[7.1]
-  def change
-    create_table :annual_report_statuses_per_account_counts do |t| # rubocop:disable Rails/CreateTableWithTimestamps
-      t.integer :year, null: false
-      t.bigint :account_id, null: false
-      t.bigint :statuses_count, null: false
-    end
-
-    add_index :annual_report_statuses_per_account_counts, [:year, :account_id], unique: true
-  end
-end
diff --git a/db/migrate/20241111141355_create_tag_trends.rb b/db/migrate/20241111141355_create_tag_trends.rb
deleted file mode 100644
index c4c7d13d19..0000000000
--- a/db/migrate/20241111141355_create_tag_trends.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-class CreateTagTrends < ActiveRecord::Migration[7.2]
-  def change
-    create_table :tag_trends do |t| # rubocop:disable Rails/CreateTableWithTimestamps
-      t.references :tag, null: false, foreign_key: { on_delete: :cascade }, index: false
-      t.float :score, null: false, default: 0
-      t.integer :rank, null: false, default: 0
-      t.boolean :allowed, null: false, default: false
-      t.string :language, null: false, default: ''
-    end
-
-    add_index :tag_trends, [:tag_id, :language], unique: true
-  end
-end
diff --git a/db/migrate/20241123224956_create_terms_of_services.rb b/db/migrate/20241123224956_create_terms_of_services.rb
deleted file mode 100644
index dda2b0647c..0000000000
--- a/db/migrate/20241123224956_create_terms_of_services.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-class CreateTermsOfServices < ActiveRecord::Migration[7.2]
-  def change
-    create_table :terms_of_services do |t|
-      t.text :text, null: false, default: ''
-      t.text :changelog, null: false, default: ''
-      t.datetime :published_at
-      t.datetime :notification_sent_at
-
-      t.timestamps
-    end
-  end
-end
diff --git a/db/migrate/20241205103523_create_fasp_providers.rb b/db/migrate/20241205103523_create_fasp_providers.rb
deleted file mode 100644
index ac1d52e8a7..0000000000
--- a/db/migrate/20241205103523_create_fasp_providers.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-class CreateFaspProviders < ActiveRecord::Migration[7.2]
-  def change
-    create_table :fasp_providers do |t|
-      t.boolean :confirmed, null: false, default: false
-      t.string :name, null: false
-      t.string :base_url, null: false, index: { unique: true }
-      t.string :sign_in_url
-      t.string :remote_identifier, null: false
-      t.string :provider_public_key_pem, null: false
-      t.string :server_private_key_pem, null: false
-      t.jsonb :capabilities, null: false, default: []
-      t.jsonb :privacy_policy
-      t.string :contact_email
-      t.string :fediverse_account
-
-      t.timestamps
-    end
-  end
-end
diff --git a/db/migrate/20241205135901_remove_legacy_user_settings_data.rb b/db/migrate/20241205135901_remove_legacy_user_settings_data.rb
deleted file mode 100644
index 82f86aad14..0000000000
--- a/db/migrate/20241205135901_remove_legacy_user_settings_data.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-class RemoveLegacyUserSettingsData < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM settings
-      WHERE
-        thing_type IS NOT NULL
-        AND thing_id IS NOT NULL
-    SQL
-
-    # When running these migrations on mastodon.social, we saw 'notification_emails'
-    # and 'interactions' records that were not associated to a user and caused a
-    # migration issue.
-    # While I have not been able to pinpoint the exact cause of the issue, it is likely
-    # related to the settings system changes made in b11fdc3ae3f90731c01149a5a36dc64e065d4ea2.
-    # So, delete a few user settings that should already have been deleted.
-    connection.execute(<<~SQL.squish)
-      DELETE FROM settings
-      WHERE var IN (
-        'notification_emails', 'interactions', 'boost_modal', 'auto_play_gif',
-        'delete_modal', 'system_font_ui', 'default_sensitive', 'unfollow_modal',
-        'reduce_motion', 'display_sensitive_media', 'hide_network', 'expand_spoilers',
-        'display_media', 'aggregate_reblogs', 'show_application', 'advanced_layout',
-        'use_blurhash', 'use_pending_items')
-    SQL
-  end
-
-  def down
-    raise ActiveRecord::IrreversibleMigration
-  end
-end
diff --git a/db/migrate/20241205162640_add_missing_delete_cascade_webauthn_credentials.rb b/db/migrate/20241205162640_add_missing_delete_cascade_webauthn_credentials.rb
deleted file mode 100644
index 3fe75a50d3..0000000000
--- a/db/migrate/20241205162640_add_missing_delete_cascade_webauthn_credentials.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-class AddMissingDeleteCascadeWebauthnCredentials < ActiveRecord::Migration[7.2]
-  def up
-    safety_assured do
-      execute <<~SQL.squish
-        ALTER TABLE webauthn_credentials
-          DROP CONSTRAINT fk_rails_a4355aef77,
-          ADD CONSTRAINT fk_rails_a4355aef77
-            FOREIGN KEY (user_id)
-            REFERENCES users(id)
-            ON DELETE CASCADE
-      SQL
-    end
-  end
-
-  def down
-    safety_assured do
-      execute <<~SQL.squish
-        ALTER TABLE webauthn_credentials
-          DROP CONSTRAINT fk_rails_a4355aef77,
-          ADD CONSTRAINT fk_rails_a4355aef77
-            FOREIGN KEY (user_id)
-            REFERENCES users(id)
-      SQL
-    end
-  end
-end
diff --git a/db/migrate/20241205163118_add_missing_delete_cascade_account_moderation_notes.rb b/db/migrate/20241205163118_add_missing_delete_cascade_account_moderation_notes.rb
deleted file mode 100644
index 1252e94574..0000000000
--- a/db/migrate/20241205163118_add_missing_delete_cascade_account_moderation_notes.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-class AddMissingDeleteCascadeAccountModerationNotes < ActiveRecord::Migration[7.2]
-  def up
-    safety_assured do
-      execute <<~SQL.squish
-        ALTER TABLE account_moderation_notes
-          DROP CONSTRAINT fk_rails_3f8b75089b,
-          ADD CONSTRAINT fk_rails_3f8b75089b
-            FOREIGN KEY (account_id)
-            REFERENCES accounts(id)
-            ON DELETE CASCADE
-      SQL
-
-      execute <<~SQL.squish
-        ALTER TABLE account_moderation_notes
-          DROP CONSTRAINT fk_rails_dd62ed5ac3,
-          ADD CONSTRAINT fk_rails_dd62ed5ac3
-            FOREIGN KEY (target_account_id)
-            REFERENCES accounts(id)
-            ON DELETE CASCADE
-      SQL
-    end
-  end
-
-  def down
-    safety_assured do
-      execute <<~SQL.squish
-        ALTER TABLE account_moderation_notes
-          DROP CONSTRAINT fk_rails_3f8b75089b,
-          ADD CONSTRAINT fk_rails_3f8b75089b
-            FOREIGN KEY (account_id)
-            REFERENCES accounts(id)
-      SQL
-
-      execute <<~SQL.squish
-        ALTER TABLE account_moderation_notes
-          DROP CONSTRAINT fk_rails_dd62ed5ac3,
-          ADD CONSTRAINT fk_rails_dd62ed5ac3
-            FOREIGN KEY (target_account_id)
-            REFERENCES accounts(id)
-      SQL
-    end
-  end
-end
diff --git a/db/migrate/20241206131513_create_fasp_debug_callbacks.rb b/db/migrate/20241206131513_create_fasp_debug_callbacks.rb
deleted file mode 100644
index 6b221ce93f..0000000000
--- a/db/migrate/20241206131513_create_fasp_debug_callbacks.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-class CreateFaspDebugCallbacks < ActiveRecord::Migration[7.2]
-  def change
-    create_table :fasp_debug_callbacks do |t|
-      t.references :fasp_provider, null: false, foreign_key: true
-      t.string :ip, null: false
-      t.text :request_body, null: false
-
-      t.timestamps
-    end
-  end
-end
diff --git a/db/migrate/20241208232829_add_favourite_to_lists_and_antennas.rb b/db/migrate/20241208232829_add_favourite_to_lists_and_antennas.rb
deleted file mode 100644
index ff0d40efda..0000000000
--- a/db/migrate/20241208232829_add_favourite_to_lists_and_antennas.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-class AddFavouriteToListsAndAntennas < ActiveRecord::Migration[7.2]
-  class Antenna < ApplicationRecord; end
-
-  def up
-    add_column :lists, :favourite, :boolean, null: false, default: true
-    add_column :antennas, :favourite, :boolean, null: false, default: true
-
-    Antenna.where(insert_feeds: true).in_batches.update_all(favourite: false)
-  end
-
-  def down
-    remove_column :lists, :favourite
-    remove_column :antennas, :favourite
-  end
-end
diff --git a/db/migrate/20241210140838_add_not_null_to_account_pin_account_columns.rb b/db/migrate/20241210140838_add_not_null_to_account_pin_account_columns.rb
deleted file mode 100644
index 69b5b4a025..0000000000
--- a/db/migrate/20241210140838_add_not_null_to_account_pin_account_columns.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToAccountPinAccountColumns < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM account_pins
-      WHERE account_id IS NULL
-      OR target_account_id IS NULL
-    SQL
-
-    safety_assured do
-      change_column_null :account_pins, :account_id, false
-      change_column_null :account_pins, :target_account_id, false
-    end
-  end
-
-  def down
-    safety_assured do
-      change_column_null :account_pins, :account_id, true
-      change_column_null :account_pins, :target_account_id, true
-    end
-  end
-end
diff --git a/db/migrate/20241212152158_add_not_null_to_account_alias_columns.rb b/db/migrate/20241212152158_add_not_null_to_account_alias_columns.rb
deleted file mode 100644
index cd57f3b2fa..0000000000
--- a/db/migrate/20241212152158_add_not_null_to_account_alias_columns.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToAccountAliasColumns < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM account_aliases
-      WHERE account_id IS NULL
-    SQL
-
-    safety_assured { change_column_null :account_aliases, :account_id, false }
-  end
-
-  def down
-    safety_assured { change_column_null :account_aliases, :account_id, true }
-  end
-end
diff --git a/db/migrate/20241212152618_add_not_null_to_account_deletion_request_columns.rb b/db/migrate/20241212152618_add_not_null_to_account_deletion_request_columns.rb
deleted file mode 100644
index 80c57b1e39..0000000000
--- a/db/migrate/20241212152618_add_not_null_to_account_deletion_request_columns.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToAccountDeletionRequestColumns < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM account_deletion_requests
-      WHERE account_id IS NULL
-    SQL
-
-    safety_assured { change_column_null :account_deletion_requests, :account_id, false }
-  end
-
-  def down
-    safety_assured { change_column_null :account_deletion_requests, :account_id, true }
-  end
-end
diff --git a/db/migrate/20241212152734_add_not_null_to_account_domain_block_columns.rb b/db/migrate/20241212152734_add_not_null_to_account_domain_block_columns.rb
deleted file mode 100644
index 38ab6b9ad4..0000000000
--- a/db/migrate/20241212152734_add_not_null_to_account_domain_block_columns.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToAccountDomainBlockColumns < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM account_domain_blocks
-      WHERE account_id IS NULL
-      OR domain IS NULL
-    SQL
-
-    safety_assured do
-      change_column_null :account_domain_blocks, :account_id, false
-      change_column_null :account_domain_blocks, :domain, false
-    end
-  end
-
-  def down
-    safety_assured do
-      change_column_null :account_domain_blocks, :account_id, true
-      change_column_null :account_domain_blocks, :domain, true
-    end
-  end
-end
diff --git a/db/migrate/20241212152910_add_not_null_to_admin_action_log_columns.rb b/db/migrate/20241212152910_add_not_null_to_admin_action_log_columns.rb
deleted file mode 100644
index 092ad00e85..0000000000
--- a/db/migrate/20241212152910_add_not_null_to_admin_action_log_columns.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToAdminActionLogColumns < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM admin_action_logs
-      WHERE account_id IS NULL
-    SQL
-
-    safety_assured { change_column_null :admin_action_logs, :account_id, false }
-  end
-
-  def down
-    safety_assured { change_column_null :admin_action_logs, :account_id, true }
-  end
-end
diff --git a/db/migrate/20241212153054_add_not_null_to_announcement_mute_columns.rb b/db/migrate/20241212153054_add_not_null_to_announcement_mute_columns.rb
deleted file mode 100644
index 052f0300a9..0000000000
--- a/db/migrate/20241212153054_add_not_null_to_announcement_mute_columns.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToAnnouncementMuteColumns < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM announcement_mutes
-      WHERE account_id IS NULL
-      OR announcement_id IS NULL
-    SQL
-
-    safety_assured do
-      change_column_null :announcement_mutes, :account_id, false
-      change_column_null :announcement_mutes, :announcement_id, false
-    end
-  end
-
-  def down
-    safety_assured do
-      change_column_null :announcement_mutes, :account_id, true
-      change_column_null :announcement_mutes, :announcement_id, true
-    end
-  end
-end
diff --git a/db/migrate/20241212153202_add_not_null_to_announcement_reaction_columns.rb b/db/migrate/20241212153202_add_not_null_to_announcement_reaction_columns.rb
deleted file mode 100644
index a877eccb3a..0000000000
--- a/db/migrate/20241212153202_add_not_null_to_announcement_reaction_columns.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToAnnouncementReactionColumns < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM announcement_reactions
-      WHERE account_id IS NULL
-      OR announcement_id IS NULL
-    SQL
-
-    safety_assured do
-      change_column_null :announcement_reactions, :account_id, false
-      change_column_null :announcement_reactions, :announcement_id, false
-    end
-  end
-
-  def down
-    safety_assured do
-      change_column_null :announcement_reactions, :account_id, true
-      change_column_null :announcement_reactions, :announcement_id, true
-    end
-  end
-end
diff --git a/db/migrate/20241212153254_add_not_null_to_custom_filter_columns.rb b/db/migrate/20241212153254_add_not_null_to_custom_filter_columns.rb
deleted file mode 100644
index 5eeb5f942a..0000000000
--- a/db/migrate/20241212153254_add_not_null_to_custom_filter_columns.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToCustomFilterColumns < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM custom_filters
-      WHERE account_id IS NULL
-    SQL
-
-    safety_assured { change_column_null :custom_filters, :account_id, false }
-  end
-
-  def down
-    safety_assured { change_column_null :custom_filters, :account_id, true }
-  end
-end
diff --git a/db/migrate/20241212154231_add_not_null_to_scheduled_status_columns.rb b/db/migrate/20241212154231_add_not_null_to_scheduled_status_columns.rb
deleted file mode 100644
index 141b95b63a..0000000000
--- a/db/migrate/20241212154231_add_not_null_to_scheduled_status_columns.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToScheduledStatusColumns < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM scheduled_statuses
-      WHERE account_id IS NULL
-    SQL
-
-    safety_assured { change_column_null :scheduled_statuses, :account_id, false }
-  end
-
-  def down
-    safety_assured { change_column_null :scheduled_statuses, :account_id, true }
-  end
-end
diff --git a/db/migrate/20241212154346_add_not_null_to_user_invite_request_columns.rb b/db/migrate/20241212154346_add_not_null_to_user_invite_request_columns.rb
deleted file mode 100644
index 7066f69691..0000000000
--- a/db/migrate/20241212154346_add_not_null_to_user_invite_request_columns.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToUserInviteRequestColumns < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM user_invite_requests
-      WHERE user_id IS NULL
-    SQL
-
-    safety_assured { change_column_null :user_invite_requests, :user_id, false }
-  end
-
-  def down
-    safety_assured { change_column_null :user_invite_requests, :user_id, true }
-  end
-end
diff --git a/db/migrate/20241213170027_add_not_null_to_account_conversation_account_column.rb b/db/migrate/20241213170027_add_not_null_to_account_conversation_account_column.rb
deleted file mode 100644
index 5bfa55bcc6..0000000000
--- a/db/migrate/20241213170027_add_not_null_to_account_conversation_account_column.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToAccountConversationAccountColumn < ActiveRecord::Migration[7.2]
-  def change
-    add_check_constraint :account_conversations, 'account_id IS NOT NULL', name: 'account_conversations_account_id_null', validate: false
-  end
-end
diff --git a/db/migrate/20241213170036_validate_not_null_to_account_conversation_account_column.rb b/db/migrate/20241213170036_validate_not_null_to_account_conversation_account_column.rb
deleted file mode 100644
index 0186559dc3..0000000000
--- a/db/migrate/20241213170036_validate_not_null_to_account_conversation_account_column.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class ValidateNotNullToAccountConversationAccountColumn < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM account_conversations
-      WHERE account_id IS NULL
-    SQL
-
-    validate_check_constraint :account_conversations, name: 'account_conversations_account_id_null'
-    change_column_null :account_conversations, :account_id, false
-    remove_check_constraint :account_conversations, name: 'account_conversations_account_id_null'
-  end
-
-  def down
-    add_check_constraint :account_conversations, 'account_id IS NOT NULL', name: 'account_conversations_account_id_null', validate: false
-    change_column_null :account_conversations, :account_id, true
-  end
-end
diff --git a/db/migrate/20241213170043_add_not_null_to_account_conversation_conversation_column.rb b/db/migrate/20241213170043_add_not_null_to_account_conversation_conversation_column.rb
deleted file mode 100644
index e253a1b3e1..0000000000
--- a/db/migrate/20241213170043_add_not_null_to_account_conversation_conversation_column.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToAccountConversationConversationColumn < ActiveRecord::Migration[7.2]
-  def change
-    add_check_constraint :account_conversations, 'conversation_id IS NOT NULL', name: 'account_conversations_conversation_id_null', validate: false
-  end
-end
diff --git a/db/migrate/20241213170053_validate_not_null_to_account_conversation_conversation_column.rb b/db/migrate/20241213170053_validate_not_null_to_account_conversation_conversation_column.rb
deleted file mode 100644
index 324e9180e2..0000000000
--- a/db/migrate/20241213170053_validate_not_null_to_account_conversation_conversation_column.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class ValidateNotNullToAccountConversationConversationColumn < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM account_conversations
-      WHERE conversation_id IS NULL
-    SQL
-
-    validate_check_constraint :account_conversations, name: 'account_conversations_conversation_id_null'
-    change_column_null :account_conversations, :conversation_id, false
-    remove_check_constraint :account_conversations, name: 'account_conversations_conversation_id_null'
-  end
-
-  def down
-    add_check_constraint :account_conversations, 'conversation_id IS NOT NULL', name: 'account_conversations_conversation_id_null', validate: false
-    change_column_null :account_conversations, :conversation_id, true
-  end
-end
diff --git a/db/migrate/20241216223425_add_not_null_to_account_note_account_column.rb b/db/migrate/20241216223425_add_not_null_to_account_note_account_column.rb
deleted file mode 100644
index 85c10ace22..0000000000
--- a/db/migrate/20241216223425_add_not_null_to_account_note_account_column.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToAccountNoteAccountColumn < ActiveRecord::Migration[7.2]
-  def change
-    add_check_constraint :account_notes, 'account_id IS NOT NULL', name: 'account_notes_account_id_null', validate: false
-  end
-end
diff --git a/db/migrate/20241216223433_validate_not_null_to_account_note_account_column.rb b/db/migrate/20241216223433_validate_not_null_to_account_note_account_column.rb
deleted file mode 100644
index 50907ffe9d..0000000000
--- a/db/migrate/20241216223433_validate_not_null_to_account_note_account_column.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class ValidateNotNullToAccountNoteAccountColumn < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM account_notes
-      WHERE account_id IS NULL
-    SQL
-
-    validate_check_constraint :account_notes, name: 'account_notes_account_id_null'
-    change_column_null :account_notes, :account_id, false
-    remove_check_constraint :account_notes, name: 'account_notes_account_id_null'
-  end
-
-  def down
-    add_check_constraint :account_notes, 'account_id IS NOT NULL', name: 'account_notes_account_id_null', validate: false
-    change_column_null :account_notes, :account_id, true
-  end
-end
diff --git a/db/migrate/20241216223446_add_not_null_to_account_note_target_account_column.rb b/db/migrate/20241216223446_add_not_null_to_account_note_target_account_column.rb
deleted file mode 100644
index 9b389bfbff..0000000000
--- a/db/migrate/20241216223446_add_not_null_to_account_note_target_account_column.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToAccountNoteTargetAccountColumn < ActiveRecord::Migration[7.2]
-  def change
-    add_check_constraint :account_notes, 'target_account_id IS NOT NULL', name: 'account_notes_target_account_id_null', validate: false
-  end
-end
diff --git a/db/migrate/20241216223452_validate_not_null_to_account_note_target_account_column.rb b/db/migrate/20241216223452_validate_not_null_to_account_note_target_account_column.rb
deleted file mode 100644
index 2021a0eb50..0000000000
--- a/db/migrate/20241216223452_validate_not_null_to_account_note_target_account_column.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class ValidateNotNullToAccountNoteTargetAccountColumn < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM account_notes
-      WHERE target_account_id IS NULL
-    SQL
-
-    validate_check_constraint :account_notes, name: 'account_notes_target_account_id_null'
-    change_column_null :account_notes, :target_account_id, false
-    remove_check_constraint :account_notes, name: 'account_notes_target_account_id_null'
-  end
-
-  def down
-    add_check_constraint :account_notes, 'target_account_id IS NOT NULL', name: 'account_notes_target_account_id_null', validate: false
-    change_column_null :account_notes, :target_account_id, true
-  end
-end
diff --git a/db/migrate/20241216223852_add_not_null_to_marker_user_column.rb b/db/migrate/20241216223852_add_not_null_to_marker_user_column.rb
deleted file mode 100644
index 9d08fd8964..0000000000
--- a/db/migrate/20241216223852_add_not_null_to_marker_user_column.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToMarkerUserColumn < ActiveRecord::Migration[7.2]
-  def change
-    add_check_constraint :markers, 'user_id IS NOT NULL', name: 'markers_user_id_null', validate: false
-  end
-end
diff --git a/db/migrate/20241216223859_validate_not_null_to_marker_user_column.rb b/db/migrate/20241216223859_validate_not_null_to_marker_user_column.rb
deleted file mode 100644
index 37ec90eb9b..0000000000
--- a/db/migrate/20241216223859_validate_not_null_to_marker_user_column.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class ValidateNotNullToMarkerUserColumn < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM markers
-      WHERE user_id IS NULL
-    SQL
-
-    validate_check_constraint :markers, name: 'markers_user_id_null'
-    change_column_null :markers, :user_id, false
-    remove_check_constraint :markers, name: 'markers_user_id_null'
-  end
-
-  def down
-    add_check_constraint :markers, 'user_id IS NOT NULL', name: 'markers_user_id_null', validate: false
-    change_column_null :markers, :user_id, true
-  end
-end
diff --git a/db/migrate/20241216224211_add_not_null_to_poll_vote_account_column.rb b/db/migrate/20241216224211_add_not_null_to_poll_vote_account_column.rb
deleted file mode 100644
index 827f146b5e..0000000000
--- a/db/migrate/20241216224211_add_not_null_to_poll_vote_account_column.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToPollVoteAccountColumn < ActiveRecord::Migration[7.2]
-  def change
-    add_check_constraint :poll_votes, 'account_id IS NOT NULL', name: 'poll_votes_account_id_null', validate: false
-  end
-end
diff --git a/db/migrate/20241216224218_validate_not_null_to_poll_vote_account_column.rb b/db/migrate/20241216224218_validate_not_null_to_poll_vote_account_column.rb
deleted file mode 100644
index 4dfa5403c1..0000000000
--- a/db/migrate/20241216224218_validate_not_null_to_poll_vote_account_column.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class ValidateNotNullToPollVoteAccountColumn < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM poll_votes
-      WHERE account_id IS NULL
-    SQL
-
-    validate_check_constraint :poll_votes, name: 'poll_votes_account_id_null'
-    change_column_null :poll_votes, :account_id, false
-    remove_check_constraint :poll_votes, name: 'poll_votes_account_id_null'
-  end
-
-  def down
-    add_check_constraint :poll_votes, 'account_id IS NOT NULL', name: 'poll_votes_account_id_null', validate: false
-    change_column_null :poll_votes, :account_id, true
-  end
-end
diff --git a/db/migrate/20241216224229_add_not_null_to_poll_vote_poll_column.rb b/db/migrate/20241216224229_add_not_null_to_poll_vote_poll_column.rb
deleted file mode 100644
index 6e8a2f4f67..0000000000
--- a/db/migrate/20241216224229_add_not_null_to_poll_vote_poll_column.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToPollVotePollColumn < ActiveRecord::Migration[7.2]
-  def change
-    add_check_constraint :poll_votes, 'poll_id IS NOT NULL', name: 'poll_votes_poll_id_null', validate: false
-  end
-end
diff --git a/db/migrate/20241216224237_validate_not_null_to_poll_vote_poll_column.rb b/db/migrate/20241216224237_validate_not_null_to_poll_vote_poll_column.rb
deleted file mode 100644
index 9b71406548..0000000000
--- a/db/migrate/20241216224237_validate_not_null_to_poll_vote_poll_column.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class ValidateNotNullToPollVotePollColumn < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM poll_votes
-      WHERE poll_id IS NULL
-    SQL
-
-    validate_check_constraint :poll_votes, name: 'poll_votes_poll_id_null'
-    change_column_null :poll_votes, :poll_id, false
-    remove_check_constraint :poll_votes, name: 'poll_votes_poll_id_null'
-  end
-
-  def down
-    add_check_constraint :poll_votes, 'poll_id IS NOT NULL', name: 'poll_votes_poll_id_null', validate: false
-    change_column_null :poll_votes, :poll_id, true
-  end
-end
diff --git a/db/migrate/20241216224507_add_not_null_to_poll_account_column.rb b/db/migrate/20241216224507_add_not_null_to_poll_account_column.rb
deleted file mode 100644
index 9ff5c2d8f3..0000000000
--- a/db/migrate/20241216224507_add_not_null_to_poll_account_column.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToPollAccountColumn < ActiveRecord::Migration[7.2]
-  def change
-    add_check_constraint :polls, 'account_id IS NOT NULL', name: 'polls_account_id_null', validate: false
-  end
-end
diff --git a/db/migrate/20241216224514_validate_not_null_to_poll_account_column.rb b/db/migrate/20241216224514_validate_not_null_to_poll_account_column.rb
deleted file mode 100644
index 91e835359c..0000000000
--- a/db/migrate/20241216224514_validate_not_null_to_poll_account_column.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class ValidateNotNullToPollAccountColumn < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM polls
-      WHERE account_id IS NULL
-    SQL
-
-    validate_check_constraint :polls, name: 'polls_account_id_null'
-    change_column_null :polls, :account_id, false
-    remove_check_constraint :polls, name: 'polls_account_id_null'
-  end
-
-  def down
-    add_check_constraint :polls, 'account_id IS NOT NULL', name: 'polls_account_id_null', validate: false
-    change_column_null :polls, :account_id, true
-  end
-end
diff --git a/db/migrate/20241216224520_add_not_null_to_poll_status_column.rb b/db/migrate/20241216224520_add_not_null_to_poll_status_column.rb
deleted file mode 100644
index 5c638c6af0..0000000000
--- a/db/migrate/20241216224520_add_not_null_to_poll_status_column.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToPollStatusColumn < ActiveRecord::Migration[7.2]
-  def change
-    add_check_constraint :polls, 'status_id IS NOT NULL', name: 'polls_status_id_null', validate: false
-  end
-end
diff --git a/db/migrate/20241216224530_validate_not_null_to_poll_status_column.rb b/db/migrate/20241216224530_validate_not_null_to_poll_status_column.rb
deleted file mode 100644
index b5985ce062..0000000000
--- a/db/migrate/20241216224530_validate_not_null_to_poll_status_column.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class ValidateNotNullToPollStatusColumn < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM polls
-      WHERE status_id IS NULL
-    SQL
-
-    validate_check_constraint :polls, name: 'polls_status_id_null'
-    change_column_null :polls, :status_id, false
-    remove_check_constraint :polls, name: 'polls_status_id_null'
-  end
-
-  def down
-    add_check_constraint :polls, 'status_id IS NOT NULL', name: 'polls_status_id_null', validate: false
-    change_column_null :polls, :status_id, true
-  end
-end
diff --git a/db/migrate/20241216224813_add_not_null_to_tombstone_account_column.rb b/db/migrate/20241216224813_add_not_null_to_tombstone_account_column.rb
deleted file mode 100644
index 1a380cdef3..0000000000
--- a/db/migrate/20241216224813_add_not_null_to_tombstone_account_column.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotNullToTombstoneAccountColumn < ActiveRecord::Migration[7.2]
-  def change
-    add_check_constraint :tombstones, 'account_id IS NOT NULL', name: 'tombstones_account_id_null', validate: false
-  end
-end
diff --git a/db/migrate/20241216224825_validate_not_null_to_tombstone_account_column.rb b/db/migrate/20241216224825_validate_not_null_to_tombstone_account_column.rb
deleted file mode 100644
index 5fefcca33d..0000000000
--- a/db/migrate/20241216224825_validate_not_null_to_tombstone_account_column.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class ValidateNotNullToTombstoneAccountColumn < ActiveRecord::Migration[7.2]
-  def up
-    connection.execute(<<~SQL.squish)
-      DELETE FROM tombstones
-      WHERE account_id IS NULL
-    SQL
-
-    validate_check_constraint :tombstones, name: 'tombstones_account_id_null'
-    change_column_null :tombstones, :account_id, false
-    remove_check_constraint :tombstones, name: 'tombstones_account_id_null'
-  end
-
-  def down
-    add_check_constraint :tombstones, 'account_id IS NOT NULL', name: 'tombstones_account_id_null', validate: false
-    change_column_null :tombstones, :account_id, true
-  end
-end
diff --git a/db/migrate/20250108111200_add_standard_to_push_subscription.rb b/db/migrate/20250108111200_add_standard_to_push_subscription.rb
deleted file mode 100644
index eb72f9c62e..0000000000
--- a/db/migrate/20250108111200_add_standard_to_push_subscription.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddStandardToPushSubscription < ActiveRecord::Migration[8.0]
-  def change
-    add_column :web_push_subscriptions, :standard, :boolean, null: false, default: false
-  end
-end
diff --git a/db/migrate/20250123091137_remove_half_warn_filter_option.rb b/db/migrate/20250123091137_remove_half_warn_filter_option.rb
deleted file mode 100644
index 4c777b2c02..0000000000
--- a/db/migrate/20250123091137_remove_half_warn_filter_option.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-class RemoveHalfWarnFilterOption < ActiveRecord::Migration[8.0]
-  class CustomFilter < ApplicationRecord; end
-
-  def up
-    CustomFilter.where(action: 2).in_batches.update_all(action: 0)
-  end
-
-  def down; end
-end
diff --git a/db/migrate/20250130232529_set_antenna_list_id_default_value.rb b/db/migrate/20250130232529_set_antenna_list_id_default_value.rb
deleted file mode 100644
index 67b5e7ebd2..0000000000
--- a/db/migrate/20250130232529_set_antenna_list_id_default_value.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class SetAntennaListIdDefaultValue < ActiveRecord::Migration[8.0]
-  def change
-    change_column_default :antennas, :list_id, from: nil, to: 0
-  end
-end
diff --git a/db/migrate/20250221143646_add_notification_sent_at_to_announcements.rb b/db/migrate/20250221143646_add_notification_sent_at_to_announcements.rb
deleted file mode 100644
index 9cf1521a82..0000000000
--- a/db/migrate/20250221143646_add_notification_sent_at_to_announcements.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddNotificationSentAtToAnnouncements < ActiveRecord::Migration[8.0]
-  def change
-    add_column :announcements, :notification_sent_at, :datetime
-  end
-end
diff --git a/db/migrate/20250224144617_add_effective_date_to_terms_of_services.rb b/db/migrate/20250224144617_add_effective_date_to_terms_of_services.rb
deleted file mode 100644
index e46378387d..0000000000
--- a/db/migrate/20250224144617_add_effective_date_to_terms_of_services.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddEffectiveDateToTermsOfServices < ActiveRecord::Migration[8.0]
-  def change
-    add_column :terms_of_services, :effective_date, :date
-  end
-end
diff --git a/db/migrate/20250305074104_add_effective_date_index_to_terms_of_services.rb b/db/migrate/20250305074104_add_effective_date_index_to_terms_of_services.rb
deleted file mode 100644
index 78cc809ec8..0000000000
--- a/db/migrate/20250305074104_add_effective_date_index_to_terms_of_services.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-class AddEffectiveDateIndexToTermsOfServices < ActiveRecord::Migration[8.0]
-  disable_ddl_transaction!
-
-  def change
-    add_index :terms_of_services, :effective_date, unique: true, algorithm: :concurrently, where: 'effective_date IS NOT NULL'
-  end
-end
diff --git a/db/migrate/20250313123400_add_age_verified_at_to_users.rb b/db/migrate/20250313123400_add_age_verified_at_to_users.rb
deleted file mode 100644
index c6cd6120ef..0000000000
--- a/db/migrate/20250313123400_add_age_verified_at_to_users.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddAgeVerifiedAtToUsers < ActiveRecord::Migration[8.0]
-  def change
-    add_column :users, :age_verified_at, :datetime
-  end
-end
diff --git a/db/migrate/20250410144908_drop_imports.rb b/db/migrate/20250410144908_drop_imports.rb
deleted file mode 100644
index 7be9daf750..0000000000
--- a/db/migrate/20250410144908_drop_imports.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-class DropImports < ActiveRecord::Migration[7.1]
-  def up
-    drop_table :imports
-  end
-
-  def down
-    raise ActiveRecord::IrreversibleMigration
-  end
-end
diff --git a/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb b/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb
index ff4012ffa1..7788431cd5 100644
--- a/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb
+++ b/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb
@@ -7,7 +7,7 @@ class RemoveSuspendedSilencedAccountFields < ActiveRecord::Migration[5.2]
 
   class DomainBlock < ApplicationRecord
     # Dummy class, to make migration possible across version changes
-    enum :severity, [:silence, :suspend, :noop]
+    enum severity: [:silence, :suspend, :noop]
 
     has_many :accounts, foreign_key: :domain, primary_key: :domain
   end
diff --git a/db/post_migrate/20190519130537_remove_boosts_widening_audience.rb b/db/post_migrate/20190519130537_remove_boosts_widening_audience.rb
index a1b8fa7032..89a95041ee 100644
--- a/db/post_migrate/20190519130537_remove_boosts_widening_audience.rb
+++ b/db/post_migrate/20190519130537_remove_boosts_widening_audience.rb
@@ -4,26 +4,19 @@ class RemoveBoostsWideningAudience < ActiveRecord::Migration[5.2]
   disable_ddl_transaction!
 
   def up
-    # add_column :statuses, :searchability, :integer
-    # add_column :statuses, :limited_scope, :integer
+    public_boosts = Status.find_by_sql(<<-SQL.squish)
+      SELECT boost.id
+      FROM statuses AS boost
+      LEFT JOIN statuses AS boosted ON boost.reblog_of_id = boosted.id
+      WHERE
+        boost.id > 101746055577600000
+        AND (boost.local = TRUE OR boost.uri IS NULL)
+        AND boost.visibility IN (0, 1)
+        AND boost.reblog_of_id IS NOT NULL
+        AND boosted.visibility = 2
+    SQL
 
-    # Status.find_by_sql(<<-SQL.squish)
-    #   SELECT boost.id
-    #   FROM statuses AS boost
-    #   LEFT JOIN statuses AS boosted ON boost.reblog_of_id = boosted.id
-    #   WHERE
-    #     boost.id > 101746055577600000
-    #     AND (boost.local = TRUE OR boost.uri IS NULL)
-    #     AND boost.visibility IN (0, 1)
-    #     AND boost.reblog_of_id IS NOT NULL
-    #     AND boosted.visibility = 2
-    # SQL
-
-    # Sorry, but remove to fix test
-    # RemovalWorker.push_bulk(public_boosts.pluck(:id))
-
-    # remove_column :statuses, :searchability
-    # remove_column :statuses, :limited_scope
+    RemovalWorker.push_bulk(public_boosts.pluck(:id))
   end
 
   def down
diff --git a/db/post_migrate/20201017234926_fill_account_suspension_origin.rb b/db/post_migrate/20201017234926_fill_account_suspension_origin.rb
index 0bf9dbff03..b00f9df533 100644
--- a/db/post_migrate/20201017234926_fill_account_suspension_origin.rb
+++ b/db/post_migrate/20201017234926_fill_account_suspension_origin.rb
@@ -6,7 +6,7 @@ class FillAccountSuspensionOrigin < ActiveRecord::Migration[5.2]
   class MigrationAccount < ApplicationRecord
     self.table_name = :accounts
     scope :suspended, -> { where.not(suspended_at: nil) }
-    enum :suspension_origin, { local: 0, remote: 1 }, prefix: true
+    enum suspension_origin: { local: 0, remote: 1 }, _prefix: true
   end
 
   def up
diff --git a/db/post_migrate/20230803082451_add_unique_index_on_preview_cards_statuses.rb b/db/post_migrate/20230803082451_add_unique_index_on_preview_cards_statuses.rb
index c35ad80028..4271f8c08a 100644
--- a/db/post_migrate/20230803082451_add_unique_index_on_preview_cards_statuses.rb
+++ b/db/post_migrate/20230803082451_add_unique_index_on_preview_cards_statuses.rb
@@ -15,10 +15,21 @@ class AddUniqueIndexOnPreviewCardsStatuses < ActiveRecord::Migration[6.1]
 
   private
 
+  def supports_concurrent_reindex?
+    @supports_concurrent_reindex ||= begin
+      ActiveRecord::Base.connection.database_version >= 120_000
+    end
+  end
+
   def deduplicate_and_reindex!
     deduplicate_preview_cards!
 
-    safety_assured { execute 'REINDEX INDEX CONCURRENTLY preview_cards_statuses_pkey' }
+    if supports_concurrent_reindex?
+      safety_assured { execute 'REINDEX INDEX CONCURRENTLY preview_cards_statuses_pkey' }
+    else
+      remove_index :preview_cards_statuses, name: :preview_cards_statuses_pkey
+      add_index :preview_cards_statuses, [:status_id, :preview_card_id], name: :preview_cards_statuses_pkey, algorithm: :concurrently, unique: true
+    end
   rescue ActiveRecord::RecordNotUnique
     retry
   end
diff --git a/db/post_migrate/20241123160722_move_tag_trends_to_table.rb b/db/post_migrate/20241123160722_move_tag_trends_to_table.rb
deleted file mode 100644
index 629592ba8a..0000000000
--- a/db/post_migrate/20241123160722_move_tag_trends_to_table.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-class MoveTagTrendsToTable < ActiveRecord::Migration[7.2]
-  include Redisable
-
-  disable_ddl_transaction!
-
-  def up
-    redis.zrange('trending_tags:all', 0, -1, with_scores: true).each_slice(1_000) do |data|
-      TagTrend.upsert_all(data.map { |(tag_id, score)| { tag_id: tag_id, score: score, language: '', allowed: redis.zscore('trending_tags:allowed', tag_id).present? } }, unique_by: %w(tag_id language))
-    end
-
-    TagTrend.recalculate_ordered_rank
-
-    redis.del('trending_tags:allowed', 'trending_tags:all')
-  end
-
-  def down
-    raise ActiveRecord::IrreversibleMigration
-  end
-end
diff --git a/db/post_migrate/20241205135925_remove_legacy_user_settings_columns.rb b/db/post_migrate/20241205135925_remove_legacy_user_settings_columns.rb
deleted file mode 100644
index 3f0eaec4ed..0000000000
--- a/db/post_migrate/20241205135925_remove_legacy_user_settings_columns.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-class RemoveLegacyUserSettingsColumns < ActiveRecord::Migration[7.2]
-  disable_ddl_transaction!
-
-  def up
-    # In normal usage this should not find anything to delete
-    # Deletion here is already done in RemoveLegacyUserSettingsData migration
-    # and no data like this should be created from app at this point
-    # Deleting again out of caution
-    connection.execute(<<~SQL.squish)
-      DELETE FROM settings
-      WHERE
-        thing_type IS NOT NULL
-        AND thing_id IS NOT NULL
-    SQL
-
-    # When running these migrations on mastodon.social, we saw 'notification_emails'
-    # and 'interactions' records that were not associated to a user and caused a
-    # migration issue.
-    # While I have not been able to pinpoint the exact cause of the issue, it is likely
-    # related to the settings system changes made in b11fdc3ae3f90731c01149a5a36dc64e065d4ea2.
-    # So, delete a few user settings that should already have been deleted.
-    connection.execute(<<~SQL.squish)
-      DELETE FROM settings
-      WHERE var IN (
-        'notification_emails', 'interactions', 'boost_modal', 'auto_play_gif',
-        'delete_modal', 'system_font_ui', 'default_sensitive', 'unfollow_modal',
-        'reduce_motion', 'display_sensitive_media', 'hide_network', 'expand_spoilers',
-        'display_media', 'aggregate_reblogs', 'show_application', 'advanced_layout',
-        'use_blurhash', 'use_pending_items')
-    SQL
-
-    add_index :settings, :var, unique: true, algorithm: :concurrently
-    remove_index :settings, [:thing_type, :thing_id, :var], name: :index_settings_on_thing_type_and_thing_id_and_var, unique: true
-
-    safety_assured do
-      remove_column :settings, :thing_type, :string
-      remove_column :settings, :thing_id, :bigint
-    end
-  end
-
-  def down
-    raise ActiveRecord::IrreversibleMigration
-  end
-end
diff --git a/db/post_migrate/20250129144440_add_new_public_index_to_statuses.rb b/db/post_migrate/20250129144440_add_new_public_index_to_statuses.rb
deleted file mode 100644
index 82cc9128b6..0000000000
--- a/db/post_migrate/20250129144440_add_new_public_index_to_statuses.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-class AddNewPublicIndexToStatuses < ActiveRecord::Migration[8.0]
-  disable_ddl_transaction!
-
-  def change
-    add_index :statuses, [:id, :language, :account_id], name: :index_statuses_public_20250129, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' # rubocop:disable Naming/VariableNumber
-  end
-end
diff --git a/db/post_migrate/20250129144813_remove_old_public_index_to_statuses.rb b/db/post_migrate/20250129144813_remove_old_public_index_to_statuses.rb
deleted file mode 100644
index 306fde7449..0000000000
--- a/db/post_migrate/20250129144813_remove_old_public_index_to_statuses.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-class RemoveOldPublicIndexToStatuses < ActiveRecord::Migration[8.0]
-  def change; end
-end
diff --git a/db/post_migrate/20250216231806_add_new_public_index_for_kmyblue_to_statuses.rb b/db/post_migrate/20250216231806_add_new_public_index_for_kmyblue_to_statuses.rb
deleted file mode 100644
index f63b5242b3..0000000000
--- a/db/post_migrate/20250216231806_add_new_public_index_for_kmyblue_to_statuses.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-class AddNewPublicIndexForKmyblueToStatuses < ActiveRecord::Migration[8.0]
-  disable_ddl_transaction!
-
-  def change
-    add_index :statuses, [:id, :language, :account_id], name: :index_statuses_public_20250210, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility IN (0, 10, 11) AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' # rubocop:disable Naming/VariableNumber
-  end
-end
diff --git a/db/post_migrate/20250216231904_remove_old_public_index_for_original_to_statuses.rb b/db/post_migrate/20250216231904_remove_old_public_index_for_original_to_statuses.rb
deleted file mode 100644
index 2a2371b50c..0000000000
--- a/db/post_migrate/20250216231904_remove_old_public_index_for_original_to_statuses.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-class RemoveOldPublicIndexForOriginalToStatuses < ActiveRecord::Migration[8.0]
-  disable_ddl_transaction!
-
-  def up
-    remove_index :statuses, [:id, :account_id], if_exists: true, name: :index_statuses_public_20231213, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility IN (0, 10, 11) AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' # rubocop:disable Naming/VariableNumber
-    remove_index :statuses, [:id, :account_id], if_exists: true, name: :index_statuses_public_20200119, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' # rubocop:disable Naming/VariableNumber
-    remove_index :statuses, [:id, :language, :account_id], name: :index_statuses_public_20250129, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' # rubocop:disable Naming/VariableNumber
-  end
-
-  def down
-    add_index :statuses, [:id, :language, :account_id], name: :index_statuses_public_20250129, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' # rubocop:disable Naming/VariableNumber
-  end
-end
diff --git a/db/schema.rb b/db/schema.rb
index d9a082eb24..76a8fb7568 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,22 +10,23 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
+ActiveRecord::Schema[7.1].define(version: 2024_10_07_071624) do
   # These are extensions that must be enabled in order to support this database
-  enable_extension "pg_catalog.plpgsql"
+  enable_extension "plpgsql"
 
   create_table "account_aliases", force: :cascade do |t|
-    t.bigint "account_id", null: false
+    t.bigint "account_id"
     t.string "acct", default: "", null: false
     t.string "uri", default: "", null: false
     t.datetime "created_at", precision: nil, null: false
     t.datetime "updated_at", precision: nil, null: false
     t.index ["account_id", "uri"], name: "index_account_aliases_on_account_id_and_uri", unique: true
+    t.index ["account_id"], name: "index_account_aliases_on_account_id"
   end
 
   create_table "account_conversations", force: :cascade do |t|
-    t.bigint "account_id", null: false
-    t.bigint "conversation_id", null: false
+    t.bigint "account_id"
+    t.bigint "conversation_id"
     t.bigint "participant_account_ids", default: [], null: false, array: true
     t.bigint "status_ids", default: [], null: false, array: true
     t.bigint "last_status_id"
@@ -36,17 +37,17 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   end
 
   create_table "account_deletion_requests", force: :cascade do |t|
-    t.bigint "account_id", null: false
+    t.bigint "account_id"
     t.datetime "created_at", precision: nil, null: false
     t.datetime "updated_at", precision: nil, null: false
     t.index ["account_id"], name: "index_account_deletion_requests_on_account_id"
   end
 
   create_table "account_domain_blocks", force: :cascade do |t|
-    t.string "domain", null: false
+    t.string "domain"
     t.datetime "created_at", precision: nil, null: false
     t.datetime "updated_at", precision: nil, null: false
-    t.bigint "account_id", null: false
+    t.bigint "account_id"
     t.index ["account_id", "domain"], name: "index_account_domain_blocks_on_account_id_and_domain", unique: true
   end
 
@@ -72,8 +73,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   end
 
   create_table "account_notes", force: :cascade do |t|
-    t.bigint "account_id", null: false
-    t.bigint "target_account_id", null: false
+    t.bigint "account_id"
+    t.bigint "target_account_id"
     t.text "comment", null: false
     t.datetime "created_at", precision: nil, null: false
     t.datetime "updated_at", precision: nil, null: false
@@ -82,8 +83,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   end
 
   create_table "account_pins", force: :cascade do |t|
-    t.bigint "account_id", null: false
-    t.bigint "target_account_id", null: false
+    t.bigint "account_id"
+    t.bigint "target_account_id"
     t.datetime "created_at", precision: nil, null: false
     t.datetime "updated_at", precision: nil, null: false
     t.index ["account_id", "target_account_id"], name: "index_account_pins_on_account_id_and_target_account_id", unique: true
@@ -98,6 +99,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.integer "followers_count", default: 0, null: false
     t.integer "following_count", default: 0, null: false
     t.index ["account_id", "relationship_severance_event_id"], name: "idx_on_account_id_relationship_severance_event_id_7bd82bf20e", unique: true
+    t.index ["account_id"], name: "index_account_relationship_severance_events_on_account_id"
     t.index ["relationship_severance_event_id"], name: "idx_on_relationship_severance_event_id_403f53e707"
   end
 
@@ -220,7 +222,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   end
 
   create_table "admin_action_logs", force: :cascade do |t|
-    t.bigint "account_id", null: false
+    t.bigint "account_id"
     t.string "action", default: "", null: false
     t.string "target_type"
     t.bigint "target_id"
@@ -234,8 +236,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   end
 
   create_table "announcement_mutes", force: :cascade do |t|
-    t.bigint "account_id", null: false
-    t.bigint "announcement_id", null: false
+    t.bigint "account_id"
+    t.bigint "announcement_id"
     t.datetime "created_at", precision: nil, null: false
     t.datetime "updated_at", precision: nil, null: false
     t.index ["account_id", "announcement_id"], name: "index_announcement_mutes_on_account_id_and_announcement_id", unique: true
@@ -243,8 +245,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   end
 
   create_table "announcement_reactions", force: :cascade do |t|
-    t.bigint "account_id", null: false
-    t.bigint "announcement_id", null: false
+    t.bigint "account_id"
+    t.bigint "announcement_id"
     t.string "name", default: "", null: false
     t.bigint "custom_emoji_id"
     t.datetime "created_at", precision: nil, null: false
@@ -265,14 +267,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.datetime "updated_at", precision: nil, null: false
     t.datetime "published_at", precision: nil
     t.bigint "status_ids", array: true
-    t.datetime "notification_sent_at"
-  end
-
-  create_table "annual_report_statuses_per_account_counts", force: :cascade do |t|
-    t.integer "year", null: false
-    t.bigint "account_id", null: false
-    t.bigint "statuses_count", null: false
-    t.index ["year", "account_id"], name: "idx_on_year_account_id_ff3e167cef", unique: true
   end
 
   create_table "antenna_accounts", force: :cascade do |t|
@@ -310,7 +304,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
 
   create_table "antennas", force: :cascade do |t|
     t.bigint "account_id", null: false
-    t.bigint "list_id", default: 0, null: false
+    t.bigint "list_id", null: false
     t.string "title", default: "", null: false
     t.jsonb "keywords"
     t.jsonb "exclude_keywords"
@@ -330,7 +324,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.boolean "ignore_reblog", default: false, null: false
     t.boolean "insert_feeds", default: false, null: false
     t.boolean "ltl", default: false, null: false
-    t.boolean "favourite", default: true, null: false
     t.index ["account_id"], name: "index_antennas_on_account_id"
     t.index ["any_accounts"], name: "index_antennas_on_any_accounts"
     t.index ["any_domains"], name: "index_antennas_on_any_domains"
@@ -540,10 +533,11 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.datetime "updated_at", null: false
     t.index ["custom_filter_id"], name: "index_custom_filter_statuses_on_custom_filter_id"
     t.index ["status_id", "custom_filter_id"], name: "index_custom_filter_statuses_on_status_id_and_custom_filter_id", unique: true
+    t.index ["status_id"], name: "index_custom_filter_statuses_on_status_id"
   end
 
   create_table "custom_filters", force: :cascade do |t|
-    t.bigint "account_id", null: false
+    t.bigint "account_id"
     t.datetime "expires_at", precision: nil
     t.text "phrase", default: "", null: false
     t.string "context", default: [], null: false, array: true
@@ -610,32 +604,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.index ["uri"], name: "index_emoji_reactions_on_uri", unique: true
   end
 
-  create_table "fasp_debug_callbacks", force: :cascade do |t|
-    t.bigint "fasp_provider_id", null: false
-    t.string "ip", null: false
-    t.text "request_body", null: false
-    t.datetime "created_at", null: false
-    t.datetime "updated_at", null: false
-    t.index ["fasp_provider_id"], name: "index_fasp_debug_callbacks_on_fasp_provider_id"
-  end
-
-  create_table "fasp_providers", force: :cascade do |t|
-    t.boolean "confirmed", default: false, null: false
-    t.string "name", null: false
-    t.string "base_url", null: false
-    t.string "sign_in_url"
-    t.string "remote_identifier", null: false
-    t.string "provider_public_key_pem", null: false
-    t.string "server_private_key_pem", null: false
-    t.jsonb "capabilities", default: [], null: false
-    t.jsonb "privacy_policy"
-    t.string "contact_email"
-    t.string "fediverse_account"
-    t.datetime "created_at", null: false
-    t.datetime "updated_at", null: false
-    t.index ["base_url"], name: "index_fasp_providers_on_base_url", unique: true
-  end
-
   create_table "favourites", force: :cascade do |t|
     t.datetime "created_at", precision: nil, null: false
     t.datetime "updated_at", precision: nil, null: false
@@ -739,6 +707,19 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.index ["user_id"], name: "index_identities_on_user_id"
   end
 
+  create_table "imports", force: :cascade do |t|
+    t.integer "type", null: false
+    t.boolean "approved", default: false, null: false
+    t.datetime "created_at", precision: nil, null: false
+    t.datetime "updated_at", precision: nil, null: false
+    t.string "data_file_name"
+    t.string "data_content_type"
+    t.datetime "data_updated_at", precision: nil
+    t.bigint "account_id", null: false
+    t.boolean "overwrite", default: false, null: false
+    t.integer "data_file_size"
+  end
+
   create_table "instance_infos", force: :cascade do |t|
     t.string "domain", default: "", null: false
     t.string "software", default: "", null: false
@@ -801,7 +782,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.integer "replies_policy", default: 0, null: false
     t.boolean "exclusive", default: false, null: false
     t.boolean "notify", default: false, null: false
-    t.boolean "favourite", default: true, null: false
     t.index ["account_id"], name: "index_lists_on_account_id"
   end
 
@@ -818,7 +798,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   end
 
   create_table "markers", force: :cascade do |t|
-    t.bigint "user_id", null: false
+    t.bigint "user_id"
     t.string "timeline", default: "", null: false
     t.bigint "last_read_id", default: 0, null: false
     t.integer "lock_version", default: 0, null: false
@@ -1090,8 +1070,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   end
 
   create_table "poll_votes", force: :cascade do |t|
-    t.bigint "account_id", null: false
-    t.bigint "poll_id", null: false
+    t.bigint "account_id"
+    t.bigint "poll_id"
     t.integer "choice", default: 0, null: false
     t.datetime "created_at", precision: nil, null: false
     t.datetime "updated_at", precision: nil, null: false
@@ -1101,8 +1081,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   end
 
   create_table "polls", force: :cascade do |t|
-    t.bigint "account_id", null: false
-    t.bigint "status_id", null: false
+    t.bigint "account_id"
+    t.bigint "status_id"
     t.datetime "expires_at", precision: nil
     t.string "options", default: [], null: false, array: true
     t.bigint "cached_tallies", default: [], null: false, array: true
@@ -1250,7 +1230,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   end
 
   create_table "scheduled_statuses", force: :cascade do |t|
-    t.bigint "account_id", null: false
+    t.bigint "account_id"
     t.datetime "scheduled_at", precision: nil
     t.jsonb "params"
     t.index ["account_id"], name: "index_scheduled_statuses_on_account_id"
@@ -1283,9 +1263,11 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   create_table "settings", force: :cascade do |t|
     t.string "var", null: false
     t.text "value"
+    t.string "thing_type"
     t.datetime "created_at", precision: nil
     t.datetime "updated_at", precision: nil
-    t.index ["var"], name: "index_settings_on_var", unique: true
+    t.bigint "thing_id"
+    t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true
   end
 
   create_table "severed_relationships", force: :cascade do |t|
@@ -1390,8 +1372,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.integer "emoji_reactions_count", default: 0, null: false
     t.integer "emoji_reaction_accounts_count", default: 0, null: false
     t.integer "status_referred_by_count", default: 0, null: false
-    t.bigint "untrusted_favourites_count"
-    t.bigint "untrusted_reblogs_count"
     t.index ["status_id"], name: "index_status_stats_on_status_id", unique: true
   end
 
@@ -1433,14 +1413,13 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.boolean "markdown", default: false
     t.integer "limited_scope"
     t.bigint "quote_of_id"
-    t.datetime "fetched_replies_at"
     t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)"
     t.index ["account_id", "reblog_of_id", "deleted_at", "searchability"], name: "index_statuses_for_get_following_accounts_to_search", where: "((deleted_at IS NULL) AND (reblog_of_id IS NULL) AND (searchability = ANY (ARRAY[0, 10, 1])))"
     t.index ["account_id"], name: "index_statuses_on_account_id"
     t.index ["conversation_id"], name: "index_statuses_on_conversation_id"
     t.index ["deleted_at"], name: "index_statuses_on_deleted_at", where: "(deleted_at IS NOT NULL)"
     t.index ["id", "account_id"], name: "index_statuses_local_20231213", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = ANY (ARRAY[0, 10, 11])) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
-    t.index ["id", "language", "account_id"], name: "index_statuses_public_20250210", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = ANY (ARRAY[0, 10, 11])) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
+    t.index ["id", "account_id"], name: "index_statuses_public_20231213", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = ANY (ARRAY[0, 10, 11])) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
     t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id", where: "(in_reply_to_account_id IS NOT NULL)"
     t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id", where: "(in_reply_to_id IS NOT NULL)"
     t.index ["quote_of_id", "account_id"], name: "index_statuses_on_quote_of_id_and_account_id"
@@ -1464,15 +1443,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.index ["tag_id"], name: "index_tag_follows_on_tag_id"
   end
 
-  create_table "tag_trends", force: :cascade do |t|
-    t.bigint "tag_id", null: false
-    t.float "score", default: 0.0, null: false
-    t.integer "rank", default: 0, null: false
-    t.boolean "allowed", default: false, null: false
-    t.string "language", default: "", null: false
-    t.index ["tag_id", "language"], name: "index_tag_trends_on_tag_id_and_language", unique: true
-  end
-
   create_table "tags", force: :cascade do |t|
     t.string "name", default: "", null: false
     t.datetime "created_at", precision: nil, null: false
@@ -1489,19 +1459,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.index "lower((name)::text) text_pattern_ops", name: "index_tags_on_name_lower_btree", unique: true
   end
 
-  create_table "terms_of_services", force: :cascade do |t|
-    t.text "text", default: "", null: false
-    t.text "changelog", default: "", null: false
-    t.datetime "published_at"
-    t.datetime "notification_sent_at"
-    t.datetime "created_at", null: false
-    t.datetime "updated_at", null: false
-    t.date "effective_date"
-    t.index ["effective_date"], name: "index_terms_of_services_on_effective_date", unique: true, where: "(effective_date IS NOT NULL)"
-  end
-
   create_table "tombstones", force: :cascade do |t|
-    t.bigint "account_id", null: false
+    t.bigint "account_id"
     t.string "uri", null: false
     t.datetime "created_at", precision: nil, null: false
     t.datetime "updated_at", precision: nil, null: false
@@ -1518,7 +1477,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   end
 
   create_table "user_invite_requests", force: :cascade do |t|
-    t.bigint "user_id", null: false
+    t.bigint "user_id"
     t.text "text"
     t.datetime "created_at", precision: nil, null: false
     t.datetime "updated_at", precision: nil, null: false
@@ -1572,7 +1531,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.text "settings"
     t.string "time_zone"
     t.string "otp_secret"
-    t.datetime "age_verified_at"
     t.index ["account_id"], name: "index_users_on_account_id"
     t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
     t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id", where: "(created_by_application_id IS NOT NULL)"
@@ -1591,7 +1549,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.datetime "updated_at", precision: nil, null: false
     t.bigint "access_token_id"
     t.bigint "user_id"
-    t.boolean "standard", default: false, null: false
     t.index ["access_token_id"], name: "index_web_push_subscriptions_on_access_token_id", where: "(access_token_id IS NOT NULL)"
     t.index ["user_id"], name: "index_web_push_subscriptions_on_user_id"
   end
@@ -1614,6 +1571,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
     t.datetime "updated_at", precision: nil, null: false
     t.index ["external_id"], name: "index_webauthn_credentials_on_external_id", unique: true
     t.index ["user_id", "nickname"], name: "index_webauthn_credentials_on_user_id_and_nickname", unique: true
+    t.index ["user_id"], name: "index_webauthn_credentials_on_user_id"
   end
 
   create_table "webhooks", force: :cascade do |t|
@@ -1634,8 +1592,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade
   add_foreign_key "account_migrations", "accounts", column: "target_account_id", on_delete: :nullify
   add_foreign_key "account_migrations", "accounts", on_delete: :cascade
-  add_foreign_key "account_moderation_notes", "accounts", column: "target_account_id", on_delete: :cascade
-  add_foreign_key "account_moderation_notes", "accounts", on_delete: :cascade
+  add_foreign_key "account_moderation_notes", "accounts"
+  add_foreign_key "account_moderation_notes", "accounts", column: "target_account_id"
   add_foreign_key "account_notes", "accounts", column: "target_account_id", on_delete: :cascade
   add_foreign_key "account_notes", "accounts", on_delete: :cascade
   add_foreign_key "account_pins", "accounts", column: "target_account_id", on_delete: :cascade
@@ -1693,7 +1651,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   add_foreign_key "emoji_reactions", "accounts", on_delete: :cascade
   add_foreign_key "emoji_reactions", "custom_emojis", on_delete: :cascade
   add_foreign_key "emoji_reactions", "statuses", on_delete: :cascade
-  add_foreign_key "fasp_debug_callbacks", "fasp_providers"
   add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade
   add_foreign_key "favourites", "statuses", name: "fk_b0e856845e", on_delete: :cascade
   add_foreign_key "featured_tags", "accounts", on_delete: :cascade
@@ -1707,6 +1664,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade
   add_foreign_key "generated_annual_reports", "accounts"
   add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade
+  add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade
   add_foreign_key "invites", "users", on_delete: :cascade
   add_foreign_key "list_accounts", "accounts", on_delete: :cascade
   add_foreign_key "list_accounts", "follow_requests", on_delete: :cascade
@@ -1782,7 +1740,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   add_foreign_key "statuses_tags", "tags", name: "fk_3081861e21", on_delete: :cascade
   add_foreign_key "tag_follows", "accounts", on_delete: :cascade
   add_foreign_key "tag_follows", "tags", on_delete: :cascade
-  add_foreign_key "tag_trends", "tags", on_delete: :cascade
   add_foreign_key "tombstones", "accounts", on_delete: :cascade
   add_foreign_key "user_invite_requests", "users", on_delete: :cascade
   add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade
@@ -1792,7 +1749,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
   add_foreign_key "web_push_subscriptions", "oauth_access_tokens", column: "access_token_id", on_delete: :cascade
   add_foreign_key "web_push_subscriptions", "users", on_delete: :cascade
   add_foreign_key "web_settings", "users", name: "fk_11910667b2", on_delete: :cascade
-  add_foreign_key "webauthn_credentials", "users", on_delete: :cascade
+  add_foreign_key "webauthn_credentials", "users"
 
   create_view "instances", materialized: true, sql_definition: <<-SQL
       WITH domain_counts(domain, accounts_count) AS (
diff --git a/dist/nginx.conf b/dist/nginx.conf
index 3ab9bb66a2..5bb9903864 100644
--- a/dist/nginx.conf
+++ b/dist/nginx.conf
@@ -63,7 +63,6 @@ server {
   gzip_buffers 16 8k;
   gzip_http_version 1.1;
   gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml image/x-icon;
-  gzip_static on;
 
   location / {
     try_files $uri @proxy;
diff --git a/docker-compose.yml b/docker-compose.yml
index 0ec0c43bb6..10a3cac99d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,5 @@
 # This file is designed for production server deployment, not local development work
-# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/docs/DEVELOPMENT.md#docker
+# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker
 
 services:
   db:
@@ -59,7 +59,7 @@ services:
   web:
     # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
     build: .
-    image: kmyblue:18.0-dev
+    image: kmyblue:15.11-lts
     restart: always
     env_file: .env.production
     command: bundle exec puma -C config/puma.rb
@@ -83,7 +83,7 @@ services:
     build:
       dockerfile: ./streaming/Dockerfile
       context: .
-    image: kmyblue-streaming:18.0-dev
+    image: kmyblue-streaming:15.11-lts
     restart: always
     env_file: .env.production
     command: node ./streaming/index.js
@@ -101,7 +101,7 @@ services:
 
   sidekiq:
     build: .
-    image: kmyblue:18.0-dev
+    image: kmyblue:15.11-lts
     restart: always
     env_file: .env.production
     command: bundle exec sidekiq
diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md
deleted file mode 100644
index bbbad6f1e1..0000000000
--- a/docs/DEVELOPMENT.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# Development
-
-## Overview
-
-Before starting local development, read the [CONTRIBUTING] guide to understand
-what changes are desirable and what general processes to use.
-
-## Environments
-
-### Vagrant
-
-A **Vagrant** configuration is included for development purposes. To use it,
-complete the following steps:
-
-- Install Vagrant and Virtualbox
-- Install the `vagrant-hostsupdater` plugin:
-  `vagrant plugin install vagrant-hostsupdater`
-- Run `vagrant up`
-- Run `vagrant ssh -c "cd /vagrant && bin/dev"`
-- Open `http://mastodon.local` in your browser
-
-### macOS
-
-To set up **macOS** for native development, complete the following steps:
-
-- Install [Homebrew] and run:
-  `brew install postgresql@14 redis imagemagick libidn nvm`
-  to install the required project dependencies
-- Use a Ruby version manager to activate the ruby in `.ruby-version` and run
-  `nvm use` to activate the node version from `.nvmrc`
-- Run the `bin/setup` script, which will install the required ruby gems and node
-  packages and prepare the database for local development
-- Finally, run the `bin/dev` script which will launch services via `overmind`
-  (if installed) or `foreman`
-
-### Docker
-
-For production hosting and deployment with **Docker**, use the `Dockerfile` and
-`docker-compose.yml` in the project root directory.
-
-For local development, install and launch [Docker], and run:
-
-```shell
-docker compose -f .devcontainer/compose.yaml up -d
-docker compose -f .devcontainer/compose.yaml exec app bin/setup
-docker compose -f .devcontainer/compose.yaml exec app bin/dev
-```
-
-### Dev Containers
-
-Within IDEs that support the [Development Containers] specification, start the
-"Mastodon on local machine" container from the editor. The necessary `docker
-compose` commands to build and setup the container should run automatically. For
-**Visual Studio Code** this requires installing the [Dev Container extension].
-
-### GitHub Codespaces
-
-[GitHub Codespaces] provides a web-based version of VS Code and a cloud hosted
-development environment configured with the software needed for this project.
-
-[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)][codespace]
-
-- Click the button to create a new codespace, and confirm the options
-- Wait for the environment to build (takes a few minutes)
-- When the editor is ready, run `bin/dev` in the terminal
-- Wait for an _Open in Browser_ prompt. This will open Mastodon
-- On the _Ports_ tab "stream" setting change _Port visibility_ → _Public_
-
-[codespace]: https://codespaces.new/mastodon/mastodon?quickstart=1&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json
-[CONTRIBUTING]: ../CONTRIBUTING.md
-[Dev Container extension]: https://containers.dev/supporting#dev-containers
-[Development Containers]: https://containers.dev/supporting
-[Docker]: https://docs.docker.com
-[GitHub Codespaces]: https://docs.github.com/en/codespaces
-[Homebrew]: https://brew.sh
diff --git a/eslint.config.mjs b/eslint.config.mjs
deleted file mode 100644
index 99279b56d7..0000000000
--- a/eslint.config.mjs
+++ /dev/null
@@ -1,395 +0,0 @@
-// @ts-check
-
-import js from '@eslint/js';
-import { globalIgnores } from 'eslint/config';
-import formatjs from 'eslint-plugin-formatjs';
-// @ts-expect-error -- No typings
-import importPlugin from 'eslint-plugin-import';
-import jsdoc from 'eslint-plugin-jsdoc';
-import jsxA11Y from 'eslint-plugin-jsx-a11y';
-import promisePlugin from 'eslint-plugin-promise';
-import react from 'eslint-plugin-react';
-import reactHooks from 'eslint-plugin-react-hooks';
-import globals from 'globals';
-import tseslint from 'typescript-eslint';
-
-/** @type {import('typescript-eslint').ConfigArray} */
-export const baseConfig = [
-  js.configs.recommended,
-  importPlugin.flatConfigs.recommended,
-  jsdoc.configs['flat/recommended'],
-  promisePlugin.configs['flat/recommended'],
-  {
-    linterOptions: {
-      reportUnusedDisableDirectives: 'error',
-      reportUnusedInlineConfigs: 'error',
-    },
-    rules: {
-      'consistent-return': 'error',
-      'dot-notation': 'error',
-
-      eqeqeq: [
-        'error',
-        'always',
-        {
-          null: 'ignore',
-        },
-      ],
-
-      'no-console': [
-        'warn',
-        {
-          allow: ['error', 'warn'],
-        },
-      ],
-
-      'no-empty': [
-        'error',
-        {
-          allowEmptyCatch: true,
-        },
-      ],
-
-      'no-restricted-properties': [
-        'error',
-        {
-          property: 'substring',
-          message: 'Use .slice instead of .substring.',
-        },
-        {
-          property: 'substr',
-          message: 'Use .slice instead of .substr.',
-        },
-      ],
-
-      'no-unused-expressions': 'error',
-      'no-unused-vars': 'off',
-
-      'valid-typeof': 'error',
-
-      'import/extensions': [
-        'error',
-        'always',
-        {
-          js: 'never',
-          jsx: 'never',
-          mjs: 'never',
-          ts: 'never',
-          mts: 'never',
-          tsx: 'never',
-        },
-      ],
-      'import/first': 'error',
-      'import/newline-after-import': 'error',
-      'import/no-anonymous-default-export': 'error',
-      'import/no-amd': 'error',
-      'import/no-commonjs': 'error',
-      'import/no-import-module-exports': 'error',
-      'import/no-relative-packages': 'error',
-      'import/no-self-import': 'error',
-      'import/no-useless-path-segments': 'error',
-      'import/order': [
-        'error',
-        {
-          alphabetize: {
-            order: 'asc',
-          },
-
-          'newlines-between': 'always',
-
-          groups: [
-            'builtin',
-            'external',
-            'internal',
-            'parent',
-            ['index', 'sibling'],
-            'object',
-          ],
-
-          pathGroups: [
-            {
-              pattern: '{react,react-dom,react-dom/client,prop-types}',
-              group: 'builtin',
-              position: 'after',
-            },
-            {
-              pattern: '{react-intl,intl-messageformat}',
-              group: 'builtin',
-              position: 'after',
-            },
-            {
-              pattern:
-                '{classnames,react-helmet,react-router,react-router-dom}',
-              group: 'external',
-              position: 'before',
-            },
-            {
-              pattern:
-                '{immutable,@reduxjs/toolkit,react-redux,react-immutable-proptypes,react-immutable-pure-component}',
-              group: 'external',
-              position: 'before',
-            },
-            {
-              pattern: '{mastodon/**}',
-              group: 'internal',
-              position: 'after',
-            },
-          ],
-
-          pathGroupsExcludedImportTypes: [],
-        },
-      ],
-
-      'jsdoc/check-types': 'off',
-      'jsdoc/no-undefined-types': 'off',
-      'jsdoc/require-jsdoc': 'off',
-      'jsdoc/require-param-description': 'off',
-      'jsdoc/require-property-description': 'off',
-      'jsdoc/require-returns-description': 'off',
-      'jsdoc/require-returns': 'off',
-
-      'promise/always-return': 'off',
-      'promise/catch-or-return': [
-        'error',
-        {
-          allowFinally: true,
-        },
-      ],
-      'promise/no-callback-in-promise': 'off',
-      'promise/no-nesting': 'off',
-      'promise/no-promise-in-callback': 'off',
-    },
-  },
-];
-
-export default tseslint.config([
-  baseConfig,
-  globalIgnores([
-    'build/**/*',
-    'coverage/**/*',
-    'db/**/*',
-    'lib/**/*',
-    'log/**/*',
-    'node_modules/**/*',
-    'public/**/*',
-    '!public/embed.js',
-    'spec/**/*',
-    'tmp/**/*',
-    'vendor/**/*',
-    'streaming/**/*',
-  ]),
-  react.configs.flat.recommended,
-  react.configs.flat['jsx-runtime'],
-  reactHooks.configs['recommended-latest'],
-  jsxA11Y.flatConfigs.recommended,
-  importPlugin.flatConfigs.react,
-  // @ts-expect-error -- For some reason the formatjs package exports an empty object?
-  formatjs.configs.strict,
-  {
-    languageOptions: {
-      globals: {
-        ...globals.browser,
-      },
-
-      parser: tseslint.parser,
-      ecmaVersion: 2021,
-      sourceType: 'module',
-    },
-
-    settings: {
-      react: {
-        version: 'detect',
-      },
-
-      'import/ignore': ['node_modules', '\\.(css|scss|json)$'],
-
-      'import/resolver': {
-        typescript: {},
-      },
-    },
-
-    rules: {
-      'no-restricted-syntax': [
-        'error',
-        {
-          // eslint-disable-next-line no-restricted-syntax
-          selector: 'Literal[value=/•/], JSXText[value=/•/]',
-          // eslint-disable-next-line no-restricted-syntax
-          message: "Use '·' (middle dot) instead of '•' (bullet)",
-        },
-      ],
-
-      'formatjs/enforce-description': 'off', // description values not currently used
-      'formatjs/enforce-id': 'off', // Explicit IDs are used in the project
-      'formatjs/enforce-placeholders': 'off', // Issues in short_number.jsx
-      'formatjs/no-invalid-icu': 'error',
-      'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
-      'formatjs/no-multiple-plurals': 'off', // Should be looked at
-
-      'jsx-a11y/click-events-have-key-events': 'off',
-      'jsx-a11y/label-has-associated-control': 'off',
-      'jsx-a11y/media-has-caption': 'off',
-      'jsx-a11y/no-autofocus': 'off',
-      'jsx-a11y/no-interactive-element-to-noninteractive-role': 'off',
-      'jsx-a11y/no-noninteractive-tabindex': 'off',
-      'jsx-a11y/no-static-element-interactions': [
-        'warn',
-        {
-          handlers: ['onClick'],
-        },
-      ],
-
-      'import/no-extraneous-dependencies': [
-        'error',
-        {
-          devDependencies: [
-            'eslint.config.mjs',
-            'config/webpack/**',
-            'app/javascript/mastodon/performance.js',
-            'app/javascript/mastodon/test_setup.js',
-            'app/javascript/**/__tests__/**',
-          ],
-        },
-      ],
-      'import/no-webpack-loader-syntax': 'error',
-
-      'react/jsx-filename-extension': [
-        'error',
-        {
-          extensions: ['.jsx', 'tsx'],
-        },
-      ],
-
-      'react/jsx-boolean-value': 'error',
-      'react/display-name': 'off',
-      'react/jsx-fragments': ['error', 'syntax'],
-      'react/jsx-equals-spacing': 'error',
-      'react/jsx-no-bind': 'error',
-      'react/jsx-no-useless-fragment': 'error',
-      'react/jsx-no-target-blank': [
-        'error',
-        {
-          allowReferrer: true,
-        },
-      ],
-      'react/jsx-tag-spacing': 'error',
-      'react/jsx-wrap-multilines': 'error',
-      'react/self-closing-comp': 'error',
-    },
-  },
-  {
-    files: [
-      'app/javascript/mastodon/common.js',
-      'app/javascript/mastodon/features/emoji/unicode_to_unified_name.js',
-      'app/javascript/mastodon/features/emoji/emoji_compressed.js',
-      'app/javascript/mastodon/features/emoji/unicode_to_filename.js',
-      'app/javascript/mastodon/service_worker/web_push_locales.js',
-      '**/*.config.js',
-      '**/.*rc.js',
-      '**/ide-helper.js',
-      'config/webpack/**/*',
-      'config/formatjs-formatter.js',
-    ],
-
-    languageOptions: {
-      globals: {
-        ...globals.commonjs,
-        ...globals.node,
-      },
-
-      ecmaVersion: 5,
-      sourceType: 'commonjs',
-    },
-
-    rules: {
-      'import/no-commonjs': 'off',
-    },
-  },
-  {
-    files: ['**/*.ts', '**/*.tsx'],
-
-    extends: [
-      tseslint.configs.strictTypeChecked,
-      tseslint.configs.stylisticTypeChecked,
-      react.configs.flat.recommended,
-      react.configs.flat['jsx-runtime'],
-      reactHooks.configs['recommended-latest'],
-      jsxA11Y.flatConfigs.recommended,
-      importPlugin.flatConfigs.react,
-      importPlugin.flatConfigs.typescript,
-      jsdoc.configs['flat/recommended-typescript'],
-    ],
-
-    languageOptions: {
-      parserOptions: {
-        projectService: true,
-      },
-    },
-
-    rules: {
-      // This is not needed as we use noImplicitReturns, which handles this in addition to understanding types
-      'consistent-return': 'off',
-
-      'formatjs/enforce-plural-rules': 'off',
-
-      'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
-      'import/no-default-export': 'warn',
-
-      'jsdoc/require-jsdoc': 'off',
-
-      'react/prefer-stateless-function': 'warn',
-      'react/function-component-definition': [
-        'error',
-        {
-          namedComponents: 'arrow-function',
-        },
-      ],
-      'react/prop-types': 'off',
-
-      '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
-      '@typescript-eslint/consistent-type-exports': 'error',
-      '@typescript-eslint/consistent-type-imports': 'error',
-      '@typescript-eslint/prefer-nullish-coalescing': [
-        'error',
-        {
-          ignorePrimitives: {
-            boolean: true,
-          },
-        },
-      ],
-      '@typescript-eslint/no-restricted-imports': [
-        'warn',
-        {
-          name: 'react-redux',
-          importNames: ['useSelector', 'useDispatch'],
-          message:
-            'Use typed hooks `useAppDispatch` and `useAppSelector` instead.',
-        },
-      ],
-      '@typescript-eslint/no-unused-vars': [
-        'error',
-        {
-          vars: 'all',
-          args: 'after-used',
-          destructuredArrayIgnorePattern: '^_',
-          ignoreRestSiblings: true,
-        },
-      ],
-      '@typescript-eslint/restrict-template-expressions': [
-        'warn',
-        {
-          allowNumber: true,
-        },
-      ],
-    },
-  },
-  {
-    files: ['**/__tests__/*.js', '**/__tests__/*.jsx'],
-
-    languageOptions: {
-      globals: {
-        ...globals.jest,
-      },
-    },
-  },
-]);
diff --git a/lib/active_record/batches.rb b/lib/active_record/batches.rb
index fb0b13954b..7960b6e10d 100644
--- a/lib/active_record/batches.rb
+++ b/lib/active_record/batches.rb
@@ -13,9 +13,7 @@ module ActiveRecord
 
       column_names.unshift(primary_key)
 
-      cursor = Array(primary_key)
-
-      relation = relation.reorder(build_batch_orders(cursor, order).to_h).limit(batch_limit)
+      relation = relation.reorder(build_batch_orders(order).to_h).limit(batch_limit)
       relation.skip_query_cache!
 
       batch_relation = relation
diff --git a/lib/active_record/database_tasks_extensions.rb b/lib/active_record/database_tasks_extensions.rb
index b95421a462..d52186113c 100644
--- a/lib/active_record/database_tasks_extensions.rb
+++ b/lib/active_record/database_tasks_extensions.rb
@@ -1,6 +1,5 @@
 # frozen_string_literal: true
 
-require_relative '../mastodon/database'
 require_relative '../mastodon/snowflake'
 
 module ActiveRecord
@@ -9,8 +8,6 @@ module ActiveRecord
       original_load_schema = instance_method(:load_schema)
 
       define_method(:load_schema) do |db_config, *args|
-        Mastodon::Database.add_post_migrate_path_to_rails(force: true)
-
         ActiveRecord::Base.establish_connection(db_config)
         Mastodon::Snowflake.define_timestamp_id
 
diff --git a/lib/active_record/with_recursive.rb b/lib/active_record/with_recursive.rb
new file mode 100644
index 0000000000..4bd3e81eed
--- /dev/null
+++ b/lib/active_record/with_recursive.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+# Add support for writing recursive CTEs in ActiveRecord
+
+# Initially from Lorin Thwaits (https://github.com/lorint) as per comment:
+# https://github.com/vlado/activerecord-cte/issues/16#issuecomment-1433043310
+
+# Modified from the above code to change the signature to
+# `with_recursive(hash)` and extending CTE hash values to also includes arrays
+# of values that get turned into UNION ALL expressions.
+
+# This implementation has been merged in Rails: https://github.com/rails/rails/pull/51601
+
+module ActiveRecord
+  module QueryMethodsExtensions
+    def with_recursive(*args)
+      @with_is_recursive = true
+      check_if_method_has_arguments!(__callee__, args)
+      spawn.with_recursive!(*args)
+    end
+
+    # Like #with_recursive but modifies the relation in place.
+    def with_recursive!(*args) # :nodoc:
+      self.with_values += args
+      @with_is_recursive = true
+      self
+    end
+
+    private
+
+    def build_with(arel)
+      return if with_values.empty?
+
+      with_statements = with_values.map do |with_value|
+        raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
+
+        build_with_value_from_hash(with_value)
+      end
+
+      # Was:  arel.with(with_statements)
+      @with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
+    end
+
+    def build_with_value_from_hash(hash)
+      hash.map do |name, value|
+        Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
+      end
+    end
+
+    def build_with_expression_from_value(value)
+      case value
+      when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
+      when ActiveRecord::Relation then value.arel
+      when Arel::SelectManager then value
+      when Array then value.map { |e| build_with_expression_from_value(e) }.reduce { |result, value| Arel::Nodes::UnionAll.new(result, value) }
+      else
+        raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
+      end
+    end
+  end
+end
+
+ActiveSupport.on_load(:active_record) do
+  ActiveRecord::QueryMethods.prepend(ActiveRecord::QueryMethodsExtensions)
+end
diff --git a/lib/arel/union_parenthesizing.rb b/lib/arel/union_parenthesizing.rb
new file mode 100644
index 0000000000..852d8e92d8
--- /dev/null
+++ b/lib/arel/union_parenthesizing.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+# Fix an issue with `LIMIT` ocurring on the left side of a `UNION` causing syntax errors.
+# See https://github.com/rails/rails/issues/40181
+
+# The fix has been merged in ActiveRecord: https://github.com/rails/rails/pull/51549
+# TODO: drop this when available in ActiveRecord
+
+# rubocop:disable all -- This is a mostly vendored file
+
+module Arel
+  module Visitors
+    class ToSql
+      private
+
+        def infix_value_with_paren(o, collector, value, suppress_parens = false)
+          collector << "( " unless suppress_parens
+          collector = if o.left.class == o.class
+            infix_value_with_paren(o.left, collector, value, true)
+          else
+            select_parentheses o.left, collector, false # Changed from `visit o.left, collector`
+          end
+          collector << value
+          collector = if o.right.class == o.class
+            infix_value_with_paren(o.right, collector, value, true)
+          else
+            select_parentheses o.right, collector, false # Changed from `visit o.right, collector`
+          end
+          collector << " )" unless suppress_parens
+          collector
+        end
+
+        def select_parentheses(o, collector, always_wrap_selects = true)
+          if o.is_a?(Nodes::SelectStatement) && (always_wrap_selects || require_parentheses?(o))
+            collector << "("
+            visit o, collector
+            collector << ")"
+            collector
+          else
+            visit o, collector
+          end
+        end
+
+        def require_parentheses?(o)
+          !o.orders.empty? || o.limit || o.offset
+        end
+    end
+  end
+end
+
+# rubocop:enable all
diff --git a/lib/devise/strategies/two_factor_ldap_authenticatable.rb b/lib/devise/strategies/two_factor_ldap_authenticatable.rb
index 2efd1323a8..c8258deb16 100644
--- a/lib/devise/strategies/two_factor_ldap_authenticatable.rb
+++ b/lib/devise/strategies/two_factor_ldap_authenticatable.rb
@@ -23,7 +23,7 @@ module Devise
       protected
 
       def valid_params?
-        params[scope].is_a?(Hash) && params[scope][:password].present?
+        params[scope] && params[scope][:password].present?
       end
     end
   end
diff --git a/lib/devise/strategies/two_factor_pam_authenticatable.rb b/lib/devise/strategies/two_factor_pam_authenticatable.rb
index 2164b03234..a9db1b6a29 100644
--- a/lib/devise/strategies/two_factor_pam_authenticatable.rb
+++ b/lib/devise/strategies/two_factor_pam_authenticatable.rb
@@ -22,7 +22,7 @@ module Devise
       protected
 
       def valid_params?
-        params[scope].is_a?(Hash) && params[scope][:password].present?
+        params[scope] && params[scope][:password].present?
       end
     end
   end
diff --git a/lib/exceptions.rb b/lib/exceptions.rb
index 93fcc38dce..d3b92f4a09 100644
--- a/lib/exceptions.rb
+++ b/lib/exceptions.rb
@@ -12,7 +12,6 @@ module Mastodon
   class RateLimitExceededError < Error; end
   class SyntaxError < Error; end
   class InvalidParameterError < Error; end
-  class SignatureVerificationError < Error; end
 
   class UnexpectedResponseError < Error
     attr_reader :response
@@ -36,11 +35,4 @@ module Mastodon
       super()
     end
   end
-
-  HTTP_CONNECTION_ERRORS = [
-    HTTP::ConnectionError,
-    HTTP::Error,
-    HTTP::TimeoutError,
-    OpenSSL::SSL::SSLError,
-  ].freeze
 end
diff --git a/lib/mastodon/cli/accounts.rb b/lib/mastodon/cli/accounts.rb
index 41127741ef..08a28e5f5c 100644
--- a/lib/mastodon/cli/accounts.rb
+++ b/lib/mastodon/cli/accounts.rb
@@ -1,5 +1,6 @@
 # frozen_string_literal: true
 
+require 'set'
 require_relative 'base'
 
 module Mastodon::CLI
@@ -164,7 +165,7 @@ module Mastodon::CLI
       user.disabled = false if options[:enable]
       user.disabled = true if options[:disable]
       user.approved = true if options[:approve]
-      user.disable_two_factor! if options[:disable_2fa]
+      user.otp_required_for_login = false if options[:disable_2fa]
 
       if user.save
         user.confirm if options[:confirm]
@@ -304,7 +305,7 @@ module Mastodon::CLI
 
         begin
           code = Request.new(:head, account.uri).perform(&:code)
-        rescue *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::PrivateNetworkAddressError
+        rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Mastodon::PrivateNetworkAddressError
           skip_domains << account.domain
         end
 
@@ -321,9 +322,7 @@ module Mastodon::CLI
 
       unless skip_domains.empty?
         say('The following domains were not available during the check:', :yellow)
-        shell.indent(2) do
-          skip_domains.each { |domain| say(domain) }
-        end
+        skip_domains.each { |domain| say("    #{domain}") }
       end
     end
 
diff --git a/lib/mastodon/cli/cache.rb b/lib/mastodon/cli/cache.rb
index 4152037587..0edc4b01d6 100644
--- a/lib/mastodon/cli/cache.rb
+++ b/lib/mastodon/cli/cache.rb
@@ -52,7 +52,7 @@ module Mastodon::CLI
       account.account_stat.tap do |account_stat|
         account_stat.following_count = account.active_relationships.count
         account_stat.followers_count = account.passive_relationships.count
-        account_stat.statuses_count  = account.statuses.not_direct_visibility.count
+        account_stat.statuses_count  = account.statuses.where.not(visibility: :direct).count
 
         account_stat.save if account_stat.changed?
       end
@@ -60,7 +60,7 @@ module Mastodon::CLI
 
     def recount_status_stats(status)
       status.status_stat.tap do |status_stat|
-        status_stat.replies_count    = status.replies.not_direct_visibility.count
+        status_stat.replies_count    = status.replies.where.not(visibility: :direct).count
         status_stat.reblogs_count    = status.reblogs.count
         status_stat.favourites_count = status.favourites.count
         status_stat.emoji_reactions  = status.generate_emoji_reactions_grouped_by_name
diff --git a/lib/mastodon/cli/email_domain_blocks.rb b/lib/mastodon/cli/email_domain_blocks.rb
index 0cc9ccb705..1e90a7c283 100644
--- a/lib/mastodon/cli/email_domain_blocks.rb
+++ b/lib/mastodon/cli/email_domain_blocks.rb
@@ -7,13 +7,11 @@ module Mastodon::CLI
   class EmailDomainBlocks < Base
     desc 'list', 'List blocked e-mail domains'
     def list
-      EmailDomainBlock.parents.find_each do |parent|
-        say(parent.domain.to_s, :white)
+      EmailDomainBlock.where(parent_id: nil).find_each do |entry|
+        say(entry.domain.to_s, :white)
 
-        shell.indent do
-          EmailDomainBlock.where(parent_id: parent.id).find_each do |child|
-            say(child.domain, :cyan)
-          end
+        EmailDomainBlock.where(parent_id: entry.id).find_each do |child|
+          say("  #{child.domain}", :cyan)
         end
       end
     end
@@ -45,7 +43,12 @@ module Mastodon::CLI
         end
 
         other_domains = []
-        other_domains = DomainResource.new(domain).mx if options[:with_dns_records]
+        if options[:with_dns_records]
+          Resolv::DNS.open do |dns|
+            dns.timeouts = 5
+            other_domains = dns.getresources(domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }.compact_blank
+          end
+        end
 
         email_domain_block = EmailDomainBlock.new(domain: domain, other_domains: other_domains)
         email_domain_block.save!
diff --git a/lib/mastodon/cli/emoji.rb b/lib/mastodon/cli/emoji.rb
index ecfa33c419..f43e6fdf9d 100644
--- a/lib/mastodon/cli/emoji.rb
+++ b/lib/mastodon/cli/emoji.rb
@@ -62,9 +62,7 @@ module Mastodon::CLI
             failed += 1
             say('Failure/Error: ', :red)
             say(entry.full_name)
-            shell.indent(2) do
-              say(custom_emoji.errors[:image].join(', '), :red)
-            end
+            say("    #{custom_emoji.errors[:image].join(', ')}", :red)
           end
         end
       end
diff --git a/lib/mastodon/cli/federation.rb b/lib/mastodon/cli/federation.rb
index f512bca358..c738796557 100644
--- a/lib/mastodon/cli/federation.rb
+++ b/lib/mastodon/cli/federation.rb
@@ -76,7 +76,7 @@ module Mastodon::CLI
       def self_destruct_value
         Rails
           .application
-          .message_verifier(SelfDestructHelper::VERIFY_PURPOSE)
+          .message_verifier('self-destruct')
           .generate(Rails.configuration.x.local_domain)
       end
     end
diff --git a/lib/mastodon/cli/feeds.rb b/lib/mastodon/cli/feeds.rb
index f594a750ed..c0635bbcde 100644
--- a/lib/mastodon/cli/feeds.rb
+++ b/lib/mastodon/cli/feeds.rb
@@ -11,21 +11,17 @@ module Mastodon::CLI
     option :concurrency, type: :numeric, default: 5, aliases: [:c]
     option :verbose, type: :boolean, aliases: [:v]
     option :dry_run, type: :boolean, default: false
-    option :skip_filled_timelines
     desc 'build [USERNAME]', 'Build home and list feeds for one or all users'
     long_desc <<-LONG_DESC
       Build home and list feeds that are stored in Redis from the database.
 
-      With the --skip-filled-timelines, timelines which contain more than half
-      the maximum number of posts will be skipped.
-
       With the --all option, all active users will be processed.
       Otherwise, a single user specified by USERNAME.
     LONG_DESC
     def build(username = nil)
       if options[:all] || username.nil?
         processed, = parallelize_with_progress(active_user_accounts) do |account|
-          PrecomputeFeedService.new.call(account, skip_filled_timelines: options[:skip_filled_timelines]) unless dry_run?
+          PrecomputeFeedService.new.call(account) unless dry_run?
         end
 
         say("Regenerated feeds for #{processed} accounts #{dry_run_mode_suffix}", :green, true)
@@ -34,7 +30,7 @@ module Mastodon::CLI
 
         fail_with_message 'No such account' if account.nil?
 
-        PrecomputeFeedService.new.call(account, skip_filled_timelines: options[:skip_filled_timelines]) unless dry_run?
+        PrecomputeFeedService.new.call(account) unless dry_run?
 
         say("OK #{dry_run_mode_suffix}", :green, true)
       else
diff --git a/lib/mastodon/cli/ip_blocks.rb b/lib/mastodon/cli/ip_blocks.rb
index f1f40c99ce..ef24f2e047 100644
--- a/lib/mastodon/cli/ip_blocks.rb
+++ b/lib/mastodon/cli/ip_blocks.rb
@@ -80,9 +80,9 @@ module Mastodon::CLI
         end
 
         ip_blocks = if options[:force]
-                      IpBlock.containing(address)
+                      IpBlock.where('ip >>= ?', address)
                     else
-                      IpBlock.contained_by(address)
+                      IpBlock.where('ip <<= ?', address)
                     end
 
         if ip_blocks.empty?
diff --git a/lib/mastodon/cli/maintenance.rb b/lib/mastodon/cli/maintenance.rb
index 32ee35c7c7..532fbc328a 100644
--- a/lib/mastodon/cli/maintenance.rb
+++ b/lib/mastodon/cli/maintenance.rb
@@ -192,7 +192,6 @@ module Mastodon::CLI
       verify_schema_version!
       verify_sidekiq_not_active!
       verify_backup_warning!
-      disable_timeout!
     end
 
     def process_deduplications
@@ -252,13 +251,6 @@ module Mastodon::CLI
       fail_with_message 'Maintenance process stopped.' unless yes?('Continue? (Yes/No)')
     end
 
-    def disable_timeout!
-      # Remove server-configured timeout if present
-      database_connection.execute(<<~SQL.squish)
-        SET statement_timeout = 0
-      SQL
-    end
-
     def deduplicate_accounts!
       remove_index_if_exists!(:accounts, 'index_accounts_on_username_and_domain_lower')
 
diff --git a/lib/mastodon/cli/media.rb b/lib/mastodon/cli/media.rb
index 1059eb6066..70c11ee1a1 100644
--- a/lib/mastodon/cli/media.rb
+++ b/lib/mastodon/cli/media.rb
@@ -6,8 +6,6 @@ module Mastodon::CLI
   class Media < Base
     include ActionView::Helpers::NumberHelper
 
-    class UnrecognizedOrphanType < StandardError; end
-
     VALID_PATH_SEGMENTS_SIZE = [7, 10].freeze
 
     option :days, type: :numeric, default: 7, aliases: [:d]
@@ -122,10 +120,23 @@ module Mastodon::CLI
             object.acl.put(acl: s3_permissions) if options[:fix_permissions] && !dry_run?
 
             path_segments = object.key.split('/')
+            path_segments.delete('cache')
+
+            unless VALID_PATH_SEGMENTS_SIZE.include?(path_segments.size)
+              progress.log(pastel.yellow("Unrecognized file found: #{object.key}"))
+              next
+            end
+
+            model_name      = path_segments.first.classify
+            attachment_name = path_segments[1].singularize
+            record_id       = path_segments[2...-2].join.to_i
+            file_name       = path_segments.last
+            record          = record_map.dig(model_name, record_id)
+            attachment      = record&.public_send(attachment_name)
 
             progress.increment
 
-            next unless orphaned_file?(path_segments, record_map)
+            next unless attachment.blank? || !attachment.variant?(file_name)
 
             begin
               object.delete unless dry_run?
@@ -137,8 +148,6 @@ module Mastodon::CLI
             rescue => e
               progress.log(pastel.red("Error processing #{object.key}: #{e}"))
             end
-          rescue UnrecognizedOrphanType
-            progress.log(pastel.yellow("Unrecognized file found: #{object.key}"))
           end
         end
       when :fog
@@ -156,10 +165,26 @@ module Mastodon::CLI
           key = path.gsub("#{root_path}#{File::SEPARATOR}", '')
 
           path_segments = key.split(File::SEPARATOR)
+          path_segments.delete('cache')
+
+          unless VALID_PATH_SEGMENTS_SIZE.include?(path_segments.size)
+            progress.log(pastel.yellow("Unrecognized file found: #{key}"))
+            next
+          end
+
+          model_name      = path_segments.first.classify
+          record_id       = path_segments[2...-2].join.to_i
+          attachment_name = path_segments[1].singularize
+          file_name       = path_segments.last
+
+          next unless PRELOAD_MODEL_WHITELIST.include?(model_name)
+
+          record     = model_name.constantize.find_by(id: record_id)
+          attachment = record&.public_send(attachment_name)
 
           progress.increment
 
-          next unless orphaned_file?(path_segments)
+          next unless attachment.blank? || !attachment.variant?(file_name)
 
           begin
             size = File.size(path)
@@ -180,8 +205,6 @@ module Mastodon::CLI
           rescue => e
             progress.log(pastel.red("Error processing #{key}: #{e}"))
           end
-        rescue UnrecognizedOrphanType
-          progress.log(pastel.yellow("Unrecognized file found: #{path}"))
         end
       end
 
@@ -255,10 +278,14 @@ module Mastodon::CLI
 
     desc 'usage', 'Calculate disk space consumed by Mastodon'
     def usage
-      print_table [
-        %w(Object Total Local),
-        *object_storage_summary,
-      ]
+      say("Attachments:\t#{number_to_human_size(media_attachment_storage_size)} (#{number_to_human_size(local_media_attachment_storage_size)} local)")
+      say("Custom emoji:\t#{number_to_human_size(CustomEmoji.sum(:image_file_size))} (#{number_to_human_size(CustomEmoji.local.sum(:image_file_size))} local)")
+      say("Preview cards:\t#{number_to_human_size(PreviewCard.sum(:image_file_size))}")
+      say("Avatars:\t#{number_to_human_size(Account.sum(:avatar_file_size))} (#{number_to_human_size(Account.local.sum(:avatar_file_size))} local)")
+      say("Headers:\t#{number_to_human_size(Account.sum(:header_file_size))} (#{number_to_human_size(Account.local.sum(:header_file_size))} local)")
+      say("Backups:\t#{number_to_human_size(Backup.sum(:dump_file_size))}")
+      say("Imports:\t#{number_to_human_size(Import.sum(:data_file_size))}")
+      say("Settings:\t#{number_to_human_size(SiteUpload.sum(:file_file_size))}")
     end
 
     desc 'lookup URL', 'Lookup where media is displayed by passing a media URL'
@@ -273,7 +300,7 @@ module Mastodon::CLI
       model_name = path_segments.first.classify
       record_id  = path_segments[2...-2].join.to_i
 
-      fail_with_message "Cannot find corresponding model: #{model_name}" unless PRELOADED_MODELS.include?(model_name)
+      fail_with_message "Cannot find corresponding model: #{model_name}" unless PRELOAD_MODEL_WHITELIST.include?(model_name)
 
       record = model_name.constantize.find_by(id: record_id)
       record = record.status if record.respond_to?(:status)
@@ -289,35 +316,34 @@ module Mastodon::CLI
       fail_with_message 'Invalid URL'
     end
 
-    PRELOADED_MODELS = %w(
+    private
+
+    def media_attachment_storage_size
+      MediaAttachment.sum(file_and_thumbnail_size_sql)
+    end
+
+    def local_media_attachment_storage_size
+      MediaAttachment.where(account: Account.local).sum(file_and_thumbnail_size_sql)
+    end
+
+    def file_and_thumbnail_size_sql
+      Arel.sql(
+        <<~SQL.squish
+          COALESCE(file_file_size, 0) + COALESCE(thumbnail_file_size, 0)
+        SQL
+      )
+    end
+
+    PRELOAD_MODEL_WHITELIST = %w(
       Account
       Backup
       CustomEmoji
+      Import
       MediaAttachment
       PreviewCard
       SiteUpload
     ).freeze
 
-    private
-
-    def object_storage_summary
-      [
-        [:attachments, MediaAttachment.sum(combined_media_sum), MediaAttachment.where(account: Account.local).sum(combined_media_sum)],
-        [:custom_emoji, CustomEmoji.sum(:image_file_size), CustomEmoji.local.sum(:image_file_size)],
-        [:avatars, Account.sum(:avatar_file_size), Account.local.sum(:avatar_file_size)],
-        [:headers, Account.sum(:header_file_size), Account.local.sum(:header_file_size)],
-        [:preview_cards, PreviewCard.sum(:image_file_size), nil],
-        [:backups, Backup.sum(:dump_file_size), nil],
-        [:settings, SiteUpload.sum(:file_file_size), nil],
-      ].map { |label, total, local| [label.to_s.titleize, number_to_human_size(total), local.present? ? number_to_human_size(local) : nil] }
-    end
-
-    def combined_media_sum
-      Arel.sql(<<~SQL.squish)
-        COALESCE(file_file_size, 0) + COALESCE(thumbnail_file_size, 0)
-      SQL
-    end
-
     def preload_records_from_mixed_objects(objects)
       preload_map = Hash.new { |hash, key| hash[key] = [] }
 
@@ -330,7 +356,7 @@ module Mastodon::CLI
         model_name = segments.first.classify
         record_id  = segments[2...-2].join.to_i
 
-        next unless PRELOADED_MODELS.include?(model_name)
+        next unless PRELOAD_MODEL_WHITELIST.include?(model_name)
 
         preload_map[model_name] << record_id
       end
@@ -339,23 +365,5 @@ module Mastodon::CLI
         model_map[model_name] = model_name.constantize.where(id: record_ids).index_by(&:id)
       end
     end
-
-    def orphaned_file?(path_segments, record_map = nil)
-      path_segments.delete('cache')
-
-      raise UnrecognizedOrphanType unless VALID_PATH_SEGMENTS_SIZE.include?(path_segments.size)
-
-      model_name      = path_segments.first.classify
-      record_id       = path_segments[2...-2].join.to_i
-      attachment_name = path_segments[1].singularize
-      file_name       = path_segments.last
-
-      raise UnrecognizedOrphanType unless PRELOADED_MODELS.include?(model_name)
-
-      record     = record_map.present? ? record_map.dig(model_name, record_id) : model_name.constantize.find_by(id: record_id)
-      attachment = record&.public_send(attachment_name)
-
-      attachment.blank? || !attachment.variant?(file_name)
-    end
   end
 end
diff --git a/lib/mastodon/cli/progress_helper.rb b/lib/mastodon/cli/progress_helper.rb
index a0cfec69cf..5634343796 100644
--- a/lib/mastodon/cli/progress_helper.rb
+++ b/lib/mastodon/cli/progress_helper.rb
@@ -1,11 +1,11 @@
 # frozen_string_literal: true
 
-dev_null = Logger.new(File::NULL)
+dev_null = Logger.new('/dev/null')
 
 Rails.logger                 = dev_null
 ActiveRecord::Base.logger    = dev_null
 ActiveJob::Base.logger       = dev_null
-HttpLog.configuration.logger = dev_null if defined?(HttpLog)
+HttpLog.configuration.logger = dev_null
 Paperclip.options[:log]      = false
 Chewy.logger                 = dev_null
 
diff --git a/lib/mastodon/cli/statuses.rb b/lib/mastodon/cli/statuses.rb
index 7104181e97..f441dbcd84 100644
--- a/lib/mastodon/cli/statuses.rb
+++ b/lib/mastodon/cli/statuses.rb
@@ -40,11 +40,17 @@ module Mastodon::CLI
     def remove_statuses
       return if options[:skip_status_remove]
 
-      start_at = Time.now.to_f
+      say('Creating temporary database indices...')
+
+      ActiveRecord::Base.connection.add_index(:media_attachments, :remote_url, name: :index_media_attachments_remote_url, where: 'remote_url is not null', algorithm: :concurrently, if_not_exists: true)
 
       max_id   = Mastodon::Snowflake.id_at(options[:days].days.ago, with_random: false)
+      start_at = Time.now.to_f
 
       unless options[:continue] && ActiveRecord::Base.connection.table_exists?('statuses_to_be_deleted')
+        ActiveRecord::Base.connection.add_index(:accounts, :id, name: :index_accounts_local, where: 'domain is null', algorithm: :concurrently, if_not_exists: true)
+        ActiveRecord::Base.connection.add_index(:status_pins, :status_id, name: :index_status_pins_status_id, algorithm: :concurrently, if_not_exists: true)
+
         say('Extract the deletion target from statuses... This might take a while...')
 
         ActiveRecord::Base.connection.create_table('statuses_to_be_deleted', force: true)
@@ -66,6 +72,9 @@ module Mastodon::CLI
         SQL
 
         say('Removing temporary database indices to restore write performance...')
+
+        ActiveRecord::Base.connection.remove_index(:accounts, name: :index_accounts_local, if_exists: true)
+        ActiveRecord::Base.connection.remove_index(:status_pins, name: :index_status_pins_status_id, if_exists: true)
       end
 
       say('Beginning statuses removal... This might take a while...')
@@ -93,6 +102,12 @@ module Mastodon::CLI
       ActiveRecord::Base.connection.drop_table('statuses_to_be_deleted')
 
       say("Done after #{Time.now.to_f - start_at}s, removed #{removed} out of #{processed} statuses.", :green)
+    ensure
+      say('Removing temporary database indices to restore write performance...')
+
+      ActiveRecord::Base.connection.remove_index(:accounts, name: :index_accounts_local, if_exists: true)
+      ActiveRecord::Base.connection.remove_index(:status_pins, name: :index_status_pins_status_id, if_exists: true)
+      ActiveRecord::Base.connection.remove_index(:media_attachments, name: :index_media_attachments_remote_url, if_exists: true)
     end
 
     def remove_orphans_media_attachments
diff --git a/lib/mastodon/database.rb b/lib/mastodon/database.rb
deleted file mode 100644
index 133e45d7ac..0000000000
--- a/lib/mastodon/database.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-# This file is entirely lifted from GitLab.
-
-# The original file:
-# https://gitlab.com/gitlab-org/gitlab/-/blob/69127d59467185cf4ff1109d88ceec1f499f354f/lib/gitlab/database.rb#L244-258
-
-# Copyright (c) 2011-present GitLab B.V.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-module Mastodon
-  module Database
-    def self.add_post_migrate_path_to_rails(force: false)
-      return if ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS'] && !force
-
-      Rails.application.config.paths['db'].each do |db_path|
-        path = Rails.root.join(db_path, 'post_migrate').to_s
-
-        next if Rails.application.config.paths['db/migrate'].include?(path)
-
-        Rails.application.config.paths['db/migrate'] << path
-
-        # Rails memoizes migrations at certain points where it won't read the above
-        # path just yet. As such we must also update the following list of paths.
-        ActiveRecord::Migrator.migrations_paths << path
-      end
-    end
-  end
-end
diff --git a/lib/mastodon/feature.rb b/lib/mastodon/feature.rb
deleted file mode 100644
index 650f1d7b8c..0000000000
--- a/lib/mastodon/feature.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-module Mastodon::Feature
-  class << self
-    def enabled_features
-      @enabled_features ||=
-        (Rails.configuration.x.mastodon.experimental_features || '').split(',').map(&:strip)
-    end
-
-    def method_missing(name)
-      if respond_to_missing?(name)
-        feature = name.to_s.delete_suffix('_enabled?')
-        enabled = enabled_features.include?(feature)
-        define_singleton_method(name) { enabled }
-
-        return enabled
-      end
-
-      super
-    end
-
-    def respond_to_missing?(name, include_all = false)
-      name.to_s.end_with?('_enabled?') || super
-    end
-  end
-end
diff --git a/lib/mastodon/middleware/prometheus_queue_time.rb b/lib/mastodon/middleware/prometheus_queue_time.rb
deleted file mode 100644
index bb8add51ec..0000000000
--- a/lib/mastodon/middleware/prometheus_queue_time.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-module Mastodon
-  module Middleware
-    class PrometheusQueueTime < ::PrometheusExporter::Middleware
-      # Overwrite to only collect the queue time metric
-      def call(env)
-        queue_time = measure_queue_time(env)
-
-        result = @app.call(env)
-
-        result
-      ensure
-        obj = {
-          type: 'web',
-          queue_time: queue_time,
-          default_labels: {},
-        }
-
-        @client.send_json(obj)
-      end
-    end
-  end
-end
diff --git a/lib/mastodon/middleware/public_file_server.rb b/lib/mastodon/middleware/public_file_server.rb
deleted file mode 100644
index b9a1edb9f5..0000000000
--- a/lib/mastodon/middleware/public_file_server.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-require 'action_dispatch/middleware/static'
-
-module Mastodon
-  module Middleware
-    class PublicFileServer
-      SERVICE_WORKER_TTL = 7.days.to_i
-      CACHE_TTL          = 28.days.to_i
-
-      def initialize(app)
-        @app = app
-        @file_handler = ActionDispatch::FileHandler.new(Rails.application.paths['public'].first)
-      end
-
-      def call(env)
-        file = @file_handler.attempt(env)
-
-        # If the request is not a static file, move on!
-        return @app.call(env) if file.nil?
-
-        status, headers, response = file
-
-        # Set cache headers on static files. Some paths require different cache headers
-        headers['Cache-Control'] = begin
-          request_path = env['REQUEST_PATH']
-
-          if request_path.start_with?('/sw.js')
-            "public, max-age=#{SERVICE_WORKER_TTL}, must-revalidate"
-          elsif request_path.start_with?(paperclip_root_url)
-            "public, max-age=#{CACHE_TTL}, immutable"
-          else
-            "public, max-age=#{CACHE_TTL}, must-revalidate"
-          end
-        end
-
-        # Override the default CSP header set by the CSP middleware
-        headers['Content-Security-Policy'] = "default-src 'none'; form-action 'none'" if request_path.start_with?(paperclip_root_url)
-
-        headers['X-Content-Type-Options'] = 'nosniff'
-
-        [status, headers, response]
-      end
-
-      private
-
-      def paperclip_root_url
-        ENV.fetch('PAPERCLIP_ROOT_URL', '/system')
-      end
-    end
-  end
-end
diff --git a/lib/mastodon/middleware/socket_cleanup.rb b/lib/mastodon/middleware/socket_cleanup.rb
deleted file mode 100644
index 8b33cb0cec..0000000000
--- a/lib/mastodon/middleware/socket_cleanup.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module Mastodon
-  module Middleware
-    class SocketCleanup
-      def initialize(app)
-        @app = app
-      end
-
-      def call(env)
-        @app.call(env)
-      ensure
-        clean_up_sockets!
-      end
-
-      private
-
-      def clean_up_sockets!
-        clean_up_redis_socket!
-        clean_up_statsd_socket!
-      end
-
-      def clean_up_redis_socket!
-        RedisConnection.pool.checkin if Thread.current[:redis]
-        Thread.current[:redis] = nil
-      end
-
-      def clean_up_statsd_socket!
-        Thread.current[:statsd_socket]&.close
-        Thread.current[:statsd_socket] = nil
-      end
-    end
-  end
-end
diff --git a/lib/mastodon/migration_warning.rb b/lib/mastodon/migration_warning.rb
index b90ceb2916..227f6705d3 100644
--- a/lib/mastodon/migration_warning.rb
+++ b/lib/mastodon/migration_warning.rb
@@ -4,7 +4,7 @@ module Mastodon
   module MigrationWarning
     WARNING_SECONDS = 10
 
-    DEFAULT_WARNING = <<~WARNING_MESSAGE.freeze
+    DEFAULT_WARNING = <<~WARNING_MESSAGE
       WARNING: This migration may take a *long* time for large instances.
       It will *not* lock tables for any significant time, but it may run
       for a very long time. We will pause for #{WARNING_SECONDS} seconds to allow you to
diff --git a/lib/mastodon/rack_middleware.rb b/lib/mastodon/rack_middleware.rb
new file mode 100644
index 0000000000..0e452f06d6
--- /dev/null
+++ b/lib/mastodon/rack_middleware.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class Mastodon::RackMiddleware
+  def initialize(app)
+    @app = app
+  end
+
+  def call(env)
+    @app.call(env)
+  ensure
+    clean_up_sockets!
+  end
+
+  private
+
+  def clean_up_sockets!
+    clean_up_redis_socket!
+    clean_up_statsd_socket!
+  end
+
+  def clean_up_redis_socket!
+    RedisConnection.pool.checkin if Thread.current[:redis]
+    Thread.current[:redis] = nil
+  end
+
+  def clean_up_statsd_socket!
+    Thread.current[:statsd_socket]&.close
+    Thread.current[:statsd_socket] = nil
+  end
+end
diff --git a/lib/mastodon/sidekiq_middleware.rb b/lib/mastodon/sidekiq_middleware.rb
index 4df22002c8..8ce1124c69 100644
--- a/lib/mastodon/sidekiq_middleware.rb
+++ b/lib/mastodon/sidekiq_middleware.rb
@@ -3,10 +3,8 @@
 class Mastodon::SidekiqMiddleware
   BACKTRACE_LIMIT = 3
 
-  def call(_worker_class, job, _queue, &block)
-    setup_query_log_tags(job) do
-      Chewy.strategy(:mastodon, &block)
-    end
+  def call(*, &block)
+    Chewy.strategy(:mastodon, &block)
   rescue Mastodon::HostValidationError
     # Do not retry
   rescue => e
@@ -63,14 +61,4 @@ class Mastodon::SidekiqMiddleware
     Thread.current[:statsd_socket]&.close
     Thread.current[:statsd_socket] = nil
   end
-
-  def setup_query_log_tags(job, &block)
-    if Rails.configuration.active_record.query_log_tags_enabled
-      # If `wrapped` is set, this is an `ActiveJob` which is already in the execution context
-      sidekiq_job_class = job['wrapped'].present? ? nil : job['class'].to_s
-      ActiveSupport::ExecutionContext.set(sidekiq_job_class: sidekiq_job_class, &block)
-    else
-      yield
-    end
-  end
 end
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index acf7a4e79a..84c95a1761 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -2,23 +2,23 @@
 
 module Mastodon
   module Version
-    KMYBLUE_API_VERSION = 2
+    KMYBLUE_API_VERSION = 1
 
     module_function
 
     # If you change the version number, also change the image version in docker-compose.yml.
 
     def kmyblue_major
-      18
+      15
     end
 
     def kmyblue_minor
-      0
+      11
     end
 
     def kmyblue_flag
-      # 'LTS'
-      'dev'
+      'LTS'
+      # 'dev'
       # nil
     end
 
@@ -27,19 +27,19 @@ module Mastodon
     end
 
     def minor
-      4
+      3
     end
 
     def patch
-      0
+      6
     end
 
     def default_prerelease
-      'alpha.4'
+      ''
     end
 
     def prerelease
-      version_configuration[:prerelease].presence || default_prerelease
+      ENV['MASTODON_VERSION_PRERELEASE'].presence || default_prerelease
     end
 
     def to_a_of_kmyblue
@@ -63,7 +63,7 @@ module Mastodon
     end
 
     def build_metadata_of_mastodon
-      version_configuration[:metadata]
+      ENV.fetch('MASTODON_VERSION_METADATA', nil)
     end
 
     def to_a
@@ -96,22 +96,22 @@ module Mastodon
 
     def api_versions
       {
-        mastodon: 5,
+        mastodon: 2,
         kmyblue: KMYBLUE_API_VERSION,
       }
     end
 
     def repository
-      source_configuration[:repository]
+      ENV.fetch('GITHUB_REPOSITORY', 'kmycode/mastodon')
     end
 
     def source_base_url
-      source_configuration[:base_url] || "https://github.com/#{repository}"
+      ENV.fetch('SOURCE_BASE_URL', "https://github.com/#{repository}")
     end
 
     # specify git tag or commit hash here
     def source_tag
-      source_configuration[:tag]
+      ENV.fetch('SOURCE_TAG', nil)
     end
 
     def source_url
@@ -122,24 +122,8 @@ module Mastodon
       end
     end
 
-    def source_commit
-      ENV.fetch('SOURCE_COMMIT', nil)
-    end
-
     def user_agent
       @user_agent ||= "Mastodon/#{Version} (#{HTTP::Request::USER_AGENT}; +http#{Rails.configuration.x.use_https ? 's' : ''}://#{Rails.configuration.x.web_domain}/)"
     end
-
-    def version_configuration
-      mastodon_configuration.version
-    end
-
-    def source_configuration
-      mastodon_configuration.source
-    end
-
-    def mastodon_configuration
-      Rails.configuration.x.mastodon
-    end
   end
 end
diff --git a/lib/paperclip/transcoder.rb b/lib/paperclip/transcoder.rb
index 47e3f9f547..3efffa355a 100644
--- a/lib/paperclip/transcoder.rb
+++ b/lib/paperclip/transcoder.rb
@@ -52,8 +52,8 @@ module Paperclip
           @output_options['bufsize'] = bitrate * 5
 
           if high_vfr?(metadata)
-            # TODO: change to `fps_mode` in the future, as `vsync` is being deprecated
             @output_options['vsync'] = 'vfr'
+            @output_options['r'] = @vfr_threshold
           end
         end
       end
diff --git a/lib/paperclip/vips_lazy_thumbnail.rb b/lib/paperclip/vips_lazy_thumbnail.rb
index 528d5604dc..fea4b86064 100644
--- a/lib/paperclip/vips_lazy_thumbnail.rb
+++ b/lib/paperclip/vips_lazy_thumbnail.rb
@@ -123,14 +123,7 @@ module Paperclip
     end
 
     def needs_convert?
-      strip_animations? || needs_different_geometry? || needs_different_format? || needs_metadata_stripping?
-    end
-
-    def strip_animations?
-      # Detecting whether the source image is animated across all our supported
-      # input file formats is not trivial, and converting unconditionally is just
-      # as simple for now
-      options[:style] == :static
+      needs_different_geometry? || needs_different_format? || needs_metadata_stripping?
     end
 
     def needs_different_geometry?
diff --git a/lib/public_file_server_middleware.rb b/lib/public_file_server_middleware.rb
new file mode 100644
index 0000000000..7e02e37a08
--- /dev/null
+++ b/lib/public_file_server_middleware.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'action_dispatch/middleware/static'
+
+class PublicFileServerMiddleware
+  SERVICE_WORKER_TTL = 7.days.to_i
+  CACHE_TTL          = 28.days.to_i
+
+  def initialize(app)
+    @app = app
+    @file_handler = ActionDispatch::FileHandler.new(Rails.application.paths['public'].first)
+  end
+
+  def call(env)
+    file = @file_handler.attempt(env)
+
+    # If the request is not a static file, move on!
+    return @app.call(env) if file.nil?
+
+    status, headers, response = file
+
+    # Set cache headers on static files. Some paths require different cache headers
+    headers['Cache-Control'] = begin
+      request_path = env['REQUEST_PATH']
+
+      if request_path.start_with?('/sw.js')
+        "public, max-age=#{SERVICE_WORKER_TTL}, must-revalidate"
+      elsif request_path.start_with?(paperclip_root_url)
+        "public, max-age=#{CACHE_TTL}, immutable"
+      else
+        "public, max-age=#{CACHE_TTL}, must-revalidate"
+      end
+    end
+
+    # Override the default CSP header set by the CSP middleware
+    headers['Content-Security-Policy'] = "default-src 'none'; form-action 'none'" if request_path.start_with?(paperclip_root_url)
+
+    headers['X-Content-Type-Options'] = 'nosniff'
+
+    [status, headers, response]
+  end
+
+  private
+
+  def paperclip_root_url
+    ENV.fetch('PAPERCLIP_ROOT_URL', '/system')
+  end
+end
diff --git a/lib/sanitize_ext/sanitize_config.rb b/lib/sanitize_ext/sanitize_config.rb
index 410a58b07e..368e2b3bfa 100644
--- a/lib/sanitize_ext/sanitize_config.rb
+++ b/lib/sanitize_ext/sanitize_config.rb
@@ -21,7 +21,7 @@ class Sanitize
       gemini
     ).freeze
 
-    ALLOWED_CLASS_TRANSFORMER = lambda do |env|
+    CLASS_WHITELIST_TRANSFORMER = lambda do |env|
       node = env[:node]
       class_list = node['class']&.split(/[\t\n\f\r ]/)
 
@@ -87,44 +87,6 @@ class Sanitize
       current_node.wrap('<p></p>')
     end
 
-    # We assume that incomming <math> nodes are of the form
-    # <math><semantics>...<annotation>...</annotation></semantics></math>
-    # according to the [FEP]. We try to grab the most relevant plain-text
-    # annotation from the semantics node, and use it to display a representation
-    # of the mathematics.
-    #
-    # FEP: https://codeberg.org/fediverse/fep/src/branch/main/fep/dc88/fep-dc88.md
-    MATH_TRANSFORMER = lambda do |env|
-      math = env[:node]
-      return if env[:is_allowlisted]
-      return unless math.element? && env[:node_name] == 'math'
-
-      semantics = math.element_children[0]
-      return if semantics.nil? || semantics.name != 'semantics'
-
-      # next, we find the plain-text description
-      is_annotation_with_encoding = lambda do |encoding, node|
-        return false unless node.name == 'annotation'
-
-        node.attributes['encoding'].value == encoding
-      end
-
-      annotation = semantics.children.find(&is_annotation_with_encoding.curry['application/x-tex'])
-      if annotation
-        text = if math.attributes['display']&.value == 'block'
-                 "$$#{annotation.text}$$"
-               else
-                 "$#{annotation.text}$"
-               end
-        math.replace(math.document.create_text_node(text))
-        return
-      end
-      # Don't bother surrounding 'text/plain' annotations with dollar signs,
-      # since it isn't LaTeX
-      annotation = semantics.children.find(&is_annotation_with_encoding.curry['text/plain'])
-      math.replace(math.document.create_text_node(annotation.text)) unless annotation.nil?
-    end
-
     MASTODON_STRICT = freeze_config(
       elements: %w(p br span a del s pre blockquote code b strong u i em ul ol li ruby rt rp),
 
@@ -137,7 +99,7 @@ class Sanitize
 
       add_attributes: {
         'a' => {
-          'rel' => 'nofollow noopener',
+          'rel' => 'nofollow noopener noreferrer',
           'target' => '_blank',
         },
       },
@@ -145,9 +107,8 @@ class Sanitize
       protocols: {},
 
       transformers: [
-        ALLOWED_CLASS_TRANSFORMER,
+        CLASS_WHITELIST_TRANSFORMER,
         TRANSLATE_TRANSFORMER,
-        MATH_TRANSFORMER,
         UNSUPPORTED_ELEMENTS_TRANSFORMER,
         UNSUPPORTED_HREF_TRANSFORMER,
         PHISHING_SCAM_HREF_TRANSFORMER,
diff --git a/lib/tasks/annotate_rb.rake b/lib/tasks/annotate_rb.rake
deleted file mode 100644
index e8368b2e94..0000000000
--- a/lib/tasks/annotate_rb.rake
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-# This rake task was added by annotate_rb gem.
-
-# Can set `ANNOTATERB_SKIP_ON_DB_TASKS` to be anything to skip this
-if Rails.env.development? && ENV['ANNOTATERB_SKIP_ON_DB_TASKS'].nil?
-  require 'annotate_rb'
-
-  AnnotateRb::Core.load_rake_tasks
-end
diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake
new file mode 100644
index 0000000000..4b5997920a
--- /dev/null
+++ b/lib/tasks/auto_annotate_models.rake
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+if Rails.env.development?
+  task :set_annotation_options do
+    Annotate.set_defaults(
+      'routes' => 'false',
+      'models' => 'true',
+      'position_in_routes' => 'before',
+      'position_in_class' => 'before',
+      'position_in_test' => 'before',
+      'position_in_fixture' => 'before',
+      'position_in_factory' => 'before',
+      'position_in_serializer' => 'before',
+      'show_foreign_keys' => 'false',
+      'show_indexes' => 'false',
+      'simple_indexes' => 'false',
+      'model_dir' => 'app/models',
+      'root_dir' => '',
+      'include_version' => 'false',
+      'require' => '',
+      'exclude_tests' => 'true',
+      'exclude_fixtures' => 'true',
+      'exclude_factories' => 'true',
+      'exclude_serializers' => 'true',
+      'exclude_scaffolds' => 'true',
+      'exclude_controllers' => 'true',
+      'exclude_helpers' => 'true',
+      'ignore_model_sub_dir' => 'false',
+      'ignore_columns' => nil,
+      'ignore_routes' => nil,
+      'ignore_unknown_models' => 'false',
+      'hide_limit_column_types' => 'integer,boolean',
+      'skip_on_db_migrate' => 'false',
+      'format_bare' => 'true',
+      'format_rdoc' => 'false',
+      'format_markdown' => 'false',
+      'sort' => 'false',
+      'force' => 'false',
+      'trace' => 'false',
+      'wrapper_open' => nil,
+      'wrapper_close' => nil
+    )
+  end
+
+  Annotate.load_tasks
+end
diff --git a/lib/tasks/dangerous.rake b/lib/tasks/dangerous.rake
index fbd9268271..fcc2a7b52c 100644
--- a/lib/tasks/dangerous.rake
+++ b/lib/tasks/dangerous.rake
@@ -14,11 +14,6 @@ namespace :dangerous do
     end
 
     target_migrations = %w(
-      20250216231904
-      20250216231806
-      20250130232529
-      20250123091137
-      20241208232829
       20240828123604
       20240709063700
       20240426233435
@@ -177,7 +172,6 @@ namespace :dangerous do
       %w(domain_blocks reject_send_sensitive),
       %w(domain_blocks reject_straight_follow),
       %w(favourites uri),
-      %w(lists favourite),
       %w(lists notify),
       %w(statuses limited_scope),
       %w(statuses markdown),
@@ -202,9 +196,9 @@ namespace :dangerous do
     ActiveRecord::Base.connection.execute('UPDATE custom_filters SET action = 0 WHERE action = 2')
     ActiveRecord::Base.connection.execute('UPDATE account_warnings SET action = 1250 WHERE action = 1200')
     ActiveRecord::Base.connection.execute('CREATE INDEX IF NOT EXISTS index_statuses_local_20190824 ON statuses USING btree (id DESC, account_id) WHERE (local OR (uri IS NULL)) AND deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))')
-    ActiveRecord::Base.connection.execute('CREATE INDEX IF NOT EXISTS index_statuses_public_20250129 ON statuses USING btree (id DESC, language, account_id) WHERE deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))')
+    ActiveRecord::Base.connection.execute('CREATE INDEX IF NOT EXISTS index_statuses_public_20200119 ON statuses USING btree (id DESC, account_id) WHERE deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))')
     ActiveRecord::Base.connection.execute('DROP INDEX IF EXISTS index_statuses_local_20231213')
-    ActiveRecord::Base.connection.execute('DROP INDEX IF EXISTS index_statuses_public_20250210')
+    ActiveRecord::Base.connection.execute('DROP INDEX IF EXISTS index_statuses_public_20231213')
     ActiveRecord::Base.connection.execute('ALTER TABLE ONLY custom_filter_keywords ALTER COLUMN whole_word SET DEFAULT true')
     prompt.ok 'Proceed'
 
diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake
index f261041901..e5a58ae3d5 100644
--- a/lib/tasks/db.rake
+++ b/lib/tasks/db.rake
@@ -62,14 +62,8 @@ namespace :db do
   end
 
   task pre_migration_check: :environment do
-    pg_version = ActiveRecord::Base.connection.database_version
-    abort 'This version of Mastodon requires PostgreSQL 12.0 or newer. Please update PostgreSQL before updating Mastodon.' if pg_version < 120_000
-
-    schema_version = ActiveRecord::Migrator.current_version
-    abort <<~MESSAGE if ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS'] && schema_version < 2023_09_07_150100
-      Zero-downtime migrations from Mastodon versions earlier than 4.2.0 are not supported.
-      Please update to Mastodon 4.2.x first or upgrade by stopping all services and running migrations without `SKIP_POST_DEPLOYMENT_MIGRATIONS`.
-    MESSAGE
+    version = ActiveRecord::Base.connection.database_version
+    abort 'This version of Mastodon requires PostgreSQL 12.0 or newer. Please update PostgreSQL before updating Mastodon.' if version < 120_000
   end
 
   Rake::Task['db:migrate'].enhance(['db:pre_migration_check'])
diff --git a/lib/tasks/emojis.rake b/lib/tasks/emojis.rake
index 3b2cf46a13..fb18f21cf5 100644
--- a/lib/tasks/emojis.rake
+++ b/lib/tasks/emojis.rake
@@ -103,36 +103,4 @@ namespace :emojis do
       gen_border map[emoji], 'white'
     end
   end
-
-  desc 'Download the JSON sheet data of emojis'
-  task :download_sheet_json do
-    source = 'https://raw.githubusercontent.com/iamcal/emoji-data/refs/tags/v15.1.2/emoji.json'
-    dest   = Rails.root.join('app', 'javascript', 'mastodon', 'features', 'emoji', 'emoji_sheet.json')
-
-    puts "Downloading emoji data from source... (#{source})"
-
-    res = HTTP.get(source).to_s
-    data = JSON.parse(res)
-
-    filtered_data = data.map do |emoji|
-      filtered_item = {
-        'unified' => emoji['unified'],
-        'sheet_x' => emoji['sheet_x'],
-        'sheet_y' => emoji['sheet_y'],
-        'skin_variations' => {},
-      }
-
-      emoji['skin_variations']&.each do |key, variation|
-        filtered_item['skin_variations'][key] = {
-          'unified' => variation['unified'],
-          'sheet_x' => variation['sheet_x'],
-          'sheet_y' => variation['sheet_y'],
-        }
-      end
-
-      filtered_item
-    end
-
-    File.write(dest, JSON.generate(filtered_data))
-  end
 end
diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake
index 7a9edd7016..eac99321e7 100644
--- a/lib/tasks/mastodon.rake
+++ b/lib/tasks/mastodon.rake
@@ -604,11 +604,11 @@ namespace :mastodon do
 end
 
 def disable_log_stdout!
-  dev_null = Logger.new(File::NULL)
+  dev_null = Logger.new('/dev/null')
 
   Rails.logger                 = dev_null
   ActiveRecord::Base.logger    = dev_null
-  HttpLog.configuration.logger = dev_null if defined?(HttpLog)
+  HttpLog.configuration.logger = dev_null
   Paperclip.options[:log]      = false
 end
 
diff --git a/lib/tasks/repo.rake b/lib/tasks/repo.rake
index c8f977f651..539c44273f 100644
--- a/lib/tasks/repo.rake
+++ b/lib/tasks/repo.rake
@@ -18,7 +18,7 @@ namespace :repo do
 
     url = "https://api.github.com/repos/#{REPOSITORY_NAME}/contributors?anon=1"
 
-    HttpLog.config.compact_log = true if defined?(HttpLog)
+    HttpLog.config.compact_log = true
 
     while url.present?
       response     = HTTP.get(url)
@@ -43,7 +43,7 @@ namespace :repo do
     path = Rails.root.join('CHANGELOG.md')
     tmp  = Tempfile.new
 
-    HttpLog.config.compact_log = true if defined?(HttpLog)
+    HttpLog.config.compact_log = true
 
     begin
       File.open(path, 'r') do |file|
diff --git a/lib/tasks/statistics.rake b/lib/tasks/statistics.rake
new file mode 100644
index 0000000000..82840f4fdc
--- /dev/null
+++ b/lib/tasks/statistics.rake
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+task stats: 'mastodon:stats'
+
+namespace :mastodon do
+  desc 'Report code statistics (KLOCs, etc)'
+  task :stats do
+    require 'rails/code_statistics'
+    [
+      ['App Libraries', 'app/lib'],
+      %w(Presenters app/presenters),
+      %w(Policies app/policies),
+      %w(Serializers app/serializers),
+      %w(Services app/services),
+      %w(Validators app/validators),
+      %w(Workers app/workers),
+    ].each do |name, dir|
+      STATS_DIRECTORIES << [name, dir]
+    end
+  end
+end
diff --git a/lib/tasks/tests.rake b/lib/tasks/tests.rake
index c64b75691e..076d8f2e5e 100644
--- a/lib/tasks/tests.rake
+++ b/lib/tasks/tests.rake
@@ -11,7 +11,7 @@ namespace :tests do
         '3_3_0' => 2020_12_18_054746,
       }.each do |release, version|
         ActiveRecord::Tasks::DatabaseTasks
-          .migration_connection_pool
+          .migration_connection
           .migration_context
           .migrate(version)
         Rake::Task["tests:migrations:populate_v#{release}"]
diff --git a/lint-staged.config.js b/lint-staged.config.js
index 63f5258a94..6740def512 100644
--- a/lint-staged.config.js
+++ b/lint-staged.config.js
@@ -1,9 +1,9 @@
 const config = {
   '*': 'prettier --ignore-unknown --write',
-  'Gemfile|*.{rb,ruby,ru,rake}': 'bin/rubocop --force-exclusion -a',
+  'Capfile|Gemfile|*.{rb,ruby,ru,rake}': 'bin/rubocop --force-exclusion -a',
   '*.{js,jsx,ts,tsx}': 'eslint --fix',
   '*.{css,scss}': 'stylelint --fix',
-  '*.haml': 'bin/haml-lint -a',
+  '*.haml': 'bundle exec haml-lint -a',
   '**/*.ts?(x)': () => 'tsc -p tsconfig.json --noEmit',
 };
 
diff --git a/package.json b/package.json
index b111350da0..5fa2f9f4ad 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@mastodon/mastodon",
   "license": "AGPL-3.0-or-later",
-  "packageManager": "yarn@4.9.1",
+  "packageManager": "yarn@4.5.0",
   "engines": {
     "node": ">=18"
   },
@@ -16,14 +16,14 @@
     "clean:kbdraft": "git branch | grep kb-draft- | xargs git branch -d",
     "clean:kbtopic": "git branch | grep kbtopic- | xargs git branch -d",
     "clean:upstream": "git branch | grep upstream- | xargs git branch -d",
-    "fix:js": "eslint . --cache --fix",
+    "fix:js": "eslint . --ext=.js,.jsx,.ts,.tsx --cache --report-unused-disable-directives --fix",
     "fix:css": "stylelint --fix \"**/*.{css,scss}\"",
     "fix": "yarn fix:js && yarn fix:css",
     "format": "prettier --write --log-level warn .",
     "format:check": "prettier --check --ignore-unknown .",
     "i18n:extract": "formatjs extract 'app/javascript/**/*.{js,jsx,ts,tsx}' --ignore '**/*.d.ts' --out-file app/javascript/mastodon/locales/en.json --format config/formatjs-formatter.js",
     "jest": "cross-env NODE_ENV=test jest",
-    "lint:js": "cd $INIT_CWD && eslint --cache --report-unused-disable-directives",
+    "lint:js": "eslint . --ext=.js,.jsx,.ts,.tsx --cache --report-unused-disable-directives",
     "lint:css": "stylelint \"**/*.{css,scss}\"",
     "lint": "yarn lint:js && yarn lint:css",
     "postversion": "git push --tags",
@@ -46,25 +46,21 @@
     "@babel/preset-react": "^7.22.3",
     "@babel/preset-typescript": "^7.21.5",
     "@babel/runtime": "^7.23.7",
-    "@csstools/stylelint-formatter-github": "^1.0.0",
     "@dnd-kit/core": "^6.1.0",
-    "@dnd-kit/sortable": "^10.0.0",
+    "@dnd-kit/sortable": "^8.0.0",
     "@dnd-kit/utilities": "^3.2.2",
-    "@emoji-mart/data": "1.2.1",
-    "@formatjs/intl-pluralrules": "^5.4.4",
+    "@formatjs/intl-pluralrules": "^5.2.2",
     "@gamestdio/websocket": "^0.3.2",
     "@github/webauthn-json": "^2.1.1",
     "@hello-pangea/dnd": "^17.0.0",
-    "@rails/ujs": "7.1.501",
-    "@react-spring/web": "^9.7.5",
+    "@rails/ujs": "7.1.401",
     "@reduxjs/toolkit": "^2.0.1",
     "@svgr/webpack": "^5.5.0",
-    "@use-gesture/react": "^10.3.1",
     "arrow-key-navigation": "^1.2.0",
     "async-mutex": "^0.5.0",
     "axios": "^1.4.0",
     "babel-loader": "^8.3.0",
-    "babel-plugin-formatjs": "^10.5.37",
+    "babel-plugin-formatjs": "^10.5.1",
     "babel-plugin-lodash": "patch:babel-plugin-lodash@npm%3A3.3.4#~/.yarn/patches/babel-plugin-lodash-npm-3.3.4-c7161075b6.patch",
     "babel-plugin-preval": "^5.1.0",
     "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
@@ -89,9 +85,8 @@
     "http-link-header": "^1.1.1",
     "immutable": "^4.3.0",
     "imports-loader": "^1.2.0",
-    "intl-messageformat": "^10.7.16",
+    "intl-messageformat": "^10.3.5",
     "js-yaml": "^4.1.0",
-    "lande": "^1.0.10",
     "lodash": "^4.17.21",
     "mark-loader": "^0.1.6",
     "marky": "^1.2.5",
@@ -108,7 +103,9 @@
     "react-hotkeys": "^1.1.4",
     "react-immutable-proptypes": "^2.2.0",
     "react-immutable-pure-component": "^2.2.2",
-    "react-intl": "^7.1.10",
+    "react-intl": "^6.4.2",
+    "react-motion": "^0.5.2",
+    "react-notification": "^6.8.5",
     "react-overlays": "^5.2.1",
     "react-redux": "^9.0.4",
     "react-redux-loading-bar": "^5.0.8",
@@ -129,7 +126,7 @@
     "stringz": "^2.1.0",
     "substring-trie": "^1.0.2",
     "terser-webpack-plugin": "^4.2.3",
-    "tesseract.js": "^6.0.0",
+    "tesseract.js": "^2.1.5",
     "tiny-queue": "^0.2.1",
     "twitter-text": "3.1.0",
     "use-debounce": "^10.0.0",
@@ -147,15 +144,13 @@
     "workbox-window": "^7.0.0"
   },
   "devDependencies": {
-    "@eslint/js": "^9.23.0",
     "@formatjs/cli": "^6.1.1",
     "@testing-library/dom": "^10.2.0",
     "@testing-library/jest-dom": "^6.0.0",
     "@testing-library/react": "^16.0.0",
     "@types/babel__core": "^7.20.1",
-    "@types/emoji-mart": "3.0.14",
+    "@types/emoji-mart": "^3.0.9",
     "@types/escape-html": "^1.0.2",
-    "@types/eslint-plugin-jsx-a11y": "^6",
     "@types/hoist-non-react-statics": "^3.3.1",
     "@types/http-link-header": "^1.0.3",
     "@types/intl": "^1.2.0",
@@ -170,6 +165,7 @@
     "@types/react-dom": "^18.2.4",
     "@types/react-helmet": "^6.1.6",
     "@types/react-immutable-proptypes": "^2.1.0",
+    "@types/react-motion": "^0.0.40",
     "@types/react-router": "^5.1.20",
     "@types/react-router-dom": "^5.3.3",
     "@types/react-sparklines": "^1.7.2",
@@ -180,33 +176,32 @@
     "@types/requestidlecallback": "^0.3.5",
     "@types/webpack": "^4.41.33",
     "@types/webpack-env": "^1.18.4",
+    "@typescript-eslint/eslint-plugin": "^8.0.0",
+    "@typescript-eslint/parser": "^8.0.0",
     "babel-jest": "^29.5.0",
-    "eslint": "^9.23.0",
-    "eslint-import-resolver-typescript": "^4.2.5",
-    "eslint-plugin-formatjs": "^5.3.1",
-    "eslint-plugin-import": "~2.31.0",
-    "eslint-plugin-jsdoc": "^50.6.9",
-    "eslint-plugin-jsx-a11y": "~6.10.2",
-    "eslint-plugin-promise": "~7.2.1",
-    "eslint-plugin-react": "^7.37.4",
-    "eslint-plugin-react-hooks": "^5.2.0",
-    "globals": "^16.0.0",
+    "eslint": "^8.41.0",
+    "eslint-define-config": "^2.0.0",
+    "eslint-import-resolver-typescript": "^3.5.5",
+    "eslint-plugin-formatjs": "^4.10.1",
+    "eslint-plugin-import": "~2.30.0",
+    "eslint-plugin-jsdoc": "^50.0.0",
+    "eslint-plugin-jsx-a11y": "~6.10.0",
+    "eslint-plugin-promise": "~7.1.0",
+    "eslint-plugin-react": "^7.33.2",
+    "eslint-plugin-react-hooks": "^4.6.0",
     "husky": "^9.0.11",
     "jest": "^29.5.0",
     "jest-environment-jsdom": "^29.5.0",
     "lint-staged": "^15.0.0",
     "prettier": "^3.3.3",
     "react-test-renderer": "^18.2.0",
-    "stylelint": "^16.11.0",
+    "stylelint": "^16.0.2",
     "stylelint-config-prettier-scss": "^1.0.0",
-    "stylelint-config-standard-scss": "^14.0.0",
-    "typescript": "~5.7.3",
-    "typescript-eslint": "^8.28.0",
+    "stylelint-config-standard-scss": "^13.0.0",
+    "typescript": "^5.0.4",
     "webpack-dev-server": "^3.11.3"
   },
   "resolutions": {
-    "@types/react": "^18.2.7",
-    "@types/react-dom": "^18.2.4",
     "kind-of": "^6.0.3",
     "webpack/terser-webpack-plugin": "^4.2.3"
   },
diff --git a/public/avatars/original/missing.png b/public/avatars/original/missing.png
index 3b37e69c5d..781370782e 100644
Binary files a/public/avatars/original/missing.png and b/public/avatars/original/missing.png differ
diff --git a/public/emoji/sheet_13.png b/public/emoji/sheet_13.png
new file mode 100644
index 0000000000..1ba12b6191
Binary files /dev/null and b/public/emoji/sheet_13.png differ
diff --git a/public/emoji/sheet_15.png b/public/emoji/sheet_15.png
deleted file mode 100644
index 3d0a679119..0000000000
Binary files a/public/emoji/sheet_15.png and /dev/null differ
diff --git a/public/favicon.ico b/public/favicon.ico
old mode 100644
new mode 100755
index 7f865cfe96..fc5e475d42
Binary files a/public/favicon.ico and b/public/favicon.ico differ
diff --git a/public/loading.gif b/public/loading.gif
deleted file mode 100755
index b3e1dbef8f..0000000000
Binary files a/public/loading.gif and /dev/null differ
diff --git a/public/loading.png b/public/loading.png
deleted file mode 100755
index 6a20cbfcb6..0000000000
Binary files a/public/loading.png and /dev/null differ
diff --git a/public/robots.txt b/public/robots.txt
index e42b884001..3bd45adaef 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -9,4 +9,3 @@ Disallow: /
 User-agent: *
 Disallow: /media_proxy/
 Disallow: /interact/
-Disallow: /api/v1/instance/domain_blocks
diff --git a/spec/controllers/.rubocop.yml b/spec/controllers/.rubocop.yml
new file mode 100644
index 0000000000..51d7c23de1
--- /dev/null
+++ b/spec/controllers/.rubocop.yml
@@ -0,0 +1,6 @@
+inherit_from: ../../.rubocop.yml
+
+# Anonymous controllers in specs cannot access `described_class`, explanation:
+# https://github.com/rubocop/rubocop-rspec/blob/v2.26.1/lib/rubocop/cop/rspec/described_class.rb#L36-L56
+RSpec/DescribedClass:
+  SkipBlocks: true
diff --git a/spec/requests/activitypub/collections_spec.rb b/spec/controllers/activitypub/collections_controller_spec.rb
similarity index 59%
rename from spec/requests/activitypub/collections_spec.rb
rename to spec/controllers/activitypub/collections_controller_spec.rb
index d2761f98ea..408e0dd2f6 100644
--- a/spec/requests/activitypub/collections_spec.rb
+++ b/spec/controllers/activitypub/collections_controller_spec.rb
@@ -2,19 +2,22 @@
 
 require 'rails_helper'
 
-RSpec.describe 'ActivityPub Collections' do
+RSpec.describe ActivityPub::CollectionsController do
   let!(:account) { Fabricate(:account) }
   let!(:private_pinned) { Fabricate(:status, account: account, text: 'secret private stuff', visibility: :private) }
   let(:remote_account) { nil }
 
   before do
-    Fabricate.times(2, :status_pin, account: account)
+    allow(controller).to receive(:signed_request_actor).and_return(remote_account)
+
+    Fabricate(:status_pin, account: account)
+    Fabricate(:status_pin, account: account)
     Fabricate(:status_pin, account: account, status: private_pinned)
     Fabricate(:status, account: account, visibility: :private)
   end
 
   describe 'GET #show' do
-    subject { get account_collection_path(id: id, account_username: account.username), headers: nil, sign_with: remote_account }
+    subject(:response) { get :show, params: { id: id, account_username: account.username } }
 
     context 'when id is "featured"' do
       let(:id) { 'featured' }
@@ -23,13 +26,10 @@ RSpec.describe 'ActivityPub Collections' do
         let(:remote_account) { nil }
 
         it 'returns http success and correct media type and correct items' do
-          subject
-
           expect(response)
             .to have_http_status(200)
             .and have_cacheable_headers
-          expect(response.media_type)
-            .to eq 'application/activity+json'
+          expect(response.media_type).to eq 'application/activity+json'
 
           expect(response.parsed_body[:orderedItems])
             .to be_an(Array)
@@ -45,21 +45,17 @@ RSpec.describe 'ActivityPub Collections' do
           end
 
           it 'returns http gone' do
-            subject
-
-            expect(response)
-              .to have_http_status(410)
+            expect(response).to have_http_status(410)
           end
         end
 
         context 'when account is temporarily suspended' do
-          before { account.suspend! }
+          before do
+            account.suspend!
+          end
 
           it 'returns http forbidden' do
-            subject
-
-            expect(response)
-              .to have_http_status(403)
+            expect(response).to have_http_status(403)
           end
         end
       end
@@ -69,14 +65,11 @@ RSpec.describe 'ActivityPub Collections' do
 
         context 'when getting a featured resource' do
           it 'returns http success and correct media type and expected items' do
-            subject
-
             expect(response)
               .to have_http_status(200)
               .and have_cacheable_headers
 
-            expect(response.media_type)
-              .to eq 'application/activity+json'
+            expect(response.media_type).to eq 'application/activity+json'
 
             expect(response.parsed_body[:orderedItems])
               .to be_an(Array)
@@ -87,45 +80,39 @@ RSpec.describe 'ActivityPub Collections' do
         end
 
         context 'with authorized fetch mode' do
-          before { Setting.authorized_fetch = true }
+          before do
+            allow(controller).to receive(:authorized_fetch_mode?).and_return(true)
+          end
 
           context 'when signed request account is blocked' do
-            before { account.block!(remote_account) }
+            before do
+              account.block!(remote_account)
+            end
 
             it 'returns http success and correct media type and cache headers and empty items' do
-              subject
+              expect(response).to have_http_status(200)
+              expect(response.media_type).to eq 'application/activity+json'
+              expect(response.headers['Cache-Control']).to include 'private'
 
-              expect(response)
-                .to have_http_status(200)
-              expect(response.media_type)
-                .to eq('application/activity+json')
-              expect(response.headers['Cache-Control'])
-                .to include('private')
-
-              expect(response.parsed_body)
-                .to include(
-                  orderedItems: be_an(Array).and(be_empty)
-                )
+              expect(response.parsed_body[:orderedItems])
+                .to be_an(Array)
+                .and be_empty
             end
           end
 
           context 'when signed request account is domain blocked' do
-            before { account.block_domain!(remote_account.domain) }
+            before do
+              account.block_domain!(remote_account.domain)
+            end
 
             it 'returns http success and correct media type and cache headers and empty items' do
-              subject
+              expect(response).to have_http_status(200)
+              expect(response.media_type).to eq 'application/activity+json'
+              expect(response.headers['Cache-Control']).to include 'private'
 
-              expect(response)
-                .to have_http_status(200)
-              expect(response.media_type)
-                .to eq('application/activity+json')
-              expect(response.headers['Cache-Control'])
-                .to include('private')
-
-              expect(response.parsed_body)
-                .to include(
-                  orderedItems: be_an(Array).and(be_empty)
-                )
+              expect(response.parsed_body[:orderedItems])
+                .to be_an(Array)
+                .and be_empty
             end
           end
         end
@@ -136,10 +123,7 @@ RSpec.describe 'ActivityPub Collections' do
       let(:id) { 'hoge' }
 
       it 'returns http not found' do
-        subject
-
-        expect(response)
-          .to have_http_status(404)
+        expect(response).to have_http_status(404)
       end
     end
   end
diff --git a/spec/requests/activitypub/followers_synchronizations_spec.rb b/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb
similarity index 68%
rename from spec/requests/activitypub/followers_synchronizations_spec.rb
rename to spec/controllers/activitypub/followers_synchronizations_controller_spec.rb
index 97b8a7908e..cbd982f18f 100644
--- a/spec/requests/activitypub/followers_synchronizations_spec.rb
+++ b/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb
@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-RSpec.describe 'ActivityPub Follower Synchronizations' do
+RSpec.describe ActivityPub::FollowersSynchronizationsController do
   let!(:account) { Fabricate(:account) }
   let!(:follower_example_com_user_a) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/a') }
   let!(:follower_example_com_user_b) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/b') }
@@ -14,34 +14,32 @@ RSpec.describe 'ActivityPub Follower Synchronizations' do
     follower_example_com_user_b.follow!(account)
     follower_foo_com_user_a.follow!(account)
     follower_example_com_instance_actor.follow!(account)
+
+    allow(controller).to receive(:signed_request_actor).and_return(remote_account)
   end
 
   describe 'GET #show' do
     context 'without signature' do
-      subject { get account_followers_synchronization_path(account_username: account.username) }
+      let(:remote_account) { nil }
+
+      before do
+        get :show, params: { account_username: account.username }
+      end
 
       it 'returns http not authorized' do
-        subject
-
-        expect(response)
-          .to have_http_status(401)
+        expect(response).to have_http_status(401)
       end
     end
 
     context 'with signature from example.com' do
-      subject { get account_followers_synchronization_path(account_username: account.username), headers: nil, sign_with: remote_account }
+      subject(:response) { get :show, params: { account_username: account.username } }
 
       let(:remote_account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/instance') }
 
       it 'returns http success and cache control and activity json types and correct items' do
-        subject
-
-        expect(response)
-          .to have_http_status(200)
-        expect(response.headers['Cache-Control'])
-          .to eq 'max-age=0, private'
-        expect(response.media_type)
-          .to eq 'application/activity+json'
+        expect(response).to have_http_status(200)
+        expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
+        expect(response.media_type).to eq 'application/activity+json'
 
         expect(response.parsed_body[:orderedItems])
           .to be_an(Array)
@@ -59,21 +57,17 @@ RSpec.describe 'ActivityPub Follower Synchronizations' do
         end
 
         it 'returns http gone' do
-          subject
-
-          expect(response)
-            .to have_http_status(410)
+          expect(response).to have_http_status(410)
         end
       end
 
       context 'when account is temporarily suspended' do
-        before { account.suspend! }
+        before do
+          account.suspend!
+        end
 
         it 'returns http forbidden' do
-          subject
-
-          expect(response)
-            .to have_http_status(403)
+          expect(response).to have_http_status(403)
         end
       end
     end
diff --git a/spec/controllers/activitypub/inboxes_controller_spec.rb b/spec/controllers/activitypub/inboxes_controller_spec.rb
new file mode 100644
index 0000000000..feca543cb7
--- /dev/null
+++ b/spec/controllers/activitypub/inboxes_controller_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe ActivityPub::InboxesController do
+  let(:remote_account) { nil }
+
+  before do
+    allow(controller).to receive(:signed_request_actor).and_return(remote_account)
+  end
+
+  describe 'POST #create' do
+    context 'with signature' do
+      let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub) }
+
+      before do
+        post :create, body: '{}'
+      end
+
+      it 'returns http accepted' do
+        expect(response).to have_http_status(202)
+      end
+
+      context 'with a specific account' do
+        subject(:response) { post :create, params: { account_username: account.username }, body: '{}' }
+
+        let(:account) { Fabricate(:account) }
+
+        context 'when account is permanently suspended' do
+          before do
+            account.suspend!
+            account.deletion_request.destroy
+          end
+
+          it 'returns http gone' do
+            expect(response).to have_http_status(410)
+          end
+        end
+
+        context 'when account is temporarily suspended' do
+          before do
+            account.suspend!
+          end
+
+          it 'returns http accepted' do
+            expect(response).to have_http_status(202)
+          end
+        end
+      end
+    end
+
+    context 'with Collection-Synchronization header' do
+      let(:remote_account)             { Fabricate(:account, followers_url: 'https://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor', protocol: :activitypub) }
+      let(:synchronization_collection) { remote_account.followers_url }
+      let(:synchronization_url)        { 'https://example.com/followers-for-domain' }
+      let(:synchronization_hash)       { 'somehash' }
+      let(:synchronization_header)     { "collectionId=\"#{synchronization_collection}\", digest=\"#{synchronization_hash}\", url=\"#{synchronization_url}\"" }
+
+      before do
+        allow(ActivityPub::FollowersSynchronizationWorker).to receive(:perform_async).and_return(nil)
+        allow(remote_account).to receive(:local_followers_hash).and_return('somehash')
+
+        request.headers['Collection-Synchronization'] = synchronization_header
+        post :create, body: '{}'
+      end
+
+      context 'with mismatching target collection' do
+        let(:synchronization_collection) { 'https://example.com/followers2' }
+
+        it 'does not start a synchronization job' do
+          expect(ActivityPub::FollowersSynchronizationWorker).to_not have_received(:perform_async)
+        end
+      end
+
+      context 'with mismatching domain in partial collection attribute' do
+        let(:synchronization_url) { 'https://example.org/followers' }
+
+        it 'does not start a synchronization job' do
+          expect(ActivityPub::FollowersSynchronizationWorker).to_not have_received(:perform_async)
+        end
+      end
+
+      context 'with matching digest' do
+        it 'does not start a synchronization job' do
+          expect(ActivityPub::FollowersSynchronizationWorker).to_not have_received(:perform_async)
+        end
+      end
+
+      context 'with mismatching digest' do
+        let(:synchronization_hash) { 'wronghash' }
+
+        it 'starts a synchronization job' do
+          expect(ActivityPub::FollowersSynchronizationWorker).to have_received(:perform_async)
+        end
+      end
+
+      it 'returns http accepted' do
+        expect(response).to have_http_status(202)
+      end
+    end
+
+    context 'without signature' do
+      before do
+        post :create, body: '{}'
+      end
+
+      it 'returns http not authorized' do
+        expect(response).to have_http_status(401)
+      end
+    end
+  end
+end
diff --git a/spec/requests/activitypub/outboxes_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb
similarity index 63%
rename from spec/requests/activitypub/outboxes_spec.rb
rename to spec/controllers/activitypub/outboxes_controller_spec.rb
index 22b2f97c07..ca986dcabb 100644
--- a/spec/requests/activitypub/outboxes_spec.rb
+++ b/spec/controllers/activitypub/outboxes_controller_spec.rb
@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-RSpec.describe 'ActivityPub Outboxes' do
+RSpec.describe ActivityPub::OutboxesController do
   let!(:account) { Fabricate(:account) }
 
   before do
@@ -11,11 +11,13 @@ RSpec.describe 'ActivityPub Outboxes' do
     Fabricate(:status, account: account, visibility: :private)
     Fabricate(:status, account: account, visibility: :direct)
     Fabricate(:status, account: account, visibility: :limited)
+
+    allow(controller).to receive(:signed_request_actor).and_return(remote_account)
   end
 
   describe 'GET #show' do
     context 'without signature' do
-      subject { get account_outbox_path(account_username: account.username, page: page) }
+      subject(:response) { get :show, params: { account_username: account.username, page: page } }
 
       let(:remote_account) { nil }
 
@@ -23,18 +25,13 @@ RSpec.describe 'ActivityPub Outboxes' do
         let(:page) { nil }
 
         it 'returns http success and correct media type and headers and items count' do
-          subject
-
           expect(response)
             .to have_http_status(200)
             .and have_cacheable_headers
 
-          expect(response.media_type)
-            .to eq 'application/activity+json'
-          expect(response.headers['Vary'])
-            .to be_nil
-          expect(response.parsed_body[:totalItems])
-            .to eq 4
+          expect(response.media_type).to eq 'application/activity+json'
+          expect(response.headers['Vary']).to be_nil
+          expect(response.parsed_body[:totalItems]).to eq 4
         end
 
         context 'when account is permanently suspended' do
@@ -44,21 +41,17 @@ RSpec.describe 'ActivityPub Outboxes' do
           end
 
           it 'returns http gone' do
-            subject
-
-            expect(response)
-              .to have_http_status(410)
+            expect(response).to have_http_status(410)
           end
         end
 
         context 'when account is temporarily suspended' do
-          before { account.suspend! }
+          before do
+            account.suspend!
+          end
 
           it 'returns http forbidden' do
-            subject
-
-            expect(response)
-              .to have_http_status(403)
+            expect(response).to have_http_status(403)
           end
         end
       end
@@ -67,16 +60,12 @@ RSpec.describe 'ActivityPub Outboxes' do
         let(:page) { 'true' }
 
         it 'returns http success and correct media type and vary header and items' do
-          subject
-
           expect(response)
             .to have_http_status(200)
             .and have_cacheable_headers
 
-          expect(response.media_type)
-            .to eq 'application/activity+json'
-          expect(response.headers['Vary'])
-            .to include 'Signature'
+          expect(response.media_type).to eq 'application/activity+json'
+          expect(response.headers['Vary']).to include 'Signature'
 
           expect(response.parsed_body)
             .to include(
@@ -93,42 +82,35 @@ RSpec.describe 'ActivityPub Outboxes' do
           end
 
           it 'returns http gone' do
-            subject
-
-            expect(response)
-              .to have_http_status(410)
+            expect(response).to have_http_status(410)
           end
         end
 
         context 'when account is temporarily suspended' do
-          before { account.suspend! }
+          before do
+            account.suspend!
+          end
 
           it 'returns http forbidden' do
-            subject
-
-            expect(response)
-              .to have_http_status(403)
+            expect(response).to have_http_status(403)
           end
         end
       end
     end
 
     context 'with signature' do
-      subject { get account_outbox_path(account_username: account.username, page: page), headers: nil, sign_with: remote_account }
-
       let(:remote_account) { Fabricate(:account, domain: 'example.com') }
       let(:page) { 'true' }
 
       context 'when signed request account does not follow account' do
-        it 'returns http success and correct media type and headers and items' do
-          subject
+        before do
+          get :show, params: { account_username: account.username, page: page }
+        end
 
-          expect(response)
-            .to have_http_status(200)
-          expect(response.media_type)
-            .to eq 'application/activity+json'
-          expect(response.headers['Cache-Control'])
-            .to eq 'private, no-store'
+        it 'returns http success and correct media type and headers and items' do
+          expect(response).to have_http_status(200)
+          expect(response.media_type).to eq 'application/activity+json'
+          expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
 
           expect(response.parsed_body)
             .to include(
@@ -140,17 +122,15 @@ RSpec.describe 'ActivityPub Outboxes' do
       end
 
       context 'when signed request account follows account' do
-        before { remote_account.follow!(account) }
+        before do
+          remote_account.follow!(account)
+          get :show, params: { account_username: account.username, page: page }
+        end
 
         it 'returns http success and correct media type and headers and items' do
-          subject
-
-          expect(response)
-            .to have_http_status(200)
-          expect(response.media_type)
-            .to eq 'application/activity+json'
-          expect(response.headers['Cache-Control'])
-            .to eq 'private, no-store'
+          expect(response).to have_http_status(200)
+          expect(response.media_type).to eq 'application/activity+json'
+          expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
 
           expect(response.parsed_body)
             .to include(
@@ -162,17 +142,15 @@ RSpec.describe 'ActivityPub Outboxes' do
       end
 
       context 'when signed request account is blocked' do
-        before { account.block!(remote_account) }
+        before do
+          account.block!(remote_account)
+          get :show, params: { account_username: account.username, page: page }
+        end
 
         it 'returns http success and correct media type and headers and items' do
-          subject
-
-          expect(response)
-            .to have_http_status(200)
-          expect(response.media_type)
-            .to eq 'application/activity+json'
-          expect(response.headers['Cache-Control'])
-            .to eq 'private, no-store'
+          expect(response).to have_http_status(200)
+          expect(response.media_type).to eq 'application/activity+json'
+          expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
 
           expect(response.parsed_body)
             .to include(
@@ -182,17 +160,15 @@ RSpec.describe 'ActivityPub Outboxes' do
       end
 
       context 'when signed request account is domain blocked' do
-        before { account.block_domain!(remote_account.domain) }
+        before do
+          account.block_domain!(remote_account.domain)
+          get :show, params: { account_username: account.username, page: page }
+        end
 
         it 'returns http success and correct media type and headers and items' do
-          subject
-
-          expect(response)
-            .to have_http_status(200)
-          expect(response.media_type)
-            .to eq 'application/activity+json'
-          expect(response.headers['Cache-Control'])
-            .to eq 'private, no-store'
+          expect(response).to have_http_status(200)
+          expect(response.media_type).to eq 'application/activity+json'
+          expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
 
           expect(response.parsed_body)
             .to include(
diff --git a/spec/requests/activitypub/replies_spec.rb b/spec/controllers/activitypub/replies_controller_spec.rb
similarity index 78%
rename from spec/requests/activitypub/replies_spec.rb
rename to spec/controllers/activitypub/replies_controller_spec.rb
index 313cab2a44..d7c2c2d3b0 100644
--- a/spec/requests/activitypub/replies_spec.rb
+++ b/spec/controllers/activitypub/replies_controller_spec.rb
@@ -2,9 +2,9 @@
 
 require 'rails_helper'
 
-RSpec.describe 'ActivityPub Replies' do
+RSpec.describe ActivityPub::RepliesController do
   let(:status) { Fabricate(:status, visibility: parent_visibility) }
-  let(:remote_account) { Fabricate(:account, domain: 'foobar.com') }
+  let(:remote_account)  { Fabricate(:account, domain: 'foobar.com') }
   let(:remote_reply_id) { 'https://foobar.com/statuses/1234' }
   let(:remote_querier) { nil }
 
@@ -13,10 +13,7 @@ RSpec.describe 'ActivityPub Replies' do
       let(:parent_visibility) { :private }
 
       it 'returns http not found' do
-        subject
-
-        expect(response)
-          .to have_http_status(404)
+        expect(response).to have_http_status(404)
       end
     end
 
@@ -24,10 +21,7 @@ RSpec.describe 'ActivityPub Replies' do
       let(:parent_visibility) { :direct }
 
       it 'returns http not found' do
-        subject
-
-        expect(response)
-          .to have_http_status(404)
+        expect(response).to have_http_status(404)
       end
     end
   end
@@ -37,10 +31,7 @@ RSpec.describe 'ActivityPub Replies' do
       let(:parent_visibility) { :public }
 
       it 'returns http not found' do
-        subject
-
-        expect(response)
-          .to have_http_status(404)
+        expect(response).to have_http_status(404)
       end
     end
 
@@ -57,23 +48,19 @@ RSpec.describe 'ActivityPub Replies' do
       end
 
       it 'returns http gone' do
-        subject
-
-        expect(response)
-          .to have_http_status(410)
+        expect(response).to have_http_status(410)
       end
     end
 
     context 'when account is temporarily suspended' do
       let(:parent_visibility) { :public }
 
-      before { status.account.suspend! }
+      before do
+        status.account.suspend!
+      end
 
       it 'returns http forbidden' do
-        subject
-
-        expect(response)
-          .to have_http_status(403)
+        expect(response).to have_http_status(403)
       end
     end
 
@@ -81,20 +68,15 @@ RSpec.describe 'ActivityPub Replies' do
       let(:parent_visibility) { :public }
 
       it 'returns http success and correct media type' do
-        subject
-
         expect(response)
           .to have_http_status(200)
           .and have_cacheable_headers
 
-        expect(response.media_type)
-          .to eq 'application/activity+json'
+        expect(response.media_type).to eq 'application/activity+json'
       end
 
-      context 'without `only_other_accounts` param' do
+      context 'without only_other_accounts' do
         it "returns items with thread author's replies" do
-          subject
-
           expect(response.parsed_body)
             .to include(
               first: be_a(Hash).and(
@@ -109,8 +91,6 @@ RSpec.describe 'ActivityPub Replies' do
 
         context 'when there are few self-replies' do
           it 'points next to replies from other people' do
-            subject
-
             expect(response.parsed_body)
               .to include(
                 first: be_a(Hash).and(
@@ -128,8 +108,6 @@ RSpec.describe 'ActivityPub Replies' do
           end
 
           it 'points next to other self-replies' do
-            subject
-
             expect(response.parsed_body)
               .to include(
                 first: be_a(Hash).and(
@@ -142,33 +120,31 @@ RSpec.describe 'ActivityPub Replies' do
         end
       end
 
-      context 'with `only_other_accounts` param' do
+      context 'with only_other_accounts' do
         let(:only_other_accounts) { 'true' }
 
-        it 'returns items with other public or unlisted replies and correctly inlines replies and uses IDs' do
-          subject
-
+        it 'returns items with other public or unlisted replies' do
           expect(response.parsed_body)
             .to include(
               first: be_a(Hash).and(
                 include(items: be_an(Array).and(have_attributes(size: 3)))
               )
             )
+        end
 
-          # Only inline replies that are local and public, or unlisted
+        it 'only inlines items that are local and public or unlisted replies' do
           expect(inlined_replies)
             .to all(satisfy { |item| targets_public_collection?(item) })
             .and all(satisfy { |item| ActivityPub::TagManager.instance.local_uri?(item[:id]) })
+        end
 
-          # Use ids for remote replies
+        it 'uses ids for remote toots' do
           expect(remote_replies)
             .to all(satisfy { |item| item.is_a?(String) && !ActivityPub::TagManager.instance.local_uri?(item) })
         end
 
         context 'when there are few replies' do
           it 'does not have a next page' do
-            subject
-
             expect(response.parsed_body)
               .to include(
                 first: be_a(Hash).and(not_include(next: be_present))
@@ -182,8 +158,6 @@ RSpec.describe 'ActivityPub Replies' do
           end
 
           it 'points next to other replies' do
-            subject
-
             expect(response.parsed_body)
               .to include(
                 first: be_a(Hash).and(
@@ -202,8 +176,10 @@ RSpec.describe 'ActivityPub Replies' do
 
   before do
     stub_const 'ActivityPub::RepliesController::DESCENDANTS_LIMIT', 5
+    allow(controller).to receive(:signed_request_actor).and_return(remote_querier)
 
-    Fabricate.times(2, :status, thread: status, visibility: :public)
+    Fabricate(:status, thread: status, visibility: :public)
+    Fabricate(:status, thread: status, visibility: :public)
     Fabricate(:status, thread: status, visibility: :private)
     Fabricate(:status, account: status.account, thread: status, visibility: :public)
     Fabricate(:status, account: status.account, thread: status, visibility: :private)
@@ -212,29 +188,31 @@ RSpec.describe 'ActivityPub Replies' do
   end
 
   describe 'GET #index' do
+    subject(:response) { get :index, params: { account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts } }
+
     let(:only_other_accounts) { nil }
 
     context 'with no signature' do
-      subject { get account_status_replies_path(account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts) }
-
       it_behaves_like 'allowed access'
     end
 
     context 'with signature' do
-      subject { get account_status_replies_path(account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts), headers: nil, sign_with: remote_querier }
-
       let(:remote_querier) { Fabricate(:account, domain: 'example.com') }
 
       it_behaves_like 'allowed access'
 
       context 'when signed request account is blocked' do
-        before { status.account.block!(remote_querier) }
+        before do
+          status.account.block!(remote_querier)
+        end
 
         it_behaves_like 'disallowed access'
       end
 
       context 'when signed request account is domain blocked' do
-        before { status.account.block_domain!(remote_querier.domain) }
+        before do
+          status.account.block_domain!(remote_querier.domain)
+        end
 
         it_behaves_like 'disallowed access'
       end
diff --git a/spec/controllers/admin/account_actions_controller_spec.rb b/spec/controllers/admin/account_actions_controller_spec.rb
new file mode 100644
index 0000000000..d513b3d4a0
--- /dev/null
+++ b/spec/controllers/admin/account_actions_controller_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::AccountActionsController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #new' do
+    let(:account) { Fabricate(:account) }
+
+    it 'returns http success' do
+      get :new, params: { account_id: account.id }
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'POST #create' do
+    let(:account) { Fabricate(:account) }
+
+    it 'records the account action' do
+      expect do
+        post :create, params: { account_id: account.id, admin_account_action: { type: 'silence' } }
+      end.to change { account.strikes.count }.by(1)
+
+      expect(response).to redirect_to(admin_account_path(account.id))
+    end
+  end
+end
diff --git a/spec/controllers/admin/account_moderation_notes_controller_spec.rb b/spec/controllers/admin/account_moderation_notes_controller_spec.rb
new file mode 100644
index 0000000000..5ea546f418
--- /dev/null
+++ b/spec/controllers/admin/account_moderation_notes_controller_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::AccountModerationNotesController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+  let(:target_account) { Fabricate(:account) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'POST #create' do
+    subject { post :create, params: params }
+
+    context 'when parameters are valid' do
+      let(:params) { { account_moderation_note: { target_account_id: target_account.id, content: 'test content' } } }
+
+      it 'successfully creates a note' do
+        expect { subject }.to change(AccountModerationNote, :count).by(1)
+        expect(response).to redirect_to admin_account_path(target_account.id)
+      end
+    end
+
+    context 'when the content is too short' do
+      let(:params) { { account_moderation_note: { target_account_id: target_account.id, content: '' } } }
+
+      it 'fails to create a note' do
+        expect { subject }.to_not change(AccountModerationNote, :count)
+        expect(response).to render_template 'admin/accounts/show'
+      end
+    end
+
+    context 'when the content is too long' do
+      let(:params) { { account_moderation_note: { target_account_id: target_account.id, content: 'test' * AccountModerationNote::CONTENT_SIZE_LIMIT } } }
+
+      it 'fails to create a note' do
+        expect { subject }.to_not change(AccountModerationNote, :count)
+        expect(response).to render_template 'admin/accounts/show'
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    subject { delete :destroy, params: { id: note.id } }
+
+    let!(:note) { Fabricate(:account_moderation_note, account: account, target_account: target_account) }
+    let(:account) { Fabricate(:account) }
+
+    it 'destroys note' do
+      expect { subject }.to change(AccountModerationNote, :count).by(-1)
+      expect(response).to redirect_to admin_account_path(target_account.id)
+    end
+  end
+end
diff --git a/spec/controllers/admin/accounts_controller_spec.rb b/spec/controllers/admin/accounts_controller_spec.rb
index 2ec3bca746..a182300106 100644
--- a/spec/controllers/admin/accounts_controller_spec.rb
+++ b/spec/controllers/admin/accounts_controller_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Admin::AccountsController do
   before { sign_in current_user, scope: :user }
 
   describe 'GET #index' do
-    let(:current_user) { Fabricate(:admin_user) }
+    let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
     let(:params) do
       {
         origin: 'local',
@@ -53,21 +53,22 @@ RSpec.describe Admin::AccountsController do
   end
 
   describe 'GET #show' do
-    let(:current_user) { Fabricate(:admin_user) }
+    let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     describe 'account moderation notes' do
       let(:account) { Fabricate(:account) }
 
       it 'includes moderation notes' do
-        note1 = Fabricate(:account_moderation_note, target_account: account, content: 'Note 1 remarks')
-        note2 = Fabricate(:account_moderation_note, target_account: account, content: 'Note 2 remarks')
+        note1 = Fabricate(:account_moderation_note, target_account: account)
+        note2 = Fabricate(:account_moderation_note, target_account: account)
 
         get :show, params: { id: account.id }
         expect(response).to have_http_status(200)
 
-        expect(response.body)
-          .to include(note1.content)
-          .and include(note2.content)
+        moderation_notes = assigns(:moderation_notes).to_a
+
+        expect(moderation_notes.size).to be 2
+        expect(moderation_notes).to eq [note1, note2]
       end
     end
 
diff --git a/spec/system/admin/action_logs_spec.rb b/spec/controllers/admin/action_logs_controller_spec.rb
similarity index 65%
rename from spec/system/admin/action_logs_spec.rb
rename to spec/controllers/admin/action_logs_controller_spec.rb
index b6a6996f91..3daf260672 100644
--- a/spec/system/admin/action_logs_spec.rb
+++ b/spec/controllers/admin/action_logs_controller_spec.rb
@@ -2,33 +2,29 @@
 
 require 'rails_helper'
 
-RSpec.describe 'Admin Action Logs' do
+RSpec.describe Admin::ActionLogsController do
+  render_views
+
   # Action logs typically cause issues when their targets are not in the database
   let!(:account) { Fabricate(:account) }
 
   before do
-    populate_action_logs
-    sign_in Fabricate(:admin_user)
-  end
-
-  describe 'Viewing action logs' do
-    it 'shows page with action logs listed' do
-      visit admin_action_logs_path
-
-      expect(page)
-        .to have_title(I18n.t('admin.action_logs.title'))
-        .and have_css('.log-entry')
-    end
-  end
-
-  private
-
-  def populate_action_logs
     orphaned_log_types.map do |type|
       Fabricate(:action_log, account: account, action: 'destroy', target_type: type, target_id: 1312)
     end
   end
 
+  describe 'GET #index' do
+    it 'returns 200' do
+      sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
+      get :index, params: { page: 1 }
+
+      expect(response).to have_http_status(200)
+    end
+  end
+
+  private
+
   def orphaned_log_types
     %w(
       Account
diff --git a/spec/controllers/admin/base_controller_spec.rb b/spec/controllers/admin/base_controller_spec.rb
index d739b54644..8b8b7fe63d 100644
--- a/spec/controllers/admin/base_controller_spec.rb
+++ b/spec/controllers/admin/base_controller_spec.rb
@@ -3,54 +3,40 @@
 require 'rails_helper'
 
 RSpec.describe Admin::BaseController do
-  render_views
-
   controller do
     def success
       authorize :dashboard, :index?
-      render html: '<p>success</p>', layout: true
+      render 'admin/reports/show'
     end
   end
 
-  before { routes.draw { get 'success' => 'admin/base#success' } }
+  it 'requires administrator or moderator' do
+    routes.draw { get 'success' => 'admin/base#success' }
+    sign_in(Fabricate(:user))
+    get :success
 
-  context 'when signed in as regular user' do
-    before { sign_in Fabricate(:user) }
-
-    it 'responds with unauthorized' do
-      get :success
-
-      expect(response).to have_http_status(403)
-    end
+    expect(response).to have_http_status(403)
   end
 
-  context 'when signed in as moderator' do
-    before { sign_in Fabricate(:moderator_user) }
+  it 'returns private cache control headers' do
+    routes.draw { get 'success' => 'admin/base#success' }
+    sign_in(Fabricate(:user, role: UserRole.find_by(name: 'Moderator')))
+    get :success
 
-    it 'returns success with private headers and admin layout' do
-      get :success
-
-      expect(response)
-        .to have_http_status(200)
-      expect(response.headers['Cache-Control'])
-        .to include('private, no-store')
-      expect(response.parsed_body)
-        .to have_css('body.admin')
-    end
+    expect(response.headers['Cache-Control']).to include('private, no-store')
   end
 
-  context 'when signed in as admin' do
-    before { sign_in Fabricate(:admin_user) }
+  it 'renders admin layout as a moderator' do
+    routes.draw { get 'success' => 'admin/base#success' }
+    sign_in(Fabricate(:user, role: UserRole.find_by(name: 'Moderator')))
+    get :success
+    expect(response).to render_template layout: 'admin'
+  end
 
-    it 'returns success with private headers and admin layout' do
-      get :success
-
-      expect(response)
-        .to have_http_status(200)
-      expect(response.headers['Cache-Control'])
-        .to include('private, no-store')
-      expect(response.parsed_body)
-        .to have_css('body.admin')
-    end
+  it 'renders admin layout as an admin' do
+    routes.draw { get 'success' => 'admin/base#success' }
+    sign_in(Fabricate(:user, role: UserRole.find_by(name: 'Admin')))
+    get :success
+    expect(response).to render_template layout: 'admin'
   end
 end
diff --git a/spec/controllers/admin/change_emails_controller_spec.rb b/spec/controllers/admin/change_emails_controller_spec.rb
new file mode 100644
index 0000000000..dd8a764b64
--- /dev/null
+++ b/spec/controllers/admin/change_emails_controller_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::ChangeEmailsController do
+  render_views
+
+  let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in admin
+  end
+
+  describe 'GET #show' do
+    it 'returns http success' do
+      user = Fabricate(:user)
+
+      get :show, params: { account_id: user.account.id }
+
+      expect(response).to have_http_status(200)
+    end
+  end
+
+  describe 'GET #update' do
+    before do
+      allow(UserMailer).to receive(:confirmation_instructions)
+        .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil))
+    end
+
+    it 'returns http success' do
+      user = Fabricate(:user)
+
+      previous_email = user.email
+
+      post :update, params: { account_id: user.account.id, user: { unconfirmed_email: 'test@example.com' } }
+
+      user.reload
+
+      expect(user.email).to eq previous_email
+      expect(user.unconfirmed_email).to eq 'test@example.com'
+      expect(user.confirmation_token).to_not be_nil
+
+      expect(UserMailer).to have_received(:confirmation_instructions).with(user, user.confirmation_token, { to: 'test@example.com' })
+
+      expect(response).to redirect_to(admin_account_path(user.account.id))
+    end
+  end
+end
diff --git a/spec/controllers/admin/confirmations_controller_spec.rb b/spec/controllers/admin/confirmations_controller_spec.rb
index 22035d15e6..9559160786 100644
--- a/spec/controllers/admin/confirmations_controller_spec.rb
+++ b/spec/controllers/admin/confirmations_controller_spec.rb
@@ -6,12 +6,12 @@ RSpec.describe Admin::ConfirmationsController do
   render_views
 
   before do
-    sign_in Fabricate(:admin_user), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'POST #create' do
     it 'confirms the user' do
-      user = Fabricate(:user, confirmed_at: nil)
+      user = Fabricate(:user, confirmed_at: false)
       post :create, params: { account_id: user.account.id }
 
       expect(response).to redirect_to(admin_accounts_path)
diff --git a/spec/controllers/admin/custom_emojis_controller_spec.rb b/spec/controllers/admin/custom_emojis_controller_spec.rb
new file mode 100644
index 0000000000..57c2a6d21b
--- /dev/null
+++ b/spec/controllers/admin/custom_emojis_controller_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::CustomEmojisController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    before do
+      Fabricate(:custom_emoji)
+    end
+
+    it 'renders index page' do
+      get :index
+
+      expect(response).to have_http_status 200
+      expect(response).to render_template :index
+    end
+  end
+
+  describe 'GET #new' do
+    it 'renders new page' do
+      get :new
+
+      expect(response).to have_http_status 200
+      expect(response).to render_template :new
+    end
+  end
+
+  describe 'POST #create' do
+    subject { post :create, params: { custom_emoji: params } }
+
+    let(:image) { fixture_file_upload(Rails.root.join('spec', 'fixtures', 'files', 'emojo.png'), 'image/png') }
+
+    context 'when parameter is valid' do
+      let(:params) { { shortcode: 'test', image: image } }
+
+      it 'creates custom emoji' do
+        expect { subject }.to change(CustomEmoji, :count).by(1)
+      end
+    end
+
+    context 'when parameter is invalid' do
+      let(:params) { { shortcode: 't', image: image } }
+
+      it 'renders new' do
+        expect(subject).to render_template :new
+      end
+    end
+  end
+end
diff --git a/spec/controllers/admin/dashboard_controller_spec.rb b/spec/controllers/admin/dashboard_controller_spec.rb
new file mode 100644
index 0000000000..9177be4b6d
--- /dev/null
+++ b/spec/controllers/admin/dashboard_controller_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::DashboardController do
+  render_views
+
+  describe 'GET #index' do
+    let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) }
+
+    before do
+      stub_system_checks
+      Fabricate :software_update
+      sign_in(user)
+    end
+
+    it 'returns http success and body with system check messages' do
+      get :index
+
+      expect(response)
+        .to have_http_status(200)
+        .and have_attributes(
+          body: include(I18n.t('admin.system_checks.software_version_patch_check.message_html'))
+        )
+    end
+
+    private
+
+    def stub_system_checks
+      stub_const 'Admin::SystemCheck::ACTIVE_CHECKS', [
+        Admin::SystemCheck::SoftwareVersionCheck,
+      ]
+    end
+  end
+end
diff --git a/spec/controllers/admin/disputes/appeals_controller_spec.rb b/spec/controllers/admin/disputes/appeals_controller_spec.rb
index b67ee30f74..678ceee115 100644
--- a/spec/controllers/admin/disputes/appeals_controller_spec.rb
+++ b/spec/controllers/admin/disputes/appeals_controller_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe Admin::Disputes::AppealsController do
   let(:appeal) { Fabricate(:appeal, strike: strike, account: target_account) }
 
   describe 'GET #index' do
-    let(:current_user) { Fabricate(:admin_user) }
+    let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     before { appeal }
 
@@ -32,7 +32,7 @@ RSpec.describe Admin::Disputes::AppealsController do
   describe 'POST #approve' do
     subject { post :approve, params: { id: appeal.id } }
 
-    let(:current_user) { Fabricate(:admin_user) }
+    let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     it 'redirects back to the strike page and notifies target account about approved appeal', :inline_jobs do
       emails = capture_emails { subject }
@@ -56,7 +56,7 @@ RSpec.describe Admin::Disputes::AppealsController do
   describe 'POST #reject' do
     subject { post :reject, params: { id: appeal.id } }
 
-    let(:current_user) { Fabricate(:admin_user) }
+    let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     it 'redirects back to the strike page and notifies target account about rejected appeal', :inline_jobs do
       emails = capture_emails { subject }
diff --git a/spec/controllers/admin/domain_allows_controller_spec.rb b/spec/controllers/admin/domain_allows_controller_spec.rb
new file mode 100644
index 0000000000..036d229091
--- /dev/null
+++ b/spec/controllers/admin/domain_allows_controller_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::DomainAllowsController do
+  render_views
+
+  before do
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
+  end
+
+  describe 'GET #new' do
+    it 'assigns a new domain allow' do
+      get :new
+
+      expect(response).to have_http_status(200)
+    end
+  end
+
+  describe 'POST #create' do
+    it 'blocks the domain when succeeded to save' do
+      post :create, params: { domain_allow: { domain: 'example.com' } }
+
+      expect(flash[:notice]).to eq I18n.t('admin.domain_allows.created_msg')
+      expect(response).to redirect_to(admin_instances_path)
+    end
+
+    it 'renders new when failed to save' do
+      Fabricate(:domain_allow, domain: 'example.com')
+
+      post :create, params: { domain_allow: { domain: 'example.com' } }
+
+      expect(response).to render_template :new
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    it 'disallows the domain' do
+      service = instance_double(UnallowDomainService, call: true)
+      allow(UnallowDomainService).to receive(:new).and_return(service)
+      domain_allow = Fabricate(:domain_allow)
+      delete :destroy, params: { id: domain_allow.id }
+
+      expect(service).to have_received(:call).with(domain_allow)
+      expect(flash[:notice]).to eq I18n.t('admin.domain_allows.destroyed_msg')
+      expect(response).to redirect_to(admin_instances_path)
+    end
+  end
+end
diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb
index bf3737bdb3..a99ca6c641 100644
--- a/spec/controllers/admin/domain_blocks_controller_spec.rb
+++ b/spec/controllers/admin/domain_blocks_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Admin::DomainBlocksController do
   render_views
 
   before do
-    sign_in Fabricate(:admin_user), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'GET #new' do
@@ -48,11 +48,15 @@ RSpec.describe Admin::DomainBlocksController do
         post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } }
       end
 
-      it 'records a block, calls a worker, redirects' do
+      it 'records a block' do
         expect(DomainBlock.exists?(domain: 'example.com', severity: 'silence')).to be true
+      end
 
+      it 'calls DomainBlockWorker' do
         expect(DomainBlockWorker).to have_received(:perform_async)
+      end
 
+      it 'redirects with a success message' do
         expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg')
         expect(response).to redirect_to(admin_instances_path(limited: '1'))
       end
@@ -64,13 +68,16 @@ RSpec.describe Admin::DomainBlocksController do
         post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } }
       end
 
-      it 'does not record a block or call worker, renders new' do
+      it 'does not record a block' do
         expect(DomainBlock.exists?(domain: 'example.com', severity: 'silence')).to be false
+      end
 
+      it 'does not call DomainBlockWorker' do
         expect(DomainBlockWorker).to_not have_received(:perform_async)
+      end
 
-        expect(response.parsed_body.title)
-          .to match(I18n.t('admin.domain_blocks.new.title'))
+      it 'renders new' do
+        expect(response).to render_template :new
       end
     end
 
@@ -80,13 +87,16 @@ RSpec.describe Admin::DomainBlocksController do
           post :create, params: { domain_block: { domain: 'example.com', severity: 'suspend', reject_media: true, reject_reports: true } }
         end
 
-        it 'does not record a block or call worker, renders confirm suspension' do
+        it 'does not record a block' do
           expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be false
+        end
 
+        it 'does not call DomainBlockWorker' do
           expect(DomainBlockWorker).to_not have_received(:perform_async)
+        end
 
-          expect(response.parsed_body.title)
-            .to match(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
+        it 'renders confirm_suspension' do
+          expect(response).to render_template :confirm_suspension
         end
       end
 
@@ -95,11 +105,15 @@ RSpec.describe Admin::DomainBlocksController do
           post :create, params: { :domain_block => { domain: 'example.com', severity: 'suspend', reject_media: true, reject_reports: true }, 'confirm' => '' }
         end
 
-        it 'records a block and calls worker and redirects' do
+        it 'records a block' do
           expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be true
+        end
 
+        it 'calls DomainBlockWorker' do
           expect(DomainBlockWorker).to have_received(:perform_async)
+        end
 
+        it 'redirects with a success message' do
           expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg')
           expect(response).to redirect_to(admin_instances_path(limited: '1'))
         end
@@ -116,13 +130,16 @@ RSpec.describe Admin::DomainBlocksController do
           post :create, params: { domain_block: { domain: 'example.com', severity: 'suspend', reject_media: true, reject_reports: true } }
         end
 
-        it 'does not record a block or call worker, renders confirm suspension' do
+        it 'does not record a block' do
           expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be false
+        end
 
+        it 'does not call DomainBlockWorker' do
           expect(DomainBlockWorker).to_not have_received(:perform_async)
+        end
 
-          expect(response.parsed_body.title)
-            .to match(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
+        it 'renders confirm_suspension' do
+          expect(response).to render_template :confirm_suspension
         end
       end
 
@@ -131,11 +148,15 @@ RSpec.describe Admin::DomainBlocksController do
           post :create, params: { :domain_block => { domain: 'example.com', severity: 'suspend', reject_media: true, reject_reports: true }, 'confirm' => '' }
         end
 
-        it 'updates the record and calls worker, redirects' do
+        it 'updates the record' do
           expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be true
+        end
 
+        it 'calls DomainBlockWorker' do
           expect(DomainBlockWorker).to have_received(:perform_async)
+        end
 
+        it 'redirects with a success message' do
           expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg')
           expect(response).to redirect_to(admin_instances_path(limited: '1'))
         end
diff --git a/spec/controllers/admin/email_domain_blocks_controller_spec.rb b/spec/controllers/admin/email_domain_blocks_controller_spec.rb
new file mode 100644
index 0000000000..4de3ef0f62
--- /dev/null
+++ b/spec/controllers/admin/email_domain_blocks_controller_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::EmailDomainBlocksController do
+  render_views
+
+  before do
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
+  end
+
+  describe 'GET #index' do
+    around do |example|
+      default_per_page = EmailDomainBlock.default_per_page
+      EmailDomainBlock.paginates_per 2
+      example.run
+      EmailDomainBlock.paginates_per default_per_page
+    end
+
+    it 'returns http success' do
+      2.times { Fabricate(:email_domain_block) }
+      Fabricate(:email_domain_block, allow_with_approval: true)
+      get :index, params: { page: 2 }
+      expect(response).to have_http_status(200)
+    end
+  end
+
+  describe 'GET #new' do
+    it 'returns http success' do
+      get :new
+      expect(response).to have_http_status(200)
+    end
+  end
+
+  describe 'POST #create' do
+    context 'when resolve button is pressed' do
+      before do
+        resolver = instance_double(Resolv::DNS)
+
+        allow(resolver).to receive(:getresources)
+          .with('example.com', Resolv::DNS::Resource::IN::MX)
+          .and_return([])
+        allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([])
+        allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([])
+        allow(resolver).to receive(:timeouts=).and_return(nil)
+        allow(Resolv::DNS).to receive(:open).and_yield(resolver)
+
+        post :create, params: { email_domain_block: { domain: 'example.com' } }
+      end
+
+      it 'renders new template' do
+        expect(response).to render_template(:new)
+      end
+    end
+
+    context 'when save button is pressed' do
+      before do
+        post :create, params: { email_domain_block: { domain: 'example.com' }, save: '' }
+      end
+
+      it 'blocks the domain' do
+        expect(EmailDomainBlock.find_by(domain: 'example.com')).to_not be_nil
+      end
+
+      it 'redirects to e-mail domain blocks' do
+        expect(response).to redirect_to(admin_email_domain_blocks_path)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/admin/export_domain_allows_controller_spec.rb b/spec/controllers/admin/export_domain_allows_controller_spec.rb
index dcb1f55a99..0a2e342620 100644
--- a/spec/controllers/admin/export_domain_allows_controller_spec.rb
+++ b/spec/controllers/admin/export_domain_allows_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Admin::ExportDomainAllowsController do
   render_views
 
   before do
-    sign_in Fabricate(:admin_user), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'GET #new' do
@@ -32,16 +32,15 @@ RSpec.describe Admin::ExportDomainAllowsController do
     it 'allows imported domains' do
       post :import, params: { admin_import: { data: fixture_file_upload('domain_allows.csv') } }
 
-      expect(response)
-        .to redirect_to(admin_instances_path)
+      expect(response).to redirect_to(admin_instances_path)
 
-      # Header row should not be imported, but domains should
-      expect(DomainAllow)
-        .to_not exist(domain: '#domain')
-      expect(DomainAllow)
-        .to exist(domain: 'good.domain')
-      expect(DomainAllow)
-        .to exist(domain: 'better.domain')
+      # Header should not be imported
+      expect(DomainAllow.where(domain: '#domain').present?).to be(false)
+
+      # Domains should now be added
+      get :export, params: { format: :csv }
+      expect(response).to have_http_status(200)
+      expect(response.body).to eq(domain_allows_csv_file)
     end
 
     it 'displays error on no file selected' do
diff --git a/spec/controllers/admin/export_domain_blocks_controller_spec.rb b/spec/controllers/admin/export_domain_blocks_controller_spec.rb
index 1e748e34b6..25b919aa76 100644
--- a/spec/controllers/admin/export_domain_blocks_controller_spec.rb
+++ b/spec/controllers/admin/export_domain_blocks_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Admin::ExportDomainBlocksController do
   render_views
 
   before do
-    sign_in Fabricate(:admin_user), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'GET #new' do
@@ -44,11 +44,11 @@ RSpec.describe Admin::ExportDomainBlocksController do
       end
 
       it 'renders page with extended domain blocks' do
-        expect(mapped_batch_table_rows_with_expanded_params).to contain_exactly(
-          ['bad.domain', false],
-          ['worse.domain', false],
-          ['reject.media', false],
-          ['little.spam', true]
+        expect(assigns(:domain_blocks).map { |block| [block.domain, block.reject_favourite, block.reject_friend] }).to contain_exactly(
+          ['bad.domain', false, false],
+          ['worse.domain', false, false],
+          ['reject.media', false, false],
+          ['little.spam', true, false]
         )
       end
 
@@ -73,10 +73,6 @@ RSpec.describe Admin::ExportDomainBlocksController do
       batch_table_rows.map { |row| [row.at_css('[id$=_domain]')['value'], row.at_css('[id$=_severity]')['value'].to_sym] }
     end
 
-    def mapped_batch_table_rows_with_expanded_params
-      batch_table_rows.map { |row| [row.at_css('[id$=_domain]')['value'], row.at_css('[id$=_reject_favourite]')['value'] == 'true'] }
-    end
-
     def batch_table_rows
       response.parsed_body.css('body div.batch-table__row')
     end
diff --git a/spec/controllers/admin/follow_recommendations_controller_spec.rb b/spec/controllers/admin/follow_recommendations_controller_spec.rb
new file mode 100644
index 0000000000..d614f2ef43
--- /dev/null
+++ b/spec/controllers/admin/follow_recommendations_controller_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::FollowRecommendationsController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #show' do
+    it 'returns http success' do
+      get :show
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+end
diff --git a/spec/controllers/admin/instances_controller_spec.rb b/spec/controllers/admin/instances_controller_spec.rb
index b6508eb38b..1e65373e1f 100644
--- a/spec/controllers/admin/instances_controller_spec.rb
+++ b/spec/controllers/admin/instances_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe Admin::InstancesController do
   render_views
 
-  let(:current_user) { Fabricate(:admin_user) }
+  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
   let!(:account_popular_main) { Fabricate(:account, domain: 'popular') }
 
@@ -49,11 +49,23 @@ RSpec.describe Admin::InstancesController do
 
       expect(response).to have_http_status(200)
 
-      expect(response.body)
-        .to include(I18n.t('admin.instances.totals_time_period_hint_html'))
-        .and include(I18n.t('accounts.nothing_here'))
+      instance = assigns(:instance)
+      expect(instance).to_not be_new_record
 
       expect(Admin::ActionLogFilter).to have_received(:new).with(target_domain: account_popular_main.domain)
+
+      action_logs = assigns(:action_logs).to_a
+      expect(action_logs.size).to eq 0
+    end
+
+    context 'with an unknown domain' do
+      it 'returns http success' do
+        get :show, params: { id: 'unknown.example' }
+        expect(response).to have_http_status(200)
+
+        instance = assigns(:instance)
+        expect(instance).to be_new_record
+      end
     end
   end
 
diff --git a/spec/controllers/admin/ip_blocks_controller_spec.rb b/spec/controllers/admin/ip_blocks_controller_spec.rb
new file mode 100644
index 0000000000..2e32db5a01
--- /dev/null
+++ b/spec/controllers/admin/ip_blocks_controller_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::IpBlocksController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    it 'returns http success' do
+      get :index
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'GET #new' do
+    it 'returns http success and renders view' do
+      get :new
+
+      expect(response).to have_http_status(:success)
+      expect(response).to render_template(:new)
+    end
+  end
+
+  describe 'POST #create' do
+    context 'with valid data' do
+      it 'creates a new ip block and redirects' do
+        expect do
+          post :create, params: { ip_block: { ip: '1.1.1.1', severity: 'no_access', expires_in: 1.day.to_i.to_s } }
+        end.to change(IpBlock, :count).by(1)
+
+        expect(response).to redirect_to(admin_ip_blocks_path)
+        expect(flash.notice).to match(I18n.t('admin.ip_blocks.created_msg'))
+      end
+    end
+
+    context 'with invalid data' do
+      it 'does not create new a ip block and renders new' do
+        expect do
+          post :create, params: { ip_block: { ip: '1.1.1.1' } }
+        end.to_not change(IpBlock, :count)
+
+        expect(response).to have_http_status(:success)
+        expect(response).to render_template(:new)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/admin/relationships_controller_spec.rb b/spec/controllers/admin/relationships_controller_spec.rb
new file mode 100644
index 0000000000..214be7c7cd
--- /dev/null
+++ b/spec/controllers/admin/relationships_controller_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::RelationshipsController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    let(:account) { Fabricate(:account) }
+
+    it 'returns http success' do
+      get :index, params: { account_id: account.id }
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+end
diff --git a/spec/controllers/admin/relays_controller_spec.rb b/spec/controllers/admin/relays_controller_spec.rb
new file mode 100644
index 0000000000..c6251a6d76
--- /dev/null
+++ b/spec/controllers/admin/relays_controller_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::RelaysController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    it 'returns http success' do
+      get :index
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'GET #new' do
+    it 'returns http success and renders view' do
+      get :new
+
+      expect(response).to have_http_status(:success)
+      expect(response).to render_template(:new)
+    end
+  end
+
+  describe 'POST #create' do
+    context 'with valid data' do
+      let(:inbox_url) { 'https://example.com/inbox' }
+
+      before do
+        stub_request(:post, inbox_url).to_return(status: 200)
+      end
+
+      it 'creates a new relay and redirects' do
+        expect do
+          post :create, params: { relay: { inbox_url: inbox_url } }
+        end.to change(Relay, :count).by(1)
+
+        expect(response).to redirect_to(admin_relays_path)
+      end
+    end
+
+    context 'with invalid data' do
+      it 'does not create new a relay and renders new' do
+        expect do
+          post :create, params: { relay: { inbox_url: 'invalid' } }
+        end.to_not change(Relay, :count)
+
+        expect(response).to have_http_status(:success)
+        expect(response).to render_template(:new)
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    let(:relay) { Fabricate(:relay) }
+
+    it 'deletes an existing relay' do
+      delete :destroy, params: { id: relay.id }
+
+      expect { relay.reload }.to raise_error(ActiveRecord::RecordNotFound)
+      expect(response).to redirect_to(admin_relays_path)
+    end
+  end
+
+  describe 'POST #enable' do
+    let(:relay) { Fabricate(:relay, state: :idle) }
+
+    before do
+      stub_request(:post, /example.com/).to_return(status: 200)
+    end
+
+    it 'updates a relay from idle to pending' do
+      post :enable, params: { id: relay.id }
+
+      expect(relay.reload).to be_pending
+      expect(response).to redirect_to(admin_relays_path)
+    end
+  end
+
+  describe 'POST #disable' do
+    let(:relay) { Fabricate(:relay, state: :pending) }
+
+    before do
+      stub_request(:post, /example.com/).to_return(status: 200)
+    end
+
+    it 'updates a relay from pending to idle' do
+      post :disable, params: { id: relay.id }
+
+      expect(relay.reload).to be_idle
+      expect(response).to redirect_to(admin_relays_path)
+    end
+  end
+end
diff --git a/spec/controllers/admin/report_notes_controller_spec.rb b/spec/controllers/admin/report_notes_controller_spec.rb
new file mode 100644
index 0000000000..423a64ebc4
--- /dev/null
+++ b/spec/controllers/admin/report_notes_controller_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::ReportNotesController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'POST #create' do
+    subject { post :create, params: params }
+
+    let(:report) { Fabricate(:report, action_taken_at: action_taken, action_taken_by_account_id: account_id) }
+
+    context 'when parameter is valid' do
+      context 'when report is unsolved' do
+        let(:action_taken) { nil }
+        let(:account_id) { nil }
+
+        context 'when create_and_resolve flag is on' do
+          let(:params) { { report_note: { report_id: report.id, content: 'test content' }, create_and_resolve: nil } }
+
+          it 'creates a report note and resolves report' do
+            expect { subject }.to change(ReportNote, :count).by(1)
+            expect(report.reload).to be_action_taken
+            expect(response).to redirect_to admin_reports_path
+          end
+        end
+
+        context 'when create_and_resolve flag is false' do
+          let(:params) { { report_note: { report_id: report.id, content: 'test content' } } }
+
+          it 'creates a report note and does not resolve report' do
+            expect { subject }.to change(ReportNote, :count).by(1)
+            expect(report.reload).to_not be_action_taken
+            expect(response).to redirect_to admin_report_path(report)
+          end
+        end
+      end
+
+      context 'when report is resolved' do
+        let(:action_taken) { Time.now.utc }
+        let(:account_id) { user.account.id }
+
+        context 'when create_and_unresolve flag is on' do
+          let(:params) { { report_note: { report_id: report.id, content: 'test content' }, create_and_unresolve: nil } }
+
+          it 'creates a report note and unresolves report' do
+            expect { subject }.to change(ReportNote, :count).by(1)
+            expect(report.reload).to_not be_action_taken
+            expect(response).to redirect_to admin_report_path(report)
+          end
+        end
+
+        context 'when create_and_unresolve flag is false' do
+          let(:params) { { report_note: { report_id: report.id, content: 'test content' } } }
+
+          it 'creates a report note and does not unresolve report' do
+            expect { subject }.to change(ReportNote, :count).by(1)
+            expect(report.reload).to be_action_taken
+            expect(response).to redirect_to admin_report_path(report)
+          end
+        end
+      end
+    end
+
+    context 'when content is too short' do
+      let(:params) { { report_note: { report_id: report.id, content: '' } } }
+      let(:action_taken) { nil }
+      let(:account_id) { nil }
+
+      it 'renders admin/reports/show' do
+        expect { subject }.to_not change(ReportNote, :count)
+        expect(subject).to render_template 'admin/reports/show'
+      end
+    end
+
+    context 'when content is too long' do
+      let(:params) { { report_note: { report_id: report.id, content: 'test' * ReportNote::CONTENT_SIZE_LIMIT } } }
+      let(:action_taken) { nil }
+      let(:account_id) { nil }
+
+      it 'renders admin/reports/show' do
+        expect { subject }.to_not change(ReportNote, :count)
+        expect(subject).to render_template 'admin/reports/show'
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    subject { delete :destroy, params: { id: report_note.id } }
+
+    let!(:report_note) { Fabricate(:report_note) }
+
+    it 'deletes note' do
+      expect { subject }.to change(ReportNote, :count).by(-1)
+      expect(response).to redirect_to admin_report_path(report_note.report)
+    end
+  end
+end
diff --git a/spec/controllers/admin/reports/actions_controller_spec.rb b/spec/controllers/admin/reports/actions_controller_spec.rb
index 87a48a7a3a..6185702c30 100644
--- a/spec/controllers/admin/reports/actions_controller_spec.rb
+++ b/spec/controllers/admin/reports/actions_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe Admin::Reports::ActionsController do
   render_views
 
-  let(:user) { Fabricate(:admin_user) }
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
   before do
     sign_in user, scope: :user
diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb
new file mode 100644
index 0000000000..1252ceb1f4
--- /dev/null
+++ b/spec/controllers/admin/reports_controller_spec.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::ReportsController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    it 'returns http success with no filters' do
+      specified = Fabricate(:report, action_taken_at: nil, comment: 'First report')
+      other = Fabricate(:report, action_taken_at: Time.now.utc, comment: 'Second report')
+
+      get :index
+
+      expect(response).to have_http_status(200)
+      expect(response.body)
+        .to include(specified.comment)
+        .and not_include(other.comment)
+    end
+
+    it 'returns http success with resolved filter' do
+      specified = Fabricate(:report, action_taken_at: Time.now.utc, comment: 'First report')
+      other = Fabricate(:report, action_taken_at: nil, comment: 'Second report')
+
+      get :index, params: { resolved: '1' }
+
+      expect(response).to have_http_status(200)
+      expect(response.body)
+        .to include(specified.comment)
+        .and not_include(other.comment)
+    end
+  end
+
+  describe 'GET #show' do
+    it 'renders report' do
+      report = Fabricate(:report, comment: 'A big problem')
+
+      get :show, params: { id: report }
+
+      expect(response).to have_http_status(200)
+      expect(response.body)
+        .to include(report.comment)
+    end
+
+    describe 'account moderation notes' do
+      let(:report) { Fabricate(:report) }
+
+      it 'includes moderation notes' do
+        note1 = Fabricate(:report_note, report: report)
+        note2 = Fabricate(:report_note, report: report)
+
+        get :show, params: { id: report }
+
+        expect(response).to have_http_status(200)
+
+        report_notes = assigns(:report_notes).to_a
+
+        expect(report_notes.size).to be 2
+        expect(report_notes).to eq [note1, note2]
+      end
+    end
+  end
+
+  describe 'POST #resolve' do
+    it 'resolves the report' do
+      report = Fabricate(:report)
+
+      put :resolve, params: { id: report }
+      expect(response).to redirect_to(admin_reports_path)
+      report.reload
+      expect(report.action_taken_by_account).to eq user.account
+      expect(report.action_taken?).to be true
+      expect(last_action_log.target).to eq(report)
+    end
+  end
+
+  describe 'POST #reopen' do
+    it 'reopens the report' do
+      report = Fabricate(:report, action_taken_at: 3.days.ago)
+
+      put :reopen, params: { id: report }
+      expect(response).to redirect_to(admin_report_path(report))
+      report.reload
+      expect(report.action_taken_by_account).to be_nil
+      expect(report.action_taken?).to be false
+      expect(last_action_log.target).to eq(report)
+    end
+  end
+
+  describe 'POST #assign_to_self' do
+    it 'reopens the report' do
+      report = Fabricate(:report)
+
+      put :assign_to_self, params: { id: report }
+      expect(response).to redirect_to(admin_report_path(report))
+      report.reload
+      expect(report.assigned_account).to eq user.account
+      expect(last_action_log.target).to eq(report)
+    end
+  end
+
+  describe 'POST #unassign' do
+    it 'reopens the report' do
+      report = Fabricate(:report, assigned_account_id: Account.last.id)
+
+      put :unassign, params: { id: report }
+      expect(response).to redirect_to(admin_report_path(report))
+      report.reload
+      expect(report.assigned_account).to be_nil
+      expect(last_action_log.target).to eq(report)
+    end
+  end
+
+  private
+
+  def last_action_log
+    Admin::ActionLog.last
+  end
+end
diff --git a/spec/controllers/admin/roles_controller_spec.rb b/spec/controllers/admin/roles_controller_spec.rb
new file mode 100644
index 0000000000..2c43a0ca87
--- /dev/null
+++ b/spec/controllers/admin/roles_controller_spec.rb
@@ -0,0 +1,251 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::RolesController do
+  render_views
+
+  let(:permissions)  { UserRole::Flags::NONE }
+  let(:current_role) { UserRole.create(name: 'Foo', permissions: permissions, position: 10) }
+  let(:current_user) { Fabricate(:user, role: current_role) }
+
+  before do
+    sign_in current_user, scope: :user
+  end
+
+  describe 'GET #index' do
+    before do
+      get :index
+    end
+
+    context 'when user does not have permission to manage roles' do
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(403)
+      end
+    end
+
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+    end
+  end
+
+  describe 'GET #new' do
+    before do
+      get :new
+    end
+
+    context 'when user does not have permission to manage roles' do
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(403)
+      end
+    end
+
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+    end
+  end
+
+  describe 'POST #create' do
+    let(:selected_position) { 1 }
+    let(:selected_permissions_as_keys) { %w(manage_roles) }
+
+    before do
+      post :create, params: { user_role: { name: 'Bar', position: selected_position, permissions_as_keys: selected_permissions_as_keys } }
+    end
+
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+      context 'when new role\'s does not elevate above the user\'s role' do
+        let(:selected_position) { 1 }
+        let(:selected_permissions_as_keys) { %w(manage_roles) }
+
+        it 'redirects to roles page' do
+          expect(response).to redirect_to(admin_roles_path)
+        end
+
+        it 'creates new role' do
+          expect(UserRole.find_by(name: 'Bar')).to_not be_nil
+        end
+      end
+
+      context 'when new role\'s position is higher than user\'s role' do
+        let(:selected_position) { 100 }
+        let(:selected_permissions_as_keys) { %w(manage_roles) }
+
+        it 'renders new template' do
+          expect(response).to render_template(:new)
+        end
+
+        it 'does not create new role' do
+          expect(UserRole.find_by(name: 'Bar')).to be_nil
+        end
+      end
+
+      context 'when new role has permissions the user does not have' do
+        let(:selected_position) { 1 }
+        let(:selected_permissions_as_keys) { %w(manage_roles manage_users manage_reports) }
+
+        it 'renders new template' do
+          expect(response).to render_template(:new)
+        end
+
+        it 'does not create new role' do
+          expect(UserRole.find_by(name: 'Bar')).to be_nil
+        end
+      end
+
+      context 'when user has administrator permission' do
+        let(:permissions) { UserRole::FLAGS[:administrator] }
+
+        let(:selected_position) { 1 }
+        let(:selected_permissions_as_keys) { %w(manage_roles manage_users manage_reports) }
+
+        it 'redirects to roles page' do
+          expect(response).to redirect_to(admin_roles_path)
+        end
+
+        it 'creates new role' do
+          expect(UserRole.find_by(name: 'Bar')).to_not be_nil
+        end
+      end
+    end
+  end
+
+  describe 'GET #edit' do
+    let(:role_position) { 8 }
+    let(:role) { UserRole.create(name: 'Bar', permissions: UserRole::FLAGS[:manage_users], position: role_position) }
+
+    before do
+      get :edit, params: { id: role.id }
+    end
+
+    context 'when user does not have permission to manage roles' do
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(403)
+      end
+    end
+
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+      context 'when user outranks the role' do
+        it 'returns http success' do
+          expect(response).to have_http_status(:success)
+        end
+      end
+
+      context 'when role outranks user' do
+        let(:role_position) { current_role.position + 1 }
+
+        it 'returns http forbidden' do
+          expect(response).to have_http_status(403)
+        end
+      end
+    end
+  end
+
+  describe 'PUT #update' do
+    let(:role_position) { 8 }
+    let(:role_permissions) { UserRole::FLAGS[:manage_users] }
+    let(:role) { UserRole.create(name: 'Bar', permissions: role_permissions, position: role_position) }
+
+    let(:selected_position) { 8 }
+    let(:selected_permissions_as_keys) { %w(manage_users) }
+
+    before do
+      put :update, params: { id: role.id, user_role: { name: 'Baz', position: selected_position, permissions_as_keys: selected_permissions_as_keys } }
+    end
+
+    context 'when user does not have permission to manage roles' do
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(403)
+      end
+
+      it 'does not update the role' do
+        expect(role.reload.name).to eq 'Bar'
+      end
+    end
+
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+      context 'when role has permissions the user doesn\'t' do
+        it 'renders edit template' do
+          expect(response).to render_template(:edit)
+        end
+
+        it 'does not update the role' do
+          expect(role.reload.name).to eq 'Bar'
+        end
+      end
+
+      context 'when user has all permissions of the role' do
+        let(:permissions) { UserRole::FLAGS[:manage_roles] | UserRole::FLAGS[:manage_users] }
+
+        context 'when user outranks the role' do
+          it 'redirects to roles page' do
+            expect(response).to redirect_to(admin_roles_path)
+          end
+
+          it 'updates the role' do
+            expect(role.reload.name).to eq 'Baz'
+          end
+        end
+
+        context 'when role outranks user' do
+          let(:role_position) { current_role.position + 1 }
+
+          it 'returns http forbidden' do
+            expect(response).to have_http_status(403)
+          end
+
+          it 'does not update the role' do
+            expect(role.reload.name).to eq 'Bar'
+          end
+        end
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    let(:role_position) { 8 }
+    let(:role) { UserRole.create(name: 'Bar', permissions: UserRole::FLAGS[:manage_users], position: role_position) }
+
+    before do
+      delete :destroy, params: { id: role.id }
+    end
+
+    context 'when user does not have permission to manage roles' do
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(403)
+      end
+    end
+
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+      context 'when user outranks the role' do
+        it 'redirects to roles page' do
+          expect(response).to redirect_to(admin_roles_path)
+        end
+      end
+
+      context 'when role outranks user' do
+        let(:role_position) { current_role.position + 1 }
+
+        it 'returns http forbidden' do
+          expect(response).to have_http_status(403)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/controllers/admin/rules_controller_spec.rb b/spec/controllers/admin/rules_controller_spec.rb
new file mode 100644
index 0000000000..1b2a2010d0
--- /dev/null
+++ b/spec/controllers/admin/rules_controller_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::RulesController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    it 'returns http success' do
+      get :index
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'GET #edit' do
+    let(:rule) { Fabricate(:rule) }
+
+    it 'returns http success and renders edit' do
+      get :edit, params: { id: rule.id }
+
+      expect(response).to have_http_status(:success)
+      expect(response).to render_template(:edit)
+    end
+  end
+
+  describe 'POST #create' do
+    context 'with valid data' do
+      it 'creates a new rule and redirects' do
+        expect do
+          post :create, params: { rule: { text: 'The rule text.' } }
+        end.to change(Rule, :count).by(1)
+
+        expect(response).to redirect_to(admin_rules_path)
+      end
+    end
+
+    context 'with invalid data' do
+      it 'does creates a new rule and renders index' do
+        expect do
+          post :create, params: { rule: { text: '' } }
+        end.to_not change(Rule, :count)
+
+        expect(response).to render_template(:index)
+      end
+    end
+  end
+
+  describe 'PUT #update' do
+    let(:rule) { Fabricate(:rule, text: 'Original text') }
+
+    context 'with valid data' do
+      it 'updates the rule and redirects' do
+        put :update, params: { id: rule.id, rule: { text: 'Updated text.' } }
+
+        expect(response).to redirect_to(admin_rules_path)
+      end
+    end
+
+    context 'with invalid data' do
+      it 'does not update the rule and renders index' do
+        put :update, params: { id: rule.id, rule: { text: '' } }
+
+        expect(response).to render_template(:edit)
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    let!(:rule) { Fabricate(:rule) }
+
+    it 'destroys the rule and redirects' do
+      delete :destroy, params: { id: rule.id }
+
+      expect(rule.reload).to be_discarded
+      expect(response).to redirect_to(admin_rules_path)
+    end
+  end
+end
diff --git a/spec/controllers/admin/settings/branding_controller_spec.rb b/spec/controllers/admin/settings/branding_controller_spec.rb
new file mode 100644
index 0000000000..5e46910cc6
--- /dev/null
+++ b/spec/controllers/admin/settings/branding_controller_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::Settings::BrandingController do
+  render_views
+
+  describe 'When signed in as an admin' do
+    before do
+      sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
+    end
+
+    describe 'PUT #update' do
+      it 'cannot create a setting value for a non-admin key' do
+        expect(Setting.new_setting_key).to be_blank
+
+        patch :update, params: { form_admin_settings: { new_setting_key: 'New key value' } }
+
+        expect(response).to redirect_to(admin_settings_branding_path)
+        expect(Setting.new_setting_key).to be_nil
+      end
+    end
+  end
+end
diff --git a/spec/controllers/admin/site_uploads_controller_spec.rb b/spec/controllers/admin/site_uploads_controller_spec.rb
new file mode 100644
index 0000000000..9c65c63b78
--- /dev/null
+++ b/spec/controllers/admin/site_uploads_controller_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::SiteUploadsController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'DELETE #destroy' do
+    let(:site_upload) { Fabricate(:site_upload, var: 'thumbnail') }
+
+    it 'returns http success' do
+      delete :destroy, params: { id: site_upload.id }
+
+      expect(response).to redirect_to(admin_settings_path)
+    end
+  end
+end
diff --git a/spec/controllers/admin/statuses_controller_spec.rb b/spec/controllers/admin/statuses_controller_spec.rb
index 2809c8ec3f..e6053a6e8a 100644
--- a/spec/controllers/admin/statuses_controller_spec.rb
+++ b/spec/controllers/admin/statuses_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe Admin::StatusesController do
   render_views
 
-  let(:user) { Fabricate(:admin_user) }
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
   let(:account) { Fabricate(:account) }
   let!(:status) { Fabricate(:status, account: account) }
   let(:media_attached_status) { Fabricate(:status, account: account, sensitive: !sensitive) }
diff --git a/spec/controllers/admin/trends/links/preview_card_providers_controller_spec.rb b/spec/controllers/admin/trends/links/preview_card_providers_controller_spec.rb
new file mode 100644
index 0000000000..ce62a13db6
--- /dev/null
+++ b/spec/controllers/admin/trends/links/preview_card_providers_controller_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::Trends::Links::PreviewCardProvidersController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    it 'returns http success' do
+      get :index
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+end
diff --git a/spec/controllers/admin/trends/links_controller_spec.rb b/spec/controllers/admin/trends/links_controller_spec.rb
new file mode 100644
index 0000000000..984f3007c2
--- /dev/null
+++ b/spec/controllers/admin/trends/links_controller_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::Trends::LinksController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    it 'returns http success' do
+      get :index
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+end
diff --git a/spec/controllers/admin/trends/statuses_controller_spec.rb b/spec/controllers/admin/trends/statuses_controller_spec.rb
new file mode 100644
index 0000000000..eecf4ab4f2
--- /dev/null
+++ b/spec/controllers/admin/trends/statuses_controller_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::Trends::StatusesController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    it 'returns http success' do
+      get :index
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+end
diff --git a/spec/controllers/admin/trends/tags_controller_spec.rb b/spec/controllers/admin/trends/tags_controller_spec.rb
new file mode 100644
index 0000000000..51ad1860c8
--- /dev/null
+++ b/spec/controllers/admin/trends/tags_controller_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::Trends::TagsController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    it 'returns http success' do
+      get :index
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+end
diff --git a/spec/controllers/admin/users/roles_controller_spec.rb b/spec/controllers/admin/users/roles_controller_spec.rb
new file mode 100644
index 0000000000..bfc2bb151f
--- /dev/null
+++ b/spec/controllers/admin/users/roles_controller_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::Users::RolesController do
+  render_views
+
+  let(:current_role) { UserRole.create(name: 'Foo', permissions: UserRole::FLAGS[:manage_roles], position: 10) }
+  let(:current_user) { Fabricate(:user, role: current_role) }
+
+  let(:previous_role) { nil }
+  let(:user) { Fabricate(:user, role: previous_role) }
+
+  before do
+    sign_in current_user, scope: :user
+  end
+
+  describe 'GET #show' do
+    before do
+      get :show, params: { user_id: user.id }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+
+    context 'when target user is higher ranked than current user' do
+      let(:previous_role) { UserRole.create(name: 'Baz', permissions: UserRole::FLAGS[:administrator], position: 100) }
+
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(403)
+      end
+    end
+  end
+
+  describe 'PUT #update' do
+    let(:selected_role) { UserRole.create(name: 'Bar', permissions: permissions, position: position) }
+
+    before do
+      put :update, params: { user_id: user.id, user: { role_id: selected_role.id } }
+    end
+
+    context 'with manage roles permissions' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+      let(:position) { 1 }
+
+      it 'updates user role' do
+        expect(user.reload.role_id).to eq selected_role&.id
+      end
+
+      it 'redirects back to account page' do
+        expect(response).to redirect_to(admin_account_path(user.account_id))
+      end
+    end
+
+    context 'when selected role has higher position than current user\'s role' do
+      let(:permissions) { UserRole::FLAGS[:administrator] }
+      let(:position) { 100 }
+
+      it 'does not update user role' do
+        expect(user.reload.role_id).to eq previous_role&.id
+      end
+
+      it 'renders edit form' do
+        expect(response).to render_template(:show)
+      end
+    end
+
+    context 'when target user is higher ranked than current user' do
+      let(:previous_role) { UserRole.create(name: 'Baz', permissions: UserRole::FLAGS[:administrator], position: 100) }
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+      let(:position) { 1 }
+
+      it 'does not update user role' do
+        expect(user.reload.role_id).to eq previous_role&.id
+      end
+
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(403)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/admin/users/two_factor_authentications_controller_spec.rb b/spec/controllers/admin/users/two_factor_authentications_controller_spec.rb
new file mode 100644
index 0000000000..1f0a6ac34d
--- /dev/null
+++ b/spec/controllers/admin/users/two_factor_authentications_controller_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require 'webauthn/fake_client'
+
+RSpec.describe Admin::Users::TwoFactorAuthenticationsController do
+  render_views
+
+  let(:user) { Fabricate(:user) }
+
+  before do
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
+  end
+
+  describe 'DELETE #destroy' do
+    context 'when user has OTP enabled' do
+      before do
+        user.update(otp_required_for_login: true)
+      end
+
+      it 'redirects to admin account page' do
+        delete :destroy, params: { user_id: user.id }
+
+        user.reload
+        expect(user.otp_enabled?).to be false
+        expect(response).to redirect_to(admin_account_path(user.account_id))
+      end
+    end
+
+    context 'when user has OTP and WebAuthn enabled' do
+      let(:fake_client) { WebAuthn::FakeClient.new('http://test.host') }
+
+      before do
+        user.update(otp_required_for_login: true, webauthn_id: WebAuthn.generate_user_id)
+
+        public_key_credential = WebAuthn::Credential.from_create(fake_client.create)
+        Fabricate(:webauthn_credential,
+                  user_id: user.id,
+                  external_id: public_key_credential.id,
+                  public_key: public_key_credential.public_key,
+                  nickname: 'Security Key')
+      end
+
+      it 'redirects to admin account page' do
+        delete :destroy, params: { user_id: user.id }
+
+        user.reload
+        expect(user.otp_enabled?).to be false
+        expect(user.webauthn_enabled?).to be false
+        expect(response).to redirect_to(admin_account_path(user.account_id))
+      end
+    end
+  end
+end
diff --git a/spec/controllers/admin/warning_presets_controller_spec.rb b/spec/controllers/admin/warning_presets_controller_spec.rb
new file mode 100644
index 0000000000..4171bbad82
--- /dev/null
+++ b/spec/controllers/admin/warning_presets_controller_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::WarningPresetsController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    it 'returns http success' do
+      get :index
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'GET #edit' do
+    let(:account_warning_preset) { Fabricate(:account_warning_preset) }
+
+    it 'returns http success and renders edit' do
+      get :edit, params: { id: account_warning_preset.id }
+
+      expect(response).to have_http_status(:success)
+      expect(response).to render_template(:edit)
+    end
+  end
+
+  describe 'POST #create' do
+    context 'with valid data' do
+      it 'creates a new account_warning_preset and redirects' do
+        expect do
+          post :create, params: { account_warning_preset: { text: 'The account_warning_preset text.' } }
+        end.to change(AccountWarningPreset, :count).by(1)
+
+        expect(response).to redirect_to(admin_warning_presets_path)
+      end
+    end
+
+    context 'with invalid data' do
+      it 'does creates a new account_warning_preset and renders index' do
+        expect do
+          post :create, params: { account_warning_preset: { text: '' } }
+        end.to_not change(AccountWarningPreset, :count)
+
+        expect(response).to render_template(:index)
+      end
+    end
+  end
+
+  describe 'PUT #update' do
+    let(:account_warning_preset) { Fabricate(:account_warning_preset, text: 'Original text') }
+
+    context 'with valid data' do
+      it 'updates the account_warning_preset and redirects' do
+        put :update, params: { id: account_warning_preset.id, account_warning_preset: { text: 'Updated text.' } }
+
+        expect(response).to redirect_to(admin_warning_presets_path)
+      end
+    end
+
+    context 'with invalid data' do
+      it 'does not update the account_warning_preset and renders index' do
+        put :update, params: { id: account_warning_preset.id, account_warning_preset: { text: '' } }
+
+        expect(response).to render_template(:edit)
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    let!(:account_warning_preset) { Fabricate(:account_warning_preset) }
+
+    it 'destroys the account_warning_preset and redirects' do
+      delete :destroy, params: { id: account_warning_preset.id }
+
+      expect { account_warning_preset.reload }.to raise_error(ActiveRecord::RecordNotFound)
+      expect(response).to redirect_to(admin_warning_presets_path)
+    end
+  end
+end
diff --git a/spec/controllers/admin/webhooks/secrets_controller_spec.rb b/spec/controllers/admin/webhooks/secrets_controller_spec.rb
new file mode 100644
index 0000000000..61ae8cdaa5
--- /dev/null
+++ b/spec/controllers/admin/webhooks/secrets_controller_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::Webhooks::SecretsController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'POST #rotate' do
+    let(:webhook) { Fabricate(:webhook) }
+
+    it 'returns http success' do
+      post :rotate, params: { webhook_id: webhook.id }
+
+      expect(response).to redirect_to(admin_webhook_path(webhook))
+    end
+  end
+end
diff --git a/spec/controllers/admin/webhooks_controller_spec.rb b/spec/controllers/admin/webhooks_controller_spec.rb
new file mode 100644
index 0000000000..4fe787c26c
--- /dev/null
+++ b/spec/controllers/admin/webhooks_controller_spec.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::WebhooksController do
+  render_views
+
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    it 'returns http success' do
+      get :index
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'GET #new' do
+    it 'returns http success and renders view' do
+      get :new
+
+      expect(response).to have_http_status(:success)
+      expect(response).to render_template(:new)
+    end
+  end
+
+  describe 'POST #create' do
+    it 'creates a new webhook record with valid data' do
+      expect do
+        post :create, params: { webhook: { url: 'https://example.com/hook', events: ['account.approved'] } }
+      end.to change(Webhook, :count).by(1)
+
+      expect(response).to be_redirect
+    end
+
+    it 'does not create a new webhook record with invalid data' do
+      expect do
+        post :create, params: { webhook: { url: 'https://example.com/hook', events: [] } }
+      end.to_not change(Webhook, :count)
+
+      expect(response).to have_http_status(:success)
+      expect(response).to render_template(:new)
+    end
+  end
+
+  context 'with an existing record' do
+    let!(:webhook) { Fabricate(:webhook, events: ['account.created', 'report.created']) }
+
+    describe 'GET #show' do
+      it 'returns http success and renders view' do
+        get :show, params: { id: webhook.id }
+
+        expect(response).to have_http_status(:success)
+        expect(response).to render_template(:show)
+      end
+    end
+
+    describe 'GET #edit' do
+      it 'returns http success and renders view' do
+        get :edit, params: { id: webhook.id }
+
+        expect(response).to have_http_status(:success)
+        expect(response).to render_template(:edit)
+      end
+    end
+
+    describe 'PUT #update' do
+      it 'updates the record with valid data' do
+        put :update, params: { id: webhook.id, webhook: { url: 'https://example.com/new/location' } }
+
+        expect(webhook.reload.url).to match(%r{new/location})
+        expect(response).to redirect_to(admin_webhook_path(webhook))
+      end
+
+      it 'does not update the record with invalid data' do
+        expect do
+          put :update, params: { id: webhook.id, webhook: { url: '' } }
+        end.to_not change(webhook, :url)
+
+        expect(response).to have_http_status(:success)
+        expect(response).to render_template(:edit)
+      end
+    end
+
+    describe 'POST #enable' do
+      it 'enables the webhook' do
+        post :enable, params: { id: webhook.id }
+
+        expect(webhook.reload).to be_enabled
+        expect(response).to redirect_to(admin_webhook_path(webhook))
+      end
+    end
+
+    describe 'POST #disable' do
+      it 'disables the webhook' do
+        post :disable, params: { id: webhook.id }
+
+        expect(webhook.reload).to_not be_enabled
+        expect(response).to redirect_to(admin_webhook_path(webhook))
+      end
+    end
+
+    describe 'DELETE #destroy' do
+      it 'destroys the record' do
+        expect do
+          delete :destroy, params: { id: webhook.id }
+        end.to change(Webhook, :count).by(-1)
+
+        expect(response).to redirect_to(admin_webhooks_path)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb
index bd4073dfaf..1e0e7c8f4d 100644
--- a/spec/controllers/api/base_controller_spec.rb
+++ b/spec/controllers/api/base_controller_spec.rb
@@ -7,6 +7,10 @@ RSpec.describe Api::BaseController do
     def success
       head 200
     end
+
+    def failure
+      FakeService.new
+    end
   end
 
   it 'returns private cache control headers by default' do
diff --git a/spec/controllers/api/web/push_subscriptions_controller_spec.rb b/spec/controllers/api/web/push_subscriptions_controller_spec.rb
index 1e01709262..acc0312113 100644
--- a/spec/controllers/api/web/push_subscriptions_controller_spec.rb
+++ b/spec/controllers/api/web/push_subscriptions_controller_spec.rb
@@ -15,7 +15,6 @@ RSpec.describe Api::Web::PushSubscriptionsController do
           p256dh: 'BEm_a0bdPDhf0SOsrnB2-ategf1hHoCnpXgQsFj5JCkcoMrMt2WHoPfEYOYPzOIs9mZE8ZUaD7VA5vouy0kEkr8=',
           auth: 'eH_C8rq2raXqlcBVDa1gLg==',
         },
-        standard: standard,
       },
     }
   end
@@ -37,7 +36,6 @@ RSpec.describe Api::Web::PushSubscriptionsController do
       },
     }
   end
-  let(:standard) { '1' }
 
   before do
     sign_in(user)
@@ -53,27 +51,14 @@ RSpec.describe Api::Web::PushSubscriptionsController do
 
       user.reload
 
-      expect(created_push_subscription)
-        .to have_attributes(
-          endpoint: eq(create_payload[:subscription][:endpoint]),
-          key_p256dh: eq(create_payload[:subscription][:keys][:p256dh]),
-          key_auth: eq(create_payload[:subscription][:keys][:auth])
-        )
-        .and be_standard
+      expect(created_push_subscription).to have_attributes(
+        endpoint: eq(create_payload[:subscription][:endpoint]),
+        key_p256dh: eq(create_payload[:subscription][:keys][:p256dh]),
+        key_auth: eq(create_payload[:subscription][:keys][:auth])
+      )
       expect(user.session_activations.first.web_push_subscription).to eq(created_push_subscription)
     end
 
-    context 'when standard is provided as false value' do
-      let(:standard) { '0' }
-
-      it 'saves push subscription with standard as false' do
-        post :create, format: :json, params: create_payload
-
-        expect(created_push_subscription)
-          .to_not be_standard
-      end
-    end
-
     context 'with a user who has a session with a prior subscription' do
       let!(:prior_subscription) { Fabricate(:web_push_subscription, session_activation: user.session_activations.last) }
 
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 2e7a59db05..4ee951628e 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -3,8 +3,6 @@
 require 'rails_helper'
 
 RSpec.describe ApplicationController do
-  render_views
-
   controller do
     def success
       head 200
@@ -25,22 +23,9 @@ RSpec.describe ApplicationController do
 
   shared_examples 'respond_with_error' do |code|
     it "returns http #{code} for http and renders template" do
-      subject
+      expect(subject).to render_template("errors/#{code}", layout: 'error')
 
-      expect(response)
-        .to have_http_status(code)
-      expect(response.parsed_body)
-        .to have_css('body[class=error]')
-      expect(response.parsed_body.css('h1').to_s)
-        .to include(error_content(code))
-    end
-
-    def error_content(code)
-      if code == 422
-        I18n.t('errors.422.content')
-      else
-        I18n.t("errors.#{code}")
-      end
+      expect(response).to have_http_status(code)
     end
   end
 
diff --git a/spec/controllers/auth/challenges_controller_spec.rb b/spec/controllers/auth/challenges_controller_spec.rb
new file mode 100644
index 0000000000..56fdfa61b5
--- /dev/null
+++ b/spec/controllers/auth/challenges_controller_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Auth::ChallengesController do
+  render_views
+
+  let(:password) { 'foobar12345' }
+  let(:user) { Fabricate(:user, password: password) }
+
+  before do
+    sign_in user
+  end
+
+  describe 'POST #create' do
+    let(:return_to) { edit_user_registration_path }
+
+    context 'with correct password' do
+      before { post :create, params: { form_challenge: { return_to: return_to, current_password: password } } }
+
+      it 'redirects back' do
+        expect(response).to redirect_to(return_to)
+      end
+
+      it 'sets session' do
+        expect(session[:challenge_passed_at]).to_not be_nil
+      end
+    end
+
+    context 'with incorrect password' do
+      before { post :create, params: { form_challenge: { return_to: return_to, current_password: 'hhfggjjd562' } } }
+
+      it 'renders challenge' do
+        expect(response).to render_template('auth/challenges/new')
+      end
+
+      it 'displays error' do
+        expect(response.body).to include 'Invalid password'
+      end
+
+      it 'does not set session' do
+        expect(session[:challenge_passed_at]).to be_nil
+      end
+    end
+  end
+end
diff --git a/spec/controllers/auth/confirmations_controller_spec.rb b/spec/controllers/auth/confirmations_controller_spec.rb
index 09a178f0e8..a5b212e660 100644
--- a/spec/controllers/auth/confirmations_controller_spec.rb
+++ b/spec/controllers/auth/confirmations_controller_spec.rb
@@ -23,11 +23,12 @@ RSpec.describe Auth::ConfirmationsController do
         get :show, params: { confirmation_token: 'foobar' }
       end
 
-      it 'redirects to login and queues worker' do
-        expect(response)
-          .to redirect_to(new_user_session_path)
-        expect(BootstrapTimelineWorker)
-          .to have_received(:perform_async).with(user.account_id)
+      it 'redirects to login' do
+        expect(response).to redirect_to(new_user_session_path)
+      end
+
+      it 'queues up bootstrapping of home timeline' do
+        expect(BootstrapTimelineWorker).to have_received(:perform_async).with(user.account_id)
       end
     end
 
@@ -87,13 +88,13 @@ RSpec.describe Auth::ConfirmationsController do
         get :show, params: { confirmation_token: 'foobar' }
       end
 
-      it 'redirects to login, confirms email, does not queue worker' do
-        expect(response)
-          .to redirect_to(new_user_session_path)
-        expect(user.reload.unconfirmed_email)
-          .to be_nil
-        expect(BootstrapTimelineWorker)
-          .to_not have_received(:perform_async)
+      it 'redirects to login and confirms email' do
+        expect(response).to redirect_to(new_user_session_path)
+        expect(user.reload.unconfirmed_email).to be_nil
+      end
+
+      it 'does not queue up bootstrapping of home timeline' do
+        expect(BootstrapTimelineWorker).to_not have_received(:perform_async)
       end
     end
   end
diff --git a/spec/controllers/auth/passwords_controller_spec.rb b/spec/controllers/auth/passwords_controller_spec.rb
new file mode 100644
index 0000000000..9ccbb9e494
--- /dev/null
+++ b/spec/controllers/auth/passwords_controller_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Auth::PasswordsController do
+  include Devise::Test::ControllerHelpers
+
+  describe 'GET #new' do
+    it 'returns http success' do
+      request.env['devise.mapping'] = Devise.mappings[:user]
+      get :new
+      expect(response).to have_http_status(200)
+    end
+  end
+
+  describe 'GET #edit' do
+    let(:user) { Fabricate(:user) }
+
+    before do
+      request.env['devise.mapping'] = Devise.mappings[:user]
+    end
+
+    context 'with valid reset_password_token' do
+      it 'returns http success' do
+        token = user.send_reset_password_instructions
+
+        get :edit, params: { reset_password_token: token }
+
+        expect(response).to have_http_status(200)
+      end
+    end
+
+    context 'with invalid reset_password_token' do
+      it 'redirects to #new' do
+        get :edit, params: { reset_password_token: 'some_invalid_value' }
+        expect(response).to redirect_to subject.new_password_path(subject.send(:resource_name))
+      end
+    end
+  end
+
+  describe 'POST #update' do
+    let(:user) { Fabricate(:user) }
+    let(:password) { 'reset0password' }
+
+    before do
+      request.env['devise.mapping'] = Devise.mappings[:user]
+    end
+
+    context 'with valid reset_password_token' do
+      let!(:session_activation) { Fabricate(:session_activation, user: user) }
+      let!(:access_token) { Fabricate(:access_token, resource_owner_id: user.id) }
+      let!(:web_push_subscription) { Fabricate(:web_push_subscription, access_token: access_token) }
+
+      before do
+        token = user.send_reset_password_instructions
+
+        post :update, params: { user: { password: password, password_confirmation: password, reset_password_token: token } }
+      end
+
+      it 'redirect to sign in' do
+        expect(response).to redirect_to '/auth/sign_in'
+      end
+
+      it 'changes password' do
+        this_user = User.find(user.id)
+
+        expect(this_user).to_not be_nil
+        expect(this_user.valid_password?(password)).to be true
+      end
+
+      it 'deactivates all sessions' do
+        expect(user.session_activations.count).to eq 0
+        expect { session_activation.reload }.to raise_error(ActiveRecord::RecordNotFound)
+      end
+
+      it 'revokes all access tokens' do
+        expect(Doorkeeper::AccessToken.active_for(user).count).to eq 0
+      end
+
+      it 'removes push subscriptions' do
+        expect(Web::PushSubscription.where(user: user).or(Web::PushSubscription.where(access_token: access_token)).count).to eq 0
+        expect { web_push_subscription.reload }.to raise_error(ActiveRecord::RecordNotFound)
+      end
+    end
+
+    context 'with invalid reset_password_token' do
+      before do
+        post :update, params: { user: { password: password, password_confirmation: password, reset_password_token: 'some_invalid_value' } }
+      end
+
+      it 'renders reset password' do
+        expect(response).to render_template(:new)
+      end
+
+      it 'retains password' do
+        this_user = User.find(user.id)
+
+        expect(this_user).to_not be_nil
+        expect(this_user.external_or_valid_password?(user.password)).to be true
+      end
+    end
+  end
+end
diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb
index e7a8dd6d7f..cecfa7b5be 100644
--- a/spec/controllers/auth/registrations_controller_spec.rb
+++ b/spec/controllers/auth/registrations_controller_spec.rb
@@ -6,33 +6,25 @@ RSpec.describe Auth::RegistrationsController do
   render_views
 
   shared_examples 'checks for enabled registrations' do |path|
-    context 'when in single user mode and open for registration' do
-      before do
-        Setting.registrations_mode = 'open'
-        allow(Rails.configuration.x).to receive(:single_user_mode).and_return(true)
-      end
+    it 'redirects if it is in single user mode while it is open for registration' do
+      Fabricate(:account)
+      Setting.registrations_mode = 'open'
+      allow(Rails.configuration.x).to receive(:single_user_mode).and_return(true)
 
-      it 'redirects to root' do
-        Fabricate(:account)
-        get path
+      get path
 
-        expect(response).to redirect_to '/'
-        expect(Rails.configuration.x).to have_received(:single_user_mode)
-      end
+      expect(response).to redirect_to '/'
+      expect(Rails.configuration.x).to have_received(:single_user_mode)
     end
 
-    context 'when registrations closed and not in single user mode' do
-      before do
-        Setting.registrations_mode = 'none'
-        allow(Rails.configuration.x).to receive(:single_user_mode).and_return(false)
-      end
+    it 'redirects if it is not open for registration while it is not in single user mode' do
+      Setting.registrations_mode = 'none'
+      allow(Rails.configuration.x).to receive(:single_user_mode).and_return(false)
 
-      it 'redirects to root' do
-        get path
+      get path
 
-        expect(response).to redirect_to '/'
-        expect(Rails.configuration.x).to have_received(:single_user_mode)
-      end
+      expect(response).to redirect_to '/'
+      expect(Rails.configuration.x).to have_received(:single_user_mode)
     end
   end
 
@@ -43,12 +35,12 @@ RSpec.describe Auth::RegistrationsController do
       get :edit
     end
 
-    it 'returns http success and cache headers' do
-      expect(response)
-        .to have_http_status(200)
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
 
-      expect(response.headers['Cache-Control'])
-        .to include('private, no-store')
+    it 'returns private cache control header' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
     end
   end
 
@@ -61,13 +53,14 @@ RSpec.describe Auth::RegistrationsController do
       sign_in(user, scope: :user)
     end
 
-    it 'returns http success and cache headers' do
+    it 'returns http success' do
       put :update
+      expect(response).to have_http_status(200)
+    end
 
-      expect(response)
-        .to have_http_status(200)
-      expect(response.headers['Cache-Control'])
-        .to include('private, no-store')
+    it 'returns private cache control headers' do
+      put :update
+      expect(response.headers['Cache-Control']).to include('private, no-store')
     end
 
     it 'can update the user email' do
@@ -181,14 +174,16 @@ RSpec.describe Auth::RegistrationsController do
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', agreement: 'true' } }
       end
 
-      it 'redirects to setup and creates user' do
+      it 'redirects to setup' do
         subject
+        expect(response).to redirect_to auth_setup_path
+      end
 
-        expect(response)
-          .to redirect_to auth_setup_path
-        expect(User.find_by(email: 'test@example.com'))
-          .to be_present
-          .and have_attributes(locale: eq(accept_language))
+      it 'creates user' do
+        subject
+        user = User.find_by(email: 'test@example.com')
+        expect(user).to_not be_nil
+        expect(user.locale).to eq(accept_language)
       end
     end
 
@@ -259,18 +254,17 @@ RSpec.describe Auth::RegistrationsController do
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', agreement: 'true' } }
       end
 
-      it 'redirects to setup and creates user' do
+      it 'redirects to setup' do
         subject
+        expect(response).to redirect_to auth_setup_path
+      end
 
-        expect(response)
-          .to redirect_to auth_setup_path
-
-        expect(User.find_by(email: 'test@example.com'))
-          .to be_present
-          .and have_attributes(
-            locale: eq(accept_language),
-            approved: be(false)
-          )
+      it 'creates user' do
+        subject
+        user = User.find_by(email: 'test@example.com')
+        expect(user).to_not be_nil
+        expect(user.locale).to eq(accept_language)
+        expect(user.approved).to be(false)
       end
     end
 
@@ -282,17 +276,17 @@ RSpec.describe Auth::RegistrationsController do
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', invite_code: invite.code, agreement: 'true' } }
       end
 
-      it 'redirects to setup and creates user' do
+      it 'redirects to setup' do
         subject
-
         expect(response).to redirect_to auth_setup_path
+      end
 
-        expect(User.find_by(email: 'test@example.com'))
-          .to be_present
-          .and have_attributes(
-            locale: eq(accept_language),
-            approved: be(false)
-          )
+      it 'creates user' do
+        subject
+        user = User.find_by(email: 'test@example.com')
+        expect(user).to_not be_nil
+        expect(user.locale).to eq(accept_language)
+        expect(user.approved).to be(false)
       end
     end
 
@@ -306,17 +300,17 @@ RSpec.describe Auth::RegistrationsController do
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', invite_code: invite.code, agreement: 'true' } }
       end
 
-      it 'redirects to setup and creates user' do
+      it 'redirects to setup' do
         subject
-
         expect(response).to redirect_to auth_setup_path
+      end
 
-        expect(User.find_by(email: 'test@example.com'))
-          .to be_present
-          .and have_attributes(
-            locale: eq(accept_language),
-            approved: be(true)
-          )
+      it 'creates user' do
+        subject
+        user = User.find_by(email: 'test@example.com')
+        expect(user).to_not be_nil
+        expect(user.locale).to eq(accept_language)
+        expect(user.approved).to be(true)
       end
     end
 
@@ -342,42 +336,6 @@ RSpec.describe Auth::RegistrationsController do
       end
     end
 
-    context 'when age verification is enabled' do
-      subject { post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', agreement: 'true' }.merge(date_of_birth) } }
-
-      before do
-        Setting.min_age = 16
-      end
-
-      let(:date_of_birth) { {} }
-
-      context 'when date of birth is below age limit' do
-        let(:date_of_birth) { 13.years.ago.then { |date| { 'date_of_birth(1i)': date.day.to_s, 'date_of_birth(2i)': date.month.to_s, 'date_of_birth(3i)': date.year.to_s } } }
-
-        it 'does not create user' do
-          subject
-          user = User.find_by(email: 'test@example.com')
-          expect(user).to be_nil
-        end
-      end
-
-      context 'when date of birth is above age limit' do
-        let(:date_of_birth) { 17.years.ago.then { |date| { 'date_of_birth(1i)': date.day.to_s, 'date_of_birth(2i)': date.month.to_s, 'date_of_birth(3i)': date.year.to_s } } }
-
-        it 'redirects to setup and creates user' do
-          subject
-
-          expect(response).to redirect_to auth_setup_path
-
-          expect(User.find_by(email: 'test@example.com'))
-            .to be_present
-            .and have_attributes(
-              age_verified_at: not_eq(nil)
-            )
-        end
-      end
-    end
-
     context 'when max user count is set' do
       subject do
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', agreement: 'true' } }
@@ -554,11 +512,12 @@ RSpec.describe Auth::RegistrationsController do
       delete :destroy
     end
 
-    it 'returns http not found and keeps user' do
-      expect(response)
-        .to have_http_status(404)
-      expect(User.find(user.id))
-        .to_not be_nil
+    it 'returns http not found' do
+      expect(response).to have_http_status(404)
+    end
+
+    it 'does not delete user' do
+      expect(User.find(user.id)).to_not be_nil
     end
   end
 end
diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb
index 9ecae92801..d4769a4722 100644
--- a/spec/controllers/auth/sessions_controller_spec.rb
+++ b/spec/controllers/auth/sessions_controller_spec.rb
@@ -262,8 +262,8 @@ RSpec.describe Auth::SessionsController do
           end
 
           it 'renders two factor authentication page' do
-            expect(response.body)
-              .to include(I18n.t('simple_form.hints.sessions.otp'))
+            expect(controller).to render_template('two_factor')
+            expect(controller).to render_template(partial: '_otp_authentication_form')
           end
         end
 
@@ -278,8 +278,8 @@ RSpec.describe Auth::SessionsController do
           end
 
           it 'renders two factor authentication page' do
-            expect(response.body)
-              .to include(I18n.t('simple_form.hints.sessions.otp'))
+            expect(controller).to render_template('two_factor')
+            expect(controller).to render_template(partial: '_otp_authentication_form')
           end
         end
 
@@ -289,8 +289,8 @@ RSpec.describe Auth::SessionsController do
           end
 
           it 'renders two factor authentication page' do
-            expect(response.body)
-              .to include(I18n.t('simple_form.hints.sessions.otp'))
+            expect(controller).to render_template('two_factor')
+            expect(controller).to render_template(partial: '_otp_authentication_form')
           end
         end
 
@@ -417,8 +417,8 @@ RSpec.describe Auth::SessionsController do
           end
 
           it 'renders webauthn authentication page' do
-            expect(response.body)
-              .to include(I18n.t('simple_form.title.sessions.webauthn'))
+            expect(controller).to render_template('two_factor')
+            expect(controller).to render_template(partial: '_webauthn_form')
           end
         end
 
@@ -428,8 +428,8 @@ RSpec.describe Auth::SessionsController do
           end
 
           it 'renders webauthn authentication page' do
-            expect(response.body)
-              .to include(I18n.t('simple_form.title.sessions.webauthn'))
+            expect(controller).to render_template('two_factor')
+            expect(controller).to render_template(partial: '_webauthn_form')
           end
         end
 
diff --git a/spec/controllers/auth/setup_controller_spec.rb b/spec/controllers/auth/setup_controller_spec.rb
new file mode 100644
index 0000000000..28b07cb4b2
--- /dev/null
+++ b/spec/controllers/auth/setup_controller_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Auth::SetupController do
+  render_views
+
+  describe 'GET #show' do
+    context 'with a signed out request' do
+      it 'returns http redirect' do
+        get :show
+        expect(response).to be_redirect
+      end
+    end
+
+    context 'with an unconfirmed signed in user' do
+      before { sign_in Fabricate(:user, confirmed_at: nil) }
+
+      it 'returns http success' do
+        get :show
+        expect(response).to have_http_status(200)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb
index 4a44fd3498..384406a0ea 100644
--- a/spec/controllers/concerns/account_controller_concern_spec.rb
+++ b/spec/controllers/concerns/account_controller_concern_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe AccountControllerConcern do
   controller(ApplicationController) do
-    include AccountControllerConcern # rubocop:disable RSpec/DescribedClass
+    include AccountControllerConcern
 
     def success
       render plain: @account.username # rubocop:disable RSpec/InstanceVariable
diff --git a/spec/controllers/concerns/api/rate_limit_headers_spec.rb b/spec/controllers/concerns/api/rate_limit_headers_spec.rb
index dd16531350..6372c94e6c 100644
--- a/spec/controllers/concerns/api/rate_limit_headers_spec.rb
+++ b/spec/controllers/concerns/api/rate_limit_headers_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe Api::RateLimitHeaders do
   controller(ApplicationController) do
-    include Api::RateLimitHeaders # rubocop:disable RSpec/DescribedClass
+    include Api::RateLimitHeaders
 
     def show
       head 200
@@ -40,11 +40,15 @@ RSpec.describe Api::RateLimitHeaders do
         end
       end
 
-      it 'provides rate limit information in headers' do
+      it 'applies rate limiting limit header' do
         expect(response.headers['X-RateLimit-Limit']).to eq '100'
+      end
 
+      it 'applies rate limiting remaining header' do
         expect(response.headers['X-RateLimit-Remaining']).to eq '80'
+      end
 
+      it 'applies rate limiting reset header' do
         expect(response.headers['X-RateLimit-Reset']).to eq (start_time + 10.seconds).iso8601(6)
       end
     end
diff --git a/spec/controllers/concerns/challengable_concern_spec.rb b/spec/controllers/concerns/challengable_concern_spec.rb
index 823003c8de..169e2122f8 100644
--- a/spec/controllers/concerns/challengable_concern_spec.rb
+++ b/spec/controllers/concerns/challengable_concern_spec.rb
@@ -3,10 +3,8 @@
 require 'rails_helper'
 
 RSpec.describe ChallengableConcern do
-  render_views
-
   controller(ApplicationController) do
-    include ChallengableConcern # rubocop:disable RSpec/DescribedClass
+    include ChallengableConcern
 
     before_action :require_challenge!
 
@@ -87,35 +85,29 @@ RSpec.describe ChallengableConcern do
       before { get :foo }
 
       it 'renders challenge' do
-        expect(response.parsed_body)
-          .to have_title(I18n.t('challenge.prompt'))
+        expect(response).to render_template('auth/challenges/new', layout: :auth)
       end
+
+      # See Auth::ChallengesControllerSpec
     end
 
     context 'with POST requests' do
       before { post :bar }
 
       it 'renders challenge' do
-        expect(response.parsed_body)
-          .to have_title(I18n.t('challenge.prompt'))
+        expect(response).to render_template('auth/challenges/new', layout: :auth)
       end
 
       it 'accepts correct password' do
         post :bar, params: { form_challenge: { current_password: password } }
-
-        expect(response.body)
-          .to eq 'bar'
-        expect(session[:challenge_passed_at])
-          .to_not be_nil
+        expect(response.body).to eq 'bar'
+        expect(session[:challenge_passed_at]).to_not be_nil
       end
 
       it 'rejects wrong password' do
         post :bar, params: { form_challenge: { current_password: 'dddfff888123' } }
-
-        expect(response.parsed_body)
-          .to have_title(I18n.t('challenge.prompt'))
-        expect(session[:challenge_passed_at])
-          .to be_nil
+        expect(response.body).to render_template('auth/challenges/new', layout: :auth)
+        expect(session[:challenge_passed_at]).to be_nil
       end
     end
   end
diff --git a/spec/controllers/concerns/localized_spec.rb b/spec/controllers/concerns/localized_spec.rb
index 4798e8270c..b1f805ae50 100644
--- a/spec/controllers/concerns/localized_spec.rb
+++ b/spec/controllers/concerns/localized_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe Localized do
   controller(ApplicationController) do
-    include Localized # rubocop:disable RSpec/DescribedClass
+    include Localized
 
     def success
       render plain: I18n.locale, status: 200
diff --git a/spec/controllers/concerns/preloading_concern_spec.rb b/spec/controllers/concerns/preloading_concern_spec.rb
index 2cbccb411e..795afbc45e 100644
--- a/spec/controllers/concerns/preloading_concern_spec.rb
+++ b/spec/controllers/concerns/preloading_concern_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe PreloadingConcern do
   controller(ApplicationController) do
-    include PreloadingConcern # rubocop:disable RSpec/DescribedClass
+    include PreloadingConcern
 
     def empty_array
       render plain: preload_collection([], Status).size
diff --git a/spec/controllers/concerns/settings/export_controller_concern_spec.rb b/spec/controllers/concerns/settings/export_controller_concern_spec.rb
index 6c1a06114c..2c67991e3a 100644
--- a/spec/controllers/concerns/settings/export_controller_concern_spec.rb
+++ b/spec/controllers/concerns/settings/export_controller_concern_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe Settings::ExportControllerConcern do
   controller(ApplicationController) do
-    include Settings::ExportControllerConcern # rubocop:disable RSpec/DescribedClass
+    include Settings::ExportControllerConcern
 
     def index
       send_export_file
diff --git a/spec/controllers/concerns/user_tracking_concern_spec.rb b/spec/controllers/concerns/user_tracking_concern_spec.rb
index d67b0ef5e7..cc61e285cc 100644
--- a/spec/controllers/concerns/user_tracking_concern_spec.rb
+++ b/spec/controllers/concerns/user_tracking_concern_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe UserTrackingConcern do
   controller(ApplicationController) do
-    include UserTrackingConcern # rubocop:disable RSpec/DescribedClass
+    include UserTrackingConcern
 
     def show
       render plain: 'show'
diff --git a/spec/controllers/disputes/appeals_controller_spec.rb b/spec/controllers/disputes/appeals_controller_spec.rb
new file mode 100644
index 0000000000..3e874bbdcc
--- /dev/null
+++ b/spec/controllers/disputes/appeals_controller_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Disputes::AppealsController do
+  render_views
+
+  before { sign_in current_user, scope: :user }
+
+  let!(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
+
+  describe '#create' do
+    subject { post :create, params: params }
+
+    context 'with valid params' do
+      let(:current_user) { Fabricate(:user) }
+      let(:strike) { Fabricate(:account_warning, target_account: current_user.account) }
+      let(:params) { { strike_id: strike.id, appeal: { text: 'Foo' } } }
+
+      it 'notifies staff about new appeal and redirects back to strike page', :inline_jobs do
+        emails = capture_emails { subject }
+
+        expect(emails.size)
+          .to eq(1)
+        expect(emails.first)
+          .to have_attributes(
+            to: contain_exactly(admin.email),
+            subject: eq(I18n.t('admin_mailer.new_appeal.subject', username: current_user.account.acct, instance: Rails.configuration.x.local_domain))
+          )
+        expect(response).to redirect_to(disputes_strike_path(strike.id))
+      end
+    end
+
+    context 'with invalid params' do
+      let(:current_user) { Fabricate(:user) }
+      let(:strike) { Fabricate(:account_warning, target_account: current_user.account) }
+      let(:params) { { strike_id: strike.id, appeal: { text: '' } } }
+
+      it 'does not send email and renders strike show page', :inline_jobs do
+        emails = capture_emails { subject }
+
+        expect(emails).to be_empty
+        expect(response).to render_template('disputes/strikes/show')
+      end
+    end
+  end
+end
diff --git a/spec/controllers/disputes/strikes_controller_spec.rb b/spec/controllers/disputes/strikes_controller_spec.rb
new file mode 100644
index 0000000000..f6d28fc09a
--- /dev/null
+++ b/spec/controllers/disputes/strikes_controller_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Disputes::StrikesController do
+  render_views
+
+  before { sign_in current_user, scope: :user }
+
+  describe '#show' do
+    let(:current_user) { Fabricate(:user) }
+    let(:strike) { Fabricate(:account_warning, target_account: current_user.account) }
+
+    before do
+      get :show, params: { id: strike.id }
+    end
+
+    context 'when meant for the user' do
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+    end
+
+    context 'when meant for a different user' do
+      let(:strike) { Fabricate(:account_warning) }
+
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(403)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/filters/statuses_controller_spec.rb b/spec/controllers/filters/statuses_controller_spec.rb
new file mode 100644
index 0000000000..f1fed76fca
--- /dev/null
+++ b/spec/controllers/filters/statuses_controller_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Filters::StatusesController do
+  render_views
+
+  describe 'GET #index' do
+    let(:filter) { Fabricate(:custom_filter) }
+
+    context 'with signed out user' do
+      it 'redirects' do
+        get :index, params: { filter_id: filter }
+
+        expect(response).to be_redirect
+      end
+    end
+
+    context 'with a signed in user' do
+      context 'with the filter user signed in' do
+        before do
+          sign_in(filter.account.user)
+          get :index, params: { filter_id: filter }
+        end
+
+        it 'returns http success' do
+          expect(response).to have_http_status(200)
+        end
+
+        it 'returns private cache control headers' do
+          expect(response.headers['Cache-Control']).to include('private, no-store')
+        end
+      end
+
+      context 'with another user signed in' do
+        before do
+          sign_in(Fabricate(:user))
+          get :index, params: { filter_id: filter }
+        end
+
+        it 'returns http not found' do
+          expect(response).to have_http_status(404)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/controllers/filters_controller_spec.rb b/spec/controllers/filters_controller_spec.rb
new file mode 100644
index 0000000000..de043e8ae3
--- /dev/null
+++ b/spec/controllers/filters_controller_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe FiltersController do
+  render_views
+
+  describe 'GET #index' do
+    context 'with signed out user' do
+      before do
+        get :index
+      end
+
+      it 'redirects' do
+        expect(response).to be_redirect
+      end
+    end
+
+    context 'with a signed in user' do
+      before do
+        sign_in(Fabricate(:user))
+        get :index
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
+
+      it 'returns private cache control headers' do
+        expect(response.headers['Cache-Control']).to include('private, no-store')
+      end
+    end
+  end
+end
diff --git a/spec/requests/intents_spec.rb b/spec/controllers/intents_controller_spec.rb
similarity index 90%
rename from spec/requests/intents_spec.rb
rename to spec/controllers/intents_controller_spec.rb
index b62f570d7a..668d833ea7 100644
--- a/spec/requests/intents_spec.rb
+++ b/spec/controllers/intents_controller_spec.rb
@@ -2,15 +2,15 @@
 
 require 'rails_helper'
 
-RSpec.describe 'Intents' do
+RSpec.describe IntentsController do
+  render_views
+
   let(:user) { Fabricate(:user) }
 
   before { sign_in user, scope: :user }
 
-  describe 'GET /intent' do
-    subject { response }
-
-    before { get intent_path(uri: uri) }
+  describe 'GET #show' do
+    subject { get :show, params: { uri: uri } }
 
     context 'when schema is web+mastodon' do
       context 'when host is follow' do
diff --git a/spec/controllers/oauth/tokens_controller_spec.rb b/spec/controllers/oauth/tokens_controller_spec.rb
new file mode 100644
index 0000000000..a2eed797e0
--- /dev/null
+++ b/spec/controllers/oauth/tokens_controller_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Oauth::TokensController do
+  describe 'POST #revoke' do
+    let!(:user) { Fabricate(:user) }
+    let!(:application) { Fabricate(:application, confidential: false) }
+    let!(:access_token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: application) }
+    let!(:web_push_subscription) { Fabricate(:web_push_subscription, user: user, access_token: access_token) }
+
+    it 'revokes the token and removes subscriptions' do
+      post :revoke, params: { client_id: application.uid, token: access_token.token }
+
+      expect(access_token.reload.revoked_at)
+        .to_not be_nil
+      expect(Web::PushSubscription.where(access_token: access_token).count)
+        .to eq(0)
+      expect { web_push_subscription.reload }
+        .to raise_error(ActiveRecord::RecordNotFound)
+    end
+  end
+end
diff --git a/spec/controllers/relationships_controller_spec.rb b/spec/controllers/relationships_controller_spec.rb
index 75b5e71f35..323fcc995d 100644
--- a/spec/controllers/relationships_controller_spec.rb
+++ b/spec/controllers/relationships_controller_spec.rb
@@ -14,9 +14,11 @@ RSpec.describe RelationshipsController do
         get :show, params: { page: 2, relationship: 'followed_by' }
       end
 
-      it 'returns http success and private cache control headers' do
+      it 'returns http success' do
         expect(response).to have_http_status(200)
+      end
 
+      it 'returns private cache control headers' do
         expect(response.headers['Cache-Control']).to include('private, no-store')
       end
     end
diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb
new file mode 100644
index 0000000000..98104b8454
--- /dev/null
+++ b/spec/controllers/settings/deletes_controller_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::DeletesController do
+  render_views
+
+  describe 'GET #show' do
+    context 'when signed in' do
+      let(:user) { Fabricate(:user) }
+
+      before do
+        sign_in user, scope: :user
+        get :show
+      end
+
+      it 'renders confirmation page with private cache control headers', :aggregate_failures do
+        expect(response).to have_http_status(200)
+        expect(response.headers['Cache-Control']).to include('private, no-store')
+      end
+
+      context 'when suspended' do
+        let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) }
+
+        it 'returns http forbidden with private cache control headers', :aggregate_failures do
+          expect(response).to have_http_status(403)
+          expect(response.headers['Cache-Control']).to include('private, no-store')
+        end
+      end
+    end
+
+    context 'when not signed in' do
+      it 'redirects' do
+        get :show
+        expect(response).to redirect_to '/auth/sign_in'
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    context 'when signed in' do
+      let(:user) { Fabricate(:user, password: 'petsmoldoggos') }
+
+      before do
+        sign_in user, scope: :user
+      end
+
+      context 'with correct password' do
+        before do
+          delete :destroy, params: { form_delete_confirmation: { password: 'petsmoldoggos' } }
+        end
+
+        it 'removes user record and redirects', :aggregate_failures, :inline_jobs do
+          expect(response).to redirect_to '/auth/sign_in'
+          expect(User.find_by(id: user.id)).to be_nil
+          expect(user.account.reload).to be_suspended
+          expect(CanonicalEmailBlock.block?(user.email)).to be false
+        end
+
+        context 'when suspended' do
+          let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) }
+
+          it 'returns http forbidden' do
+            expect(response).to have_http_status(403)
+          end
+        end
+      end
+
+      context 'with incorrect password' do
+        before do
+          delete :destroy, params: { form_delete_confirmation: { password: 'blaze420' } }
+        end
+
+        it 'redirects back to confirmation page' do
+          expect(response).to redirect_to settings_delete_path
+        end
+      end
+    end
+
+    context 'when not signed in' do
+      it 'redirects' do
+        delete :destroy
+        expect(response).to redirect_to '/auth/sign_in'
+      end
+    end
+  end
+end
diff --git a/spec/controllers/settings/featured_tags_controller_spec.rb b/spec/controllers/settings/featured_tags_controller_spec.rb
new file mode 100644
index 0000000000..f414e818f5
--- /dev/null
+++ b/spec/controllers/settings/featured_tags_controller_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::FeaturedTagsController do
+  render_views
+
+  context 'when user is not signed in' do
+    subject { post :create }
+
+    it { is_expected.to redirect_to new_user_session_path }
+  end
+
+  context 'when user is signed in' do
+    let(:user) { Fabricate(:user, password: '12345678') }
+
+    before { sign_in user, scope: :user }
+
+    describe 'POST #create' do
+      subject { post :create, params: { featured_tag: params } }
+
+      context 'when parameter is valid' do
+        let(:params) { { name: 'test' } }
+
+        it 'creates featured tag' do
+          expect { subject }.to change { user.account.featured_tags.count }.by(1)
+        end
+      end
+
+      context 'when parameter is invalid' do
+        let(:params) { { name: 'test, #foo !bleh' } }
+
+        it 'renders new' do
+          expect(subject).to render_template :index
+        end
+      end
+    end
+
+    describe 'GET to #index' do
+      let(:tag) { Fabricate(:tag) }
+
+      before do
+        status = Fabricate :status, account: user.account
+        status.tags << tag
+      end
+
+      it 'responds with success' do
+        get :index
+
+        expect(response).to have_http_status(200)
+        expect(response.body).to include(
+          settings_featured_tags_path(featured_tag: { name: tag.name })
+        )
+      end
+    end
+
+    describe 'DELETE to #destroy' do
+      let(:featured_tag) { Fabricate(:featured_tag, account: user.account) }
+
+      it 'removes the featured tag' do
+        delete :destroy, params: { id: featured_tag.id }
+
+        expect(response).to redirect_to(settings_featured_tags_path)
+        expect { featured_tag.reload }.to raise_error(ActiveRecord::RecordNotFound)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/settings/login_activities_controller_spec.rb b/spec/controllers/settings/login_activities_controller_spec.rb
new file mode 100644
index 0000000000..3447620abb
--- /dev/null
+++ b/spec/controllers/settings/login_activities_controller_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::LoginActivitiesController do
+  render_views
+
+  let!(:user) { Fabricate(:user) }
+  let!(:login_activity) { Fabricate :login_activity, user: user }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    before do
+      get :index
+    end
+
+    it 'returns http success with private cache control headers', :aggregate_failures do
+      expect(response).to have_http_status(200)
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+      expect(response.body)
+        .to include(login_activity.user_agent)
+        .and include(login_activity.authentication_method)
+        .and include(login_activity.ip.to_s)
+    end
+  end
+end
diff --git a/spec/controllers/settings/migration/redirects_controller_spec.rb b/spec/controllers/settings/migration/redirects_controller_spec.rb
new file mode 100644
index 0000000000..d853fe8ae6
--- /dev/null
+++ b/spec/controllers/settings/migration/redirects_controller_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::Migration::RedirectsController do
+  render_views
+
+  let!(:user) { Fabricate(:user, password: 'testtest') }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #new' do
+    before do
+      get :new
+    end
+
+    it 'returns http success with private cache control headers', :aggregate_failures do
+      expect(response).to have_http_status(200)
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
+  end
+
+  describe 'POST #create' do
+    context 'with valid params' do
+      before { stub_resolver }
+
+      it 'redirects to the settings migration path' do
+        post :create, params: { form_redirect: { acct: 'new@host.com', current_password: 'testtest' } }
+
+        expect(response).to redirect_to(settings_migration_path)
+      end
+    end
+
+    context 'with non valid params' do
+      it 'returns success and renders the new page' do
+        post :create, params: { form_redirect: { acct: '' } }
+
+        expect(response).to have_http_status(200)
+        expect(response).to render_template(:new)
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    let(:account) { Fabricate(:account) }
+
+    before do
+      user.account.update(moved_to_account_id: account.id)
+    end
+
+    it 'resets the account and sends an update' do
+      delete :destroy
+
+      expect(response).to redirect_to(settings_migration_path)
+      expect(user.account.reload.moved_to_account).to be_nil
+    end
+  end
+
+  private
+
+  def stub_resolver
+    resolver = instance_double(ResolveAccountService, call: Fabricate(:account))
+    allow(ResolveAccountService).to receive(:new).and_return(resolver)
+  end
+end
diff --git a/spec/controllers/settings/migrations_controller_spec.rb b/spec/controllers/settings/migrations_controller_spec.rb
new file mode 100644
index 0000000000..dca4c925fd
--- /dev/null
+++ b/spec/controllers/settings/migrations_controller_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::MigrationsController do
+  render_views
+
+  describe 'GET #show' do
+    context 'when user is not sign in' do
+      subject { get :show }
+
+      it { is_expected.to redirect_to new_user_session_path }
+    end
+
+    context 'when user is sign in' do
+      subject { get :show }
+
+      let(:user) { Fabricate(:account, moved_to_account: moved_to_account).user }
+
+      before { sign_in user, scope: :user }
+
+      context 'when user does not have moved to account' do
+        let(:moved_to_account) { nil }
+
+        it 'renders show page' do
+          expect(subject).to have_http_status 200
+          expect(subject).to render_template :show
+        end
+      end
+
+      context 'when user has a moved to account' do
+        let(:moved_to_account) { Fabricate(:account) }
+
+        it 'renders show page' do
+          expect(subject).to have_http_status 200
+          expect(subject).to render_template :show
+        end
+      end
+    end
+  end
+
+  describe 'POST #create' do
+    context 'when user is not sign in' do
+      subject { post :create }
+
+      it { is_expected.to redirect_to new_user_session_path }
+    end
+
+    context 'when user is signed in' do
+      subject { post :create, params: { account_migration: { acct: acct, current_password: '12345678' } } }
+
+      let(:user) { Fabricate(:user, password: '12345678') }
+
+      before { sign_in user, scope: :user }
+
+      context 'when migration account is changed' do
+        let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) }
+
+        it 'updates moved to account' do
+          expect(subject).to redirect_to settings_migration_path
+          expect(user.account.reload.moved_to_account_id).to eq acct.id
+        end
+      end
+
+      context 'when acct is the current account' do
+        let(:acct) { user.account }
+
+        it 'does not update the moved account', :aggregate_failures do
+          subject
+
+          expect(user.account.reload.moved_to_account_id).to be_nil
+          expect(response).to render_template :show
+        end
+      end
+
+      context 'when target account does not reference the account being moved from' do
+        let(:acct) { Fabricate(:account, also_known_as: []) }
+
+        it 'does not update the moved account', :aggregate_failures do
+          subject
+
+          expect(user.account.reload.moved_to_account_id).to be_nil
+          expect(response).to render_template :show
+        end
+      end
+
+      context 'when a recent migration already exists' do
+        let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) }
+
+        before do
+          moved_to = Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)])
+          user.account.migrations.create!(acct: moved_to.acct)
+        end
+
+        it 'does not update the moved account', :aggregate_failures do
+          subject
+
+          expect(user.account.reload.moved_to_account_id).to be_nil
+          expect(response).to render_template :show
+        end
+      end
+    end
+  end
+end
diff --git a/spec/controllers/settings/pictures_controller_spec.rb b/spec/controllers/settings/pictures_controller_spec.rb
new file mode 100644
index 0000000000..683d231ed1
--- /dev/null
+++ b/spec/controllers/settings/pictures_controller_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::PicturesController do
+  render_views
+
+  let!(:user) { Fabricate(:user) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'DELETE #destroy' do
+    context 'with invalid picture id' do
+      it 'returns http bad request' do
+        delete :destroy, params: { id: 'invalid' }
+        expect(response).to have_http_status(400)
+      end
+    end
+
+    context 'with valid picture id' do
+      context 'when account updates correctly' do
+        let(:service) { instance_double(UpdateAccountService, call: true) }
+
+        before do
+          allow(UpdateAccountService).to receive(:new).and_return(service)
+        end
+
+        it 'updates the account' do
+          delete :destroy, params: { id: 'avatar' }
+          expect(response).to redirect_to(settings_profile_path)
+          expect(response).to have_http_status(303)
+          expect(service).to have_received(:call).with(user.account, { 'avatar' => nil, 'avatar_remote_url' => '' })
+        end
+      end
+
+      context 'when account cannot update' do
+        let(:service) { instance_double(UpdateAccountService, call: false) }
+
+        before do
+          allow(UpdateAccountService).to receive(:new).and_return(service)
+        end
+
+        it 'redirects to profile' do
+          delete :destroy, params: { id: 'avatar' }
+          expect(response).to redirect_to(settings_profile_path)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/controllers/settings/preferences/appearance_controller_spec.rb b/spec/controllers/settings/preferences/appearance_controller_spec.rb
new file mode 100644
index 0000000000..14f9b244ab
--- /dev/null
+++ b/spec/controllers/settings/preferences/appearance_controller_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::Preferences::AppearanceController do
+  render_views
+
+  let!(:user) { Fabricate(:user) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #show' do
+    before do
+      get :show
+    end
+
+    it 'returns http success with private cache control headers', :aggregate_failures do
+      expect(response).to have_http_status(200)
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
+  end
+
+  describe 'PUT #update' do
+    subject { put :update, params: { user: { settings_attributes: { theme: 'contrast' } } } }
+
+    it 'redirects correctly' do
+      expect { subject }
+        .to change { user.reload.settings.theme }.to('contrast')
+
+      expect(response).to redirect_to(settings_preferences_appearance_path)
+    end
+  end
+end
diff --git a/spec/controllers/settings/preferences/notifications_controller_spec.rb b/spec/controllers/settings/preferences/notifications_controller_spec.rb
new file mode 100644
index 0000000000..edfdea50e0
--- /dev/null
+++ b/spec/controllers/settings/preferences/notifications_controller_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::Preferences::NotificationsController do
+  render_views
+
+  let(:user) { Fabricate(:user) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #show' do
+    before do
+      get :show
+    end
+
+    it 'returns http success with private cache control headers', :aggregate_failures do
+      expect(response).to have_http_status(200)
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
+  end
+
+  describe 'PUT #update' do
+    it 'updates notifications settings' do
+      user.settings.update('notification_emails.follow': false)
+      user.save
+
+      put :update, params: {
+        user: {
+          settings_attributes: {
+            'notification_emails.follow': '1',
+          },
+        },
+      }
+
+      expect(response).to redirect_to(settings_preferences_notifications_path)
+      user.reload
+      expect(user.settings['notification_emails.follow']).to be true
+    end
+  end
+end
diff --git a/spec/controllers/settings/preferences/other_controller_spec.rb b/spec/controllers/settings/preferences/other_controller_spec.rb
new file mode 100644
index 0000000000..117fdeea7c
--- /dev/null
+++ b/spec/controllers/settings/preferences/other_controller_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::Preferences::OtherController do
+  render_views
+
+  let(:user) { Fabricate(:user, chosen_languages: []) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #show' do
+    before do
+      get :show
+    end
+
+    it 'returns http success with private cache control headers', :aggregate_failures do
+      expect(response).to have_http_status(200)
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
+  end
+
+  describe 'PUT #update' do
+    it 'updates the user record' do
+      put :update, params: { user: { locale: 'en', chosen_languages: ['es', 'fr', ''] } }
+
+      expect(response).to redirect_to(settings_preferences_other_path)
+      user.reload
+      expect(user.locale).to eq 'en'
+      expect(user.chosen_languages).to eq %w(es fr)
+    end
+
+    it 'updates user settings' do
+      user.settings.update('web.reblog_modal': false, 'web.delete_modal': true)
+      user.save
+
+      put :update, params: {
+        user: {
+          settings_attributes: {
+            'web.reblog_modal': '1',
+            'web.delete_modal': '0',
+          },
+        },
+      }
+
+      expect(response).to redirect_to(settings_preferences_other_path)
+      user.reload
+      expect(user.settings['web.reblog_modal']).to be true
+      expect(user.settings['web.delete_modal']).to be false
+    end
+  end
+end
diff --git a/spec/controllers/settings/privacy_controller_spec.rb b/spec/controllers/settings/privacy_controller_spec.rb
new file mode 100644
index 0000000000..59fd342199
--- /dev/null
+++ b/spec/controllers/settings/privacy_controller_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::PrivacyController do
+  render_views
+
+  let!(:user) { Fabricate(:user) }
+  let(:account) { user.account }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #show' do
+    before do
+      get :show
+    end
+
+    it 'returns http success with private cache control headers', :aggregate_failures do
+      expect(response)
+        .to have_http_status(200)
+        .and have_attributes(
+          headers: include(
+            'Cache-Control' => 'private, no-store'
+          )
+        )
+    end
+  end
+
+  describe 'PUT #update' do
+    context 'when update succeeds' do
+      before do
+        allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
+      end
+
+      it 'updates the user profile' do
+        put :update, params: { account: { discoverable: '1', settings: { indexable: '1' } } }
+
+        expect(account.reload.discoverable)
+          .to be(true)
+
+        expect(response)
+          .to redirect_to(settings_privacy_path)
+
+        expect(ActivityPub::UpdateDistributionWorker)
+          .to have_received(:perform_async).with(account.id)
+      end
+    end
+
+    context 'when update fails' do
+      before do
+        allow(UpdateAccountService).to receive(:new).and_return(failing_update_service)
+        allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
+      end
+
+      it 'updates the user profile' do
+        put :update, params: { account: { discoverable: '1', settings: { indexable: '1' } } }
+
+        expect(response)
+          .to render_template(:show)
+
+        expect(ActivityPub::UpdateDistributionWorker)
+          .to_not have_received(:perform_async)
+      end
+
+      private
+
+      def failing_update_service
+        instance_double(UpdateAccountService, call: false)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/settings/profiles_controller_spec.rb b/spec/controllers/settings/profiles_controller_spec.rb
new file mode 100644
index 0000000000..e3197f0a6d
--- /dev/null
+++ b/spec/controllers/settings/profiles_controller_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::ProfilesController do
+  render_views
+
+  let!(:user) { Fabricate(:user) }
+  let(:account) { user.account }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #show' do
+    before do
+      get :show
+    end
+
+    it 'returns http success with private cache control headers', :aggregate_failures do
+      expect(response).to have_http_status(200)
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
+  end
+
+  describe 'PUT #update' do
+    before do
+      user.account.update(display_name: 'Old name')
+    end
+
+    it 'updates the user profile' do
+      allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
+      put :update, params: { account: { display_name: 'New name' } }
+      expect(account.reload.display_name).to eq 'New name'
+      expect(response).to redirect_to(settings_profile_path)
+      expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
+    end
+  end
+
+  describe 'PUT #update with new profile image' do
+    it 'updates profile image' do
+      allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
+      expect(account.avatar.instance.avatar_file_name).to be_nil
+
+      put :update, params: { account: { avatar: fixture_file_upload('avatar.gif', 'image/gif') } }
+      expect(response).to redirect_to(settings_profile_path)
+      expect(account.reload.avatar.instance.avatar_file_name).to_not be_nil
+      expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
+    end
+  end
+end
diff --git a/spec/controllers/settings/sessions_controller_spec.rb b/spec/controllers/settings/sessions_controller_spec.rb
new file mode 100644
index 0000000000..c098af7485
--- /dev/null
+++ b/spec/controllers/settings/sessions_controller_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::SessionsController do
+  render_views
+
+  let(:user) { Fabricate(:user) }
+  let(:session_activation) { Fabricate(:session_activation, user: user) }
+
+  before { sign_in user, scope: :user }
+
+  describe 'DELETE #destroy' do
+    subject { delete :destroy, params: { id: id } }
+
+    context 'when session activation exists' do
+      let(:id) { session_activation.id }
+
+      it 'destroys session activation' do
+        expect(subject).to redirect_to edit_user_registration_path
+        expect(SessionActivation.find_by(id: id)).to be_nil
+      end
+    end
+
+    context 'when session activation does not exist' do
+      let(:id) { session_activation.id + 1000 }
+
+      it 'destroys session activation' do
+        expect(subject).to have_http_status 404
+      end
+    end
+  end
+end
diff --git a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
index 45c5e77323..224310b7ef 100644
--- a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
+++ b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
@@ -5,14 +5,14 @@ require 'rails_helper'
 RSpec.describe Settings::TwoFactorAuthentication::ConfirmationsController do
   render_views
 
-  shared_examples 'renders expected page' do
-    it 'renders the new view with QR code' do
+  shared_examples 'renders :new' do
+    it 'renders the new view' do
       subject
 
       expect(response).to have_http_status(200)
+      expect(response).to render_template(:new)
       expect(response.body)
         .to include(qr_code_markup)
-        .and include(I18n.t('settings.two_factor_authentication'))
     end
 
     def qr_code_markup
@@ -34,7 +34,7 @@ RSpec.describe Settings::TwoFactorAuthentication::ConfirmationsController do
             get :new, session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' }
           end
 
-          include_examples 'renders expected page'
+          include_examples 'renders :new'
         end
 
         it 'redirects if a new otp_secret has not been set in the session' do
@@ -66,13 +66,10 @@ RSpec.describe Settings::TwoFactorAuthentication::ConfirmationsController do
             expect { post_create_with_options }
               .to change { user.reload.otp_secret }.to 'thisisasecretforthespecofnewview'
 
-            expect(flash[:notice])
-              .to eq(I18n.t('two_factor_authentication.enabled_success'))
-            expect(response)
-              .to have_http_status(200)
-            expect(response.body)
-              .to include(*otp_backup_codes)
-              .and include(I18n.t('settings.two_factor_authentication'))
+            expect(flash[:notice]).to eq 'Two-factor authentication successfully enabled'
+            expect(response).to have_http_status(200)
+            expect(response).to render_template('settings/two_factor_authentication/recovery_codes/index')
+            expect(response.body).to include(*otp_backup_codes)
           end
         end
 
@@ -89,12 +86,10 @@ RSpec.describe Settings::TwoFactorAuthentication::ConfirmationsController do
 
           it 'renders page with error message' do
             subject
-
-            expect(response.body)
-              .to include(I18n.t('otp_authentication.wrong_code'))
+            expect(response.body).to include 'The entered code was invalid! Are server time and device time correct?'
           end
 
-          include_examples 'renders expected page'
+          include_examples 'renders :new'
         end
 
         private
@@ -121,4 +116,18 @@ RSpec.describe Settings::TwoFactorAuthentication::ConfirmationsController do
       end
     end
   end
+
+  context 'when not signed in' do
+    it 'redirects on POST to create' do
+      post :create, params: { form_two_factor_confirmation: { otp_attempt: '123456' } }
+
+      expect(response).to redirect_to('/auth/sign_in')
+    end
+
+    it 'redirects on GET to new' do
+      get :new
+
+      expect(response).to redirect_to('/auth/sign_in')
+    end
+  end
 end
diff --git a/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb
new file mode 100644
index 0000000000..0defc52cde
--- /dev/null
+++ b/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::TwoFactorAuthentication::RecoveryCodesController do
+  render_views
+
+  describe 'POST #create' do
+    it 'updates the codes and shows them on a view when signed in' do
+      user = Fabricate(:user)
+      otp_backup_codes = user.generate_otp_backup_codes!
+      allow(user).to receive(:generate_otp_backup_codes!).and_return(otp_backup_codes)
+      allow(controller).to receive(:current_user).and_return(user)
+
+      sign_in user, scope: :user
+      post :create, session: { challenge_passed_at: Time.now.utc }
+
+      expect(flash[:notice]).to eq 'Recovery codes successfully regenerated'
+      expect(response).to have_http_status(200)
+      expect(response).to render_template(:index)
+      expect(response.body)
+        .to include(*otp_backup_codes)
+    end
+
+    it 'redirects when not signed in' do
+      post :create
+      expect(response).to redirect_to '/auth/sign_in'
+    end
+  end
+end
diff --git a/spec/controllers/settings/two_factor_authentication_methods_controller_spec.rb b/spec/controllers/settings/two_factor_authentication_methods_controller_spec.rb
new file mode 100644
index 0000000000..c55f113d4d
--- /dev/null
+++ b/spec/controllers/settings/two_factor_authentication_methods_controller_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::TwoFactorAuthenticationMethodsController do
+  render_views
+
+  context 'when not signed in' do
+    describe 'GET to #index' do
+      it 'redirects' do
+        get :index
+
+        expect(response).to redirect_to '/auth/sign_in'
+      end
+    end
+  end
+
+  context 'when signed in' do
+    let(:user) { Fabricate(:user) }
+
+    before do
+      sign_in user, scope: :user
+    end
+
+    describe 'GET #index' do
+      describe 'when user has enabled otp' do
+        before do
+          user.update(otp_required_for_login: true)
+          get :index
+        end
+
+        it 'returns http success with private cache control headers', :aggregate_failures do
+          expect(response).to have_http_status(200)
+          expect(response.headers['Cache-Control']).to include('private, no-store')
+        end
+      end
+
+      describe 'when user has not enabled otp' do
+        before do
+          user.update(otp_required_for_login: false)
+          get :index
+        end
+
+        it 'redirects to enable otp' do
+          expect(response).to redirect_to(settings_otp_authentication_path)
+        end
+      end
+    end
+
+    describe 'POST to #disable' do
+      before do
+        user.update(otp_required_for_login: true)
+      end
+
+      context 'when user has not passed challenge' do
+        it 'renders challenge page' do
+          post :disable
+
+          expect(response).to have_http_status(200)
+          expect(response).to render_template('auth/challenges/new')
+        end
+      end
+
+      context 'when user has passed challenge' do
+        before do
+          mailer = instance_double(ApplicationMailer::MessageDelivery, deliver_later!: true)
+          allow(UserMailer).to receive(:two_factor_disabled).with(user).and_return(mailer)
+        end
+
+        it 'redirects to settings page' do
+          post :disable, session: { challenge_passed_at: 10.minutes.ago }
+
+          expect(UserMailer).to have_received(:two_factor_disabled).with(user)
+          expect(response).to redirect_to(settings_otp_authentication_path)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/controllers/settings/verifications_controller_spec.rb b/spec/controllers/settings/verifications_controller_spec.rb
new file mode 100644
index 0000000000..1a8df485b5
--- /dev/null
+++ b/spec/controllers/settings/verifications_controller_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::VerificationsController do
+  render_views
+
+  let!(:user) { Fabricate(:user) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #show' do
+    before do
+      get :show
+    end
+
+    it 'returns http success with private cache control headers', :aggregate_failures do
+      expect(response)
+        .to have_http_status(200)
+        .and have_attributes(
+          headers: include(
+            'Cache-Control' => 'private, no-store'
+          )
+        )
+    end
+  end
+end
diff --git a/spec/controllers/statuses_cleanup_controller_spec.rb b/spec/controllers/statuses_cleanup_controller_spec.rb
new file mode 100644
index 0000000000..8a72621993
--- /dev/null
+++ b/spec/controllers/statuses_cleanup_controller_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe StatusesCleanupController do
+  render_views
+
+  let!(:user) { Fabricate(:user) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #show' do
+    before do
+      get :show
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
+  end
+
+  describe 'PUT #update' do
+    before do
+      put :update, params: { account_statuses_cleanup_policy: { enabled: true, min_status_age: 2.weeks.seconds, keep_direct: false, keep_polls: true } }
+    end
+
+    it 'updates the account status cleanup policy' do
+      expect(user.account.statuses_cleanup_policy.enabled).to be true
+      expect(user.account.statuses_cleanup_policy.keep_direct).to be false
+      expect(user.account.statuses_cleanup_policy.keep_polls).to be true
+    end
+
+    it 'redirects' do
+      expect(response).to redirect_to(statuses_cleanup_path)
+    end
+  end
+end
diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb
new file mode 100644
index 0000000000..121e4aa6c6
--- /dev/null
+++ b/spec/controllers/statuses_controller_spec.rb
@@ -0,0 +1,739 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe StatusesController do
+  render_views
+
+  describe 'GET #show' do
+    let(:account) { Fabricate(:account) }
+    let(:status)  { Fabricate(:status, account: account) }
+
+    context 'when account is permanently suspended' do
+      before do
+        account.suspend!
+        account.deletion_request.destroy
+
+        get :show, params: { account_username: account.username, id: status.id }
+      end
+
+      it 'returns http gone' do
+        expect(response).to have_http_status(410)
+      end
+    end
+
+    context 'when account is temporarily suspended' do
+      before do
+        account.suspend!
+
+        get :show, params: { account_username: account.username, id: status.id }
+      end
+
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(403)
+      end
+    end
+
+    context 'when status is a reblog' do
+      let(:original_account) { Fabricate(:account, domain: 'example.com') }
+      let(:original_status) { Fabricate(:status, account: original_account, url: 'https://example.com/123') }
+      let(:status) { Fabricate(:status, account: account, reblog: original_status) }
+
+      before do
+        get :show, params: { account_username: status.account.username, id: status.id }
+      end
+
+      it 'redirects to the original status' do
+        expect(response).to redirect_to(original_status.url)
+      end
+    end
+
+    context 'when status is public' do
+      before do
+        get :show, params: { account_username: status.account.username, id: status.id, format: format }
+      end
+
+      context 'with HTML' do
+        let(:format) { 'html' }
+
+        it 'renders status successfully', :aggregate_failures do
+          expect(response)
+            .to have_http_status(200)
+            .and render_template(:show)
+          expect(response.headers).to include(
+            'Vary' => 'Accept, Accept-Language, Cookie',
+            'Cache-Control' => include('public'),
+            'Link' => include('activity+json')
+          )
+          expect(response.body).to include status.text
+        end
+      end
+
+      context 'with JSON' do
+        let(:format) { 'json' }
+
+        it 'renders ActivityPub Note object successfully', :aggregate_failures do
+          expect(response)
+            .to have_http_status(200)
+            .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie')
+
+          expect(response.headers).to include(
+            'Content-Type' => include('application/activity+json'),
+            'Link' => include('activity+json')
+          )
+          expect(response.parsed_body)
+            .to include(content: include(status.text))
+        end
+      end
+    end
+
+    context 'when status is private' do
+      let(:status) { Fabricate(:status, account: account, visibility: :private) }
+
+      before do
+        get :show, params: { account_username: status.account.username, id: status.id, format: format }
+      end
+
+      context 'with JSON' do
+        let(:format) { 'json' }
+
+        it 'returns http not found' do
+          expect(response).to have_http_status(404)
+        end
+      end
+
+      context 'with HTML' do
+        let(:format) { 'html' }
+
+        it 'returns http not found' do
+          expect(response).to have_http_status(404)
+        end
+      end
+    end
+
+    context 'when status is direct' do
+      let(:status) { Fabricate(:status, account: account, visibility: :direct) }
+
+      before do
+        get :show, params: { account_username: status.account.username, id: status.id, format: format }
+      end
+
+      context 'with JSON' do
+        let(:format) { 'json' }
+
+        it 'returns http not found' do
+          expect(response).to have_http_status(404)
+        end
+      end
+
+      context 'with HTML' do
+        let(:format) { 'html' }
+
+        it 'returns http not found' do
+          expect(response).to have_http_status(404)
+        end
+      end
+    end
+
+    context 'when signed-in' do
+      let(:user) { Fabricate(:user) }
+
+      before do
+        sign_in(user)
+      end
+
+      context 'when account blocks user' do
+        before do
+          account.block!(user.account)
+          get :show, params: { account_username: status.account.username, id: status.id }
+        end
+
+        it 'returns http not found' do
+          expect(response).to have_http_status(404)
+        end
+      end
+
+      context 'when status is public' do
+        before do
+          get :show, params: { account_username: status.account.username, id: status.id, format: format }
+        end
+
+        context 'with HTML' do
+          let(:format) { 'html' }
+
+          it 'renders status successfully', :aggregate_failures do
+            expect(response)
+              .to have_http_status(200)
+              .and render_template(:show)
+            expect(response.headers).to include(
+              'Vary' => 'Accept, Accept-Language, Cookie',
+              'Cache-Control' => include('private'),
+              'Link' => include('activity+json')
+            )
+            expect(response.body).to include status.text
+          end
+        end
+
+        context 'with JSON' do
+          let(:format) { 'json' }
+
+          it 'renders ActivityPub Note object successfully', :aggregate_failures do
+            expect(response)
+              .to have_http_status(200)
+            expect(response.headers).to include(
+              'Vary' => 'Accept, Accept-Language, Cookie',
+              'Cache-Control' => include('private'),
+              'Content-Type' => include('application/activity+json'),
+              'Link' => include('activity+json')
+            )
+            expect(response.parsed_body)
+              .to include(content: include(status.text))
+          end
+        end
+      end
+
+      context 'when status is private' do
+        let(:status) { Fabricate(:status, account: account, visibility: :private) }
+
+        context 'when user is authorized to see it' do
+          before do
+            user.account.follow!(account)
+            get :show, params: { account_username: status.account.username, id: status.id, format: format }
+          end
+
+          context 'with HTML' do
+            let(:format) { 'html' }
+
+            it 'renders status successfully', :aggregate_failures do
+              expect(response)
+                .to have_http_status(200)
+                .and render_template(:show)
+
+              expect(response.headers).to include(
+                'Vary' => 'Accept, Accept-Language, Cookie',
+                'Cache-Control' => include('private'),
+                'Link' => include('activity+json')
+              )
+              expect(response.body).to include status.text
+            end
+          end
+
+          context 'with JSON' do
+            let(:format) { 'json' }
+
+            it 'renders ActivityPub Note object successfully', :aggregate_failures do
+              expect(response)
+                .to have_http_status(200)
+              expect(response.headers).to include(
+                'Vary' => 'Accept, Accept-Language, Cookie',
+                'Cache-Control' => include('private'),
+                'Content-Type' => include('application/activity+json'),
+                'Link' => include('activity+json')
+              )
+              expect(response.parsed_body)
+                .to include(content: include(status.text))
+            end
+          end
+        end
+
+        context 'when user is not authorized to see it' do
+          before do
+            get :show, params: { account_username: status.account.username, id: status.id, format: format }
+          end
+
+          context 'with JSON' do
+            let(:format) { 'json' }
+
+            it 'returns http not found' do
+              expect(response).to have_http_status(404)
+            end
+          end
+
+          context 'with HTML' do
+            let(:format) { 'html' }
+
+            it 'returns http not found' do
+              expect(response).to have_http_status(404)
+            end
+          end
+        end
+      end
+
+      context 'when status is direct' do
+        let(:status) { Fabricate(:status, account: account, visibility: :direct) }
+
+        context 'when user is authorized to see it' do
+          before do
+            Fabricate(:mention, account: user.account, status: status)
+            get :show, params: { account_username: status.account.username, id: status.id, format: format }
+          end
+
+          context 'with HTML' do
+            let(:format) { 'html' }
+
+            it 'renders status successfully', :aggregate_failures do
+              expect(response)
+                .to have_http_status(200)
+                .and render_template(:show)
+              expect(response.headers).to include(
+                'Vary' => 'Accept, Accept-Language, Cookie',
+                'Cache-Control' => include('private'),
+                'Link' => include('activity+json')
+              )
+              expect(response.body).to include status.text
+            end
+          end
+
+          context 'with JSON' do
+            let(:format) { 'json' }
+
+            it 'renders ActivityPub Note object successfully' do
+              expect(response)
+                .to have_http_status(200)
+              expect(response.headers).to include(
+                'Vary' => 'Accept, Accept-Language, Cookie',
+                'Cache-Control' => include('private'),
+                'Content-Type' => include('application/activity+json'),
+                'Link' => include('activity+json')
+              )
+              expect(response.parsed_body)
+                .to include(content: include(status.text))
+            end
+          end
+        end
+
+        context 'when user is not authorized to see it' do
+          before do
+            get :show, params: { account_username: status.account.username, id: status.id, format: format }
+          end
+
+          context 'with JSON' do
+            let(:format) { 'json' }
+
+            it 'returns http not found' do
+              expect(response).to have_http_status(404)
+            end
+          end
+
+          context 'with HTML' do
+            let(:format) { 'html' }
+
+            it 'returns http not found' do
+              expect(response).to have_http_status(404)
+            end
+          end
+        end
+      end
+    end
+
+    context 'with signature' do
+      let(:remote_account) { Fabricate(:account, domain: 'example.com') }
+
+      before do
+        allow(controller).to receive(:signed_request_actor).and_return(remote_account)
+      end
+
+      context 'when account blocks account' do
+        before do
+          account.block!(remote_account)
+          get :show, params: { account_username: status.account.username, id: status.id }
+        end
+
+        it 'returns http not found' do
+          expect(response).to have_http_status(404)
+        end
+      end
+
+      context 'when account domain blocks account' do
+        before do
+          account.block_domain!(remote_account.domain)
+          get :show, params: { account_username: status.account.username, id: status.id }
+        end
+
+        it 'returns http not found' do
+          expect(response).to have_http_status(404)
+        end
+      end
+
+      context 'when status is public' do
+        before do
+          get :show, params: { account_username: status.account.username, id: status.id, format: format }
+        end
+
+        context 'with HTML' do
+          let(:format) { 'html' }
+
+          it 'renders status successfully', :aggregate_failures do
+            expect(response)
+              .to have_http_status(200)
+              .and render_template(:show)
+            expect(response.headers).to include(
+              'Vary' => 'Accept, Accept-Language, Cookie',
+              'Cache-Control' => include('private'),
+              'Link' => include('activity+json')
+            )
+            expect(response.body).to include status.text
+          end
+        end
+
+        context 'with JSON' do
+          let(:format) { 'json' }
+
+          it 'renders ActivityPub Note object successfully', :aggregate_failures do
+            expect(response)
+              .to have_http_status(200)
+              .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie')
+            expect(response.headers).to include(
+              'Content-Type' => include('application/activity+json'),
+              'Link' => include('activity+json')
+            )
+            expect(response.parsed_body)
+              .to include(content: include(status.text))
+          end
+        end
+      end
+
+      context 'when status is private' do
+        let(:status) { Fabricate(:status, account: account, visibility: :private) }
+
+        context 'when user is authorized to see it' do
+          before do
+            remote_account.follow!(account)
+            get :show, params: { account_username: status.account.username, id: status.id, format: format }
+          end
+
+          context 'with HTML' do
+            let(:format) { 'html' }
+
+            it 'renders status successfully', :aggregate_failures do
+              expect(response)
+                .to have_http_status(200)
+                .and render_template(:show)
+              expect(response.headers).to include(
+                'Vary' => 'Accept, Accept-Language, Cookie',
+                'Cache-Control' => include('private'),
+                'Link' => include('activity+json')
+              )
+              expect(response.body).to include status.text
+            end
+          end
+
+          context 'with JSON' do
+            let(:format) { 'json' }
+
+            it 'renders ActivityPub Note object successfully' do
+              expect(response)
+                .to have_http_status(200)
+              expect(response.headers).to include(
+                'Vary' => 'Accept, Accept-Language, Cookie',
+                'Cache-Control' => include('private'),
+                'Content-Type' => include('application/activity+json'),
+                'Link' => include('activity+json')
+              )
+
+              expect(response.parsed_body)
+                .to include(content: include(status.text))
+            end
+          end
+        end
+
+        context 'when user is not authorized to see it' do
+          before do
+            get :show, params: { account_username: status.account.username, id: status.id, format: format }
+          end
+
+          context 'with JSON' do
+            let(:format) { 'json' }
+
+            it 'returns http not found' do
+              expect(response).to have_http_status(404)
+            end
+          end
+
+          context 'with HTML' do
+            let(:format) { 'html' }
+
+            it 'returns http not found' do
+              expect(response).to have_http_status(404)
+            end
+          end
+        end
+      end
+
+      context 'when status is direct' do
+        let(:status) { Fabricate(:status, account: account, visibility: :direct) }
+
+        context 'when user is authorized to see it' do
+          before do
+            Fabricate(:mention, account: remote_account, status: status)
+            get :show, params: { account_username: status.account.username, id: status.id, format: format }
+          end
+
+          context 'with HTML' do
+            let(:format) { 'html' }
+
+            it 'renders status successfully', :aggregate_failures do
+              expect(response)
+                .to have_http_status(200)
+                .and render_template(:show)
+              expect(response.headers).to include(
+                'Vary' => 'Accept, Accept-Language, Cookie',
+                'Cache-Control' => include('private'),
+                'Link' => include('activity+json')
+              )
+              expect(response.body).to include status.text
+            end
+          end
+
+          context 'with JSON' do
+            let(:format) { 'json' }
+
+            it 'renders ActivityPub Note object', :aggregate_failures do
+              expect(response)
+                .to have_http_status(200)
+              expect(response.headers).to include(
+                'Vary' => 'Accept, Accept-Language, Cookie',
+                'Cache-Control' => include('private'),
+                'Content-Type' => include('application/activity+json'),
+                'Link' => include('activity+json')
+              )
+              expect(response.parsed_body)
+                .to include(content: include(status.text))
+            end
+          end
+        end
+
+        context 'when user is not authorized to see it' do
+          before do
+            get :show, params: { account_username: status.account.username, id: status.id, format: format }
+          end
+
+          context 'with JSON' do
+            let(:format) { 'json' }
+
+            it 'returns http not found' do
+              expect(response).to have_http_status(404)
+            end
+          end
+
+          context 'with HTML' do
+            let(:format) { 'html' }
+
+            it 'returns http not found' do
+              expect(response).to have_http_status(404)
+            end
+          end
+        end
+      end
+    end
+  end
+
+  describe 'GET #activity' do
+    let(:account) { Fabricate(:account) }
+    let(:status)  { Fabricate(:status, account: account) }
+
+    context 'when account is permanently suspended' do
+      before do
+        account.suspend!
+        account.deletion_request.destroy
+
+        get :activity, params: { account_username: account.username, id: status.id }
+      end
+
+      it 'returns http gone' do
+        expect(response).to have_http_status(410)
+      end
+    end
+
+    context 'when account is temporarily suspended' do
+      before do
+        account.suspend!
+
+        get :activity, params: { account_username: account.username, id: status.id }
+      end
+
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(403)
+      end
+    end
+
+    context 'when status is public' do
+      before do
+        status.update(visibility: :public)
+        get :activity, params: { account_username: account.username, id: status.id }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+    end
+
+    context 'when status is private' do
+      before do
+        status.update(visibility: :private)
+        get :activity, params: { account_username: account.username, id: status.id }
+      end
+
+      it 'returns http not_found' do
+        expect(response).to have_http_status(404)
+      end
+    end
+
+    context 'when status is direct' do
+      before do
+        status.update(visibility: :direct)
+        get :activity, params: { account_username: account.username, id: status.id }
+      end
+
+      it 'returns http not_found' do
+        expect(response).to have_http_status(404)
+      end
+    end
+
+    context 'when signed-in' do
+      let(:user) { Fabricate(:user) }
+
+      before do
+        sign_in(user)
+      end
+
+      context 'when status is public' do
+        before do
+          status.update(visibility: :public)
+          get :activity, params: { account_username: account.username, id: status.id }
+        end
+
+        it 'returns http success' do
+          expect(response).to have_http_status(:success)
+        end
+      end
+
+      context 'when status is private' do
+        before do
+          status.update(visibility: :private)
+        end
+
+        context 'when user is authorized to see it' do
+          before do
+            user.account.follow!(account)
+            get :activity, params: { account_username: account.username, id: status.id }
+          end
+
+          it 'returns http success' do
+            expect(response).to have_http_status(200)
+          end
+        end
+
+        context 'when user is not authorized to see it' do
+          before do
+            get :activity, params: { account_username: account.username, id: status.id }
+          end
+
+          it 'returns http not_found' do
+            expect(response).to have_http_status(404)
+          end
+        end
+      end
+
+      context 'when status is direct' do
+        before do
+          status.update(visibility: :direct)
+        end
+
+        context 'when user is authorized to see it' do
+          before do
+            Fabricate(:mention, account: user.account, status: status)
+            get :activity, params: { account_username: account.username, id: status.id }
+          end
+
+          it 'returns http success' do
+            expect(response).to have_http_status(200)
+          end
+        end
+
+        context 'when user is not authorized to see it' do
+          before do
+            get :activity, params: { account_username: account.username, id: status.id }
+          end
+
+          it 'returns http not_found' do
+            expect(response).to have_http_status(404)
+          end
+        end
+      end
+    end
+
+    context 'with signature' do
+      let(:remote_account) { Fabricate(:account, domain: 'example.com') }
+
+      before do
+        allow(controller).to receive(:signed_request_actor).and_return(remote_account)
+      end
+
+      context 'when status is public' do
+        before do
+          status.update(visibility: :public)
+          get :activity, params: { account_username: account.username, id: status.id }
+        end
+
+        it 'returns http success' do
+          expect(response).to have_http_status(:success)
+        end
+      end
+
+      context 'when status is private' do
+        before do
+          status.update(visibility: :private)
+        end
+
+        context 'when user is authorized to see it' do
+          before do
+            remote_account.follow!(account)
+            get :activity, params: { account_username: account.username, id: status.id }
+          end
+
+          it 'returns http success' do
+            expect(response).to have_http_status(200)
+          end
+        end
+
+        context 'when user is not authorized to see it' do
+          before do
+            get :activity, params: { account_username: account.username, id: status.id }
+          end
+
+          it 'returns http not_found' do
+            expect(response).to have_http_status(404)
+          end
+        end
+      end
+
+      context 'when status is direct' do
+        before do
+          status.update(visibility: :direct)
+        end
+
+        context 'when user is authorized to see it' do
+          before do
+            Fabricate(:mention, account: remote_account, status: status)
+            get :activity, params: { account_username: account.username, id: status.id }
+          end
+
+          it 'returns http success' do
+            expect(response).to have_http_status(200)
+          end
+        end
+
+        context 'when user is not authorized to see it' do
+          before do
+            get :activity, params: { account_username: account.username, id: status.id }
+          end
+
+          it 'returns http not_found' do
+            expect(response).to have_http_status(404)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/fabricators/account_conversation_fabricator.rb b/spec/fabricators/account_conversation_fabricator.rb
deleted file mode 100644
index 6145ed8254..0000000000
--- a/spec/fabricators/account_conversation_fabricator.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-Fabricator(:account_conversation) do
-  account
-  conversation
-  status_ids { [Fabricate(:status).id] }
-end
diff --git a/spec/fabricators/fasp/debug_callback_fabricator.rb b/spec/fabricators/fasp/debug_callback_fabricator.rb
deleted file mode 100644
index 28c1c00be8..0000000000
--- a/spec/fabricators/fasp/debug_callback_fabricator.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-Fabricator(:fasp_debug_callback, from: 'Fasp::DebugCallback') do
-  fasp_provider
-  ip            '127.0.0.234'
-  request_body  'MyText'
-end
diff --git a/spec/fabricators/fasp/provider_fabricator.rb b/spec/fabricators/fasp/provider_fabricator.rb
deleted file mode 100644
index fd7867402a..0000000000
--- a/spec/fabricators/fasp/provider_fabricator.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-Fabricator(:fasp_provider, from: 'Fasp::Provider') do
-  name                    { Faker::App.name }
-  base_url                { Faker::Internet.unique.url }
-  sign_in_url             { Faker::Internet.url }
-  remote_identifier       'MyString'
-  provider_public_key_pem "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAh2ldXsaej2MXj0DHdCx7XibSo66uKlrLfJ5J6hte1Gk=\n-----END PUBLIC KEY-----\n"
-  server_private_key_pem  "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICDjlajhVb8XfzyTchQWKraMKwtQW+r4opoAg7V3kw1Q\n-----END PRIVATE KEY-----\n"
-  capabilities            []
-end
-
-Fabricator(:confirmed_fasp, from: :fasp_provider) do
-  confirmed    true
-  capabilities [
-    { id: 'callback', version: '0.1' },
-    { id: 'data_sharing', version: '0.1' },
-  ]
-end
-
-Fabricator(:debug_fasp, from: :fasp_provider) do
-  confirmed    true
-  capabilities [
-    { id: 'callback', version: '0.1', enabled: true },
-  ]
-
-  after_build do |fasp|
-    # Prevent fabrication from attempting an HTTP call to the provider
-    def fasp.update_remote_capabilities = true
-  end
-end
diff --git a/spec/fabricators/import_fabricator.rb b/spec/fabricators/import_fabricator.rb
new file mode 100644
index 0000000000..4951bb9a4d
--- /dev/null
+++ b/spec/fabricators/import_fabricator.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+Fabricator(:import) do
+  account
+  type :following
+  data { attachment_fixture('imports.txt') }
+end
diff --git a/spec/fabricators/preview_card_trend_fabricator.rb b/spec/fabricators/preview_card_trend_fabricator.rb
deleted file mode 100644
index 14c126cc89..0000000000
--- a/spec/fabricators/preview_card_trend_fabricator.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-Fabricator(:preview_card_trend) do
-  preview_card
-end
diff --git a/spec/fabricators/scheduled_expiration_status_fabricator.rb b/spec/fabricators/scheduled_expiration_status_fabricator.rb
deleted file mode 100644
index 9cbacc9b76..0000000000
--- a/spec/fabricators/scheduled_expiration_status_fabricator.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-Fabricator(:scheduled_expiration_status) do
-  account { Fabricate.build(:account) }
-  status { Fabricate.build(:status) }
-  scheduled_at { 20.hours.from_now }
-end
diff --git a/spec/fabricators/setting_fabricator.rb b/spec/fabricators/setting_fabricator.rb
index 689a0de002..ce9a48e901 100644
--- a/spec/fabricators/setting_fabricator.rb
+++ b/spec/fabricators/setting_fabricator.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 Fabricator(:setting) do
-  var { sequence(:var) { |n| "var_#{n}" } }
+  var 'var'
 end
diff --git a/spec/fabricators/status_edit_fabricator.rb b/spec/fabricators/status_edit_fabricator.rb
deleted file mode 100644
index 4df61dcfda..0000000000
--- a/spec/fabricators/status_edit_fabricator.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-Fabricator(:status_edit) do
-  status { Fabricate.build(:status) }
-end
diff --git a/spec/fabricators/status_trend_fabricator.rb b/spec/fabricators/status_trend_fabricator.rb
deleted file mode 100644
index c775892b1f..0000000000
--- a/spec/fabricators/status_trend_fabricator.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-Fabricator(:status_trend) do
-  status
-  account
-end
diff --git a/spec/fabricators/tag_trend_fabricator.rb b/spec/fabricators/tag_trend_fabricator.rb
deleted file mode 100644
index ddf9b9bf40..0000000000
--- a/spec/fabricators/tag_trend_fabricator.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-Fabricator(:tag_trend) do
-  tag
-end
diff --git a/spec/fabricators/terms_of_service_fabricator.rb b/spec/fabricators/terms_of_service_fabricator.rb
deleted file mode 100644
index 9b437c4d4b..0000000000
--- a/spec/fabricators/terms_of_service_fabricator.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-Fabricator(:terms_of_service) do
-  text { Faker::Lorem.paragraph }
-  changelog { Faker::Lorem.paragraph }
-  published_at { Time.zone.now }
-  notification_sent_at { Time.zone.now }
-  effective_date { Faker::Date.unique.forward }
-end
diff --git a/spec/fabricators/tombstone_fabricator.rb b/spec/fabricators/tombstone_fabricator.rb
deleted file mode 100644
index b4cab086af..0000000000
--- a/spec/fabricators/tombstone_fabricator.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-Fabricator(:tombstone) do
-  account
-  uri { sequence(:uri) { |i| "https://host.example/value/#{i}" } }
-end
diff --git a/spec/fabricators/user_fabricator.rb b/spec/fabricators/user_fabricator.rb
index 104d7f9931..0df7caea60 100644
--- a/spec/fabricators/user_fabricator.rb
+++ b/spec/fabricators/user_fabricator.rb
@@ -13,15 +13,3 @@ Fabricator(:user) do
   current_sign_in_at { Time.zone.now }
   agreement true
 end
-
-Fabricator(:admin_user, from: :user) do
-  role UserRole.find_by(name: 'Admin')
-end
-
-Fabricator(:moderator_user, from: :user) do
-  role UserRole.find_by(name: 'Moderator')
-end
-
-Fabricator(:owner_user, from: :user) do
-  role UserRole.find_by(name: 'Owner')
-end
diff --git a/spec/fixtures/files/4096x4097.png b/spec/fixtures/files/4096x4097.png
new file mode 100644
index 0000000000..d1110cc2df
Binary files /dev/null and b/spec/fixtures/files/4096x4097.png differ
diff --git a/spec/fixtures/files/avatar.avif b/spec/fixtures/files/avatar.avif
deleted file mode 100644
index f306942dbe..0000000000
Binary files a/spec/fixtures/files/avatar.avif and /dev/null differ
diff --git a/spec/flatware_helper.rb b/spec/flatware_helper.rb
index a1bcb62340..57a7c1f56a 100644
--- a/spec/flatware_helper.rb
+++ b/spec/flatware_helper.rb
@@ -3,7 +3,7 @@
 if defined?(Flatware)
   Flatware.configure do |config|
     config.after_fork do |test_env_number|
-      if ENV.fetch('COVERAGE', false)
+      unless ENV.fetch('DISABLE_SIMPLECOV', nil) == 'true'
         require 'simplecov'
         SimpleCov.at_fork.call(test_env_number) # Combines parallel coverage results
       end
diff --git a/spec/helpers/accounts_helper_spec.rb b/spec/helpers/accounts_helper_spec.rb
index 8526482362..2c949cde69 100644
--- a/spec/helpers/accounts_helper_spec.rb
+++ b/spec/helpers/accounts_helper_spec.rb
@@ -3,6 +3,16 @@
 require 'rails_helper'
 
 RSpec.describe AccountsHelper do
+  def set_not_embedded_view
+    params[:controller] = "not_#{StatusesHelper::EMBEDDED_CONTROLLER}"
+    params[:action] = "not_#{StatusesHelper::EMBEDDED_ACTION}"
+  end
+
+  def set_embedded_view
+    params[:controller] = StatusesHelper::EMBEDDED_CONTROLLER
+    params[:action] = StatusesHelper::EMBEDDED_ACTION
+  end
+
   describe '#display_name' do
     it 'uses the display name when it exists' do
       account = Account.new(display_name: 'Display', username: 'Username')
@@ -18,8 +28,9 @@ RSpec.describe AccountsHelper do
   end
 
   describe '#acct' do
-    it 'is fully qualified for local accounts' do
+    it 'is fully qualified for embedded local accounts' do
       allow(Rails.configuration.x).to receive(:local_domain).and_return('local_domain')
+      set_embedded_view
       account = Account.new(domain: nil, username: 'user')
 
       acct = helper.acct(account)
@@ -27,12 +38,32 @@ RSpec.describe AccountsHelper do
       expect(acct).to eq '@user@local_domain'
     end
 
-    it 'is fully qualified for remote accounts' do
+    it 'is fully qualified for embedded foreign accounts' do
+      set_embedded_view
       account = Account.new(domain: 'foreign_server.com', username: 'user')
 
       acct = helper.acct(account)
 
       expect(acct).to eq '@user@foreign_server.com'
     end
+
+    it 'is fully qualified for non embedded foreign accounts' do
+      set_not_embedded_view
+      account = Account.new(domain: 'foreign_server.com', username: 'user')
+
+      acct = helper.acct(account)
+
+      expect(acct).to eq '@user@foreign_server.com'
+    end
+
+    it 'is fully qualified for non embedded local accounts' do
+      allow(Rails.configuration.x).to receive(:local_domain).and_return('local_domain')
+      set_not_embedded_view
+      account = Account.new(domain: nil, username: 'user')
+
+      acct = helper.acct(account)
+
+      expect(acct).to eq '@user@local_domain'
+    end
   end
 end
diff --git a/spec/helpers/admin/trends/statuses_helper_spec.rb b/spec/helpers/admin/trends/statuses_helper_spec.rb
index 6abc4569b4..fa5c337e97 100644
--- a/spec/helpers/admin/trends/statuses_helper_spec.rb
+++ b/spec/helpers/admin/trends/statuses_helper_spec.rb
@@ -28,19 +28,6 @@ RSpec.describe Admin::Trends::StatusesHelper do
       end
     end
 
-    context 'with a remote status that has excessive attributes' do
-      let(:attr_limit) { Nokogiri::Gumbo::DEFAULT_MAX_ATTRIBUTES * 2 }
-      let(:html) { "<html><body #{(1..attr_limit).map { |x| "attr-#{x}" }.join(' ')}><p>text</p></body></html>" }
-
-      let(:status) { Fabricate.build(:status, uri: 'https://host.example', text: html) }
-
-      it 'renders a correct preview text' do
-        result = helper.one_line_preview(status)
-
-        expect(result).to eq ''
-      end
-    end
-
     context 'with a status that has empty text' do
       let(:status) { Fabricate.build(:status, text: '') }
 
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index ad52e9136d..bf46c16c79 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -5,20 +5,12 @@ require 'rails_helper'
 RSpec.describe ApplicationHelper do
   describe 'body_classes' do
     context 'with a body class string from a controller' do
-      before do
-        user = Fabricate :user
-        user.settings['web.use_system_font'] = true
-        user.settings['web.reduce_motion'] = true
-        user.save
+      before { helper.extend controller_helpers }
 
-        helper.extend controller_helpers
-      end
-
-      it 'uses the current theme and user settings classes in the result' do
+      it 'uses the controller body classes in the result' do
         expect(helper.body_classes)
-          .to match(/theme-default/)
-          .and match(/system-font/)
-          .and match(/reduce-motion/)
+          .to match(/modal-layout compose-standalone/)
+          .and match(/theme-default/)
       end
 
       it 'includes values set via content_for' do
@@ -32,8 +24,10 @@ RSpec.describe ApplicationHelper do
 
       def controller_helpers
         Module.new do
+          def body_class_string = 'modal-layout compose-standalone'
+
           def current_account
-            @current_account ||= Fabricate(:account, user: User.last)
+            @current_account ||= Fabricate(:account)
           end
 
           def current_theme = 'default'
@@ -237,6 +231,28 @@ RSpec.describe ApplicationHelper do
     end
   end
 
+  describe 'visibility_icon' do
+    it 'returns a globe icon for a public visible status' do
+      result = helper.visibility_icon Status.new(visibility: 'public')
+      expect(result).to match(/globe/)
+    end
+
+    it 'returns an unlock icon for a unlisted visible status' do
+      result = helper.visibility_icon Status.new(visibility: 'unlisted')
+      expect(result).to match(/lock_open/)
+    end
+
+    it 'returns a lock icon for a private visible status' do
+      result = helper.visibility_icon Status.new(visibility: 'private')
+      expect(result).to match(/lock/)
+    end
+
+    it 'returns an at icon for a direct visible status' do
+      result = helper.visibility_icon Status.new(visibility: 'direct')
+      expect(result).to match(/alternate_email/)
+    end
+  end
+
   describe 'title' do
     it 'returns site title on production environment' do
       Setting.site_title = 'site title'
diff --git a/spec/helpers/database_helper_spec.rb b/spec/helpers/database_helper_spec.rb
deleted file mode 100644
index 9ea398bf4c..0000000000
--- a/spec/helpers/database_helper_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe DatabaseHelper do
-  context 'when a replica is enabled' do
-    around do |example|
-      ClimateControl.modify REPLICA_DB_NAME: 'prod-relay-quantum-tunnel-mirror' do
-        example.run
-      end
-    end
-
-    before { allow(ApplicationRecord).to receive(:connected_to) }
-
-    describe '#with_read_replica' do
-      it 'uses the replica for connections' do
-        helper.with_read_replica { _x = 1 }
-
-        expect(ApplicationRecord)
-          .to have_received(:connected_to).with(role: :reading, prevent_writes: true)
-      end
-    end
-
-    describe '#with_primary' do
-      it 'uses the primary for connections' do
-        helper.with_primary { _x = 1 }
-
-        expect(ApplicationRecord)
-          .to have_received(:connected_to).with(role: :writing)
-      end
-    end
-  end
-
-  context 'when a replica is not enabled' do
-    around do |example|
-      ClimateControl.modify REPLICA_DB_NAME: nil do
-        example.run
-      end
-    end
-
-    before { allow(ApplicationRecord).to receive(:connected_to) }
-
-    describe '#with_read_replica' do
-      it 'does not use the replica for connections' do
-        helper.with_read_replica { _x = 1 }
-
-        expect(ApplicationRecord)
-          .to_not have_received(:connected_to).with(role: :reading, prevent_writes: true)
-      end
-    end
-
-    describe '#with_primary' do
-      it 'does not use the primary for connections' do
-        helper.with_primary { _x = 1 }
-
-        expect(ApplicationRecord)
-          .to_not have_received(:connected_to).with(role: :writing)
-      end
-    end
-  end
-end
diff --git a/spec/helpers/formatting_helper_spec.rb b/spec/helpers/formatting_helper_spec.rb
index 5ff534e4eb..136a609b1c 100644
--- a/spec/helpers/formatting_helper_spec.rb
+++ b/spec/helpers/formatting_helper_spec.rb
@@ -6,57 +6,19 @@ RSpec.describe FormattingHelper do
   include Devise::Test::ControllerHelpers
 
   describe '#rss_status_content_format' do
-    subject { helper.rss_status_content_format(status) }
+    let(:status) { Fabricate(:status, text: 'Hello world<>', spoiler_text: 'This is a spoiler<>', poll: Fabricate(:poll, options: %w(Yes<> No))) }
+    let(:html) { helper.rss_status_content_format(status) }
 
-    context 'with a simple status' do
-      let(:status) { Fabricate.build :status, text: 'Hello world' }
-
-      it 'renders the formatted elements' do
-        expect(parsed_result.css('p').first.text)
-          .to eq('Hello world')
-      end
+    it 'renders the spoiler text' do
+      expect(html).to include('<p>This is a spoiler&lt;&gt;</p><hr>')
     end
 
-    context 'with a spoiler and an emoji and a poll' do
-      let(:status) { Fabricate(:status, text: 'Hello :world: <>', spoiler_text: 'This is a spoiler<>', poll: Fabricate(:poll, options: %w(Yes<> No))) }
-
-      before { Fabricate :custom_emoji, shortcode: 'world' }
-
-      it 'renders the formatted elements' do
-        expect(spoiler_node.css('strong').text)
-          .to eq('Content warning:')
-        expect(spoiler_node.text)
-          .to include('This is a spoiler<>')
-        expect(content_node.text)
-          .to eq('Hello  <>')
-        expect(content_node.css('img').first.to_h.symbolize_keys)
-          .to include(
-            rel: 'emoji',
-            title: ':world:'
-          )
-        expect(poll_node.css('radio').first.text)
-          .to eq('Yes<>')
-        expect(poll_node.css('radio').first.to_h.symbolize_keys)
-          .to include(
-            disabled: 'disabled'
-          )
-      end
-
-      def spoiler_node
-        parsed_result.css('p').first
-      end
-
-      def content_node
-        parsed_result.css('p')[1]
-      end
-
-      def poll_node
-        parsed_result.css('p').last
-      end
+    it 'renders the status text' do
+      expect(html).to include('<p>Hello world&lt;&gt;</p>')
     end
 
-    def parsed_result
-      Nokogiri::HTML.fragment(subject)
+    it 'renders the poll' do
+      expect(html).to include('<radio disabled="disabled">Yes&lt;&gt;</radio><br>')
     end
   end
 end
diff --git a/spec/helpers/self_destruct_helper_spec.rb b/spec/helpers/self_destruct_helper_spec.rb
index dca1590764..09d7347eee 100644
--- a/spec/helpers/self_destruct_helper_spec.rb
+++ b/spec/helpers/self_destruct_helper_spec.rb
@@ -3,20 +3,19 @@
 require 'rails_helper'
 
 RSpec.describe SelfDestructHelper do
-  describe '#self_destruct?' do
-    before { Rails.configuration.x.mastodon.self_destruct_value = destruct_value }
-    after { Rails.configuration.x.mastodon.self_destruct_value = nil }
-
+  describe 'self_destruct?' do
     context 'when SELF_DESTRUCT is unset' do
-      let(:destruct_value) { nil }
-
       it 'returns false' do
         expect(helper.self_destruct?).to be false
       end
     end
 
     context 'when SELF_DESTRUCT is set to an invalid value' do
-      let(:destruct_value) { 'true' }
+      around do |example|
+        ClimateControl.modify SELF_DESTRUCT: 'true' do
+          example.run
+        end
+      end
 
       it 'returns false' do
         expect(helper.self_destruct?).to be false
@@ -24,10 +23,9 @@ RSpec.describe SelfDestructHelper do
     end
 
     context 'when SELF_DESTRUCT is set to value signed for the wrong purpose' do
-      let(:destruct_value) { Rails.configuration.x.mastodon.self_destruct_value = Rails.application.message_verifier('foo').generate('example.com') }
-
       around do |example|
         ClimateControl.modify(
+          SELF_DESTRUCT: Rails.application.message_verifier('foo').generate('example.com'),
           LOCAL_DOMAIN: 'example.com'
         ) do
           example.run
@@ -40,10 +38,9 @@ RSpec.describe SelfDestructHelper do
     end
 
     context 'when SELF_DESTRUCT is set to value signed for the wrong domain' do
-      let(:destruct_value) { Rails.configuration.x.mastodon.self_destruct_value = Rails.application.message_verifier(described_class::VERIFY_PURPOSE).generate('foo.com') }
-
       around do |example|
         ClimateControl.modify(
+          SELF_DESTRUCT: Rails.application.message_verifier('self-destruct').generate('foo.com'),
           LOCAL_DOMAIN: 'example.com'
         ) do
           example.run
@@ -56,10 +53,9 @@ RSpec.describe SelfDestructHelper do
     end
 
     context 'when SELF_DESTRUCT is set to a correctly-signed value' do
-      let(:destruct_value) { Rails.configuration.x.mastodon.self_destruct_value = Rails.application.message_verifier(described_class::VERIFY_PURPOSE).generate('example.com') }
-
       around do |example|
         ClimateControl.modify(
+          SELF_DESTRUCT: Rails.application.message_verifier('self-destruct').generate('example.com'),
           LOCAL_DOMAIN: 'example.com'
         ) do
           example.run
diff --git a/spec/helpers/statuses_helper_spec.rb b/spec/helpers/statuses_helper_spec.rb
index fd21910a63..edd3e8f2f7 100644
--- a/spec/helpers/statuses_helper_spec.rb
+++ b/spec/helpers/statuses_helper_spec.rb
@@ -47,26 +47,6 @@ RSpec.describe StatusesHelper do
       end
     end
 
-    context 'with a status that is public_unlisted' do
-      let(:status) { Status.new(visibility: 'public_unlisted') }
-
-      it 'returns the correct fa icon' do
-        result = helper.visibility_icon(status)
-
-        expect(result).to match('cloud')
-      end
-    end
-
-    context 'with a status that is login' do
-      let(:status) { Status.new(visibility: 'login') }
-
-      it 'returns the correct fa icon' do
-        result = helper.visibility_icon(status)
-
-        expect(result).to match('key')
-      end
-    end
-
     context 'with a status that is unlisted' do
       let(:status) { Status.new(visibility: 'unlisted') }
 
@@ -96,15 +76,29 @@ RSpec.describe StatusesHelper do
         expect(result).to match('alternate_email')
       end
     end
+  end
 
-    context 'with a status that is limited' do
-      let(:status) { Status.new(visibility: 'limited') }
+  describe '#stream_link_target' do
+    it 'returns nil if it is not an embedded view' do
+      set_not_embedded_view
 
-      it 'returns the correct fa icon' do
-        result = helper.visibility_icon(status)
+      expect(helper.stream_link_target).to be_nil
+    end
 
-        expect(result).to match('shield')
-      end
+    it 'returns _blank if it is an embedded view' do
+      set_embedded_view
+
+      expect(helper.stream_link_target).to eq '_blank'
     end
   end
+
+  def set_not_embedded_view
+    params[:controller] = "not_#{StatusesHelper::EMBEDDED_CONTROLLER}"
+    params[:action] = "not_#{StatusesHelper::EMBEDDED_ACTION}"
+  end
+
+  def set_embedded_view
+    params[:controller] = StatusesHelper::EMBEDDED_CONTROLLER
+    params[:action] = StatusesHelper::EMBEDDED_ACTION
+  end
 end
diff --git a/spec/helpers/theme_helper_spec.rb b/spec/helpers/theme_helper_spec.rb
index 51611b8211..7663e59436 100644
--- a/spec/helpers/theme_helper_spec.rb
+++ b/spec/helpers/theme_helper_spec.rb
@@ -79,45 +79,6 @@ RSpec.describe ThemeHelper do
     end
   end
 
-  describe '#custom_stylesheet' do
-    let(:custom_css) { 'body {}' }
-    let(:custom_digest) { Digest::SHA256.hexdigest(custom_css) }
-
-    before do
-      Setting.custom_css = custom_css
-    end
-
-    context 'when custom css setting value digest is present' do
-      before { Rails.cache.write(:setting_digest_custom_css, custom_digest) }
-
-      it 'returns value from settings' do
-        expect(custom_stylesheet)
-          .to match("/css/custom-#{custom_digest[...8]}.css")
-      end
-    end
-
-    context 'when custom css setting value digest is expired' do
-      before { Rails.cache.delete(:setting_digest_custom_css) }
-
-      it 'returns value from settings' do
-        expect(custom_stylesheet)
-          .to match("/css/custom-#{custom_digest[...8]}.css")
-      end
-    end
-
-    context 'when custom css setting is not present' do
-      before do
-        Setting.custom_css = nil
-        Rails.cache.delete(:setting_digest_custom_css)
-      end
-
-      it 'returns default value' do
-        expect(custom_stylesheet)
-          .to be_blank
-      end
-    end
-  end
-
   private
 
   def html_links
diff --git a/spec/lib/account_reach_finder_spec.rb b/spec/lib/account_reach_finder_spec.rb
index ed16c07c22..0c1d92b2da 100644
--- a/spec/lib/account_reach_finder_spec.rb
+++ b/spec/lib/account_reach_finder_spec.rb
@@ -13,28 +13,13 @@ RSpec.describe AccountReachFinder do
   let(:ap_mentioned_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-3', domain: 'example.com') }
   let(:ap_mentioned_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.org/inbox-4', domain: 'example.org') }
 
-  let(:ap_followed_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-5', domain: 'example.com') }
-  let(:ap_followed_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-6', domain: 'example.org') }
-
-  let(:ap_requested_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-7', domain: 'example.com') }
-  let(:ap_requested_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-8', domain: 'example.org') }
-
   let(:unrelated_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/unrelated-inbox', domain: 'example.com') }
-  let(:old_followed_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/old-followed-inbox', domain: 'example.com') }
 
   before do
-    travel_to(2.months.ago) { account.follow!(old_followed_account) }
-
     ap_follower_example_com.follow!(account)
     ap_follower_example_org.follow!(account)
     ap_follower_with_shared.follow!(account)
 
-    account.follow!(ap_followed_example_com)
-    account.follow!(ap_followed_example_org)
-
-    account.request_follow!(ap_requested_example_com)
-    account.request_follow!(ap_requested_example_org)
-
     Fabricate(:status, account: account).tap do |status|
       status.mentions << Mention.new(account: ap_follower_example_com)
       status.mentions << Mention.new(account: ap_mentioned_with_shared)
@@ -59,10 +44,7 @@ RSpec.describe AccountReachFinder do
       expect(subject)
         .to include(*follower_inbox_urls)
         .and include(*mentioned_account_inbox_urls)
-        .and include(*recently_followed_inbox_urls)
-        .and include(*recently_requested_inbox_urls)
         .and not_include(unrelated_account.preferred_inbox_url)
-        .and not_include(old_followed_account.preferred_inbox_url)
     end
 
     def follower_inbox_urls
@@ -74,15 +56,5 @@ RSpec.describe AccountReachFinder do
       [ap_mentioned_with_shared, ap_mentioned_example_com, ap_mentioned_example_org]
         .map(&:preferred_inbox_url)
     end
-
-    def recently_followed_inbox_urls
-      [ap_followed_example_com, ap_followed_example_org]
-        .map(&:preferred_inbox_url)
-    end
-
-    def recently_requested_inbox_urls
-      [ap_requested_example_com, ap_requested_example_org]
-        .map(&:preferred_inbox_url)
-    end
   end
 end
diff --git a/spec/lib/account_statuses_filter_spec.rb b/spec/lib/account_statuses_filter_spec.rb
index 0ca28cfb9b..fd5357c1bb 100644
--- a/spec/lib/account_statuses_filter_spec.rb
+++ b/spec/lib/account_statuses_filter_spec.rb
@@ -57,24 +57,36 @@ RSpec.describe AccountStatusesFilter do
     end
 
     shared_examples 'filter params' do
-      it 'respects param options in results' do
-        expect(results_for(only_media: true))
-          .to all(satisfy(&:with_media?))
+      context 'with only_media param' do
+        let(:params) { { only_media: true } }
 
-        expect(results_for(tagged: tag.name))
-          .to all(satisfy { |status| status.tags.include?(tag) })
-
-        expect(results_for(exclude_replies: true))
-          .to all(satisfy { |status| !status.reply? })
-
-        expect(results_for(exclude_reblogs: true))
-          .to all(satisfy { |status| !status.reblog? })
+        it 'returns only statuses with media' do
+          expect(subject.all?(&:with_media?)).to be true
+        end
       end
 
-      def results_for(params)
-        described_class
-          .new(account, current_account, params)
-          .results
+      context 'with tagged param' do
+        let(:params) { { tagged: tag.name } }
+
+        it 'returns only statuses with tag' do
+          expect(subject.all? { |s| s.tags.include?(tag) }).to be true
+        end
+      end
+
+      context 'with exclude_replies param' do
+        let(:params) { { exclude_replies: true } }
+
+        it 'returns only statuses that are not replies' do
+          expect(subject.none?(&:reply?)).to be true
+        end
+      end
+
+      context 'with exclude_reblogs param' do
+        let(:params) { { exclude_reblogs: true } }
+
+        it 'returns only statuses that are not reblogs' do
+          expect(subject.none?(&:reblog?)).to be true
+        end
       end
     end
 
diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb
index 108111c06b..a2067357fa 100644
--- a/spec/lib/activitypub/activity/create_spec.rb
+++ b/spec/lib/activitypub/activity/create_spec.rb
@@ -2413,31 +2413,6 @@ RSpec.describe ActivityPub::Activity::Create do
           end
         end
       end
-
-      context 'with counts' do
-        let(:object_json) do
-          build_object(
-            likes: {
-              id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar', '/likes'].join,
-              type: 'Collection',
-              totalItems: 50,
-            },
-            shares: {
-              id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar', '/shares'].join,
-              type: 'Collection',
-              totalItems: 100,
-            }
-          )
-        end
-
-        it 'uses the counts from the created object' do
-          expect { subject.perform }.to change(sender.statuses, :count).by(1)
-
-          status = sender.statuses.first
-          expect(status.untrusted_favourites_count).to eq 50
-          expect(status.untrusted_reblogs_count).to eq 100
-        end
-      end
     end
 
     context 'when object URI uses bearcaps' do
diff --git a/spec/lib/activitypub/activity/update_spec.rb b/spec/lib/activitypub/activity/update_spec.rb
index f3b24e91d2..d2c4c1ce28 100644
--- a/spec/lib/activitypub/activity/update_spec.rb
+++ b/spec/lib/activitypub/activity/update_spec.rb
@@ -177,69 +177,5 @@ RSpec.describe ActivityPub::Activity::Update do
         }))).to have_been_made.once
       end
     end
-
-    context 'with a Note object' do
-      let(:updated) { nil }
-      let(:favourites) { 50 }
-      let(:reblogs) { 100 }
-
-      let!(:status) { Fabricate(:status, uri: 'https://example.com/statuses/poll', account: sender) }
-      let(:json) do
-        {
-          '@context': 'https://www.w3.org/ns/activitystreams',
-          id: 'foo',
-          type: 'Update',
-          actor: sender.uri,
-          object: {
-            type: 'Note',
-            id: status.uri,
-            content: 'Foo',
-            updated: updated,
-            likes: {
-              id: "#{status.uri}/likes",
-              type: 'Collection',
-              totalItems: favourites,
-            },
-            shares: {
-              id: "#{status.uri}/shares",
-              type: 'Collection',
-              totalItems: reblogs,
-            },
-          },
-        }.with_indifferent_access
-      end
-
-      shared_examples 'updates counts' do
-        it 'updates the reblog count' do
-          expect(status.untrusted_reblogs_count).to eq reblogs
-        end
-
-        it 'updates the favourites count' do
-          expect(status.untrusted_favourites_count).to eq favourites
-        end
-      end
-
-      context 'with an implicit update' do
-        before do
-          status.update!(uri: ActivityPub::TagManager.instance.uri_for(status))
-          subject.perform
-        end
-
-        it_behaves_like 'updates counts'
-      end
-
-      context 'with an explicit update' do
-        let(:favourites) { 150 }
-        let(:reblogs) { 200 }
-        let(:updated) { Time.now.utc.iso8601 }
-
-        before do
-          status.update!(uri: ActivityPub::TagManager.instance.uri_for(status))
-          subject.perform
-        end
-
-        it_behaves_like 'updates counts'
-      end
-    end
   end
 end
diff --git a/spec/lib/activitypub/parser/custom_emoji_parser_spec.rb b/spec/lib/activitypub/parser/custom_emoji_parser_spec.rb
deleted file mode 100644
index 89421b6d6d..0000000000
--- a/spec/lib/activitypub/parser/custom_emoji_parser_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ActivityPub::Parser::CustomEmojiParser do
-  subject { described_class.new(json) }
-
-  context 'with fedibird license' do
-    let(:json) do
-      {
-        '@context': 'https://www.w3.org/ns/activitystreams',
-        id: ['https://example.com/#foo'].join,
-        name: 'ohagi',
-        license: 'Ohagi is ohagi',
-      }.with_indifferent_access
-    end
-
-    it 'load license' do
-      expect(subject.license).to eq 'Ohagi is ohagi'
-    end
-  end
-
-  context 'with misskey license' do
-    let(:json) do
-      {
-        '@context': 'https://www.w3.org/ns/activitystreams',
-        id: ['https://example.com/#foo'].join,
-        name: 'ohagi',
-        _misskey_license: {
-          freeText: 'Ohagi is ohagi',
-        },
-      }.with_indifferent_access
-    end
-
-    it 'load license' do
-      expect(subject.license).to eq 'Ohagi is ohagi'
-    end
-  end
-
-  context 'without license' do
-    let(:json) do
-      {
-        '@context': 'https://www.w3.org/ns/activitystreams',
-        id: ['https://example.com/#foo'].join,
-        name: 'ohagi',
-      }.with_indifferent_access
-    end
-
-    it 'do not load license' do
-      expect(subject.license).to be_nil
-    end
-  end
-end
diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb
index 87f8d568be..2bc7820542 100644
--- a/spec/lib/activitypub/tag_manager_spec.rb
+++ b/spec/lib/activitypub/tag_manager_spec.rb
@@ -7,50 +7,17 @@ RSpec.describe ActivityPub::TagManager do
 
   subject { described_class.instance }
 
-  let(:domain) { "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}" }
-
-  describe '#public_collection?' do
-    it 'returns true for the special public collection and common shorthands' do
-      expect(subject.public_collection?('https://www.w3.org/ns/activitystreams#Public')).to be true
-      expect(subject.public_collection?('as:Public')).to be true
-      expect(subject.public_collection?('Public')).to be true
-    end
-
-    it 'returns false for other URIs' do
-      expect(subject.public_collection?('https://example.com/foo/bar')).to be false
-    end
-  end
-
   describe '#url_for' do
-    it 'returns a string starting with web domain' do
+    it 'returns a string' do
       account = Fabricate(:account)
-      expect(subject.url_for(account)).to be_a(String)
-        .and start_with(domain)
+      expect(subject.url_for(account)).to be_a String
     end
   end
 
   describe '#uri_for' do
-    it 'returns a string starting with web domain' do
+    it 'returns a string' do
       account = Fabricate(:account)
-      expect(subject.uri_for(account)).to be_a(String)
-        .and start_with(domain)
-    end
-  end
-
-  describe '#activity_uri_for' do
-    context 'when given an account' do
-      it 'raises an exception' do
-        account = Fabricate(:account)
-        expect { subject.activity_uri_for(account) }.to raise_error(ArgumentError)
-      end
-    end
-
-    context 'when given a local activity' do
-      it 'returns a string starting with web domain' do
-        status = Fabricate(:status)
-        expect(subject.uri_for(status)).to be_a(String)
-          .and start_with(domain)
-      end
+      expect(subject.uri_for(account)).to be_a String
     end
   end
 
@@ -287,23 +254,6 @@ RSpec.describe ActivityPub::TagManager do
     end
   end
 
-  describe '#uris_to_local_accounts' do
-    it 'returns the expected local accounts' do
-      account = Fabricate(:account)
-      expect(subject.uris_to_local_accounts([subject.uri_for(account), instance_actor_url])).to contain_exactly(account, Account.representative)
-    end
-
-    it 'does not return remote accounts' do
-      account = Fabricate(:account, uri: 'https://example.com/123', domain: 'example.com')
-      expect(subject.uris_to_local_accounts([subject.uri_for(account)])).to be_empty
-    end
-
-    it 'does not return an account for a local post' do
-      status = Fabricate(:status)
-      expect(subject.uris_to_local_accounts([subject.uri_for(status)])).to be_empty
-    end
-  end
-
   describe '#uri_to_resource' do
     it 'returns the local account' do
       account = Fabricate(:account)
diff --git a/spec/lib/admin/system_check/database_schema_check_spec.rb b/spec/lib/admin/system_check/database_schema_check_spec.rb
index 3dc5763f92..311d524956 100644
--- a/spec/lib/admin/system_check/database_schema_check_spec.rb
+++ b/spec/lib/admin/system_check/database_schema_check_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Admin::SystemCheck::DatabaseSchemaCheck do
     context 'when database needs migration' do
       before do
         context = instance_double(ActiveRecord::MigrationContext, needs_migration?: true)
-        allow(ActiveRecord::Base.connection_pool).to receive(:migration_context).and_return(context)
+        allow(ActiveRecord::Base.connection).to receive(:migration_context).and_return(context)
       end
 
       it 'returns false' do
@@ -24,7 +24,7 @@ RSpec.describe Admin::SystemCheck::DatabaseSchemaCheck do
     context 'when database does not need migration' do
       before do
         context = instance_double(ActiveRecord::MigrationContext, needs_migration?: false)
-        allow(ActiveRecord::Base.connection_pool).to receive(:migration_context).and_return(context)
+        allow(ActiveRecord::Base.connection).to receive(:migration_context).and_return(context)
       end
 
       it 'returns true' do
diff --git a/spec/lib/admin/system_check/software_version_check_spec.rb b/spec/lib/admin/system_check/software_version_check_spec.rb
index 75dc1a4732..8460d90668 100644
--- a/spec/lib/admin/system_check/software_version_check_spec.rb
+++ b/spec/lib/admin/system_check/software_version_check_spec.rb
@@ -27,10 +27,9 @@ RSpec.describe Admin::SystemCheck::SoftwareVersionCheck do
 
       context 'when checks are disabled' do
         around do |example|
-          original = Rails.configuration.x.mastodon.software_update_url
-          Rails.configuration.x.mastodon.software_update_url = ''
-          example.run
-          Rails.configuration.x.mastodon.software_update_url = original
+          ClimateControl.modify UPDATE_CHECK_URL: '' do
+            example.run
+          end
         end
 
         it 'returns true' do
diff --git a/spec/lib/annual_report/commonly_interacted_with_accounts_spec.rb b/spec/lib/annual_report/commonly_interacted_with_accounts_spec.rb
index 12bf3810db..e99d3cb4a7 100644
--- a/spec/lib/annual_report/commonly_interacted_with_accounts_spec.rb
+++ b/spec/lib/annual_report/commonly_interacted_with_accounts_spec.rb
@@ -21,27 +21,18 @@ RSpec.describe AnnualReport::CommonlyInteractedWithAccounts do
       let(:account) { Fabricate :account }
 
       let(:other_account) { Fabricate :account }
-      let(:most_other_account) { Fabricate :account }
 
       before do
         _other = Fabricate :status
-
         Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: other_account).id
         Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: other_account).id
-
-        Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: most_other_account).id
-        Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: most_other_account).id
-        Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: most_other_account).id
       end
 
       it 'builds a report for an account' do
         expect(subject.generate)
           .to include(
-            commonly_interacted_with_accounts: eq(
-              [
-                { account_id: most_other_account.id.to_s, count: 3 },
-                { account_id: other_account.id.to_s, count: 2 },
-              ]
+            commonly_interacted_with_accounts: contain_exactly(
+              include(account_id: other_account.id, count: 2)
             )
           )
       end
diff --git a/spec/lib/annual_report/most_reblogged_accounts_spec.rb b/spec/lib/annual_report/most_reblogged_accounts_spec.rb
index 956549c325..0280ba1992 100644
--- a/spec/lib/annual_report/most_reblogged_accounts_spec.rb
+++ b/spec/lib/annual_report/most_reblogged_accounts_spec.rb
@@ -21,26 +21,18 @@ RSpec.describe AnnualReport::MostRebloggedAccounts do
       let(:account) { Fabricate :account }
 
       let(:other_account) { Fabricate :account }
-      let(:most_other_account) { Fabricate :account }
 
       before do
         _other = Fabricate :status
         Fabricate :status, account: account, reblog: Fabricate(:status, account: other_account)
         Fabricate :status, account: account, reblog: Fabricate(:status, account: other_account)
-
-        Fabricate :status, account: account, reblog: Fabricate(:status, account: most_other_account)
-        Fabricate :status, account: account, reblog: Fabricate(:status, account: most_other_account)
-        Fabricate :status, account: account, reblog: Fabricate(:status, account: most_other_account)
       end
 
       it 'builds a report for an account' do
         expect(subject.generate)
           .to include(
-            most_reblogged_accounts: eq(
-              [
-                { account_id: most_other_account.id.to_s, count: 3 },
-                { account_id: other_account.id.to_s, count: 2 },
-              ]
+            most_reblogged_accounts: contain_exactly(
+              include(account_id: other_account.id, count: 2)
             )
           )
       end
diff --git a/spec/lib/annual_report/most_used_apps_spec.rb b/spec/lib/annual_report/most_used_apps_spec.rb
index ab7022ef20..d2fcecc4d8 100644
--- a/spec/lib/annual_report/most_used_apps_spec.rb
+++ b/spec/lib/annual_report/most_used_apps_spec.rb
@@ -20,23 +20,18 @@ RSpec.describe AnnualReport::MostUsedApps do
     context 'with an active account' do
       let(:account) { Fabricate :account }
 
-      let(:application) { Fabricate :application, name: 'App' }
-      let(:most_application) { Fabricate :application, name: 'Most App' }
+      let(:application) { Fabricate :application }
 
       before do
         _other = Fabricate :status
         Fabricate.times 2, :status, account: account, application: application
-        Fabricate.times 3, :status, account: account, application: most_application
       end
 
       it 'builds a report for an account' do
         expect(subject.generate)
           .to include(
-            most_used_apps: eq(
-              [
-                { name: most_application.name, count: 3 },
-                { name: application.name, count: 2 },
-              ]
+            most_used_apps: contain_exactly(
+              include(name: application.name, count: 2)
             )
           )
       end
diff --git a/spec/lib/annual_report/percentiles_spec.rb b/spec/lib/annual_report/percentiles_spec.rb
index 11df81cfb6..1d1df3166b 100644
--- a/spec/lib/annual_report/percentiles_spec.rb
+++ b/spec/lib/annual_report/percentiles_spec.rb
@@ -4,20 +4,17 @@ require 'rails_helper'
 
 RSpec.describe AnnualReport::Percentiles do
   describe '#generate' do
-    subject { described_class.new(account, year) }
-
-    let(:year) { Time.zone.now.year }
+    subject { described_class.new(account, Time.zone.now.year) }
 
     context 'with an inactive account' do
       let(:account) { Fabricate :account }
 
       it 'builds a report for an account' do
-        described_class.prepare(year)
-
         expect(subject.generate)
           .to include(
             percentiles: include(
-              statuses: 100
+              followers: 0,
+              statuses: 0
             )
           )
       end
@@ -28,15 +25,16 @@ RSpec.describe AnnualReport::Percentiles do
 
       before do
         Fabricate.times 2, :status # Others as `account`
+        Fabricate.times 2, :follow # Others as `target_account`
         Fabricate.times 2, :status, account: account
+        Fabricate.times 2, :follow, target_account: account
       end
 
       it 'builds a report for an account' do
-        described_class.prepare(year)
-
         expect(subject.generate)
           .to include(
             percentiles: include(
+              followers: 50,
               statuses: 50
             )
           )
diff --git a/spec/lib/annual_report/top_hashtags_spec.rb b/spec/lib/annual_report/top_hashtags_spec.rb
index b9cc9392ed..58a9152184 100644
--- a/spec/lib/annual_report/top_hashtags_spec.rb
+++ b/spec/lib/annual_report/top_hashtags_spec.rb
@@ -21,31 +21,20 @@ RSpec.describe AnnualReport::TopHashtags do
       let(:account) { Fabricate :account }
 
       let(:tag) { Fabricate :tag }
-      let(:most_tag) { Fabricate :tag }
 
       before do
         _other = Fabricate :status
-
         first = Fabricate :status, account: account
         first.tags << tag
-        first.tags << most_tag
-
         last = Fabricate :status, account: account
         last.tags << tag
-        last.tags << most_tag
-
-        middle = Fabricate :status, account: account
-        middle.tags << most_tag
       end
 
       it 'builds a report for an account' do
         expect(subject.generate)
           .to include(
-            top_hashtags: eq(
-              [
-                { name: most_tag.name, count: 3 },
-                { name: tag.name, count: 2 },
-              ]
+            top_hashtags: contain_exactly(
+              include(name: tag.name, count: 2)
             )
           )
       end
diff --git a/spec/lib/annual_report/top_statuses_spec.rb b/spec/lib/annual_report/top_statuses_spec.rb
index af29df1f65..b956b03973 100644
--- a/spec/lib/annual_report/top_statuses_spec.rb
+++ b/spec/lib/annual_report/top_statuses_spec.rb
@@ -39,9 +39,9 @@ RSpec.describe AnnualReport::TopStatuses do
         expect(subject.generate)
           .to include(
             top_statuses: include(
-              by_reblogs: reblogged_status.id.to_s,
-              by_favourites: favourited_status.id.to_s,
-              by_replies: replied_status.id.to_s
+              by_reblogs: reblogged_status.id,
+              by_favourites: favourited_status.id,
+              by_replies: replied_status.id
             )
           )
       end
diff --git a/spec/lib/annual_report_spec.rb b/spec/lib/annual_report_spec.rb
index fa898d3ac5..bd4d0f3387 100644
--- a/spec/lib/annual_report_spec.rb
+++ b/spec/lib/annual_report_spec.rb
@@ -13,13 +13,4 @@ RSpec.describe AnnualReport do
         .to change(GeneratedAnnualReport, :count).by(1)
     end
   end
-
-  describe '.prepare' do
-    before { Fabricate :status }
-
-    it 'generates records from source class which prepare data' do
-      expect { described_class.prepare(Time.current.year) }
-        .to change(AnnualReport::StatusesPerAccountCount, :count).by(1)
-    end
-  end
 end
diff --git a/spec/lib/domain_resource_spec.rb b/spec/lib/domain_resource_spec.rb
deleted file mode 100644
index 0d239fd9de..0000000000
--- a/spec/lib/domain_resource_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe DomainResource do
-  describe '#mx' do
-    subject { described_class.new(domain) }
-
-    let(:domain) { 'example.host' }
-    let(:exchange) { 'mx.host' }
-
-    before { configure_mx(domain: domain, exchange: exchange) }
-
-    it 'returns array of hostnames' do
-      expect(subject.mx)
-        .to eq([exchange])
-    end
-  end
-end
diff --git a/spec/lib/fasp/request_spec.rb b/spec/lib/fasp/request_spec.rb
deleted file mode 100644
index 5d81c09722..0000000000
--- a/spec/lib/fasp/request_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-require 'securerandom'
-
-RSpec.describe Fasp::Request do
-  include ProviderRequestHelper
-
-  subject { described_class.new(provider) }
-
-  let(:provider) do
-    Fabricate(:fasp_provider, base_url: 'https://reqprov.example.com/fasp')
-  end
-
-  shared_examples 'a provider request' do |method|
-    context 'when the response is signed by the provider' do
-      before do
-        stub_provider_request(provider, method:, path: '/test_path')
-      end
-
-      it "performs a signed #{method.to_s.upcase} request relative to the base_path of the fasp" do
-        subject.send(method, '/test_path')
-
-        expect(WebMock).to have_requested(method, 'https://reqprov.example.com/fasp/test_path')
-          .with(headers: {
-            'Signature' => /.+/,
-            'Signature-Input' => /.+/,
-          })
-      end
-    end
-
-    context 'when the response is not signed' do
-      before do
-        stub_request(method, 'https://reqprov.example.com/fasp/test_path')
-          .to_return(status: 200)
-      end
-
-      it 'raises an error' do
-        expect do
-          subject.send(method, '/test_path')
-        end.to raise_error(Mastodon::SignatureVerificationError)
-      end
-    end
-  end
-
-  describe '#get' do
-    include_examples 'a provider request', :get
-  end
-
-  describe '#post' do
-    include_examples 'a provider request', :post
-  end
-
-  describe '#delete' do
-    include_examples 'a provider request', :delete
-  end
-end
diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb
index b0b7505c8c..5ea0ba29a9 100644
--- a/spec/lib/feed_manager_spec.rb
+++ b/spec/lib/feed_manager_spec.rb
@@ -3,8 +3,6 @@
 require 'rails_helper'
 
 RSpec.describe FeedManager do
-  subject { described_class.instance }
-
   before do |example|
     unless example.metadata[:skip_stub]
       stub_const 'FeedManager::MAX_ITEMS', 10
@@ -35,26 +33,26 @@ RSpec.describe FeedManager do
       it 'returns false for followee\'s status' do
         status = Fabricate(:status, text: 'Hello world', account: alice)
         bob.follow!(alice)
-        expect(subject.filter?(:home, status, bob)).to be false
+        expect(described_class.instance.filter?(:home, status, bob)).to be false
       end
 
       it 'returns false for reblog by followee' do
         status = Fabricate(:status, text: 'Hello world', account: jeff)
         reblog = Fabricate(:status, reblog: status, account: alice)
         bob.follow!(alice)
-        expect(subject.filter?(:home, reblog, bob)).to be false
+        expect(described_class.instance.filter?(:home, reblog, bob)).to be false
       end
 
       it 'returns true for post from account who blocked me' do
         status = Fabricate(:status, text: 'Hello, World', account: alice)
         alice.block!(bob)
-        expect(subject.filter?(:home, status, bob)).to be true
+        expect(described_class.instance.filter?(:home, status, bob)).to be true
       end
 
       it 'returns true for post from blocked account' do
         status = Fabricate(:status, text: 'Hello, World', account: alice)
         bob.block!(alice)
-        expect(subject.filter?(:home, status, bob)).to be true
+        expect(described_class.instance.filter?(:home, status, bob)).to be true
       end
 
       it 'returns true for reblog by followee of blocked account' do
@@ -62,7 +60,7 @@ RSpec.describe FeedManager do
         reblog = Fabricate(:status, reblog: status, account: alice)
         bob.follow!(alice)
         bob.block!(jeff)
-        expect(subject.filter?(:home, reblog, bob)).to be true
+        expect(described_class.instance.filter?(:home, reblog, bob)).to be true
       end
 
       it 'returns true for reblog by followee of muted account' do
@@ -70,7 +68,7 @@ RSpec.describe FeedManager do
         reblog = Fabricate(:status, reblog: status, account: alice)
         bob.follow!(alice)
         bob.mute!(jeff)
-        expect(subject.filter?(:home, reblog, bob)).to be true
+        expect(described_class.instance.filter?(:home, reblog, bob)).to be true
       end
 
       it 'returns true for reblog by followee of someone who is blocking recipient' do
@@ -78,14 +76,14 @@ RSpec.describe FeedManager do
         reblog = Fabricate(:status, reblog: status, account: alice)
         bob.follow!(alice)
         jeff.block!(bob)
-        expect(subject.filter?(:home, reblog, bob)).to be true
+        expect(described_class.instance.filter?(:home, reblog, bob)).to be true
       end
 
       it 'returns true for reblog from account with reblogs disabled' do
         status = Fabricate(:status, text: 'Hello world', account: jeff)
         reblog = Fabricate(:status, reblog: status, account: alice)
         bob.follow!(alice, reblogs: false)
-        expect(subject.filter?(:home, reblog, bob)).to be true
+        expect(described_class.instance.filter?(:home, reblog, bob)).to be true
       end
 
       it 'returns false for reply by followee to another followee' do
@@ -93,49 +91,49 @@ RSpec.describe FeedManager do
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: alice)
         bob.follow!(alice)
         bob.follow!(jeff)
-        expect(subject.filter?(:home, reply, bob)).to be false
+        expect(described_class.instance.filter?(:home, reply, bob)).to be false
       end
 
       it 'returns false for reply by followee to recipient' do
         status = Fabricate(:status, text: 'Hello world', account: bob)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: alice)
         bob.follow!(alice)
-        expect(subject.filter?(:home, reply, bob)).to be false
+        expect(described_class.instance.filter?(:home, reply, bob)).to be false
       end
 
       it 'returns false for reply by followee to self' do
         status = Fabricate(:status, text: 'Hello world', account: alice)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: alice)
         bob.follow!(alice)
-        expect(subject.filter?(:home, reply, bob)).to be false
+        expect(described_class.instance.filter?(:home, reply, bob)).to be false
       end
 
       it 'returns true for reply by followee to non-followed account' do
         status = Fabricate(:status, text: 'Hello world', account: jeff)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: alice)
         bob.follow!(alice)
-        expect(subject.filter?(:home, reply, bob)).to be true
+        expect(described_class.instance.filter?(:home, reply, bob)).to be true
       end
 
       it 'returns true for the second reply by followee to a non-federated status' do
         reply        = Fabricate(:status, text: 'Reply 1', reply: true, account: alice)
         second_reply = Fabricate(:status, text: 'Reply 2', thread: reply, account: alice)
         bob.follow!(alice)
-        expect(subject.filter?(:home, second_reply, bob)).to be true
+        expect(described_class.instance.filter?(:home, second_reply, bob)).to be true
       end
 
       it 'returns false for status by followee mentioning another account' do
         bob.follow!(alice)
         jeff.follow!(alice)
         status = PostStatusService.new.call(alice, text: 'Hey @jeff')
-        expect(subject.filter?(:home, status, bob)).to be false
+        expect(described_class.instance.filter?(:home, status, bob)).to be false
       end
 
       it 'returns true for status by followee mentioning blocked account' do
         bob.block!(jeff)
         bob.follow!(alice)
         status = PostStatusService.new.call(alice, text: 'Hey @jeff')
-        expect(subject.filter?(:home, status, bob)).to be true
+        expect(described_class.instance.filter?(:home, status, bob)).to be true
       end
 
       it 'returns true for reblog of a personally blocked domain' do
@@ -143,19 +141,19 @@ RSpec.describe FeedManager do
         alice.follow!(jeff)
         status = Fabricate(:status, text: 'Hello world', account: bob)
         reblog = Fabricate(:status, reblog: status, account: jeff)
-        expect(subject.filter?(:home, reblog, alice)).to be true
+        expect(described_class.instance.filter?(:home, reblog, alice)).to be true
       end
 
       it 'returns true for German post when follow is set to English only' do
         alice.follow!(bob, languages: %w(en))
         status = Fabricate(:status, text: 'Hallo Welt', account: bob, language: 'de')
-        expect(subject.filter?(:home, status, alice)).to be true
+        expect(described_class.instance.filter?(:home, status, alice)).to be true
       end
 
       it 'returns false for German post when follow is set to German' do
         alice.follow!(bob, languages: %w(de))
         status = Fabricate(:status, text: 'Hallo Welt', account: bob, language: 'de')
-        expect(subject.filter?(:home, status, alice)).to be false
+        expect(described_class.instance.filter?(:home, status, alice)).to be false
       end
 
       it 'returns true for post from followee on exclusive list' do
@@ -164,8 +162,8 @@ RSpec.describe FeedManager do
         list.accounts << bob
         allow(List).to receive(:where).and_return(list)
         status = Fabricate(:status, text: 'I post a lot', account: bob)
-        expect(subject.filter?(:home, status, alice)).to be true
-        expect(subject.filter(:home, status, alice)).to be :skip_home
+        expect(described_class.instance.filter?(:home, status, alice)).to be true
+        expect(described_class.instance.filter(:home, status, alice)).to be :skip_home
       end
 
       it 'returns true for reblog from followee on exclusive list' do
@@ -175,8 +173,8 @@ RSpec.describe FeedManager do
         allow(List).to receive(:where).and_return(list)
         status = Fabricate(:status, text: 'I post a lot', account: bob)
         reblog = Fabricate(:status, reblog: status, account: jeff)
-        expect(subject.filter?(:home, reblog, alice)).to be true
-        expect(subject.filter(:home, reblog, alice)).to be :skip_home
+        expect(described_class.instance.filter?(:home, reblog, alice)).to be true
+        expect(described_class.instance.filter(:home, reblog, alice)).to be :skip_home
       end
 
       it 'returns false for post from followee on non-exclusive list' do
@@ -184,7 +182,7 @@ RSpec.describe FeedManager do
         alice.follow!(bob)
         list.accounts << bob
         status = Fabricate(:status, text: 'I post a lot', account: bob)
-        expect(subject.filter?(:home, status, alice)).to be false
+        expect(described_class.instance.filter?(:home, status, alice)).to be false
       end
 
       it 'returns false for reblog from followee on non-exclusive list' do
@@ -193,7 +191,7 @@ RSpec.describe FeedManager do
         list.accounts << jeff
         status = Fabricate(:status, text: 'I post a lot', account: bob)
         reblog = Fabricate(:status, reblog: status, account: jeff)
-        expect(subject.filter?(:home, reblog, alice)).to be false
+        expect(described_class.instance.filter?(:home, reblog, alice)).to be false
       end
 
       it 'returns true for post from followee on exclusive antenna' do
@@ -202,7 +200,7 @@ RSpec.describe FeedManager do
         antenna.accounts << bob
         allow(Antenna).to receive(:where).and_return(antenna)
         status = Fabricate(:status, text: 'I post a lot', account: bob)
-        expect(subject.filter?(:home, status, alice)).to be true
+        expect(described_class.instance.filter?(:home, status, alice)).to be true
       end
 
       it 'returns true for reblog from followee on exclusive antenna' do
@@ -212,7 +210,7 @@ RSpec.describe FeedManager do
         allow(Antenna).to receive(:where).and_return(antenna)
         status = Fabricate(:status, text: 'I post a lot', account: bob)
         reblog = Fabricate(:status, reblog: status, account: jeff)
-        expect(subject.filter?(:home, reblog, alice)).to be true
+        expect(described_class.instance.filter?(:home, reblog, alice)).to be true
       end
 
       it 'returns false for post from followee on non-exclusive antenna' do
@@ -220,7 +218,7 @@ RSpec.describe FeedManager do
         alice.follow!(bob)
         antenna.accounts << bob
         status = Fabricate(:status, text: 'I post a lot', account: bob)
-        expect(subject.filter?(:home, status, alice)).to be false
+        expect(described_class.instance.filter?(:home, status, alice)).to be false
       end
 
       it 'returns false for reblog from followee on non-exclusive antenna' do
@@ -229,7 +227,7 @@ RSpec.describe FeedManager do
         antenna.accounts << jeff
         status = Fabricate(:status, text: 'I post a lot', account: bob)
         reblog = Fabricate(:status, reblog: status, account: jeff)
-        expect(subject.filter?(:home, reblog, alice)).to be false
+        expect(described_class.instance.filter?(:home, reblog, alice)).to be false
       end
     end
 
@@ -244,25 +242,14 @@ RSpec.describe FeedManager do
       it "returns false for followee's status" do
         status = Fabricate(:status, text: 'Hello world', account: alice)
 
-        expect(subject.filter?(:list, status, list)).to be false
+        expect(described_class.instance.filter?(:list, status, list)).to be false
       end
 
       it 'returns false for reblog by followee' do
         status = Fabricate(:status, text: 'Hello world', account: jeff)
         reblog = Fabricate(:status, reblog: status, account: alice)
 
-        expect(subject.filter?(:list, reblog, list)).to be false
-      end
-    end
-
-    context 'with stl list feed' do
-      let(:antenna) { Fabricate(:antenna, account: bob, insert_feeds: true, list_id: list.id, stl: true) }
-      let(:list) { Fabricate(:list, account: bob) }
-
-      it "returns false for followee's status" do
-        status = Fabricate(:status, text: 'Hello world', account: alice, visibility: :unlisted)
-
-        expect(subject.filter?(:list, status, list, stl_home: true)).to be false
+        expect(described_class.instance.filter?(:list, reblog, list)).to be false
       end
     end
 
@@ -270,27 +257,27 @@ RSpec.describe FeedManager do
       it 'returns true for status that mentions blocked account' do
         bob.block!(jeff)
         status = PostStatusService.new.call(alice, text: 'Hey @jeff')
-        expect(subject.filter?(:mentions, status, bob)).to be true
+        expect(described_class.instance.filter?(:mentions, status, bob)).to be true
       end
 
       it 'returns true for status that replies to a blocked account' do
         status = Fabricate(:status, text: 'Hello world', account: jeff)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: alice)
         bob.block!(jeff)
-        expect(subject.filter?(:mentions, reply, bob)).to be true
+        expect(described_class.instance.filter?(:mentions, reply, bob)).to be true
       end
 
       it 'returns false for status by limited account who recipient is not following' do
         status = Fabricate(:status, text: 'Hello world', account: alice)
         alice.silence!
-        expect(subject.filter?(:mentions, status, bob)).to be false
+        expect(described_class.instance.filter?(:mentions, status, bob)).to be false
       end
 
       it 'returns false for status by followed limited account' do
         status = Fabricate(:status, text: 'Hello world', account: alice)
         alice.silence!
         bob.follow!(alice)
-        expect(subject.filter?(:mentions, status, bob)).to be false
+        expect(described_class.instance.filter?(:mentions, status, bob)).to be false
       end
     end
   end
@@ -302,7 +289,7 @@ RSpec.describe FeedManager do
       members = Array.new(described_class::MAX_ITEMS) { |count| [count, count] }
       redis.zadd("feed:home:#{account.id}", members)
 
-      subject.push_to_home(account, status)
+      described_class.instance.push_to_home(account, status)
 
       expect(redis.zcard("feed:home:#{account.id}")).to eq described_class::MAX_ITEMS
     end
@@ -313,7 +300,7 @@ RSpec.describe FeedManager do
         reblogged = Fabricate(:status)
         reblog = Fabricate(:status, reblog: reblogged)
 
-        expect(subject.push_to_home(account, reblog)).to be true
+        expect(described_class.instance.push_to_home(account, reblog)).to be true
       end
 
       it 'does not save a new reblog of a recent status' do
@@ -321,9 +308,9 @@ RSpec.describe FeedManager do
         reblogged = Fabricate(:status)
         reblog = Fabricate(:status, reblog: reblogged)
 
-        subject.push_to_home(account, reblogged)
+        described_class.instance.push_to_home(account, reblogged)
 
-        expect(subject.push_to_home(account, reblog)).to be false
+        expect(described_class.instance.push_to_home(account, reblog)).to be false
       end
 
       it 'saves a new reblog of an old status' do
@@ -331,14 +318,14 @@ RSpec.describe FeedManager do
         reblogged = Fabricate(:status)
         reblog = Fabricate(:status, reblog: reblogged)
 
-        subject.push_to_home(account, reblogged)
+        described_class.instance.push_to_home(account, reblogged)
 
         # Fill the feed with intervening statuses
         described_class::REBLOG_FALLOFF.times do
-          subject.push_to_home(account, Fabricate(:status))
+          described_class.instance.push_to_home(account, Fabricate(:status))
         end
 
-        expect(subject.push_to_home(account, reblog)).to be true
+        expect(described_class.instance.push_to_home(account, reblog)).to be true
       end
 
       it 'does not save a new reblog of a recently-reblogged status' do
@@ -347,10 +334,10 @@ RSpec.describe FeedManager do
         reblogs = Array.new(2) { Fabricate(:status, reblog: reblogged) }
 
         # The first reblog will be accepted
-        subject.push_to_home(account, reblogs.first)
+        described_class.instance.push_to_home(account, reblogs.first)
 
         # The second reblog should be ignored
-        expect(subject.push_to_home(account, reblogs.last)).to be false
+        expect(described_class.instance.push_to_home(account, reblogs.last)).to be false
       end
 
       it 'saves a new reblog of a recently-reblogged status when previous reblog has been deleted' do
@@ -359,15 +346,15 @@ RSpec.describe FeedManager do
         old_reblog = Fabricate(:status, reblog: reblogged)
 
         # The first reblog should be accepted
-        expect(subject.push_to_home(account, old_reblog)).to be true
+        expect(described_class.instance.push_to_home(account, old_reblog)).to be true
 
         # The first reblog should be successfully removed
-        expect(subject.unpush_from_home(account, old_reblog)).to be true
+        expect(described_class.instance.unpush_from_home(account, old_reblog)).to be true
 
         reblog = Fabricate(:status, reblog: reblogged)
 
         # The second reblog should be accepted
-        expect(subject.push_to_home(account, reblog)).to be true
+        expect(described_class.instance.push_to_home(account, reblog)).to be true
       end
 
       it 'does not save a new reblog of a multiply-reblogged-then-unreblogged status' do
@@ -376,14 +363,14 @@ RSpec.describe FeedManager do
         reblogs = Array.new(3) { Fabricate(:status, reblog: reblogged) }
 
         # Accept the reblogs
-        subject.push_to_home(account, reblogs[0])
-        subject.push_to_home(account, reblogs[1])
+        described_class.instance.push_to_home(account, reblogs[0])
+        described_class.instance.push_to_home(account, reblogs[1])
 
         # Unreblog the first one
-        subject.unpush_from_home(account, reblogs[0])
+        described_class.instance.unpush_from_home(account, reblogs[0])
 
         # The last reblog should still be ignored
-        expect(subject.push_to_home(account, reblogs.last)).to be false
+        expect(described_class.instance.push_to_home(account, reblogs.last)).to be false
       end
 
       it 'saves a new reblog of a long-ago-reblogged status' do
@@ -392,15 +379,15 @@ RSpec.describe FeedManager do
         reblogs = Array.new(2) { Fabricate(:status, reblog: reblogged) }
 
         # The first reblog will be accepted
-        subject.push_to_home(account, reblogs.first)
+        described_class.instance.push_to_home(account, reblogs.first)
 
         # Fill the feed with intervening statuses
         described_class::REBLOG_FALLOFF.times do
-          subject.push_to_home(account, Fabricate(:status))
+          described_class.instance.push_to_home(account, Fabricate(:status))
         end
 
         # The second reblog should also be accepted
-        expect(subject.push_to_home(account, reblogs.last)).to be true
+        expect(described_class.instance.push_to_home(account, reblogs.last)).to be true
       end
     end
 
@@ -408,23 +395,23 @@ RSpec.describe FeedManager do
       account = Fabricate(:account)
       reblog = Fabricate(:status)
       status = Fabricate(:status, reblog: reblog)
-      subject.push_to_home(account, status)
+      described_class.instance.push_to_home(account, status)
 
-      expect(subject.push_to_home(account, reblog)).to be false
+      expect(described_class.instance.push_to_home(account, reblog)).to be false
     end
   end
 
   describe '#push_to_list' do
-    let(:list_owner) { Fabricate(:account, username: 'list_owner') }
+    let(:owner) { Fabricate(:account, username: 'owner') }
     let(:alice) { Fabricate(:account, username: 'alice') }
     let(:bob)   { Fabricate(:account, username: 'bob') }
     let(:eve)   { Fabricate(:account, username: 'eve') }
-    let(:list)  { Fabricate(:list, account: list_owner) }
+    let(:list)  { Fabricate(:list, account: owner) }
 
     before do
-      list_owner.follow!(alice)
-      list_owner.follow!(bob)
-      list_owner.follow!(eve)
+      owner.follow!(alice)
+      owner.follow!(bob)
+      owner.follow!(eve)
 
       list.accounts << alice
       list.accounts << bob
@@ -433,9 +420,9 @@ RSpec.describe FeedManager do
     it "does not push when the given status's reblog is already inserted" do
       reblog = Fabricate(:status)
       status = Fabricate(:status, reblog: reblog)
-      subject.push_to_list(list, status)
+      described_class.instance.push_to_list(list, status)
 
-      expect(subject.push_to_list(list, reblog)).to be false
+      expect(described_class.instance.push_to_list(list, reblog)).to be false
     end
 
     context 'when replies policy is set to no replies' do
@@ -445,19 +432,19 @@ RSpec.describe FeedManager do
 
       it 'pushes statuses that are not replies' do
         status = Fabricate(:status, text: 'Hello world', account: bob)
-        expect(subject.push_to_list(list, status)).to be true
+        expect(described_class.instance.push_to_list(list, status)).to be true
       end
 
       it 'pushes statuses that are replies to list owner' do
-        status = Fabricate(:status, text: 'Hello world', account: list_owner)
+        status = Fabricate(:status, text: 'Hello world', account: owner)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(subject.push_to_list(list, reply)).to be true
+        expect(described_class.instance.push_to_list(list, reply)).to be true
       end
 
       it 'does not push replies to another member of the list' do
         status = Fabricate(:status, text: 'Hello world', account: alice)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(subject.push_to_list(list, reply)).to be false
+        expect(described_class.instance.push_to_list(list, reply)).to be false
       end
     end
 
@@ -468,25 +455,25 @@ RSpec.describe FeedManager do
 
       it 'pushes statuses that are not replies' do
         status = Fabricate(:status, text: 'Hello world', account: bob)
-        expect(subject.push_to_list(list, status)).to be true
+        expect(described_class.instance.push_to_list(list, status)).to be true
       end
 
       it 'pushes statuses that are replies to list owner' do
-        status = Fabricate(:status, text: 'Hello world', account: list_owner)
+        status = Fabricate(:status, text: 'Hello world', account: owner)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(subject.push_to_list(list, reply)).to be true
+        expect(described_class.instance.push_to_list(list, reply)).to be true
       end
 
       it 'pushes replies to another member of the list' do
         status = Fabricate(:status, text: 'Hello world', account: alice)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(subject.push_to_list(list, reply)).to be true
+        expect(described_class.instance.push_to_list(list, reply)).to be true
       end
 
       it 'does not push replies to someone not a member of the list' do
         status = Fabricate(:status, text: 'Hello world', account: eve)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(subject.push_to_list(list, reply)).to be false
+        expect(described_class.instance.push_to_list(list, reply)).to be false
       end
     end
 
@@ -497,25 +484,25 @@ RSpec.describe FeedManager do
 
       it 'pushes statuses that are not replies' do
         status = Fabricate(:status, text: 'Hello world', account: bob)
-        expect(subject.push_to_list(list, status)).to be true
+        expect(described_class.instance.push_to_list(list, status)).to be true
       end
 
       it 'pushes statuses that are replies to list owner' do
-        status = Fabricate(:status, text: 'Hello world', account: list_owner)
+        status = Fabricate(:status, text: 'Hello world', account: owner)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(subject.push_to_list(list, reply)).to be true
+        expect(described_class.instance.push_to_list(list, reply)).to be true
       end
 
       it 'pushes replies to another member of the list' do
         status = Fabricate(:status, text: 'Hello world', account: alice)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(subject.push_to_list(list, reply)).to be true
+        expect(described_class.instance.push_to_list(list, reply)).to be true
       end
 
       it 'pushes replies to someone not a member of the list' do
         status = Fabricate(:status, text: 'Hello world', account: eve)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(subject.push_to_list(list, reply)).to be true
+        expect(described_class.instance.push_to_list(list, reply)).to be true
       end
     end
   end
@@ -525,9 +512,9 @@ RSpec.describe FeedManager do
       account = Fabricate(:account, id: 0)
       reblog = Fabricate(:status)
       status = Fabricate(:status, reblog: reblog)
-      subject.push_to_home(account, status)
+      described_class.instance.push_to_home(account, status)
 
-      subject.merge_into_home(account, reblog.account)
+      described_class.instance.merge_into_home(account, reblog.account)
 
       expect(redis.zscore('feed:home:0', reblog.id)).to be_nil
     end
@@ -540,14 +527,14 @@ RSpec.describe FeedManager do
       reblogged = Fabricate(:status)
       status    = Fabricate(:status, reblog: reblogged)
 
-      subject.push_to_home(receiver, reblogged)
-      described_class::REBLOG_FALLOFF.times { subject.push_to_home(receiver, Fabricate(:status)) }
-      subject.push_to_home(receiver, status)
+      described_class.instance.push_to_home(receiver, reblogged)
+      described_class::REBLOG_FALLOFF.times { described_class.instance.push_to_home(receiver, Fabricate(:status)) }
+      described_class.instance.push_to_home(receiver, status)
 
       # The reblogging status should show up under normal conditions.
       expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s)
 
-      subject.unpush_from_home(receiver, status)
+      described_class.instance.unpush_from_home(receiver, status)
 
       # Restore original status
       expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to_not include(status.id.to_s)
@@ -558,12 +545,12 @@ RSpec.describe FeedManager do
       reblogged = Fabricate(:status)
       status    = Fabricate(:status, reblog: reblogged)
 
-      subject.push_to_home(receiver, status)
+      described_class.instance.push_to_home(receiver, status)
 
       # The reblogging status should show up under normal conditions.
       expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [status.id.to_s]
 
-      subject.unpush_from_home(receiver, status)
+      described_class.instance.unpush_from_home(receiver, status)
 
       expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to be_empty
     end
@@ -573,14 +560,14 @@ RSpec.describe FeedManager do
       reblogs   = Array.new(3) { Fabricate(:status, reblog: reblogged) }
 
       reblogs.each do |reblog|
-        subject.push_to_home(receiver, reblog)
+        described_class.instance.push_to_home(receiver, reblog)
       end
 
       # The reblogging status should show up under normal conditions.
       expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [reblogs.first.id.to_s]
 
       reblogs[0...-1].each do |reblog|
-        subject.unpush_from_home(receiver, reblog)
+        described_class.instance.unpush_from_home(receiver, reblog)
       end
 
       expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [reblogs.last.id.to_s]
@@ -589,10 +576,10 @@ RSpec.describe FeedManager do
     it 'sends push updates' do
       status = Fabricate(:status)
 
-      subject.push_to_home(receiver, status)
+      described_class.instance.push_to_home(receiver, status)
 
       allow(redis).to receive_messages(publish: nil)
-      subject.unpush_from_home(receiver, status)
+      described_class.instance.unpush_from_home(receiver, status)
 
       deletion = Oj.dump(event: :delete, payload: status.id.to_s)
       expect(redis).to have_received(:publish).with("timeline:#{receiver.id}", deletion)
@@ -606,9 +593,9 @@ RSpec.describe FeedManager do
     it 'leaves a tagged status' do
       status = Fabricate(:status)
       status.tags << tag
-      subject.push_to_home(receiver, status)
+      described_class.instance.push_to_home(receiver, status)
 
-      subject.unmerge_tag_from_home(tag, receiver)
+      described_class.instance.unmerge_tag_from_home(tag, receiver)
 
       expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to_not include(status.id.to_s)
     end
@@ -619,9 +606,9 @@ RSpec.describe FeedManager do
 
       status = Fabricate(:status, account: followee)
       status.tags << tag
-      subject.push_to_home(receiver, status)
+      described_class.instance.push_to_home(receiver, status)
 
-      subject.unmerge_tag_from_home(tag, receiver)
+      described_class.instance.unmerge_tag_from_home(tag, receiver)
 
       expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s)
     end
@@ -629,9 +616,9 @@ RSpec.describe FeedManager do
     it 'remains a tagged status written by receiver' do
       status = Fabricate(:status, account: receiver)
       status.tags << tag
-      subject.push_to_home(receiver, status)
+      described_class.instance.push_to_home(receiver, status)
 
-      subject.unmerge_tag_from_home(tag, receiver)
+      described_class.instance.unmerge_tag_from_home(tag, receiver)
 
       expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s)
     end
@@ -662,7 +649,7 @@ RSpec.describe FeedManager do
     end
 
     it 'correctly cleans the home timeline' do
-      subject.clear_from_home(account, target_account)
+      described_class.instance.clear_from_home(account, target_account)
 
       expect(redis.zrange("feed:home:#{account.id}", 0, -1)).to eq [status_from_followed_account_first.id.to_s, status_from_followed_account_next.id.to_s]
     end
diff --git a/spec/lib/link_details_extractor_spec.rb b/spec/lib/link_details_extractor_spec.rb
index cb072c4870..d8d9db0ad1 100644
--- a/spec/lib/link_details_extractor_spec.rb
+++ b/spec/lib/link_details_extractor_spec.rb
@@ -49,8 +49,7 @@ RSpec.describe LinkDetailsExtractor do
       <html lang="en">
       <head>
         <title>Man bites dog</title>
-        <meta name="descripTION" content="A dog&#39;s tale">
-        <link rel="pretty IcoN" href="/favicon.ico">
+        <meta name="description" content="A dog&#39;s tale">
       </head>
       </html>
     HTML
@@ -60,8 +59,7 @@ RSpec.describe LinkDetailsExtractor do
         .to have_attributes(
           title: eq('Man bites dog'),
           description: eq("A dog's tale"),
-          language: eq('en'),
-          icon: eq('https://example.com/favicon.ico')
+          language: eq('en')
         )
     end
   end
@@ -249,44 +247,6 @@ RSpec.describe LinkDetailsExtractor do
         expect(subject.author_name).to eq 'Author 1, Author 2'
       end
     end
-
-    context 'with embedded arrays' do
-      let(:ld_json) do
-        {
-          '@context' => 'https://schema.org',
-          '@type' => 'NewsArticle',
-          'headline' => 'A lot of authors',
-          'description' => 'But we decided to cram them into one',
-          'author' => [[{
-            '@type' => 'Person',
-            'name' => ['Author 1'],
-          }]],
-          'publisher' => [[{
-            '@type' => 'NewsMediaOrganization',
-            'name' => 'Pet News',
-            'url' => 'https://example.com',
-          }]],
-        }.to_json
-      end
-      let(:html) { <<~HTML }
-        <!doctype html>
-        <html>
-        <body>
-          <script type="application/ld+json">
-            #{ld_json}
-          </script>
-        </body>
-        </html>
-      HTML
-
-      it 'gives correct author_name' do
-        expect(subject.author_name).to eq 'Author 1'
-      end
-
-      it 'gives provider_name' do
-        expect(subject.provider_name).to eq 'Pet News'
-      end
-    end
   end
 
   context 'when Open Graph protocol data is present' do
@@ -296,7 +256,7 @@ RSpec.describe LinkDetailsExtractor do
       <head>
         <meta property="og:url" content="https://example.com/dog.html">
         <meta property="og:title" content="Man bites dog">
-        <meta property="OG:description" content="A dog's tale">
+        <meta property="og:description" content="A dog's tale">
         <meta property="article:published_time" content="2022-01-31T19:53:00+00:00">
         <meta property="og:author" content="Charlie Brown">
         <meta property="og:locale" content="en">
diff --git a/spec/lib/mastodon/cli/maintenance_spec.rb b/spec/lib/mastodon/cli/maintenance_spec.rb
index 3e8eb9c360..6a15677f43 100644
--- a/spec/lib/mastodon/cli/maintenance_spec.rb
+++ b/spec/lib/mastodon/cli/maintenance_spec.rb
@@ -89,8 +89,10 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :accounts, name: :index_accounts_on_username_and_domain_lower
-          duplicate_record(:account, username: duplicate_account_username, domain: duplicate_account_domain)
-          duplicate_record(:account, username: duplicate_account_username, domain: nil)
+          _remote_account = Fabricate(:account, username: duplicate_account_username, domain: duplicate_account_domain)
+          _remote_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: duplicate_account_domain).save(validate: false)
+          _local_account = Fabricate(:account, username: duplicate_account_username, domain: nil)
+          _local_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: nil).save(validate: false)
         end
 
         def choose_local_account_to_keep
@@ -125,7 +127,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :users, :email
-          duplicate_record(:user, email: duplicate_email)
+          Fabricate(:user, email: duplicate_email)
+          Fabricate.build(:user, email: duplicate_email).save(validate: false)
         end
       end
 
@@ -153,7 +156,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :users, :confirmation_token
-          duplicate_record(:user, confirmation_token: duplicate_confirmation_token)
+          Fabricate(:user, confirmation_token: duplicate_confirmation_token)
+          Fabricate.build(:user, confirmation_token: duplicate_confirmation_token).save(validate: false)
         end
       end
 
@@ -181,7 +185,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :users, :reset_password_token
-          duplicate_record(:user, reset_password_token: duplicate_reset_password_token)
+          Fabricate(:user, reset_password_token: duplicate_reset_password_token)
+          Fabricate.build(:user, reset_password_token: duplicate_reset_password_token).save(validate: false)
         end
       end
 
@@ -209,7 +214,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :account_domain_blocks, [:account_id, :domain]
-          duplicate_record(:account_domain_block, account: account, domain: duplicate_domain)
+          Fabricate(:account_domain_block, account: account, domain: duplicate_domain)
+          Fabricate.build(:account_domain_block, account: account, domain: duplicate_domain).save(validate: false)
         end
       end
 
@@ -238,7 +244,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :announcement_reactions, [:account_id, :announcement_id, :name]
-          duplicate_record(:announcement_reaction, account: account, announcement: announcement, name: name)
+          Fabricate(:announcement_reaction, account: account, announcement: announcement, name: name)
+          Fabricate.build(:announcement_reaction, account: account, announcement: announcement, name: name).save(validate: false)
         end
       end
 
@@ -265,7 +272,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :conversations, :uri
-          duplicate_record(:conversation, uri: uri)
+          Fabricate(:conversation, uri: uri)
+          Fabricate.build(:conversation, uri: uri).save(validate: false)
         end
       end
 
@@ -293,7 +301,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :custom_emojis, [:shortcode, :domain]
-          duplicate_record(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain)
+          Fabricate(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain)
+          Fabricate.build(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain).save(validate: false)
         end
       end
 
@@ -320,7 +329,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :custom_emoji_categories, :name
-          duplicate_record(:custom_emoji_category, name: duplicate_name)
+          Fabricate(:custom_emoji_category, name: duplicate_name)
+          Fabricate.build(:custom_emoji_category, name: duplicate_name).save(validate: false)
         end
       end
 
@@ -347,7 +357,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :domain_allows, :domain
-          duplicate_record(:domain_allow, domain: domain)
+          Fabricate(:domain_allow, domain: domain)
+          Fabricate.build(:domain_allow, domain: domain).save(validate: false)
         end
       end
 
@@ -374,7 +385,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :domain_blocks, :domain
-          duplicate_record(:domain_block, domain: domain)
+          Fabricate(:domain_block, domain: domain)
+          Fabricate.build(:domain_block, domain: domain).save(validate: false)
         end
       end
 
@@ -401,7 +413,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :email_domain_blocks, :domain
-          duplicate_record(:email_domain_block, domain: domain)
+          Fabricate(:email_domain_block, domain: domain)
+          Fabricate.build(:email_domain_block, domain: domain).save(validate: false)
         end
       end
 
@@ -428,7 +441,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :media_attachments, :shortcode
-          duplicate_record(:media_attachment, shortcode: shortcode)
+          Fabricate(:media_attachment, shortcode: shortcode)
+          Fabricate.build(:media_attachment, shortcode: shortcode).save(validate: false)
         end
       end
 
@@ -455,7 +469,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :preview_cards, :url
-          duplicate_record(:preview_card, url: url)
+          Fabricate(:preview_card, url: url)
+          Fabricate.build(:preview_card, url: url).save(validate: false)
         end
       end
 
@@ -515,7 +530,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :tags, name: 'index_tags_on_name_lower_btree'
-          duplicate_record(:tag, name: name)
+          Fabricate(:tag, name: name)
+          Fabricate.build(:tag, name: name).save(validate: false)
         end
       end
 
@@ -542,7 +558,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :webauthn_credentials, :external_id
-          duplicate_record(:webauthn_credential, external_id: external_id)
+          Fabricate(:webauthn_credential, external_id: external_id)
+          Fabricate.build(:webauthn_credential, external_id: external_id).save(validate: false)
         end
       end
 
@@ -569,15 +586,11 @@ RSpec.describe Mastodon::CLI::Maintenance do
 
         def prepare_duplicate_data
           ActiveRecord::Base.connection.remove_index :webhooks, :url
-          duplicate_record(:webhook, url: url)
+          Fabricate(:webhook, url: url)
+          Fabricate.build(:webhook, url: url).save(validate: false)
         end
       end
 
-      def duplicate_record(fabricator, options = {})
-        Fabricate(fabricator, options)
-        Fabricate.build(fabricator, options).save(validate: false)
-      end
-
       def agree_to_backup_warning
         allow(cli.shell)
           .to receive(:yes?)
diff --git a/spec/lib/mastodon/feature_spec.rb b/spec/lib/mastodon/feature_spec.rb
deleted file mode 100644
index f8236d8959..0000000000
--- a/spec/lib/mastodon/feature_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Mastodon::Feature do
-  describe '::testing_only_enabled?' do
-    subject { described_class.testing_only_enabled? }
-
-    it { is_expected.to be true }
-  end
-
-  describe '::unspecified_feature_enabled?' do
-    context 'when example is not tagged with a feature' do
-      subject { described_class.unspecified_feature_enabled? }
-
-      it { is_expected.to be false }
-    end
-
-    context 'when example is tagged with a feature', feature: 'unspecified_feature' do
-      subject { described_class.unspecified_feature_enabled? }
-
-      it { is_expected.to be true }
-    end
-  end
-end
diff --git a/spec/lib/mastodon/middleware/prometheus_queue_time_spec.rb b/spec/lib/mastodon/middleware/prometheus_queue_time_spec.rb
deleted file mode 100644
index eaab93772d..0000000000
--- a/spec/lib/mastodon/middleware/prometheus_queue_time_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-require 'prometheus_exporter'
-require 'prometheus_exporter/middleware'
-require 'mastodon/middleware/prometheus_queue_time'
-
-RSpec.describe Mastodon::Middleware::PrometheusQueueTime do
-  subject { described_class.new(app, client:) }
-
-  let(:app) do
-    proc { |_env| [200, {}, 'OK'] }
-  end
-  let(:client) do
-    instance_double(PrometheusExporter::Client, send_json: true)
-  end
-
-  describe '#call' do
-    let(:env) do
-      {
-        'HTTP_X_REQUEST_START' => "t=#{(Time.now.to_f * 1000).to_i}",
-      }
-    end
-
-    it 'reports a queue time to the client' do
-      subject.call(env)
-
-      expect(client).to have_received(:send_json)
-        .with(hash_including(queue_time: instance_of(Float)))
-    end
-  end
-end
diff --git a/spec/lib/plain_text_formatter_spec.rb b/spec/lib/plain_text_formatter_spec.rb
index 4683e15093..b22f473d0c 100644
--- a/spec/lib/plain_text_formatter_spec.rb
+++ b/spec/lib/plain_text_formatter_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe PlainTextFormatter do
     subject { described_class.new(status.text, status.local?).to_s }
 
     context 'when status is local' do
-      let(:status) { Fabricate.build(:status, text: '<p>a text by a nerd who uses an HTML tag in text</p>', uri: nil) }
+      let(:status) { Fabricate(:status, text: '<p>a text by a nerd who uses an HTML tag in text</p>', uri: nil) }
 
       it 'returns the raw text' do
         expect(subject).to eq '<p>a text by a nerd who uses an HTML tag in text</p>'
@@ -15,10 +15,10 @@ RSpec.describe PlainTextFormatter do
     end
 
     context 'when status is remote' do
-      let(:remote_account) { Fabricate.build(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') }
+      let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') }
 
       context 'when text contains inline HTML tags' do
-        let(:status) { Fabricate.build(:status, account: remote_account, text: '<b>Lorem</b> <em>ipsum</em>') }
+        let(:status) { Fabricate(:status, account: remote_account, text: '<b>Lorem</b> <em>ipsum</em>') }
 
         it 'strips the tags' do
           expect(subject).to eq 'Lorem ipsum'
@@ -26,7 +26,7 @@ RSpec.describe PlainTextFormatter do
       end
 
       context 'when text contains <p> tags' do
-        let(:status) { Fabricate.build(:status, account: remote_account, text: '<p>Lorem</p><p>ipsum</p>') }
+        let(:status) { Fabricate(:status, account: remote_account, text: '<p>Lorem</p><p>ipsum</p>') }
 
         it 'inserts a newline' do
           expect(subject).to eq "Lorem\nipsum"
@@ -34,7 +34,7 @@ RSpec.describe PlainTextFormatter do
       end
 
       context 'when text contains a single <br> tag' do
-        let(:status) { Fabricate.build(:status, account: remote_account, text: 'Lorem<br>ipsum') }
+        let(:status) { Fabricate(:status, account: remote_account, text: 'Lorem<br>ipsum') }
 
         it 'inserts a newline' do
           expect(subject).to eq "Lorem\nipsum"
@@ -42,7 +42,7 @@ RSpec.describe PlainTextFormatter do
       end
 
       context 'when text contains consecutive <br> tag' do
-        let(:status) { Fabricate.build(:status, account: remote_account, text: 'Lorem<br><br><br>ipsum') }
+        let(:status) { Fabricate(:status, account: remote_account, text: 'Lorem<br><br><br>ipsum') }
 
         it 'inserts a single newline' do
           expect(subject).to eq "Lorem\nipsum"
@@ -50,7 +50,7 @@ RSpec.describe PlainTextFormatter do
       end
 
       context 'when text contains HTML entity' do
-        let(:status) { Fabricate.build(:status, account: remote_account, text: 'Lorem &amp; ipsum &#x2764;') }
+        let(:status) { Fabricate(:status, account: remote_account, text: 'Lorem &amp; ipsum &#x2764;') }
 
         it 'unescapes the entity' do
           expect(subject).to eq 'Lorem & ipsum ❤'
@@ -58,7 +58,7 @@ RSpec.describe PlainTextFormatter do
       end
 
       context 'when text contains <script> tag' do
-        let(:status) { Fabricate.build(:status, account: remote_account, text: 'Lorem <script> alert("Booh!") </script>ipsum') }
+        let(:status) { Fabricate(:status, account: remote_account, text: 'Lorem <script> alert("Booh!") </script>ipsum') }
 
         it 'strips the tag and its contents' do
           expect(subject).to eq 'Lorem ipsum'
@@ -66,7 +66,7 @@ RSpec.describe PlainTextFormatter do
       end
 
       context 'when text contains an HTML comment tags' do
-        let(:status) { Fabricate.build(:status, account: remote_account, text: 'Lorem <!-- Booh! -->ipsum') }
+        let(:status) { Fabricate(:status, account: remote_account, text: 'Lorem <!-- Booh! -->ipsum') }
 
         it 'strips the comment' do
           expect(subject).to eq 'Lorem ipsum'
@@ -74,7 +74,7 @@ RSpec.describe PlainTextFormatter do
       end
 
       context 'when text contains HTML ruby tags' do
-        let(:status) { Fabricate.build(:status, account: remote_account, text: '<p>Lorem <ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby> ipsum</p>') }
+        let(:status) { Fabricate(:status, account: remote_account, text: '<p>Lorem <ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby> ipsum</p>') }
 
         it 'strips the comment' do
           expect(subject).to eq 'Lorem 明日 (Ashita) ipsum'
diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb
index 79c400e098..549362d1bf 100644
--- a/spec/lib/request_spec.rb
+++ b/spec/lib/request_spec.rb
@@ -4,9 +4,7 @@ require 'rails_helper'
 require 'securerandom'
 
 RSpec.describe Request do
-  subject { described_class.new(:get, 'http://example.com', **options) }
-
-  let(:options) { {} }
+  subject { described_class.new(:get, 'http://example.com') }
 
   describe '#headers' do
     it 'returns user agent' do
@@ -41,8 +39,8 @@ RSpec.describe Request do
   end
 
   describe '#perform' do
-    context 'with valid host and non-persistent connection' do
-      before { stub_request(:get, 'http://example.com').to_return(body: 'lorem ipsum') }
+    context 'with valid host' do
+      before { stub_request(:get, 'http://example.com') }
 
       it 'executes a HTTP request' do
         expect { |block| subject.perform(&block) }.to yield_control
@@ -69,9 +67,9 @@ RSpec.describe Request do
         expect(subject.send(:http_client)).to have_received(:close)
       end
 
-      it 'yields response' do
+      it 'returns response which implements body_with_limit' do
         subject.perform do |response|
-          expect(response.body_with_limit).to eq 'lorem ipsum'
+          expect(response).to respond_to :body_with_limit
         end
       end
     end
@@ -116,43 +114,6 @@ RSpec.describe Request do
         expect { subject.perform }.to raise_error Mastodon::ValidationError
       end
     end
-
-    context 'with persistent connection' do
-      before { stub_request(:get, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes)) }
-
-      let(:http_client) { described_class.http_client.persistent('http://example.com') }
-      let(:options) { { http_client: http_client } }
-
-      it 'leaves connection open after completely consumed response' do
-        allow(http_client).to receive(:close)
-
-        subject.perform { |response| response.truncated_body(3.megabytes) }
-
-        expect(http_client).to_not have_received(:close)
-      end
-
-      it 'leaves connection open after nearly consumed response' do
-        allow(http_client).to receive(:close)
-
-        subject.perform { |response| response.truncated_body(1.8.megabytes) }
-
-        expect(http_client).to_not have_received(:close)
-      end
-
-      it 'closes connection after unconsumed response' do
-        allow(http_client).to receive(:close)
-
-        subject.perform
-
-        expect(http_client).to have_received(:close)
-      end
-
-      it 'yields response' do
-        subject.perform do |response|
-          expect(response.body_with_limit(2.megabytes).size).to eq 2.megabytes
-        end
-      end
-    end
   end
 
   describe "response's body_with_limit method" do
diff --git a/spec/lib/sanitize/config_spec.rb b/spec/lib/sanitize/config_spec.rb
index b4c849c427..17b78a95f6 100644
--- a/spec/lib/sanitize/config_spec.rb
+++ b/spec/lib/sanitize/config_spec.rb
@@ -39,15 +39,15 @@ RSpec.describe Sanitize::Config do
     end
 
     it 'keeps a with href' do
-      expect(Sanitize.fragment('<a href="http://example.com">Test</a>', subject)).to eq '<a href="http://example.com" rel="nofollow noopener" target="_blank">Test</a>'
+      expect(Sanitize.fragment('<a href="http://example.com">Test</a>', subject)).to eq '<a href="http://example.com" rel="nofollow noopener noreferrer" target="_blank">Test</a>'
     end
 
     it 'keeps a with translate="no"' do
-      expect(Sanitize.fragment('<a href="http://example.com" translate="no">Test</a>', subject)).to eq '<a href="http://example.com" translate="no" rel="nofollow noopener" target="_blank">Test</a>'
+      expect(Sanitize.fragment('<a href="http://example.com" translate="no">Test</a>', subject)).to eq '<a href="http://example.com" translate="no" rel="nofollow noopener noreferrer" target="_blank">Test</a>'
     end
 
     it 'removes "translate" attribute with invalid value' do
-      expect(Sanitize.fragment('<a href="http://example.com" translate="foo">Test</a>', subject)).to eq '<a href="http://example.com" rel="nofollow noopener" target="_blank">Test</a>'
+      expect(Sanitize.fragment('<a href="http://example.com" translate="foo">Test</a>', subject)).to eq '<a href="http://example.com" rel="nofollow noopener noreferrer" target="_blank">Test</a>'
     end
 
     it 'removes a with unparsable href' do
@@ -55,27 +55,7 @@ RSpec.describe Sanitize::Config do
     end
 
     it 'keeps a with supported scheme and no host' do
-      expect(Sanitize.fragment('<a href="dweb:/a/foo">Test</a>', subject)).to eq '<a href="dweb:/a/foo" rel="nofollow noopener" target="_blank">Test</a>'
-    end
-
-    it 'sanitizes math to LaTeX' do
-      mathml = '<math><semantics><mrow><msup><mi>x</mi><mi>n</mi></msup><mo>+</mo><mi>y</mi></mrow><annotation encoding="application/x-tex">x^n+y</annotation></semantics></math>'
-      expect(Sanitize.fragment(mathml, subject)).to eq '$x^n+y$'
-    end
-
-    it 'sanitizes math blocks to LaTeX' do
-      mathml = '<math display="block"><semantics><mrow><msup><mi>x</mi><mi>n</mi></msup><mo>+</mo><mi>y</mi></mrow><annotation encoding="application/x-tex">x^n+y</annotation></semantics></math>'
-      expect(Sanitize.fragment(mathml, subject)).to eq '$$x^n+y$$'
-    end
-
-    it 'math sanitizer falls back to plaintext' do
-      mathml = '<math><semantics><msqrt><mi>x</mi></msqrt><annotation encoding="text/plain">sqrt(x)</annotation></semantics></math>'
-      expect(Sanitize.fragment(mathml, subject)).to eq 'sqrt(x)'
-    end
-
-    it 'prefers latex' do
-      mathml = '<math><semantics><msqrt><mi>x</mi></msqrt><annotation encoding="text/plain">sqrt(x)</annotation><annotation encoding="application/x-tex">\\sqrt x</annotation></semantics></math>'
-      expect(Sanitize.fragment(mathml, subject)).to eq '$\sqrt x$'
+      expect(Sanitize.fragment('<a href="dweb:/a/foo">Test</a>', subject)).to eq '<a href="dweb:/a/foo" rel="nofollow noopener noreferrer" target="_blank">Test</a>'
     end
   end
 end
diff --git a/spec/lib/search_query_transformer_spec.rb b/spec/lib/search_query_transformer_spec.rb
index 7d94407b3d..cfc3ad14be 100644
--- a/spec/lib/search_query_transformer_spec.rb
+++ b/spec/lib/search_query_transformer_spec.rb
@@ -16,7 +16,6 @@ RSpec.describe SearchQueryTransformer do
       ['"2022-01-01"', '2022-01-01'],
       ['12345678', '12345678'],
       ['"12345678"', '12345678'],
-      ['"2024-10-31T23:47:20Z"', '2024-10-31T23:47:20Z'],
     ].each do |value, parsed|
       context "with #{operator}:#{value}" do
         let(:query) { "#{operator}:#{value}" }
diff --git a/spec/lib/text_formatter_spec.rb b/spec/lib/text_formatter_spec.rb
index a71655ed2e..bde17bb79c 100644
--- a/spec/lib/text_formatter_spec.rb
+++ b/spec/lib/text_formatter_spec.rb
@@ -50,7 +50,7 @@ RSpec.describe TextFormatter do
       end
     end
 
-    context 'when given a stand-alone Google URL' do
+    context 'when given a stand-alone google URL' do
       let(:text) { 'http://google.com' }
 
       it 'matches the full URL' do
@@ -280,26 +280,6 @@ RSpec.describe TextFormatter do
       end
     end
 
-    context 'when given a lengthy URL' do
-      let(:text) { 'lorem https://prepitaph.org/wip/web-dovespair/ ipsum' }
-
-      it 'truncates the URL' do
-        expect(subject).to include '<span class="invisible">https://</span>'
-        expect(subject).to include '<span class="ellipsis">prepitaph.org/wip/web-dovespai</span>'
-        expect(subject).to include '<span class="invisible">r/</span>'
-      end
-    end
-
-    context 'when given a sufficiently short URL' do
-      let(:text) { 'lorem https://prepitaph.org/wip/web-devspair/ ipsum' }
-
-      it 'does not truncate the URL' do
-        expect(subject).to include '<span class="invisible">https://</span>'
-        expect(subject).to include '<span class="">prepitaph.org/wip/web-devspair/</span>'
-        expect(subject).to include '<span class="invisible"></span>'
-      end
-    end
-
     context 'when given text containing a hashtag' do
       let(:text)  { '#hashtag' }
 
diff --git a/spec/mailers/admin_mailer_spec.rb b/spec/mailers/admin_mailer_spec.rb
index 22386189ca..846147902d 100644
--- a/spec/mailers/admin_mailer_spec.rb
+++ b/spec/mailers/admin_mailer_spec.rb
@@ -91,7 +91,6 @@ RSpec.describe AdminMailer do
     before do
       PreviewCardTrend.create!(preview_card: link)
       StatusTrend.create!(status: status, account: Fabricate(:account))
-      TagTrend.create!(tag: tag)
       recipient.user.update(locale: :en)
     end
 
diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb
index e677a24df2..2722538e1a 100644
--- a/spec/mailers/previews/user_mailer_preview.rb
+++ b/spec/mailers/previews/user_mailer_preview.rb
@@ -98,9 +98,4 @@ class UserMailerPreview < ActionMailer::Preview
   def failed_2fa
     UserMailer.failed_2fa(User.first, '127.0.0.1', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0', Time.now.utc)
   end
-
-  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/terms_of_service_changed
-  def terms_of_service_changed
-    UserMailer.terms_of_service_changed(User.first, TermsOfService.live.first)
-  end
 end
diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb
index 3f40e24c8b..0257465817 100644
--- a/spec/mailers/user_mailer_spec.rb
+++ b/spec/mailers/user_mailer_spec.rb
@@ -3,17 +3,6 @@
 require 'rails_helper'
 
 RSpec.describe UserMailer do
-  shared_examples 'delivery to memorialized user' do
-    context 'when the account is memorialized' do
-      before { receiver.account.update(memorial: true) }
-
-      it 'does not deliver mail' do
-        emails = capture_emails { mail.deliver_now }
-        expect(emails).to be_empty
-      end
-    end
-  end
-
   let(:receiver) { Fabricate(:user) }
 
   describe '#confirmation_instructions' do
@@ -32,7 +21,6 @@ RSpec.describe UserMailer do
     include_examples 'localized subject',
                      'devise.mailer.confirmation_instructions.subject',
                      instance: Rails.configuration.x.local_domain
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#reconfirmation_instructions' do
@@ -51,7 +39,6 @@ RSpec.describe UserMailer do
     include_examples 'localized subject',
                      'devise.mailer.confirmation_instructions.subject',
                      instance: Rails.configuration.x.local_domain
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#reset_password_instructions' do
@@ -68,7 +55,6 @@ RSpec.describe UserMailer do
 
     include_examples 'localized subject',
                      'devise.mailer.reset_password_instructions.subject'
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#password_change' do
@@ -84,7 +70,6 @@ RSpec.describe UserMailer do
 
     include_examples 'localized subject',
                      'devise.mailer.password_change.subject'
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#email_changed' do
@@ -100,7 +85,6 @@ RSpec.describe UserMailer do
 
     include_examples 'localized subject',
                      'devise.mailer.email_changed.subject'
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#warning' do
@@ -131,7 +115,6 @@ RSpec.describe UserMailer do
 
     include_examples 'localized subject',
                      'devise.mailer.webauthn_credential.deleted.subject'
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#suspicious_sign_in' do
@@ -203,8 +186,6 @@ RSpec.describe UserMailer do
         .and(have_subject(I18n.t('devise.mailer.two_factor_enabled.subject')))
         .and(have_body_text(I18n.t('devise.mailer.two_factor_enabled.explanation')))
     end
-
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#two_factor_disabled' do
@@ -216,8 +197,6 @@ RSpec.describe UserMailer do
         .and(have_subject(I18n.t('devise.mailer.two_factor_disabled.subject')))
         .and(have_body_text(I18n.t('devise.mailer.two_factor_disabled.explanation')))
     end
-
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#webauthn_enabled' do
@@ -229,8 +208,6 @@ RSpec.describe UserMailer do
         .and(have_subject(I18n.t('devise.mailer.webauthn_enabled.subject')))
         .and(have_body_text(I18n.t('devise.mailer.webauthn_enabled.explanation')))
     end
-
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#webauthn_disabled' do
@@ -242,8 +219,6 @@ RSpec.describe UserMailer do
         .and(have_subject(I18n.t('devise.mailer.webauthn_disabled.subject')))
         .and(have_body_text(I18n.t('devise.mailer.webauthn_disabled.explanation')))
     end
-
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#two_factor_recovery_codes_changed' do
@@ -255,8 +230,6 @@ RSpec.describe UserMailer do
         .and(have_subject(I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')))
         .and(have_body_text(I18n.t('devise.mailer.two_factor_recovery_codes_changed.explanation')))
     end
-
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#webauthn_credential_added' do
@@ -269,8 +242,6 @@ RSpec.describe UserMailer do
         .and(have_subject(I18n.t('devise.mailer.webauthn_credential.added.subject')))
         .and(have_body_text(I18n.t('devise.mailer.webauthn_credential.added.explanation')))
     end
-
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#welcome' do
@@ -288,8 +259,6 @@ RSpec.describe UserMailer do
         .and(have_subject(I18n.t('user_mailer.welcome.subject')))
         .and(have_body_text(I18n.t('user_mailer.welcome.explanation')))
     end
-
-    include_examples 'delivery to memorialized user'
   end
 
   describe '#backup_ready' do
@@ -302,31 +271,5 @@ RSpec.describe UserMailer do
         .and(have_subject(I18n.t('user_mailer.backup_ready.subject')))
         .and(have_body_text(I18n.t('user_mailer.backup_ready.explanation')))
     end
-
-    include_examples 'delivery to memorialized user'
-  end
-
-  describe '#terms_of_service_changed' do
-    let(:terms) { Fabricate :terms_of_service }
-    let(:mail) { described_class.terms_of_service_changed(receiver, terms) }
-
-    it 'renders terms_of_service_changed mail' do
-      expect(mail)
-        .to be_present
-        .and(have_subject(I18n.t('user_mailer.terms_of_service_changed.subject')))
-        .and(have_body_text(I18n.t('user_mailer.terms_of_service_changed.changelog')))
-    end
-  end
-
-  describe '#announcement_published' do
-    let(:announcement) { Fabricate :announcement }
-    let(:mail) { described_class.announcement_published(receiver, announcement) }
-
-    it 'renders announcement_published mail' do
-      expect(mail)
-        .to be_present
-        .and(have_subject(I18n.t('user_mailer.announcement_published.subject')))
-        .and(have_body_text(I18n.t('user_mailer.announcement_published.description', domain: Rails.configuration.x.local_domain)))
-    end
   end
 end
diff --git a/spec/models/account_alias_spec.rb b/spec/models/account_alias_spec.rb
index 17c83967a4..fc8c6bd250 100644
--- a/spec/models/account_alias_spec.rb
+++ b/spec/models/account_alias_spec.rb
@@ -8,26 +8,4 @@ RSpec.describe AccountAlias do
       it { is_expected.to normalize(:acct).from('  @username@domain  ').to('username@domain') }
     end
   end
-
-  describe 'Validations' do
-    subject { described_class.new(account:) }
-
-    let(:account) { Fabricate :account }
-
-    it { is_expected.to_not allow_values(nil, '').for(:uri).against(:acct).with_message(not_found_message) }
-
-    it { is_expected.to_not allow_values(account_uri).for(:uri).against(:acct).with_message(self_move_message) }
-
-    def account_uri
-      ActivityPub::TagManager.instance.uri_for(subject.account)
-    end
-
-    def not_found_message
-      I18n.t('migrations.errors.not_found')
-    end
-
-    def self_move_message
-      I18n.t('migrations.errors.move_to_self')
-    end
-  end
 end
diff --git a/spec/models/account_migration_spec.rb b/spec/models/account_migration_spec.rb
index b92771e8f5..d658915ce3 100644
--- a/spec/models/account_migration_spec.rb
+++ b/spec/models/account_migration_spec.rb
@@ -9,8 +9,8 @@ RSpec.describe AccountMigration do
     end
   end
 
-  describe 'Validations' do
-    subject { Fabricate.build :account_migration, account: source_account }
+  describe 'validations' do
+    subject { described_class.new(account: source_account, acct: target_acct) }
 
     let(:source_account) { Fabricate(:account) }
     let(:target_acct)    { target_account.acct }
@@ -26,7 +26,9 @@ RSpec.describe AccountMigration do
         allow(service_double).to receive(:call).with(target_acct, anything).and_return(target_account)
       end
 
-      it { is_expected.to allow_value(target_account.acct).for(:acct) }
+      it 'passes validations' do
+        expect(subject).to be_valid
+      end
     end
 
     context 'with unresolvable account' do
@@ -38,13 +40,17 @@ RSpec.describe AccountMigration do
         allow(service_double).to receive(:call).with(target_acct, anything).and_return(nil)
       end
 
-      it { is_expected.to_not allow_value(target_acct).for(:acct) }
+      it 'has errors on acct field' do
+        expect(subject).to model_have_error_on_field(:acct)
+      end
     end
 
     context 'with a space in the domain part' do
       let(:target_acct) { 'target@remote. org' }
 
-      it { is_expected.to_not allow_value(target_acct).for(:acct) }
+      it 'has errors on acct field' do
+        expect(subject).to model_have_error_on_field(:acct)
+      end
     end
   end
 end
diff --git a/spec/models/account_moderation_note_spec.rb b/spec/models/account_moderation_note_spec.rb
index f3bcff4cd6..079774c492 100644
--- a/spec/models/account_moderation_note_spec.rb
+++ b/spec/models/account_moderation_note_spec.rb
@@ -3,24 +3,29 @@
 require 'rails_helper'
 
 RSpec.describe AccountModerationNote do
-  describe 'Scopes' do
-    describe '.chronological' do
-      it 'returns account moderation notes oldest to newest' do
-        account = Fabricate(:account)
-        note1 = Fabricate(:account_moderation_note, target_account: account)
-        note2 = Fabricate(:account_moderation_note, target_account: account)
+  describe 'chronological scope' do
+    it 'returns account moderation notes oldest to newest' do
+      account = Fabricate(:account)
+      note1 = Fabricate(:account_moderation_note, target_account: account)
+      note2 = Fabricate(:account_moderation_note, target_account: account)
 
-        expect(account.targeted_moderation_notes.chronological).to eq [note1, note2]
-      end
+      expect(account.targeted_moderation_notes.chronological).to eq [note1, note2]
     end
   end
 
-  describe 'Validations' do
-    subject { Fabricate.build :account_moderation_note }
+  describe 'validations' do
+    it 'is invalid if the content is empty' do
+      report = Fabricate.build(:account_moderation_note, content: '')
+      expect(report.valid?).to be false
+    end
 
-    describe 'content' do
-      it { is_expected.to_not allow_value('').for(:content) }
-      it { is_expected.to validate_length_of(:content).is_at_most(described_class::CONTENT_SIZE_LIMIT) }
+    it 'is invalid if content is longer than character limit' do
+      report = Fabricate.build(:account_moderation_note, content: comment_over_limit)
+      expect(report.valid?).to be false
+    end
+
+    def comment_over_limit
+      Faker::Lorem.paragraph_by_chars(number: described_class::CONTENT_SIZE_LIMIT * 2)
     end
   end
 end
diff --git a/spec/models/account_pin_spec.rb b/spec/models/account_pin_spec.rb
deleted file mode 100644
index b3460da2fb..0000000000
--- a/spec/models/account_pin_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe AccountPin do
-  describe 'Associations' do
-    it { is_expected.to belong_to(:account).required }
-    it { is_expected.to belong_to(:target_account).required }
-  end
-
-  describe 'Validations' do
-    describe 'the follow relationship' do
-      subject { Fabricate.build :account_pin, account: account }
-
-      let(:account) { Fabricate :account }
-      let(:target_account) { Fabricate :account }
-
-      context 'when account is following target account' do
-        before { account.follow!(target_account) }
-
-        it { is_expected.to allow_value(target_account).for(:target_account).against(:base) }
-      end
-
-      context 'when account is not following target account' do
-        it { is_expected.to_not allow_value(target_account).for(:target_account).against(:base).with_message(not_following_message) }
-
-        def not_following_message
-          I18n.t('accounts.pin_errors.following')
-        end
-      end
-    end
-  end
-end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index 193ceaecde..524b4f423f 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -3,7 +3,6 @@
 require 'rails_helper'
 
 RSpec.describe Account do
-  include_examples 'Account::Search'
   include_examples 'Reviewable'
 
   context 'with an account record' do
@@ -11,6 +10,64 @@ RSpec.describe Account do
 
     let(:bob) { Fabricate(:account, username: 'bob') }
 
+    describe '#suspended_locally?' do
+      context 'when the account is not suspended' do
+        it 'returns false' do
+          expect(subject.suspended_locally?).to be false
+        end
+      end
+
+      context 'when the account is suspended locally' do
+        before do
+          subject.update!(suspended_at: 1.day.ago, suspension_origin: :local)
+        end
+
+        it 'returns true' do
+          expect(subject.suspended_locally?).to be true
+        end
+      end
+
+      context 'when the account is suspended remotely' do
+        before do
+          subject.update!(suspended_at: 1.day.ago, suspension_origin: :remote)
+        end
+
+        it 'returns false' do
+          expect(subject.suspended_locally?).to be false
+        end
+      end
+    end
+
+    describe '#suspend!' do
+      it 'marks the account as suspended and creates a deletion request' do
+        expect { subject.suspend! }
+          .to change(subject, :suspended?).from(false).to(true)
+          .and change(subject, :suspended_locally?).from(false).to(true)
+          .and(change { AccountDeletionRequest.exists?(account: subject) }.from(false).to(true))
+      end
+
+      context 'when the account is of a local user' do
+        subject { local_user_account }
+
+        let!(:local_user_account) { Fabricate(:user, email: 'foo+bar@domain.org').account }
+
+        it 'creates a canonical domain block' do
+          subject.suspend!
+          expect(CanonicalEmailBlock.block?(subject.user_email)).to be true
+        end
+
+        context 'when a canonical domain block already exists for that email' do
+          before do
+            Fabricate(:canonical_email_block, email: subject.user_email)
+          end
+
+          it 'does not raise an error' do
+            expect { subject.suspend! }.to_not raise_error
+          end
+        end
+      end
+    end
+
     describe '#follow!' do
       it 'creates a follow' do
         follow = subject.follow!(bob)
@@ -49,44 +106,14 @@ RSpec.describe Account do
   end
 
   describe '#local?' do
-    context 'when the domain is null' do
-      subject { Fabricate.build :account, domain: nil }
-
-      it { is_expected.to be_local }
+    it 'returns true when the account is local' do
+      account = Fabricate(:account, domain: nil)
+      expect(account.local?).to be true
     end
 
-    context 'when the domain is present' do
-      subject { Fabricate.build :account, domain: 'host.example' }
-
-      it { is_expected.to_not be_local }
-    end
-  end
-
-  describe '#remote?' do
-    context 'when the domain is null' do
-      subject { Fabricate.build :account, domain: nil }
-
-      it { is_expected.to_not be_remote }
-    end
-
-    context 'when the domain is present' do
-      subject { Fabricate.build :account, domain: 'host.example' }
-
-      it { is_expected.to be_remote }
-    end
-  end
-
-  describe '#actor_type_application?' do
-    context 'when the actor is not of type application' do
-      subject { Fabricate.build :account, actor_type: 'Person' }
-
-      it { is_expected.to_not be_actor_type_application }
-    end
-
-    context 'when the actor is of type application' do
-      subject { Fabricate.build :account, actor_type: 'Application' }
-
-      it { is_expected.to be_actor_type_application }
+    it 'returns false when the account is on a different domain' do
+      account = Fabricate(:account, domain: 'foreign.tld')
+      expect(account.local?).to be false
     end
   end
 
@@ -181,16 +208,16 @@ RSpec.describe Account do
       end
     end
 
-    context 'when last_webfingered_at is before the threshold' do
-      let(:last_webfingered_at) { (described_class::STALE_THRESHOLD + 1.hour).ago }
+    context 'when last_webfingered_at is more than 24 hours before' do
+      let(:last_webfingered_at) { 25.hours.ago }
 
       it 'returns true' do
         expect(account.possibly_stale?).to be true
       end
     end
 
-    context 'when last_webfingered_at is after the threshold' do
-      let(:last_webfingered_at) { (described_class::STALE_THRESHOLD - 1.hour).ago }
+    context 'when last_webfingered_at is less than 24 hours before' do
+      let(:last_webfingered_at) { 23.hours.ago }
 
       it 'returns false' do
         expect(account.possibly_stale?).to be false
@@ -551,6 +578,298 @@ RSpec.describe Account do
     end
   end
 
+  describe '.search_for' do
+    before do
+      _missing = Fabricate(
+        :account,
+        display_name: 'Missing',
+        username: 'missing',
+        domain: 'missing.com'
+      )
+    end
+
+    it 'does not return suspended users' do
+      Fabricate(
+        :account,
+        display_name: 'Display Name',
+        username: 'username',
+        domain: 'example.com',
+        suspended: true
+      )
+
+      results = described_class.search_for('username')
+      expect(results).to eq []
+    end
+
+    it 'does not return unapproved users' do
+      match = Fabricate(
+        :account,
+        display_name: 'Display Name',
+        username: 'username'
+      )
+
+      match.user.update(approved: false)
+
+      results = described_class.search_for('username')
+      expect(results).to eq []
+    end
+
+    it 'does not return unconfirmed users' do
+      match = Fabricate(
+        :account,
+        display_name: 'Display Name',
+        username: 'username'
+      )
+
+      match.user.update(confirmed_at: nil)
+
+      results = described_class.search_for('username')
+      expect(results).to eq []
+    end
+
+    it 'accepts ?, \, : and space as delimiter' do
+      match = Fabricate(
+        :account,
+        display_name: 'A & l & i & c & e',
+        username: 'username',
+        domain: 'example.com'
+      )
+
+      results = described_class.search_for('A?l\i:c e')
+      expect(results).to eq [match]
+    end
+
+    it 'finds accounts with matching display_name' do
+      match = Fabricate(
+        :account,
+        display_name: 'Display Name',
+        username: 'username',
+        domain: 'example.com'
+      )
+
+      results = described_class.search_for('display')
+      expect(results).to eq [match]
+    end
+
+    it 'finds accounts with matching username' do
+      match = Fabricate(
+        :account,
+        display_name: 'Display Name',
+        username: 'username',
+        domain: 'example.com'
+      )
+
+      results = described_class.search_for('username')
+      expect(results).to eq [match]
+    end
+
+    it 'finds accounts with matching domain' do
+      match = Fabricate(
+        :account,
+        display_name: 'Display Name',
+        username: 'username',
+        domain: 'example.com'
+      )
+
+      results = described_class.search_for('example')
+      expect(results).to eq [match]
+    end
+
+    it 'limits via constant by default' do
+      stub_const('Account::Search::DEFAULT_LIMIT', 1)
+      2.times.each { Fabricate(:account, display_name: 'Display Name') }
+      results = described_class.search_for('display')
+      expect(results.size).to eq 1
+    end
+
+    it 'accepts arbitrary limits' do
+      2.times.each { Fabricate(:account, display_name: 'Display Name') }
+      results = described_class.search_for('display', limit: 1)
+      expect(results.size).to eq 1
+    end
+
+    it 'ranks multiple matches higher' do
+      matches = [
+        { username: 'username', display_name: 'username' },
+        { display_name: 'Display Name', username: 'username', domain: 'example.com' },
+      ].map(&method(:Fabricate).curry(2).call(:account))
+
+      results = described_class.search_for('username')
+      expect(results).to eq matches
+    end
+  end
+
+  describe '.advanced_search_for' do
+    let(:account) { Fabricate(:account) }
+
+    context 'when limiting search to followed accounts' do
+      it 'accepts ?, \, : and space as delimiter' do
+        match = Fabricate(
+          :account,
+          display_name: 'A & l & i & c & e',
+          username: 'username',
+          domain: 'example.com'
+        )
+        account.follow!(match)
+
+        results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
+        expect(results).to eq [match]
+      end
+
+      it 'does not return non-followed accounts' do
+        Fabricate(
+          :account,
+          display_name: 'A & l & i & c & e',
+          username: 'username',
+          domain: 'example.com'
+        )
+
+        results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
+        expect(results).to eq []
+      end
+
+      it 'does not return suspended users' do
+        Fabricate(
+          :account,
+          display_name: 'Display Name',
+          username: 'username',
+          domain: 'example.com',
+          suspended: true
+        )
+
+        results = described_class.advanced_search_for('username', account, limit: 10, following: true)
+        expect(results).to eq []
+      end
+
+      it 'does not return unapproved users' do
+        match = Fabricate(
+          :account,
+          display_name: 'Display Name',
+          username: 'username'
+        )
+
+        match.user.update(approved: false)
+
+        results = described_class.advanced_search_for('username', account, limit: 10, following: true)
+        expect(results).to eq []
+      end
+
+      it 'does not return unconfirmed users' do
+        match = Fabricate(
+          :account,
+          display_name: 'Display Name',
+          username: 'username'
+        )
+
+        match.user.update(confirmed_at: nil)
+
+        results = described_class.advanced_search_for('username', account, limit: 10, following: true)
+        expect(results).to eq []
+      end
+    end
+
+    context 'when limiting search to follower accounts' do
+      it 'accepts ?, \, : and space as delimiter' do
+        match = Fabricate(
+          :account,
+          display_name: 'A & l & i & c & e',
+          username: 'username',
+          domain: 'example.com'
+        )
+        match.follow!(account)
+
+        results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, follower: true)
+        expect(results).to eq [match]
+      end
+
+      it 'does not return non-follower accounts' do
+        Fabricate(
+          :account,
+          display_name: 'A & l & i & c & e',
+          username: 'username',
+          domain: 'example.com'
+        )
+
+        results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, follower: true)
+        expect(results).to eq []
+      end
+    end
+
+    it 'does not return suspended users' do
+      Fabricate(
+        :account,
+        display_name: 'Display Name',
+        username: 'username',
+        domain: 'example.com',
+        suspended: true
+      )
+
+      results = described_class.advanced_search_for('username', account)
+      expect(results).to eq []
+    end
+
+    it 'does not return unapproved users' do
+      match = Fabricate(
+        :account,
+        display_name: 'Display Name',
+        username: 'username'
+      )
+
+      match.user.update(approved: false)
+
+      results = described_class.advanced_search_for('username', account)
+      expect(results).to eq []
+    end
+
+    it 'does not return unconfirmed users' do
+      match = Fabricate(
+        :account,
+        display_name: 'Display Name',
+        username: 'username'
+      )
+
+      match.user.update(confirmed_at: nil)
+
+      results = described_class.advanced_search_for('username', account)
+      expect(results).to eq []
+    end
+
+    it 'accepts ?, \, : and space as delimiter' do
+      match = Fabricate(
+        :account,
+        display_name: 'A & l & i & c & e',
+        username: 'username',
+        domain: 'example.com'
+      )
+
+      results = described_class.advanced_search_for('A?l\i:c e', account)
+      expect(results).to eq [match]
+    end
+
+    it 'limits result count by default value' do
+      stub_const('Account::Search::DEFAULT_LIMIT', 1)
+      2.times { Fabricate(:account, display_name: 'Display Name') }
+      results = described_class.advanced_search_for('display', account)
+      expect(results.size).to eq 1
+    end
+
+    it 'accepts arbitrary limits' do
+      2.times { Fabricate(:account, display_name: 'Display Name') }
+      results = described_class.advanced_search_for('display', account, limit: 1)
+      expect(results.size).to eq 1
+    end
+
+    it 'ranks followed accounts higher' do
+      match = Fabricate(:account, username: 'Matching')
+      followed_match = Fabricate(:account, username: 'Matcher')
+      Fabricate(:follow, account: account, target_account: followed_match)
+
+      results = described_class.advanced_search_for('match', account)
+      expect(results).to eq [followed_match, match]
+      expect(results.first.rank).to be > results.last.rank
+    end
+  end
+
   describe '#statuses_count' do
     subject { Fabricate(:account) }
 
@@ -656,42 +975,26 @@ RSpec.describe Account do
     end
   end
 
-  describe 'Callbacks' do
-    describe 'Stripping content when required' do
-      context 'with a remote account' do
-        subject { Fabricate.build :account, domain: 'host.example', note: '   note   ', display_name: '   display name   ' }
+  describe '#prepare_contents' do
+    subject { Fabricate.build :account, domain: domain, note: '  padded note  ', display_name: '  padded name  ' }
 
-        it 'preserves content' do
-          expect { subject.valid? }
-            .to not_change(subject, :note)
-            .and not_change(subject, :display_name)
-        end
+    context 'with local account' do
+      let(:domain) { nil }
+
+      it 'strips values' do
+        expect { subject.valid? }
+          .to change(subject, :note).to('padded note')
+          .and(change(subject, :display_name).to('padded name'))
       end
+    end
 
-      context 'with a local account' do
-        subject { Fabricate.build :account, domain: nil, note:, display_name: }
+    context 'with remote account' do
+      let(:domain) { 'host.example' }
 
-        context 'with populated fields' do
-          let(:note) { '   note   ' }
-          let(:display_name) { '   display name   ' }
-
-          it 'strips content' do
-            expect { subject.valid? }
-              .to change(subject, :note).to('note')
-              .and change(subject, :display_name).to('display name')
-          end
-        end
-
-        context 'with empty fields' do
-          let(:note) { nil }
-          let(:display_name) { nil }
-
-          it 'preserves content' do
-            expect { subject.valid? }
-              .to not_change(subject, :note)
-              .and not_change(subject, :display_name)
-          end
-        end
+      it 'preserves values' do
+        expect { subject.valid? }
+          .to not_change(subject, :note)
+          .and(not_change(subject, :display_name))
       end
     end
   end
@@ -712,33 +1015,56 @@ RSpec.describe Account do
     end
   end
 
+  describe '#attribution_domains_as_text=' do
+    subject { Fabricate(:account) }
+
+    it 'sets attribution_domains accordingly' do
+      subject.attribution_domains_as_text = "hoge.com\nexample.com"
+
+      expect(subject.attribution_domains).to contain_exactly('hoge.com', 'example.com')
+    end
+
+    it 'strips leading "*."' do
+      subject.attribution_domains_as_text = "hoge.com\n*.example.com"
+
+      expect(subject.attribution_domains).to contain_exactly('hoge.com', 'example.com')
+    end
+
+    it 'strips the protocol if present' do
+      subject.attribution_domains_as_text = "http://hoge.com\nhttps://example.com"
+
+      expect(subject.attribution_domains).to contain_exactly('hoge.com', 'example.com')
+    end
+
+    it 'strips a combination of leading "*." and protocol' do
+      subject.attribution_domains_as_text = "http://*.hoge.com\nhttps://*.example.com"
+
+      expect(subject.attribution_domains).to contain_exactly('hoge.com', 'example.com')
+    end
+  end
+
   describe 'Normalizations' do
     describe 'username' do
       it { is_expected.to normalize(:username).from(" \u3000bob \t \u00a0 \n ").to('bob') }
     end
-
-    describe 'attribution_domains' do
-      it { is_expected.to normalize(:attribution_domains).from(['example.com', ' example.com ', ' example.net ']).to(['example.com', 'example.net']) }
-      it { is_expected.to normalize(:attribution_domains).from(['https://example.com', 'http://example.net', '*.example.org']).to(['example.com', 'example.net', 'example.org']) }
-      it { is_expected.to normalize(:attribution_domains).from(['', ' ', nil]).to([]) }
-    end
   end
 
-  describe 'Validations' do
+  describe 'validations' do
     it { is_expected.to validate_presence_of(:username) }
 
-    it { is_expected.to_not allow_value('').for(:domain) }
-
-    context 'when account is local' do
-      subject { Fabricate.build :account, domain: nil }
-
-      context 'with an existing differently-cased username account' do
-        before { Fabricate :account, username: 'the_doctor' }
-
-        it { is_expected.to_not allow_value('the_Doctor').for(:username) }
+    context 'when is local' do
+      it 'is invalid if the username is not unique in case-insensitive comparison among local accounts' do
+        _account = Fabricate(:account, username: 'the_doctor')
+        non_unique_account = Fabricate.build(:account, username: 'the_Doctor')
+        non_unique_account.valid?
+        expect(non_unique_account).to model_have_error_on_field(:username)
       end
 
-      it { is_expected.to_not allow_value('support').for(:username) }
+      it 'is invalid if the username is reserved' do
+        account = Fabricate.build(:account, username: 'support')
+        account.valid?
+        expect(account).to model_have_error_on_field(:username)
+      end
 
       it 'is valid when username is reserved but record has already been created' do
         account = Fabricate.build(:account, username: 'support')
@@ -746,10 +1072,9 @@ RSpec.describe Account do
         expect(account.valid?).to be true
       end
 
-      context 'with the instance actor' do
-        subject { Fabricate.build :account, id: described_class::INSTANCE_ACTOR_ID, actor_type: 'Application', locked: true }
-
-        it { is_expected.to allow_value('example.com').for(:username) }
+      it 'is valid if we are creating an instance actor account with a period' do
+        account = Fabricate.build(:account, id: described_class::INSTANCE_ACTOR_ID, actor_type: 'Application', locked: true, username: 'example.com')
+        expect(account.valid?).to be true
       end
 
       it 'is valid if we are creating a possibly-conflicting instance actor account' do
@@ -758,42 +1083,81 @@ RSpec.describe Account do
         expect(instance_account.valid?).to be true
       end
 
-      it { is_expected.to_not allow_values('the-doctor', 'the.doctor').for(:username) }
-
-      it { is_expected.to validate_length_of(:username).is_at_most(described_class::USERNAME_LENGTH_LIMIT) }
-      it { is_expected.to validate_length_of(:display_name).is_at_most(described_class::DISPLAY_NAME_LENGTH_LIMIT) }
-
-      it { is_expected.to_not allow_values(account_note_over_limit).for(:note) }
-
-      it { is_expected.to allow_value(fields_empty_name_value).for(:fields) }
-      it { is_expected.to_not allow_values(fields_over_limit, fields_empty_name).for(:fields) }
-
-      it { is_expected.to validate_absence_of(:followers_url).on(:create) }
-      it { is_expected.to validate_absence_of(:inbox_url).on(:create) }
-      it { is_expected.to validate_absence_of(:shared_inbox_url).on(:create) }
-      it { is_expected.to validate_absence_of(:uri).on(:create) }
-
-      it { is_expected.to allow_values([], ['example.com'], (1..100).to_a).for(:attribution_domains) }
-      it { is_expected.to_not allow_values(['example com'], ['@'], (1..101).to_a).for(:attribution_domains) }
-    end
-
-    context 'when account is remote' do
-      subject { Fabricate.build :account, domain: 'host.example' }
-
-      context 'when a normalized domain account exists' do
-        subject { Fabricate.build :account, domain: 'xn--r9j5b5b' }
-
-        before { Fabricate(:account, domain: 'にゃん', username: 'username') }
-
-        it { is_expected.to_not allow_values('username', 'Username').for(:username) }
+      it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do
+        account = Fabricate.build(:account, username: 'the-doctor')
+        account.valid?
+        expect(account).to model_have_error_on_field(:username)
       end
 
-      it { is_expected.to allow_values('the-doctor', username_over_limit).for(:username) }
-      it { is_expected.to_not allow_values('the doctor').for(:username) }
+      it 'is invalid if the username contains a period' do
+        account = Fabricate.build(:account, username: 'the.doctor')
+        account.valid?
+        expect(account).to model_have_error_on_field(:username)
+      end
 
-      it { is_expected.to allow_values(display_name_over_limit).for(:display_name) }
+      it 'is invalid if the username is longer than the character limit' do
+        account = Fabricate.build(:account, username: username_over_limit)
+        account.valid?
+        expect(account).to model_have_error_on_field(:username)
+      end
 
-      it { is_expected.to allow_values(account_note_over_limit).for(:note) }
+      it 'is invalid if the display name is longer than the character limit' do
+        account = Fabricate.build(:account, display_name: display_name_over_limit)
+        account.valid?
+        expect(account).to model_have_error_on_field(:display_name)
+      end
+
+      it 'is invalid if the note is longer than the character limit' do
+        account = Fabricate.build(:account, note: account_note_over_limit)
+        account.valid?
+        expect(account).to model_have_error_on_field(:note)
+      end
+    end
+
+    context 'when is remote' do
+      it 'is invalid if the username is same among accounts in the same normalized domain' do
+        Fabricate(:account, domain: 'にゃん', username: 'username')
+        account = Fabricate.build(:account, domain: 'xn--r9j5b5b', username: 'username')
+        account.valid?
+        expect(account).to model_have_error_on_field(:username)
+      end
+
+      it 'is invalid if the username is not unique in case-insensitive comparison among accounts in the same normalized domain' do
+        Fabricate(:account, domain: 'にゃん', username: 'username')
+        account = Fabricate.build(:account, domain: 'xn--r9j5b5b', username: 'Username')
+        account.valid?
+        expect(account).to model_have_error_on_field(:username)
+      end
+
+      it 'is valid even if the username contains hyphens' do
+        account = Fabricate.build(:account, domain: 'domain', username: 'the-doctor')
+        account.valid?
+        expect(account).to_not model_have_error_on_field(:username)
+      end
+
+      it 'is invalid if the username doesn\'t only contains letters, numbers, underscores and hyphens' do
+        account = Fabricate.build(:account, domain: 'domain', username: 'the doctor')
+        account.valid?
+        expect(account).to model_have_error_on_field(:username)
+      end
+
+      it 'is valid even if the username is longer than the character limit' do
+        account = Fabricate.build(:account, domain: 'domain', username: username_over_limit)
+        account.valid?
+        expect(account).to_not model_have_error_on_field(:username)
+      end
+
+      it 'is valid even if the display name is longer than the character limit' do
+        account = Fabricate.build(:account, domain: 'domain', display_name: display_name_over_limit)
+        account.valid?
+        expect(account).to_not model_have_error_on_field(:display_name)
+      end
+
+      it 'is valid even if the note is longer than the character limit' do
+        account = Fabricate.build(:account, domain: 'domain', note: account_note_over_limit)
+        account.valid?
+        expect(account).to_not model_have_error_on_field(:note)
+      end
     end
 
     def username_over_limit
@@ -807,18 +1171,6 @@ RSpec.describe Account do
     def account_note_over_limit
       'a' * described_class::NOTE_LENGTH_LIMIT * 2
     end
-
-    def fields_empty_name_value
-      Array.new(4) { { 'name' => '', 'value' => '' } }
-    end
-
-    def fields_over_limit
-      Array.new(described_class::DEFAULT_FIELDS_SIZE + 1) { { 'name' => 'Name', 'value' => 'Value', 'verified_at' => '01/01/1970' } }
-    end
-
-    def fields_empty_name
-      [{ 'name' => '', 'value' => 'Value', 'verified_at' => '01/01/1970' }]
-    end
   end
 
   describe 'scopes' do
@@ -948,6 +1300,22 @@ RSpec.describe Account do
       end
     end
 
+    describe 'silenced' do
+      it 'returns an array of accounts who are silenced' do
+        silenced_account = Fabricate(:account, silenced: true)
+        _account = Fabricate(:account, silenced: false)
+        expect(described_class.silenced).to contain_exactly(silenced_account)
+      end
+    end
+
+    describe 'suspended' do
+      it 'returns an array of accounts who are suspended' do
+        suspended_account = Fabricate(:account, suspended: true)
+        _account = Fabricate(:account, suspended: false)
+        expect(described_class.suspended).to contain_exactly(suspended_account)
+      end
+    end
+
     describe 'searchable' do
       let!(:suspended_local)        { Fabricate(:account, suspended: true, username: 'suspended_local') }
       let!(:suspended_remote)       { Fabricate(:account, suspended: true, domain: 'example.org', username: 'suspended_remote') }
diff --git a/spec/models/account_statuses_cleanup_policy_spec.rb b/spec/models/account_statuses_cleanup_policy_spec.rb
index 548e1105a1..c142a0359a 100644
--- a/spec/models/account_statuses_cleanup_policy_spec.rb
+++ b/spec/models/account_statuses_cleanup_policy_spec.rb
@@ -5,12 +5,13 @@ require 'rails_helper'
 RSpec.describe AccountStatusesCleanupPolicy do
   let(:account) { Fabricate(:account, username: 'alice', domain: nil) }
 
-  describe 'Validations' do
-    subject { Fabricate.build :account_statuses_cleanup_policy }
-
-    let(:remote_account) { Fabricate(:account, domain: 'example.com') }
-
-    it { is_expected.to_not allow_value(remote_account).for(:account) }
+  describe 'validation' do
+    it 'disallow remote accounts' do
+      account.update(domain: 'example.com')
+      account_statuses_cleanup_policy = Fabricate.build(:account_statuses_cleanup_policy, account: account)
+      account_statuses_cleanup_policy.valid?
+      expect(account_statuses_cleanup_policy).to model_have_error_on_field(:account)
+    end
   end
 
   describe 'save hooks' do
@@ -129,8 +130,8 @@ RSpec.describe AccountStatusesCleanupPolicy do
     let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) }
 
     it 'records the given id' do
-      expect { account_statuses_cleanup_policy.record_last_inspected(42) }
-        .to change(account_statuses_cleanup_policy, :last_inspected).from(nil).to(42)
+      account_statuses_cleanup_policy.record_last_inspected(42)
+      expect(account_statuses_cleanup_policy.last_inspected).to eq 42
     end
   end
 
@@ -153,8 +154,8 @@ RSpec.describe AccountStatusesCleanupPolicy do
         end
 
         it 'does not change the recorded id' do
-          expect { subject }
-            .to_not change(account_statuses_cleanup_policy, :last_inspected).from(42)
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 42
         end
       end
 
@@ -164,8 +165,8 @@ RSpec.describe AccountStatusesCleanupPolicy do
         end
 
         it 'records the older id' do
-          expect { subject }
-            .to change(account_statuses_cleanup_policy, :last_inspected).from(42).to(10)
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 10
         end
       end
     end
@@ -179,8 +180,8 @@ RSpec.describe AccountStatusesCleanupPolicy do
         end
 
         it 'does not change the recorded id' do
-          expect { subject }
-            .to_not change(account_statuses_cleanup_policy, :last_inspected).from(42)
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 42
         end
       end
 
@@ -190,8 +191,8 @@ RSpec.describe AccountStatusesCleanupPolicy do
         end
 
         it 'records the older id' do
-          expect { subject }
-            .to change(account_statuses_cleanup_policy, :last_inspected).from(42).to(10)
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 10
         end
       end
     end
@@ -205,8 +206,8 @@ RSpec.describe AccountStatusesCleanupPolicy do
         end
 
         it 'does not change the recorded id' do
-          expect { subject }
-            .to_not change(account_statuses_cleanup_policy, :last_inspected).from(42)
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 42
         end
       end
 
@@ -216,8 +217,8 @@ RSpec.describe AccountStatusesCleanupPolicy do
         end
 
         it 'records the older id' do
-          expect { subject }
-            .to change(account_statuses_cleanup_policy, :last_inspected).from(42).to(10)
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 10
         end
       end
     end
@@ -227,8 +228,8 @@ RSpec.describe AccountStatusesCleanupPolicy do
       let(:status) { Fabricate(:status, account: account) }
 
       it 'does not change the recorded id' do
-        expect { subject }
-          .to_not change(account_statuses_cleanup_policy, :last_inspected).from(42)
+        subject
+        expect(account_statuses_cleanup_policy.last_inspected).to eq 42
       end
     end
   end
@@ -338,7 +339,14 @@ RSpec.describe AccountStatusesCleanupPolicy do
     end
 
     context 'when policy is set to keep DMs and reject everything else' do
-      before { establish_policy(keep_direct: true) }
+      before do
+        account_statuses_cleanup_policy.keep_direct = true
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
 
       it 'returns every old status except does not return the old direct message for deletion' do
         expect(subject.pluck(:id))
@@ -348,7 +356,14 @@ RSpec.describe AccountStatusesCleanupPolicy do
     end
 
     context 'when policy is set to keep self-bookmarked toots and reject everything else' do
-      before { establish_policy(keep_self_bookmark: true) }
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = true
+      end
 
       it 'returns every old status but does not return the old self-bookmarked message for deletion' do
         expect(subject.pluck(:id))
@@ -358,7 +373,14 @@ RSpec.describe AccountStatusesCleanupPolicy do
     end
 
     context 'when policy is set to keep self-faved toots and reject everything else' do
-      before { establish_policy(keep_self_fav: true) }
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = true
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
 
       it 'returns every old status but does not return the old self-faved message for deletion' do
         expect(subject.pluck(:id))
@@ -368,7 +390,14 @@ RSpec.describe AccountStatusesCleanupPolicy do
     end
 
     context 'when policy is set to keep toots with media and reject everything else' do
-      before { establish_policy(keep_media: true) }
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = true
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
 
       it 'returns every old status but does not return the old message with media for deletion' do
         expect(subject.pluck(:id))
@@ -378,7 +407,14 @@ RSpec.describe AccountStatusesCleanupPolicy do
     end
 
     context 'when policy is set to keep toots with polls and reject everything else' do
-      before { establish_policy(keep_polls: true) }
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = true
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
 
       it 'returns every old status but does not return the old poll message for deletion' do
         expect(subject.pluck(:id))
@@ -388,7 +424,14 @@ RSpec.describe AccountStatusesCleanupPolicy do
     end
 
     context 'when policy is set to keep pinned toots and reject everything else' do
-      before { establish_policy(keep_pinned: true) }
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = true
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
 
       it 'returns every old status but does not return the old pinned message for deletion' do
         expect(subject.pluck(:id))
@@ -398,7 +441,14 @@ RSpec.describe AccountStatusesCleanupPolicy do
     end
 
     context 'when policy is to not keep any special messages' do
-      before { establish_policy }
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
 
       it 'returns every old status but does not return the recent or unrelated statuses' do
         expect(subject.pluck(:id))
@@ -409,7 +459,14 @@ RSpec.describe AccountStatusesCleanupPolicy do
     end
 
     context 'when policy is set to keep every category of toots' do
-      before { establish_policy(keep_direct: true, keep_pinned: true, keep_polls: true, keep_media: true, keep_self_fav: true, keep_self_bookmark: true) }
+      before do
+        account_statuses_cleanup_policy.keep_direct = true
+        account_statuses_cleanup_policy.keep_pinned = true
+        account_statuses_cleanup_policy.keep_polls = true
+        account_statuses_cleanup_policy.keep_media = true
+        account_statuses_cleanup_policy.keep_self_fav = true
+        account_statuses_cleanup_policy.keep_self_bookmark = true
+      end
 
       it 'returns normal statuses and does not return unrelated old status' do
         expect(subject.pluck(:id))
@@ -445,24 +502,5 @@ RSpec.describe AccountStatusesCleanupPolicy do
           .and include(very_old_status.id, faved_primary.id, reblogged_primary.id, reblogged_secondary.id)
       end
     end
-
-    private
-
-    def establish_policy(options = {})
-      default_policy_options.merge(options).each do |attribute, value|
-        account_statuses_cleanup_policy.send :"#{attribute}=", value
-      end
-    end
-
-    def default_policy_options
-      {
-        keep_direct: false,
-        keep_media: false,
-        keep_pinned: false,
-        keep_polls: false,
-        keep_self_bookmark: false,
-        keep_self_fav: false,
-      }
-    end
   end
 end
diff --git a/spec/models/account_summary_spec.rb b/spec/models/account_summary_spec.rb
deleted file mode 100644
index cede8cda55..0000000000
--- a/spec/models/account_summary_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe AccountSummary do
-  describe 'Scopes' do
-    describe '.localized' do
-      let(:first) { Fabricate :account }
-      let(:last) { Fabricate :account }
-
-      before do
-        Fabricate :status, account: first, language: 'en'
-        Fabricate :status, account: last, language: 'es'
-        described_class.refresh
-      end
-
-      it 'returns records in order of language' do
-        expect(described_class.localized('en'))
-          .to contain_exactly(
-            have_attributes(account_id: first.id, language: 'en'),
-            have_attributes(account_id: last.id, language: 'es')
-          )
-      end
-    end
-  end
-end
diff --git a/spec/models/account_warning_spec.rb b/spec/models/account_warning_spec.rb
index 9fe2b331eb..37866ce3da 100644
--- a/spec/models/account_warning_spec.rb
+++ b/spec/models/account_warning_spec.rb
@@ -8,18 +8,4 @@ RSpec.describe AccountWarning do
       it { is_expected.to normalize(:text).from(nil).to('') }
     end
   end
-
-  describe '#appeal_eligible?' do
-    context 'when created too long ago' do
-      subject { Fabricate.build :account_warning, created_at: (described_class::APPEAL_WINDOW * 2).ago }
-
-      it { is_expected.to_not be_appeal_eligible }
-    end
-
-    context 'when created recently' do
-      subject { Fabricate.build :account_warning, created_at: (described_class::APPEAL_WINDOW - 2.days).ago }
-
-      it { is_expected.to be_appeal_eligible }
-    end
-  end
 end
diff --git a/spec/models/admin/account_action_spec.rb b/spec/models/admin/account_action_spec.rb
index 6032594850..49bc2b4a91 100644
--- a/spec/models/admin/account_action_spec.rb
+++ b/spec/models/admin/account_action_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Admin::AccountAction do
   describe '#save!' do
     subject              { account_action.save! }
 
-    let(:account)        { Fabricate(:admin_user).account }
+    let(:account)        { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
     let(:target_account) { Fabricate(:account) }
     let(:type)           { 'disable' }
 
diff --git a/spec/models/announcement_spec.rb b/spec/models/announcement_spec.rb
index 884b352cb5..8bd1e74b29 100644
--- a/spec/models/announcement_spec.rb
+++ b/spec/models/announcement_spec.rb
@@ -67,30 +67,18 @@ RSpec.describe Announcement do
     it { is_expected.to validate_presence_of(:text) }
 
     describe 'ends_at' do
-      context 'when starts_at is present' do
-        subject { Fabricate.build :announcement, starts_at: 1.day.ago }
+      it 'validates presence when starts_at is present' do
+        record = Fabricate.build(:announcement, starts_at: 1.day.ago)
 
-        it { is_expected.to validate_presence_of(:ends_at) }
+        expect(record).to_not be_valid
+        expect(record.errors[:ends_at]).to be_present
       end
 
-      context 'when starts_at is missing' do
-        subject { Fabricate.build :announcement, starts_at: nil }
+      it 'does not validate presence when starts_at is missing' do
+        record = Fabricate.build(:announcement, starts_at: nil)
 
-        it { is_expected.to_not validate_presence_of(:ends_at) }
-      end
-    end
-
-    describe 'starts_at' do
-      context 'when ends_at is present' do
-        subject { Fabricate.build :announcement, ends_at: 1.day.ago }
-
-        it { is_expected.to validate_presence_of(:starts_at) }
-      end
-
-      context 'when ends_at is missing' do
-        subject { Fabricate.build :announcement, ends_at: nil }
-
-        it { is_expected.to_not validate_presence_of(:starts_at) }
+        expect(record).to be_valid
+        expect(record.errors[:ends_at]).to_not be_present
       end
     end
   end
diff --git a/spec/models/appeal_spec.rb b/spec/models/appeal_spec.rb
index d624e14949..7e324582ed 100644
--- a/spec/models/appeal_spec.rb
+++ b/spec/models/appeal_spec.rb
@@ -4,85 +4,20 @@ require 'rails_helper'
 
 RSpec.describe Appeal do
   describe 'Validations' do
-    subject { Fabricate.build :appeal, strike: Fabricate(:account_warning) }
+    it 'validates text length is under limit' do
+      appeal = Fabricate.build(
+        :appeal,
+        strike: Fabricate(:account_warning),
+        text: 'a' * described_class::TEXT_LENGTH_LIMIT * 2
+      )
 
-    it { is_expected.to validate_length_of(:text).is_at_most(described_class::TEXT_LENGTH_LIMIT) }
-
-    context 'with a strike created too long ago' do
-      let(:strike) { Fabricate.build :account_warning, created_at: (AccountWarning::APPEAL_WINDOW * 2).ago }
-
-      it { is_expected.to_not allow_values(strike).for(:strike).against(:base).on(:create) }
+      expect(appeal).to_not be_valid
+      expect(appeal).to model_have_error_on_field(:text)
     end
   end
 
-  describe 'Query methods' do
-    describe '#pending?' do
-      subject { Fabricate.build :appeal, approved_at:, rejected_at: }
-
-      context 'with not approved and not rejected' do
-        let(:approved_at) { nil }
-        let(:rejected_at) { nil }
-
-        it { expect(subject).to be_pending }
-      end
-
-      context 'with approved and rejected' do
-        let(:approved_at) { 1.day.ago }
-        let(:rejected_at) { 1.day.ago }
-
-        it { expect(subject).to_not be_pending }
-      end
-
-      context 'with approved and not rejected' do
-        let(:approved_at) { 1.day.ago }
-        let(:rejected_at) { nil }
-
-        it { expect(subject).to_not be_pending }
-      end
-
-      context 'with not approved and rejected' do
-        let(:approved_at) { nil }
-        let(:rejected_at) { 1.day.ago }
-
-        it { expect(subject).to_not be_pending }
-      end
-    end
-
-    describe '#approved?' do
-      subject { Fabricate.build :appeal, approved_at: }
-
-      context 'with not approved' do
-        let(:approved_at) { nil }
-
-        it { expect(subject).to_not be_approved }
-      end
-
-      context 'with approved' do
-        let(:approved_at) { 1.day.ago }
-
-        it { expect(subject).to be_approved }
-      end
-    end
-
-    describe '#rejected?' do
-      subject { Fabricate.build :appeal, rejected_at: }
-
-      context 'with not rejected' do
-        let(:rejected_at) { nil }
-
-        it { expect(subject).to_not be_rejected }
-      end
-
-      context 'with rejected' do
-        let(:rejected_at) { 1.day.ago }
-
-        it { expect(subject).to be_rejected }
-      end
-    end
-  end
-
-  describe 'Scopes' do
-    describe '.approved' do
+  describe 'scopes' do
+    describe 'approved' do
       let(:approved_appeal) { Fabricate(:appeal, approved_at: 10.days.ago) }
       let(:not_approved_appeal) { Fabricate(:appeal, approved_at: nil) }
 
@@ -92,7 +27,7 @@ RSpec.describe Appeal do
       end
     end
 
-    describe '.rejected' do
+    describe 'rejected' do
       let(:rejected_appeal) { Fabricate(:appeal, rejected_at: 10.days.ago) }
       let(:not_rejected_appeal) { Fabricate(:appeal, rejected_at: nil) }
 
@@ -102,7 +37,7 @@ RSpec.describe Appeal do
       end
     end
 
-    describe '.pending' do
+    describe 'pending' do
       let(:approved_appeal) { Fabricate(:appeal, approved_at: 10.days.ago) }
       let(:rejected_appeal) { Fabricate(:appeal, rejected_at: 10.days.ago) }
       let(:pending_appeal) { Fabricate(:appeal, rejected_at: nil, approved_at: nil) }
diff --git a/spec/models/bulk_import_spec.rb b/spec/models/bulk_import_spec.rb
deleted file mode 100644
index a3bd01d2a8..0000000000
--- a/spec/models/bulk_import_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe BulkImport do
-  describe 'Associations' do
-    it { is_expected.to belong_to(:account).required }
-    it { is_expected.to have_many(:rows).class_name('BulkImportRow').inverse_of(:bulk_import).dependent(:delete_all) }
-  end
-
-  describe 'Validations' do
-    subject { Fabricate.build :bulk_import }
-
-    it { is_expected.to validate_presence_of(:type) }
-  end
-
-  describe 'Scopes' do
-    describe '.archival_completed' do
-      let!(:old_import) { Fabricate :bulk_import, created_at: 1.month.ago }
-      let!(:new_import) { Fabricate :bulk_import, created_at: 1.day.ago }
-
-      it 'returns imports which have passed the archive window period' do
-        expect(described_class.archival_completed)
-          .to include(old_import)
-          .and not_include(new_import)
-      end
-    end
-
-    describe '.confirmation_missed' do
-      let!(:old_unconfirmed_import) { Fabricate :bulk_import, created_at: 1.week.ago, state: :unconfirmed }
-      let!(:old_scheduled_import) { Fabricate :bulk_import, created_at: 1.week.ago, state: :scheduled }
-      let!(:new_unconfirmed_import) { Fabricate :bulk_import, created_at: 1.minute.ago, state: :unconfirmed }
-
-      it 'returns imports which have passed the confirmation window without confirming' do
-        expect(described_class.confirmation_missed)
-          .to include(old_unconfirmed_import)
-          .and not_include(old_scheduled_import)
-          .and not_include(new_unconfirmed_import)
-      end
-    end
-  end
-end
diff --git a/spec/models/concerns/account/interactions_spec.rb b/spec/models/concerns/account/interactions_spec.rb
index e8215a27ab..5515643884 100644
--- a/spec/models/concerns/account/interactions_spec.rb
+++ b/spec/models/concerns/account/interactions_spec.rb
@@ -3,10 +3,10 @@
 require 'rails_helper'
 
 RSpec.describe Account::Interactions do
-  let(:account)            { Fabricate(:account) }
+  let(:account)            { Fabricate(:account, username: 'account') }
   let(:account_id)         { account.id }
   let(:account_ids)        { [account_id] }
-  let(:target_account)     { Fabricate(:account) }
+  let(:target_account)     { Fabricate(:account, username: 'target') }
   let(:target_account_id)  { target_account.id }
   let(:target_account_ids) { [target_account_id] }
   let(:follower_account)   { Fabricate(:account, username: 'follower') }
diff --git a/spec/models/concerns/account/sensitizes_spec.rb b/spec/models/concerns/account/sensitizes_spec.rb
deleted file mode 100644
index 5416d40570..0000000000
--- a/spec/models/concerns/account/sensitizes_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Account::Sensitizes do
-  describe 'Scopes' do
-    describe '.sensitized' do
-      let(:sensitized_account) { Fabricate :account, sensitized_at: 2.days.ago }
-
-      before { Fabricate :account, sensitized_at: nil }
-
-      it 'returns an array of accounts who are sensitized' do
-        expect(Account.sensitized)
-          .to contain_exactly(sensitized_account)
-      end
-    end
-  end
-end
diff --git a/spec/models/concerns/account/silences_spec.rb b/spec/models/concerns/account/silences_spec.rb
deleted file mode 100644
index 0d8337ce1d..0000000000
--- a/spec/models/concerns/account/silences_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Account::Silences do
-  describe 'Scopes' do
-    describe '.silenced' do
-      let(:silenced_account) { Fabricate :account, silenced: true }
-
-      before { Fabricate :account, silenced: false }
-
-      it 'returns an array of accounts who are silenced' do
-        expect(Account.silenced)
-          .to contain_exactly(silenced_account)
-      end
-    end
-  end
-end
diff --git a/spec/models/concerns/account/suspensions_spec.rb b/spec/models/concerns/account/suspensions_spec.rb
deleted file mode 100644
index 3d71f1b723..0000000000
--- a/spec/models/concerns/account/suspensions_spec.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Account::Suspensions do
-  subject { Fabricate(:account) }
-
-  describe '.suspended' do
-    let!(:suspended_account) { Fabricate :account, suspended: true }
-
-    before { Fabricate :account, suspended: false }
-
-    it 'returns accounts that are suspended' do
-      expect(Account.suspended)
-        .to contain_exactly(suspended_account)
-    end
-  end
-
-  describe '#suspended_locally?' do
-    context 'when the account is not suspended' do
-      it { is_expected.to_not be_suspended_locally }
-    end
-
-    context 'when the account is suspended locally' do
-      before { subject.update!(suspended_at: 1.day.ago, suspension_origin: :local) }
-
-      it { is_expected.to be_suspended_locally }
-    end
-
-    context 'when the account is suspended remotely' do
-      before { subject.update!(suspended_at: 1.day.ago, suspension_origin: :remote) }
-
-      it { is_expected.to_not be_suspended_locally }
-    end
-  end
-
-  describe '#suspend!' do
-    it 'marks the account as suspended and creates a deletion request' do
-      expect { subject.suspend! }
-        .to change(subject, :suspended?).from(false).to(true)
-        .and change(subject, :suspended_locally?).from(false).to(true)
-        .and(change { AccountDeletionRequest.exists?(account: subject) }.from(false).to(true))
-    end
-
-    context 'when the account is of a local user' do
-      subject { local_user_account }
-
-      let!(:local_user_account) { Fabricate(:user, email: 'foo+bar@domain.org').account }
-
-      it 'creates a canonical domain block' do
-        expect { subject.suspend! }
-          .to change { CanonicalEmailBlock.block?(subject.user_email) }.from(false).to(true)
-      end
-
-      context 'when a canonical domain block already exists for that email' do
-        before { Fabricate(:canonical_email_block, email: subject.user_email) }
-
-        it 'does not raise an error' do
-          expect { subject.suspend! }
-            .to_not raise_error
-        end
-      end
-    end
-  end
-end
diff --git a/spec/models/concerns/status/fetch_replies_concern_spec.rb b/spec/models/concerns/status/fetch_replies_concern_spec.rb
deleted file mode 100644
index e9c81d43b1..0000000000
--- a/spec/models/concerns/status/fetch_replies_concern_spec.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Status::FetchRepliesConcern do
-  ActiveRecord.verbose_query_logs = true
-
-  let!(:alice)  { Fabricate(:account, username: 'alice') }
-  let!(:bob)    { Fabricate(:account, username: 'bob', domain: 'other.com') }
-
-  let!(:account) { alice }
-  let!(:status_old) { Fabricate(:status, account: account, fetched_replies_at: 1.year.ago, created_at: 1.year.ago) }
-  let!(:status_fetched_recently) { Fabricate(:status, account: account, fetched_replies_at: 1.second.ago, created_at: 1.year.ago) }
-  let!(:status_created_recently) { Fabricate(:status, account: account, created_at: 1.second.ago) }
-  let!(:status_never_fetched) { Fabricate(:status, account: account, created_at: 1.year.ago) }
-
-  describe 'should_fetch_replies' do
-    let!(:statuses) { Status.should_fetch_replies.all }
-
-    context 'with a local status' do
-      it 'never fetches local replies' do
-        expect(statuses).to eq([])
-      end
-    end
-
-    context 'with a remote status' do
-      let(:account) { bob }
-
-      it 'fetches old statuses' do
-        expect(statuses).to include(status_old)
-      end
-
-      it 'fetches statuses that have never been fetched and weren\'t created recently' do
-        expect(statuses).to include(status_never_fetched)
-      end
-
-      it 'does not fetch statuses that were fetched recently' do
-        expect(statuses).to_not include(status_fetched_recently)
-      end
-
-      it 'does not fetch statuses that were created recently' do
-        expect(statuses).to_not include(status_created_recently)
-      end
-    end
-  end
-
-  describe 'should_not_fetch_replies' do
-    let!(:statuses) { Status.should_not_fetch_replies.all }
-
-    context 'with a local status' do
-      it 'does not fetch local statuses' do
-        expect(statuses).to include(status_old, status_never_fetched, status_fetched_recently, status_never_fetched)
-      end
-    end
-
-    context 'with a remote status' do
-      let(:account) { bob }
-
-      it 'fetches old statuses' do
-        expect(statuses).to_not include(status_old)
-      end
-
-      it 'fetches statuses that have never been fetched and weren\'t created recently' do
-        expect(statuses).to_not include(status_never_fetched)
-      end
-
-      it 'does not fetch statuses that were fetched recently' do
-        expect(statuses).to include(status_fetched_recently)
-      end
-
-      it 'does not fetch statuses that were created recently' do
-        expect(statuses).to include(status_created_recently)
-      end
-    end
-  end
-
-  describe 'unsubscribed' do
-    let!(:spike)  { Fabricate(:account, username: 'spike', domain: 'other.com') }
-    let!(:status) { Fabricate(:status, account: bob, updated_at: 1.day.ago) }
-
-    context 'when the status is from an account with only remote followers after last update' do
-      before do
-        Fabricate(:follow, account: spike, target_account: bob)
-      end
-
-      it 'shows the status as unsubscribed' do
-        expect(Status.unsubscribed).to eq([status])
-      end
-    end
-
-    context 'when the status is from an account with only remote followers before last update' do
-      before do
-        Fabricate(:follow, account: spike, target_account: bob, created_at: 2.days.ago)
-      end
-
-      it 'shows the status as unsubscribed' do
-        expect(Status.unsubscribed).to eq([status])
-      end
-    end
-
-    context 'when status is from account with local followers after last update' do
-      before do
-        Fabricate(:follow, account: alice, target_account: bob)
-      end
-
-      it 'shows the status as unsubscribed' do
-        expect(Status.unsubscribed).to eq([status])
-      end
-    end
-
-    context 'when status is from account with local followers before last update' do
-      before do
-        Fabricate(:follow, account: alice, target_account: bob, created_at: 2.days.ago)
-      end
-
-      it 'does not show the status as unsubscribed' do
-        expect(Status.unsubscribed).to eq([])
-      end
-    end
-
-    context 'when the status has no followers' do
-      it 'shows the status as unsubscribed' do
-        expect(Status.unsubscribed).to eq([status])
-      end
-    end
-  end
-end
diff --git a/spec/models/custom_filter_spec.rb b/spec/models/custom_filter_spec.rb
index 168cbb7c91..afbc420241 100644
--- a/spec/models/custom_filter_spec.rb
+++ b/spec/models/custom_filter_spec.rb
@@ -3,14 +3,23 @@
 require 'rails_helper'
 
 RSpec.describe CustomFilter do
-  include_examples 'Expireable'
-
   describe 'Validations' do
     it { is_expected.to validate_presence_of(:title) }
     it { is_expected.to validate_presence_of(:context) }
 
-    it { is_expected.to_not allow_values([], %w(invalid)).for(:context) }
-    it { is_expected.to allow_values(%w(home)).for(:context) }
+    it 'requires non-empty of context' do
+      record = described_class.new(context: [])
+      record.valid?
+
+      expect(record).to model_have_error_on_field(:context)
+    end
+
+    it 'requires valid context value' do
+      record = described_class.new(context: ['invalid'])
+      record.valid?
+
+      expect(record).to model_have_error_on_field(:context)
+    end
   end
 
   describe 'Normalizations' do
@@ -18,28 +27,4 @@ RSpec.describe CustomFilter do
       it { is_expected.to normalize(:context).from(['home', 'notifications', 'public    ', '']).to(%w(home notifications public)) }
     end
   end
-
-  describe '#expires_in' do
-    subject { custom_filter.expires_in }
-
-    let(:custom_filter) { Fabricate.build(:custom_filter, expires_at: expires_at) }
-
-    context 'when expires_at is nil' do
-      let(:expires_at) { nil }
-
-      it { is_expected.to be_nil }
-    end
-
-    context 'when expires is beyond the end of the range' do
-      let(:expires_at) { described_class::EXPIRATION_DURATIONS.last.from_now + 2.days }
-
-      it { is_expected.to be_nil }
-    end
-
-    context 'when expires is before the start of the range' do
-      let(:expires_at) { described_class::EXPIRATION_DURATIONS.first.from_now - 10.minutes }
-
-      it { is_expected.to eq(described_class::EXPIRATION_DURATIONS.first) }
-    end
-  end
 end
diff --git a/spec/models/domain_allow_spec.rb b/spec/models/domain_allow_spec.rb
index fbb324657e..d8f438f07e 100644
--- a/spec/models/domain_allow_spec.rb
+++ b/spec/models/domain_allow_spec.rb
@@ -6,10 +6,11 @@ RSpec.describe DomainAllow do
   describe 'Validations' do
     it { is_expected.to validate_presence_of(:domain) }
 
-    context 'when a normalized domain exists' do
-      before { Fabricate(:domain_allow, domain: 'にゃん') }
-
-      it { is_expected.to_not allow_value('xn--r9j5b5b').for(:domain) }
+    it 'is invalid if the same normalized domain already exists' do
+      _domain_allow = Fabricate(:domain_allow, domain: 'にゃん')
+      domain_allow_with_normalized_value = Fabricate.build(:domain_allow, domain: 'xn--r9j5b5b')
+      domain_allow_with_normalized_value.valid?
+      expect(domain_allow_with_normalized_value).to model_have_error_on_field(:domain)
     end
   end
 end
diff --git a/spec/models/domain_block_spec.rb b/spec/models/domain_block_spec.rb
index 14f904ea7f..8278454cd7 100644
--- a/spec/models/domain_block_spec.rb
+++ b/spec/models/domain_block_spec.rb
@@ -3,13 +3,14 @@
 require 'rails_helper'
 
 RSpec.describe DomainBlock do
-  describe 'Validations' do
+  describe 'validations' do
     it { is_expected.to validate_presence_of(:domain) }
 
-    context 'when a normalized domain exists' do
-      before { Fabricate(:domain_block, domain: 'にゃん') }
-
-      it { is_expected.to_not allow_value('xn--r9j5b5b').for(:domain) }
+    it 'is invalid if the same normalized domain already exists' do
+      _domain_block = Fabricate(:domain_block, domain: 'にゃん')
+      domain_block_with_normalized_value = Fabricate.build(:domain_block, domain: 'xn--r9j5b5b')
+      domain_block_with_normalized_value.valid?
+      expect(domain_block_with_normalized_value).to model_have_error_on_field(:domain)
     end
   end
 
@@ -104,26 +105,4 @@ RSpec.describe DomainBlock do
       end
     end
   end
-
-  describe '#policies' do
-    subject { domain_block.policies }
-
-    context 'when severity is suspend' do
-      let(:domain_block) { Fabricate.build :domain_block, severity: :suspend }
-
-      it { is_expected.to eq(%i(suspend)) }
-    end
-
-    context 'when severity is noop' do
-      let(:domain_block) { Fabricate.build :domain_block, severity: :noop, reject_media: true }
-
-      it { is_expected.to eq(%i(reject_media)) }
-    end
-
-    context 'when severity is silence' do
-      let(:domain_block) { Fabricate.build :domain_block, severity: :silence, reject_reports: true }
-
-      it { is_expected.to eq(%i(silence reject_reports)) }
-    end
-  end
 end
diff --git a/spec/models/doorkeeper/application_spec.rb b/spec/models/doorkeeper/application_spec.rb
deleted file mode 100644
index e026d90caa..0000000000
--- a/spec/models/doorkeeper/application_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Doorkeeper::Application do
-  describe 'Associations' do
-    it { is_expected.to have_many(:created_users).class_name('User').inverse_of(:created_by_application).with_foreign_key(:created_by_application_id) }
-  end
-
-  describe 'Validations' do
-    it { is_expected.to validate_length_of(:name).is_at_most(described_class::APP_NAME_LIMIT) }
-    it { is_expected.to validate_length_of(:redirect_uri).is_at_most(described_class::APP_REDIRECT_URI_LIMIT) }
-    it { is_expected.to validate_length_of(:website).is_at_most(described_class::APP_WEBSITE_LIMIT) }
-  end
-end
diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb
index 81aaf88585..48e78830dd 100644
--- a/spec/models/export_spec.rb
+++ b/spec/models/export_spec.rb
@@ -103,4 +103,75 @@ RSpec.describe Export do
         )
     end
   end
+
+  describe '#total_storage' do
+    it 'returns the total size of the media attachments' do
+      media_attachment = Fabricate(:media_attachment, account: account)
+      expect(subject.total_storage).to eq media_attachment.file_file_size || 0
+    end
+  end
+
+  describe '#total_statuses' do
+    before { Fabricate.times(2, :status, account: account) }
+
+    it 'returns the total number of statuses' do
+      expect(subject.total_statuses).to eq(2)
+    end
+  end
+
+  describe '#total_bookmarks' do
+    before { Fabricate.times(2, :bookmark, account: account) }
+
+    it 'returns the total number of bookmarks' do
+      expect(subject.total_bookmarks).to eq(2)
+    end
+  end
+
+  describe '#total_follows' do
+    before { target_accounts.each { |target_account| account.follow!(target_account) } }
+
+    it 'returns the total number of the followed accounts' do
+      expect(subject.total_follows).to eq(2)
+    end
+  end
+
+  describe '#total_lists' do
+    before { Fabricate.times(2, :list, account: account) }
+
+    it 'returns the total number of lists' do
+      expect(subject.total_lists).to eq(2)
+    end
+  end
+
+  describe '#total_followers' do
+    before { target_accounts.each { |target_account| target_account.follow!(account) } }
+
+    it 'returns the total number of the follower accounts' do
+      expect(subject.total_followers).to eq(2)
+    end
+  end
+
+  describe '#total_blocks' do
+    before { target_accounts.each { |target_account| account.block!(target_account) } }
+
+    it 'returns the total number of the blocked accounts' do
+      expect(subject.total_blocks).to eq(2)
+    end
+  end
+
+  describe '#total_mutes' do
+    before { target_accounts.each { |target_account| account.mute!(target_account) } }
+
+    it 'returns the total number of the muted accounts' do
+      expect(subject.total_mutes).to eq(2)
+    end
+  end
+
+  describe '#total_domain_blocks' do
+    before { Fabricate.times(2, :account_domain_block, account: account) }
+
+    it 'returns the total number of account domain blocks' do
+      expect(subject.total_domain_blocks).to eq(2)
+    end
+  end
 end
diff --git a/spec/models/fasp/provider_spec.rb b/spec/models/fasp/provider_spec.rb
deleted file mode 100644
index 52df4638fd..0000000000
--- a/spec/models/fasp/provider_spec.rb
+++ /dev/null
@@ -1,209 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Fasp::Provider do
-  include ProviderRequestHelper
-
-  describe '#capabilities' do
-    subject { described_class.new(confirmed: true, capabilities:) }
-
-    let(:capabilities) do
-      [
-        { 'id' => 'one', 'enabled' => false },
-        { 'id' => 'two' },
-      ]
-    end
-
-    it 'returns an array of `Fasp::Capability` objects' do
-      expect(subject.capabilities).to all(be_a(Fasp::Capability))
-    end
-  end
-
-  describe '#capabilities_attributes=' do
-    subject { described_class.new(confirmed: true) }
-
-    let(:capabilities_params) do
-      {
-        '0' => { 'id' => 'one', 'enabled' => '1' },
-        '1' => { 'id' => 'two', 'enabled' => '0' },
-        '2' => { 'id' => 'three' },
-      }
-    end
-
-    it 'sets capabilities from nested form style hash' do
-      subject.capabilities_attributes = capabilities_params
-
-      expect(subject).to be_capability('one')
-      expect(subject).to be_capability('two')
-      expect(subject).to be_capability('three')
-      expect(subject).to be_capability_enabled('one')
-      expect(subject).to_not be_capability_enabled('two')
-      expect(subject).to_not be_capability_enabled('three')
-    end
-  end
-
-  describe '#capability?' do
-    subject { described_class.new(confirmed:, capabilities:) }
-
-    let(:capabilities) do
-      [
-        { 'id' => 'one', 'enabled' => false },
-        { 'id' => 'two', 'enabled' => true },
-      ]
-    end
-
-    context 'when the provider is not confirmed' do
-      let(:confirmed) { false }
-
-      it 'always returns false' do
-        expect(subject.capability?('one')).to be false
-        expect(subject.capability?('two')).to be false
-      end
-    end
-
-    context 'when the provider is confirmed' do
-      let(:confirmed) { true }
-
-      it 'returns true for available and false for missing capabilities' do
-        expect(subject.capability?('one')).to be true
-        expect(subject.capability?('two')).to be true
-        expect(subject.capability?('three')).to be false
-      end
-    end
-  end
-
-  describe '#capability_enabled?' do
-    subject { described_class.new(confirmed:, capabilities:) }
-
-    let(:capabilities) do
-      [
-        { 'id' => 'one', 'enabled' => false },
-        { 'id' => 'two', 'enabled' => true },
-      ]
-    end
-
-    context 'when the provider is not confirmed' do
-      let(:confirmed) { false }
-
-      it 'always returns false' do
-        expect(subject).to_not be_capability_enabled('one')
-        expect(subject).to_not be_capability_enabled('two')
-      end
-    end
-
-    context 'when the provider is confirmed' do
-      let(:confirmed) { true }
-
-      it 'returns true for enabled and false for disabled or missing capabilities' do
-        expect(subject).to_not be_capability_enabled('one')
-        expect(subject).to be_capability_enabled('two')
-        expect(subject).to_not be_capability_enabled('three')
-      end
-    end
-  end
-
-  describe '#server_private_key' do
-    subject { Fabricate(:fasp_provider) }
-
-    it 'returns an OpenSSL::PKey::PKey' do
-      expect(subject.server_private_key).to be_a OpenSSL::PKey::PKey
-    end
-  end
-
-  describe '#server_public_key_base64' do
-    subject { Fabricate(:fasp_provider) }
-
-    it 'returns the server public key base64 encoded' do
-      expect(subject.server_public_key_base64).to eq 'T2RHkakkqAOWEMRYv9OY7LGsuIcAdmBlxuXOKax6sjw='
-    end
-  end
-
-  describe '#provider_public_key_base64=' do
-    subject { Fabricate(:fasp_provider) }
-
-    it 'allows setting the provider public key from a base64 encoded raw key' do
-      subject.provider_public_key_base64 = '9qgjOfWRhozWc9dwx5JmbshizZ7TyPBhYk9+b5tE3e4='
-
-      expect(subject.provider_public_key_pem).to eq "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA9qgjOfWRhozWc9dwx5JmbshizZ7TyPBhYk9+b5tE3e4=\n-----END PUBLIC KEY-----\n"
-    end
-  end
-
-  describe '#provider_public_key' do
-    subject { Fabricate(:fasp_provider) }
-
-    it 'returns an OpenSSL::PKey::PKey' do
-      expect(subject.provider_public_key).to be_a OpenSSL::PKey::PKey
-    end
-  end
-
-  describe '#provider_public_key_raw' do
-    subject { Fabricate(:fasp_provider) }
-
-    it 'returns a string comprised of raw bytes' do
-      expect(subject.provider_public_key_raw).to be_a String
-      expect(subject.provider_public_key_raw.encoding).to eq Encoding::BINARY
-    end
-  end
-
-  describe '#provider_public_key_fingerprint' do
-    subject { Fabricate(:fasp_provider) }
-
-    it 'returns a base64 encoded sha256 hash of the public key' do
-      expect(subject.provider_public_key_fingerprint).to eq '/AmW9EMlVq4o+Qcu9lNfTE8Ss/v9+evMPtyj2R437qE='
-    end
-  end
-
-  describe '#url' do
-    subject { Fabricate(:fasp_provider, base_url: 'https://myprovider.example.com/fasp_base/') }
-
-    it 'returns a full URL for a given path' do
-      url = subject.url('/test_path')
-      expect(url).to eq 'https://myprovider.example.com/fasp_base/test_path'
-    end
-  end
-
-  describe '#update_info!' do
-    subject { Fabricate(:fasp_provider, base_url: 'https://myprov.example.com/fasp/') }
-
-    before do
-      stub_provider_request(subject,
-                            path: '/provider_info',
-                            response_body: {
-                              capabilities: [
-                                { id: 'debug', version: '0.1' },
-                              ],
-                              contactEmail: 'newcontact@example.com',
-                              fediverseAccount: '@newfedi@social.example.com',
-                              privacyPolicy: 'https::///example.com/privacy',
-                              signInUrl: 'https://myprov.example.com/sign_in',
-                            })
-    end
-
-    context 'when setting confirm to `true`' do
-      it 'updates the provider and marks it as `confirmed`' do
-        subject.update_info!(confirm: true)
-
-        expect(subject.contact_email).to eq 'newcontact@example.com'
-        expect(subject.fediverse_account).to eq '@newfedi@social.example.com'
-        expect(subject.privacy_policy).to eq 'https::///example.com/privacy'
-        expect(subject.sign_in_url).to eq 'https://myprov.example.com/sign_in'
-        expect(subject).to be_confirmed
-        expect(subject).to be_persisted
-      end
-    end
-
-    context 'when setting confirm to `false`' do
-      it 'updates the provider but does not mark it as `confirmed`' do
-        subject.update_info!
-
-        expect(subject.contact_email).to eq 'newcontact@example.com'
-        expect(subject.fediverse_account).to eq '@newfedi@social.example.com'
-        expect(subject.privacy_policy).to eq 'https::///example.com/privacy'
-        expect(subject.sign_in_url).to eq 'https://myprov.example.com/sign_in'
-        expect(subject).to_not be_confirmed
-        expect(subject).to be_persisted
-      end
-    end
-  end
-end
diff --git a/spec/models/follow_request_spec.rb b/spec/models/follow_request_spec.rb
index 237875deab..9cccb82903 100644
--- a/spec/models/follow_request_spec.rb
+++ b/spec/models/follow_request_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe FollowRequest do
       follow_request.authorize!
 
       expect(account).to have_received(:follow!).with(target_account, reblogs: true, notify: false, uri: follow_request.uri, languages: nil, bypass_limit: true)
-      expect(MergeWorker).to have_received(:perform_async).with(target_account.id, account.id, 'home')
+      expect(MergeWorker).to have_received(:perform_async).with(target_account.id, account.id)
       expect(follow_request).to have_received(:destroy!)
     end
 
diff --git a/spec/models/follow_spec.rb b/spec/models/follow_spec.rb
index 8684170dcf..f22bd6ea88 100644
--- a/spec/models/follow_spec.rb
+++ b/spec/models/follow_spec.rb
@@ -3,26 +3,27 @@
 require 'rails_helper'
 
 RSpec.describe Follow do
-  describe 'Associations' do
+  let(:alice) { Fabricate(:account, username: 'alice') }
+  let(:bob)   { Fabricate(:account, username: 'bob') }
+
+  describe 'validations' do
+    subject { described_class.new(account: alice, target_account: bob, rate_limit: true) }
+
     it { is_expected.to belong_to(:account).required }
     it { is_expected.to belong_to(:target_account).required }
-  end
 
-  describe 'Validations' do
-    subject { Fabricate.build :follow, rate_limit: true }
+    it 'is invalid if account already follows too many people' do
+      alice.update(following_count: FollowLimitValidator::LIMIT)
 
-    let(:account) { Fabricate(:account) }
-
-    context 'when account follows too many people' do
-      before { account.update(following_count: FollowLimitValidator::LIMIT) }
-
-      it { is_expected.to_not allow_value(account).for(:account).against(:base) }
+      expect(subject).to_not be_valid
+      expect(subject).to model_have_error_on_field(:base)
     end
 
-    context 'when account is on brink of following too many people' do
-      before { account.update(following_count: FollowLimitValidator::LIMIT - 1) }
+    it 'is valid if account is only on the brink of following too many people' do
+      alice.update(following_count: FollowLimitValidator::LIMIT - 1)
 
-      it { is_expected.to allow_value(account).for(:account).against(:base) }
+      expect(subject).to be_valid
+      expect(subject).to_not model_have_error_on_field(:base)
     end
   end
 
@@ -53,58 +54,4 @@ RSpec.describe Follow do
       expect(account.requested?(target_account)).to be true
     end
   end
-
-  describe '#local?' do
-    it { is_expected.to_not be_local }
-  end
-
-  describe 'Callbacks' do
-    describe 'Setting a URI' do
-      context 'when URI exists' do
-        subject { Fabricate.build :follow, uri: 'https://uri/value' }
-
-        it 'does not change' do
-          expect { subject.save }
-            .to not_change(subject, :uri)
-        end
-      end
-
-      context 'when URI is blank' do
-        subject { Fabricate.build :follow, uri: nil }
-
-        it 'populates the value' do
-          expect { subject.save }
-            .to change(subject, :uri).to(be_present)
-        end
-      end
-    end
-
-    describe 'Maintaining counters' do
-      subject { Fabricate.build :follow, account:, target_account: }
-
-      let(:account) { Fabricate :account }
-      let(:target_account) { Fabricate :account }
-
-      before do
-        account.account_stat.update following_count: 123
-        target_account.account_stat.update followers_count: 123
-      end
-
-      describe 'saving the follow' do
-        it 'increments counters' do
-          expect { subject.save }
-            .to change(account, :following_count).by(1)
-            .and(change(target_account, :followers_count).by(1))
-        end
-      end
-
-      describe 'destroying the follow' do
-        it 'decrements counters' do
-          expect { subject.destroy }
-            .to change(account, :following_count).by(-1)
-            .and(change(target_account, :followers_count).by(-1))
-        end
-      end
-    end
-  end
 end
diff --git a/spec/models/form/account_batch_spec.rb b/spec/models/form/account_batch_spec.rb
index 8db9a2a254..26fb1b953a 100644
--- a/spec/models/form/account_batch_spec.rb
+++ b/spec/models/form/account_batch_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Form::AccountBatch do
   describe '#save' do
     subject           { account_batch.save }
 
-    let(:account)     { Fabricate(:admin_user).account }
+    let(:account)     { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
     let(:account_ids) { [] }
     let(:query)       { Account.none }
 
diff --git a/spec/models/form/admin_settings_spec.rb b/spec/models/form/admin_settings_spec.rb
index 899d56703a..6080b9e081 100644
--- a/spec/models/form/admin_settings_spec.rb
+++ b/spec/models/form/admin_settings_spec.rb
@@ -3,52 +3,32 @@
 require 'rails_helper'
 
 RSpec.describe Form::AdminSettings do
-  describe 'Validations' do
+  describe 'validations' do
     describe 'site_contact_username' do
       context 'with no accounts' do
-        it { is_expected.to_not allow_value('Test').for(:site_contact_username) }
+        it 'is not valid' do
+          setting = described_class.new(site_contact_username: 'Test')
+          setting.valid?
+
+          expect(setting).to model_have_error_on_field(:site_contact_username)
+        end
       end
 
       context 'with an account' do
         before { Fabricate(:account, username: 'Glorp') }
 
-        it { is_expected.to_not allow_value('Test').for(:site_contact_username) }
-        it { is_expected.to allow_value('Glorp').for(:site_contact_username) }
-      end
-    end
-  end
+        it 'is not valid when account doesnt match' do
+          setting = described_class.new(site_contact_username: 'Test')
+          setting.valid?
 
-  describe '#save' do
-    describe 'updating digest values' do
-      context 'when updating custom css to real value' do
-        subject { described_class.new(custom_css: css) }
-
-        let(:css) { 'body { color: red; }' }
-        let(:digested) { Digest::SHA256.hexdigest(css) }
-
-        it 'changes relevant digest value' do
-          expect { subject.save }
-            .to(change { Rails.cache.read(:setting_digest_custom_css) }.to(digested))
+          expect(setting).to model_have_error_on_field(:site_contact_username)
         end
-      end
 
-      context 'when updating custom css to empty value' do
-        subject { described_class.new(custom_css: '') }
+        it 'is valid when account matches' do
+          setting = described_class.new(site_contact_username: 'Glorp')
+          setting.valid?
 
-        before { Rails.cache.write(:setting_digest_custom_css, 'previous-value') }
-
-        it 'changes relevant digest value' do
-          expect { subject.save }
-            .to(change { Rails.cache.read(:setting_digest_custom_css) }.to(be_blank))
-        end
-      end
-
-      context 'when updating other fields' do
-        subject { described_class.new(site_contact_email: 'test@example.host') }
-
-        it 'does not update digests' do
-          expect { subject.save }
-            .to(not_change { Rails.cache.read(:setting_digest_custom_css) })
+          expect(setting).to_not model_have_error_on_field(:site_contact_username)
         end
       end
     end
diff --git a/spec/models/form/custom_emoji_batch_spec.rb b/spec/models/form/custom_emoji_batch_spec.rb
index 483ef79345..134699a7d7 100644
--- a/spec/models/form/custom_emoji_batch_spec.rb
+++ b/spec/models/form/custom_emoji_batch_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Form::CustomEmojiBatch do
     subject { described_class.new({ current_account: account }.merge(options)) }
 
     let(:options) { {} }
-    let(:account) { Fabricate(:admin_user).account }
+    let(:account) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
 
     context 'with empty custom_emoji_ids' do
       let(:options) { { custom_emoji_ids: [] } }
diff --git a/spec/models/form/import_spec.rb b/spec/models/form/import_spec.rb
index e1c1538de9..69b38427ef 100644
--- a/spec/models/form/import_spec.rb
+++ b/spec/models/form/import_spec.rb
@@ -61,7 +61,10 @@ RSpec.describe Form::Import do
       let(:import_type) { 'following' }
       let(:import_file) { 'boop.ogg' }
 
-      it { is_expected.to_not allow_value(data).for(:data) }
+      it 'has errors' do
+        # NOTE: not testing more specific error because we don't know the string to match
+        expect(subject).to model_have_error_on_field(:data)
+      end
     end
 
     context 'when importing more follows than allowed' do
@@ -234,26 +237,51 @@ RSpec.describe Form::Import do
       let(:import_file) { file }
       let(:import_mode) { mode }
 
-      before { subject.save }
+      before do
+        subject.save
+      end
+
+      it 'creates the expected rows' do
+        expect(account.bulk_imports.first.rows.pluck(:data)).to match_array(expected_rows)
+      end
 
       context 'with a BulkImport' do
         let(:bulk_import) { account.bulk_imports.first }
 
-        it 'creates a bulk import with correct values' do
-          expect(bulk_import)
-            .to be_present
-            .and have_attributes(
-              type: eq(subject.type),
-              original_filename: eq(subject.data.original_filename),
-              likely_mismatched?: eq(subject.likely_mismatched?),
-              overwrite?: eq(!!subject.overwrite), # rubocop:disable Style/DoubleNegation
-              processed_items: eq(0),
-              imported_items: eq(0),
-              total_items: eq(bulk_import.rows.count),
-              state_unconfirmed?: be(true)
-            )
-          expect(bulk_import.rows.pluck(:data))
-            .to match_array(expected_rows)
+        it 'creates a non-nil bulk import' do
+          expect(bulk_import).to_not be_nil
+        end
+
+        it 'matches the subjects type' do
+          expect(bulk_import.type.to_sym).to eq subject.type.to_sym
+        end
+
+        it 'matches the subjects original filename' do
+          expect(bulk_import.original_filename).to eq subject.data.original_filename
+        end
+
+        it 'matches the subjects likely_mismatched? value' do
+          expect(bulk_import.likely_mismatched?).to eq subject.likely_mismatched?
+        end
+
+        it 'matches the subject overwrite value' do
+          expect(bulk_import.overwrite?).to eq !!subject.overwrite # rubocop:disable Style/DoubleNegation
+        end
+
+        it 'has zero processed items' do
+          expect(bulk_import.processed_items).to eq 0
+        end
+
+        it 'has zero imported items' do
+          expect(bulk_import.imported_items).to eq 0
+        end
+
+        it 'has a correct total_items value' do
+          expect(bulk_import.total_items).to eq bulk_import.rows.count
+        end
+
+        it 'defaults to unconfirmed true' do
+          expect(bulk_import.state_unconfirmed?).to be true
         end
       end
     end
diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb
new file mode 100644
index 0000000000..587e0a9d26
--- /dev/null
+++ b/spec/models/import_spec.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Import do
+  describe 'Validations' do
+    it { is_expected.to validate_presence_of(:type) }
+    it { is_expected.to validate_presence_of(:data) }
+  end
+end
diff --git a/spec/models/invite_spec.rb b/spec/models/invite_spec.rb
index 6363f77a64..4ad589f2c7 100644
--- a/spec/models/invite_spec.rb
+++ b/spec/models/invite_spec.rb
@@ -3,31 +3,6 @@
 require 'rails_helper'
 
 RSpec.describe Invite do
-  include_examples 'Expireable'
-
-  describe 'Associations' do
-    it { is_expected.to belong_to(:user).inverse_of(:invites) }
-    it { is_expected.to have_many(:users).inverse_of(:invite) }
-  end
-
-  describe 'Validations' do
-    it { is_expected.to validate_length_of(:comment).is_at_most(described_class::COMMENT_SIZE_LIMIT) }
-  end
-
-  describe 'Scopes' do
-    describe '.available' do
-      let!(:no_expires) { Fabricate :invite, expires_at: nil }
-      let!(:past_expires) { Fabricate :invite, expires_at: 2.days.ago }
-      let!(:future_expires) { Fabricate :invite, expires_at: 2.days.from_now }
-
-      it 'returns future and non-epiring records' do
-        expect(described_class.available)
-          .to include(no_expires, future_expires)
-          .and not_include(past_expires)
-      end
-    end
-  end
-
   describe '#valid_for_use?' do
     it 'returns true when there are no limitations' do
       invite = Fabricate(:invite, max_uses: nil, expires_at: nil)
@@ -60,26 +35,4 @@ RSpec.describe Invite do
       expect(invite.valid_for_use?).to be false
     end
   end
-
-  describe 'Callbacks' do
-    describe 'Setting the invite code' do
-      context 'when creating a new record' do
-        subject { Fabricate.build :invite }
-
-        it 'sets a code value' do
-          expect { subject.save }
-            .to change(subject, :code).from(be_blank).to(be_present)
-        end
-      end
-
-      context 'when updating a record' do
-        subject { Fabricate :invite }
-
-        it 'does not change the code value' do
-          expect { subject.update(max_uses: 123_456) }
-            .to not_change(subject, :code)
-        end
-      end
-    end
-  end
 end
diff --git a/spec/models/ip_block_spec.rb b/spec/models/ip_block_spec.rb
index 856d55be9d..ac9f5db601 100644
--- a/spec/models/ip_block_spec.rb
+++ b/spec/models/ip_block_spec.rb
@@ -3,17 +3,18 @@
 require 'rails_helper'
 
 RSpec.describe IpBlock do
-  include_examples 'Expireable'
-
-  describe 'Validations' do
-    subject { Fabricate.build :ip_block }
-
+  describe 'validations' do
     it { is_expected.to validate_presence_of(:ip) }
     it { is_expected.to validate_presence_of(:severity) }
 
-    it { is_expected.to validate_uniqueness_of(:ip) }
+    it 'validates ip uniqueness', :aggregate_failures do
+      described_class.create!(ip: '127.0.0.1', severity: :no_access)
 
-    it { is_expected.to allow_values(:sign_up_requires_approval, :sign_up_block, :no_access).for(:severity) }
+      ip_block = described_class.new(ip: '127.0.0.1', severity: :no_access)
+
+      expect(ip_block).to_not be_valid
+      expect(ip_block).to model_have_error_on_field(:ip)
+    end
   end
 
   describe '#to_log_human_identifier' do
diff --git a/spec/models/login_activity_spec.rb b/spec/models/login_activity_spec.rb
deleted file mode 100644
index 5b7935e8ba..0000000000
--- a/spec/models/login_activity_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe LoginActivity do
-  include_examples 'BrowserDetection'
-
-  describe 'Associations' do
-    it { is_expected.to belong_to(:user).required }
-  end
-
-  describe 'Validations' do
-    subject { Fabricate.build :login_activity }
-
-    it { is_expected.to define_enum_for(:authentication_method).backed_by_column_of_type(:string) }
-  end
-end
diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb
index 43e9ed087b..5f91ae0967 100644
--- a/spec/models/media_attachment_spec.rb
+++ b/spec/models/media_attachment_spec.rb
@@ -295,21 +295,12 @@ RSpec.describe MediaAttachment, :attachment_processing do
     end
 
     it 'queues CacheBusterWorker jobs' do
-      original_url = media.file.url(:original)
-      small_url = media.file.url(:small)
+      original_path = media.file.path(:original)
+      small_path = media.file.path(:small)
 
       expect { media.destroy }
-        .to enqueue_sidekiq_job(CacheBusterWorker).with(original_url)
-        .and enqueue_sidekiq_job(CacheBusterWorker).with(small_url)
-    end
-
-    context 'with a missing remote attachment' do
-      let(:media) { Fabricate(:media_attachment, remote_url: 'https://example.com/foo.png', file: nil) }
-
-      it 'does not queue CacheBusterWorker jobs' do
-        expect { media.destroy }
-          .to_not enqueue_sidekiq_job(CacheBusterWorker)
-      end
+        .to enqueue_sidekiq_job(CacheBusterWorker).with(original_path)
+        .and enqueue_sidekiq_job(CacheBusterWorker).with(small_path)
     end
   end
 
diff --git a/spec/models/mention_spec.rb b/spec/models/mention_spec.rb
index 67c22b5d1f..3a9b9fddf2 100644
--- a/spec/models/mention_spec.rb
+++ b/spec/models/mention_spec.rb
@@ -3,14 +3,8 @@
 require 'rails_helper'
 
 RSpec.describe Mention do
-  describe 'Associations' do
+  describe 'validations' do
     it { is_expected.to belong_to(:account).required }
     it { is_expected.to belong_to(:status).required }
   end
-
-  describe 'Validations' do
-    subject { Fabricate.build :mention }
-
-    it { is_expected.to validate_uniqueness_of(:account_id).scoped_to(:status_id) }
-  end
 end
diff --git a/spec/models/mute_spec.rb b/spec/models/mute_spec.rb
deleted file mode 100644
index 33aa4f15dc..0000000000
--- a/spec/models/mute_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Mute do
-  include_examples 'Expireable'
-
-  describe 'Associations' do
-    it { is_expected.to belong_to(:account).required }
-    it { is_expected.to belong_to(:target_account).required }
-  end
-
-  describe 'Validations' do
-    subject { Fabricate.build :mute }
-
-    it { is_expected.to validate_uniqueness_of(:account_id).scoped_to(:target_account_id) }
-  end
-end
diff --git a/spec/models/poll_spec.rb b/spec/models/poll_spec.rb
index 3288119546..66f521ab3f 100644
--- a/spec/models/poll_spec.rb
+++ b/spec/models/poll_spec.rb
@@ -3,7 +3,32 @@
 require 'rails_helper'
 
 RSpec.describe Poll do
-  include_examples 'Expireable'
+  describe 'Scopes' do
+    let(:status) { Fabricate(:status) }
+    let(:attached_poll) { Fabricate(:poll, status: status) }
+    let(:not_attached_poll) do
+      Fabricate(:poll).tap do |poll|
+        poll.status = nil
+        poll.save(validate: false)
+      end
+    end
+
+    describe '.attached' do
+      it 'finds the correct records' do
+        results = described_class.attached
+
+        expect(results).to eq([attached_poll])
+      end
+    end
+
+    describe '.unattached' do
+      it 'finds the correct records' do
+        results = described_class.unattached
+
+        expect(results).to eq([not_attached_poll])
+      end
+    end
+  end
 
   describe '#reset_votes!' do
     let(:poll) { Fabricate :poll, cached_tallies: [2, 3], votes_count: 5, voters_count: 5 }
diff --git a/spec/models/preview_card_spec.rb b/spec/models/preview_card_spec.rb
index 0fe76c37b0..c0bc2b6e0e 100644
--- a/spec/models/preview_card_spec.rb
+++ b/spec/models/preview_card_spec.rb
@@ -9,10 +9,26 @@ RSpec.describe PreviewCard do
     end
   end
 
-  describe 'Validations' do
-    describe 'url' do
-      it { is_expected.to allow_values('http://example.host/path', 'https://example.host/path').for(:url) }
-      it { is_expected.to_not allow_value('javascript:alert()').for(:url) }
+  describe 'validations' do
+    describe 'urls' do
+      it 'allows http schemes' do
+        record = described_class.new(url: 'http://example.host/path')
+
+        expect(record).to be_valid
+      end
+
+      it 'allows https schemes' do
+        record = described_class.new(url: 'https://example.host/path')
+
+        expect(record).to be_valid
+      end
+
+      it 'does not allow javascript: schemes' do
+        record = described_class.new(url: 'javascript:alert()')
+
+        expect(record).to_not be_valid
+        expect(record).to model_have_error_on_field(:url)
+      end
     end
   end
 end
diff --git a/spec/models/preview_card_trend_spec.rb b/spec/models/preview_card_trend_spec.rb
deleted file mode 100644
index a5cb159af3..0000000000
--- a/spec/models/preview_card_trend_spec.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe PreviewCardTrend do
-  include_examples 'RankedTrend'
-
-  describe 'Associations' do
-    it { is_expected.to belong_to(:preview_card).required }
-  end
-end
diff --git a/spec/models/report_note_spec.rb b/spec/models/report_note_spec.rb
index 0d1c0f619e..417971c9a1 100644
--- a/spec/models/report_note_spec.rb
+++ b/spec/models/report_note_spec.rb
@@ -3,24 +3,29 @@
 require 'rails_helper'
 
 RSpec.describe ReportNote do
-  describe 'Scopes' do
-    describe '.chronological' do
-      it 'returns report notes oldest to newest' do
-        report = Fabricate(:report)
-        note1 = Fabricate(:report_note, report: report)
-        note2 = Fabricate(:report_note, report: report)
+  describe 'chronological scope' do
+    it 'returns report notes oldest to newest' do
+      report = Fabricate(:report)
+      note1 = Fabricate(:report_note, report: report)
+      note2 = Fabricate(:report_note, report: report)
 
-        expect(report.notes.chronological).to eq [note1, note2]
-      end
+      expect(report.notes.chronological).to eq [note1, note2]
     end
   end
 
-  describe 'Validations' do
-    subject { Fabricate.build :report_note }
+  describe 'validations' do
+    it 'is invalid if the content is empty' do
+      report = Fabricate.build(:report_note, content: '')
+      expect(report.valid?).to be false
+    end
 
-    describe 'content' do
-      it { is_expected.to_not allow_value('').for(:content) }
-      it { is_expected.to validate_length_of(:content).is_at_most(described_class::CONTENT_SIZE_LIMIT) }
+    it 'is invalid if content is longer than character limit' do
+      report = Fabricate.build(:report_note, content: comment_over_limit)
+      expect(report.valid?).to be false
+    end
+
+    def comment_over_limit
+      Faker::Lorem.paragraph_by_chars(number: described_class::CONTENT_SIZE_LIMIT * 2)
     end
   end
 end
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index 0bee7dfed1..a0e4f6fafd 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -105,64 +105,28 @@ RSpec.describe Report do
   describe 'history' do
     subject(:action_logs) { report.history }
 
-    let(:report) { Fabricate(:report, target_account_id: target_account.id, status_ids: [status.id]) }
+    let(:report) { Fabricate(:report, target_account_id: target_account.id, status_ids: [status.id], created_at: 3.days.ago, updated_at: 1.day.ago) }
     let(:target_account) { Fabricate(:account) }
     let(:status) { Fabricate(:status) }
-    let(:account_warning) { Fabricate(:account_warning, report_id: report.id) }
 
-    let!(:matched_type_account_warning) { Fabricate(:action_log, target_type: 'AccountWarning', target_id: account_warning.id) }
-    let!(:matched_type_account) { Fabricate(:action_log, target_type: 'Account', target_id: report.target_account_id) }
-    let!(:matched_type_report) { Fabricate(:action_log, target_type: 'Report', target_id: report.id) }
-    let!(:matched_type_status) { Fabricate(:action_log, target_type: 'Status', target_id: status.id) }
+    before do
+      Fabricate(:action_log, target_type: 'Report', account_id: target_account.id, target_id: report.id, created_at: 2.days.ago)
+      Fabricate(:action_log, target_type: 'Account', account_id: target_account.id, target_id: report.target_account_id, created_at: 2.days.ago)
+      Fabricate(:action_log, target_type: 'Status', account_id: target_account.id, target_id: status.id, created_at: 2.days.ago)
+    end
 
-    let!(:unmatched_type_account_warning) { Fabricate(:action_log, target_type: 'AccountWarning') }
-    let!(:unmatched_type_account) { Fabricate(:action_log, target_type: 'Account') }
-    let!(:unmatched_type_report) { Fabricate(:action_log, target_type: 'Report') }
-    let!(:unmatched_type_status) { Fabricate(:action_log, target_type: 'Status') }
-
-    it 'returns expected logs' do
-      expect(action_logs)
-        .to have_attributes(count: 4)
-        .and include(matched_type_account_warning)
-        .and include(matched_type_account)
-        .and include(matched_type_report)
-        .and include(matched_type_status)
-        .and not_include(unmatched_type_account_warning)
-        .and not_include(unmatched_type_account)
-        .and not_include(unmatched_type_report)
-        .and not_include(unmatched_type_status)
+    it 'returns right logs' do
+      expect(action_logs.count).to eq 3
     end
   end
 
-  describe '#unresolved_siblings?' do
-    subject { Fabricate :report }
-
-    context 'when the target account has other unresolved reports' do
-      before { Fabricate :report, action_taken_at: nil, target_account: subject.target_account }
-
-      it { is_expected.to be_unresolved_siblings }
-    end
-
-    context 'when the target account has a resolved report' do
-      before { Fabricate :report, action_taken_at: 3.days.ago, target_account: subject.target_account }
-
-      it { is_expected.to_not be_unresolved_siblings }
-    end
-
-    context 'when the target account has no other reports' do
-      before { described_class.where(target_account: subject.target_account).destroy_all }
-
-      it { is_expected.to_not be_unresolved_siblings }
-    end
-  end
-
-  describe 'Validations' do
+  describe 'validations' do
     let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
 
     it 'is invalid if comment is longer than character limit and reporter is local' do
-      report = Fabricate.build(:report)
-
-      expect(report).to_not allow_value(comment_over_limit).for(:comment)
+      report = Fabricate.build(:report, comment: comment_over_limit)
+      expect(report.valid?).to be false
+      expect(report).to model_have_error_on_field(:comment)
     end
 
     it 'is valid if comment is longer than character limit and reporter is not local' do
@@ -171,16 +135,16 @@ RSpec.describe Report do
     end
 
     it 'is invalid if it references invalid rules' do
-      report = Fabricate.build(:report, category: :violation)
-
-      expect(report).to_not allow_value([-1]).for(:rule_ids)
+      report = Fabricate.build(:report, category: :violation, rule_ids: [-1])
+      expect(report.valid?).to be false
+      expect(report).to model_have_error_on_field(:rule_ids)
     end
 
     it 'is invalid if it references rules but category is not "violation"' do
       rule = Fabricate(:rule)
-      report = Fabricate.build(:report, category: :spam)
-
-      expect(report).to_not allow_value(rule.id).for(:rule_ids)
+      report = Fabricate.build(:report, category: :spam, rule_ids: rule.id)
+      expect(report.valid?).to be false
+      expect(report).to model_have_error_on_field(:rule_ids)
     end
 
     def comment_over_limit
diff --git a/spec/models/session_activation_spec.rb b/spec/models/session_activation_spec.rb
index bb9b3c785f..bed411c369 100644
--- a/spec/models/session_activation_spec.rb
+++ b/spec/models/session_activation_spec.rb
@@ -3,7 +3,39 @@
 require 'rails_helper'
 
 RSpec.describe SessionActivation do
-  include_examples 'BrowserDetection'
+  describe '#detection' do
+    let(:session_activation) { Fabricate(:session_activation, user_agent: 'Chrome/62.0.3202.89') }
+
+    it 'sets a Browser instance as detection' do
+      expect(session_activation.detection).to be_a Browser::Chrome
+    end
+  end
+
+  describe '#browser' do
+    before do
+      allow(session_activation).to receive(:detection).and_return(detection)
+    end
+
+    let(:detection)          { instance_double(Browser::Chrome, id: 1) }
+    let(:session_activation) { Fabricate(:session_activation) }
+
+    it 'returns detection.id' do
+      expect(session_activation.browser).to be 1
+    end
+  end
+
+  describe '#platform' do
+    before do
+      allow(session_activation).to receive(:detection).and_return(detection)
+    end
+
+    let(:session_activation) { Fabricate(:session_activation) }
+    let(:detection)          { instance_double(Browser::Chrome, platform: instance_double(Browser::Platform, id: 1)) }
+
+    it 'returns detection.platform.id' do
+      expect(session_activation.platform).to be 1
+    end
+  end
 
   describe '.active?' do
     subject { described_class.active?(id) }
diff --git a/spec/models/software_update_spec.rb b/spec/models/software_update_spec.rb
index 8c698e51b3..0a494b0c4c 100644
--- a/spec/models/software_update_spec.rb
+++ b/spec/models/software_update_spec.rb
@@ -3,83 +3,6 @@
 require 'rails_helper'
 
 RSpec.describe SoftwareUpdate do
-  describe 'Scopes' do
-    describe '.urgent' do
-      let!(:urgent_update) { Fabricate :software_update, urgent: true }
-      let!(:non_urgent_update) { Fabricate :software_update, urgent: false }
-
-      it 'returns records that are urgent' do
-        expect(described_class.urgent)
-          .to include(urgent_update)
-          .and not_include(non_urgent_update)
-      end
-    end
-  end
-
-  describe '.by_version' do
-    let!(:latest_update) { Fabricate :software_update, version: '4.0.0' }
-    let!(:older_update) { Fabricate :software_update, version: '3.0.0' }
-
-    it 'returns record in gem version order' do
-      expect(described_class.by_version)
-        .to eq([older_update, latest_update])
-    end
-  end
-
-  describe '#pending?' do
-    subject { described_class.new(version: update_version) }
-
-    before { allow(Mastodon::Version).to receive(:gem_version).and_return(Gem::Version.new(mastodon_version)) }
-
-    context 'when the runtime version is older than the update' do
-      let(:mastodon_version) { '4.0.0' }
-      let(:update_version) { '5.0.0' }
-
-      it { is_expected.to be_pending }
-    end
-
-    context 'when the runtime version is newer than the update' do
-      let(:mastodon_version) { '6.0.0' }
-      let(:update_version) { '5.0.0' }
-
-      it { is_expected.to_not be_pending }
-    end
-
-    context 'when the runtime version is same as the update' do
-      let(:mastodon_version) { '4.0.0' }
-      let(:update_version) { '4.0.0' }
-
-      it { is_expected.to_not be_pending }
-    end
-  end
-
-  describe '#outdated?' do
-    subject { described_class.new(version: update_version) }
-
-    before { allow(Mastodon::Version).to receive(:gem_version).and_return(Gem::Version.new(mastodon_version)) }
-
-    context 'when the runtime version is older than the update' do
-      let(:mastodon_version) { '4.0.0' }
-      let(:update_version) { '5.0.0' }
-
-      it { is_expected.to_not be_outdated }
-    end
-
-    context 'when the runtime version is newer than the update' do
-      let(:mastodon_version) { '6.0.0' }
-      let(:update_version) { '5.0.0' }
-
-      it { is_expected.to be_outdated }
-    end
-
-    context 'when the runtime version is same as the update' do
-      let(:mastodon_version) { '4.0.0' }
-      let(:update_version) { '4.0.0' }
-
-      it { is_expected.to be_outdated }
-    end
-  end
-
   describe '.pending_to_a' do
     before do
       allow(Mastodon::Version).to receive(:gem_version).and_return(Gem::Version.new(mastodon_version))
diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb
index 1501d43cc4..da375009ae 100644
--- a/spec/models/status_pin_spec.rb
+++ b/spec/models/status_pin_spec.rb
@@ -3,100 +3,70 @@
 require 'rails_helper'
 
 RSpec.describe StatusPin do
-  describe 'Validations' do
-    subject { Fabricate.build :status_pin }
+  describe 'validations' do
+    it 'allows pins of own statuses' do
+      account = Fabricate(:account)
+      status  = Fabricate(:status, account: account)
 
-    context 'with an account pinning statuses' do
-      subject { Fabricate.build :status_pin, account: account }
-
-      let(:account) { Fabricate(:account) }
-
-      context 'with a self-owned status' do
-        let(:status) { Fabricate(:status, account: account) }
-
-        it { is_expected.to allow_value(status).for(:status) }
-      end
-
-      context 'with a status from someone else' do
-        let(:status) { Fabricate(:status) }
-
-        it { is_expected.to_not allow_value(status).for(:status).against(:base) }
-      end
-
-      context 'with a reblog status' do
-        let(:status) { Fabricate(:status, reblog: Fabricate(:status, account: account)) }
-
-        it { is_expected.to_not allow_value(status).for(:status).against(:base) }
-      end
-
-      context 'with a private status' do
-        let(:status) { Fabricate(:status, account: account, visibility: :private) }
-
-        it { is_expected.to allow_value(status).for(:status).against(:base) }
-      end
-
-      context 'with a direct status' do
-        let(:status) { Fabricate(:status, account: account, visibility: :direct) }
-
-        it { is_expected.to_not allow_value(status).for(:status).against(:base) }
-      end
+      expect(described_class.new(account: account, status: status).save).to be true
     end
 
-    context 'with a validator pin limit' do
+    it 'does not allow pins of statuses by someone else' do
+      account = Fabricate(:account)
+      status  = Fabricate(:status)
+
+      expect(described_class.new(account: account, status: status).save).to be false
+    end
+
+    it 'does not allow pins of reblogs' do
+      account = Fabricate(:account)
+      status  = Fabricate(:status, account: account)
+      reblog  = Fabricate(:status, reblog: status)
+
+      expect(described_class.new(account: account, status: reblog).save).to be false
+    end
+
+    it 'does allow pins of direct statuses' do
+      account = Fabricate(:account)
+      status  = Fabricate(:status, account: account, visibility: :private)
+
+      expect(described_class.new(account: account, status: status).save).to be true
+    end
+
+    it 'does not allow pins of direct statuses' do
+      account = Fabricate(:account)
+      status  = Fabricate(:status, account: account, visibility: :direct)
+
+      expect(described_class.new(account: account, status: status).save).to be false
+    end
+
+    context 'with a pin limit' do
       before { stub_const('StatusPinValidator::PIN_LIMIT', 2) }
 
-      context 'with a local account at the limit' do
-        let(:account) { Fabricate :account }
+      it 'does not allow pins above the max' do
+        account = Fabricate(:account)
 
-        before { Fabricate.times(StatusPinValidator::PIN_LIMIT, :status_pin, account: account) }
+        Fabricate.times(StatusPinValidator::PIN_LIMIT, :status_pin, account: account)
 
-        it { is_expected.to_not allow_value(account).for(:account).against(:base).with_message(I18n.t('statuses.pin_errors.limit')) }
+        pin = described_class.new(account: account, status: Fabricate(:status, account: account))
+        expect(pin.save)
+          .to be(false)
+
+        expect(pin.errors[:base])
+          .to contain_exactly(I18n.t('statuses.pin_errors.limit'))
       end
 
-      context 'with a remote account at the limit' do
-        let(:account) { Fabricate :account, domain: 'remote.test' }
+      it 'allows pins above the max for remote accounts' do
+        account = Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/')
 
-        before { Fabricate.times(StatusPinValidator::PIN_LIMIT, :status_pin, account: account) }
+        Fabricate.times(StatusPinValidator::PIN_LIMIT, :status_pin, account: account)
 
-        it { is_expected.to allow_value(account).for(:account) }
-      end
-    end
-  end
+        pin = described_class.new(account: account, status: Fabricate(:status, account: account))
+        expect(pin.save)
+          .to be(true)
 
-  describe 'Callbacks' do
-    describe 'Invalidating status via policy' do
-      subject { Fabricate :status_pin, status: Fabricate(:status, account: account), account: account }
-
-      context 'with a local account that owns the status and has a policy' do
-        let(:account) { Fabricate :account, domain: nil }
-
-        before do
-          Fabricate :account_statuses_cleanup_policy, account: account
-          account.statuses_cleanup_policy.record_last_inspected(subject.status.id + 1_024)
-        end
-
-        it 'calls the invalidator on destroy' do
-          expect { subject.destroy }
-            .to change(account.statuses_cleanup_policy, :last_inspected)
-        end
-      end
-
-      context 'with a local account that owns the status and does not have a policy' do
-        let(:account) { Fabricate :account, domain: nil }
-
-        it 'does not call the invalidator on destroy' do
-          expect { subject.destroy }
-            .to_not change(account, :updated_at)
-        end
-      end
-
-      context 'with a remote account' do
-        let(:account) { Fabricate :account, domain: 'host.example' }
-
-        it 'does not call the invalidator on destroy' do
-          expect { subject.destroy }
-            .to_not change(account, :updated_at)
-        end
+        expect(pin.errors[:base])
+          .to be_empty
       end
     end
   end
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 13acb7d909..7e757f0759 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -9,8 +9,6 @@ RSpec.describe Status do
   let(:bob)   { Fabricate(:account, username: 'bob') }
   let(:other) { Fabricate(:status, account: bob, text: 'Skulls for the skull god! The enemy\'s gates are sideways!') }
 
-  include_examples 'Status::Visibility'
-
   describe '#local?' do
     it 'returns true when no remote URI is set' do
       expect(subject.local?).to be true
@@ -86,6 +84,178 @@ RSpec.describe Status do
     end
   end
 
+  describe '#hidden?' do
+    context 'when private_visibility?' do
+      it 'returns true' do
+        subject.visibility = :private
+        expect(subject.hidden?).to be true
+      end
+    end
+
+    context 'when direct_visibility?' do
+      it 'returns true' do
+        subject.visibility = :direct
+        expect(subject.hidden?).to be true
+      end
+    end
+
+    context 'when public_visibility?' do
+      it 'returns false' do
+        subject.visibility = :public
+        expect(subject.hidden?).to be false
+      end
+    end
+
+    context 'when unlisted_visibility?' do
+      it 'returns false' do
+        subject.visibility = :unlisted
+        expect(subject.hidden?).to be false
+      end
+    end
+  end
+
+  describe '#compute_searchability' do
+    subject { Fabricate(:status, account: account, searchability: status_searchability) }
+
+    let(:account_searchability) { :public }
+    let(:status_searchability) { :public }
+    let(:account_domain) { 'example.com' }
+    let(:silenced_at) { nil }
+    let(:account) { Fabricate(:account, domain: account_domain, searchability: account_searchability, silenced_at: silenced_at) }
+
+    context 'when public-public' do
+      it 'returns public' do
+        expect(subject.compute_searchability).to eq 'public'
+      end
+    end
+
+    context 'when public-public but silenced' do
+      let(:silenced_at) { Time.now.utc }
+
+      it 'returns private' do
+        expect(subject.compute_searchability).to eq 'private'
+      end
+    end
+
+    context 'when public-public_unlisted but silenced' do
+      let(:silenced_at) { Time.now.utc }
+      let(:status_searchability) { :public_unlisted }
+
+      it 'returns private' do
+        expect(subject.compute_searchability).to eq 'private'
+      end
+    end
+
+    context 'when public-public_unlisted' do
+      let(:status_searchability) { :public_unlisted }
+
+      it 'returns public' do
+        expect(subject.compute_searchability).to eq 'public'
+      end
+
+      it 'returns public_unlisted for local' do
+        expect(subject.compute_searchability_local).to eq 'public_unlisted'
+      end
+    end
+
+    context 'when public-private' do
+      let(:status_searchability) { :private }
+
+      it 'returns private' do
+        expect(subject.compute_searchability).to eq 'private'
+      end
+    end
+
+    context 'when public-direct' do
+      let(:status_searchability) { :direct }
+
+      it 'returns direct' do
+        expect(subject.compute_searchability).to eq 'direct'
+      end
+    end
+
+    context 'when private-public' do
+      let(:account_searchability) { :private }
+
+      it 'returns private' do
+        expect(subject.compute_searchability).to eq 'private'
+      end
+    end
+
+    context 'when direct-public' do
+      let(:account_searchability) { :direct }
+
+      it 'returns direct' do
+        expect(subject.compute_searchability).to eq 'direct'
+      end
+    end
+
+    context 'when limited-public' do
+      let(:account_searchability) { :limited }
+
+      it 'returns limited' do
+        expect(subject.compute_searchability).to eq 'limited'
+      end
+    end
+
+    context 'when private-limited' do
+      let(:account_searchability) { :private }
+      let(:status_searchability) { :limited }
+
+      it 'returns limited' do
+        expect(subject.compute_searchability).to eq 'limited'
+      end
+    end
+
+    context 'when private-public of local account' do
+      let(:account_searchability) { :private }
+      let(:account_domain) { nil }
+      let(:status_searchability) { :public }
+
+      it 'returns public' do
+        expect(subject.compute_searchability).to eq 'public'
+      end
+    end
+
+    context 'when direct-public of local account' do
+      let(:account_searchability) { :direct }
+      let(:account_domain) { nil }
+      let(:status_searchability) { :public }
+
+      it 'returns public' do
+        expect(subject.compute_searchability).to eq 'public'
+      end
+    end
+
+    context 'when limited-public of local account' do
+      let(:account_searchability) { :limited }
+      let(:account_domain) { nil }
+      let(:status_searchability) { :public }
+
+      it 'returns public' do
+        expect(subject.compute_searchability).to eq 'public'
+      end
+    end
+
+    context 'when public-public_unlisted of local account' do
+      let(:account_searchability) { :public }
+      let(:account_domain) { nil }
+      let(:status_searchability) { :public_unlisted }
+
+      it 'returns public' do
+        expect(subject.compute_searchability).to eq 'public'
+      end
+
+      it 'returns public_unlisted for local' do
+        expect(subject.compute_searchability_local).to eq 'public_unlisted'
+      end
+
+      it 'returns private for activitypub' do
+        expect(subject.compute_searchability_activitypub).to eq 'private'
+      end
+    end
+  end
+
   describe '#quote' do
     let(:target_status) { Fabricate(:status) }
     let(:quote) { true }
@@ -169,31 +339,6 @@ RSpec.describe Status do
     end
   end
 
-  describe '#untrusted_reblogs_count' do
-    before do
-      alice.update(domain: 'example.com')
-      subject.status_stat.tap do |status_stat|
-        status_stat.untrusted_reblogs_count = 0
-        status_stat.save
-      end
-      subject.save
-    end
-
-    it 'is incremented by the number of reblogs' do
-      Fabricate(:status, account: bob, reblog: subject)
-      Fabricate(:status, account: alice, reblog: subject)
-
-      expect(subject.untrusted_reblogs_count).to eq 2
-    end
-
-    it 'is decremented when reblog is removed' do
-      reblog = Fabricate(:status, account: bob, reblog: subject)
-      expect(subject.untrusted_reblogs_count).to eq 1
-      reblog.destroy
-      expect(subject.untrusted_reblogs_count).to eq 0
-    end
-  end
-
   describe '#replies_count' do
     it 'is the number of replies' do
       Fabricate(:status, account: bob, thread: subject)
@@ -224,31 +369,6 @@ RSpec.describe Status do
     end
   end
 
-  describe '#untrusted_favourites_count' do
-    before do
-      alice.update(domain: 'example.com')
-      subject.status_stat.tap do |status_stat|
-        status_stat.untrusted_favourites_count = 0
-        status_stat.save
-      end
-      subject.save
-    end
-
-    it 'is incremented by favorites' do
-      Fabricate(:favourite, account: bob, status: subject)
-      Fabricate(:favourite, account: alice, status: subject)
-
-      expect(subject.untrusted_favourites_count).to eq 2
-    end
-
-    it 'is decremented when favourite is removed' do
-      favourite = Fabricate(:favourite, account: bob, status: subject)
-      expect(subject.untrusted_favourites_count).to eq 1
-      favourite.destroy
-      expect(subject.untrusted_favourites_count).to eq 0
-    end
-  end
-
   describe '#proper' do
     it 'is itself for original statuses' do
       expect(subject.proper).to eq subject
@@ -593,53 +713,11 @@ RSpec.describe Status do
     end
   end
 
-  describe 'Validations' do
-    context 'with a remote account' do
-      subject { Fabricate.build :status, account: remote_account }
-
-      let(:remote_account) { Fabricate :account, domain: 'example.com' }
-
-      it { is_expected.to_not allow_value('').for(:uri) }
-    end
-  end
-
-  describe 'Callbacks' do
-    describe 'Stripping content when required' do
-      context 'with a remote account' do
-        subject { Fabricate.build :status, local: false, account:, text: '   text   ', spoiler_text: '   spoiler   ' }
-
-        let(:account) { Fabricate.build :account, domain: 'host.example' }
-
-        it 'preserves content' do
-          expect { subject.valid? }
-            .to not_change(subject, :text)
-            .and not_change(subject, :spoiler_text)
-        end
-      end
-
-      context 'with a local account' do
-        let(:account) { Fabricate.build :account, domain: nil }
-
-        context 'with populated fields' do
-          subject { Fabricate.build :status, local: true, account:, text: '   text   ', spoiler_text: '   spoiler   ' }
-
-          it 'strips content' do
-            expect { subject.valid? }
-              .to change(subject, :text).to('text')
-              .and change(subject, :spoiler_text).to('spoiler')
-          end
-        end
-
-        context 'with empty fields' do
-          subject { Fabricate.build :status, local: true, account:, text: nil, spoiler_text: nil }
-
-          it 'preserves content' do
-            expect { subject.valid? }
-              .to not_change(subject, :text)
-              .and not_change(subject, :spoiler_text)
-          end
-        end
-      end
+  describe 'validation' do
+    it 'disallow empty uri for remote status' do
+      alice.update(domain: 'example.com')
+      status = Fabricate.build(:status, uri: '', account: alice)
+      expect(status).to model_have_error_on_field(:uri)
     end
   end
 
diff --git a/spec/models/status_trend_spec.rb b/spec/models/status_trend_spec.rb
deleted file mode 100644
index 50fb9b5f5c..0000000000
--- a/spec/models/status_trend_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe StatusTrend do
-  include_examples 'RankedTrend'
-
-  describe 'Associations' do
-    it { is_expected.to belong_to(:account).required }
-    it { is_expected.to belong_to(:status).required }
-  end
-end
diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb
index a1cc6a064f..18dd26be94 100644
--- a/spec/models/tag_spec.rb
+++ b/spec/models/tag_spec.rb
@@ -5,39 +5,7 @@ require 'rails_helper'
 RSpec.describe Tag do
   include_examples 'Reviewable'
 
-  describe 'Validations' do
-    describe 'name' do
-      context 'with a new record' do
-        subject { Fabricate.build :tag, name: 'original' }
-
-        it { is_expected.to allow_value('changed').for(:name) }
-      end
-
-      context 'with an existing record' do
-        subject { Fabricate :tag, name: 'original' }
-
-        it { is_expected.to_not allow_value('changed').for(:name).with_message(previous_name_error_message) }
-      end
-    end
-
-    describe 'display_name' do
-      context 'with a new record' do
-        subject { Fabricate.build :tag, name: 'original', display_name: 'OriginalDisplayName' }
-
-        it { is_expected.to allow_value('ChangedDisplayName').for(:display_name) }
-      end
-
-      context 'with an existing record' do
-        subject { Fabricate :tag, name: 'original', display_name: 'OriginalDisplayName' }
-
-        it { is_expected.to_not allow_value('ChangedDisplayName').for(:display_name).with_message(previous_name_error_message) }
-      end
-    end
-
-    def previous_name_error_message
-      I18n.t('tags.does_not_match_previous_name')
-    end
-
+  describe 'validations' do
     it 'invalid with #' do
       expect(described_class.new(name: '#hello_world')).to_not be_valid
     end
diff --git a/spec/models/tag_trend_spec.rb b/spec/models/tag_trend_spec.rb
deleted file mode 100644
index 37b50686db..0000000000
--- a/spec/models/tag_trend_spec.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe TagTrend do
-  include_examples 'RankedTrend'
-
-  describe 'Associations' do
-    it { is_expected.to belong_to(:tag).required }
-  end
-end
diff --git a/spec/models/terms_of_service_spec.rb b/spec/models/terms_of_service_spec.rb
deleted file mode 100644
index d32ba4e642..0000000000
--- a/spec/models/terms_of_service_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe TermsOfService do
-  describe '#scope_for_notification' do
-    subject { terms_of_service.scope_for_notification }
-
-    let(:published_at) { Time.now.utc }
-    let(:terms_of_service) { Fabricate(:terms_of_service, published_at: published_at) }
-    let(:user_before) { Fabricate(:user, created_at: published_at - 2.days) }
-    let(:user_before_unconfirmed) { Fabricate(:user, created_at: published_at - 2.days, confirmed_at: nil) }
-    let(:user_before_suspended) { Fabricate(:user, created_at: published_at - 2.days) }
-    let(:user_after) { Fabricate(:user, created_at: published_at + 1.hour) }
-
-    before do
-      user_before_suspended.account.suspend!
-      user_before_unconfirmed
-      user_before
-      user_after
-    end
-
-    it 'includes only users created before the terms of service were published' do
-      expect(subject.pluck(:id)).to match_array(user_before.id)
-    end
-  end
-end
diff --git a/spec/models/tombstone_spec.rb b/spec/models/tombstone_spec.rb
deleted file mode 100644
index ba0603fe3d..0000000000
--- a/spec/models/tombstone_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Tombstone do
-  describe 'Associations' do
-    it { is_expected.to belong_to(:account).required }
-  end
-
-  describe 'Validations' do
-    subject { Fabricate.build :tombstone }
-
-    it { is_expected.to validate_presence_of(:uri) }
-  end
-end
diff --git a/spec/models/trends/links_spec.rb b/spec/models/trends/links_spec.rb
deleted file mode 100644
index 81a4270c38..0000000000
--- a/spec/models/trends/links_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Trends::Links do
-  describe 'Trends::Links::Query' do
-    subject { described_class.new.query }
-
-    describe '#records' do
-      context 'with scored cards' do
-        let!(:higher_score) { Fabricate :preview_card_trend, score: 10, language: 'en' }
-        let!(:lower_score) { Fabricate :preview_card_trend, score: 1, language: 'es' }
-
-        it 'returns higher score first' do
-          expect(subject.records)
-            .to eq([higher_score.preview_card, lower_score.preview_card])
-        end
-
-        context 'with preferred locale' do
-          before { subject.in_locale!('es') }
-
-          it 'returns in language order' do
-            expect(subject.records)
-              .to eq([lower_score.preview_card, higher_score.preview_card])
-          end
-        end
-
-        context 'when account has chosen languages' do
-          let!(:lang_match_higher_score) { Fabricate :preview_card_trend, score: 10, language: 'is' }
-          let!(:lang_match_lower_score) { Fabricate :preview_card_trend, score: 1, language: 'da' }
-          let(:user) { Fabricate :user, chosen_languages: %w(da is) }
-          let(:account) { Fabricate :account, user: user }
-
-          before { subject.filtered_for!(account) }
-
-          it 'returns results' do
-            expect(subject.records)
-              .to eq([lang_match_higher_score.preview_card, lang_match_lower_score.preview_card, higher_score.preview_card, lower_score.preview_card])
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/models/trends/statuses_spec.rb b/spec/models/trends/statuses_spec.rb
index 3d522eedcd..999c855867 100644
--- a/spec/models/trends/statuses_spec.rb
+++ b/spec/models/trends/statuses_spec.rb
@@ -45,45 +45,6 @@ RSpec.describe Trends::Statuses do
     end
   end
 
-  describe 'Trends::Statuses::Query methods' do
-    subject { described_class.new.query }
-
-    describe '#records' do
-      context 'with scored cards' do
-        let!(:higher_score) { Fabricate :status_trend, score: 10, language: 'en' }
-        let!(:lower_score) { Fabricate :status_trend, score: 1, language: 'es' }
-
-        it 'returns higher score first' do
-          expect(subject.records)
-            .to eq([higher_score.status, lower_score.status])
-        end
-
-        context 'with preferred locale' do
-          before { subject.in_locale!('es') }
-
-          it 'returns in language order' do
-            expect(subject.records)
-              .to eq([lower_score.status, higher_score.status])
-          end
-        end
-
-        context 'when account has chosen languages' do
-          let!(:lang_match_higher_score) { Fabricate :status_trend, score: 10, language: 'is' }
-          let!(:lang_match_lower_score) { Fabricate :status_trend, score: 1, language: 'da' }
-          let(:user) { Fabricate :user, chosen_languages: %w(da is) }
-          let(:account) { Fabricate :account, user: user }
-
-          before { subject.filtered_for!(account) }
-
-          it 'returns results' do
-            expect(subject.records)
-              .to eq([lang_match_higher_score.status, lang_match_lower_score.status, higher_score.status, lower_score.status])
-          end
-        end
-      end
-    end
-  end
-
   describe '#add' do
     let(:status) { Fabricate(:status) }
 
diff --git a/spec/models/trends/tags_spec.rb b/spec/models/trends/tags_spec.rb
index b066391e4c..91ca91f078 100644
--- a/spec/models/trends/tags_spec.rb
+++ b/spec/models/trends/tags_spec.rb
@@ -56,45 +56,6 @@ RSpec.describe Trends::Tags do
     end
   end
 
-  describe 'Trends::Tags::Query' do
-    subject { described_class.new.query }
-
-    describe '#records' do
-      context 'with scored cards' do
-        let!(:higher_score) { Fabricate :tag_trend, score: 10, language: 'en' }
-        let!(:lower_score) { Fabricate :tag_trend, score: 1, language: 'es' }
-
-        it 'returns higher score first' do
-          expect(subject.records)
-            .to eq([higher_score.tag, lower_score.tag])
-        end
-
-        context 'with preferred locale' do
-          before { subject.in_locale!('es') }
-
-          it 'returns in language order' do
-            expect(subject.records)
-              .to eq([lower_score.tag, higher_score.tag])
-          end
-        end
-
-        context 'when account has chosen languages' do
-          let!(:lang_match_higher_score) { Fabricate :tag_trend, score: 10, language: 'is' }
-          let!(:lang_match_lower_score) { Fabricate :tag_trend, score: 1, language: 'da' }
-          let(:user) { Fabricate :user, chosen_languages: %w(da is) }
-          let(:account) { Fabricate :account, user: user }
-
-          before { subject.filtered_for!(account) }
-
-          it 'returns results' do
-            expect(subject.records)
-              .to eq([lang_match_higher_score.tag, lang_match_lower_score.tag, higher_score.tag, lower_score.tag])
-          end
-        end
-      end
-    end
-  end
-
   describe '#refresh' do
     let!(:today) { at_time }
     let!(:yesterday) { today - 1.day }
@@ -127,10 +88,10 @@ RSpec.describe Trends::Tags do
 
     it 'decays scores' do
       subject.refresh(yesterday + 12.hours)
-      original_score = TagTrend.find_by(tag: tag_ocs).score
+      original_score = subject.score(tag_ocs.id)
       expect(original_score).to eq 144.0
       subject.refresh(yesterday + 12.hours + subject.options[:max_score_halflife])
-      decayed_score = TagTrend.find_by(tag: tag_ocs).score
+      decayed_score = subject.score(tag_ocs.id)
       expect(decayed_score).to be <= original_score / 2
     end
   end
diff --git a/spec/models/user_role_spec.rb b/spec/models/user_role_spec.rb
index 6cc5896786..365fc75e7b 100644
--- a/spec/models/user_role_spec.rb
+++ b/spec/models/user_role_spec.rb
@@ -3,77 +3,9 @@
 require 'rails_helper'
 
 RSpec.describe UserRole do
-  describe 'Validations' do
-    describe 'name' do
-      context 'when everyone' do
-        subject { described_class.everyone }
-
-        it { is_expected.to_not validate_presence_of(:name) }
-      end
-
-      context 'when not everyone' do
-        subject { Fabricate.build :user_role }
-
-        it { is_expected.to validate_presence_of(:name) }
-      end
-    end
-
-    describe 'position' do
-      subject { Fabricate.build :user_role }
-
-      let(:limit) { described_class::POSITION_LIMIT }
-
-      it { is_expected.to validate_numericality_of(:position).is_in(-limit..limit) }
-    end
-
-    describe 'color' do
-      it { is_expected.to allow_values('#112233', '#aabbcc', '').for(:color) }
-      it { is_expected.to_not allow_values('x', '112233445566', '#xxyyzz').for(:color) }
-    end
-
-    context 'when current_account is set' do
-      subject { Fabricate :user_role }
-
-      let(:account) { Fabricate :account }
-
-      before { subject.current_account = account }
-
-      it { is_expected.to_not allow_value(999_999).for(:position).with_message(:elevated) }
-
-      it { is_expected.to_not allow_value(999_999).for(:permissions).against(:permissions_as_keys).with_message(:elevated) }
-
-      context 'when current_account is changing their own role' do
-        let(:account) { Fabricate :account, user: Fabricate(:user, role: subject) }
-
-        it { is_expected.to_not allow_value(100).for(:permissions).against(:permissions_as_keys).with_message(:own_role) }
-        it { is_expected.to_not allow_value(100).for(:position).with_message(:own_role) }
-      end
-    end
-  end
-
-  describe 'Callback for position' do
-    context 'when everyone' do
-      subject { Fabricate.build :user_role, id: described_class::EVERYONE_ROLE_ID }
-
-      it 'sets the position to nobody position' do
-        expect { subject.valid? }
-          .to change(subject, :position).to(described_class::NOBODY_POSITION)
-      end
-    end
-
-    context 'when not everyone' do
-      subject { Fabricate.build :user_role }
-
-      it 'does not change the position' do
-        expect { subject.valid? }
-          .to_not change(subject, :position)
-      end
-    end
-  end
+  subject { described_class.create(name: 'Foo', position: 1) }
 
   describe '#can?' do
-    subject { Fabricate :user_role }
-
     context 'with a single flag' do
       it 'returns true if any of them are present' do
         subject.permissions = described_class::FLAGS[:manage_reports]
@@ -160,8 +92,6 @@ RSpec.describe UserRole do
   end
 
   describe '#computed_permissions' do
-    subject { Fabricate :user_role }
-
     context 'when the role is nobody' do
       subject { described_class.nobody }
 
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 9bdb86483a..be9a5a76eb 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -33,12 +33,14 @@ RSpec.describe User do
     end
   end
 
-  describe 'Associations' do
+  describe 'validations' do
     it { is_expected.to belong_to(:account).required }
-  end
 
-  describe 'Validations' do
-    it { is_expected.to_not allow_value('john@').for(:email) }
+    it 'is invalid without a valid email' do
+      user = Fabricate.build(:user, email: 'john@')
+      user.valid?
+      expect(user).to model_have_error_on_field(:email)
+    end
 
     it 'is valid with an invalid e-mail that has already been saved' do
       user = Fabricate.build(:user, email: 'invalid-email')
@@ -46,7 +48,11 @@ RSpec.describe User do
       expect(user.valid?).to be true
     end
 
-    it { is_expected.to allow_value('admin@localhost').for(:email) }
+    it 'is valid with a localhost e-mail address' do
+      user = Fabricate.build(:user, email: 'admin@localhost')
+      user.valid?
+      expect(user.valid?).to be true
+    end
   end
 
   describe 'Normalizations' do
@@ -177,39 +183,6 @@ RSpec.describe User do
     end
   end
 
-  describe '#update_sign_in!' do
-    context 'with an existing user' do
-      let!(:user) { Fabricate :user, last_sign_in_at: 10.days.ago, current_sign_in_at: 1.hour.ago, sign_in_count: 123 }
-
-      context 'with new sign in false' do
-        it 'updates timestamps but not counts' do
-          expect { user.update_sign_in!(new_sign_in: false) }
-            .to change(user, :last_sign_in_at)
-            .and change(user, :current_sign_in_at)
-            .and not_change(user, :sign_in_count)
-        end
-      end
-
-      context 'with new sign in true' do
-        it 'updates timestamps and counts' do
-          expect { user.update_sign_in!(new_sign_in: true) }
-            .to change(user, :last_sign_in_at)
-            .and change(user, :current_sign_in_at)
-            .and change(user, :sign_in_count).by(1)
-        end
-      end
-    end
-
-    context 'with a new user' do
-      let(:user) { Fabricate.build :user }
-
-      it 'does not persist the user' do
-        expect { user.update_sign_in! }
-          .to_not change(user, :persisted?).from(false)
-      end
-    end
-  end
-
   describe '#confirmed?' do
     it 'returns true when a confirmed_at is set' do
       user = Fabricate.build(:user, confirmed_at: Time.now.utc)
@@ -633,7 +606,7 @@ RSpec.describe User do
   end
 
   describe '.those_who_can' do
-    before { Fabricate(:moderator_user) }
+    before { Fabricate(:user, role: UserRole.find_by(name: 'Moderator')) }
 
     context 'when there are not any user roles' do
       before { UserRole.destroy_all }
@@ -650,34 +623,11 @@ RSpec.describe User do
     end
 
     context 'when there are users with roles' do
-      let!(:admin_user) { Fabricate(:admin_user) }
+      let!(:admin_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
       it 'returns the users with the role' do
         expect(described_class.those_who_can(:manage_blocks)).to eq([admin_user])
       end
     end
   end
-
-  describe '#applications_last_used' do
-    let!(:user) { Fabricate(:user) }
-
-    let!(:never_used_application) { Fabricate :application, owner: user }
-    let!(:application_one) { Fabricate :application, owner: user }
-    let!(:application_two) { Fabricate :application, owner: user }
-
-    before do
-      _other_user_token = Fabricate :access_token, last_used_at: 3.days.ago
-      _never_used_token = Fabricate :access_token, application: never_used_application, resource_owner_id: user.id, last_used_at: nil
-      _app_one_old_token = Fabricate :access_token, application: application_one, resource_owner_id: user.id, last_used_at: 5.days.ago
-      _app_one_new_token = Fabricate :access_token, application: application_one, resource_owner_id: user.id, last_used_at: 1.day.ago
-      _never_used_token = Fabricate :access_token, application: application_two, resource_owner_id: user.id, last_used_at: 5.days.ago
-    end
-
-    it 'returns a hash of unique applications with last used values' do
-      expect(user.applications_last_used)
-        .to include(application_one.id => be_within(1.0).of(1.day.ago))
-        .and include(application_two.id => be_within(1.0).of(5.days.ago))
-        .and not_include(never_used_application.id)
-    end
-  end
 end
diff --git a/spec/models/webauthn_credential_spec.rb b/spec/models/webauthn_credential_spec.rb
index 067c696c13..c4105d9150 100644
--- a/spec/models/webauthn_credential_spec.rb
+++ b/spec/models/webauthn_credential_spec.rb
@@ -3,17 +3,53 @@
 require 'rails_helper'
 
 RSpec.describe WebauthnCredential do
-  describe 'Validations' do
-    subject { Fabricate.build :webauthn_credential }
-
+  describe 'validations' do
     it { is_expected.to validate_presence_of(:external_id) }
     it { is_expected.to validate_presence_of(:public_key) }
     it { is_expected.to validate_presence_of(:nickname) }
     it { is_expected.to validate_presence_of(:sign_count) }
 
-    it { is_expected.to validate_uniqueness_of(:external_id) }
-    it { is_expected.to validate_uniqueness_of(:nickname).scoped_to(:user_id) }
+    it 'is invalid if already exist a webauthn credential with the same external id' do
+      Fabricate(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw')
+      new_webauthn_credential = Fabricate.build(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw')
 
-    it { is_expected.to validate_numericality_of(:sign_count).only_integer.is_greater_than_or_equal_to(0).is_less_than_or_equal_to(described_class::SIGN_COUNT_LIMIT - 1) }
+      new_webauthn_credential.valid?
+
+      expect(new_webauthn_credential).to model_have_error_on_field(:external_id)
+    end
+
+    it 'is invalid if user already registered a webauthn credential with the same nickname' do
+      user = Fabricate(:user)
+      Fabricate(:webauthn_credential, user_id: user.id, nickname: 'USB Key')
+      new_webauthn_credential = Fabricate.build(:webauthn_credential, user_id: user.id, nickname: 'USB Key')
+
+      new_webauthn_credential.valid?
+
+      expect(new_webauthn_credential).to model_have_error_on_field(:nickname)
+    end
+
+    it 'is invalid if sign_count is not a number' do
+      webauthn_credential = Fabricate.build(:webauthn_credential, sign_count: 'invalid sign_count')
+
+      webauthn_credential.valid?
+
+      expect(webauthn_credential).to model_have_error_on_field(:sign_count)
+    end
+
+    it 'is invalid if sign_count is negative number' do
+      webauthn_credential = Fabricate.build(:webauthn_credential, sign_count: -1)
+
+      webauthn_credential.valid?
+
+      expect(webauthn_credential).to model_have_error_on_field(:sign_count)
+    end
+
+    it 'is invalid if sign_count is greater than the limit' do
+      webauthn_credential = Fabricate.build(:webauthn_credential, sign_count: (described_class::SIGN_COUNT_LIMIT * 2))
+
+      webauthn_credential.valid?
+
+      expect(webauthn_credential).to model_have_error_on_field(:sign_count)
+    end
   end
 end
diff --git a/spec/models/webhook_spec.rb b/spec/models/webhook_spec.rb
index 59b4212d62..03ef1dcc68 100644
--- a/spec/models/webhook_spec.rb
+++ b/spec/models/webhook_spec.rb
@@ -6,28 +6,20 @@ RSpec.describe Webhook do
   let(:webhook) { Fabricate(:webhook) }
 
   describe 'Validations' do
-    subject { Fabricate.build :webhook }
-
     it { is_expected.to validate_presence_of(:events) }
 
-    it { is_expected.to_not allow_values([], %w(account.invalid)).for(:events) }
+    it 'requires non-empty events value' do
+      record = described_class.new(events: [])
+      record.valid?
 
-    it { is_expected.to_not allow_values('{{account }').for(:template) }
+      expect(record).to model_have_error_on_field(:events)
+    end
 
-    context 'when current_account is assigned' do
-      subject { Fabricate.build :webhook, current_account: account }
+    it 'requires valid events value from EVENTS' do
+      record = described_class.new(events: ['account.invalid'])
+      record.valid?
 
-      context 'with account that has permissions' do
-        let(:account) { Fabricate(:admin_user).account }
-
-        it { is_expected.to allow_values(%w(account.created)).for(:events) }
-      end
-
-      context 'with account lacking permissions' do
-        let(:account) { Fabricate :account }
-
-        it { is_expected.to_not allow_values(%w(account.created)).for(:events) }
-      end
+      expect(record).to model_have_error_on_field(:events)
     end
   end
 
@@ -37,96 +29,6 @@ RSpec.describe Webhook do
     end
   end
 
-  describe 'Callbacks' do
-    describe 'Generating a secret' do
-      context 'when secret exists already' do
-        subject { described_class.new(secret: 'secret') }
-
-        it 'does not override' do
-          expect { subject.valid? }
-            .to_not change(subject, :secret)
-        end
-      end
-
-      context 'when secret does not exist' do
-        subject { described_class.new(secret: nil) }
-
-        it 'does not override' do
-          expect { subject.valid? }
-            .to change(subject, :secret)
-        end
-      end
-    end
-  end
-
-  describe '.permission_for_event' do
-    subject { described_class.permission_for_event(event) }
-
-    context 'with a nil value' do
-      let(:event) { nil }
-
-      it { is_expected.to be_nil }
-    end
-
-    context 'with an account approved event' do
-      let(:event) { 'account.approved' }
-
-      it { is_expected.to eq(:manage_users) }
-    end
-
-    context 'with an account created event' do
-      let(:event) { 'account.created' }
-
-      it { is_expected.to eq(:manage_users) }
-    end
-
-    context 'with an account updated event' do
-      let(:event) { 'account.updated' }
-
-      it { is_expected.to eq(:manage_users) }
-    end
-
-    context 'with an report created event' do
-      let(:event) { 'report.created' }
-
-      it { is_expected.to eq(:manage_reports) }
-    end
-
-    context 'with an report updated event' do
-      let(:event) { 'report.updated' }
-
-      it { is_expected.to eq(:manage_reports) }
-    end
-
-    context 'with an status created event' do
-      let(:event) { 'status.created' }
-
-      it { is_expected.to eq(:view_devops) }
-    end
-
-    context 'with an status updated event' do
-      let(:event) { 'status.updated' }
-
-      it { is_expected.to eq(:view_devops) }
-    end
-  end
-
-  describe '#required_permissions' do
-    subject { described_class.new(events:).required_permissions }
-
-    context 'with empty events' do
-      let(:events) { [] }
-
-      it { is_expected.to eq([]) }
-    end
-
-    context 'with multiple event types' do
-      let(:events) { %w(account.created account.updated status.created) }
-
-      it { is_expected.to eq %i(manage_users view_devops) }
-    end
-  end
-
   describe '#rotate_secret!' do
     it 'changes the secret' do
       expect { webhook.rotate_secret! }
diff --git a/spec/policies/account_moderation_note_policy_spec.rb b/spec/policies/account_moderation_note_policy_spec.rb
index 2a9cdabaa2..8b33a71012 100644
--- a/spec/policies/account_moderation_note_policy_spec.rb
+++ b/spec/policies/account_moderation_note_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe AccountModerationNotePolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :create? do
diff --git a/spec/policies/account_policy_spec.rb b/spec/policies/account_policy_spec.rb
index 8b2edb15b0..75724e831b 100644
--- a/spec/policies/account_policy_spec.rb
+++ b/spec/policies/account_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe AccountPolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
   let(:alice)   { Fabricate(:account) }
 
@@ -70,7 +70,7 @@ RSpec.describe AccountPolicy do
   end
 
   permissions :suspend?, :silence? do
-    let(:staff) { Fabricate(:admin_user).account }
+    let(:staff) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
 
     context 'when staff' do
       context 'when record is staff' do
@@ -94,7 +94,7 @@ RSpec.describe AccountPolicy do
   end
 
   permissions :memorialize? do
-    let(:other_admin) { Fabricate(:admin_user).account }
+    let(:other_admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
 
     context 'when admin' do
       context 'when record is admin' do
diff --git a/spec/policies/account_warning_policy_spec.rb b/spec/policies/account_warning_policy_spec.rb
index 2603794886..9abc9d35d6 100644
--- a/spec/policies/account_warning_policy_spec.rb
+++ b/spec/policies/account_warning_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe AccountWarningPolicy do
   subject { described_class }
 
-  let(:admin) { Fabricate(:admin_user).account }
+  let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:account) { Fabricate(:account) }
 
   permissions :show? do
@@ -31,11 +31,11 @@ RSpec.describe AccountWarningPolicy do
 
     context 'when account is target' do
       context 'when record is appealable' do
-        it { is_expected.to permit(account, AccountWarning.new(target_account_id: account.id, created_at: AccountWarning::APPEAL_WINDOW.ago + 1.hour)) }
+        it { is_expected.to permit(account, AccountWarning.new(target_account_id: account.id, created_at: Appeal::MAX_STRIKE_AGE.ago + 1.hour)) }
       end
 
       context 'when record is not appealable' do
-        it { is_expected.to_not permit(account, AccountWarning.new(target_account_id: account.id, created_at: AccountWarning::APPEAL_WINDOW.ago - 1.hour)) }
+        it { is_expected.to_not permit(account, AccountWarning.new(target_account_id: account.id, created_at: Appeal::MAX_STRIKE_AGE.ago - 1.hour)) }
       end
     end
   end
diff --git a/spec/policies/account_warning_preset_policy_spec.rb b/spec/policies/account_warning_preset_policy_spec.rb
index e8223ab1c7..33f2fb1187 100644
--- a/spec/policies/account_warning_preset_policy_spec.rb
+++ b/spec/policies/account_warning_preset_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe AccountWarningPresetPolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :create?, :update?, :destroy? do
diff --git a/spec/policies/admin/fasp/provider_policy_spec.rb b/spec/policies/admin/fasp/provider_policy_spec.rb
deleted file mode 100644
index 802760f2e9..0000000000
--- a/spec/policies/admin/fasp/provider_policy_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Admin::Fasp::ProviderPolicy, type: :policy do
-  subject { described_class }
-
-  let(:admin) { Fabricate(:admin_user).account }
-  let(:user) { Fabricate(:account) }
-
-  shared_examples 'admin only' do |target|
-    let(:provider) { target.is_a?(Symbol) ? Fabricate(target) : target }
-
-    context 'with an admin' do
-      it 'permits' do
-        expect(subject).to permit(admin, provider)
-      end
-    end
-
-    context 'with a non-admin' do
-      it 'denies' do
-        expect(subject).to_not permit(user, provider)
-      end
-    end
-  end
-
-  permissions :index?, :create? do
-    include_examples 'admin only', Fasp::Provider
-  end
-
-  permissions :show?, :create?, :update?, :destroy? do
-    include_examples 'admin only', :fasp_provider
-  end
-end
diff --git a/spec/policies/admin/status_policy_spec.rb b/spec/policies/admin/status_policy_spec.rb
index 5f8285c562..4df29393e3 100644
--- a/spec/policies/admin/status_policy_spec.rb
+++ b/spec/policies/admin/status_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe Admin::StatusPolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
   let(:status) { Fabricate(:status, visibility: status_visibility) }
   let(:status_visibility) { :public }
diff --git a/spec/policies/announcement_policy_spec.rb b/spec/policies/announcement_policy_spec.rb
index 2fec34f8e4..ab0c1dbaf5 100644
--- a/spec/policies/announcement_policy_spec.rb
+++ b/spec/policies/announcement_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe AnnouncementPolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :create?, :update?, :destroy? do
diff --git a/spec/policies/appeal_policy_spec.rb b/spec/policies/appeal_policy_spec.rb
index e3e1d62ba7..cdb93bf56c 100644
--- a/spec/policies/appeal_policy_spec.rb
+++ b/spec/policies/appeal_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe AppealPolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
   let(:appeal) { Fabricate(:appeal) }
 
diff --git a/spec/policies/audit_log_policy_spec.rb b/spec/policies/audit_log_policy_spec.rb
index b1769eb487..d9d9359433 100644
--- a/spec/policies/audit_log_policy_spec.rb
+++ b/spec/policies/audit_log_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe AuditLogPolicy do
   subject { described_class }
 
-  let(:admin) { Fabricate(:admin_user).account }
+  let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:account) { Fabricate(:account) }
 
   permissions :index? do
diff --git a/spec/policies/backup_policy_spec.rb b/spec/policies/backup_policy_spec.rb
index 28995d195b..031021d91d 100644
--- a/spec/policies/backup_policy_spec.rb
+++ b/spec/policies/backup_policy_spec.rb
@@ -23,30 +23,22 @@ RSpec.describe BackupPolicy do
 
       context 'when backups are too old' do
         it 'permits' do
-          travel(-before_time) do
+          travel(-8.days) do
             Fabricate(:backup, user: john.user)
           end
 
           expect(subject).to permit(john, Backup)
         end
-
-        def before_time
-          described_class::MIN_AGE + 2.days
-        end
       end
 
       context 'when backups are newer' do
         it 'denies' do
-          travel(-within_time) do
+          travel(-3.days) do
             Fabricate(:backup, user: john.user)
           end
 
           expect(subject).to_not permit(john, Backup)
         end
-
-        def within_time
-          described_class::MIN_AGE - 2.days
-        end
       end
     end
   end
diff --git a/spec/policies/canonical_email_block_policy_spec.rb b/spec/policies/canonical_email_block_policy_spec.rb
index bf9503dc5c..b253b439a6 100644
--- a/spec/policies/canonical_email_block_policy_spec.rb
+++ b/spec/policies/canonical_email_block_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe CanonicalEmailBlockPolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :test?, :create?, :destroy? do
diff --git a/spec/policies/custom_emoji_policy_spec.rb b/spec/policies/custom_emoji_policy_spec.rb
index 9635da4496..189885938c 100644
--- a/spec/policies/custom_emoji_policy_spec.rb
+++ b/spec/policies/custom_emoji_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe CustomEmojiPolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :enable?, :disable? do
diff --git a/spec/policies/dashboard_policy_spec.rb b/spec/policies/dashboard_policy_spec.rb
index 711bea53ec..90c71db381 100644
--- a/spec/policies/dashboard_policy_spec.rb
+++ b/spec/policies/dashboard_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe DashboardPolicy do
   subject { described_class }
 
-  let(:admin) { Fabricate(:admin_user).account }
+  let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:account) { Fabricate(:account) }
 
   permissions :index? do
diff --git a/spec/policies/delivery_policy_spec.rb b/spec/policies/delivery_policy_spec.rb
index 017a7f3c5e..8bc200159a 100644
--- a/spec/policies/delivery_policy_spec.rb
+++ b/spec/policies/delivery_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe DeliveryPolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :clear_delivery_errors?, :restart_delivery?, :stop_delivery? do
diff --git a/spec/policies/domain_allow_policy_spec.rb b/spec/policies/domain_allow_policy_spec.rb
index 1e29c4144f..1d285065b8 100644
--- a/spec/policies/domain_allow_policy_spec.rb
+++ b/spec/policies/domain_allow_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe DomainAllowPolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :create?, :destroy? do
diff --git a/spec/policies/domain_block_policy_spec.rb b/spec/policies/domain_block_policy_spec.rb
index 04081c5c9b..7c77d1870d 100644
--- a/spec/policies/domain_block_policy_spec.rb
+++ b/spec/policies/domain_block_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe DomainBlockPolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :create?, :destroy?, :update? do
diff --git a/spec/policies/email_domain_block_policy_spec.rb b/spec/policies/email_domain_block_policy_spec.rb
index 6428a2b442..e98d65a3c7 100644
--- a/spec/policies/email_domain_block_policy_spec.rb
+++ b/spec/policies/email_domain_block_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe EmailDomainBlockPolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :create?, :destroy? do
diff --git a/spec/policies/follow_recommendation_policy_spec.rb b/spec/policies/follow_recommendation_policy_spec.rb
index 181f0ed11c..665ed9b059 100644
--- a/spec/policies/follow_recommendation_policy_spec.rb
+++ b/spec/policies/follow_recommendation_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe FollowRecommendationPolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :show?, :suppress?, :unsuppress? do
diff --git a/spec/policies/instance_policy_spec.rb b/spec/policies/instance_policy_spec.rb
index 7ab843b83b..6cdc738022 100644
--- a/spec/policies/instance_policy_spec.rb
+++ b/spec/policies/instance_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe InstancePolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :destroy? do
diff --git a/spec/policies/invite_policy_spec.rb b/spec/policies/invite_policy_spec.rb
index bb3bddd4b8..3717a44999 100644
--- a/spec/policies/invite_policy_spec.rb
+++ b/spec/policies/invite_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe InvitePolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:user).account }
 
   permissions :index? do
diff --git a/spec/policies/ip_block_policy_spec.rb b/spec/policies/ip_block_policy_spec.rb
index dcd9c19785..33ea342c10 100644
--- a/spec/policies/ip_block_policy_spec.rb
+++ b/spec/policies/ip_block_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe IpBlockPolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :create?, :update?, :destroy? do
diff --git a/spec/policies/preview_card_policy_spec.rb b/spec/policies/preview_card_policy_spec.rb
index 75fcf7b373..d02a6016cd 100644
--- a/spec/policies/preview_card_policy_spec.rb
+++ b/spec/policies/preview_card_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe PreviewCardPolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :review? do
diff --git a/spec/policies/preview_card_provider_policy_spec.rb b/spec/policies/preview_card_provider_policy_spec.rb
index d3fbf44539..5e25b364a4 100644
--- a/spec/policies/preview_card_provider_policy_spec.rb
+++ b/spec/policies/preview_card_provider_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe PreviewCardProviderPolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :review? do
diff --git a/spec/policies/relay_policy_spec.rb b/spec/policies/relay_policy_spec.rb
index 8d5ed4997e..5983b2d2ff 100644
--- a/spec/policies/relay_policy_spec.rb
+++ b/spec/policies/relay_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe RelayPolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :update? do
diff --git a/spec/policies/report_note_policy_spec.rb b/spec/policies/report_note_policy_spec.rb
index 9f0c525a3c..02317f763a 100644
--- a/spec/policies/report_note_policy_spec.rb
+++ b/spec/policies/report_note_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe ReportNotePolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :create? do
diff --git a/spec/policies/report_policy_spec.rb b/spec/policies/report_policy_spec.rb
index 20b91c5fd0..67f40b5188 100644
--- a/spec/policies/report_policy_spec.rb
+++ b/spec/policies/report_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe ReportPolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :update?, :index?, :show? do
diff --git a/spec/policies/rule_policy_spec.rb b/spec/policies/rule_policy_spec.rb
index 70ae3f9417..3086f30446 100644
--- a/spec/policies/rule_policy_spec.rb
+++ b/spec/policies/rule_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe RulePolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :create?, :update?, :destroy? do
diff --git a/spec/policies/settings_policy_spec.rb b/spec/policies/settings_policy_spec.rb
index d11c3c1fa7..48821c706a 100644
--- a/spec/policies/settings_policy_spec.rb
+++ b/spec/policies/settings_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe SettingsPolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :update?, :show?, :destroy? do
diff --git a/spec/policies/software_update_policy_spec.rb b/spec/policies/software_update_policy_spec.rb
index ab067dd17e..2bda84cce9 100644
--- a/spec/policies/software_update_policy_spec.rb
+++ b/spec/policies/software_update_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe SoftwareUpdatePolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:owner_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Owner')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index? do
diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb
index c564e82f7d..2d386e829f 100644
--- a/spec/policies/status_policy_spec.rb
+++ b/spec/policies/status_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe StatusPolicy, type: :model do
   subject { described_class }
 
-  let(:admin) { Fabricate(:admin_user) }
+  let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
   let(:alice) { Fabricate(:account, username: 'alice') }
   let(:bob) { Fabricate(:account, username: 'bob') }
   let(:status) { Fabricate(:status, account: alice) }
diff --git a/spec/policies/tag_policy_spec.rb b/spec/policies/tag_policy_spec.rb
index 241f13dd3b..23166e4669 100644
--- a/spec/policies/tag_policy_spec.rb
+++ b/spec/policies/tag_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe TagPolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :update?, :review? do
diff --git a/spec/policies/terms_of_service_policy_spec.rb b/spec/policies/terms_of_service_policy_spec.rb
deleted file mode 100644
index 0deab2fe9b..0000000000
--- a/spec/policies/terms_of_service_policy_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe TermsOfServicePolicy do
-  subject { described_class }
-
-  let(:admin) { Fabricate(:admin_user).account }
-  let(:john) { Fabricate(:account) }
-
-  permissions :index?, :create? do
-    it { is_expected.to permit(admin, TermsOfService) }
-    it { is_expected.to_not permit(john, TermsOfService) }
-  end
-
-  permissions :update?, :destroy? do
-    let(:terms) { Fabricate(:terms_of_service, published_at: published) }
-
-    context 'with an unpublished terms' do
-      let(:published) { nil }
-
-      it { is_expected.to permit(admin, terms) }
-      it { is_expected.to_not permit(john, terms) }
-    end
-
-    context 'with a published terms' do
-      let(:published) { 5.days.ago }
-
-      it { is_expected.to_not permit(admin, terms) }
-      it { is_expected.to_not permit(john, terms) }
-    end
-  end
-
-  permissions :distribute? do
-    let(:terms) { Fabricate(:terms_of_service, published_at: published, notification_sent_at: notification) }
-
-    context 'with notification already sent' do
-      let(:notification) { 3.days.ago }
-
-      context 'with published true' do
-        let(:published) { 5.days.ago }
-
-        it { is_expected.to_not permit(admin, terms) }
-        it { is_expected.to_not permit(john, terms) }
-      end
-
-      context 'with published false' do
-        let(:published) { nil }
-
-        it { is_expected.to_not permit(admin, terms) }
-        it { is_expected.to_not permit(john, terms) }
-      end
-    end
-
-    context 'with notification not yet sent' do
-      let(:notification) { nil }
-
-      context 'with published true' do
-        let(:published) { 5.days.ago }
-
-        it { is_expected.to permit(admin, terms) }
-        it { is_expected.to_not permit(john, terms) }
-      end
-
-      context 'with published false' do
-        let(:published) { nil }
-
-        it { is_expected.to_not permit(admin, terms) }
-        it { is_expected.to_not permit(john, terms) }
-      end
-    end
-  end
-end
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index ba4e31189a..11a166a24e 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe UserPolicy do
   subject { described_class }
 
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :reset_password?, :change_email? do
diff --git a/spec/policies/user_role_policy_spec.rb b/spec/policies/user_role_policy_spec.rb
index 7708dfea55..c48b345d68 100644
--- a/spec/policies/user_role_policy_spec.rb
+++ b/spec/policies/user_role_policy_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe UserRolePolicy do
   subject { described_class }
 
-  let(:admin) { Fabricate(:admin_user).account }
+  let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:account) { Fabricate(:account) }
 
   permissions :index?, :create? do
diff --git a/spec/policies/webhook_policy_spec.rb b/spec/policies/webhook_policy_spec.rb
index 10b5968165..9899235d83 100644
--- a/spec/policies/webhook_policy_spec.rb
+++ b/spec/policies/webhook_policy_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe WebhookPolicy do
   let(:policy) { described_class }
-  let(:admin)   { Fabricate(:admin_user).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :create? do
diff --git a/spec/presenters/export_summary_spec.rb b/spec/presenters/export_summary_spec.rb
deleted file mode 100644
index 0ed46c857d..0000000000
--- a/spec/presenters/export_summary_spec.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ExportSummary do
-  subject { described_class.new(account) }
-
-  let(:account) { Fabricate(:account) }
-  let(:target_accounts) do
-    [
-      Fabricate(:account),
-      Fabricate(:account, username: 'one', domain: 'local.host'),
-    ]
-  end
-
-  describe '#total_storage' do
-    it 'returns the total size of the media attachments' do
-      media_attachment = Fabricate(:media_attachment, account: account)
-      expect(subject.total_storage).to eq media_attachment.file_file_size || 0
-    end
-  end
-
-  describe '#total_statuses' do
-    before { Fabricate.times(2, :status, account: account) }
-
-    it 'returns the total number of statuses' do
-      expect(subject.total_statuses).to eq(2)
-    end
-  end
-
-  describe '#total_bookmarks' do
-    before { Fabricate.times(2, :bookmark, account: account) }
-
-    it 'returns the total number of bookmarks' do
-      expect(subject.total_bookmarks).to eq(2)
-    end
-  end
-
-  describe '#total_follows' do
-    before { target_accounts.each { |target_account| account.follow!(target_account) } }
-
-    it 'returns the total number of the followed accounts' do
-      expect(subject.total_follows).to eq(2)
-    end
-  end
-
-  describe '#total_lists' do
-    before { Fabricate.times(2, :list, account: account) }
-
-    it 'returns the total number of lists' do
-      expect(subject.total_lists).to eq(2)
-    end
-  end
-
-  describe '#total_followers' do
-    before { target_accounts.each { |target_account| target_account.follow!(account) } }
-
-    it 'returns the total number of the follower accounts' do
-      expect(subject.total_followers).to eq(2)
-    end
-  end
-
-  describe '#total_blocks' do
-    before { target_accounts.each { |target_account| account.block!(target_account) } }
-
-    it 'returns the total number of the blocked accounts' do
-      expect(subject.total_blocks).to eq(2)
-    end
-  end
-
-  describe '#total_mutes' do
-    before { target_accounts.each { |target_account| account.mute!(target_account) } }
-
-    it 'returns the total number of the muted accounts' do
-      expect(subject.total_mutes).to eq(2)
-    end
-  end
-
-  describe '#total_domain_blocks' do
-    before { Fabricate.times(2, :account_domain_block, account: account) }
-
-    it 'returns the total number of account domain blocks' do
-      expect(subject.total_domain_blocks).to eq(2)
-    end
-  end
-end
diff --git a/spec/presenters/instance_presenter_spec.rb b/spec/presenters/instance_presenter_spec.rb
index 1c295134c9..51b034e674 100644
--- a/spec/presenters/instance_presenter_spec.rb
+++ b/spec/presenters/instance_presenter_spec.rb
@@ -68,7 +68,6 @@ RSpec.describe InstancePresenter do
     context 'with the GITHUB_REPOSITORY env variable set' do
       around do |example|
         ClimateControl.modify GITHUB_REPOSITORY: 'other/repo' do
-          reload_configuration
           example.run
         end
       end
@@ -81,7 +80,6 @@ RSpec.describe InstancePresenter do
     context 'without the GITHUB_REPOSITORY env variable set' do
       around do |example|
         ClimateControl.modify GITHUB_REPOSITORY: nil do
-          reload_configuration
           example.run
         end
       end
@@ -90,10 +88,6 @@ RSpec.describe InstancePresenter do
         expect(instance_presenter.source_url).to eq('https://github.com/kmycode/mastodon')
       end
     end
-
-    def reload_configuration
-      Rails.configuration.x.mastodon = Rails.application.config_for(:mastodon)
-    end
   end
 
   describe '#thumbnail' do
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 7d63ea6300..d2ad40be73 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -2,7 +2,7 @@
 
 ENV['RAILS_ENV'] ||= 'test'
 
-if ENV.fetch('COVERAGE', false)
+unless ENV['DISABLE_SIMPLECOV'] == 'true'
   require 'simplecov'
 
   SimpleCov.start 'rails' do
@@ -32,7 +32,7 @@ end
 STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020')
 ENV['STREAMING_API_BASE_URL'] = "http://localhost:#{STREAMING_PORT}"
 
-require_relative '../config/environment'
+require File.expand_path('../config/environment', __dir__)
 
 abort('The Rails environment is running in production mode!') if Rails.env.production?
 
@@ -119,11 +119,6 @@ RSpec.configure do |config|
   config.include CommandLineHelpers, type: :cli
   config.include SystemHelpers, type: :system
 
-  # TODO: Remove when Devise fixes https://github.com/heartcombo/devise/issues/5705
-  config.before do
-    Rails.application.reload_routes_unless_loaded
-  end
-
   config.around(:each, use_transactional_tests: false) do |example|
     self.use_transactional_tests = false
     example.run
diff --git a/spec/requests/accounts_spec.rb b/spec/requests/accounts_spec.rb
index 72913ebf22..afd9ac80e2 100644
--- a/spec/requests/accounts_spec.rb
+++ b/spec/requests/accounts_spec.rb
@@ -53,9 +53,8 @@ RSpec.describe 'Accounts show response' do
           it 'returns a standard HTML response', :aggregate_failures do
             expect(response)
               .to have_http_status(200)
+              .and render_template(:show)
               .and have_http_link_header(ActivityPub::TagManager.instance.uri_for(account)).for(rel: 'alternate')
-            expect(response.parsed_body.at('title').content)
-              .to include(account.username)
           end
         end
 
diff --git a/spec/requests/activitypub/inboxes_spec.rb b/spec/requests/activitypub/inboxes_spec.rb
deleted file mode 100644
index b21881b10f..0000000000
--- a/spec/requests/activitypub/inboxes_spec.rb
+++ /dev/null
@@ -1,148 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'ActivityPub Inboxes' do
-  let(:remote_account) { nil }
-
-  describe 'POST #create' do
-    context 'with signature' do
-      let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub) }
-
-      context 'without a named account' do
-        subject { post inbox_path, params: {}.to_json, sign_with: remote_account }
-
-        it 'returns http accepted' do
-          subject
-
-          expect(response)
-            .to have_http_status(202)
-        end
-      end
-
-      context 'with a specific account' do
-        subject { post account_inbox_path(account_username: account.username), params: {}.to_json, sign_with: remote_account }
-
-        let(:account) { Fabricate(:account) }
-
-        context 'when account is permanently suspended' do
-          before do
-            account.suspend!
-            account.deletion_request.destroy
-          end
-
-          it 'returns http gone' do
-            subject
-
-            expect(response)
-              .to have_http_status(410)
-          end
-        end
-
-        context 'when account is temporarily suspended' do
-          before { account.suspend! }
-
-          it 'returns http accepted' do
-            subject
-
-            expect(response)
-              .to have_http_status(202)
-          end
-        end
-      end
-    end
-
-    context 'with Collection-Synchronization header' do
-      subject { post inbox_path, params: {}.to_json, headers: { 'Collection-Synchronization' => synchronization_header }, sign_with: remote_account }
-
-      let(:remote_account) { Fabricate(:account, followers_url: 'https://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor', protocol: :activitypub) }
-      let(:synchronization_collection) { remote_account.followers_url }
-      let(:synchronization_url) { 'https://example.com/followers-for-domain' }
-      let(:synchronization_hash) { 'somehash' }
-      let(:synchronization_header) { "collectionId=\"#{synchronization_collection}\", digest=\"#{synchronization_hash}\", url=\"#{synchronization_url}\"" }
-
-      before do
-        stub_follow_sync_worker
-        stub_followers_hash
-      end
-
-      context 'with mismatching target collection' do
-        let(:synchronization_collection) { 'https://example.com/followers2' }
-
-        it 'does not start a synchronization job' do
-          subject
-
-          expect(response)
-            .to have_http_status(202)
-          expect(ActivityPub::FollowersSynchronizationWorker)
-            .to_not have_received(:perform_async)
-        end
-      end
-
-      context 'with mismatching domain in partial collection attribute' do
-        let(:synchronization_url) { 'https://example.org/followers' }
-
-        it 'does not start a synchronization job' do
-          subject
-
-          expect(response)
-            .to have_http_status(202)
-          expect(ActivityPub::FollowersSynchronizationWorker)
-            .to_not have_received(:perform_async)
-        end
-      end
-
-      context 'with matching digest' do
-        it 'does not start a synchronization job' do
-          subject
-
-          expect(response)
-            .to have_http_status(202)
-          expect(ActivityPub::FollowersSynchronizationWorker)
-            .to_not have_received(:perform_async)
-        end
-      end
-
-      context 'with mismatching digest' do
-        let(:synchronization_hash) { 'wronghash' }
-
-        it 'starts a synchronization job' do
-          subject
-
-          expect(response)
-            .to have_http_status(202)
-          expect(ActivityPub::FollowersSynchronizationWorker)
-            .to have_received(:perform_async)
-        end
-      end
-
-      it 'returns http accepted' do
-        subject
-
-        expect(response)
-          .to have_http_status(202)
-      end
-
-      def stub_follow_sync_worker
-        allow(ActivityPub::FollowersSynchronizationWorker)
-          .to receive(:perform_async)
-          .and_return(nil)
-      end
-
-      def stub_followers_hash
-        Rails.cache.write("followers_hash:#{remote_account.id}:local", 'somehash') # Populate value to match request
-      end
-    end
-
-    context 'without signature' do
-      subject { post inbox_path, params: {}.to_json }
-
-      it 'returns http not authorized' do
-        subject
-
-        expect(response)
-          .to have_http_status(401)
-      end
-    end
-  end
-end
diff --git a/spec/requests/activitypub/likes_spec.rb b/spec/requests/activitypub/likes_spec.rb
deleted file mode 100644
index d780ad5168..0000000000
--- a/spec/requests/activitypub/likes_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'ActivityPub Likes' do
-  let(:account) { Fabricate(:account) }
-  let(:status) { Fabricate :status, account: account }
-
-  before { Fabricate :favourite, status: status }
-
-  describe 'GET /accounts/:account_username/statuses/:status_id/likes' do
-    it 'returns http success and activity json types and correct items count' do
-      get account_status_likes_path(account, status)
-
-      expect(response)
-        .to have_http_status(200)
-      expect(response.media_type)
-        .to eq 'application/activity+json'
-
-      expect(response.parsed_body)
-        .to include(type: 'Collection')
-        .and include(totalItems: 1)
-    end
-  end
-end
diff --git a/spec/requests/activitypub/shares_spec.rb b/spec/requests/activitypub/shares_spec.rb
deleted file mode 100644
index 956bc7d201..0000000000
--- a/spec/requests/activitypub/shares_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'ActivityPub Shares' do
-  let(:account) { Fabricate(:account) }
-  let(:status) { Fabricate :status, account: account }
-
-  before { Fabricate :status, reblog: status }
-
-  describe 'GET /accounts/:account_username/statuses/:status_id/shares' do
-    it 'returns http success and activity json types and correct items count' do
-      get account_status_shares_path(account, status)
-
-      expect(response)
-        .to have_http_status(200)
-      expect(response.media_type)
-        .to eq 'application/activity+json'
-
-      expect(response.parsed_body)
-        .to include(type: 'Collection')
-        .and include(totalItems: 1)
-    end
-  end
-end
diff --git a/spec/requests/admin/account_actions_spec.rb b/spec/requests/admin/account_actions_spec.rb
deleted file mode 100644
index 5bf8f3e9b2..0000000000
--- a/spec/requests/admin/account_actions_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Account Actions' do
-  describe 'POST /admin/accounts/:account_id/action' do
-    before { sign_in Fabricate(:admin_user) }
-
-    let(:account) { Fabricate :account }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_account_action_path(account.id, admin_account_action: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/account_moderation_notes_spec.rb b/spec/requests/admin/account_moderation_notes_spec.rb
deleted file mode 100644
index 857bec1e09..0000000000
--- a/spec/requests/admin/account_moderation_notes_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Account Moderation Notes' do
-  describe 'POST /admin/account_moderation_notes' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_account_moderation_notes_path(account_moderation_note: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/accounts_spec.rb b/spec/requests/admin/accounts_spec.rb
deleted file mode 100644
index de655f0d36..0000000000
--- a/spec/requests/admin/accounts_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Accounts' do
-  describe 'POST /admin/accounts/batch' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post batch_admin_accounts_path(form_account_batch: 'invalid')
-
-      expect(response)
-        .to redirect_to(admin_accounts_path)
-    end
-  end
-end
diff --git a/spec/requests/admin/announcements_spec.rb b/spec/requests/admin/announcements_spec.rb
deleted file mode 100644
index 46d1b6a096..0000000000
--- a/spec/requests/admin/announcements_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Announcements' do
-  describe 'POST /admin/announcements' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_announcements_path(announcement: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/change_emails_spec.rb b/spec/requests/admin/change_emails_spec.rb
deleted file mode 100644
index 3df29eb77b..0000000000
--- a/spec/requests/admin/change_emails_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Account Change Email' do
-  describe 'PUT /admin/accounts/:account_id/change_email' do
-    before { sign_in Fabricate(:admin_user) }
-
-    let(:account) { Fabricate :account }
-
-    it 'gracefully handles invalid nested params' do
-      put admin_account_change_email_path(account.id, user: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/custom_emojis_spec.rb b/spec/requests/admin/custom_emojis_spec.rb
deleted file mode 100644
index 0c142f11f2..0000000000
--- a/spec/requests/admin/custom_emojis_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Custom Emojis' do
-  describe 'POST /admin/custom_emojis' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_custom_emojis_path(custom_emoji: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/domain_allows_spec.rb b/spec/requests/admin/domain_allows_spec.rb
deleted file mode 100644
index 0d1f6d0248..0000000000
--- a/spec/requests/admin/domain_allows_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Domain Allows' do
-  describe 'POST /admin/domain_allows' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_domain_allows_path(domain_allow: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/domain_blocks_spec.rb b/spec/requests/admin/domain_blocks_spec.rb
deleted file mode 100644
index 3b18a8ece1..0000000000
--- a/spec/requests/admin/domain_blocks_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Domain Blocks' do
-  describe 'POST /admin/domain_blocks/batch' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post batch_admin_domain_blocks_path(form_domain_block_batch: 'invalid')
-
-      expect(response)
-        .to redirect_to(admin_instances_path(limited: '1'))
-    end
-  end
-end
diff --git a/spec/requests/admin/email_domain_blocks_spec.rb b/spec/requests/admin/email_domain_blocks_spec.rb
deleted file mode 100644
index 7bccb3166b..0000000000
--- a/spec/requests/admin/email_domain_blocks_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Email Domain Blocks' do
-  describe 'POST /admin/email_domain_blocks' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_email_domain_blocks_path(email_domain_block: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-
-  describe 'POST /admin/email_domain_blocks/batch' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post batch_admin_email_domain_blocks_path(form_email_domain_block_batch: 'invalid')
-
-      expect(response)
-        .to redirect_to(admin_email_domain_blocks_path)
-    end
-  end
-end
diff --git a/spec/requests/admin/export_domain_allows_spec.rb b/spec/requests/admin/export_domain_allows_spec.rb
deleted file mode 100644
index 761c39984e..0000000000
--- a/spec/requests/admin/export_domain_allows_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Export Domain Allows' do
-  describe 'POST /admin/export_domain_allows/import' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post import_admin_export_domain_allows_path(admin_import: 'invalid')
-
-      expect(response)
-        .to redirect_to(admin_instances_path)
-    end
-  end
-end
diff --git a/spec/requests/admin/export_domain_blocks_spec.rb b/spec/requests/admin/export_domain_blocks_spec.rb
deleted file mode 100644
index d7fed5287f..0000000000
--- a/spec/requests/admin/export_domain_blocks_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Export Domain Blocks' do
-  describe 'POST /admin/export_domain_blocks/import' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post import_admin_export_domain_blocks_path(admin_import: 'invalid')
-
-      expect(response.body)
-        .to include(I18n.t('admin.export_domain_blocks.no_file'))
-    end
-  end
-end
diff --git a/spec/requests/admin/follow_recommendations_spec.rb b/spec/requests/admin/follow_recommendations_spec.rb
deleted file mode 100644
index 146c26448c..0000000000
--- a/spec/requests/admin/follow_recommendations_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Follow Recommendations' do
-  describe 'PUT /admin/follow_recommendations' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      put admin_follow_recommendations_path(form_account_batch: 'invalid')
-
-      expect(response)
-        .to redirect_to(admin_follow_recommendations_path)
-    end
-  end
-end
diff --git a/spec/requests/admin/instances_spec.rb b/spec/requests/admin/instances_spec.rb
deleted file mode 100644
index afb6602a22..0000000000
--- a/spec/requests/admin/instances_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Instances' do
-  describe 'GET /admin/instances/:id' do
-    context 'with an unknown domain' do
-      before { sign_in Fabricate(:admin_user) }
-
-      it 'returns http success' do
-        get admin_instance_path(id: 'unknown.example')
-
-        expect(response)
-          .to have_http_status(200)
-      end
-    end
-  end
-end
diff --git a/spec/requests/admin/invites_spec.rb b/spec/requests/admin/invites_spec.rb
deleted file mode 100644
index c027fd30cf..0000000000
--- a/spec/requests/admin/invites_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Invites' do
-  describe 'POST /admin/invites' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_invites_path(invite: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/ip_blocks_spec.rb b/spec/requests/admin/ip_blocks_spec.rb
deleted file mode 100644
index e74961aa3c..0000000000
--- a/spec/requests/admin/ip_blocks_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin IP Blocks' do
-  describe 'POST /admin/ip_blocks' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_ip_blocks_path(ip_block: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-
-  describe 'POST /admin/ip_blocks/batch' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post batch_admin_ip_blocks_path(form_ip_block_batch: 'invalid')
-
-      expect(response)
-        .to redirect_to(admin_ip_blocks_path)
-    end
-  end
-end
diff --git a/spec/requests/admin/relays_spec.rb b/spec/requests/admin/relays_spec.rb
deleted file mode 100644
index 5dcdfe9892..0000000000
--- a/spec/requests/admin/relays_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Relays' do
-  describe 'POST /admin/relays' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_relays_path(relay: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/report_notes_spec.rb b/spec/requests/admin/report_notes_spec.rb
deleted file mode 100644
index 170648a714..0000000000
--- a/spec/requests/admin/report_notes_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Report Notes' do
-  describe 'POST /admin/report_notes' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_report_notes_path(report_note: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/roles_spec.rb b/spec/requests/admin/roles_spec.rb
deleted file mode 100644
index 21853eb203..0000000000
--- a/spec/requests/admin/roles_spec.rb
+++ /dev/null
@@ -1,144 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Roles' do
-  context 'when signed in as lower permissions user' do
-    let(:user_role) { Fabricate(:user_role, permissions: UserRole::Flags::NONE) }
-
-    before { sign_in Fabricate(:user, role: user_role) }
-
-    describe 'GET /admin/roles' do
-      it 'returns http forbidden' do
-        get admin_roles_path
-
-        expect(response)
-          .to have_http_status(403)
-      end
-    end
-
-    describe 'GET /admin/roles/new' do
-      it 'returns http forbidden' do
-        get new_admin_role_path
-
-        expect(response)
-          .to have_http_status(403)
-      end
-    end
-
-    describe 'GET /admin/roles/:id/edit' do
-      let(:role) { Fabricate(:user_role) }
-
-      it 'returns http forbidden' do
-        get edit_admin_role_path(role)
-
-        expect(response)
-          .to have_http_status(403)
-      end
-    end
-
-    describe 'PUT /admin/roles/:id' do
-      let(:role) { Fabricate(:user_role) }
-
-      it 'returns http forbidden' do
-        put admin_role_path(role)
-
-        expect(response)
-          .to have_http_status(403)
-      end
-    end
-
-    describe 'DELETE /admin/roles/:id' do
-      let(:role) { Fabricate(:user_role) }
-
-      it 'returns http forbidden' do
-        delete admin_role_path(role)
-
-        expect(response)
-          .to have_http_status(403)
-      end
-    end
-  end
-
-  context 'when user has permissions to manage roles' do
-    let(:user_role) { Fabricate(:user_role, permissions: UserRole::FLAGS[:manage_users]) }
-
-    before { sign_in Fabricate(:user, role: user_role) }
-
-    context 'when target role permission outranks user' do
-      let(:role) { Fabricate(:user_role, position: user_role.position + 1) }
-
-      describe 'GET /admin/roles/:id/edit' do
-        it 'returns http forbidden' do
-          get edit_admin_role_path(role)
-
-          expect(response)
-            .to have_http_status(403)
-        end
-      end
-
-      describe 'PUT /admin/roles/:id' do
-        it 'returns http forbidden' do
-          put admin_role_path(role)
-
-          expect(response)
-            .to have_http_status(403)
-        end
-      end
-
-      describe 'DELETE /admin/roles/:id' do
-        it 'returns http forbidden' do
-          delete admin_role_path(role)
-
-          expect(response)
-            .to have_http_status(403)
-        end
-      end
-    end
-  end
-
-  context 'when attempting to add permissions the user does not have' do
-    let(:user_role) { Fabricate(:user_role, permissions: UserRole::FLAGS[:manage_roles], position: 5) }
-
-    before { sign_in Fabricate(:user, role: user_role) }
-
-    describe 'POST /admin/roles' do
-      subject { post admin_roles_path, params: { user_role: { name: 'Bar', position: 2, permissions_as_keys: %w(manage_roles manage_users manage_reports) } } }
-
-      it 'does not create role' do
-        expect { subject }
-          .to_not change(UserRole, :count)
-
-        expect(response.body)
-          .to include(I18n.t('admin.roles.add_new'))
-      end
-    end
-
-    describe 'PUT /admin/roles/:id' do
-      subject { put admin_role_path(role), params: { user_role: { position: 2, permissions_as_keys: %w(manage_roles manage_users manage_reports) } } }
-
-      let(:role) { Fabricate(:user_role, name: 'Bar') }
-
-      it 'does not create role' do
-        expect { subject }
-          .to_not(change { role.reload.permissions })
-
-        expect(response.parsed_body.title)
-          .to match(I18n.t('admin.roles.edit', name: 'Bar'))
-      end
-    end
-  end
-
-  context 'when signed in as admin' do
-    before { sign_in Fabricate(:admin_user) }
-
-    describe 'POST /admin/roles' do
-      it 'gracefully handles invalid nested params' do
-        post admin_roles_path(user_role: 'invalid')
-
-        expect(response)
-          .to have_http_status(400)
-      end
-    end
-  end
-end
diff --git a/spec/requests/admin/rules_spec.rb b/spec/requests/admin/rules_spec.rb
deleted file mode 100644
index 9382b38e59..0000000000
--- a/spec/requests/admin/rules_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Rules' do
-  describe 'POST /admin/rules' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_rules_path(rule: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/settings/about_spec.rb b/spec/requests/admin/settings/about_spec.rb
deleted file mode 100644
index 28be2c252d..0000000000
--- a/spec/requests/admin/settings/about_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Settings About' do
-  describe 'PUT /admin/settings/about' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      put admin_settings_about_path(form_admin_settings: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/settings/branding_spec.rb b/spec/requests/admin/settings/branding_spec.rb
deleted file mode 100644
index e5206f056f..0000000000
--- a/spec/requests/admin/settings/branding_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Settings Branding' do
-  describe 'When signed in as an admin' do
-    before { sign_in Fabricate(:admin_user) }
-
-    describe 'PUT /admin/settings/branding' do
-      it 'cannot create a setting value for a non-admin key' do
-        expect { put admin_settings_branding_path, params: { form_admin_settings: { new_setting_key: 'New key value' } } }
-          .to_not change(Setting, :new_setting_key).from(nil)
-
-        expect(response)
-          .to have_http_status(400)
-      end
-    end
-  end
-end
diff --git a/spec/requests/admin/statuses_spec.rb b/spec/requests/admin/statuses_spec.rb
deleted file mode 100644
index 9fa732e178..0000000000
--- a/spec/requests/admin/statuses_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Statuses' do
-  describe 'POST /admin/accounts/:account_id/statuses/batch' do
-    before { sign_in Fabricate(:admin_user) }
-
-    let(:account) { Fabricate :account }
-
-    it 'gracefully handles invalid nested params' do
-      post batch_admin_account_statuses_path(account.id, admin_status_batch_action: 'invalid')
-
-      expect(response)
-        .to redirect_to(admin_account_statuses_path(account.id))
-    end
-  end
-end
diff --git a/spec/requests/admin/tags_spec.rb b/spec/requests/admin/tags_spec.rb
deleted file mode 100644
index 653c5bd935..0000000000
--- a/spec/requests/admin/tags_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Tags' do
-  describe 'PUT /admin/tags/:id' do
-    before { sign_in Fabricate(:admin_user) }
-
-    let(:tag) { Fabricate :tag }
-
-    it 'gracefully handles invalid nested params' do
-      put admin_tag_path(tag.id, tag: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/terms_of_service/drafts_spec.rb b/spec/requests/admin/terms_of_service/drafts_spec.rb
deleted file mode 100644
index 2c3c77193e..0000000000
--- a/spec/requests/admin/terms_of_service/drafts_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Terms Drafts' do
-  describe 'PUT /admin/terms_of_service/draft' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      put admin_terms_of_service_draft_path(terms_of_service: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/terms_of_service/generates_spec.rb b/spec/requests/admin/terms_of_service/generates_spec.rb
deleted file mode 100644
index b8c51fdf2c..0000000000
--- a/spec/requests/admin/terms_of_service/generates_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Terms Generates' do
-  describe 'POST /admin/terms_of_service/generates' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_terms_of_service_generate_path(terms_of_service_generator: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/trends/links/preview_card_providers_spec.rb b/spec/requests/admin/trends/links/preview_card_providers_spec.rb
deleted file mode 100644
index 69bf479c68..0000000000
--- a/spec/requests/admin/trends/links/preview_card_providers_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Trends Links Preview Card Providers' do
-  describe 'POST /admin/trends/links/publishers/batch' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post batch_admin_trends_links_preview_card_providers_path(trends_preview_card_provider_batch: 'invalid')
-
-      expect(response)
-        .to redirect_to(admin_trends_links_preview_card_providers_path)
-    end
-  end
-end
diff --git a/spec/requests/admin/trends/links_spec.rb b/spec/requests/admin/trends/links_spec.rb
deleted file mode 100644
index 9fbfd56704..0000000000
--- a/spec/requests/admin/trends/links_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Trends Links' do
-  describe 'POST /admin/trends/links/batch' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post batch_admin_trends_links_path(trends_preview_card_batch: 'invalid')
-
-      expect(response)
-        .to redirect_to(admin_trends_links_path)
-    end
-  end
-end
diff --git a/spec/requests/admin/trends/statuses_spec.rb b/spec/requests/admin/trends/statuses_spec.rb
deleted file mode 100644
index ceae24ec8f..0000000000
--- a/spec/requests/admin/trends/statuses_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Trends Statuses' do
-  describe 'POST /admin/trends/statuses/batch' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post batch_admin_trends_statuses_path(trends_status_batch: 'invalid')
-
-      expect(response)
-        .to redirect_to(admin_trends_statuses_path)
-    end
-  end
-end
diff --git a/spec/requests/admin/trends/tags_spec.rb b/spec/requests/admin/trends/tags_spec.rb
deleted file mode 100644
index e505be7a19..0000000000
--- a/spec/requests/admin/trends/tags_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Trends Tags' do
-  describe 'POST /admin/trends/tags/batch' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post batch_admin_trends_tags_path(trends_tag_batch: 'invalid')
-
-      expect(response)
-        .to redirect_to(admin_trends_tags_path)
-    end
-  end
-end
diff --git a/spec/requests/admin/users/roles_spec.rb b/spec/requests/admin/users/roles_spec.rb
deleted file mode 100644
index fb88e4c87a..0000000000
--- a/spec/requests/admin/users/roles_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Users Roles' do
-  context 'when target user is higher ranked than current user' do
-    let(:current_role) { UserRole.create(name: 'Foo', permissions: UserRole::FLAGS[:manage_roles], position: 10) }
-    let(:current_user) { Fabricate(:user, role: current_role) }
-
-    let(:previous_role) { UserRole.create(name: 'Baz', permissions: UserRole::FLAGS[:administrator], position: 100) }
-    let(:user) { Fabricate(:user, role: previous_role) }
-
-    before { sign_in(current_user) }
-
-    describe 'GET /admin/users/:user_id/role' do
-      it 'returns http forbidden' do
-        get admin_user_role_path(user.id)
-
-        expect(response)
-          .to have_http_status(403)
-      end
-    end
-
-    describe 'PUT /admin/users/:user_id/role' do
-      it 'returns http forbidden' do
-        put admin_user_role_path(user.id)
-
-        expect(response)
-          .to have_http_status(403)
-      end
-    end
-  end
-
-  describe 'PUT /admin/users/:user_id/role' do
-    before { sign_in Fabricate(:admin_user) }
-
-    let(:user) { Fabricate :user }
-
-    it 'gracefully handles invalid nested params' do
-      put admin_user_role_path(user.id, user: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/warning_presets_spec.rb b/spec/requests/admin/warning_presets_spec.rb
deleted file mode 100644
index 6527cec30f..0000000000
--- a/spec/requests/admin/warning_presets_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Warning Presets' do
-  describe 'POST /admin/warning_presets' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_warning_presets_path(account_warning_preset: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/admin/webhooks_spec.rb b/spec/requests/admin/webhooks_spec.rb
deleted file mode 100644
index fe047abd3c..0000000000
--- a/spec/requests/admin/webhooks_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Webhooks' do
-  describe 'POST /admin/webhooks' do
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'gracefully handles invalid nested params' do
-      post admin_webhooks_path(webhook: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/api/fasp/debug/v0/callback/responses_spec.rb b/spec/requests/api/fasp/debug/v0/callback/responses_spec.rb
deleted file mode 100644
index 58c5e8897b..0000000000
--- a/spec/requests/api/fasp/debug/v0/callback/responses_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Api::Fasp::Debug::V0::Callback::Responses', feature: :fasp do
-  include ProviderRequestHelper
-
-  describe 'POST /api/fasp/debug/v0/callback/responses' do
-    let(:provider) { Fabricate(:debug_fasp) }
-
-    it 'create a record of the callback' do
-      payload = { test: 'call' }
-      headers = request_authentication_headers(provider,
-                                               url: api_fasp_debug_v0_callback_responses_url,
-                                               method: :post,
-                                               body: payload)
-
-      expect do
-        post api_fasp_debug_v0_callback_responses_path, headers:, params: payload, as: :json
-      end.to change(Fasp::DebugCallback, :count).by(1)
-      expect(response).to have_http_status(201)
-
-      debug_callback = Fasp::DebugCallback.last
-      expect(debug_callback.fasp_provider).to eq provider
-      expect(debug_callback.request_body).to eq '{"test":"call"}'
-    end
-  end
-end
diff --git a/spec/requests/api/fasp/registrations_spec.rb b/spec/requests/api/fasp/registrations_spec.rb
deleted file mode 100644
index 53fdfeef5c..0000000000
--- a/spec/requests/api/fasp/registrations_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Api::Fasp::Registrations', feature: :fasp do
-  describe 'POST /api/fasp/registration' do
-    subject do
-      post api_fasp_registration_path, params:
-    end
-
-    context 'when given valid data' do
-      let(:params) do
-        {
-          name: 'Test Provider',
-          baseUrl: 'https://newprovider.example.com/fasp',
-          serverId: '123',
-          publicKey: '9qgjOfWRhozWc9dwx5JmbshizZ7TyPBhYk9+b5tE3e4=',
-        }
-      end
-
-      it 'creates a new provider' do
-        expect { subject }.to change(Fasp::Provider, :count).by(1)
-
-        expect(response).to have_http_status 200
-      end
-    end
-
-    context 'when given invalid data' do
-      let(:params) do
-        {
-          name: 'incomplete',
-        }
-      end
-
-      it 'does not create a provider and returns an error code' do
-        expect { subject }.to_not change(Fasp::Provider, :count)
-
-        expect(response).to have_http_status 422
-      end
-    end
-  end
-end
diff --git a/spec/requests/api/v1/accounts/credentials_spec.rb b/spec/requests/api/v1/accounts/credentials_spec.rb
index 68ea259481..0bd3ace132 100644
--- a/spec/requests/api/v1/accounts/credentials_spec.rb
+++ b/spec/requests/api/v1/accounts/credentials_spec.rb
@@ -53,6 +53,8 @@ RSpec.describe 'credentials API' do
       patch '/api/v1/accounts/update_credentials', headers: headers, params: params
     end
 
+    before { allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async) }
+
     let(:params) do
       {
         avatar: fixture_file_upload('avatar.gif', 'image/gif'),
@@ -62,7 +64,6 @@ RSpec.describe 'credentials API' do
         indexable: true,
         locked: false,
         note: 'Hello!',
-        attribution_domains: ['example.com'],
         source: {
           privacy: 'unlisted',
           sensitive: true,
@@ -84,7 +85,7 @@ RSpec.describe 'credentials API' do
     end
 
     describe 'with invalid data' do
-      let(:params) { { note: 'a' * 2 * Account::NOTE_LENGTH_LIMIT } }
+      let(:params) { { note: 'This is too long. ' * 30 } }
 
       it 'returns http unprocessable entity' do
         subject
@@ -111,7 +112,7 @@ RSpec.describe 'credentials API' do
       })
 
       expect(ActivityPub::UpdateDistributionWorker)
-        .to have_enqueued_sidekiq_job(user.account_id)
+        .to have_received(:perform_async).with(user.account_id)
     end
 
     def expect_account_updates
@@ -120,8 +121,7 @@ RSpec.describe 'credentials API' do
           display_name: eq("Alice Isn't Dead"),
           note: 'Hello!',
           avatar: exist,
-          header: exist,
-          attribution_domains: ['example.com']
+          header: exist
         )
     end
 
diff --git a/spec/requests/api/v1/accounts/notes_spec.rb b/spec/requests/api/v1/accounts/notes_spec.rb
index e616df1e6f..1677ec07e3 100644
--- a/spec/requests/api/v1/accounts/notes_spec.rb
+++ b/spec/requests/api/v1/accounts/notes_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe 'Accounts Notes API' do
     end
 
     context 'when account note exceeds allowed length', :aggregate_failures do
-      let(:comment) { 'a' * AccountNote::COMMENT_SIZE_LIMIT * 2 }
+      let(:comment) { 'a' * 2_001 }
 
       it 'does not create account note' do
         subject
diff --git a/spec/requests/api/v1/accounts/endorsements_spec.rb b/spec/requests/api/v1/accounts/pins_spec.rb
similarity index 57%
rename from spec/requests/api/v1/accounts/endorsements_spec.rb
rename to spec/requests/api/v1/accounts/pins_spec.rb
index 6e0996a1f1..8ebcb27d28 100644
--- a/spec/requests/api/v1/accounts/endorsements_spec.rb
+++ b/spec/requests/api/v1/accounts/pins_spec.rb
@@ -13,30 +13,8 @@ RSpec.describe 'Accounts Pins API' do
     kevin.account.followers << user.account
   end
 
-  describe 'GET /api/v1/accounts/:account_id/endorsements' do
-    subject { get "/api/v1/accounts/#{user.account.id}/endorsements", headers: headers }
-
-    let(:scopes) { 'read:accounts' }
-
-    before do
-      user.account.endorsed_accounts << kevin.account
-    end
-
-    it 'returns the expected accounts', :aggregate_failures do
-      subject
-
-      expect(response).to have_http_status(200)
-      expect(response.content_type)
-        .to start_with('application/json')
-      expect(response.parsed_body)
-        .to contain_exactly(
-          hash_including(id: kevin.account_id.to_s)
-        )
-    end
-  end
-
-  describe 'POST /api/v1/accounts/:account_id/endorse' do
-    subject { post "/api/v1/accounts/#{kevin.account.id}/endorse", headers: headers }
+  describe 'POST /api/v1/accounts/:account_id/pin' do
+    subject { post "/api/v1/accounts/#{kevin.account.id}/pin", headers: headers }
 
     it 'creates account_pin', :aggregate_failures do
       expect do
@@ -48,8 +26,8 @@ RSpec.describe 'Accounts Pins API' do
     end
   end
 
-  describe 'POST /api/v1/accounts/:account_id/unendorse' do
-    subject { post "/api/v1/accounts/#{kevin.account.id}/unendorse", headers: headers }
+  describe 'POST /api/v1/accounts/:account_id/unpin' do
+    subject { post "/api/v1/accounts/#{kevin.account.id}/unpin", headers: headers }
 
     before do
       Fabricate(:account_pin, account: user.account, target_account: kevin.account)
diff --git a/spec/requests/api/v1/accounts_spec.rb b/spec/requests/api/v1/accounts_spec.rb
index 329bb5f1e4..d423a08f12 100644
--- a/spec/requests/api/v1/accounts_spec.rb
+++ b/spec/requests/api/v1/accounts_spec.rb
@@ -74,45 +74,12 @@ RSpec.describe '/api/v1/accounts' do
 
   describe 'POST /api/v1/accounts' do
     subject do
-      post '/api/v1/accounts', headers: headers, params: { username: 'test', password: '12345678', email: 'hello@world.tld', agreement: agreement, date_of_birth: date_of_birth }
+      post '/api/v1/accounts', headers: headers, params: { username: 'test', password: '12345678', email: 'hello@world.tld', agreement: agreement }
     end
 
     let(:client_app) { Fabricate(:application) }
     let(:token) { Doorkeeper::AccessToken.find_or_create_for(application: client_app, resource_owner: nil, scopes: 'read write', use_refresh_token: false) }
     let(:agreement) { nil }
-    let(:date_of_birth) { nil }
-
-    context 'when age verification is enabled' do
-      before do
-        Setting.min_age = 16
-      end
-
-      let(:agreement) { 'true' }
-
-      context 'when date of birth is below age limit' do
-        let(:date_of_birth) { 13.years.ago.strftime('%d.%m.%Y') }
-
-        it 'returns http unprocessable entity' do
-          subject
-
-          expect(response).to have_http_status(422)
-          expect(response.content_type)
-            .to start_with('application/json')
-        end
-      end
-
-      context 'when date of birth is over age limit' do
-        let(:date_of_birth) { 17.years.ago.strftime('%d.%m.%Y') }
-
-        it 'creates a user', :aggregate_failures do
-          subject
-
-          expect(response).to have_http_status(200)
-          expect(response.content_type)
-            .to start_with('application/json')
-        end
-      end
-    end
 
     context 'when given truthy agreement' do
       let(:agreement) { 'true' }
diff --git a/spec/requests/api/v1/admin/dimensions_spec.rb b/spec/requests/api/v1/admin/dimensions_spec.rb
index 3a4cd91716..81fb580ba7 100644
--- a/spec/requests/api/v1/admin/dimensions_spec.rb
+++ b/spec/requests/api/v1/admin/dimensions_spec.rb
@@ -3,7 +3,7 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin Dimensions' do
-  let(:user)    { Fabricate(:admin_user) }
+  let(:user)    { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
   let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
   let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
   let(:account) { Fabricate(:account) }
diff --git a/spec/requests/api/v1/admin/domain_blocks_spec.rb b/spec/requests/api/v1/admin/domain_blocks_spec.rb
index c43f69b7d6..7e37300a77 100644
--- a/spec/requests/api/v1/admin/domain_blocks_spec.rb
+++ b/spec/requests/api/v1/admin/domain_blocks_spec.rb
@@ -258,19 +258,6 @@ RSpec.describe 'Domain Blocks' do
           .to start_with('application/json')
       end
     end
-
-    context 'when severity is invalid' do
-      let(:params) { { domain: 'bar.com', severity: :bar } }
-
-      it 'returns http unprocessable entity' do
-        subject
-
-        expect(response).to have_http_status(422)
-        expect(response.content_type)
-          .to start_with('application/json')
-        expect(response.parsed_body[:error]).to eq('Validation failed: Severity is not included in the list')
-      end
-    end
   end
 
   describe 'PUT /api/v1/admin/domain_blocks/:id' do
diff --git a/spec/requests/api/v1/admin/ip_blocks_spec.rb b/spec/requests/api/v1/admin/ip_blocks_spec.rb
index 59ef8d2966..aa3db33915 100644
--- a/spec/requests/api/v1/admin/ip_blocks_spec.rb
+++ b/spec/requests/api/v1/admin/ip_blocks_spec.rb
@@ -187,16 +187,6 @@ RSpec.describe 'IP Blocks' do
           .to start_with('application/json')
       end
     end
-
-    context 'when the given severity is invalid' do
-      let(:params) { { ip: '151.0.32.55', severity: 'invalid' } }
-
-      it 'returns http unprocessable entity' do
-        subject
-
-        expect(response).to have_http_status(422)
-      end
-    end
   end
 
   describe 'PUT /api/v1/admin/ip_blocks/:id' do
diff --git a/spec/requests/api/v1/admin/measures_spec.rb b/spec/requests/api/v1/admin/measures_spec.rb
index b55cd0f1b2..519b5cc7b3 100644
--- a/spec/requests/api/v1/admin/measures_spec.rb
+++ b/spec/requests/api/v1/admin/measures_spec.rb
@@ -3,7 +3,7 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin Measures' do
-  let(:user)    { Fabricate(:admin_user) }
+  let(:user)    { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
   let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
   let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
   let(:account) { Fabricate(:account) }
diff --git a/spec/requests/api/v1/admin/retention_spec.rb b/spec/requests/api/v1/admin/retention_spec.rb
index 25e626e259..e28bc2a94f 100644
--- a/spec/requests/api/v1/admin/retention_spec.rb
+++ b/spec/requests/api/v1/admin/retention_spec.rb
@@ -3,7 +3,7 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin Retention' do
-  let(:user)    { Fabricate(:admin_user) }
+  let(:user)    { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
   let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
   let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
   let(:account) { Fabricate(:account) }
diff --git a/spec/requests/api/v1/antennas_spec.rb b/spec/requests/api/v1/antennas_spec.rb
index 102aabcaa7..913789fe99 100644
--- a/spec/requests/api/v1/antennas_spec.rb
+++ b/spec/requests/api/v1/antennas_spec.rb
@@ -19,7 +19,6 @@ RSpec.describe 'Antennas' do
         Fabricate(:antenna, account: user.account, title: 'second antenna', with_media_only: true),
         Fabricate(:antenna, account: user.account, title: 'third antenna', stl: true),
         Fabricate(:antenna, account: user.account, title: 'fourth antenna', ignore_reblog: true),
-        Fabricate(:antenna, account: user.account, title: 'fourth antenna', favourite: false),
       ]
     end
 
@@ -38,7 +37,6 @@ RSpec.describe 'Antennas' do
           domains_count: 0,
           tags_count: 0,
           keywords_count: 0,
-          favourite: antenna.favourite,
         }
       end
     end
@@ -82,8 +80,7 @@ RSpec.describe 'Antennas' do
         accounts_count: 0,
         domains_count: 0,
         tags_count: 0,
-        keywords_count: 0,
-        favourite: true
+        keywords_count: 0
       )
     end
 
@@ -154,7 +151,7 @@ RSpec.describe 'Antennas' do
     end
 
     let(:antenna) { Fabricate(:antenna, account: user.account, title: 'my antenna') }
-    let(:params) { { title: 'antenna', ignore_reblog: 'true', insert_feeds: 'true', favourite: 'false' } }
+    let(:params) { { title: 'antenna', ignore_reblog: 'true', insert_feeds: 'true' } }
 
     it_behaves_like 'forbidden for wrong scope', 'read read:lists'
 
@@ -163,7 +160,6 @@ RSpec.describe 'Antennas' do
         .to change_antenna_title
         .and change_antenna_ignore_reblog
         .and change_antenna_insert_feeds
-        .and change_antenna_favourite
 
       expect(response).to have_http_status(200)
       antenna.reload
@@ -180,8 +176,7 @@ RSpec.describe 'Antennas' do
         accounts_count: 0,
         domains_count: 0,
         tags_count: 0,
-        keywords_count: 0,
-        favourite: false
+        keywords_count: 0
       )
     end
 
@@ -197,10 +192,6 @@ RSpec.describe 'Antennas' do
       change { antenna.reload.insert_feeds }.from(false).to(true)
     end
 
-    def change_antenna_favourite
-      change { antenna.reload.favourite }.from(true).to(false)
-    end
-
     context 'when the antenna does not exist' do
       it 'returns http not found' do
         put '/api/v1/antennas/-1', headers: headers, params: params
diff --git a/spec/requests/api/v1/apps_spec.rb b/spec/requests/api/v1/apps_spec.rb
index 3120ab9c64..4e9147ba32 100644
--- a/spec/requests/api/v1/apps_spec.rb
+++ b/spec/requests/api/v1/apps_spec.rb
@@ -122,7 +122,7 @@ RSpec.describe 'Apps' do
     end
 
     context 'with a too-long name' do
-      let(:client_name) { 'a' * Doorkeeper::Application::APP_NAME_LIMIT * 2 }
+      let(:client_name) { 'hoge' * 20 }
 
       it 'returns http unprocessable entity' do
         subject
@@ -134,7 +134,7 @@ RSpec.describe 'Apps' do
     end
 
     context 'with a too-long website' do
-      let(:website) { "https://foo.bar/#{'a' * Doorkeeper::Application::APP_WEBSITE_LIMIT * 2}" }
+      let(:website) { "https://foo.bar/#{'hoge' * 2_000}" }
 
       it 'returns http unprocessable entity' do
         subject
@@ -146,7 +146,7 @@ RSpec.describe 'Apps' do
     end
 
     context 'with a too-long redirect_uri' do
-      let(:redirect_uris) { "https://app.example/#{'a' * Doorkeeper::Application::APP_REDIRECT_URI_LIMIT * 2}" }
+      let(:redirect_uris) { "https://app.example/#{'hoge' * 2_000}" }
 
       it 'returns http unprocessable entity' do
         subject
diff --git a/spec/requests/api/v1/domain_blocks/previews_spec.rb b/spec/requests/api/v1/domain_blocks/previews_spec.rb
deleted file mode 100644
index eb191f436b..0000000000
--- a/spec/requests/api/v1/domain_blocks/previews_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Domain Blocks Previews API' do
-  let(:user)    { Fabricate(:user) }
-  let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
-  let(:scopes)  { 'write:blocks' }
-  let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
-  let(:account) { Fabricate(:account, user: user) }
-
-  describe 'GET /api/v1/domain_blocks/preview' do
-    subject { get '/api/v1/domain_blocks/preview', params: { domain: domain }, headers: headers }
-
-    let(:domain) { 'host.example' }
-
-    before do
-      Fabricate :follow, account: account, target_account: Fabricate(:account, domain: domain)
-      Fabricate :follow, target_account: account, account: Fabricate(:account, domain: domain)
-    end
-
-    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-
-    it 'returns http success and follower counts' do
-      subject
-
-      expect(response)
-        .to have_http_status(200)
-      expect(response.content_type)
-        .to start_with('application/json')
-      expect(response.parsed_body)
-        .to include(followers_count: 1)
-        .and include(following_count: 1)
-    end
-  end
-end
diff --git a/spec/requests/api/v1/instances/domain_blocks_spec.rb b/spec/requests/api/v1/instances/domain_blocks_spec.rb
index 43cc71aed5..40b79c9691 100644
--- a/spec/requests/api/v1/instances/domain_blocks_spec.rb
+++ b/spec/requests/api/v1/instances/domain_blocks_spec.rb
@@ -4,13 +4,13 @@ require 'rails_helper'
 
 RSpec.describe 'Domain Blocks' do
   let(:user)    { Fabricate(:user) }
-  let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+  let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes).token }
   let(:scopes)  { 'read' }
-  let(:headers) { { Authorization: "Bearer #{token.token}" } }
+  let(:headers) { { Authorization: "Bearer #{token}" } }
 
   describe 'GET /api/v1/instance/domain_blocks' do
     let(:user) { Fabricate(:user) }
-    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) }
+    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id).token }
 
     before { Fabricate(:domain_block) }
 
@@ -60,7 +60,7 @@ RSpec.describe 'Domain Blocks' do
           before { user.update(approved: false) }
 
           it 'returns http not found' do
-            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token.token}" }
+            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token}" }
 
             expect(response)
               .to have_http_status(404)
@@ -71,7 +71,7 @@ RSpec.describe 'Domain Blocks' do
           before { user.update(confirmed_at: nil) }
 
           it 'returns http not found' do
-            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token.token}" }
+            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token}" }
 
             expect(response)
               .to have_http_status(404)
@@ -82,7 +82,7 @@ RSpec.describe 'Domain Blocks' do
           before { user.update(disabled: true) }
 
           it 'returns http not found' do
-            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token.token}" }
+            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token}" }
 
             expect(response)
               .to have_http_status(404)
@@ -93,7 +93,7 @@ RSpec.describe 'Domain Blocks' do
           before { user.account.update(suspended_at: Time.zone.now) }
 
           it 'returns http not found' do
-            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token.token}" }
+            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token}" }
 
             expect(response)
               .to have_http_status(403)
@@ -104,7 +104,7 @@ RSpec.describe 'Domain Blocks' do
           before { user.account.update(moved_to_account_id: Fabricate(:account).id) }
 
           it 'returns http success' do
-            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token.token}" }
+            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token}" }
 
             expect(response)
               .to have_http_status(200)
@@ -121,7 +121,7 @@ RSpec.describe 'Domain Blocks' do
 
         context 'with normal user' do
           it 'returns http success' do
-            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token.token}" }
+            get api_v1_instance_domain_blocks_path, headers: { 'Authorization' => "Bearer #{token}" }
 
             expect(response)
               .to have_http_status(200)
diff --git a/spec/requests/api/v1/instances/languages_spec.rb b/spec/requests/api/v1/instances/languages_spec.rb
index 0b325bbb6d..3d188d23c0 100644
--- a/spec/requests/api/v1/instances/languages_spec.rb
+++ b/spec/requests/api/v1/instances/languages_spec.rb
@@ -9,21 +9,10 @@ RSpec.describe 'Languages' do
     end
 
     it 'returns http success and includes supported languages' do
-      expect(response)
-        .to have_http_status(200)
+      expect(response).to have_http_status(200)
       expect(response.content_type)
         .to start_with('application/json')
-      expect(response.parsed_body)
-        .to match_array(supported_locale_expectations)
-    end
-
-    def supported_locale_expectations
-      LanguagesHelper::SUPPORTED_LOCALES.map do |key, values|
-        include(
-          code: key.to_s,
-          name: values.first
-        )
-      end
+      expect(response.parsed_body.pluck(:code)).to match_array LanguagesHelper::SUPPORTED_LOCALES.keys.map(&:to_s)
     end
   end
 end
diff --git a/spec/requests/api/v1/instances/terms_of_services_spec.rb b/spec/requests/api/v1/instances/terms_of_services_spec.rb
deleted file mode 100644
index 5feb49f48d..0000000000
--- a/spec/requests/api/v1/instances/terms_of_services_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Terms of Service' do
-  describe 'GET /api/v1/instance/terms_of_service' do
-    before do
-      Fabricate(:terms_of_service)
-    end
-
-    it 'returns http success' do
-      get api_v1_instance_terms_of_service_path
-
-      expect(response)
-        .to have_http_status(200)
-      expect(response.content_type)
-        .to start_with('application/json')
-
-      expect(response.parsed_body)
-        .to be_present
-        .and include(:content)
-    end
-  end
-end
diff --git a/spec/requests/api/v1/lists/accounts_spec.rb b/spec/requests/api/v1/lists/accounts_spec.rb
index da815f0635..3911d1f28b 100644
--- a/spec/requests/api/v1/lists/accounts_spec.rb
+++ b/spec/requests/api/v1/lists/accounts_spec.rb
@@ -95,7 +95,7 @@ RSpec.describe 'Accounts' do
       it 'does not add the account to the list', :aggregate_failures do
         subject
 
-        expect(response).to have_http_status(422)
+        expect(response).to have_http_status(404)
         expect(response.content_type)
           .to start_with('application/json')
         expect(list.accounts).to_not include(bob)
diff --git a/spec/requests/api/v1/lists_spec.rb b/spec/requests/api/v1/lists_spec.rb
index 13a954b9ac..81241c3906 100644
--- a/spec/requests/api/v1/lists_spec.rb
+++ b/spec/requests/api/v1/lists_spec.rb
@@ -20,7 +20,6 @@ RSpec.describe 'Lists' do
         Fabricate(:list, account: user.account, title: 'third list', replies_policy: :none),
         Fabricate(:list, account: user.account, title: 'fourth list', exclusive: true),
         Fabricate(:list, account: user.account, title: 'fifth list', notify: true),
-        Fabricate(:list, account: user.account, title: 'fifth list', favourite: false),
       ]
     end
 
@@ -33,7 +32,6 @@ RSpec.describe 'Lists' do
           exclusive: list.exclusive,
           antennas: list.antennas,
           notify: list.notify,
-          favourite: list.favourite,
         }
       end
     end
@@ -76,7 +74,6 @@ RSpec.describe 'Lists' do
         exclusive: list.exclusive,
         antennas: list.antennas,
         notify: list.notify,
-        favourite: true,
       })
     end
 
@@ -140,12 +137,9 @@ RSpec.describe 'Lists' do
       it 'returns http unprocessable entity' do
         subject
 
-        expect(response)
-          .to have_http_status(422)
+        expect(response).to have_http_status(422)
         expect(response.content_type)
           .to start_with('application/json')
-        expect(response.parsed_body)
-          .to include(error: /Replies policy is not included/)
       end
     end
   end
@@ -156,7 +150,7 @@ RSpec.describe 'Lists' do
     end
 
     let(:list)   { Fabricate(:list, account: user.account, title: 'my list') }
-    let(:params) { { title: 'list', replies_policy: 'followed', exclusive: 'true', favourite: 'false' } }
+    let(:params) { { title: 'list', replies_policy: 'followed', exclusive: 'true' } }
 
     it_behaves_like 'forbidden for wrong scope', 'read read:lists'
 
@@ -165,7 +159,6 @@ RSpec.describe 'Lists' do
         .to change_list_title
         .and change_list_replies_policy
         .and change_list_exclusive
-        .and change_list_favourite
 
       expect(response).to have_http_status(200)
       expect(response.content_type)
@@ -179,7 +172,6 @@ RSpec.describe 'Lists' do
         exclusive: list.exclusive,
         antennas: list.antennas,
         notify: list.notify,
-        favourite: false,
       })
     end
 
@@ -195,10 +187,6 @@ RSpec.describe 'Lists' do
       change { list.reload.exclusive }.from(false).to(true)
     end
 
-    def change_list_favourite
-      change { list.reload.favourite }.from(true).to(false)
-    end
-
     context 'when the list does not exist' do
       it 'returns http not found' do
         put '/api/v1/lists/-1', headers: headers, params: params
@@ -222,102 +210,6 @@ RSpec.describe 'Lists' do
     end
   end
 
-  shared_examples 'check list permissions when post' do |path, params|
-    context 'when the list does not exist' do
-      it 'returns http not found' do
-        post path, headers: headers, params: params
-
-        expect(response).to have_http_status(404)
-        expect(response.content_type)
-          .to start_with('application/json')
-      end
-    end
-
-    context 'when the list belongs to another user' do
-      let(:list) { Fabricate(:list) }
-
-      it 'returns http not found' do
-        subject
-
-        expect(response).to have_http_status(404)
-        expect(response.content_type)
-          .to start_with('application/json')
-      end
-    end
-  end
-
-  describe 'POST /api/v1/lists/:id/favourite' do
-    subject do
-      post "/api/v1/lists/#{list.id}/favourite", headers: headers, params: {}
-    end
-
-    let(:list) { Fabricate(:list, account: user.account, title: 'my list', favourite: false) }
-
-    it_behaves_like 'forbidden for wrong scope', 'read read:lists'
-
-    it 'returns the updated list and updates values', :aggregate_failures do
-      expect { subject }
-        .to change_favourite
-
-      expect(response).to have_http_status(200)
-      expect(response.content_type)
-        .to start_with('application/json')
-      list.reload
-
-      expect(response.parsed_body).to match({
-        id: list.id.to_s,
-        title: list.title,
-        replies_policy: list.replies_policy,
-        exclusive: list.exclusive,
-        antennas: list.antennas,
-        notify: list.notify,
-        favourite: true,
-      })
-    end
-
-    def change_favourite
-      change { list.reload.favourite }.from(false).to(true)
-    end
-
-    it_behaves_like 'check list permissions when post', '/api/v1/lists/-1/favourite', {}
-  end
-
-  describe 'POST /api/v1/lists/:id/unfavourite' do
-    subject do
-      post "/api/v1/lists/#{list.id}/unfavourite", headers: headers, params: {}
-    end
-
-    let(:list) { Fabricate(:list, account: user.account, title: 'my list', favourite: true) }
-
-    it_behaves_like 'forbidden for wrong scope', 'read read:lists'
-
-    it 'returns the updated list and updates values', :aggregate_failures do
-      expect { subject }
-        .to change_favourite
-
-      expect(response).to have_http_status(200)
-      expect(response.content_type)
-        .to start_with('application/json')
-      list.reload
-
-      expect(response.parsed_body).to match({
-        id: list.id.to_s,
-        title: list.title,
-        replies_policy: list.replies_policy,
-        exclusive: list.exclusive,
-        antennas: list.antennas,
-        notify: list.notify,
-        favourite: false,
-      })
-    end
-
-    def change_favourite
-      change { list.reload.favourite }.from(true).to(false)
-    end
-
-    it_behaves_like 'check list permissions when post', '/api/v1/lists/-1/favourite', {}
-  end
-
   describe 'DELETE /api/v1/lists/:id' do
     subject do
       delete "/api/v1/lists/#{list.id}", headers: headers
diff --git a/spec/requests/api/v1/media_spec.rb b/spec/requests/api/v1/media_spec.rb
index 4d6e250207..d7d0b92f11 100644
--- a/spec/requests/api/v1/media_spec.rb
+++ b/spec/requests/api/v1/media_spec.rb
@@ -193,57 +193,4 @@ RSpec.describe 'Media' do
       end
     end
   end
-
-  describe 'DELETE /api/v1/media/:id' do
-    subject do
-      delete "/api/v1/media/#{media.id}", headers: headers
-    end
-
-    context 'when media is not attached to a status' do
-      let(:media) { Fabricate(:media_attachment, account: user.account, status: nil) }
-
-      it 'returns http empty response' do
-        subject
-
-        expect(response).to have_http_status(200)
-        expect(response.content_type)
-          .to start_with('application/json')
-
-        expect(MediaAttachment.where(id: media.id)).to_not exist
-      end
-    end
-
-    context 'when media is attached to a status' do
-      let(:media) { Fabricate(:media_attachment, account: user.account, status: Fabricate.build(:status)) }
-
-      it 'returns http unprocessable entity' do
-        subject
-
-        expect(response).to have_http_status(422)
-        expect(response.content_type)
-          .to start_with('application/json')
-        expect(response.parsed_body).to match(
-          a_hash_including(
-            error: 'Media attachment is currently used by a status'
-          )
-        )
-
-        expect(MediaAttachment.where(id: media.id)).to exist
-      end
-    end
-
-    context 'when the media belongs to somebody else' do
-      let(:media) { Fabricate(:media_attachment, status: nil) }
-
-      it 'returns http not found' do
-        subject
-
-        expect(response).to have_http_status(404)
-        expect(response.content_type)
-          .to start_with('application/json')
-
-        expect(MediaAttachment.where(id: media.id)).to exist
-      end
-    end
-  end
 end
diff --git a/spec/requests/api/v1/polls_spec.rb b/spec/requests/api/v1/polls_spec.rb
index c93231e1ee..fd38297931 100644
--- a/spec/requests/api/v1/polls_spec.rb
+++ b/spec/requests/api/v1/polls_spec.rb
@@ -36,31 +36,6 @@ RSpec.describe 'Polls' do
       end
     end
 
-    context 'when poll is remote and needs refresh' do
-      let(:poll) { Fabricate(:poll, last_fetched_at: nil, account: remote_account, status: status) }
-      let(:remote_account) { Fabricate :account, domain: 'host.example' }
-      let(:service) { instance_double(ActivityPub::FetchRemotePollService, call: nil) }
-      let(:status) { Fabricate(:status, visibility: 'public', account: remote_account) }
-
-      before { allow(ActivityPub::FetchRemotePollService).to receive(:new).and_return(service) }
-
-      it 'returns poll data and calls fetch remote service' do
-        subject
-
-        expect(response)
-          .to have_http_status(200)
-        expect(response.content_type)
-          .to start_with('application/json')
-        expect(response.parsed_body).to match(
-          a_hash_including(
-            id: poll.id.to_s
-          )
-        )
-        expect(service)
-          .to have_received(:call).with(poll, user.account)
-      end
-    end
-
     context 'when parent status is private' do
       let(:visibility) { 'private' }
 
diff --git a/spec/requests/api/v1/profiles_spec.rb b/spec/requests/api/v1/profiles_spec.rb
index de7a20b133..fd3ab4bf58 100644
--- a/spec/requests/api/v1/profiles_spec.rb
+++ b/spec/requests/api/v1/profiles_spec.rb
@@ -15,6 +15,10 @@ RSpec.describe 'Deleting profile images' do
   let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
 
   describe 'DELETE /api/v1/profile' do
+    before do
+      allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
+    end
+
     context 'when deleting an avatar' do
       context 'with wrong scope' do
         before do
@@ -34,8 +38,7 @@ RSpec.describe 'Deleting profile images' do
         account.reload
         expect(account.avatar).to_not exist
         expect(account.header).to exist
-        expect(ActivityPub::UpdateDistributionWorker)
-          .to have_enqueued_sidekiq_job(account.id)
+        expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
       end
     end
 
@@ -58,8 +61,7 @@ RSpec.describe 'Deleting profile images' do
         account.reload
         expect(account.avatar).to exist
         expect(account.header).to_not exist
-        expect(ActivityPub::UpdateDistributionWorker)
-          .to have_enqueued_sidekiq_job(account.id)
+        expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
       end
     end
   end
diff --git a/spec/requests/api/v1/push/subscriptions_spec.rb b/spec/requests/api/v1/push/subscriptions_spec.rb
index 69adeb9b6f..8ad672c95e 100644
--- a/spec/requests/api/v1/push/subscriptions_spec.rb
+++ b/spec/requests/api/v1/push/subscriptions_spec.rb
@@ -16,7 +16,6 @@ RSpec.describe 'API V1 Push Subscriptions' do
       subscription: {
         endpoint: endpoint,
         keys: keys,
-        standard: standard,
       },
     }.with_indifferent_access
   end
@@ -37,7 +36,6 @@ RSpec.describe 'API V1 Push Subscriptions' do
       },
     }.with_indifferent_access
   end
-  let(:standard) { '1' }
   let(:scopes) { 'push' }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
   let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
@@ -68,7 +66,6 @@ RSpec.describe 'API V1 Push Subscriptions' do
           user_id: eq(user.id),
           access_token_id: eq(token.id)
         )
-        .and be_standard
 
       expect(response.parsed_body.with_indifferent_access)
         .to include(
@@ -76,17 +73,6 @@ RSpec.describe 'API V1 Push Subscriptions' do
         )
     end
 
-    context 'when standard is provided as false value' do
-      let(:standard) { '0' }
-
-      it 'saves push subscription with standard as false' do
-        subject
-
-        expect(endpoint_push_subscription)
-          .to_not be_standard
-      end
-    end
-
     it 'replaces old subscription on repeat calls' do
       2.times { subject }
 
@@ -121,13 +107,6 @@ RSpec.describe 'API V1 Push Subscriptions' do
 
       it_behaves_like 'validation error'
     end
-
-    it 'gracefully handles invalid nested params' do
-      post api_v1_push_subscription_path, params: { subscription: 'invalid' }, headers: headers
-
-      expect(response)
-        .to have_http_status(400)
-    end
   end
 
   describe 'PUT /api/v1/push/subscription' do
@@ -154,30 +133,6 @@ RSpec.describe 'API V1 Push Subscriptions' do
           policy: alerts_payload[:data][:policy]
         )
     end
-
-    it 'gracefully handles invalid nested params' do
-      put api_v1_push_subscription_path(endpoint_push_subscription), params: { data: 'invalid' }, headers: headers
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-
-  describe 'GET /api/v1/push/subscription' do
-    subject { get '/api/v1/push/subscription', headers: headers }
-
-    before { create_subscription_with_token }
-
-    it 'shows subscription details' do
-      subject
-
-      expect(response)
-        .to have_http_status(200)
-      expect(response.content_type)
-        .to start_with('application/json')
-      expect(response.parsed_body)
-        .to include(endpoint: endpoint)
-    end
   end
 
   describe 'DELETE /api/v1/push/subscription' do
diff --git a/spec/requests/api/v1/reports_spec.rb b/spec/requests/api/v1/reports_spec.rb
index 1f113c649e..18b894bf63 100644
--- a/spec/requests/api/v1/reports_spec.rb
+++ b/spec/requests/api/v1/reports_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe 'Reports' do
       post '/api/v1/reports', headers: headers, params: params
     end
 
-    let!(:admin)         { Fabricate(:admin_user) }
+    let!(:admin)         { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
     let(:status)         { Fabricate(:status) }
     let(:target_account) { status.account }
     let(:category)       { 'other' }
diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb
index 266f1eda5a..03fe3928c6 100644
--- a/spec/requests/api/v1/statuses_spec.rb
+++ b/spec/requests/api/v1/statuses_spec.rb
@@ -346,30 +346,13 @@ RSpec.describe '/api/v1/statuses' do
 
       it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
 
-      it 'discards the status and schedules removal as a redraft', :aggregate_failures do
+      it 'removes the status', :aggregate_failures do
         subject
 
         expect(response).to have_http_status(200)
         expect(response.content_type)
           .to start_with('application/json')
         expect(Status.find_by(id: status.id)).to be_nil
-        expect(RemovalWorker).to have_enqueued_sidekiq_job(status.id, { 'redraft' => true })
-      end
-
-      context 'when called with truthy delete_media' do
-        subject do
-          delete "/api/v1/statuses/#{status.id}?delete_media=true", headers: headers
-        end
-
-        it 'discards the status and schedules removal without the redraft flag', :aggregate_failures do
-          subject
-
-          expect(response).to have_http_status(200)
-          expect(response.content_type)
-            .to start_with('application/json')
-          expect(Status.find_by(id: status.id)).to be_nil
-          expect(RemovalWorker).to have_enqueued_sidekiq_job(status.id, { 'redraft' => false })
-        end
       end
     end
 
diff --git a/spec/requests/api/v1/trends/statuses_spec.rb b/spec/requests/api/v1/trends/statuses_spec.rb
index 414d7651b9..fe00c9c645 100644
--- a/spec/requests/api/v1/trends/statuses_spec.rb
+++ b/spec/requests/api/v1/trends/statuses_spec.rb
@@ -39,42 +39,6 @@ RSpec.describe 'API V1 Trends Statuses' do
         end
         Trends::Statuses.new(threshold: 1, decay_threshold: -1).refresh
       end
-
-      context 'with a comically inflated external interactions count' do
-        def prepare_fake_trends
-          fake_remote_account = Fabricate(:account, domain: 'other.com')
-          fake_status = Fabricate(:status, account: fake_remote_account, text: 'I am a big faker', trendable: true, language: 'en')
-          fake_status.status_stat.tap do |status_stat|
-            status_stat.reblogs_count = 0
-            status_stat.favourites_count = 0
-            status_stat.untrusted_reblogs_count = 1_000_000_000
-            status_stat.untrusted_favourites_count = 1_000_000_000
-            status_stat.save
-          end
-          real_remote_account = Fabricate(:account, domain: 'other.com')
-          real_status = Fabricate(:status, account: real_remote_account, text: 'I make real friends online', trendable: true, language: 'en')
-          real_status.status_stat.tap do |status_stat|
-            status_stat.reblogs_count = 10
-            status_stat.favourites_count = 10
-            status_stat.untrusted_reblogs_count = 10
-            status_stat.untrusted_favourites_count = 10
-            status_stat.save
-          end
-          Trends.statuses.add(fake_status, 100)
-          Trends.statuses.add(real_status, 101)
-          Trends::Statuses.new(threshold: 1, decay_threshold: 1).refresh
-        end
-
-        it 'ignores the feeble attempts at deception' do
-          prepare_fake_trends
-          stub_const('Api::BaseController::DEFAULT_STATUSES_LIMIT', 10)
-          get '/api/v1/trends/statuses'
-
-          expect(response).to have_http_status(200)
-          expect(response.parsed_body.length).to eq(1)
-          expect(response.parsed_body[0]['content']).to eq('I make real friends online')
-        end
-      end
     end
   end
 end
diff --git a/spec/requests/api/v1/trends/tags_spec.rb b/spec/requests/api/v1/trends/tags_spec.rb
index 097393e58d..14ab73fc96 100644
--- a/spec/requests/api/v1/trends/tags_spec.rb
+++ b/spec/requests/api/v1/trends/tags_spec.rb
@@ -15,8 +15,6 @@ RSpec.describe 'API V1 Trends Tags' do
           .and not_have_http_link_header
         expect(response.content_type)
           .to start_with('application/json')
-        expect(response.headers['Deprecation'])
-          .to be_nil
       end
     end
 
@@ -33,8 +31,6 @@ RSpec.describe 'API V1 Trends Tags' do
           .and have_http_link_header(api_v1_trends_tags_url(offset: 2)).for(rel: 'next')
         expect(response.content_type)
           .to start_with('application/json')
-        expect(response.headers['Deprecation'])
-          .to be_nil
       end
 
       def prepare_trends
diff --git a/spec/requests/api/v1/trends_spec.rb b/spec/requests/api/v1/trends_spec.rb
deleted file mode 100644
index 5bfabdca1c..0000000000
--- a/spec/requests/api/v1/trends_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'deprecated API V1 Trends Tags' do
-  describe 'GET /api/v1/trends' do
-    context 'when trends are disabled' do
-      before { Setting.trends = false }
-
-      it 'returns http success' do
-        get '/api/v1/trends'
-
-        expect(response)
-          .to have_http_status(200)
-          .and not_have_http_link_header
-        expect(response.content_type)
-          .to start_with('application/json')
-        expect(response.headers['Deprecation'])
-          .to start_with '@'
-      end
-    end
-
-    context 'when trends are enabled' do
-      before { Setting.trends = true }
-
-      it 'returns http success' do
-        prepare_trends
-        stub_const('Api::V1::Trends::TagsController::DEFAULT_TAGS_LIMIT', 2)
-        get '/api/v1/trends'
-
-        expect(response)
-          .to have_http_status(200)
-          .and have_http_link_header(api_v1_trends_tags_url(offset: 2)).for(rel: 'next')
-        expect(response.content_type)
-          .to start_with('application/json')
-        expect(response.headers['Deprecation'])
-          .to start_with '@'
-      end
-
-      def prepare_trends
-        Fabricate.times(3, :tag, trendable: true).each do |tag|
-          2.times { |i| Trends.tags.add(tag, i) }
-        end
-        Trends::Tags.new(threshold: 1).refresh
-      end
-    end
-  end
-end
diff --git a/spec/requests/api/v2/filters_spec.rb b/spec/requests/api/v2/filters_spec.rb
index ddf5743346..cb378d662a 100644
--- a/spec/requests/api/v2/filters_spec.rb
+++ b/spec/requests/api/v2/filters_spec.rb
@@ -141,21 +141,6 @@ RSpec.describe 'Filters' do
           .to start_with('application/json')
       end
     end
-
-    context 'when the given filter_action value is invalid' do
-      let(:params) { { title: 'magic', filter_action: 'imaginary_value', keywords_attributes: [keyword: 'magic'] } }
-
-      it 'returns http unprocessable entity' do
-        subject
-
-        expect(response)
-          .to have_http_status(422)
-        expect(response.content_type)
-          .to start_with('application/json')
-        expect(response.parsed_body)
-          .to include(error: /Action is not included/)
-      end
-    end
   end
 
   describe 'GET /api/v2/filters/:id' do
diff --git a/spec/requests/api/v2/instance_spec.rb b/spec/requests/api/v2/instance_spec.rb
index 788d30fa69..f6a670cc24 100644
--- a/spec/requests/api/v2/instance_spec.rb
+++ b/spec/requests/api/v2/instance_spec.rb
@@ -55,9 +55,6 @@ RSpec.describe 'Instances' do
             max_characters: StatusLengthValidator::MAX_CHARS,
             max_media_attachments: Status::MEDIA_ATTACHMENTS_LIMIT
           ),
-          media_attachments: include(
-            description_limit: MediaAttachment::MAX_DESCRIPTION_LENGTH
-          ),
           polls: include(
             max_options: PollOptionsValidator::MAX_OPTIONS
           )
diff --git a/spec/requests/api/v2/media_spec.rb b/spec/requests/api/v2/media_spec.rb
index 18ebb9cdda..70e0679f57 100644
--- a/spec/requests/api/v2/media_spec.rb
+++ b/spec/requests/api/v2/media_spec.rb
@@ -29,22 +29,6 @@ RSpec.describe 'Media API', :attachment_processing do
       end
     end
 
-    context 'when media description is too long' do
-      let(:params) do
-        {
-          file: fixture_file_upload('attachment-jpg.123456_abcd', 'image/jpeg'),
-          description: 'a' * MediaAttachment::MAX_DESCRIPTION_LENGTH * 2,
-        }
-      end
-
-      it 'returns http error' do
-        post '/api/v2/media', headers: headers, params: params
-
-        expect(response).to have_http_status(422)
-        expect(response.body).to include 'Description is too long'
-      end
-    end
-
     context 'when large format media attachment has not been processed' do
       let(:params) { { file: fixture_file_upload('attachment.webm', 'video/webm') } }
 
diff --git a/spec/requests/api/v2/notifications_spec.rb b/spec/requests/api/v2/notifications_spec.rb
index 69feb6cb6e..a7608e1419 100644
--- a/spec/requests/api/v2/notifications_spec.rb
+++ b/spec/requests/api/v2/notifications_spec.rb
@@ -158,18 +158,19 @@ RSpec.describe 'Notifications' do
           expect(response).to have_http_status(200)
           expect(response.content_type)
             .to start_with('application/json')
-          expect(response.parsed_body[:notification_groups].size)
-            .to eq(1)
-          expect(response.parsed_body.dig(:notification_groups, 0))
-            .to include(type: 'favourite')
-            .and(include(sample_account_ids: have_attributes(size: 5)))
-            .and(include(page_max_id: notification_ids.last.to_s))
-            .and(include(page_min_id: notification_ids.first.to_s))
+          expect(response.parsed_body[:notification_groups]).to contain_exactly(
+            a_hash_including(
+              type: 'favourite',
+              sample_account_ids: have_attributes(size: 5),
+              page_min_id: notification_ids.first.to_s,
+              page_max_id: notification_ids.last.to_s
+            )
+          )
         end
       end
 
       context 'with min_id param' do
-        let(:params) { { min_id: user.account.notifications.order(id: :asc).first.id - 1 } }
+        let(:params) { { min_id: user.account.notifications.reload.first.id - 1 } }
 
         it 'returns a notification group covering all notifications' do
           subject
@@ -179,13 +180,14 @@ RSpec.describe 'Notifications' do
           expect(response).to have_http_status(200)
           expect(response.content_type)
             .to start_with('application/json')
-          expect(response.parsed_body[:notification_groups].size)
-            .to eq(1)
-          expect(response.parsed_body.dig(:notification_groups, 0))
-            .to include(type: 'favourite')
-            .and(include(sample_account_ids: have_attributes(size: 5)))
-            .and(include(page_max_id: notification_ids.last.to_s))
-            .and(include(page_min_id: notification_ids.first.to_s))
+          expect(response.parsed_body[:notification_groups]).to contain_exactly(
+            a_hash_including(
+              type: 'favourite',
+              sample_account_ids: have_attributes(size: 5),
+              page_min_id: notification_ids.first.to_s,
+              page_max_id: notification_ids.last.to_s
+            )
+          )
         end
       end
     end
diff --git a/spec/requests/api/web/push_subscriptions_spec.rb b/spec/requests/api/web/push_subscriptions_spec.rb
deleted file mode 100644
index 42545b3d6e..0000000000
--- a/spec/requests/api/web/push_subscriptions_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'API Web Push Subscriptions' do
-  describe 'DELETE /api/web/push_subscriptions/:id' do
-    subject { delete api_web_push_subscription_path(token) }
-
-    context 'when the subscription exists' do
-      let!(:web_push_subscription) do
-        Fabricate(:web_push_subscription)
-      end
-      let(:token) do
-        web_push_subscription.generate_token_for(:unsubscribe)
-      end
-
-      it 'deletes the subscription' do
-        expect { subject }
-          .to change(Web::PushSubscription, :count).by(-1)
-
-        expect(response).to have_http_status(200)
-      end
-    end
-
-    context 'when the subscription does not exist' do
-      let(:web_push_subscription) do
-        Fabricate(:web_push_subscription)
-      end
-      let(:token) do
-        web_push_subscription.generate_token_for(:unsubscribe)
-      end
-
-      before do
-        token # memoize before destroying the record
-        web_push_subscription.destroy!
-      end
-
-      it 'does nothing' do
-        subject
-
-        expect(response).to have_http_status(200)
-      end
-    end
-
-    context 'when the token is invalid' do
-      let(:token) { 'invalid--invalid' }
-
-      it 'does nothing' do
-        subject
-
-        expect(response).to have_http_status(200)
-      end
-    end
-  end
-
-  describe 'POST /api/web/push_subscriptions' do
-    before { sign_in Fabricate :user }
-
-    it 'gracefully handles invalid nested params' do
-      post api_web_push_subscriptions_path, params: { subscription: 'invalid' }
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-
-  describe 'PUT /api/web/push_subscriptions' do
-    before { sign_in Fabricate :user }
-
-    let(:subscription) { Fabricate :web_push_subscription }
-
-    it 'gracefully handles invalid nested params' do
-      put api_web_push_subscription_path(subscription), params: { data: 'invalid' }
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/auth/challenges_spec.rb b/spec/requests/auth/challenges_spec.rb
deleted file mode 100644
index 8769216657..0000000000
--- a/spec/requests/auth/challenges_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Auth Challenges' do
-  let(:password) { 'foobar12345' }
-  let(:user) { Fabricate(:user, password: password) }
-
-  before { sign_in user }
-
-  describe 'POST #create' do
-    let(:return_to) { edit_user_registration_path }
-
-    context 'with correct password' do
-      it 'redirects back and sets challenge passed at in session' do
-        post '/auth/challenge', params: { form_challenge: { return_to: return_to, current_password: password } }
-
-        expect(response)
-          .to redirect_to(return_to)
-        expect(session[:challenge_passed_at])
-          .to_not be_nil
-      end
-    end
-
-    context 'with incorrect password' do
-      it 'renders challenge, displays error, does not set session' do
-        post '/auth/challenge', params: { form_challenge: { return_to: return_to, current_password: 'hhfggjjd562' } }
-
-        expect(response.body)
-          .to include(I18n.t('challenge.prompt'))
-          .and include('Invalid password')
-        expect(session[:challenge_passed_at])
-          .to be_nil
-      end
-    end
-
-    context 'with invalid params' do
-      it 'gracefully handles invalid nested params' do
-        post auth_challenge_path(form_challenge: 'invalid')
-
-        expect(response)
-          .to have_http_status(400)
-      end
-    end
-  end
-end
diff --git a/spec/requests/auth/passwords_spec.rb b/spec/requests/auth/passwords_spec.rb
deleted file mode 100644
index feefd94587..0000000000
--- a/spec/requests/auth/passwords_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Auth Passwords' do
-  describe 'GET /auth/password/edit' do
-    context 'with invalid reset_password_token' do
-      it 'redirects to #new' do
-        get edit_user_password_path, params: { reset_password_token: 'some_invalid_value' }
-
-        expect(response)
-          .to redirect_to new_user_password_path
-      end
-    end
-  end
-
-  describe 'PUT /auth/password' do
-    let(:user) { Fabricate(:user) }
-    let(:password) { 'reset0password' }
-
-    context 'with invalid reset_password_token' do
-      it 'renders reset password and retains password' do
-        put user_password_path, params: { user: { password: password, password_confirmation: password, reset_password_token: 'some_invalid_value' } }
-
-        expect(response.body)
-          .to include(I18n.t('auth.set_new_password'))
-
-        expect(User.find(user.id))
-          .to be_present
-          .and be_external_or_valid_password(user.password)
-      end
-    end
-  end
-end
diff --git a/spec/requests/auth/sessions_spec.rb b/spec/requests/auth/sessions_spec.rb
deleted file mode 100644
index 246c3794cc..0000000000
--- a/spec/requests/auth/sessions_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Auth Sessions' do
-  describe 'POST /auth/sign_in' do
-    # The rack-attack check has issues with the non-nested invalid param used here
-    before { Rack::Attack.enabled = false }
-    after { Rack::Attack.enabled = true }
-
-    it 'gracefully handles invalid nested params' do
-      post user_session_path(user: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/auth/setup_spec.rb b/spec/requests/auth/setup_spec.rb
deleted file mode 100644
index fa3c196805..0000000000
--- a/spec/requests/auth/setup_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Auth Setup' do
-  describe 'GET /auth/setup' do
-    context 'with a signed out request' do
-      it 'redirects to root' do
-        get '/auth/setup'
-
-        expect(response)
-          .to redirect_to(new_user_session_url)
-      end
-    end
-
-    context 'with a confirmed signed in user' do
-      before { sign_in Fabricate(:user, confirmed_at: 2.days.ago) }
-
-      it 'redirects to root' do
-        get '/auth/setup'
-
-        expect(response)
-          .to redirect_to(root_url)
-      end
-    end
-  end
-end
diff --git a/spec/requests/cache_spec.rb b/spec/requests/cache_spec.rb
index 8ca2817263..9cce241b38 100644
--- a/spec/requests/cache_spec.rb
+++ b/spec/requests/cache_spec.rb
@@ -10,7 +10,6 @@ module TestEndpoints
     /.well-known/nodeinfo
     /nodeinfo/2.0
     /manifest
-    /css/custom-1a2s3d4f.css
     /custom.css
     /actor
     /api/v1/instance/extended_description
@@ -173,7 +172,7 @@ RSpec.describe 'Caching behavior' do
 
   before_all do
     alice = Fabricate(:account, username: 'alice')
-    user = Fabricate(:moderator_user, email: 'user@host.example')
+    user = Fabricate(:user, email: 'user@host.example', role: UserRole.find_by(name: 'Moderator'))
     status = Fabricate(:status, account: alice, id: 110_224_538_612_341_312)
     Fabricate(:status, account: alice, id: 110_224_538_643_211_312, visibility: :private)
     Fabricate(:invite, code: 'abcdef')
diff --git a/spec/requests/custom_css_spec.rb b/spec/requests/custom_css_spec.rb
index 66ff5c4b13..a46ebd7281 100644
--- a/spec/requests/custom_css_spec.rb
+++ b/spec/requests/custom_css_spec.rb
@@ -5,19 +5,13 @@ require 'rails_helper'
 RSpec.describe 'Custom CSS' do
   include RoutingHelper
 
-  describe 'GET /css/:id.css' do
+  describe 'GET /custom.css' do
     context 'without any CSS or User Roles' do
       it 'returns empty stylesheet' do
-        get '/css/custom-123.css'
+        get '/custom.css'
 
-        expect(response)
-          .to have_http_status(200)
-          .and have_cacheable_headers
-          .and have_attributes(
-            content_type: match('text/css')
-          )
-        expect(response.body.presence)
-          .to be_nil
+        expect(response.content_type).to include('text/css')
+        expect(response.body.presence).to be_nil
       end
     end
 
@@ -27,16 +21,10 @@ RSpec.describe 'Custom CSS' do
       end
 
       it 'returns stylesheet from settings' do
-        get '/css/custom-456.css'
+        get '/custom.css'
 
-        expect(response)
-          .to have_http_status(200)
-          .and have_cacheable_headers
-          .and have_attributes(
-            content_type: match('text/css')
-          )
-        expect(response.body.strip)
-          .to eq(expected_css)
+        expect(response.content_type).to include('text/css')
+        expect(response.body.strip).to eq(expected_css)
       end
 
       def expected_css
@@ -45,5 +33,28 @@ RSpec.describe 'Custom CSS' do
         CSS
       end
     end
+
+    context 'with highlighted colored UserRole records' do
+      before do
+        _highlighted_colored = Fabricate :user_role, highlighted: true, color: '#336699', id: '123_123_123'
+        _highlighted_no_color = Fabricate :user_role, highlighted: true, color: ''
+        _no_highlight_with_color = Fabricate :user_role, highlighted: false, color: ''
+      end
+
+      it 'returns stylesheet from settings' do
+        get '/custom.css'
+
+        expect(response.content_type).to include('text/css')
+        expect(response.body.strip).to eq(expected_css)
+      end
+
+      def expected_css
+        <<~CSS.strip
+          .user-role-123123123 {
+            --user-role-accent: #336699;
+          }
+        CSS
+      end
+    end
   end
 end
diff --git a/spec/requests/custom_stylesheets_spec.rb b/spec/requests/custom_stylesheets_spec.rb
new file mode 100644
index 0000000000..9c5c058344
--- /dev/null
+++ b/spec/requests/custom_stylesheets_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe 'Custom stylesheets' do
+  describe 'GET /custom.css' do
+    before { get '/custom.css' }
+
+    it 'returns http success' do
+      expect(response)
+        .to have_http_status(200)
+        .and have_cacheable_headers
+        .and have_attributes(
+          content_type: match('text/css')
+        )
+    end
+  end
+end
diff --git a/spec/requests/disputes/appeals_spec.rb b/spec/requests/disputes/appeals_spec.rb
deleted file mode 100644
index 4eff09c800..0000000000
--- a/spec/requests/disputes/appeals_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Disputes Appeals' do
-  describe 'POST /disputes/appeals' do
-    before { sign_in strike.target_account.user }
-
-    let(:strike) { Fabricate :account_warning }
-
-    it 'gracefully handles invalid nested params' do
-      post disputes_strike_appeal_path(strike, appeal: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/disputes/strikes_spec.rb b/spec/requests/disputes/strikes_spec.rb
deleted file mode 100644
index 48685893c2..0000000000
--- a/spec/requests/disputes/strikes_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Disputes Strikes' do
-  before { sign_in current_user }
-
-  describe 'GET /disputes/strikes/:id' do
-    let(:current_user) { Fabricate(:user) }
-
-    context 'when meant for a different user' do
-      let(:strike) { Fabricate(:account_warning) }
-
-      it 'returns http forbidden' do
-        get disputes_strike_path(strike)
-
-        expect(response)
-          .to have_http_status(403)
-      end
-    end
-  end
-end
diff --git a/spec/requests/filters/statuses_spec.rb b/spec/requests/filters/statuses_spec.rb
deleted file mode 100644
index b462b56223..0000000000
--- a/spec/requests/filters/statuses_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Filters Statuses' do
-  describe 'POST /filters/:filter_id/statuses/batch' do
-    before { sign_in(user) }
-
-    let(:filter) { Fabricate :custom_filter, account: user.account }
-    let(:user) { Fabricate :user }
-
-    it 'gracefully handles invalid nested params' do
-      post batch_filter_statuses_path(filter.id, form_status_filter_batch_action: 'invalid')
-
-      expect(response)
-        .to redirect_to(edit_filter_path(filter))
-    end
-  end
-
-  describe 'GET /filters/:filter_id/statuses' do
-    let(:filter) { Fabricate(:custom_filter) }
-
-    context 'with signed out user' do
-      it 'redirects' do
-        get filter_statuses_path(filter)
-
-        expect(response)
-          .to be_redirect
-      end
-    end
-
-    context 'with a signed in user' do
-      context 'with another user signed in' do
-        before { sign_in(Fabricate(:user)) }
-
-        it 'returns http not found' do
-          get filter_statuses_path(filter)
-
-          expect(response)
-            .to have_http_status(404)
-        end
-      end
-    end
-  end
-end
diff --git a/spec/requests/filters_spec.rb b/spec/requests/filters_spec.rb
deleted file mode 100644
index e9d7436b0b..0000000000
--- a/spec/requests/filters_spec.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Filters' do
-  describe 'GET /filters' do
-    context 'with signed out user' do
-      it 'redirects to sign in page' do
-        get filters_path
-
-        expect(response)
-          .to redirect_to(new_user_session_path)
-      end
-    end
-  end
-
-  describe 'POST /filters' do
-    before { sign_in Fabricate :user }
-
-    it 'gracefully handles invalid nested params' do
-      post filters_path(custom_filter: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-
-  describe 'PUT /filters/:id' do
-    before { sign_in(filter.account.user) }
-
-    let(:filter) { Fabricate :custom_filter }
-
-    it 'gracefully handles invalid nested params' do
-      put filter_path(filter, custom_filter: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/invites_spec.rb b/spec/requests/invites_spec.rb
index 2982ff0174..8a5ad2ccd1 100644
--- a/spec/requests/invites_spec.rb
+++ b/spec/requests/invites_spec.rb
@@ -28,15 +28,4 @@ RSpec.describe 'Invites' do
       end
     end
   end
-
-  describe 'POST /invites' do
-    before { UserRole.everyone.update(permissions: UserRole.everyone.permissions | UserRole::FLAGS[:invite_users]) }
-
-    it 'gracefully handles invalid nested params' do
-      post invites_path(invite: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
 end
diff --git a/spec/requests/oauth/token_spec.rb b/spec/requests/oauth/token_spec.rb
index 74f301c577..18d232e5ab 100644
--- a/spec/requests/oauth/token_spec.rb
+++ b/spec/requests/oauth/token_spec.rb
@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-RSpec.describe 'Managing OAuth Tokens' do
+RSpec.describe 'Obtaining OAuth Tokens' do
   describe 'POST /oauth/token' do
     subject do
       post '/oauth/token', params: params
@@ -104,23 +104,4 @@ RSpec.describe 'Managing OAuth Tokens' do
       end
     end
   end
-
-  describe 'POST /oauth/revoke' do
-    subject { post '/oauth/revoke', params: { client_id: application.uid, token: access_token.token } }
-
-    let!(:user) { Fabricate(:user) }
-    let!(:application) { Fabricate(:application, confidential: false) }
-    let!(:access_token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: application) }
-    let!(:web_push_subscription) { Fabricate(:web_push_subscription, user: user, access_token: access_token) }
-
-    it 'revokes the token and removes subscriptions' do
-      expect { subject }
-        .to change { access_token.reload.revoked_at }.from(nil).to(be_present)
-
-      expect(Web::PushSubscription.where(access_token: access_token).count)
-        .to eq(0)
-      expect { web_push_subscription.reload }
-        .to raise_error(ActiveRecord::RecordNotFound)
-    end
-  end
 end
diff --git a/spec/requests/oauth/userinfo_spec.rb b/spec/requests/oauth/userinfo_spec.rb
deleted file mode 100644
index 7d6226cd41..0000000000
--- a/spec/requests/oauth/userinfo_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Oauth Userinfo Endpoint' do
-  include RoutingHelper
-
-  let(:user)     { Fabricate(:user) }
-  let(:account)  { user.account }
-  let(:token)    { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
-  let(:scopes)   { 'profile' }
-  let(:headers)  { { 'Authorization' => "Bearer #{token.token}" } }
-
-  shared_examples 'returns successfully' do
-    it 'returns http success' do
-      subject
-
-      expect(response).to have_http_status(:success)
-      expect(response.content_type).to start_with('application/json')
-      expect(response.parsed_body).to include({
-        iss: root_url,
-        sub: account_url(account),
-        name: account.display_name,
-        preferred_username: account.username,
-        profile: short_account_url(account),
-        picture: full_asset_url(account.avatar_original_url),
-      })
-    end
-  end
-
-  describe 'GET /oauth/userinfo' do
-    subject do
-      get '/oauth/userinfo', headers: headers
-    end
-
-    it_behaves_like 'forbidden for wrong scope', 'read:accounts'
-    it_behaves_like 'returns successfully'
-  end
-
-  # As this is borrowed from OpenID, the specification says we must also support
-  # POST for the userinfo endpoint:
-  # https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
-  describe 'POST /oauth/userinfo' do
-    subject do
-      post '/oauth/userinfo', headers: headers
-    end
-
-    it_behaves_like 'forbidden for wrong scope', 'read:accounts'
-    it_behaves_like 'returns successfully'
-  end
-end
diff --git a/spec/requests/relationships_spec.rb b/spec/requests/relationships_spec.rb
deleted file mode 100644
index ee6b321c46..0000000000
--- a/spec/requests/relationships_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Relationships' do
-  describe 'PUT /relationships' do
-    before { sign_in Fabricate(:user) }
-
-    it 'gracefully handles invalid nested params' do
-      put relationships_path(form_account_batch: 'invalid')
-
-      expect(response)
-        .to redirect_to(relationships_path)
-    end
-  end
-end
diff --git a/spec/requests/remote_interaction_helper_spec.rb b/spec/requests/remote_interaction_helper_spec.rb
index b89060b5b2..942f70b9a4 100644
--- a/spec/requests/remote_interaction_helper_spec.rb
+++ b/spec/requests/remote_interaction_helper_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe 'Remote Interaction Helper' do
 
       expect(response)
         .to have_http_status(200)
+        .and render_template(:index, layout: 'helper_frame')
         .and have_attributes(
           headers: include(
             'X-Frame-Options' => 'SAMEORIGIN',
@@ -16,8 +17,6 @@ RSpec.describe 'Remote Interaction Helper' do
             'Content-Security-Policy' => expected_csp_headers
           )
         )
-      expect(response.body)
-        .to match(/remote_interaction_helper/)
     end
   end
 
diff --git a/spec/requests/settings/aliases_spec.rb b/spec/requests/settings/aliases_spec.rb
deleted file mode 100644
index 6d905aa4c6..0000000000
--- a/spec/requests/settings/aliases_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Aliases' do
-  describe 'POST /settings/aliases' do
-    before { sign_in Fabricate(:user) }
-
-    it 'gracefully handles invalid nested params' do
-      post settings_aliases_path(account_alias: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/settings/applications_spec.rb b/spec/requests/settings/applications_spec.rb
index d2d91f03b1..8a5b3de895 100644
--- a/spec/requests/settings/applications_spec.rb
+++ b/spec/requests/settings/applications_spec.rb
@@ -40,23 +40,5 @@ RSpec.describe 'Settings / Exports' do
       expect(response)
         .to redirect_to(settings_applications_path)
     end
-
-    it 'gracefully handles invalid nested params' do
-      post settings_applications_path(doorkeeper_application: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-
-  describe 'PUT /settings/applications/:id' do
-    let(:application) { Fabricate :application, owner: user }
-
-    it 'gracefully handles invalid nested params' do
-      put settings_application_path(application.id, doorkeeper_application: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
   end
 end
diff --git a/spec/requests/settings/deletes_spec.rb b/spec/requests/settings/deletes_spec.rb
deleted file mode 100644
index c277181999..0000000000
--- a/spec/requests/settings/deletes_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Deletes' do
-  describe 'DELETE /settings/delete' do
-    context 'when signed in' do
-      before { sign_in(user) }
-
-      let(:user) { Fabricate(:user) }
-
-      it 'gracefully handles invalid nested params' do
-        delete settings_delete_path(form_delete_confirmation: 'invalid')
-
-        expect(response)
-          .to have_http_status(400)
-      end
-
-      context 'when suspended' do
-        let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) }
-
-        it 'returns http forbidden' do
-          delete settings_delete_path
-
-          expect(response)
-            .to have_http_status(403)
-        end
-      end
-    end
-
-    context 'when not signed in' do
-      it 'redirects to sign in' do
-        delete settings_delete_path
-
-        expect(response)
-          .to redirect_to(new_user_session_path)
-      end
-    end
-  end
-
-  describe 'GET /settings/delete' do
-    context 'when signed in' do
-      before { sign_in(user) }
-
-      context 'when suspended' do
-        let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) }
-
-        it 'returns http forbidden with private cache control headers' do
-          get settings_delete_path
-
-          expect(response)
-            .to have_http_status(403)
-          expect(response.headers['Cache-Control'])
-            .to include('private, no-store')
-        end
-      end
-    end
-
-    context 'when not signed in' do
-      it 'redirects to sign in' do
-        get settings_delete_path
-
-        expect(response)
-          .to redirect_to(new_user_session_path)
-      end
-    end
-  end
-end
diff --git a/spec/requests/settings/featured_tags_spec.rb b/spec/requests/settings/featured_tags_spec.rb
deleted file mode 100644
index 26c4950acc..0000000000
--- a/spec/requests/settings/featured_tags_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Featured Tags' do
-  describe 'POST /settings/featured_tags' do
-    context 'when signed in' do
-      before { sign_in Fabricate(:user) }
-
-      it 'gracefully handles invalid nested params' do
-        post settings_featured_tags_path(featured_tag: 'invalid')
-
-        expect(response)
-          .to have_http_status(400)
-      end
-    end
-
-    context 'when not signed in' do
-      subject { post settings_featured_tags_path }
-
-      it { is_expected.to redirect_to new_user_session_path }
-    end
-  end
-end
diff --git a/spec/requests/settings/imports_spec.rb b/spec/requests/settings/imports_spec.rb
deleted file mode 100644
index e2051e015f..0000000000
--- a/spec/requests/settings/imports_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Imports' do
-  describe 'POST /settings/imports' do
-    before { sign_in Fabricate(:user) }
-
-    it 'gracefully handles invalid nested params' do
-      post settings_imports_path(form_import: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/settings/migration/redirects_spec.rb b/spec/requests/settings/migration/redirects_spec.rb
deleted file mode 100644
index f417fbb0b2..0000000000
--- a/spec/requests/settings/migration/redirects_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Migration Redirects' do
-  describe 'POST /settings/migration/redirect' do
-    before { sign_in Fabricate(:user) }
-
-    it 'gracefully handles invalid nested params' do
-      post settings_migration_redirect_path(form_redirect: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/settings/migrations_spec.rb b/spec/requests/settings/migrations_spec.rb
deleted file mode 100644
index 0aca7fde5b..0000000000
--- a/spec/requests/settings/migrations_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Migrations' do
-  describe 'GET #show' do
-    context 'when user is not signed in' do
-      subject { get '/settings/migration' }
-
-      it { is_expected.to redirect_to new_user_session_path }
-    end
-  end
-
-  describe 'POST #create' do
-    context 'when user is not signed in' do
-      subject { post '/settings/migration' }
-
-      it { is_expected.to redirect_to new_user_session_path }
-    end
-  end
-
-  context 'when user is signed in' do
-    before { sign_in Fabricate(:user) }
-
-    it 'gracefully handles invalid nested params' do
-      post settings_migration_path(account_migration: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/settings/pictures_spec.rb b/spec/requests/settings/pictures_spec.rb
deleted file mode 100644
index f297eb3649..0000000000
--- a/spec/requests/settings/pictures_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Pictures' do
-  let!(:user) { Fabricate(:user) }
-
-  before { sign_in user }
-
-  describe 'DELETE /settings/profile/pictures/:id' do
-    context 'with invalid picture id' do
-      it 'returns http bad request' do
-        delete settings_profile_picture_path(id: 'invalid')
-
-        expect(response)
-          .to have_http_status(400)
-      end
-    end
-
-    context 'with valid picture id' do
-      before { stub_service }
-
-      context 'when account updates correctly' do
-        let(:service) { instance_double(UpdateAccountService, call: true) }
-
-        it 'updates the account' do
-          delete settings_profile_picture_path(id: 'avatar')
-
-          expect(response)
-            .to redirect_to(settings_profile_path)
-            .and have_http_status(303)
-          expect(service)
-            .to have_received(:call).with(user.account, { 'avatar' => nil, 'avatar_remote_url' => '' })
-        end
-      end
-
-      context 'when account cannot update' do
-        let(:service) { instance_double(UpdateAccountService, call: false) }
-
-        it 'redirects to profile' do
-          delete settings_profile_picture_path(id: 'avatar')
-
-          expect(response)
-            .to redirect_to(settings_profile_path)
-        end
-      end
-
-      def stub_service
-        allow(UpdateAccountService)
-          .to receive(:new)
-          .and_return(service)
-      end
-    end
-  end
-end
diff --git a/spec/requests/settings/preferences/appearance_spec.rb b/spec/requests/settings/preferences/appearance_spec.rb
deleted file mode 100644
index cfdc4dafc9..0000000000
--- a/spec/requests/settings/preferences/appearance_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Preferences Appearance' do
-  describe 'PUT /settings/preferences/appearance' do
-    before { sign_in Fabricate(:user) }
-
-    it 'gracefully handles invalid nested params' do
-      put settings_preferences_appearance_path(user: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/settings/privacy_spec.rb b/spec/requests/settings/privacy_spec.rb
deleted file mode 100644
index d02b534b74..0000000000
--- a/spec/requests/settings/privacy_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Privacy' do
-  describe 'PUT /settings/privacy' do
-    before { sign_in Fabricate(:user) }
-
-    it 'gracefully handles invalid nested params' do
-      put settings_privacy_path(account: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/settings/profiles_spec.rb b/spec/requests/settings/profiles_spec.rb
deleted file mode 100644
index a19f321f14..0000000000
--- a/spec/requests/settings/profiles_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Profiles' do
-  describe 'PUT /settings/profile' do
-    before { sign_in Fabricate(:user) }
-
-    it 'gracefully handles invalid nested params' do
-      put settings_profile_path(account: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/settings/sessions_spec.rb b/spec/requests/settings/sessions_spec.rb
deleted file mode 100644
index 6a23eac4b4..0000000000
--- a/spec/requests/settings/sessions_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Sessions' do
-  let(:user) { Fabricate(:user) }
-
-  before { sign_in(user) }
-
-  describe 'DELETE /settings/sessions/:id' do
-    context 'when session activation does not exist' do
-      it 'returns not found' do
-        delete settings_session_path(123_456_789)
-
-        expect(response)
-          .to have_http_status(404)
-      end
-    end
-  end
-end
diff --git a/spec/requests/settings/two_factor_authentication/confirmations_spec.rb b/spec/requests/settings/two_factor_authentication/confirmations_spec.rb
deleted file mode 100644
index 532fbe61ea..0000000000
--- a/spec/requests/settings/two_factor_authentication/confirmations_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings 2FA Confirmations' do
-  describe 'POST /settings/two_factor_authentication/confirmations' do
-    before do
-      sign_in Fabricate(:user, encrypted_password: '') # Empty encrypted password avoids challengable flow
-      post settings_otp_authentication_path # Sets `session[:new_otp_secret]` which is needed for next step
-    end
-
-    it 'gracefully handles invalid nested params' do
-      post settings_two_factor_authentication_confirmation_path(form_two_factor_confirmation: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-
-  context 'when not signed in' do
-    it 'redirects on POST to create' do
-      post settings_two_factor_authentication_confirmation_path(form_two_factor_confirmation: { otp_attempt: '123456' })
-
-      expect(response)
-        .to redirect_to(new_user_session_path)
-    end
-
-    it 'redirects on GET to new' do
-      get new_settings_two_factor_authentication_confirmation_path
-
-      expect(response)
-        .to redirect_to(new_user_session_path)
-    end
-  end
-end
diff --git a/spec/requests/settings/two_factor_authentication/recovery_codes_spec.rb b/spec/requests/settings/two_factor_authentication/recovery_codes_spec.rb
deleted file mode 100644
index 30cbfc2a7b..0000000000
--- a/spec/requests/settings/two_factor_authentication/recovery_codes_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings TwoFactorAuthentication RecoveryCodes' do
-  describe 'POST /settings/two_factor_authentication/recovery_codes' do
-    context 'when signed out' do
-      it 'redirects to sign in page' do
-        post settings_two_factor_authentication_recovery_codes_path
-
-        expect(response)
-          .to redirect_to(new_user_session_path)
-      end
-    end
-  end
-end
diff --git a/spec/requests/settings/two_factor_authentication_methods_spec.rb b/spec/requests/settings/two_factor_authentication_methods_spec.rb
deleted file mode 100644
index 2fda5ce919..0000000000
--- a/spec/requests/settings/two_factor_authentication_methods_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings TwoFactorAuthenticationMethods' do
-  context 'when not signed in' do
-    describe 'GET to /settings/two_factor_authentication_methods' do
-      it 'redirects to sign in page' do
-        get settings_two_factor_authentication_methods_path
-
-        expect(response)
-          .to redirect_to(new_user_session_path)
-      end
-    end
-  end
-
-  context 'when signed in' do
-    let(:user) { Fabricate(:user) }
-
-    before { sign_in user }
-
-    describe 'GET to /settings/two_factor_authentication_methods' do
-      describe 'when user has not enabled otp' do
-        before { user.update(otp_required_for_login: false) }
-
-        it 'redirects to enable otp' do
-          get settings_two_factor_authentication_methods_path
-
-          expect(response)
-            .to redirect_to(settings_otp_authentication_path)
-        end
-      end
-    end
-  end
-end
diff --git a/spec/requests/settings/verifications_spec.rb b/spec/requests/settings/verifications_spec.rb
deleted file mode 100644
index 5acfe64836..0000000000
--- a/spec/requests/settings/verifications_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Verifications' do
-  describe 'PUT /settings/verification' do
-    before { sign_in Fabricate(:user) }
-
-    it 'gracefully handles invalid nested params' do
-      put settings_verification_path(account: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/status_show_page_spec.rb b/spec/requests/status_show_page_spec.rb
deleted file mode 100644
index 758f9dcfd8..0000000000
--- a/spec/requests/status_show_page_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Statuses' do
-  describe 'GET /@:account_username/:id' do
-    include AccountsHelper
-
-    def site_hostname
-      Rails.configuration.x.web_domain || Rails.configuration.x.local_domain
-    end
-
-    it 'has valid opengraph tags' do
-      account = Fabricate(:account, username: 'alice', display_name: 'Alice')
-      status = Fabricate(:status, account: account, text: 'Hello World')
-
-      get "/@#{account.username}/#{status.id}"
-
-      expect(head_link_icons.size).to eq(3) # Three favicons with sizes
-
-      expect(head_meta_content('og:title')).to match "#{display_name(account)} (#{acct(account)})"
-      expect(head_meta_content('og:type')).to eq 'article'
-      expect(head_meta_content('og:published_time')).to eq status.created_at.iso8601
-      expect(head_meta_content('og:url')).to eq short_account_status_url(account_username: account.username, id: status.id)
-      expect(head_meta_exists('og:locale')).to be false
-    end
-
-    it 'has og:locale opengraph tag if the status has is written in a given language' do
-      status_text = "Una prova d'estatus català"
-      account = Fabricate(:account, username: 'alice', display_name: 'Alice')
-      status = Fabricate(:status, account: account, text: status_text, language: 'ca')
-
-      get "/@#{account.username}/#{status.id}"
-
-      expect(head_meta_content('og:title')).to match "#{display_name(account)} (#{acct(account)})"
-      expect(head_meta_content('og:type')).to eq 'article'
-      expect(head_meta_content('og:published_time')).to eq status.created_at.iso8601
-      expect(head_meta_content('og:url')).to eq short_account_status_url(account_username: account.username, id: status.id)
-
-      expect(head_meta_exists('og:locale')).to be true
-      expect(head_meta_content('og:locale')).to eq 'ca'
-      expect(head_meta_content('og:description')).to eq status_text
-    end
-
-    def head_link_icons
-      response
-        .parsed_body
-        .search('html head link[rel=icon]')
-    end
-
-    def head_meta_content(property)
-      response
-        .parsed_body
-        .search("html head meta[property='#{property}']")
-        .attr('content')
-        .text
-    end
-
-    def head_meta_exists(property)
-      !response
-        .parsed_body
-        .search("html head meta[property='#{property}']")
-        .empty?
-    end
-  end
-end
diff --git a/spec/requests/statuses/activity_spec.rb b/spec/requests/statuses/activity_spec.rb
deleted file mode 100644
index 20837a63a6..0000000000
--- a/spec/requests/statuses/activity_spec.rb
+++ /dev/null
@@ -1,218 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Status Activity' do
-  describe 'GET /users/:account_username/statuses/:id/activity' do
-    let(:account) { Fabricate(:account) }
-    let(:status)  { Fabricate(:status, account: account) }
-
-    context 'when signed out' do
-      subject { get activity_account_status_path(account.username, status) }
-
-      context 'when account is permanently suspended' do
-        before do
-          account.suspend!
-          account.deletion_request.destroy
-        end
-
-        it 'returns http gone' do
-          subject
-
-          expect(response)
-            .to have_http_status(410)
-        end
-      end
-
-      context 'when account is temporarily suspended' do
-        before { account.suspend! }
-
-        it 'returns http forbidden' do
-          subject
-
-          expect(response)
-            .to have_http_status(403)
-        end
-      end
-
-      context 'when status is public' do
-        before { status.update(visibility: :public) }
-
-        it 'returns http success' do
-          subject
-
-          expect(response)
-            .to have_http_status(:success)
-          expect(response.content_type)
-            .to start_with('application/activity+json')
-        end
-      end
-
-      context 'when status is private' do
-        before { status.update(visibility: :private) }
-
-        it 'returns http not_found' do
-          subject
-
-          expect(response)
-            .to have_http_status(404)
-        end
-      end
-
-      context 'when status is direct' do
-        before { status.update(visibility: :direct) }
-
-        it 'returns http not_found' do
-          subject
-
-          expect(response)
-            .to have_http_status(404)
-        end
-      end
-    end
-
-    context 'when signed in' do
-      subject { get activity_account_status_path(account.username, status) }
-
-      let(:user) { Fabricate(:user) }
-
-      before { sign_in(user) }
-
-      context 'when status is public' do
-        before { status.update(visibility: :public) }
-
-        it 'returns http success' do
-          subject
-
-          expect(response)
-            .to have_http_status(:success)
-          expect(response.content_type)
-            .to start_with('application/activity+json')
-        end
-      end
-
-      context 'when status is private' do
-        before { status.update(visibility: :private) }
-
-        context 'when user is authorized to see it' do
-          before { user.account.follow!(account) }
-
-          it 'returns http success' do
-            subject
-
-            expect(response)
-              .to have_http_status(200)
-            expect(response.content_type)
-              .to start_with('application/activity+json')
-          end
-        end
-
-        context 'when user is not authorized to see it' do
-          it 'returns http not_found' do
-            subject
-
-            expect(response)
-              .to have_http_status(404)
-          end
-        end
-      end
-
-      context 'when status is direct' do
-        before { status.update(visibility: :direct) }
-
-        context 'when user is authorized to see it' do
-          before { Fabricate(:mention, account: user.account, status: status) }
-
-          it 'returns http success' do
-            subject
-
-            expect(response)
-              .to have_http_status(200)
-            expect(response.content_type)
-              .to start_with('application/activity+json')
-          end
-        end
-
-        context 'when user is not authorized to see it' do
-          it 'returns http not_found' do
-            subject
-
-            expect(response)
-              .to have_http_status(404)
-          end
-        end
-      end
-    end
-
-    context 'with signature' do
-      subject { get activity_account_status_path(account.username, status), headers: nil, sign_with: remote_account }
-
-      let(:remote_account) { Fabricate(:account, domain: 'example.com') }
-
-      context 'when status is public' do
-        before { status.update(visibility: :public) }
-
-        it 'returns http success' do
-          subject
-
-          expect(response)
-            .to have_http_status(:success)
-          expect(response.content_type)
-            .to start_with('application/activity+json')
-        end
-      end
-
-      context 'when status is private' do
-        before { status.update(visibility: :private) }
-
-        context 'when user is authorized to see it' do
-          before { remote_account.follow!(account) }
-
-          it 'returns http success' do
-            subject
-
-            expect(response)
-              .to have_http_status(200)
-            expect(response.content_type)
-              .to start_with('application/activity+json')
-          end
-        end
-
-        context 'when user is not authorized to see it' do
-          it 'returns http not_found' do
-            subject
-
-            expect(response)
-              .to have_http_status(404)
-          end
-        end
-      end
-
-      context 'when status is direct' do
-        before { status.update(visibility: :direct) }
-
-        context 'when user is authorized to see it' do
-          before { Fabricate(:mention, account: remote_account, status: status) }
-
-          it 'returns http success' do
-            subject
-
-            expect(response)
-              .to have_http_status(200)
-            expect(response.content_type)
-              .to start_with('application/activity+json')
-          end
-        end
-
-        context 'when user is not authorized to see it' do
-          it 'returns http not_found' do
-            subject
-
-            expect(response)
-              .to have_http_status(404)
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/requests/statuses_cleanup_spec.rb b/spec/requests/statuses_cleanup_spec.rb
deleted file mode 100644
index 17a1c190ad..0000000000
--- a/spec/requests/statuses_cleanup_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Statuses Cleanup' do
-  describe 'PUT /statuses_cleanup' do
-    before { sign_in Fabricate(:user) }
-
-    it 'gracefully handles invalid nested params' do
-      put statuses_cleanup_path(account_statuses_cleanup_policy: 'invalid')
-
-      expect(response)
-        .to have_http_status(400)
-    end
-  end
-end
diff --git a/spec/requests/statuses_spec.rb b/spec/requests/statuses_spec.rb
deleted file mode 100644
index a5e4482dfa..0000000000
--- a/spec/requests/statuses_spec.rb
+++ /dev/null
@@ -1,475 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Statuses' do
-  describe 'GET /@:account_username/:id' do
-    let(:account) { Fabricate(:account) }
-    let(:status)  { Fabricate(:status, account: account) }
-
-    context 'when signed out' do
-      context 'when account is permanently suspended' do
-        before do
-          account.suspend!
-          account.deletion_request.destroy
-        end
-
-        it 'returns http gone' do
-          get "/@#{account.username}/#{status.id}"
-
-          expect(response)
-            .to have_http_status(410)
-        end
-      end
-
-      context 'when account is temporarily suspended' do
-        before { account.suspend! }
-
-        it 'returns http forbidden' do
-          get "/@#{account.username}/#{status.id}"
-
-          expect(response)
-            .to have_http_status(403)
-        end
-      end
-
-      context 'when status is a reblog' do
-        let(:original_account) { Fabricate(:account, domain: 'example.com') }
-        let(:original_status) { Fabricate(:status, account: original_account, url: 'https://example.com/123') }
-        let(:status) { Fabricate(:status, account: account, reblog: original_status) }
-
-        it 'redirects to the original status' do
-          get "/@#{status.account.username}/#{status.id}"
-
-          expect(response)
-            .to redirect_to(original_status.url)
-        end
-      end
-
-      context 'when status visibility is public' do
-        subject { get short_account_status_path(account_username: account.username, id: status.id, format: format) }
-
-        let(:status) { Fabricate(:status, account: account, visibility: :public) }
-
-        context 'with HTML' do
-          let(:format) { 'html' }
-
-          it 'renders status successfully', :aggregate_failures do
-            subject
-
-            expect(response)
-              .to have_http_status(200)
-            expect(response.headers).to include(
-              'Vary' => 'Accept, Accept-Language, Cookie',
-              'Cache-Control' => include('public'),
-              'Link' => include('activity+json')
-            )
-            expect(response.body)
-              .to include(status.text)
-          end
-        end
-
-        context 'with JSON' do
-          let(:format) { 'json' }
-
-          it 'renders ActivityPub Note object successfully', :aggregate_failures do
-            subject
-
-            expect(response)
-              .to have_http_status(200)
-              .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie')
-
-            expect(response.headers).to include(
-              'Content-Type' => include('application/activity+json'),
-              'Link' => include('activity+json')
-            )
-            expect(response.parsed_body)
-              .to include(content: include(status.text))
-          end
-        end
-      end
-
-      context 'when status visibility is private' do
-        let(:status) { Fabricate(:status, account: account, visibility: :private) }
-
-        it 'returns http not found' do
-          get short_account_status_path(account_username: account.username, id: status.id)
-
-          expect(response)
-            .to have_http_status(404)
-        end
-      end
-
-      context 'when status visibility is direct' do
-        let(:status) { Fabricate(:status, account: account, visibility: :direct) }
-
-        it 'returns http not found' do
-          get short_account_status_path(account_username: account.username, id: status.id)
-
-          expect(response)
-            .to have_http_status(404)
-        end
-      end
-    end
-
-    context 'when signed in' do
-      subject { get short_account_status_path(account_username: account.username, id: status.id, format: format) }
-
-      let(:user) { Fabricate(:user) }
-
-      before { sign_in_with_session(user) }
-
-      context 'when account blocks user' do
-        before { account.block!(user.account) }
-
-        it 'returns http not found' do
-          get "/@#{status.account.username}/#{status.id}"
-
-          expect(response)
-            .to have_http_status(404)
-        end
-      end
-
-      context 'when status is public' do
-        context 'with HTML' do
-          let(:format) { 'html' }
-
-          it 'renders status successfully', :aggregate_failures do
-            subject
-
-            expect(response)
-              .to have_http_status(200)
-            expect(response.headers).to include(
-              'Vary' => 'Accept, Accept-Language, Cookie',
-              'Cache-Control' => include('private'),
-              'Link' => include('activity+json')
-            )
-            expect(response.body)
-              .to include(status.text)
-          end
-        end
-
-        context 'with JSON' do
-          let(:format) { 'json' }
-
-          it 'renders ActivityPub Note object successfully', :aggregate_failures do
-            subject
-
-            expect(response)
-              .to have_http_status(200)
-            expect(response.headers).to include(
-              'Vary' => 'Accept, Accept-Language, Cookie',
-              'Cache-Control' => include('private'),
-              'Content-Type' => include('application/activity+json'),
-              'Link' => include('activity+json')
-            )
-            expect(response.parsed_body)
-              .to include(content: include(status.text))
-          end
-        end
-      end
-
-      context 'when status is private' do
-        let(:status) { Fabricate(:status, account: account, visibility: :private) }
-
-        context 'when user is authorized to see it' do
-          before { user.account.follow!(account) }
-
-          context 'with HTML' do
-            let(:format) { 'html' }
-
-            it 'renders status successfully', :aggregate_failures do
-              subject
-
-              expect(response)
-                .to have_http_status(200)
-
-              expect(response.headers).to include(
-                'Vary' => 'Accept, Accept-Language, Cookie',
-                'Cache-Control' => include('private'),
-                'Link' => include('activity+json')
-              )
-              expect(response.body)
-                .to include(status.text)
-            end
-          end
-
-          context 'with JSON' do
-            let(:format) { 'json' }
-
-            it 'renders ActivityPub Note object successfully', :aggregate_failures do
-              subject
-
-              expect(response)
-                .to have_http_status(200)
-              expect(response.headers).to include(
-                'Vary' => 'Accept, Accept-Language, Cookie',
-                'Cache-Control' => include('private'),
-                'Content-Type' => include('application/activity+json'),
-                'Link' => include('activity+json')
-              )
-              expect(response.parsed_body)
-                .to include(content: include(status.text))
-            end
-          end
-        end
-
-        context 'when user is not authorized to see it' do
-          let(:format) { 'html' }
-
-          it 'returns http not found' do
-            subject
-
-            expect(response)
-              .to have_http_status(404)
-          end
-        end
-      end
-
-      context 'when status is direct' do
-        let(:status) { Fabricate(:status, account: account, visibility: :direct) }
-
-        context 'when user is authorized to see it' do
-          before { Fabricate(:mention, account: user.account, status: status) }
-
-          context 'with HTML' do
-            let(:format) { 'html' }
-
-            it 'renders status successfully', :aggregate_failures do
-              subject
-
-              expect(response)
-                .to have_http_status(200)
-              expect(response.headers).to include(
-                'Vary' => 'Accept, Accept-Language, Cookie',
-                'Cache-Control' => include('private'),
-                'Link' => include('activity+json')
-              )
-              expect(response.body)
-                .to include(status.text)
-            end
-          end
-
-          context 'with JSON' do
-            let(:format) { 'json' }
-
-            it 'renders ActivityPub Note object successfully' do
-              subject
-
-              expect(response)
-                .to have_http_status(200)
-              expect(response.headers).to include(
-                'Vary' => 'Accept, Accept-Language, Cookie',
-                'Cache-Control' => include('private'),
-                'Content-Type' => include('application/activity+json'),
-                'Link' => include('activity+json')
-              )
-              expect(response.parsed_body)
-                .to include(content: include(status.text))
-            end
-          end
-        end
-
-        context 'when user is not authorized to see it' do
-          let(:format) { 'html' }
-
-          it 'returns http not found' do
-            subject
-
-            expect(response)
-              .to have_http_status(404)
-          end
-        end
-      end
-
-      private
-
-      def sign_in_with_session(user)
-        # The regular `sign_in` helper does not actually set session cookies
-        # The endpoint responses here rely on cookie/session checks to set cache privacy headers
-        # To enable that, perform a full sign in which will establish those cookies for subsequent spec requests
-        post user_session_path, params: { user: { email: user.email, password: user.password } }
-      end
-    end
-
-    context 'with "HTTP Signature" access signed by a remote account' do
-      subject do
-        get short_account_status_path(account_username: status.account.username, id: status.id, format: format),
-            headers: nil,
-            sign_with: remote_account
-      end
-
-      let(:format) { 'html' }
-      let(:remote_account) { Fabricate(:account, domain: 'host.example') }
-
-      context 'when account blocks the remote account' do
-        before { account.block!(remote_account) }
-
-        it 'returns http not found' do
-          subject
-
-          expect(response)
-            .to have_http_status(404)
-        end
-      end
-
-      context 'when account domain blocks the domain of the remote account' do
-        before { account.block_domain!(remote_account.domain) }
-
-        it 'returns http not found' do
-          subject
-
-          expect(response)
-            .to have_http_status(404)
-        end
-      end
-
-      context 'when status has public visibility' do
-        context 'with HTML' do
-          let(:format) { 'html' }
-
-          it 'renders status successfully', :aggregate_failures do
-            subject
-
-            expect(response)
-              .to have_http_status(200)
-            expect(response.headers).to include(
-              'Vary' => 'Accept, Accept-Language, Cookie',
-              'Cache-Control' => include('private'),
-              'Link' => include('activity+json')
-            )
-            expect(response.body)
-              .to include(status.text)
-          end
-        end
-
-        context 'with JSON' do
-          let(:format) { 'json' }
-
-          it 'renders ActivityPub Note object successfully', :aggregate_failures do
-            subject
-
-            expect(response)
-              .to have_http_status(200)
-              .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie')
-            expect(response.headers).to include(
-              'Content-Type' => include('application/activity+json'),
-              'Link' => include('activity+json')
-            )
-            expect(response.parsed_body)
-              .to include(content: include(status.text))
-          end
-        end
-      end
-
-      context 'when status has private visibility' do
-        let(:status) { Fabricate(:status, account: account, visibility: :private) }
-
-        context 'when user is authorized to see it' do
-          before { remote_account.follow!(account) }
-
-          context 'with HTML' do
-            let(:format) { 'html' }
-
-            it 'renders status successfully', :aggregate_failures do
-              subject
-
-              expect(response)
-                .to have_http_status(200)
-              expect(response.headers).to include(
-                'Vary' => 'Accept, Accept-Language, Cookie',
-                'Cache-Control' => include('private'),
-                'Link' => include('activity+json')
-              )
-              expect(response.body)
-                .to include(status.text)
-            end
-          end
-
-          context 'with JSON' do
-            let(:format) { 'json' }
-
-            it 'renders ActivityPub Note object successfully' do
-              subject
-
-              expect(response)
-                .to have_http_status(200)
-              expect(response.headers).to include(
-                'Vary' => 'Accept, Accept-Language, Cookie',
-                'Cache-Control' => include('private'),
-                'Content-Type' => include('application/activity+json'),
-                'Link' => include('activity+json')
-              )
-
-              expect(response.parsed_body)
-                .to include(content: include(status.text))
-            end
-          end
-        end
-
-        context 'when user is not authorized to see it' do
-          it 'returns http not found' do
-            subject
-
-            expect(response)
-              .to have_http_status(404)
-          end
-        end
-      end
-
-      context 'when status is direct' do
-        let(:status) { Fabricate(:status, account: account, visibility: :direct) }
-
-        context 'when user is authorized to see it' do
-          before { Fabricate(:mention, account: remote_account, status: status) }
-
-          context 'with HTML' do
-            let(:format) { 'html' }
-
-            it 'renders status successfully', :aggregate_failures do
-              subject
-
-              expect(response)
-                .to have_http_status(200)
-              expect(response.headers).to include(
-                'Vary' => 'Accept, Accept-Language, Cookie',
-                'Cache-Control' => include('private'),
-                'Link' => include('activity+json')
-              )
-              expect(response.body)
-                .to include(status.text)
-            end
-          end
-
-          context 'with JSON' do
-            let(:format) { 'json' }
-
-            it 'renders ActivityPub Note object', :aggregate_failures do
-              subject
-
-              expect(response)
-                .to have_http_status(200)
-              expect(response.headers).to include(
-                'Vary' => 'Accept, Accept-Language, Cookie',
-                'Cache-Control' => include('private'),
-                'Content-Type' => include('application/activity+json'),
-                'Link' => include('activity+json')
-              )
-              expect(response.parsed_body)
-                .to include(content: include(status.text))
-            end
-          end
-        end
-
-        context 'when user is not authorized to see it' do
-          it 'returns http not found' do
-            subject
-
-            expect(response)
-              .to have_http_status(404)
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/requests/tags_spec.rb b/spec/requests/tags_spec.rb
index f04d1bc2d3..fbd1f7d56e 100644
--- a/spec/requests/tags_spec.rb
+++ b/spec/requests/tags_spec.rb
@@ -7,29 +7,6 @@ RSpec.describe 'Tags' do
     context 'when tag exists' do
       let(:tag) { Fabricate :tag }
 
-      context 'with HTML format' do
-        before { get tag_path(tag) }
-
-        it 'returns page with links to alternate resources' do
-          expect(rss_links.first[:href])
-            .to eq(tag_url(tag))
-          expect(activity_json_links.first[:href])
-            .to eq(tag_url(tag))
-        end
-
-        def rss_links
-          alternate_links.css('[type="application/rss+xml"]')
-        end
-
-        def activity_json_links
-          alternate_links.css('[type="application/activity+json"]')
-        end
-
-        def alternate_links
-          response.parsed_body.css('link[rel=alternate]')
-        end
-      end
-
       context 'with JSON format' do
         before { get tag_path(tag, format: :json) }
 
diff --git a/spec/requests/well_known/host_meta_spec.rb b/spec/requests/well_known/host_meta_spec.rb
index 8d8e38f521..726911dda1 100644
--- a/spec/requests/well_known/host_meta_spec.rb
+++ b/spec/requests/well_known/host_meta_spec.rb
@@ -3,62 +3,45 @@
 require 'rails_helper'
 
 RSpec.describe 'The /.well-known/host-meta request' do
-  context 'without extension format or accept header' do
-    it 'returns http success with expected XML' do
-      get '/.well-known/host-meta'
+  it 'returns http success with valid XML response' do
+    get '/.well-known/host-meta'
 
-      expect(response)
-        .to have_http_status(200)
-        .and have_attributes(
-          media_type: 'application/xrd+xml'
-        )
+    expect(response)
+      .to have_http_status(200)
+      .and have_attributes(
+        media_type: 'application/xrd+xml'
+      )
 
-      expect(xrd_link_template_value)
-        .to eq 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}'
-    end
-
-    def xrd_link_template_value
-      response
-        .parsed_body
-        .at_xpath('/xrd:XRD/xrd:Link[@rel="lrdd"]/@template', 'xrd' => 'http://docs.oasis-open.org/ns/xri/xrd-1.0')
-        .value
-    end
+    doc = Nokogiri::XML(response.parsed_body)
+    expect(doc.at_xpath('/xrd:XRD/xrd:Link[@rel="lrdd"]/@template', 'xrd' => 'http://docs.oasis-open.org/ns/xri/xrd-1.0').value)
+      .to eq 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}'
   end
 
-  context 'with a .json format extension' do
-    it 'returns http success with expected JSON' do
-      get '/.well-known/host-meta.json'
+  it 'returns http success with valid JSON response with .json extension' do
+    get '/.well-known/host-meta.json'
 
-      expect(response)
-        .to have_http_status(200)
-        .and have_attributes(
-          media_type: 'application/json'
-        )
-      expect(response.parsed_body)
-        .to include(expected_json_template)
-    end
+    expect(response)
+      .to have_http_status(200)
+      .and have_attributes(
+        media_type: 'application/json'
+      )
+
+    expect(response.parsed_body)
+      .to include(
+        links: [
+          'rel' => 'lrdd',
+          'template' => 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}',
+        ]
+      )
   end
 
-  context 'with a JSON `Accept` header' do
-    it 'returns http success with expected JSON' do
-      get '/.well-known/host-meta', headers: { 'Accept' => 'application/json' }
+  it 'returns http success with valid JSON response with Accept header' do
+    get '/.well-known/host-meta', headers: { 'Accept' => 'application/json' }
 
-      expect(response)
-        .to have_http_status(200)
-        .and have_attributes(
-          media_type: 'application/json'
-        )
-      expect(response.parsed_body)
-        .to include(expected_json_template)
-    end
-  end
-
-  def expected_json_template
-    {
-      links: [
-        'rel' => 'lrdd',
-        'template' => 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}',
-      ],
-    }
+    expect(response)
+      .to have_http_status(200)
+      .and have_attributes(
+        media_type: 'application/json'
+      )
   end
 end
diff --git a/spec/requests/well_known/oauth_metadata_spec.rb b/spec/requests/well_known/oauth_metadata_spec.rb
index 42a6c1b328..9c86dbedfe 100644
--- a/spec/requests/well_known/oauth_metadata_spec.rb
+++ b/spec/requests/well_known/oauth_metadata_spec.rb
@@ -3,6 +3,12 @@
 require 'rails_helper'
 
 RSpec.describe 'The /.well-known/oauth-authorization-server request' do
+  let(:protocol) { ENV.fetch('LOCAL_HTTPS', true) ? :https : :http }
+
+  before do
+    host! Rails.configuration.x.local_domain
+  end
+
   it 'returns http success with valid JSON response' do
     get '/.well-known/oauth-authorization-server'
 
@@ -16,20 +22,19 @@ RSpec.describe 'The /.well-known/oauth-authorization-server request' do
     grant_types_supported << 'refresh_token' if Doorkeeper.configuration.refresh_token_enabled?
 
     expect(response.parsed_body).to include(
-      issuer: root_url,
+      issuer: root_url(protocol: protocol),
       service_documentation: 'https://docs.joinmastodon.org/',
-      authorization_endpoint: oauth_authorization_url,
-      token_endpoint: oauth_token_url,
-      userinfo_endpoint: oauth_userinfo_url,
-      revocation_endpoint: oauth_revoke_url,
+      authorization_endpoint: oauth_authorization_url(protocol: protocol),
+      token_endpoint: oauth_token_url(protocol: protocol),
+      revocation_endpoint: oauth_revoke_url(protocol: protocol),
       scopes_supported: Doorkeeper.configuration.scopes.map(&:to_s),
       response_types_supported: Doorkeeper.configuration.authorization_response_types,
       response_modes_supported: Doorkeeper.configuration.authorization_response_flows.flat_map(&:response_mode_matches).uniq,
       token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post),
       grant_types_supported: grant_types_supported,
-      code_challenge_methods_supported: Doorkeeper.configuration.pkce_code_challenge_methods_supported,
+      code_challenge_methods_supported: ['S256'],
       # non-standard extension:
-      app_registration_endpoint: api_v1_apps_url
+      app_registration_endpoint: api_v1_apps_url(protocol: protocol)
     )
   end
 end
diff --git a/spec/requests/well_known/webfinger_spec.rb b/spec/requests/well_known/webfinger_spec.rb
index b4aeb65320..aeff56aebf 100644
--- a/spec/requests/well_known/webfinger_spec.rb
+++ b/spec/requests/well_known/webfinger_spec.rb
@@ -116,13 +116,19 @@ RSpec.describe 'The /.well-known/webfinger endpoint' do
       perform_request!
     end
 
-    it 'returns http success with expect headers and media type' do
+    it 'returns http success' do
       expect(response).to have_http_status(200)
+    end
 
+    it 'sets only a Vary Origin header' do
       expect(response.headers['Vary']).to eq('Origin')
+    end
 
+    it 'returns application/jrd+json' do
       expect(response.media_type).to eq 'application/jrd+json'
+    end
 
+    it 'returns links for the internal account' do
       expect(response.parsed_body)
         .to include(
           subject: 'acct:mastodon.internal@cb6e6126.ngrok.io',
diff --git a/spec/routing/custom_css_routing_spec.rb b/spec/routing/custom_css_routing_spec.rb
deleted file mode 100644
index 26139b4744..0000000000
--- a/spec/routing/custom_css_routing_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Custom CSS routes' do
-  describe 'the legacy route' do
-    it 'routes to correct place' do
-      expect(get('/custom.css'))
-        .to route_to('custom_css#show')
-    end
-  end
-
-  describe 'the custom digest route' do
-    it 'routes to correct place' do
-      expect(get('/css/custom-1a2s3d4f.css'))
-        .to route_to('custom_css#show', id: 'custom-1a2s3d4f', format: 'css')
-    end
-  end
-end
diff --git a/spec/serializers/activitypub/accept_follow_serializer_spec.rb b/spec/serializers/activitypub/accept_follow_serializer_spec.rb
deleted file mode 100644
index 0639e64b9d..0000000000
--- a/spec/serializers/activitypub/accept_follow_serializer_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ActivityPub::AcceptFollowSerializer do
-  subject { serialized_record_json(record, described_class, adapter: ActivityPub::Adapter) }
-
-  describe 'serializing an object' do
-    let(:record) { Fabricate :follow_request }
-
-    it 'returns expected attributes' do
-      expect(subject.deep_symbolize_keys)
-        .to include(
-          actor: eq(ActivityPub::TagManager.instance.uri_for(record.target_account)),
-          id: match("#accepts/follows/#{record.id}"),
-          object: include(type: 'Follow'),
-          type: 'Accept'
-        )
-    end
-  end
-end
diff --git a/spec/serializers/activitypub/actor_serializer_spec.rb b/spec/serializers/activitypub/actor_serializer_spec.rb
deleted file mode 100644
index ad24455953..0000000000
--- a/spec/serializers/activitypub/actor_serializer_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ActivityPub::ActorSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  describe '#type' do
-    context 'with the instance actor' do
-      let(:record) { Account.find(Account::INSTANCE_ACTOR_ID) }
-
-      it { is_expected.to include('type' => 'Application') }
-    end
-
-    context 'with an application actor' do
-      let(:record) { Fabricate :account, actor_type: 'Application' }
-
-      it { is_expected.to include('type' => 'Service') }
-    end
-
-    context 'with a service actor' do
-      let(:record) { Fabricate :account, actor_type: 'Service' }
-
-      it { is_expected.to include('type' => 'Service') }
-    end
-
-    context 'with a Group actor' do
-      let(:record) { Fabricate :account, actor_type: 'Group' }
-
-      it { is_expected.to include('type' => 'Group') }
-    end
-
-    context 'with a Person actor' do
-      let(:record) { Fabricate :account, actor_type: 'Person' }
-
-      it { is_expected.to include('type' => 'Person') }
-    end
-  end
-end
diff --git a/spec/serializers/activitypub/add_serializer_spec.rb b/spec/serializers/activitypub/add_serializer_spec.rb
deleted file mode 100644
index 3b3eaeb1b0..0000000000
--- a/spec/serializers/activitypub/add_serializer_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ActivityPub::AddSerializer do
-  describe '.serializer_for' do
-    subject { described_class.serializer_for(model, {}) }
-
-    context 'with a Status model' do
-      let(:model) { Status.new }
-
-      it { is_expected.to eq(described_class::UriSerializer) }
-    end
-
-    context 'with a FeaturedTag model' do
-      let(:model) { FeaturedTag.new }
-
-      it { is_expected.to eq(ActivityPub::HashtagSerializer) }
-    end
-
-    context 'with an Array' do
-      let(:model) { [] }
-
-      it { is_expected.to eq(ActiveModel::Serializer::CollectionSerializer) }
-    end
-  end
-end
diff --git a/spec/serializers/activitypub/collection_serializer_spec.rb b/spec/serializers/activitypub/collection_serializer_spec.rb
deleted file mode 100644
index 7726df914f..0000000000
--- a/spec/serializers/activitypub/collection_serializer_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ActivityPub::CollectionSerializer do
-  describe '.serializer_for' do
-    subject { described_class.serializer_for(model, {}) }
-
-    context 'with a Status model' do
-      let(:model) { Status.new }
-
-      it { is_expected.to eq(ActivityPub::NoteSerializer) }
-    end
-
-    context 'with a FeaturedTag model' do
-      let(:model) { FeaturedTag.new }
-
-      it { is_expected.to eq(ActivityPub::HashtagSerializer) }
-    end
-
-    context 'with an ActivityPub::CollectionPresenter' do
-      let(:model) { ActivityPub::CollectionPresenter.new }
-
-      it { is_expected.to eq(described_class) }
-    end
-
-    context 'with a String' do
-      let(:model) { '' }
-
-      it { is_expected.to eq(described_class::StringSerializer) }
-    end
-
-    context 'with an Array' do
-      let(:model) { [] }
-
-      it { is_expected.to eq(ActiveModel::Serializer::CollectionSerializer) }
-    end
-  end
-end
diff --git a/spec/serializers/activitypub/emoji_serializer_spec.rb b/spec/serializers/activitypub/emoji_serializer_spec.rb
deleted file mode 100644
index 4384ae4b13..0000000000
--- a/spec/serializers/activitypub/emoji_serializer_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ActivityPub::EmojiSerializer do
-  describe '.serializer_for' do
-    subject { serialized_record_json(model, described_class, adapter: ActivityPub::Adapter) }
-
-    let(:model) { Fabricate(:custom_emoji) }
-
-    context 'without license' do
-      it 'does not have information' do
-        expect(subject).to_not include({
-          '_misskey_license' => { 'freeText' => 'Ohagi' },
-        })
-        expect(subject).to_not include({
-          'license' => 'Ohagi',
-        })
-      end
-    end
-
-    context 'with license' do
-      let(:model) { Fabricate(:custom_emoji, license: 'Ohagi') }
-
-      it 'has information' do
-        expect(subject).to include({
-          'license' => 'Ohagi',
-          '_misskey_license' => { 'freeText' => 'Ohagi' },
-        })
-      end
-    end
-  end
-end
diff --git a/spec/serializers/activitypub/reject_follow_serializer_spec.rb b/spec/serializers/activitypub/reject_follow_serializer_spec.rb
deleted file mode 100644
index 10d8749178..0000000000
--- a/spec/serializers/activitypub/reject_follow_serializer_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ActivityPub::RejectFollowSerializer do
-  subject { serialized_record_json(record, described_class, adapter: ActivityPub::Adapter) }
-
-  describe 'serializing an object' do
-    let(:record) { Fabricate :follow_request }
-
-    it 'returns expected attributes' do
-      expect(subject.deep_symbolize_keys)
-        .to include(
-          actor: eq(ActivityPub::TagManager.instance.uri_for(record.target_account)),
-          id: match("#rejects/follows/#{record.id}"),
-          object: include(type: 'Follow'),
-          type: 'Reject'
-        )
-    end
-  end
-end
diff --git a/spec/serializers/activitypub/remove_serializer_spec.rb b/spec/serializers/activitypub/remove_serializer_spec.rb
deleted file mode 100644
index 0e4b199838..0000000000
--- a/spec/serializers/activitypub/remove_serializer_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ActivityPub::RemoveSerializer do
-  describe '.serializer_for' do
-    subject { described_class.serializer_for(model, {}) }
-
-    context 'with a Status model' do
-      let(:model) { Status.new }
-
-      it { is_expected.to eq(described_class::UriSerializer) }
-    end
-
-    context 'with a FeaturedTag model' do
-      let(:model) { FeaturedTag.new }
-
-      it { is_expected.to eq(ActivityPub::HashtagSerializer) }
-    end
-
-    context 'with an Array' do
-      let(:model) { [] }
-
-      it { is_expected.to eq(ActiveModel::Serializer::CollectionSerializer) }
-    end
-  end
-end
diff --git a/spec/serializers/rest/account_relationship_severance_event_serializer_spec.rb b/spec/serializers/rest/account_relationship_severance_event_serializer_spec.rb
deleted file mode 100644
index 909fde39b3..0000000000
--- a/spec/serializers/rest/account_relationship_severance_event_serializer_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::AccountRelationshipSeveranceEventSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  let(:record) { Fabricate.build :account_relationship_severance_event, id: 123, created_at: DateTime.new(2024, 11, 28, 16, 20, 0) }
-
-  describe 'serialization' do
-    it 'returns expected values' do
-      expect(subject)
-        .to include(
-          'id' => be_a(String).and(eq('123')),
-          'created_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/account_serializer/field_serializer_spec.rb b/spec/serializers/rest/account_serializer/field_serializer_spec.rb
deleted file mode 100644
index 6d97fa573e..0000000000
--- a/spec/serializers/rest/account_serializer/field_serializer_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::AccountSerializer::FieldSerializer do
-  subject { serialized_record_json(field, described_class) }
-
-  let(:default_datetime) { DateTime.new(2024, 11, 28, 16, 20, 0) }
-  let(:account) { Fabricate.build :account }
-
-  context 'when verified_at is populated' do
-    let(:field) { Account::Field.new(account, 'name' => 'Foo', 'value' => 'Bar', 'verified_at' => default_datetime) }
-
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'verified_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/account_serializer_spec.rb b/spec/serializers/rest/account_serializer_spec.rb
index 5fd4f8d706..7daa0796a9 100644
--- a/spec/serializers/rest/account_serializer_spec.rb
+++ b/spec/serializers/rest/account_serializer_spec.rb
@@ -5,7 +5,6 @@ require 'rails_helper'
 RSpec.describe REST::AccountSerializer do
   subject { serialized_record_json(account, described_class) }
 
-  let(:default_datetime) { DateTime.new(2024, 11, 28, 16, 20, 0) }
   let(:role)    { Fabricate(:user_role, name: 'Role', highlighted: true) }
   let(:user)    { Fabricate(:user, role: role) }
   let(:account) { user.account }
@@ -45,27 +44,4 @@ RSpec.describe REST::AccountSerializer do
       expect(subject['memorial']).to be true
     end
   end
-
-  context 'when created_at is populated' do
-    before do
-      account.account_stat.update!(created_at: default_datetime)
-    end
-
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'created_at' => match_api_datetime_format
-        )
-    end
-  end
-
-  context 'when last_status_at is populated' do
-    before do
-      account.account_stat.update!(last_status_at: default_datetime)
-    end
-
-    it 'is serialized as yyyy-mm-dd' do
-      expect(subject['last_status_at']).to eq('2024-11-28')
-    end
-  end
 end
diff --git a/spec/serializers/rest/account_warning_serializer_spec.rb b/spec/serializers/rest/account_warning_serializer_spec.rb
deleted file mode 100644
index a7a9dc5f63..0000000000
--- a/spec/serializers/rest/account_warning_serializer_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::AccountWarningSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  let(:record) { Fabricate :account_warning, id: 123, status_ids: [456, 789] }
-
-  describe 'serialization' do
-    it 'returns expected values' do
-      expect(subject)
-        .to include(
-          'id' => be_a(String).and(eq('123')),
-          'status_ids' => be_a(Array).and(eq(['456', '789'])),
-          'created_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/admin/account_serializer_spec.rb b/spec/serializers/rest/admin/account_serializer_spec.rb
deleted file mode 100644
index 5f617207a7..0000000000
--- a/spec/serializers/rest/admin/account_serializer_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::Admin::AccountSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  context 'when created_at is populated' do
-    let(:record) { Fabricate :account, user: Fabricate(:user) }
-
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'created_at' => match_api_datetime_format
-        )
-    end
-  end
-
-  describe 'created_by_application_id' do
-    context 'when account is application-created' do
-      let(:record) { Fabricate :account, user: Fabricate(:user, created_by_application: application) }
-      let(:application) { Fabricate :application }
-
-      it { is_expected.to include('created_by_application_id' => application.id.to_s) }
-    end
-  end
-
-  describe 'invited_by_account_id' do
-    context 'when account was invited' do
-      let(:record) { Fabricate :account, user: Fabricate(:user, invite: invite) }
-      let(:invite) { Fabricate :invite }
-
-      it { is_expected.to include('invited_by_account_id' => invite.user.account.id.to_s) }
-    end
-  end
-end
diff --git a/spec/serializers/rest/admin/cohort_serializer_spec.rb b/spec/serializers/rest/admin/cohort_serializer_spec.rb
deleted file mode 100644
index 1305f901bb..0000000000
--- a/spec/serializers/rest/admin/cohort_serializer_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::Admin::CohortSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  let(:record) { Admin::Metrics::Retention.new('2024-01-01', '2024-01-02', 'day').cohorts.first }
-
-  describe 'serialization' do
-    it 'returns expected values' do
-      expect(subject)
-        .to include(
-          'data' => be_a(Array).and(
-            all(include('date' => match_api_datetime_format))
-          ),
-          'period' => match(/2024-01-01/).and(match_api_datetime_format)
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/admin/domain_allow_serializer_spec.rb b/spec/serializers/rest/admin/domain_allow_serializer_spec.rb
deleted file mode 100644
index ace35176a7..0000000000
--- a/spec/serializers/rest/admin/domain_allow_serializer_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::Admin::DomainAllowSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  let(:record) { Fabricate(:domain_allow) }
-
-  context 'when created_at is populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'created_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/admin/domain_block_serializer_spec.rb b/spec/serializers/rest/admin/domain_block_serializer_spec.rb
deleted file mode 100644
index 37d658b1f7..0000000000
--- a/spec/serializers/rest/admin/domain_block_serializer_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::Admin::DomainBlockSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  let(:record) { Fabricate(:domain_block) }
-
-  context 'when created_at is populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'created_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/admin/email_domain_block_serializer_spec.rb b/spec/serializers/rest/admin/email_domain_block_serializer_spec.rb
deleted file mode 100644
index e146040e24..0000000000
--- a/spec/serializers/rest/admin/email_domain_block_serializer_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::Admin::EmailDomainBlockSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  let(:record) { Fabricate(:email_domain_block) }
-
-  context 'when created_at is populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'created_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/admin/ip_block_serializer_spec.rb b/spec/serializers/rest/admin/ip_block_serializer_spec.rb
deleted file mode 100644
index 8a68161fc2..0000000000
--- a/spec/serializers/rest/admin/ip_block_serializer_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::Admin::IpBlockSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  context 'when timestamps are populated' do
-    let(:record) { Fabricate(:ip_block, expires_at: DateTime.new(2024, 11, 28, 16, 20, 0)) }
-
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'created_at' => match_api_datetime_format,
-          'expires_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/admin/ip_serializer_spec.rb b/spec/serializers/rest/admin/ip_serializer_spec.rb
deleted file mode 100644
index 5aabf74756..0000000000
--- a/spec/serializers/rest/admin/ip_serializer_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::Admin::IpSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  let(:record) { UserIp.new(used_at: 3.days.ago) }
-
-  context 'when used_at is populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'used_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/admin/measure_serializer_spec.rb b/spec/serializers/rest/admin/measure_serializer_spec.rb
deleted file mode 100644
index e158d9d9ba..0000000000
--- a/spec/serializers/rest/admin/measure_serializer_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::Admin::MeasureSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  let(:start_at) { 2.days.ago }
-  let(:end_at) { Time.now.utc }
-  let(:params) { ActionController::Parameters.new({ instance_accounts: [123] }) }
-  let(:record) { Admin::Metrics::Measure::ActiveUsersMeasure.new(start_at, end_at, params) }
-
-  context 'when start_at is populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'data' => all(
-            include('date' => match_api_datetime_format)
-          )
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/admin/report_serializer_spec.rb b/spec/serializers/rest/admin/report_serializer_spec.rb
deleted file mode 100644
index 78d7d4f10a..0000000000
--- a/spec/serializers/rest/admin/report_serializer_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::Admin::ReportSerializer do
-  subject { serialized_record_json(report, described_class) }
-
-  context 'with timestamps' do
-    let(:report) { Fabricate(:report, action_taken_at: 3.days.ago) }
-
-    it 'is serialized as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'action_taken_at' => match_api_datetime_format,
-          'created_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/admin/webhook_event_serializer_spec.rb b/spec/serializers/rest/admin/webhook_event_serializer_spec.rb
deleted file mode 100644
index 3cbfbd92a3..0000000000
--- a/spec/serializers/rest/admin/webhook_event_serializer_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::Admin::WebhookEventSerializer do
-  describe '.serializer_for' do
-    subject { described_class.serializer_for(model, {}) }
-
-    context 'with an Account model' do
-      let(:model) { Account.new }
-
-      it { is_expected.to eq(REST::Admin::AccountSerializer) }
-    end
-
-    context 'with a Report model' do
-      let(:model) { Report.new }
-
-      it { is_expected.to eq(REST::Admin::ReportSerializer) }
-    end
-
-    context 'with a Status model' do
-      let(:model) { Status.new }
-
-      it { is_expected.to eq(REST::StatusSerializer) }
-    end
-
-    context 'with an Array' do
-      let(:model) { [] }
-
-      it { is_expected.to eq(ActiveModel::Serializer::CollectionSerializer) }
-    end
-  end
-end
diff --git a/spec/serializers/rest/announcement_serializer_spec.rb b/spec/serializers/rest/announcement_serializer_spec.rb
deleted file mode 100644
index ee0acab981..0000000000
--- a/spec/serializers/rest/announcement_serializer_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::AnnouncementSerializer do
-  subject do
-    serialized_record_json(
-      announcement,
-      described_class,
-      options: {
-        scope: current_user,
-        scope_name: :current_user,
-      }
-    )
-  end
-
-  let(:current_user) { Fabricate(:user) }
-  let(:announcement) { Fabricate(:announcement, starts_at: 10.days.ago, published_at: 10.days.ago, ends_at: 5.days.from_now) }
-
-  context 'when date fields are populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'starts_at' => match_api_datetime_format,
-          'ends_at' => match_api_datetime_format,
-          'published_at' => match_api_datetime_format,
-          'updated_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/annual_report_event_serializer_spec.rb b/spec/serializers/rest/annual_report_event_serializer_spec.rb
deleted file mode 100644
index b595bf3e36..0000000000
--- a/spec/serializers/rest/annual_report_event_serializer_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::AnnualReportEventSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  describe 'serializing an object' do
-    let(:record) { Fabricate.build :generated_annual_report, year: 2024 }
-
-    it 'returns expected attributes' do
-      expect(subject.deep_symbolize_keys)
-        .to include(
-          year: '2024'
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/appeal_serializer_spec.rb b/spec/serializers/rest/appeal_serializer_spec.rb
deleted file mode 100644
index 1ae6617de5..0000000000
--- a/spec/serializers/rest/appeal_serializer_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::AppealSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  describe 'state' do
-    context 'when appeal is approved' do
-      let(:record) { Fabricate.build :appeal, approved_at: 2.days.ago }
-
-      it { is_expected.to include('state' => 'approved') }
-    end
-
-    context 'when appeal is rejected' do
-      let(:record) { Fabricate.build :appeal, rejected_at: 2.days.ago }
-
-      it { is_expected.to include('state' => 'rejected') }
-    end
-
-    context 'when appeal is not approved or rejected' do
-      let(:record) { Fabricate.build :appeal, approved_at: nil, rejected_at: nil }
-
-      it { is_expected.to include('state' => 'pending') }
-    end
-  end
-end
diff --git a/spec/serializers/rest/custom_emoji_serializer_spec.rb b/spec/serializers/rest/custom_emoji_serializer_spec.rb
index 116325e7dc..10f84278af 100644
--- a/spec/serializers/rest/custom_emoji_serializer_spec.rb
+++ b/spec/serializers/rest/custom_emoji_serializer_spec.rb
@@ -3,18 +3,15 @@
 require 'rails_helper'
 
 RSpec.describe REST::CustomEmojiSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  let(:record) { Fabricate.build :custom_emoji, id: 123, category: Fabricate(:custom_emoji_category, name: 'Category Name'), aliases: aliases }
+  let(:serialization) { serialized_record_json(record, described_class) }
+  let(:record) do
+    Fabricate(:custom_emoji, shortcode: 'ohagi', aliases: aliases)
+  end
   let(:aliases) { [] }
 
-  describe 'serialization' do
-    it 'returns expected values' do
-      expect(subject)
-        .to include(
-          'category' => be_a(String).and(eq('Category Name')),
-          'aliases' => be_a(Array).and(eq([]))
-        )
+  context 'when empty aliases' do
+    it 'returns normalized aliases' do
+      expect(serialization['aliases']).to eq []
     end
   end
 
@@ -22,7 +19,7 @@ RSpec.describe REST::CustomEmojiSerializer do
     let(:aliases) { nil }
 
     it 'returns normalized aliases' do
-      expect(subject['aliases']).to eq []
+      expect(serialization['aliases']).to eq []
     end
   end
 
@@ -30,7 +27,7 @@ RSpec.describe REST::CustomEmojiSerializer do
     let(:aliases) { [nil] }
 
     it 'returns normalized aliases' do
-      expect(subject['aliases']).to eq []
+      expect(serialization['aliases']).to eq []
     end
   end
 
@@ -38,7 +35,7 @@ RSpec.describe REST::CustomEmojiSerializer do
     let(:aliases) { ['neko'] }
 
     it 'returns normalized aliases' do
-      expect(subject['aliases']).to eq ['neko']
+      expect(serialization['aliases']).to eq ['neko']
     end
   end
 end
diff --git a/spec/serializers/rest/extended_description_serializer_spec.rb b/spec/serializers/rest/extended_description_serializer_spec.rb
deleted file mode 100644
index dc6e86e909..0000000000
--- a/spec/serializers/rest/extended_description_serializer_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::ExtendedDescriptionSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  let(:default_datetime) { DateTime.new(2024, 11, 28, 16, 20, 0) }
-
-  describe 'serialization' do
-    context 'with text present' do
-      let(:record) { ExtendedDescription.new text: 'Hello world', updated_at: default_datetime }
-
-      it 'returns expected values' do
-        expect(subject)
-          .to include(
-            'content' => eq(<<~HTML),
-              <p>Hello world</p>
-            HTML
-            'updated_at' => eq('2024-11-28T16:20:00+00:00')
-          )
-      end
-    end
-
-    context 'with text missing' do
-      let(:record) { ExtendedDescription.new text: nil, updated_at: default_datetime }
-
-      it 'returns expected values' do
-        expect(subject)
-          .to include(
-            'content' => eq(''),
-            'updated_at' => eq('2024-11-28T16:20:00+00:00')
-          )
-      end
-    end
-  end
-end
diff --git a/spec/serializers/rest/featured_tag_serializer_spec.rb b/spec/serializers/rest/featured_tag_serializer_spec.rb
deleted file mode 100644
index 883fce1aa7..0000000000
--- a/spec/serializers/rest/featured_tag_serializer_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::FeaturedTagSerializer do
-  subject do
-    serialized_record_json(
-      featured_tag,
-      described_class
-    )
-  end
-
-  let(:featured_tag) { Fabricate :featured_tag }
-
-  context 'when last_status_at is populated' do
-    before do
-      featured_tag.increment(DateTime.new(2024, 11, 28, 16, 20, 0))
-    end
-
-    it 'is serialized as yyyy-mm-dd' do
-      expect(subject['last_status_at']).to eq('2024-11-28')
-    end
-  end
-end
diff --git a/spec/serializers/rest/filter_serializer_spec.rb b/spec/serializers/rest/filter_serializer_spec.rb
deleted file mode 100644
index 4d38a29acb..0000000000
--- a/spec/serializers/rest/filter_serializer_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::FilterSerializer do
-  subject do
-    serialized_record_json(
-      filter,
-      described_class
-    )
-  end
-
-  let(:filter) { Fabricate.build :custom_filter, expires_at: DateTime.new(2024, 11, 28, 16, 20, 0) }
-
-  context 'when expires_at is populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'expires_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/marker_serializer_spec.rb b/spec/serializers/rest/marker_serializer_spec.rb
deleted file mode 100644
index 58266319eb..0000000000
--- a/spec/serializers/rest/marker_serializer_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::MarkerSerializer do
-  subject do
-    serialized_record_json(
-      marker,
-      described_class
-    )
-  end
-
-  let(:marker) { Fabricate :marker }
-
-  context 'when updated_at is populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'updated_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/muted_account_serializer_spec.rb b/spec/serializers/rest/muted_account_serializer_spec.rb
deleted file mode 100644
index 2db3f7be95..0000000000
--- a/spec/serializers/rest/muted_account_serializer_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::MutedAccountSerializer do
-  subject do
-    serialized_record_json(
-      mutee,
-      described_class,
-      options: {
-        scope: current_user,
-        scope_name: :current_user,
-      }
-    )
-  end
-
-  let(:current_user) { Fabricate(:user) }
-  let(:account) { current_user.account }
-  let(:mutee) { Fabricate(:account, username: 'mutee') }
-
-  context 'when mute_expires_at is populated and non-nil' do
-    before do
-      account.follow!(mutee)
-      account.mute!(mutee, duration: 1.day)
-    end
-
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'mute_expires_at' => match_api_datetime_format
-        )
-    end
-  end
-
-  context 'when mute has no duration' do
-    before do
-      account.follow!(mutee)
-      account.mute!(mutee)
-    end
-
-    it 'has a nil mute_expires_at' do
-      expect(subject['mute_expires_at']).to be_nil
-    end
-  end
-end
diff --git a/spec/serializers/rest/notification_group_serializer_spec.rb b/spec/serializers/rest/notification_group_serializer_spec.rb
deleted file mode 100644
index 01fd8ce0a1..0000000000
--- a/spec/serializers/rest/notification_group_serializer_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::NotificationGroupSerializer do
-  subject do
-    serialized_record_json(
-      notification_group,
-      described_class
-    )
-  end
-
-  let(:notification_group) { NotificationGroup.new pagination_data: { latest_notification_at: 3.days.ago }, notification: Fabricate(:notification), sample_accounts: [] }
-
-  context 'when latest_page_notification_at is populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'latest_page_notification_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/notification_request_serializer_spec.rb b/spec/serializers/rest/notification_request_serializer_spec.rb
deleted file mode 100644
index 6dc4b04f5c..0000000000
--- a/spec/serializers/rest/notification_request_serializer_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::NotificationRequestSerializer do
-  subject do
-    serialized_record_json(
-      notification_request,
-      described_class,
-      options: {
-        scope: current_user,
-        scope_name: :current_user,
-      }
-    )
-  end
-
-  let(:current_user) { Fabricate(:user) }
-  let(:notification_request) { Fabricate :notification_request }
-
-  context 'when timestampts are populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'created_at' => match_api_datetime_format,
-          'updated_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/notification_serializer_spec.rb b/spec/serializers/rest/notification_serializer_spec.rb
deleted file mode 100644
index b833bcb6e1..0000000000
--- a/spec/serializers/rest/notification_serializer_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::NotificationSerializer do
-  subject do
-    serialized_record_json(
-      notification,
-      described_class
-    )
-  end
-
-  let(:notification) { Fabricate :notification }
-
-  context 'when created_at is populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'created_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/poll_serializer_spec.rb b/spec/serializers/rest/poll_serializer_spec.rb
deleted file mode 100644
index 3837ac1984..0000000000
--- a/spec/serializers/rest/poll_serializer_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::PollSerializer do
-  subject do
-    serialized_record_json(
-      poll,
-      described_class,
-      options: {
-        scope: current_user,
-        scope_name: :current_user,
-      }
-    )
-  end
-
-  let(:current_user) { Fabricate(:user) }
-  let(:poll) { Fabricate.build :poll, expires_at: 5.days.from_now }
-
-  context 'when expires_at is populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'expires_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/report_serializer_spec.rb b/spec/serializers/rest/report_serializer_spec.rb
deleted file mode 100644
index 180cdbdb68..0000000000
--- a/spec/serializers/rest/report_serializer_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::ReportSerializer do
-  subject do
-    serialized_record_json(
-      report,
-      described_class
-    )
-  end
-
-  context 'with timestamps' do
-    let(:report) { Fabricate(:report, action_taken_at: 3.days.ago) }
-
-    it 'is serialized as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'created_at' => match_api_datetime_format,
-          'action_taken_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/rule_serializer_spec.rb b/spec/serializers/rest/rule_serializer_spec.rb
deleted file mode 100644
index 4d801e77d3..0000000000
--- a/spec/serializers/rest/rule_serializer_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::RuleSerializer do
-  subject { serialized_record_json(record, described_class) }
-
-  let(:record) { Fabricate.build :rule, id: 123 }
-
-  describe 'serialization' do
-    it 'returns expected values' do
-      expect(subject)
-        .to include(
-          'id' => be_a(String).and(eq('123'))
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/scheduled_status_serializer_spec.rb b/spec/serializers/rest/scheduled_status_serializer_spec.rb
deleted file mode 100644
index 2cf0098654..0000000000
--- a/spec/serializers/rest/scheduled_status_serializer_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::ScheduledStatusSerializer do
-  subject do
-    serialized_record_json(
-      scheduled_status,
-      described_class
-    )
-  end
-
-  let(:scheduled_status) { Fabricate.build(:scheduled_status, scheduled_at: 4.minutes.from_now, params: { application_id: 123 }) }
-
-  describe 'serialization' do
-    it 'returns expected values and removes application_id from params' do
-      expect(subject.deep_symbolize_keys)
-        .to include(
-          scheduled_at: be_a(String).and(match_api_datetime_format),
-          params: include(:application_id)
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/status_edit_serializer_spec.rb b/spec/serializers/rest/status_edit_serializer_spec.rb
deleted file mode 100644
index 95590d1f96..0000000000
--- a/spec/serializers/rest/status_edit_serializer_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::StatusEditSerializer do
-  subject do
-    serialized_record_json(
-      status_edit,
-      described_class
-    )
-  end
-
-  let(:status_edit) { Fabricate(:status_edit) }
-
-  context 'when created_at is populated' do
-    it 'parses as RFC 3339 datetime' do
-      expect(subject)
-        .to include(
-          'created_at' => match_api_datetime_format
-        )
-    end
-  end
-end
diff --git a/spec/serializers/rest/status_serializer_spec.rb b/spec/serializers/rest/status_serializer_spec.rb
deleted file mode 100644
index b6908aa414..0000000000
--- a/spec/serializers/rest/status_serializer_spec.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe REST::StatusSerializer do
-  subject do
-    serialized_record_json(
-      status,
-      described_class,
-      options: {
-        scope: current_user,
-        scope_name: :current_user,
-      }
-    )
-  end
-
-  let(:current_user) { Fabricate(:user) }
-  let(:alice) { Fabricate(:account, username: 'alice') }
-  let(:bob)   { Fabricate(:account, username: 'bob', domain: 'other.com') }
-  let(:status) { Fabricate(:status, account: alice) }
-
-  context 'with a remote status' do
-    let(:status) { Fabricate(:status, account: bob) }
-
-    before do
-      status.status_stat.tap do |status_stat|
-        status_stat.reblogs_count = 10
-        status_stat.favourites_count = 20
-        status_stat.save
-      end
-    end
-
-    context 'with only trusted counts' do
-      it 'shows the trusted counts' do
-        expect(subject['reblogs_count']).to eq(10)
-        expect(subject['favourites_count']).to eq(20)
-      end
-    end
-
-    context 'with untrusted counts' do
-      before do
-        status.status_stat.tap do |status_stat|
-          status_stat.untrusted_reblogs_count = 30
-          status_stat.untrusted_favourites_count = 40
-          status_stat.save
-        end
-      end
-
-      it 'shows the untrusted counts' do
-        expect(subject['reblogs_count']).to eq(30)
-        expect(subject['favourites_count']).to eq(40)
-      end
-    end
-
-    context 'with created_at' do
-      it 'is serialized as RFC 3339 datetime' do
-        expect(subject)
-          .to include(
-            'created_at' => match_api_datetime_format
-          )
-      end
-    end
-
-    context 'when edited_at is populated' do
-      let(:status) { Fabricate.build :status, edited_at: 3.days.ago }
-
-      it 'is serialized as RFC 3339 datetime' do
-        expect(subject)
-          .to include(
-            'edited_at' => match_api_datetime_format
-          )
-      end
-    end
-  end
-end
diff --git a/spec/services/activitypub/fetch_all_replies_service_spec.rb b/spec/services/activitypub/fetch_all_replies_service_spec.rb
deleted file mode 100644
index 241c1a8464..0000000000
--- a/spec/services/activitypub/fetch_all_replies_service_spec.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ActivityPub::FetchAllRepliesService do
-  subject { described_class.new }
-
-  let(:actor)          { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
-  let(:status)         { Fabricate(:status, account: actor) }
-  let(:collection_uri) { 'http://example.com/replies/1' }
-
-  let(:items) do
-    %w(
-      http://example.com/self-reply-1
-      http://example.com/self-reply-2
-      http://example.com/self-reply-3
-      http://other.com/other-reply-1
-      http://other.com/other-reply-2
-      http://other.com/other-reply-3
-      http://example.com/self-reply-4
-      http://example.com/self-reply-5
-      http://example.com/self-reply-6
-    )
-  end
-
-  let(:payload) do
-    {
-      '@context': 'https://www.w3.org/ns/activitystreams',
-      type: 'Collection',
-      id: collection_uri,
-      items: items,
-    }.with_indifferent_access
-  end
-
-  describe '#call' do
-    it 'fetches more than the default maximum and from multiple domains' do
-      allow(FetchReplyWorker).to receive(:push_bulk)
-
-      subject.call(status.uri, payload)
-
-      expect(FetchReplyWorker).to have_received(:push_bulk).with(
-        %w(
-          http://example.com/self-reply-1
-          http://example.com/self-reply-2
-          http://example.com/self-reply-3
-          http://other.com/other-reply-1
-          http://other.com/other-reply-2
-          http://other.com/other-reply-3
-          http://example.com/self-reply-4
-          http://example.com/self-reply-5
-          http://example.com/self-reply-6
-        )
-      )
-    end
-
-    context 'with a recent status' do
-      before do
-        Fabricate(:status, uri: 'http://example.com/self-reply-2', fetched_replies_at: 1.second.ago, local: false)
-      end
-
-      it 'skips statuses that have been updated recently' do
-        allow(FetchReplyWorker).to receive(:push_bulk)
-
-        subject.call(status.uri, payload)
-
-        expect(FetchReplyWorker).to have_received(:push_bulk).with(
-          %w(
-            http://example.com/self-reply-1
-            http://example.com/self-reply-3
-            http://other.com/other-reply-1
-            http://other.com/other-reply-2
-            http://other.com/other-reply-3
-            http://example.com/self-reply-4
-            http://example.com/self-reply-5
-            http://example.com/self-reply-6
-          )
-        )
-      end
-    end
-
-    context 'with an old status' do
-      before do
-        Fabricate(:status, uri: 'http://other.com/other-reply-1', fetched_replies_at: 1.year.ago, created_at: 1.year.ago, account: actor)
-      end
-
-      it 'updates the time that fetched statuses were last fetched' do
-        allow(FetchReplyWorker).to receive(:push_bulk)
-
-        subject.call(status.uri, payload)
-
-        expect(Status.find_by(uri: 'http://other.com/other-reply-1').fetched_replies_at).to be >= 1.minute.ago
-      end
-    end
-
-    context 'with unsubscribed replies' do
-      before do
-        remote_actor = Fabricate(:account, domain: 'other.com', uri: 'http://other.com/account')
-        # reply not in the collection from the remote instance, but we know about anyway without anyone following the account
-        Fabricate(:status, account: remote_actor, in_reply_to_id: status.id, uri: 'http://other.com/account/unsubscribed', fetched_replies_at: 1.year.ago, created_at: 1.year.ago)
-      end
-
-      it 'updates the unsubscribed replies' do
-        allow(FetchReplyWorker).to receive(:push_bulk)
-
-        subject.call(status.uri, payload)
-
-        expect(FetchReplyWorker).to have_received(:push_bulk).with(
-          %w(
-            http://example.com/self-reply-1
-            http://example.com/self-reply-2
-            http://example.com/self-reply-3
-            http://other.com/other-reply-1
-            http://other.com/other-reply-2
-            http://other.com/other-reply-3
-            http://example.com/self-reply-4
-            http://example.com/self-reply-5
-            http://example.com/self-reply-6
-            http://other.com/account/unsubscribed
-          )
-        )
-      end
-    end
-  end
-end
diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb
index 2503a58ac2..9d8c6e0e0a 100644
--- a/spec/services/activitypub/fetch_remote_status_service_spec.rb
+++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb
@@ -9,9 +9,6 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
 
   let!(:sender) { Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar') }
 
-  let(:follower) { Fabricate(:account, username: 'alice') }
-  let(:follow) { nil }
-  let(:response) { { body: Oj.dump(object), headers: { 'content-type': 'application/activity+json' } } }
   let(:existing_status) { nil }
 
   let(:note) do
@@ -26,14 +23,13 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
 
   before do
     stub_request(:get, 'https://foo.bar/watch?v=12345').to_return(status: 404, body: '')
-    stub_request(:get, object[:id]).to_return(**response)
+    stub_request(:get, object[:id]).to_return(body: Oj.dump(object))
   end
 
   describe '#call' do
     before do
-      follow
       existing_status
-      subject.call(object[:id])
+      subject.call(object[:id], prefetched_body: Oj.dump(object))
     end
 
     context 'with Note object' do
@@ -258,45 +254,6 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
           expect(existing_status.text).to eq 'Lorem ipsum'
           expect(existing_status.edits).to_not be_empty
         end
-
-        context 'when the status appears to have been deleted at source' do
-          let(:response) { { status: 404, body: '' } }
-
-          shared_examples 'no delete' do
-            it 'does not delete the status' do
-              existing_status.reload
-              expect(existing_status.text).to eq 'Foo'
-              expect(existing_status.edits).to be_empty
-            end
-          end
-
-          context 'when the status is orphaned/unsubscribed' do
-            it 'deletes the orphaned status' do
-              expect { existing_status.reload }.to raise_error(ActiveRecord::RecordNotFound)
-            end
-          end
-
-          context 'when the status is from an account with only remote followers' do
-            let(:follower) { Fabricate(:account, username: 'alice', domain: 'foo.bar') }
-            let(:follow) { Fabricate(:follow, account: follower, target_account: sender, created_at: 2.days.ago) }
-
-            it 'deletes the orphaned status' do
-              expect { existing_status.reload }.to raise_error(ActiveRecord::RecordNotFound)
-            end
-
-            context 'when the status is private' do
-              let(:existing_status) { Fabricate(:status, account: sender, text: 'Foo', uri: note[:id], visibility: :private) }
-
-              it_behaves_like 'no delete'
-            end
-
-            context 'when the status is direct' do
-              let(:existing_status) { Fabricate(:status, account: sender, text: 'Foo', uri: note[:id], visibility: :direct) }
-
-              it_behaves_like 'no delete'
-            end
-          end
-        end
       end
 
       context 'with a Create activity' do
diff --git a/spec/services/activitypub/fetch_replies_service_spec.rb b/spec/services/activitypub/fetch_replies_service_spec.rb
index 36159309f1..e7d8d3528a 100644
--- a/spec/services/activitypub/fetch_replies_service_spec.rb
+++ b/spec/services/activitypub/fetch_replies_service_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
         it 'queues the expected worker' do
           allow(FetchReplyWorker).to receive(:push_bulk)
 
-          subject.call(status.account.uri, payload)
+          subject.call(status, payload)
 
           expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1'])
         end
@@ -50,7 +50,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
         it 'spawns workers for up to 5 replies on the same server' do
           allow(FetchReplyWorker).to receive(:push_bulk)
 
-          subject.call(status.account.uri, payload)
+          subject.call(status, payload)
 
           expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
         end
@@ -64,7 +64,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
         it 'spawns workers for up to 5 replies on the same server' do
           allow(FetchReplyWorker).to receive(:push_bulk)
 
-          subject.call(status.account.uri, collection_uri)
+          subject.call(status, collection_uri)
 
           expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
         end
@@ -85,7 +85,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
         it 'spawns workers for up to 5 replies on the same server' do
           allow(FetchReplyWorker).to receive(:push_bulk)
 
-          subject.call(status.account.uri, payload)
+          subject.call(status, payload)
 
           expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
         end
@@ -99,7 +99,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
         it 'spawns workers for up to 5 replies on the same server' do
           allow(FetchReplyWorker).to receive(:push_bulk)
 
-          subject.call(status.account.uri, collection_uri)
+          subject.call(status, collection_uri)
 
           expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
         end
@@ -124,7 +124,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
         it 'spawns workers for up to 5 replies on the same server' do
           allow(FetchReplyWorker).to receive(:push_bulk)
 
-          subject.call(status.account.uri, payload)
+          subject.call(status, payload)
 
           expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
         end
@@ -138,7 +138,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
         it 'spawns workers for up to 5 replies on the same server' do
           allow(FetchReplyWorker).to receive(:push_bulk)
 
-          subject.call(status.account.uri, collection_uri)
+          subject.call(status, collection_uri)
 
           expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
         end
diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb
index 6d008840be..b55483b016 100644
--- a/spec/services/activitypub/process_status_update_service_spec.rb
+++ b/spec/services/activitypub/process_status_update_service_spec.rb
@@ -7,7 +7,6 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
 
   let(:thread) { nil }
   let!(:status) { Fabricate(:status, text: 'Hello world', account: Fabricate(:account, domain: 'example.com'), thread: thread) }
-  let(:bogus_mention) { 'https://example.com/users/erroringuser' }
   let(:json_tags) do
     [
       { type: 'Hashtag', name: 'hoge' },
@@ -17,6 +16,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
     ]
   end
   let(:content) { 'Hello universe' }
+  let(:bogus_mention) { 'https://example.com/users/erroringuser' }
   let(:payload) do
     {
       '@context': 'https://www.w3.org/ns/activitystreams',
@@ -275,7 +275,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
 
       it 'updates tags and featured tags' do
         expect { subject.call(status, json, json) }
-          .to change { status.tags.reload.pluck(:name) }.from(contain_exactly('test', 'foo')).to(contain_exactly('foo', 'bar'))
+          .to change { status.tags.reload.pluck(:name) }.from(%w(test foo)).to(%w(foo bar))
           .and change { status.account.featured_tags.find_by(name: 'test').statuses_count }.by(-1)
           .and change { status.account.featured_tags.find_by(name: 'bar').statuses_count }.by(1)
           .and change { status.account.featured_tags.find_by(name: 'bar').last_status_at }.from(nil).to(be_present)
diff --git a/spec/services/activitypub/synchronize_followers_service_spec.rb b/spec/services/activitypub/synchronize_followers_service_spec.rb
index 70f27627e1..974368b7d7 100644
--- a/spec/services/activitypub/synchronize_followers_service_spec.rb
+++ b/spec/services/activitypub/synchronize_followers_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do
   let(:bob)            { Fabricate(:account, username: 'bob') }
   let(:eve)            { Fabricate(:account, username: 'eve') }
   let(:mallory)        { Fabricate(:account, username: 'mallory') }
-  let(:collection_uri) { 'https://example.com/partial-followers' }
+  let(:collection_uri) { 'http://example.com/partial-followers' }
 
   let(:items) do
     [alice, eve, mallory].map do |account|
@@ -27,14 +27,14 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do
     }.with_indifferent_access
   end
 
-  before do
-    alice.follow!(actor)
-    bob.follow!(actor)
-    mallory.request_follow!(actor)
-  end
-
   shared_examples 'synchronizes followers' do
     before do
+      alice.follow!(actor)
+      bob.follow!(actor)
+      mallory.request_follow!(actor)
+
+      allow(ActivityPub::DeliveryWorker).to receive(:perform_async)
+
       subject.call(actor, collection_uri)
     end
 
@@ -46,7 +46,7 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do
       expect(mallory)
         .to be_following(actor) # Convert follow request to follow when accepted
       expect(ActivityPub::DeliveryWorker)
-        .to have_enqueued_sidekiq_job(anything, eve.id, actor.inbox_url) # Send Undo Follow to actor
+        .to have_received(:perform_async).with(anything, eve.id, actor.inbox_url) # Send Undo Follow to actor
     end
   end
 
@@ -76,7 +76,7 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do
       it_behaves_like 'synchronizes followers'
     end
 
-    context 'when the endpoint is a single-page paginated Collection of actor URIs' do
+    context 'when the endpoint is a paginated Collection of actor URIs' do
       let(:payload) do
         {
           '@context': 'https://www.w3.org/ns/activitystreams',
@@ -96,106 +96,5 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do
 
       it_behaves_like 'synchronizes followers'
     end
-
-    context 'when the endpoint is a paginated Collection of actor URIs split across multiple pages' do
-      before do
-        stub_request(:get, 'https://example.com/partial-followers')
-          .to_return(status: 200, headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
-            '@context': 'https://www.w3.org/ns/activitystreams',
-            type: 'Collection',
-            id: 'https://example.com/partial-followers',
-            first: 'https://example.com/partial-followers/1',
-          }))
-
-        stub_request(:get, 'https://example.com/partial-followers/1')
-          .to_return(status: 200, headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
-            '@context': 'https://www.w3.org/ns/activitystreams',
-            type: 'CollectionPage',
-            id: 'https://example.com/partial-followers/1',
-            partOf: 'https://example.com/partial-followers',
-            next: 'https://example.com/partial-followers/2',
-            items: [alice, eve].map { |account| ActivityPub::TagManager.instance.uri_for(account) },
-          }))
-
-        stub_request(:get, 'https://example.com/partial-followers/2')
-          .to_return(status: 200, headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
-            '@context': 'https://www.w3.org/ns/activitystreams',
-            type: 'CollectionPage',
-            id: 'https://example.com/partial-followers/2',
-            partOf: 'https://example.com/partial-followers',
-            items: ActivityPub::TagManager.instance.uri_for(mallory),
-          }))
-      end
-
-      it_behaves_like 'synchronizes followers'
-    end
-
-    context 'when the endpoint is a paginated Collection of actor URIs split across, but one page errors out' do
-      before do
-        stub_request(:get, 'https://example.com/partial-followers')
-          .to_return(status: 200, headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
-            '@context': 'https://www.w3.org/ns/activitystreams',
-            type: 'Collection',
-            id: 'https://example.com/partial-followers',
-            first: 'https://example.com/partial-followers/1',
-          }))
-
-        stub_request(:get, 'https://example.com/partial-followers/1')
-          .to_return(status: 200, headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
-            '@context': 'https://www.w3.org/ns/activitystreams',
-            type: 'CollectionPage',
-            id: 'https://example.com/partial-followers/1',
-            partOf: 'https://example.com/partial-followers',
-            next: 'https://example.com/partial-followers/2',
-            items: [mallory].map { |account| ActivityPub::TagManager.instance.uri_for(account) },
-          }))
-
-        stub_request(:get, 'https://example.com/partial-followers/2')
-          .to_return(status: 404)
-      end
-
-      it 'confirms pending follow request but does not remove extra followers' do
-        previous_follower_ids = actor.followers.pluck(:id)
-
-        subject.call(actor, collection_uri)
-
-        expect(previous_follower_ids - actor.followers.reload.pluck(:id))
-          .to be_empty
-        expect(mallory)
-          .to be_following(actor)
-      end
-    end
-
-    context 'when the endpoint is a paginated Collection of actor URIs with more pages than we allow' do
-      let(:payload) do
-        {
-          '@context': 'https://www.w3.org/ns/activitystreams',
-          type: 'Collection',
-          id: collection_uri,
-          first: {
-            type: 'CollectionPage',
-            partOf: collection_uri,
-            items: items,
-            next: "#{collection_uri}/page2",
-          },
-        }.with_indifferent_access
-      end
-
-      before do
-        stub_const('ActivityPub::SynchronizeFollowersService::MAX_COLLECTION_PAGES', 1)
-        stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
-      end
-
-      it 'confirms pending follow request but does not remove extra followers' do
-        previous_follower_ids = actor.followers.pluck(:id)
-
-        subject.call(actor, collection_uri)
-
-        expect(previous_follower_ids - actor.followers.reload.pluck(:id))
-          .to be_empty
-        expect(mallory)
-          .to be_following(actor)
-      end
-    end
   end
 end
diff --git a/spec/services/appeal_service_spec.rb b/spec/services/appeal_service_spec.rb
index a4b1acf9cf..6a47bb2cea 100644
--- a/spec/services/appeal_service_spec.rb
+++ b/spec/services/appeal_service_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe AppealService, :inline_jobs do
   describe '#call' do
-    let!(:admin) { Fabricate(:admin_user) }
+    let!(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     context 'with an existing strike' do
       let(:strike) { Fabricate(:account_warning) }
diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb
new file mode 100644
index 0000000000..0a99c5e748
--- /dev/null
+++ b/spec/services/import_service_spec.rb
@@ -0,0 +1,242 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe ImportService, :inline_jobs do
+  include RoutingHelper
+
+  let!(:account) { Fabricate(:account, locked: false) }
+  let!(:bob)     { Fabricate(:account, username: 'bob', locked: false) }
+  let!(:eve)     { Fabricate(:account, username: 'eve', domain: 'example.com', locked: false, protocol: :activitypub, inbox_url: 'https://example.com/inbox') }
+
+  before do
+    stub_request(:post, 'https://example.com/inbox').to_return(status: 200)
+  end
+
+  context 'when importing old-style list of muted users' do
+    subject { described_class.new }
+
+    let(:csv) { attachment_fixture('mute-imports.txt') }
+
+    describe 'when no accounts are muted' do
+      let(:import) { Import.create(account: account, type: 'muting', data: csv) }
+
+      it 'mutes the listed accounts, including notifications' do
+        subject.call(import)
+        expect(account.muting.count).to eq 2
+        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
+      end
+    end
+
+    describe 'when some accounts are muted and overwrite is not set' do
+      let(:import) { Import.create(account: account, type: 'muting', data: csv) }
+
+      it 'mutes the listed accounts, including notifications' do
+        account.mute!(bob, notifications: false)
+        subject.call(import)
+        expect(account.muting.count).to eq 2
+        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
+      end
+    end
+
+    describe 'when some accounts are muted and overwrite is set' do
+      let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) }
+
+      it 'mutes the listed accounts, including notifications' do
+        account.mute!(bob, notifications: false)
+        subject.call(import)
+        expect(account.muting.count).to eq 2
+        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
+      end
+    end
+  end
+
+  context 'when importing new-style list of muted users' do
+    subject { described_class.new }
+
+    let(:csv) { attachment_fixture('new-mute-imports.txt') }
+
+    describe 'when no accounts are muted' do
+      let(:import) { Import.create(account: account, type: 'muting', data: csv) }
+
+      it 'mutes the listed accounts, respecting notifications' do
+        subject.call(import)
+        expect(account.muting.count).to eq 2
+        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
+        expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
+      end
+    end
+
+    describe 'when some accounts are muted and overwrite is not set' do
+      let(:import) { Import.create(account: account, type: 'muting', data: csv) }
+
+      it 'mutes the listed accounts, respecting notifications' do
+        account.mute!(bob, notifications: true)
+        subject.call(import)
+        expect(account.muting.count).to eq 2
+        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
+        expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
+      end
+    end
+
+    describe 'when some accounts are muted and overwrite is set' do
+      let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) }
+
+      it 'mutes the listed accounts, respecting notifications' do
+        account.mute!(bob, notifications: true)
+        subject.call(import)
+        expect(account.muting.count).to eq 2
+        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
+        expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
+      end
+    end
+  end
+
+  context 'when importing old-style list of followed users' do
+    subject { described_class.new }
+
+    let(:csv) { attachment_fixture('mute-imports.txt') }
+
+    describe 'when no accounts are followed' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv) }
+
+      it 'follows the listed accounts, including boosts' do
+        subject.call(import)
+
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+      end
+    end
+
+    describe 'when some accounts are already followed and overwrite is not set' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv) }
+
+      it 'follows the listed accounts, including notifications' do
+        account.follow!(bob, reblogs: false)
+        subject.call(import)
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+      end
+    end
+
+    describe 'when some accounts are already followed and overwrite is set' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) }
+
+      it 'mutes the listed accounts, including notifications' do
+        account.follow!(bob, reblogs: false)
+        subject.call(import)
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+      end
+    end
+  end
+
+  context 'when importing new-style list of followed users' do
+    subject { described_class.new }
+
+    let(:csv) { attachment_fixture('new-following-imports.txt') }
+
+    describe 'when no accounts are followed' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv) }
+
+      it 'follows the listed accounts, respecting boosts' do
+        subject.call(import)
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+        expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
+      end
+    end
+
+    describe 'when some accounts are already followed and overwrite is not set' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv) }
+
+      it 'mutes the listed accounts, respecting notifications' do
+        account.follow!(bob, reblogs: true)
+        subject.call(import)
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+        expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
+      end
+    end
+
+    describe 'when some accounts are already followed and overwrite is set' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) }
+
+      it 'mutes the listed accounts, respecting notifications' do
+        account.follow!(bob, reblogs: true)
+        subject.call(import)
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+        expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
+      end
+    end
+  end
+
+  # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users
+  #
+  # https://github.com/mastodon/mastodon/issues/20571
+  context 'with a utf-8 encoded domains' do
+    subject { described_class.new }
+
+    let!(:nare) { Fabricate(:account, username: 'nare', domain: 'թութ.հայ', locked: false, protocol: :activitypub, inbox_url: 'https://թութ.հայ/inbox') }
+    let(:csv) { attachment_fixture('utf8-followers.txt') }
+    let(:import) { Import.create(account: account, type: 'following', data: csv) }
+
+    # Make sure to not actually go to the remote server
+    before do
+      stub_request(:post, nare.inbox_url).to_return(status: 200)
+    end
+
+    it 'follows the listed account' do
+      expect(account.follow_requests.count).to eq 0
+      subject.call(import)
+      expect(account.follow_requests.count).to eq 1
+    end
+  end
+
+  context 'when importing bookmarks' do
+    subject { described_class.new }
+
+    let(:csv) { attachment_fixture('bookmark-imports.txt') }
+    let(:local_account)  { Fabricate(:account, username: 'foo', domain: '') }
+    let!(:remote_status) { Fabricate(:status, uri: 'https://example.com/statuses/1312') }
+    let!(:direct_status) { Fabricate(:status, uri: 'https://example.com/statuses/direct', visibility: :direct) }
+
+    around do |example|
+      local_before = Rails.configuration.x.local_domain
+      web_before = Rails.configuration.x.web_domain
+      Rails.configuration.x.local_domain = 'local.com'
+      Rails.configuration.x.web_domain = 'local.com'
+      example.run
+      Rails.configuration.x.web_domain = web_before
+      Rails.configuration.x.local_domain = local_before
+    end
+
+    before do
+      service = instance_double(ActivityPub::FetchRemoteStatusService)
+      allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service)
+      allow(service).to receive(:call).with('https://unknown-remote.com/users/bar/statuses/1') do
+        Fabricate(:status, uri: 'https://unknown-remote.com/users/bar/statuses/1')
+      end
+    end
+
+    describe 'when no bookmarks are set' do
+      let(:import) { Import.create(account: account, type: 'bookmarks', data: csv) }
+
+      it 'adds the toots the user has access to to bookmarks' do
+        local_status = Fabricate(:status, account: local_account, uri: 'https://local.com/users/foo/statuses/42', id: 42, local: true)
+        subject.call(import)
+        expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to include(local_status.id)
+        expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to include(remote_status.id)
+        expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to_not include(direct_status.id)
+        expect(account.bookmarks.count).to eq 3
+      end
+    end
+  end
+end
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index 51982f52df..09a532d6da 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -70,7 +70,7 @@ RSpec.describe PostStatusService do
           subject.call(account, text: 'Hi future!', scheduled_at: invalid_scheduled_time)
         end.to raise_error(
           ActiveRecord::RecordInvalid,
-          'Validation failed: Scheduled at date must be in the future'
+          'Validation failed: Scheduled at The scheduled date must be in the future'
         )
       end
     end
diff --git a/spec/services/precompute_feed_service_spec.rb b/spec/services/precompute_feed_service_spec.rb
index 8019ffadda..858c0c2d3c 100644
--- a/spec/services/precompute_feed_service_spec.rb
+++ b/spec/services/precompute_feed_service_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe PrecomputeFeedService do
         account.request_follow!(requested_account)
         account.mute!(muted_account)
 
-        AddAccountsToListService.new.call(list, [followed_account])
+        list.accounts << followed_account
       end
 
       it "fills a user's home and list timelines with the expected posts" do
diff --git a/spec/services/reblog_service_spec.rb b/spec/services/reblog_service_spec.rb
index ef24362fbc..8f9d3cdfc9 100644
--- a/spec/services/reblog_service_spec.rb
+++ b/spec/services/reblog_service_spec.rb
@@ -109,7 +109,7 @@ RSpec.describe ReblogService do
           Status
             .where(id: reblog_of_id)
             .where(text: 'discard-status-text')
-            .touch_all(:deleted_at)
+            .update_all(deleted_at: Time.now.utc)
         end
       end
     end
diff --git a/spec/services/software_update_check_service_spec.rb b/spec/services/software_update_check_service_spec.rb
index 7f493a0cc8..c864e3120d 100644
--- a/spec/services/software_update_check_service_spec.rb
+++ b/spec/services/software_update_check_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe SoftwareUpdateCheckService do
     let(:full_update_check_url) { "#{update_check_url}?version=#{Mastodon::Version.kmyblue_major}.#{Mastodon::Version.kmyblue_minor}-lts#{Mastodon::Version.dev? ? '-dev' : ''}" }
 
     let(:devops_role)     { Fabricate(:user_role, name: 'DevOps', permissions: UserRole::FLAGS[:view_devops]) }
-    let(:owner_user)      { Fabricate(:owner_user) }
+    let(:owner_user)      { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) }
     let(:old_devops_user) { Fabricate(:user) }
     let(:none_user)       { Fabricate(:user, role: devops_role) }
     let(:patch_user)      { Fabricate(:user, role: devops_role) }
@@ -27,7 +27,6 @@ RSpec.describe SoftwareUpdateCheckService do
     before do
       Fabricate(:software_update, version: '3.5.0', type: 'major', urgent: false)
       Fabricate(:software_update, version: '42.13.12', type: 'major', urgent: false)
-      Fabricate(:software_update, version: 'Malformed', type: 'major', urgent: false)
 
       owner_user.settings.update('notification_emails.software_updates': 'all')
       owner_user.save!
@@ -51,7 +50,7 @@ RSpec.describe SoftwareUpdateCheckService do
       end
 
       it 'deletes outdated update records but keeps valid update records' do
-        expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12', 'Malformed']).to(['42.13.12'])
+        expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12']).to(['42.13.12'])
       end
     end
 
@@ -86,7 +85,7 @@ RSpec.describe SoftwareUpdateCheckService do
       end
 
       it 'updates the list of known updates' do
-        expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12', 'Malformed']).to(['4.2.1', '4.3.0', '5.0.0'])
+        expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12']).to(['4.2.1', '4.3.0', '5.0.0'])
       end
 
       context 'when no update is urgent' do
@@ -125,10 +124,9 @@ RSpec.describe SoftwareUpdateCheckService do
 
   context 'when update checking is disabled' do
     around do |example|
-      original = Rails.configuration.x.mastodon.software_update_url
-      Rails.configuration.x.mastodon.software_update_url = ''
-      example.run
-      Rails.configuration.x.mastodon.software_update_url = original
+      ClimateControl.modify UPDATE_CHECK_URL: '' do
+        example.run
+      end
     end
 
     before do
@@ -150,10 +148,9 @@ RSpec.describe SoftwareUpdateCheckService do
     let(:update_check_url) { 'https://api.example.com/update_check' }
 
     around do |example|
-      original = Rails.configuration.x.mastodon.software_update_url
-      Rails.configuration.x.mastodon.software_update_url = 'https://api.example.com/update_check'
-      example.run
-      Rails.configuration.x.mastodon.software_update_url = original
+      ClimateControl.modify UPDATE_CHECK_URL: 'https://api.example.com/update_check' do
+        example.run
+      end
     end
 
     it_behaves_like 'when the feature is enabled'
diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb
index c15c23ca30..4a2f494e0c 100644
--- a/spec/services/suspend_account_service_spec.rb
+++ b/spec/services/suspend_account_service_spec.rb
@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-RSpec.describe SuspendAccountService do
+RSpec.describe SuspendAccountService, :inline_jobs do
   shared_examples 'common behavior' do
     subject { described_class.new.call(account) }
 
@@ -11,7 +11,6 @@ RSpec.describe SuspendAccountService do
 
     before do
       allow(FeedManager.instance).to receive_messages(unmerge_from_home: nil, unmerge_from_list: nil)
-      allow(Rails.configuration.x).to receive(:cache_buster_enabled).and_return(true)
 
       local_follower.follow!(account)
       list.accounts << account
@@ -24,7 +23,6 @@ RSpec.describe SuspendAccountService do
     it 'unmerges from feeds of local followers and changes file mode and preserves suspended flag' do
       expect { subject }
         .to change_file_mode
-        .and enqueue_sidekiq_job(CacheBusterWorker).with(account.media_attachments.first.file.url(:original))
         .and not_change_suspended_flag
       expect(FeedManager.instance).to have_received(:unmerge_from_home).with(account, local_follower)
       expect(FeedManager.instance).to have_received(:unmerge_from_list).with(account, list)
@@ -40,12 +38,17 @@ RSpec.describe SuspendAccountService do
   end
 
   describe 'suspending a local account' do
-    def match_update_actor_request(json, account)
-      json = JSON.parse(json)
+    def match_update_actor_request(req, account)
+      json = JSON.parse(req.body)
       actor_id = ActivityPub::TagManager.instance.uri_for(account)
       json['type'] == 'Update' && json['actor'] == actor_id && json['object']['id'] == actor_id && json['object']['suspended']
     end
 
+    before do
+      stub_request(:post, 'https://alice.com/inbox').to_return(status: 201)
+      stub_request(:post, 'https://bob.com/inbox').to_return(status: 201)
+    end
+
     include_examples 'common behavior' do
       let!(:account)         { Fabricate(:account) }
       let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub, domain: 'alice.com') }
@@ -58,20 +61,22 @@ RSpec.describe SuspendAccountService do
 
       it 'sends an Update actor activity to followers and reporters' do
         subject
-
-        expect(ActivityPub::DeliveryWorker)
-          .to have_enqueued_sidekiq_job(satisfying { |json| match_update_actor_request(json, account) }, account.id, remote_follower.inbox_url).once
-          .and have_enqueued_sidekiq_job(satisfying { |json| match_update_actor_request(json, account) }, account.id, remote_reporter.inbox_url).once
+        expect(a_request(:post, remote_follower.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
+        expect(a_request(:post, remote_reporter.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
       end
     end
   end
 
   describe 'suspending a remote account' do
-    def match_reject_follow_request(json, account, followee)
-      json = JSON.parse(json)
+    def match_reject_follow_request(req, account, followee)
+      json = JSON.parse(req.body)
       json['type'] == 'Reject' && json['actor'] == ActivityPub::TagManager.instance.uri_for(followee) && json['object']['actor'] == account.uri
     end
 
+    before do
+      stub_request(:post, 'https://bob.com/inbox').to_return(status: 201)
+    end
+
     include_examples 'common behavior' do
       let!(:account)        { Fabricate(:account, domain: 'bob.com', uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
       let!(:local_followee) { Fabricate(:account) }
@@ -83,8 +88,7 @@ RSpec.describe SuspendAccountService do
       it 'sends a Reject Follow activity', :aggregate_failures do
         subject
 
-        expect(ActivityPub::DeliveryWorker)
-          .to have_enqueued_sidekiq_job(satisfying { |json| match_reject_follow_request(json, account, local_followee) }, local_followee.id, account.inbox_url).once
+        expect(a_request(:post, account.inbox_url).with { |req| match_reject_follow_request(req, account, local_followee) }).to have_been_made.once
       end
     end
   end
diff --git a/spec/services/unmute_service_spec.rb b/spec/services/unmute_service_spec.rb
index a052e0dd0a..92c7a70d65 100644
--- a/spec/services/unmute_service_spec.rb
+++ b/spec/services/unmute_service_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe UnmuteService do
         it 'removes the account mute and sets up a merge' do
           expect { subject.call(account, target_account) }
             .to remove_account_mute
-          expect(MergeWorker).to have_enqueued_sidekiq_job(target_account.id, account.id, 'home')
+          expect(MergeWorker).to have_enqueued_sidekiq_job(target_account.id, account.id)
         end
       end
 
diff --git a/spec/services/update_status_expiration_service_spec.rb b/spec/services/update_status_expiration_service_spec.rb
deleted file mode 100644
index b4612849c4..0000000000
--- a/spec/services/update_status_expiration_service_spec.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe UpdateStatusExpirationService do
-  subject { described_class.new.call(status) }
-
-  let(:status) { Fabricate(:status, text: text) }
-
-  before { travel_to '2023-01-01T00:00:00Z' }
-
-  shared_examples 'set expire date' do |offset|
-    it 'set expire date' do
-      subject
-      expect(ScheduledExpirationStatus.where(status: status).count).to eq 1
-      expect(ScheduledExpirationStatus.exists?(scheduled_at: Time.now.utc + offset, status: status)).to be true
-    end
-  end
-
-  shared_examples 'did not set expire date' do
-    it 'did not set expire date' do
-      subject
-      expect(ScheduledExpirationStatus.exists?(status: status)).to be false
-    end
-  end
-
-  context 'when 30 minutes' do
-    let(:text) { 'ohagi #exp30m' }
-
-    it_behaves_like 'set expire date', 30.minutes
-  end
-
-  context 'when 1 hour' do
-    let(:text) { 'ohagi #exp1h' }
-
-    it_behaves_like 'set expire date', 1.hour
-  end
-
-  context 'with multiple tags' do
-    let(:text) { 'ohagi #exp3h #exp1d' }
-
-    it_behaves_like 'set expire date', 3.hours
-  end
-
-  context 'when too long hours' do
-    let(:text) { 'ohagi #exp9999999999h' }
-
-    it_behaves_like 'did not set expire date'
-  end
-
-  context 'without tags' do
-    let(:text) { 'ohagi is ohagi' }
-
-    it_behaves_like 'did not set expire date'
-  end
-
-  context 'when update status text' do
-    before do
-      Fabricate(:scheduled_expiration_status, account: status.account, status: status)
-    end
-
-    context 'with new date' do
-      let(:text) { 'ohagi #exp1h' }
-
-      it_behaves_like 'set expire date', 1.hour
-    end
-
-    context 'without new date' do
-      let(:text) { 'ohagi' }
-
-      it_behaves_like 'did not set expire date'
-    end
-  end
-end
diff --git a/spec/services/verify_link_service_spec.rb b/spec/services/verify_link_service_spec.rb
index 7e2f9607cf..a4fd19751b 100644
--- a/spec/services/verify_link_service_spec.rb
+++ b/spec/services/verify_link_service_spec.rb
@@ -46,21 +46,6 @@ RSpec.describe VerifyLinkService do
       end
     end
 
-    context 'when a link contains an <a rel=ME> back' do
-      let(:html) do
-        <<~HTML
-          <!doctype html>
-          <body>
-            <a href="#{ActivityPub::TagManager.instance.url_for(account)}" rel=ME>Follow me on Mastodon</a>
-          </body>
-        HTML
-      end
-
-      it 'marks the field as verified' do
-        expect(field.verified?).to be true
-      end
-    end
-
     context 'when a link contains a <link> back' do
       let(:html) do
         <<~HTML
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 13683e404e..2a27544407 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -34,8 +34,8 @@ RSpec.configure do |config|
   end
 end
 
-def serialized_record_json(record, serializer, adapter: nil, options: {})
-  options[:serializer] = serializer
+def serialized_record_json(record, serializer, adapter: nil)
+  options = { serializer: serializer }
   options[:adapter] = adapter if adapter.present?
   JSON.parse(
     ActiveModelSerializers::SerializableResource.new(
diff --git a/spec/support/command_line_helpers.rb b/spec/support/command_line_helpers.rb
index 09b2b70ba1..6f9d63d939 100644
--- a/spec/support/command_line_helpers.rb
+++ b/spec/support/command_line_helpers.rb
@@ -1,9 +1,9 @@
 # frozen_string_literal: true
 
 module CommandLineHelpers
-  def output_results(*)
+  def output_results(*args)
     output(
-      include(*)
+      include(*args)
     ).to_stdout
   end
 end
diff --git a/spec/support/domain_helpers.rb b/spec/support/domain_helpers.rb
index 051cdaa7d2..9977702099 100644
--- a/spec/support/domain_helpers.rb
+++ b/spec/support/domain_helpers.rb
@@ -28,25 +28,6 @@ module DomainHelpers
       .and_yield(resolver)
   end
 
-  def configure_dns(domain:, results:)
-    resolver = instance_double(Resolv::DNS, :timeouts= => nil)
-
-    allow(resolver).to receive(:getresources)
-      .with(domain, Resolv::DNS::Resource::IN::MX)
-      .and_return(results)
-    allow(resolver)
-      .to receive(:getresources)
-      .with(domain, Resolv::DNS::Resource::IN::A)
-      .and_return(results)
-    allow(resolver)
-      .to receive(:getresources)
-      .with(domain, Resolv::DNS::Resource::IN::AAAA)
-      .and_return(results)
-    allow(Resolv::DNS)
-      .to receive(:open)
-      .and_yield(resolver)
-  end
-
   private
 
   def double_mx(exchange)
diff --git a/spec/support/examples/models/concerns/account/search.rb b/spec/support/examples/models/concerns/account/search.rb
deleted file mode 100644
index 4fcf4759c5..0000000000
--- a/spec/support/examples/models/concerns/account/search.rb
+++ /dev/null
@@ -1,295 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'Account::Search' do
-  describe '.search_for' do
-    before do
-      _missing = Fabricate(
-        :account,
-        display_name: 'Missing',
-        username: 'missing',
-        domain: 'missing.com'
-      )
-    end
-
-    it 'does not return suspended users' do
-      Fabricate(
-        :account,
-        display_name: 'Display Name',
-        username: 'username',
-        domain: 'example.com',
-        suspended: true
-      )
-
-      results = described_class.search_for('username')
-      expect(results).to eq []
-    end
-
-    it 'does not return unapproved users' do
-      match = Fabricate(
-        :account,
-        display_name: 'Display Name',
-        username: 'username'
-      )
-
-      match.user.update(approved: false)
-
-      results = described_class.search_for('username')
-      expect(results).to eq []
-    end
-
-    it 'does not return unconfirmed users' do
-      match = Fabricate(
-        :account,
-        display_name: 'Display Name',
-        username: 'username'
-      )
-
-      match.user.update(confirmed_at: nil)
-
-      results = described_class.search_for('username')
-      expect(results).to eq []
-    end
-
-    it 'accepts ?, \, : and space as delimiter' do
-      match = Fabricate(
-        :account,
-        display_name: 'A & l & i & c & e',
-        username: 'username',
-        domain: 'example.com'
-      )
-
-      results = described_class.search_for('A?l\i:c e')
-      expect(results).to eq [match]
-    end
-
-    it 'finds accounts with matching display_name' do
-      match = Fabricate(
-        :account,
-        display_name: 'Display Name',
-        username: 'username',
-        domain: 'example.com'
-      )
-
-      results = described_class.search_for('display')
-      expect(results).to eq [match]
-    end
-
-    it 'finds accounts with matching username' do
-      match = Fabricate(
-        :account,
-        display_name: 'Display Name',
-        username: 'username',
-        domain: 'example.com'
-      )
-
-      results = described_class.search_for('username')
-      expect(results).to eq [match]
-    end
-
-    it 'finds accounts with matching domain' do
-      match = Fabricate(
-        :account,
-        display_name: 'Display Name',
-        username: 'username',
-        domain: 'example.com'
-      )
-
-      results = described_class.search_for('example')
-      expect(results).to eq [match]
-    end
-
-    it 'limits via constant by default' do
-      stub_const('Account::Search::DEFAULT_LIMIT', 1)
-      2.times.each { Fabricate(:account, display_name: 'Display Name') }
-      results = described_class.search_for('display')
-      expect(results.size).to eq 1
-    end
-
-    it 'accepts arbitrary limits' do
-      2.times.each { Fabricate(:account, display_name: 'Display Name') }
-      results = described_class.search_for('display', limit: 1)
-      expect(results.size).to eq 1
-    end
-
-    it 'ranks multiple matches higher' do
-      matches = [
-        { username: 'username', display_name: 'username' },
-        { display_name: 'Display Name', username: 'username', domain: 'example.com' },
-      ].map(&method(:Fabricate).curry(2).call(:account))
-
-      results = described_class.search_for('username')
-      expect(results).to eq matches
-    end
-  end
-
-  describe '.advanced_search_for' do
-    let(:account) { Fabricate(:account) }
-
-    context 'when limiting search to followed accounts' do
-      it 'accepts ?, \, : and space as delimiter' do
-        match = Fabricate(
-          :account,
-          display_name: 'A & l & i & c & e',
-          username: 'username',
-          domain: 'example.com'
-        )
-        account.follow!(match)
-
-        results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
-        expect(results).to eq [match]
-      end
-
-      it 'does not return non-followed accounts' do
-        Fabricate(
-          :account,
-          display_name: 'A & l & i & c & e',
-          username: 'username',
-          domain: 'example.com'
-        )
-
-        results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
-        expect(results).to eq []
-      end
-
-      it 'does not return suspended users' do
-        Fabricate(
-          :account,
-          display_name: 'Display Name',
-          username: 'username',
-          domain: 'example.com',
-          suspended: true
-        )
-
-        results = described_class.advanced_search_for('username', account, limit: 10, following: true)
-        expect(results).to eq []
-      end
-
-      it 'does not return unapproved users' do
-        match = Fabricate(
-          :account,
-          display_name: 'Display Name',
-          username: 'username'
-        )
-
-        match.user.update(approved: false)
-
-        results = described_class.advanced_search_for('username', account, limit: 10, following: true)
-        expect(results).to eq []
-      end
-
-      it 'does not return unconfirmed users' do
-        match = Fabricate(
-          :account,
-          display_name: 'Display Name',
-          username: 'username'
-        )
-
-        match.user.update(confirmed_at: nil)
-
-        results = described_class.advanced_search_for('username', account, limit: 10, following: true)
-        expect(results).to eq []
-      end
-    end
-
-    context 'when limiting search to follower accounts' do
-      it 'accepts ?, \, : and space as delimiter' do
-        match = Fabricate(
-          :account,
-          display_name: 'A & l & i & c & e',
-          username: 'username',
-          domain: 'example.com'
-        )
-        match.follow!(account)
-
-        results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, follower: true)
-        expect(results).to eq [match]
-      end
-
-      it 'does not return non-follower accounts' do
-        Fabricate(
-          :account,
-          display_name: 'A & l & i & c & e',
-          username: 'username',
-          domain: 'example.com'
-        )
-
-        results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, follower: true)
-        expect(results).to eq []
-      end
-    end
-
-    it 'does not return suspended users' do
-      Fabricate(
-        :account,
-        display_name: 'Display Name',
-        username: 'username',
-        domain: 'example.com',
-        suspended: true
-      )
-
-      results = described_class.advanced_search_for('username', account)
-      expect(results).to eq []
-    end
-
-    it 'does not return unapproved users' do
-      match = Fabricate(
-        :account,
-        display_name: 'Display Name',
-        username: 'username'
-      )
-
-      match.user.update(approved: false)
-
-      results = described_class.advanced_search_for('username', account)
-      expect(results).to eq []
-    end
-
-    it 'does not return unconfirmed users' do
-      match = Fabricate(
-        :account,
-        display_name: 'Display Name',
-        username: 'username'
-      )
-
-      match.user.update(confirmed_at: nil)
-
-      results = described_class.advanced_search_for('username', account)
-      expect(results).to eq []
-    end
-
-    it 'accepts ?, \, : and space as delimiter' do
-      match = Fabricate(
-        :account,
-        display_name: 'A & l & i & c & e',
-        username: 'username',
-        domain: 'example.com'
-      )
-
-      results = described_class.advanced_search_for('A?l\i:c e', account)
-      expect(results).to eq [match]
-    end
-
-    it 'limits result count by default value' do
-      stub_const('Account::Search::DEFAULT_LIMIT', 1)
-      2.times { Fabricate(:account, display_name: 'Display Name') }
-      results = described_class.advanced_search_for('display', account)
-      expect(results.size).to eq 1
-    end
-
-    it 'accepts arbitrary limits' do
-      2.times { Fabricate(:account, display_name: 'Display Name') }
-      results = described_class.advanced_search_for('display', account, limit: 1)
-      expect(results.size).to eq 1
-    end
-
-    it 'ranks followed accounts higher' do
-      match = Fabricate(:account, username: 'Matching')
-      followed_match = Fabricate(:account, username: 'Matcher')
-      Fabricate(:follow, account: account, target_account: followed_match)
-
-      results = described_class.advanced_search_for('match', account)
-      expect(results).to eq [followed_match, match]
-      expect(results.first.rank).to be > results.last.rank
-    end
-  end
-end
diff --git a/spec/support/examples/models/concerns/account_avatar.rb b/spec/support/examples/models/concerns/account_avatar.rb
index 01614578b3..c6cc4e75a5 100644
--- a/spec/support/examples/models/concerns/account_avatar.rb
+++ b/spec/support/examples/models/concerns/account_avatar.rb
@@ -24,15 +24,6 @@ RSpec.shared_examples 'AccountAvatar' do |fabricator|
     end
   end
 
-  describe 'convertable avatars', :attachment_processing do
-    describe 'with AVIF' do
-      it 'creates a jpeg static style' do
-        account = Fabricate(fabricator, avatar: attachment_fixture('avatar.avif'))
-        expect(account.avatar_original_url.end_with?('.jpeg')).to be true
-      end
-    end
-  end
-
   describe 'base64-encoded files', :attachment_processing do
     let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" }
     let(:account) { Fabricate(fabricator, avatar: base64_attachment) }
diff --git a/spec/support/examples/models/concerns/browser_detection.rb b/spec/support/examples/models/concerns/browser_detection.rb
deleted file mode 100644
index e80fa4595c..0000000000
--- a/spec/support/examples/models/concerns/browser_detection.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'BrowserDetection' do
-  subject { described_class.new(user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Safari/605.1.15') }
-
-  describe '#detection' do
-    it 'sets a Browser instance as detection' do
-      expect(subject.detection)
-        .to be_a(Browser::Safari)
-    end
-  end
-
-  describe '#browser' do
-    it 'returns browser name from id' do
-      expect(subject.browser)
-        .to eq(:safari)
-    end
-  end
-
-  describe '#platform' do
-    it 'returns detected platform' do
-      expect(subject.platform)
-        .to eq(:mac)
-    end
-  end
-
-  describe 'Callbacks' do
-    describe 'populating the user_agent value' do
-      subject { Fabricate.build described_class.name.underscore.to_sym, user_agent: nil }
-
-      it 'changes nil to empty string' do
-        expect { subject.save }
-          .to change(subject, :user_agent).from(nil).to('')
-      end
-    end
-  end
-end
diff --git a/spec/support/examples/models/concerns/expireable.rb b/spec/support/examples/models/concerns/expireable.rb
deleted file mode 100644
index 098b98375a..0000000000
--- a/spec/support/examples/models/concerns/expireable.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'Expireable' do
-  subject { described_class.new(expires_at: expires_at) }
-
-  let(:expires_at) { nil }
-
-  describe 'Scopes' do
-    let!(:expired_record) do
-      travel_to 2.days.ago do
-        Fabricate factory_name, expires_at: 1.day.from_now
-      end
-    end
-    let!(:un_expired_record) { Fabricate factory_name, expires_at: 10.days.from_now }
-
-    describe '.expired' do
-      it 'returns expired records' do
-        expect(described_class.expired)
-          .to include(expired_record)
-          .and not_include(un_expired_record)
-      end
-    end
-  end
-
-  describe '#expires_in' do
-    context 'when expires at is nil' do
-      let(:expires_at) { nil }
-
-      it 'returns nil' do
-        expect(subject.expires_in)
-          .to be_nil
-      end
-    end
-  end
-
-  describe '#expires_in=' do
-    let(:record) { Fabricate.build factory_name }
-
-    context 'when set to nil' do
-      it 'sets expires_at to nil' do
-        record.expires_in = nil
-        expect(record.expires_at)
-          .to be_nil
-      end
-    end
-
-    context 'when set to empty' do
-      it 'sets expires_at to nil' do
-        record.expires_in = ''
-        expect(record.expires_at)
-          .to be_nil
-      end
-    end
-
-    context 'when set to a value' do
-      it 'sets expires_at to expected future time' do
-        record.expires_in = 60
-        expect(record.expires_at)
-          .to be_within(0.1).of(60.seconds.from_now)
-      end
-    end
-  end
-
-  describe '#expired?' do
-    context 'when expires_at is nil' do
-      let(:expires_at) { nil }
-
-      it { is_expected.to_not be_expired }
-    end
-
-    context 'when expires_at is in the past' do
-      let(:expires_at) { 5.days.ago }
-
-      it { is_expected.to be_expired }
-    end
-
-    context 'when expires_at is in the future' do
-      let(:expires_at) { 5.days.from_now }
-
-      it { is_expected.to_not be_expired }
-    end
-
-    describe '#expire!' do
-      subject { Fabricate factory_name }
-
-      it 'updates the timestamp' do
-        expect { subject.expire! }
-          .to change(subject, :expires_at)
-      end
-    end
-
-    describe '#expires?' do
-      context 'when value is missing' do
-        let(:expires_at) { nil }
-
-        it { is_expected.to_not be_expires }
-      end
-
-      context 'when value is present' do
-        let(:expires_at) { 3.days.from_now }
-
-        it { is_expected.to be_expires }
-      end
-    end
-  end
-
-  def factory_name
-    described_class.name.underscore.to_sym
-  end
-end
diff --git a/spec/support/examples/models/concerns/ranked_trend.rb b/spec/support/examples/models/concerns/ranked_trend.rb
deleted file mode 100644
index 827165cc83..0000000000
--- a/spec/support/examples/models/concerns/ranked_trend.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'RankedTrend' do
-  describe 'Scopes' do
-    describe '.by_rank' do
-      let!(:lower_rank) { Fabricate factory_name, rank: 5 }
-      let!(:higher_rank) { Fabricate factory_name, rank: 50 }
-
-      it 'returns records ordered by rank' do
-        expect(described_class.by_rank)
-          .to eq([higher_rank, lower_rank])
-      end
-    end
-
-    describe '.ranked_below' do
-      let!(:low_rank) { Fabricate factory_name, rank: 5 }
-      let!(:med_rank) { Fabricate factory_name, rank: 50 }
-      let!(:high_rank) { Fabricate factory_name, rank: 500 }
-
-      it 'returns records ordered by rank' do
-        expect(described_class.ranked_below(100))
-          .to include(low_rank)
-          .and include(med_rank)
-          .and not_include(high_rank)
-      end
-    end
-  end
-
-  describe '.locales' do
-    before do
-      Fabricate.times 2, factory_name, language: 'en'
-      Fabricate factory_name, language: 'es'
-    end
-
-    it 'returns unique set of languages' do
-      expect(described_class.locales)
-        .to eq(['en', 'es'])
-    end
-  end
-
-  describe '.recalculate_ordered_rank' do
-    let!(:low_score) { Fabricate factory_name, score: 5, rank: 123 }
-    let!(:high_score) { Fabricate factory_name, score: 10, rank: 456 }
-
-    it 'ranks records based on their score' do
-      expect { described_class.recalculate_ordered_rank }
-        .to change { low_score.reload.rank }.to(2)
-        .and change { high_score.reload.rank }.to(1)
-    end
-  end
-
-  def factory_name
-    described_class.name.underscore.to_sym
-  end
-end
diff --git a/spec/support/examples/models/concerns/status/visibility.rb b/spec/support/examples/models/concerns/status/visibility.rb
deleted file mode 100644
index e9b97dbf79..0000000000
--- a/spec/support/examples/models/concerns/status/visibility.rb
+++ /dev/null
@@ -1,326 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.shared_examples 'Status::Visibility' do
-  describe 'Validations' do
-    context 'when status is a reblog' do
-      subject { Fabricate.build :status, reblog: Fabricate(:status) }
-
-      it { is_expected.to allow_values('public', 'unlisted', 'private').for(:visibility) }
-      it { is_expected.to_not allow_values('direct', 'limited').for(:visibility) }
-    end
-
-    context 'when status is not reblog' do
-      subject { Fabricate.build :status, reblog_of_id: nil }
-
-      it { is_expected.to allow_values('public', 'unlisted', 'private', 'direct', 'limited').for(:visibility) }
-    end
-  end
-
-  describe 'Scopes' do
-    let!(:direct_status) { Fabricate :status, visibility: :direct }
-    let!(:limited_status) { Fabricate :status, visibility: :limited }
-    let!(:private_status) { Fabricate :status, visibility: :private }
-    let!(:public_status) { Fabricate :status, visibility: :public }
-    let!(:unlisted_status) { Fabricate :status, visibility: :unlisted }
-
-    describe '.list_eligible_visibility' do
-      it 'returns appropriate records' do
-        expect(Status.list_eligible_visibility)
-          .to include(
-            private_status,
-            public_status,
-            unlisted_status
-          )
-          .and not_include(direct_status)
-          .and not_include(limited_status)
-      end
-    end
-
-    describe '.distributable_visibility' do
-      it 'returns appropriate records' do
-        expect(Status.distributable_visibility)
-          .to include(
-            public_status,
-            unlisted_status
-          )
-          .and not_include(private_status)
-          .and not_include(direct_status)
-          .and not_include(limited_status)
-      end
-    end
-
-    describe '.not_direct_visibility' do
-      it 'returns appropriate records' do
-        expect(Status.not_direct_visibility)
-          .to include(
-            limited_status,
-            private_status,
-            public_status,
-            unlisted_status
-          )
-          .and not_include(direct_status)
-      end
-    end
-  end
-
-  describe 'Callbacks' do
-    describe 'Setting visibility in before validation' do
-      subject { Fabricate.build :status, visibility: nil }
-
-      context 'when explicit value is set' do
-        before { subject.visibility = :public }
-
-        it 'does not change' do
-          expect { subject.valid? }
-            .to_not change(subject, :visibility)
-        end
-      end
-
-      context 'when status is a reblog' do
-        before { subject.reblog = Fabricate(:status, visibility: :public) }
-
-        it 'changes to match the reblog' do
-          expect { subject.valid? }
-            .to change(subject, :visibility).to('public')
-        end
-      end
-
-      context 'when account is locked' do
-        before { subject.account = Fabricate.build(:account, locked: true) }
-
-        it 'changes to private' do
-          expect { subject.valid? }
-            .to change(subject, :visibility).to('private')
-        end
-      end
-
-      context 'when account is not locked' do
-        before { subject.account = Fabricate.build(:account, locked: false) }
-
-        it 'changes to public' do
-          expect { subject.valid? }
-            .to change(subject, :visibility).to('public')
-        end
-      end
-    end
-  end
-
-  describe '.selectable_visibilities' do
-    it 'returns options available for default privacy selection' do
-      expect(Status.selectable_visibilities)
-        .to match(%w(public public_unlisted login unlisted private))
-    end
-  end
-
-  describe '#hidden?' do
-    subject { Status.new }
-
-    context 'when visibility is private' do
-      before { subject.visibility = :private }
-
-      it { is_expected.to be_hidden }
-    end
-
-    context 'when visibility is direct' do
-      before { subject.visibility = :direct }
-
-      it { is_expected.to be_hidden }
-    end
-
-    context 'when visibility is limited' do
-      before { subject.visibility = :limited }
-
-      it { is_expected.to be_hidden }
-    end
-
-    context 'when visibility is public' do
-      before { subject.visibility = :public }
-
-      it { is_expected.to_not be_hidden }
-    end
-
-    context 'when visibility is unlisted' do
-      before { subject.visibility = :unlisted }
-
-      it { is_expected.to_not be_hidden }
-    end
-  end
-
-  describe '#distributable?' do
-    subject { Status.new }
-
-    context 'when visibility is public' do
-      before { subject.visibility = :public }
-
-      it { is_expected.to be_distributable }
-    end
-
-    context 'when visibility is unlisted' do
-      before { subject.visibility = :unlisted }
-
-      it { is_expected.to be_distributable }
-    end
-
-    context 'when visibility is private' do
-      before { subject.visibility = :private }
-
-      it { is_expected.to_not be_distributable }
-    end
-
-    context 'when visibility is direct' do
-      before { subject.visibility = :direct }
-
-      it { is_expected.to_not be_distributable }
-    end
-
-    context 'when visibility is limited' do
-      before { subject.visibility = :limited }
-
-      it { is_expected.to_not be_distributable }
-    end
-  end
-
-  describe '#compute_searchability' do
-    subject { Fabricate(:status, account: account, searchability: status_searchability) }
-
-    let(:account_searchability) { :public }
-    let(:status_searchability) { :public }
-    let(:account_domain) { 'example.com' }
-    let(:silenced_at) { nil }
-    let(:account) { Fabricate(:account, domain: account_domain, searchability: account_searchability, silenced_at: silenced_at) }
-
-    context 'when public-public' do
-      it 'returns public' do
-        expect(subject.compute_searchability).to eq 'public'
-      end
-    end
-
-    context 'when public-public but silenced' do
-      let(:silenced_at) { Time.now.utc }
-
-      it 'returns private' do
-        expect(subject.compute_searchability).to eq 'private'
-      end
-    end
-
-    context 'when public-public_unlisted but silenced' do
-      let(:silenced_at) { Time.now.utc }
-      let(:status_searchability) { :public_unlisted }
-
-      it 'returns private' do
-        expect(subject.compute_searchability).to eq 'private'
-      end
-    end
-
-    context 'when public-public_unlisted' do
-      let(:status_searchability) { :public_unlisted }
-
-      it 'returns public' do
-        expect(subject.compute_searchability).to eq 'public'
-      end
-
-      it 'returns public_unlisted for local' do
-        expect(subject.compute_searchability_local).to eq 'public_unlisted'
-      end
-    end
-
-    context 'when public-private' do
-      let(:status_searchability) { :private }
-
-      it 'returns private' do
-        expect(subject.compute_searchability).to eq 'private'
-      end
-    end
-
-    context 'when public-direct' do
-      let(:status_searchability) { :direct }
-
-      it 'returns direct' do
-        expect(subject.compute_searchability).to eq 'direct'
-      end
-    end
-
-    context 'when private-public' do
-      let(:account_searchability) { :private }
-
-      it 'returns private' do
-        expect(subject.compute_searchability).to eq 'private'
-      end
-    end
-
-    context 'when direct-public' do
-      let(:account_searchability) { :direct }
-
-      it 'returns direct' do
-        expect(subject.compute_searchability).to eq 'direct'
-      end
-    end
-
-    context 'when limited-public' do
-      let(:account_searchability) { :limited }
-
-      it 'returns limited' do
-        expect(subject.compute_searchability).to eq 'limited'
-      end
-    end
-
-    context 'when private-limited' do
-      let(:account_searchability) { :private }
-      let(:status_searchability) { :limited }
-
-      it 'returns limited' do
-        expect(subject.compute_searchability).to eq 'limited'
-      end
-    end
-
-    context 'when private-public of local account' do
-      let(:account_searchability) { :private }
-      let(:account_domain) { nil }
-      let(:status_searchability) { :public }
-
-      it 'returns public' do
-        expect(subject.compute_searchability).to eq 'public'
-      end
-    end
-
-    context 'when direct-public of local account' do
-      let(:account_searchability) { :direct }
-      let(:account_domain) { nil }
-      let(:status_searchability) { :public }
-
-      it 'returns public' do
-        expect(subject.compute_searchability).to eq 'public'
-      end
-    end
-
-    context 'when limited-public of local account' do
-      let(:account_searchability) { :limited }
-      let(:account_domain) { nil }
-      let(:status_searchability) { :public }
-
-      it 'returns public' do
-        expect(subject.compute_searchability).to eq 'public'
-      end
-    end
-
-    context 'when public-public_unlisted of local account' do
-      let(:account_searchability) { :public }
-      let(:account_domain) { nil }
-      let(:status_searchability) { :public_unlisted }
-
-      it 'returns public' do
-        expect(subject.compute_searchability).to eq 'public'
-      end
-
-      it 'returns public_unlisted for local' do
-        expect(subject.compute_searchability_local).to eq 'public_unlisted'
-      end
-
-      it 'returns private for activitypub' do
-        expect(subject.compute_searchability_activitypub).to eq 'private'
-      end
-    end
-  end
-end
diff --git a/spec/support/fasp/provider_request_helper.rb b/spec/support/fasp/provider_request_helper.rb
deleted file mode 100644
index c5d8ae4919..0000000000
--- a/spec/support/fasp/provider_request_helper.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-module ProviderRequestHelper
-  private
-
-  def stub_provider_request(provider, path: '/', method: :get, response_status: 200, response_body: '')
-    response_body = encode_body(response_body)
-    response_headers = {
-      'content-type' => 'application/json',
-    }.merge(response_authentication_headers(provider, response_status, response_body))
-
-    stub_request(method, provider.url(path))
-      .to_return do |_request|
-        {
-          status: response_status,
-          body: response_body,
-          headers: response_headers,
-        }
-      end
-  end
-
-  def request_authentication_headers(provider, url: root_url, method: :get, body: '')
-    body = encode_body(body)
-    headers = {}
-    headers['content-digest'] = content_digest(body)
-    request = Linzer.new_request(method, url, {}, headers)
-    key = private_key_for(provider)
-    signature = sign(request, key, %w(@method @target-uri content-digest))
-    headers.merge(signature.to_h)
-  end
-
-  def response_authentication_headers(provider, status, body)
-    headers = {}
-    headers['content-digest'] = content_digest(body)
-    response = Linzer.new_response(body, status, headers)
-    key = private_key_for(provider)
-    signature = sign(response, key, %w(@status content-digest))
-    headers.merge(signature.to_h)
-  end
-
-  def private_key_for(provider)
-    @cached_provider_keys ||= {}
-    @cached_provider_keys[provider] ||=
-      begin
-        key = OpenSSL::PKey.generate_key('ed25519')
-        provider.update!(provider_public_key_pem: key.public_to_pem)
-        key
-      end
-
-    {
-      id: provider.id.to_s,
-      private_key: @cached_provider_keys[provider].private_to_pem,
-    }
-  end
-
-  def sign(request_or_response, key, components)
-    message = Linzer::Message.new(request_or_response)
-    linzer_key = Linzer.new_ed25519_key(key[:private_key], key[:id])
-    Linzer.sign(linzer_key, message, components)
-  end
-
-  def encode_body(body)
-    return body if body.nil? || body.is_a?(String)
-
-    encoder = ActionDispatch::RequestEncoder.encoder(:json)
-    encoder.encode_params(body)
-  end
-
-  def content_digest(content)
-    "sha-256=:#{OpenSSL::Digest.base64digest('sha256', content)}:"
-  end
-end
diff --git a/spec/support/feature_flags.rb b/spec/support/feature_flags.rb
deleted file mode 100644
index 186711163b..0000000000
--- a/spec/support/feature_flags.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.configure do |config|
-  config.before(:example, :feature) do |example|
-    feature = example.metadata[:feature]
-    allow(Mastodon::Feature).to receive(:"#{feature}_enabled?").and_return(true)
-  end
-end
diff --git a/spec/support/matchers/api_datetime_format.rb b/spec/support/matchers/api_datetime_format.rb
deleted file mode 100644
index d19c909438..0000000000
--- a/spec/support/matchers/api_datetime_format.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-RSpec::Matchers.define :match_api_datetime_format do
-  match(notify_expectation_failures: true) do |value|
-    expect { DateTime.rfc3339(value) }
-      .to_not raise_error
-  end
-end
diff --git a/spec/support/matchers/model/model_have_error_on_field.rb b/spec/support/matchers/model/model_have_error_on_field.rb
new file mode 100644
index 0000000000..0f9c81a475
--- /dev/null
+++ b/spec/support/matchers/model/model_have_error_on_field.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :model_have_error_on_field do |expected|
+  match do |record|
+    record.valid? if record.errors.empty?
+
+    record.errors.key?(expected)
+  end
+
+  failure_message do |record|
+    keys = record.errors.attribute_names
+
+    "expect record.errors(#{keys}) to include #{expected}"
+  end
+end
diff --git a/spec/support/response_encoders.rb b/spec/support/response_encoders.rb
deleted file mode 100644
index 548dba7132..0000000000
--- a/spec/support/response_encoders.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-ActionDispatch::IntegrationTest
-  .register_encoder :xml, response_parser: ->(body) { Nokogiri::XML(body) }
diff --git a/spec/support/signed_request_helpers.rb b/spec/support/signed_request_helpers.rb
index a4423af748..8a52179cae 100644
--- a/spec/support/signed_request_helpers.rb
+++ b/spec/support/signed_request_helpers.rb
@@ -18,24 +18,4 @@ module SignedRequestHelpers
 
     super(path, headers: headers, **args)
   end
-
-  def post(path, headers: nil, sign_with: nil, **args)
-    return super(path, headers: headers, **args) if sign_with.nil?
-
-    headers ||= {}
-    headers['Date'] = Time.now.utc.httpdate
-    headers['Host'] = Rails.configuration.x.local_domain
-    headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest(args[:params].to_s)}"
-
-    signed_headers = headers.merge('(request-target)' => "post #{path}").slice('(request-target)', 'Host', 'Date', 'Digest')
-
-    key_id = ActivityPub::TagManager.instance.key_uri_for(sign_with)
-    keypair = sign_with.keypair
-    signed_string = signed_headers.map { |key, value| "#{key.downcase}: #{value}" }.join("\n")
-    signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string))
-
-    headers['Signature'] = "keyId=\"#{key_id}\",algorithm=\"rsa-sha256\",headers=\"#{signed_headers.keys.join(' ').downcase}\",signature=\"#{signature}\""
-
-    super(path, headers: headers, **args)
-  end
 end
diff --git a/spec/support/stories/profile_stories.rb b/spec/support/stories/profile_stories.rb
index 1861e84434..fd07f409f7 100644
--- a/spec/support/stories/profile_stories.rb
+++ b/spec/support/stories/profile_stories.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 module ProfileStories
-  attr_reader :bob
+  attr_reader :bob, :alice, :alice_bio
 
   def fill_in_auth_details(email, password)
     fill_in 'user_email', with: email
@@ -23,11 +23,7 @@ module ProfileStories
   def as_a_logged_in_user
     as_a_registered_user
     visit new_user_session_path
-    expect(page)
-      .to have_title(I18n.t('auth.login'))
     fill_in_auth_details(email, password)
-    expect(page)
-      .to have_css('.app-holder')
   end
 
   def as_a_logged_in_admin
@@ -36,6 +32,18 @@ module ProfileStories
     bob.update!(role: UserRole.find_by!(name: 'Admin'))
   end
 
+  def with_alice_as_local_user
+    @alice_bio = '@alice and @bob are fictional characters commonly used as' \
+                 'placeholder names in #cryptology, as well as #science and' \
+                 'engineering 📖 literature. Not affiliated with @pepe.'
+
+    @alice = Fabricate(
+      :user,
+      email: 'alice@example.com', password: password, confirmed_at: confirmed_at,
+      account: Fabricate(:account, username: 'alice', note: @alice_bio)
+    )
+  end
+
   def confirmed_at
     @confirmed_at ||= Time.zone.now
   end
diff --git a/spec/support/system_helpers.rb b/spec/support/system_helpers.rb
index 44bbc64a59..4cc1928701 100644
--- a/spec/support/system_helpers.rb
+++ b/spec/support/system_helpers.rb
@@ -1,7 +1,9 @@
 # frozen_string_literal: true
 
 module SystemHelpers
-  FRONTEND_TRANSLATIONS = JSON.parse Rails.root.join('app', 'javascript', 'mastodon', 'locales', 'en.json').read
+  def admin_user
+    Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
+  end
 
   def submit_button
     I18n.t('generic.save_changes')
@@ -18,8 +20,4 @@ module SystemHelpers
   def css_id(record)
     "##{dom_id(record)}"
   end
-
-  def frontend_translations(key)
-    FRONTEND_TRANSLATIONS[key]
-  end
 end
diff --git a/spec/system/about_spec.rb b/spec/system/about_spec.rb
index d7fd7f51a1..f832802f91 100644
--- a/spec/system/about_spec.rb
+++ b/spec/system/about_spec.rb
@@ -8,6 +8,5 @@ RSpec.describe 'About page' do
 
     expect(page)
       .to have_css('noscript', text: /Mastodon/)
-      .and have_css('body', class: 'app-body')
   end
 end
diff --git a/spec/system/account_notes_spec.rb b/spec/system/account_notes_spec.rb
deleted file mode 100644
index 1d125e1984..0000000000
--- a/spec/system/account_notes_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Account notes', :inline_jobs, :js, :streaming do
-  include ProfileStories
-
-  let(:email)               { 'test@example.com' }
-  let(:password)            { 'password' }
-  let(:confirmed_at)        { Time.zone.now }
-  let(:finished_onboarding) { true }
-
-  let!(:other_account) { Fabricate(:account) }
-
-  before { as_a_logged_in_user }
-
-  it 'can be written and viewed' do
-    visit_profile(other_account)
-
-    note_text = 'This is a personal note'
-    fill_in frontend_translations('account_note.placeholder'), with: note_text
-
-    # This is a bit awkward since there is no button to save the change
-    # The easiest way is to send ctrl+enter ourselves
-    find_field(class: 'account__header__account-note__content').send_keys [:control, :enter]
-
-    expect(page)
-      .to have_css('.account__header__account-note__content', text: note_text)
-
-    # Navigate back and forth and ensure the comment is still here
-    visit root_url
-    visit_profile(other_account)
-
-    expect(AccountNote.find_by(account: bob.account, target_account: other_account).comment)
-      .to eq note_text
-
-    expect(page)
-      .to have_css('.account__header__account-note__content', text: note_text)
-  end
-
-  def visit_profile(account)
-    visit short_account_path(account)
-
-    expect(page)
-      .to have_css('div.app-holder')
-      .and have_css('form.compose-form')
-  end
-end
diff --git a/spec/system/admin/account_actions_spec.rb b/spec/system/admin/account_actions_spec.rb
deleted file mode 100644
index 787b988a0d..0000000000
--- a/spec/system/admin/account_actions_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Account Actions' do
-  let(:user) { Fabricate(:admin_user) }
-
-  before { sign_in user }
-
-  describe 'Creating a new account action on an account' do
-    let(:account) { Fabricate(:account) }
-
-    it 'creates the action and redirects to the account page' do
-      visit new_admin_account_action_path(account_id: account.id)
-      expect(page)
-        .to have_title(I18n.t('admin.account_actions.title', acct: account.pretty_acct))
-
-      choose(option: 'silence')
-      expect { submit_form }
-        .to change { account.strikes.count }.by(1)
-      expect(page)
-        .to have_title(account.pretty_acct)
-    end
-
-    def submit_form
-      click_on I18n.t('admin.account_actions.action')
-    end
-  end
-end
diff --git a/spec/system/admin/account_moderation_notes_spec.rb b/spec/system/admin/account_moderation_notes_spec.rb
deleted file mode 100644
index fa930cea2c..0000000000
--- a/spec/system/admin/account_moderation_notes_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin::AccountModerationNotes' do
-  let(:current_user) { Fabricate(:admin_user) }
-  let(:target_account) { Fabricate(:account) }
-
-  before { sign_in current_user }
-
-  describe 'Managing account moderation note' do
-    it 'saves and then deletes a record' do
-      visit admin_account_path(target_account.id)
-
-      fill_in 'account_moderation_note_content', with: ''
-      expect { submit_form }
-        .to not_change(AccountModerationNote, :count)
-      expect(page)
-        .to have_content(/error below/)
-
-      fill_in 'account_moderation_note_content', with: 'Test message'
-      expect { submit_form }
-        .to change(AccountModerationNote, :count).by(1)
-      expect(page)
-        .to have_content(I18n.t('admin.account_moderation_notes.created_msg'))
-
-      expect { delete_note }
-        .to change(AccountModerationNote, :count).by(-1)
-      expect(page)
-        .to have_content(I18n.t('admin.account_moderation_notes.destroyed_msg'))
-    end
-
-    def submit_form
-      click_on I18n.t('admin.account_moderation_notes.create')
-    end
-
-    def delete_note
-      within('.report-notes__item__actions') do
-        click_on I18n.t('admin.reports.notes.delete')
-      end
-    end
-  end
-end
diff --git a/spec/system/admin/accounts_spec.rb b/spec/system/admin/accounts_spec.rb
index 30504ce5d7..c21e01e4f3 100644
--- a/spec/system/admin/accounts_spec.rb
+++ b/spec/system/admin/accounts_spec.rb
@@ -3,7 +3,7 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Accounts' do
-  let(:current_user) { Fabricate(:admin_user) }
+  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
   before do
     sign_in current_user
diff --git a/spec/system/admin/announcements/distributions_spec.rb b/spec/system/admin/announcements/distributions_spec.rb
deleted file mode 100644
index a503d466b0..0000000000
--- a/spec/system/admin/announcements/distributions_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Announcement Mail Distributions' do
-  let(:user) { Fabricate(:admin_user) }
-  let(:announcement) { Fabricate(:announcement, notification_sent_at: nil) }
-
-  before { sign_in(user) }
-
-  describe 'Sending an announcement notification', :inline_jobs do
-    it 'marks the announcement as notified and sends the email' do
-      visit admin_announcement_preview_path(announcement)
-      expect(page)
-        .to have_title(I18n.t('admin.announcements.preview.title'))
-
-      emails = capture_emails do
-        expect { click_on I18n.t('admin.terms_of_service.preview.send_to_all', count: 1, display_count: 1) }
-          .to(change { announcement.reload.notification_sent_at })
-      end
-      expect(emails.first)
-        .to be_present
-        .and(deliver_to(user.email))
-      expect(page)
-        .to have_title(I18n.t('admin.announcements.title'))
-    end
-  end
-end
diff --git a/spec/system/admin/announcements/previews_spec.rb b/spec/system/admin/announcements/previews_spec.rb
deleted file mode 100644
index 0c9a931cb2..0000000000
--- a/spec/system/admin/announcements/previews_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Announcements Mail Previews' do
-  let(:admin_user) { Fabricate(:admin_user) }
-  let(:announcement) { Fabricate(:announcement, notification_sent_at: nil) }
-
-  before { sign_in(admin_user) }
-
-  describe 'Viewing Announcements Mail previews' do
-    it 'shows the Announcement Mail preview page' do
-      visit admin_announcement_preview_path(announcement)
-
-      expect(page)
-        .to have_title(I18n.t('admin.announcements.preview.title'))
-    end
-  end
-end
diff --git a/spec/system/admin/announcements/tests_spec.rb b/spec/system/admin/announcements/tests_spec.rb
deleted file mode 100644
index 00767c06e8..0000000000
--- a/spec/system/admin/announcements/tests_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin TermsOfService Tests' do
-  let(:user) { Fabricate(:admin_user) }
-  let(:announcement) { Fabricate(:announcement, notification_sent_at: nil) }
-
-  before { sign_in(user) }
-
-  describe 'Sending test Announcement email', :inline_jobs do
-    it 'generates the test email' do
-      visit admin_announcement_preview_path(announcement)
-      expect(page)
-        .to have_title(I18n.t('admin.announcements.preview.title'))
-
-      emails = capture_emails { click_on I18n.t('admin.terms_of_service.preview.send_preview', email: user.email) }
-      expect(emails.first)
-        .to be_present
-        .and(deliver_to(user.email))
-      expect(page)
-        .to have_title(I18n.t('admin.announcements.title'))
-    end
-  end
-end
diff --git a/spec/system/admin/announcements_spec.rb b/spec/system/admin/announcements_spec.rb
index c1cb00cd84..87b7332639 100644
--- a/spec/system/admin/announcements_spec.rb
+++ b/spec/system/admin/announcements_spec.rb
@@ -117,10 +117,10 @@ RSpec.describe 'Admin::Announcements' do
   end
 
   def text_label
-    form_label('announcement.text')
+    I18n.t('simple_form.labels.announcement.text')
   end
 
   def admin_user
-    Fabricate(:admin_user)
+    Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
   end
 end
diff --git a/spec/system/admin/change_emails_spec.rb b/spec/system/admin/change_emails_spec.rb
deleted file mode 100644
index 6592ddff7c..0000000000
--- a/spec/system/admin/change_emails_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Change Emails' do
-  let(:admin) { Fabricate(:admin_user) }
-
-  before { sign_in admin }
-
-  describe 'Changing the email address for a user', :inline_jobs do
-    let(:user) { Fabricate :user }
-
-    it 'updates user details and sends email' do
-      visit admin_account_change_email_path(user.account.id)
-      expect(page)
-        .to have_title(I18n.t('admin.accounts.change_email.title', username: user.account.username))
-
-      fill_in 'user_unconfirmed_email', with: 'test@host.example'
-      emails = capture_emails { process_change_email }
-      expect(emails.first)
-        .to be_present
-        .and(deliver_to('test@host.example'))
-        .and(have_subject(/Confirm email/))
-      expect(page)
-        .to have_title(user.account.pretty_acct)
-    end
-
-    def process_change_email
-      expect { click_on I18n.t('admin.accounts.change_email.submit') }
-        .to not_change { user.reload.email }
-        .and(change { user.reload.unconfirmed_email }.to('test@host.example'))
-        .and(change { user.reload.confirmation_token }.from(nil).to(be_present))
-    end
-  end
-end
diff --git a/spec/system/admin/custom_emojis_spec.rb b/spec/system/admin/custom_emojis_spec.rb
index 1d54aa4d5e..e47f21f8a9 100644
--- a/spec/system/admin/custom_emojis_spec.rb
+++ b/spec/system/admin/custom_emojis_spec.rb
@@ -3,45 +3,10 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::CustomEmojis' do
-  let(:current_user) { Fabricate(:admin_user) }
+  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
-  before { sign_in current_user }
-
-  describe 'Listing existing emoji' do
-    let!(:custom_emoji) { Fabricate :custom_emoji }
-
-    it 'Shows records' do
-      visit admin_custom_emojis_path
-
-      expect(page)
-        .to have_content(I18n.t('admin.custom_emojis.title'))
-        .and have_content(custom_emoji.shortcode)
-    end
-  end
-
-  describe 'Creating a new emoji' do
-    it 'saves a new emoji record with valid attributes' do
-      visit new_admin_custom_emoji_path
-      expect(page)
-        .to have_content(I18n.t('admin.custom_emojis.title'))
-
-      expect { submit_form }
-        .to_not change(CustomEmoji, :count)
-      expect(page)
-        .to have_content(/errors below/)
-
-      fill_in I18n.t('admin.custom_emojis.shortcode'),
-              with: 'test'
-      attach_file 'custom_emoji_image',
-                  Rails.root.join('spec', 'fixtures', 'files', 'emojo.png')
-
-      expect { submit_form }
-        .to change(CustomEmoji, :count).by(1)
-    end
-
-    def submit_form
-      click_on I18n.t('admin.custom_emojis.upload')
-    end
+  before do
+    sign_in current_user
   end
 
   describe 'Performing batch updates' do
diff --git a/spec/system/admin/dashboard_spec.rb b/spec/system/admin/dashboard_spec.rb
deleted file mode 100644
index 06d31cde44..0000000000
--- a/spec/system/admin/dashboard_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Dashboard' do
-  describe 'Viewing the dashboard page' do
-    let(:user) { Fabricate(:owner_user) }
-
-    before do
-      stub_system_checks
-      Fabricate :software_update
-      sign_in(user)
-    end
-
-    it 'returns page with system check messages' do
-      visit admin_dashboard_path
-
-      expect(page)
-        .to have_title(I18n.t('admin.dashboard.title'))
-        .and have_content(I18n.t('admin.system_checks.software_version_patch_check.message_html'))
-    end
-
-    private
-
-    def stub_system_checks
-      stub_const 'Admin::SystemCheck::ACTIVE_CHECKS', [
-        Admin::SystemCheck::SoftwareVersionCheck,
-      ]
-    end
-  end
-end
diff --git a/spec/system/admin/domain_allows_spec.rb b/spec/system/admin/domain_allows_spec.rb
deleted file mode 100644
index bacbb53eac..0000000000
--- a/spec/system/admin/domain_allows_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin::DomainAllows' do
-  let(:user) { Fabricate(:admin_user) }
-  let(:domain) { 'host.example' }
-
-  before do
-    Fabricate :account, domain: domain
-    Instance.refresh
-    sign_in user
-  end
-
-  around do |example|
-    original = Rails.configuration.x.limited_federation_mode
-    Rails.configuration.x.limited_federation_mode = true
-
-    example.run
-
-    Rails.configuration.x.limited_federation_mode = original
-  end
-
-  describe 'Managing domain allows' do
-    it 'saves and then deletes a record' do
-      # Visit new page
-      visit new_admin_domain_allow_path
-      click_on I18n.t('admin.domain_allows.add_new')
-      expect(page)
-        .to have_content(I18n.t('admin.domain_allows.add_new'))
-
-      # Submit invalid with missing domain
-      fill_in 'domain_allow_domain', with: ''
-      expect { submit_form }
-        .to not_change(DomainAllow, :count)
-      expect(page)
-        .to have_content(/error below/)
-
-      # Submit valid with domain present
-      fill_in 'domain_allow_domain', with: domain
-      expect { submit_form }
-        .to change(DomainAllow, :count).by(1)
-      expect(page)
-        .to have_content(I18n.t('admin.domain_allows.created_msg'))
-
-      # Visit instance page and delete the domain allow
-      visit admin_instance_path(domain)
-      expect { delete_domain_allow }
-        .to change(DomainAllow, :count).by(-1)
-      expect(page)
-        .to have_content(I18n.t('admin.domain_allows.destroyed_msg'))
-    end
-
-    def submit_form
-      click_on I18n.t('admin.domain_allows.add_new')
-    end
-
-    def delete_domain_allow
-      click_on I18n.t('admin.domain_allows.undo')
-    end
-  end
-end
diff --git a/spec/system/admin/domain_blocks_spec.rb b/spec/system/admin/domain_blocks_spec.rb
index 56a5d11984..f00d65dfe0 100644
--- a/spec/system/admin/domain_blocks_spec.rb
+++ b/spec/system/admin/domain_blocks_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe 'blocking domains through the moderation interface' do
   before do
     allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
-    sign_in Fabricate(:admin_user), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   context 'when silencing a new domain' do
diff --git a/spec/system/admin/email_domain_blocks_spec.rb b/spec/system/admin/email_domain_blocks_spec.rb
index e88811ac49..a90bede827 100644
--- a/spec/system/admin/email_domain_blocks_spec.rb
+++ b/spec/system/admin/email_domain_blocks_spec.rb
@@ -3,45 +3,10 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::EmailDomainBlocks' do
-  let(:current_user) { Fabricate(:admin_user) }
+  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
-  before { sign_in current_user }
-
-  describe 'Managing email domain blocks' do
-    before { configure_dns(domain: 'example.com', results: []) }
-
-    let!(:email_domain_block) { Fabricate :email_domain_block }
-
-    it 'views and creates new blocks' do
-      visit admin_email_domain_blocks_path
-      expect(page)
-        .to have_content(I18n.t('admin.email_domain_blocks.title'))
-        .and have_content(email_domain_block.domain)
-
-      click_on I18n.t('admin.email_domain_blocks.add_new')
-      expect(page)
-        .to have_content(I18n.t('admin.email_domain_blocks.new.title'))
-
-      fill_in I18n.t('admin.email_domain_blocks.domain'), with: 'example.com'
-      expect { submit_resolve }
-        .to_not change(EmailDomainBlock, :count)
-      expect(page)
-        .to have_content(I18n.t('admin.email_domain_blocks.new.title'))
-
-      expect { submit_create }
-        .to change(EmailDomainBlock.where(domain: 'example.com'), :count).by(1)
-      expect(page)
-        .to have_content(I18n.t('admin.email_domain_blocks.title'))
-        .and have_content(I18n.t('admin.email_domain_blocks.created_msg'))
-    end
-
-    def submit_resolve
-      click_on I18n.t('admin.email_domain_blocks.new.resolve')
-    end
-
-    def submit_create
-      click_on I18n.t('admin.email_domain_blocks.new.create')
-    end
+  before do
+    sign_in current_user
   end
 
   describe 'Performing batch updates' do
@@ -57,27 +22,6 @@ RSpec.describe 'Admin::EmailDomainBlocks' do
       end
     end
 
-    context 'with a selected block' do
-      let!(:email_domain_block) { Fabricate :email_domain_block }
-
-      it 'deletes the block' do
-        visit admin_email_domain_blocks_path
-
-        check_item
-
-        expect { click_on button_for_delete }
-          .to change(EmailDomainBlock, :count).by(-1)
-        expect { email_domain_block.reload }
-          .to raise_error(ActiveRecord::RecordNotFound)
-      end
-    end
-
-    def check_item
-      within '.batch-table__row' do
-        find('input[type=checkbox]').check
-      end
-    end
-
     def button_for_delete
       I18n.t('admin.email_domain_blocks.delete')
     end
diff --git a/spec/system/admin/fasp/debug/callbacks_spec.rb b/spec/system/admin/fasp/debug/callbacks_spec.rb
deleted file mode 100644
index 0e47aac677..0000000000
--- a/spec/system/admin/fasp/debug/callbacks_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Debug FASP Callback Management', feature: :fasp do
-  before { sign_in Fabricate(:admin_user) }
-
-  describe 'Viewing and deleting callbacks' do
-    let(:provider) { Fabricate(:fasp_provider, name: 'debug prov') }
-
-    before do
-      Fabricate(:fasp_debug_callback, fasp_provider: provider, request_body: 'called back')
-    end
-
-    it 'displays callbacks and allows to delete them' do
-      visit admin_fasp_debug_callbacks_path
-
-      expect(page).to have_css('h2', text: I18n.t('admin.fasp.debug.callbacks.title'))
-      expect(page).to have_css('td', text: 'debug prov')
-      expect(page).to have_css('code', text: 'called back')
-
-      expect do
-        click_on I18n.t('admin.fasp.debug.callbacks.delete')
-
-        expect(page).to have_css('h2', text: I18n.t('admin.fasp.debug.callbacks.title'))
-      end.to change(Fasp::DebugCallback, :count).by(-1)
-    end
-  end
-end
diff --git a/spec/system/admin/fasp/debug_calls_spec.rb b/spec/system/admin/fasp/debug_calls_spec.rb
deleted file mode 100644
index d2f6a3a08b..0000000000
--- a/spec/system/admin/fasp/debug_calls_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'FASP Debug Calls', feature: :fasp do
-  include ProviderRequestHelper
-
-  before { sign_in Fabricate(:admin_user) }
-
-  describe 'Triggering a FASP debug call' do
-    let!(:provider) { Fabricate(:debug_fasp) }
-    let!(:debug_call) do
-      stub_provider_request(provider,
-                            method: :post,
-                            path: '/debug/v0/callback/logs',
-                            response_status: 201)
-    end
-
-    it 'makes a debug call to the provider' do
-      visit admin_fasp_providers_path
-
-      expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
-      expect(page).to have_css('td', text: provider.name)
-
-      within 'table#providers' do
-        click_on I18n.t('admin.fasp.providers.callback')
-      end
-
-      expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
-      expect(debug_call).to have_been_requested
-    end
-  end
-end
diff --git a/spec/system/admin/fasp/providers_spec.rb b/spec/system/admin/fasp/providers_spec.rb
deleted file mode 100644
index 03837ad5d9..0000000000
--- a/spec/system/admin/fasp/providers_spec.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'FASP Management', feature: :fasp do
-  include ProviderRequestHelper
-
-  before { sign_in Fabricate(:admin_user) }
-
-  describe 'Managing capabilities' do
-    let!(:provider) { Fabricate(:confirmed_fasp) }
-    let!(:enable_call) do
-      stub_provider_request(provider,
-                            method: :post,
-                            path: '/capabilities/callback/0/activation')
-    end
-    let!(:disable_call) do
-      stub_provider_request(provider,
-                            method: :delete,
-                            path: '/capabilities/callback/0/activation')
-    end
-
-    before do
-      # We currently err on the side of caution and prefer to send
-      # a "disable capability" call too often over risking to miss
-      # one. So the following call _can_ happen here, and if it does
-      # that is fine, but it has no bearing on the behavior that is
-      # being tested.
-      stub_provider_request(provider,
-                            method: :delete,
-                            path: '/capabilities/data_sharing/0/activation')
-    end
-
-    it 'allows enabling and disabling of capabilities' do
-      visit admin_fasp_providers_path
-
-      expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
-      expect(page).to have_css('td', text: provider.name)
-
-      click_on I18n.t('admin.fasp.providers.edit')
-
-      expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.edit'))
-
-      check 'callback'
-
-      click_on I18n.t('admin.fasp.providers.save')
-
-      expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
-      expect(provider.reload).to be_capability_enabled('callback')
-      expect(enable_call).to have_been_requested
-
-      click_on I18n.t('admin.fasp.providers.edit')
-
-      expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.edit'))
-
-      uncheck 'callback'
-
-      click_on I18n.t('admin.fasp.providers.save')
-
-      expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
-      expect(provider.reload).to_not be_capability_enabled('callback')
-      expect(disable_call).to have_been_requested
-    end
-  end
-
-  describe 'Removing a provider' do
-    let!(:provider) { Fabricate(:fasp_provider) }
-
-    it 'allows to completely remove a provider' do
-      visit admin_fasp_providers_path
-
-      expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
-      expect(page).to have_css('td', text: provider.name)
-
-      click_on I18n.t('admin.fasp.providers.delete')
-
-      expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
-      expect(page).to have_no_css('td', text: provider.name)
-    end
-  end
-end
diff --git a/spec/system/admin/fasp/registrations_spec.rb b/spec/system/admin/fasp/registrations_spec.rb
deleted file mode 100644
index 3da6f01915..0000000000
--- a/spec/system/admin/fasp/registrations_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'FASP registration', feature: :fasp do
-  include ProviderRequestHelper
-
-  before { sign_in Fabricate(:admin_user) }
-
-  describe 'Confirming an unconfirmed FASP' do
-    let(:provider) { Fabricate(:fasp_provider, confirmed: false) }
-
-    before do
-      stub_provider_request(provider,
-                            path: '/provider_info',
-                            response_body: {
-                              capabilities: [
-                                { id: 'debug', version: '0.1' },
-                              ],
-                              contactEmail: 'newcontact@example.com',
-                              fediverseAccount: '@newfedi@social.example.com',
-                              privacyPolicy: 'https::///example.com/privacy',
-                              signInUrl: 'https://myprov.example.com/sign_in',
-                            })
-    end
-
-    it 'displays key fingerprint and updates the provider on confirmation' do
-      visit new_admin_fasp_provider_registration_path(provider)
-
-      expect(page).to have_css('code', text: provider.provider_public_key_fingerprint)
-
-      click_on I18n.t('admin.fasp.providers.registrations.confirm')
-
-      expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.edit'))
-
-      expect(provider.reload).to be_confirmed
-    end
-  end
-end
diff --git a/spec/system/admin/follow_recommendations_spec.rb b/spec/system/admin/follow_recommendations_spec.rb
deleted file mode 100644
index 141a0f8152..0000000000
--- a/spec/system/admin/follow_recommendations_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Follow Recommendations' do
-  let(:user) { Fabricate(:admin_user) }
-
-  before { sign_in(user) }
-
-  describe 'Viewing follow recommendations details' do
-    it 'shows a list of accounts' do
-      visit admin_follow_recommendations_path
-
-      expect(page)
-        .to have_content(I18n.t('admin.follow_recommendations.title'))
-    end
-  end
-end
diff --git a/spec/system/admin/invites_spec.rb b/spec/system/admin/invites_spec.rb
index 53300c1ad0..f2cee626c6 100644
--- a/spec/system/admin/invites_spec.rb
+++ b/spec/system/admin/invites_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe 'Admin Invites' do
   describe 'Invite interaction' do
     let!(:invite) { Fabricate(:invite, expires_at: nil) }
 
-    let(:user) { Fabricate(:admin_user) }
+    let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     before { sign_in user }
 
@@ -57,7 +57,7 @@ RSpec.describe 'Admin Invites' do
     end
 
     def max_use_field
-      form_label('defaults.max_uses')
+      I18n.t('simple_form.labels.defaults.max_uses')
     end
   end
 end
diff --git a/spec/system/admin/ip_blocks_spec.rb b/spec/system/admin/ip_blocks_spec.rb
index 3bed506b68..9c03520277 100644
--- a/spec/system/admin/ip_blocks_spec.rb
+++ b/spec/system/admin/ip_blocks_spec.rb
@@ -3,69 +3,22 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::IpBlocks' do
-  let(:current_user) { Fabricate(:admin_user) }
+  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
-  before { sign_in current_user }
-
-  describe 'Creating an IP Block' do
-    it 'lists blocks and creates new ones' do
-      # Visit index page
-      visit admin_ip_blocks_path
-      expect(page)
-        .to have_content(I18n.t('admin.ip_blocks.title'))
-
-      # Navigate to new
-      click_on I18n.t('admin.ip_blocks.add_new')
-
-      # Invalid with missing IP
-      fill_in 'ip_block_ip', with: ''
-      expect { submit_form }
-        .to_not change(IpBlock, :count)
-      expect(page)
-        .to have_content(/error below/)
-
-      # Valid with IP
-      fill_in 'ip_block_ip', with: '192.168.1.1'
-      expect { submit_form }
-        .to change(IpBlock, :count).by(1)
-      expect(page)
-        .to have_content(I18n.t('admin.ip_blocks.created_msg'))
-    end
-
-    def submit_form
-      click_on I18n.t('admin.ip_blocks.add_new')
-    end
+  before do
+    sign_in current_user
   end
 
   describe 'Performing batch updates' do
+    before do
+      visit admin_ip_blocks_path
+    end
+
     context 'without selecting any records' do
       it 'displays a notice about selection' do
-        visit admin_ip_blocks_path
-
         click_on button_for_delete
-        expect(page)
-          .to have_content(selection_error_text)
-      end
-    end
 
-    context 'with a selected block' do
-      let!(:ip_block) { Fabricate :ip_block }
-
-      it 'deletes the block' do
-        visit admin_ip_blocks_path
-
-        check_item
-
-        expect { click_on button_for_delete }
-          .to change(IpBlock, :count).by(-1)
-        expect { ip_block.reload }
-          .to raise_error(ActiveRecord::RecordNotFound)
-      end
-    end
-
-    def check_item
-      within '.batch-table__row' do
-        find('input[type=checkbox]').check
+        expect(page).to have_content(selection_error_text)
       end
     end
 
diff --git a/spec/system/admin/relationships_spec.rb b/spec/system/admin/relationships_spec.rb
deleted file mode 100644
index ba90fe156e..0000000000
--- a/spec/system/admin/relationships_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Relationships' do
-  let(:admin_user) { Fabricate(:admin_user) }
-
-  before { sign_in(admin_user) }
-
-  describe 'Viewing account relationships page' do
-    let(:account) { Fabricate(:account) }
-
-    it 'shows page with relationships for account' do
-      visit admin_account_relationships_path(account.id)
-
-      expect(page)
-        .to have_title(I18n.t('admin.relationships.title', acct: account.pretty_acct))
-    end
-  end
-end
diff --git a/spec/system/admin/relays_spec.rb b/spec/system/admin/relays_spec.rb
deleted file mode 100644
index a5b92a4d0d..0000000000
--- a/spec/system/admin/relays_spec.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Relays' do
-  describe 'Managing relays' do
-    before { sign_in Fabricate(:admin_user) }
-
-    describe 'Viewing relays' do
-      let!(:relay) { Fabricate :relay }
-
-      it 'lists existing records' do
-        visit admin_relays_path
-
-        expect(page)
-          .to have_content(I18n.t('admin.relays.title'))
-          .and have_content(relay.inbox_url)
-      end
-    end
-
-    describe 'Creating a new relay' do
-      it 'creates new record with valid attributes' do
-        visit admin_relays_path
-
-        # Visit new page
-        click_on I18n.t('admin.relays.setup')
-        expect(page)
-          .to have_content(I18n.t('admin.relays.add_new'))
-
-        # Invalid submission
-        fill_in 'relay_inbox_url', with: ''
-        expect { submit_form }
-          .to_not change(Relay, :count)
-        expect(page)
-          .to have_content(/errors below/)
-
-        # Valid submission
-        fill_in 'relay_inbox_url', with: 'https://host.example/hooks/123'
-        expect { submit_form }
-          .to change(Relay, :count).by(1)
-        expect(page)
-          .to have_content(I18n.t('admin.relays.title'))
-      end
-
-      def submit_form
-        click_on I18n.t('admin.relays.save_and_enable')
-      end
-    end
-
-    describe 'Destroy a relay' do
-      let!(:relay) { Fabricate :relay }
-
-      it 'removes the record' do
-        visit admin_relays_path
-
-        expect { click_on I18n.t('admin.relays.delete') }
-          .to change(Relay, :count).by(-1)
-        expect { relay.reload }
-          .to raise_error(ActiveRecord::RecordNotFound)
-      end
-    end
-
-    describe 'Toggle state of relay' do
-      let!(:relay) { Fabricate :relay, state: :accepted }
-
-      it 'switches state as requested' do
-        visit admin_relays_path
-
-        # Disable the initially enabled record
-        expect { click_on I18n.t('admin.relays.disable') }
-          .to change { relay.reload.accepted? }.to(false)
-
-        relay.update(state: :rejected)
-        # Re-enable the record
-        expect { click_on I18n.t('admin.relays.enable') }
-          .to change { relay.reload.pending? }.to(true)
-      end
-    end
-  end
-end
diff --git a/spec/system/admin/report_notes_spec.rb b/spec/system/admin/report_notes_spec.rb
deleted file mode 100644
index 143bc8ac7c..0000000000
--- a/spec/system/admin/report_notes_spec.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Report Notes' do
-  let(:user) { Fabricate(:admin_user) }
-
-  before { sign_in user }
-
-  describe 'Creating notes' do
-    context 'when report is unresolved' do
-      let(:report) { Fabricate(:report, action_taken_at: nil, action_taken_by_account_id: nil) }
-
-      context 'when resolve is selected' do
-        it 'creates a report note and resolves report' do
-          visit admin_report_path(report)
-
-          fill_in 'report_note_content', with: 'Report note text'
-          expect { submit_form }
-            .to change(ReportNote, :count).by(1)
-          expect(report.reload)
-            .to be_action_taken
-          expect(page)
-            .to have_content(success_message)
-        end
-
-        def submit_form
-          click_on I18n.t('admin.reports.notes.create_and_resolve')
-        end
-      end
-
-      context 'when resolve is not selected' do
-        it 'creates a report note and does not resolve report' do
-          visit admin_report_path(report)
-
-          fill_in 'report_note_content', with: 'Report note text'
-          expect { submit_form }
-            .to change(ReportNote, :count).by(1)
-          expect(report.reload)
-            .to_not be_action_taken
-          expect(page)
-            .to have_content(success_message)
-        end
-
-        def submit_form
-          click_on I18n.t('admin.reports.notes.create')
-        end
-      end
-    end
-
-    context 'when report is resolved' do
-      let(:report) { Fabricate(:report, action_taken_at: Time.current, action_taken_by_account_id: user.account.id) }
-
-      context 'when create_and_unresolve flag is on' do
-        it 'creates a report note and unresolves report' do
-          visit admin_report_path(report)
-
-          fill_in 'report_note_content', with: 'Report note text'
-          expect { submit_form }
-            .to change(ReportNote, :count).by(1)
-          expect(report.reload)
-            .to_not be_action_taken
-          expect(page)
-            .to have_content(success_message)
-        end
-
-        def submit_form
-          click_on I18n.t('admin.reports.notes.create_and_unresolve')
-        end
-      end
-
-      context 'when create_and_unresolve flag is false' do
-        it 'creates a report note and does not unresolve report' do
-          visit admin_report_path(report)
-
-          fill_in 'report_note_content', with: 'Report note text'
-          expect { submit_form }
-            .to change(ReportNote, :count).by(1)
-          expect(report.reload)
-            .to be_action_taken
-          expect(page)
-            .to have_content(success_message)
-        end
-
-        def submit_form
-          click_on I18n.t('admin.reports.notes.create')
-        end
-      end
-    end
-
-    context 'when content is not valid' do
-      let(:report) { Fabricate(:report) }
-
-      it 'does not create a note' do
-        visit admin_report_path(report)
-
-        fill_in 'report_note_content', with: ''
-        expect { submit_form }
-          .to_not change(ReportNote, :count)
-        expect(page)
-          .to have_content(/error below/)
-      end
-
-      def submit_form
-        click_on I18n.t('admin.reports.notes.create')
-      end
-    end
-
-    def success_message
-      I18n.t('admin.report_notes.created_msg')
-    end
-  end
-
-  describe 'Removing notes' do
-    let!(:report_note) { Fabricate(:report_note) }
-
-    it 'deletes note' do
-      visit admin_report_path(report_note.report)
-
-      expect { delete_note }
-        .to change(ReportNote, :count).by(-1)
-      expect(page)
-        .to have_content(I18n.t('admin.report_notes.destroyed_msg'))
-    end
-
-    def delete_note
-      click_on I18n.t('admin.reports.notes.delete')
-    end
-  end
-end
diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb
deleted file mode 100644
index 90845a02f7..0000000000
--- a/spec/system/admin/reports_spec.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Reports' do
-  let(:user) { Fabricate(:admin_user) }
-
-  before { sign_in(user) }
-
-  describe 'Viewing existing reports' do
-    let!(:unresolved_report) { Fabricate(:report, action_taken_at: nil, comment: 'First report') }
-    let!(:resolved_report) { Fabricate(:report, action_taken_at: Time.now.utc, comment: 'Second report') }
-    let!(:report_note) { Fabricate :report_note, report: resolved_report, content: 'Note about resolved report' }
-
-    it 'Shows basic report details' do
-      visit admin_reports_path
-
-      expect(page)
-        .to have_content(unresolved_report.comment)
-        .and have_no_content(resolved_report.comment)
-
-      click_on I18n.t('admin.reports.resolved')
-      expect(page)
-        .to have_content(resolved_report.comment)
-        .and have_no_content(unresolved_report.comment)
-
-      click_on resolved_report.comment
-      expect(page)
-        .to have_title(I18n.t('admin.reports.report', id: resolved_report.id))
-        .and have_content(resolved_report.comment)
-        .and have_content(report_note.content)
-    end
-  end
-
-  describe 'Resolving reports' do
-    let!(:report) { Fabricate :report }
-
-    it 'resolves an open report' do
-      visit admin_report_path(report)
-      within '.content__heading__actions' do
-        click_on I18n.t('admin.reports.mark_as_resolved')
-      end
-
-      expect(page)
-        .to have_title(I18n.t('admin.reports.title'))
-        .and have_content(I18n.t('admin.reports.resolved_msg'))
-
-      report.reload
-      expect(report.action_taken_by_account)
-        .to eq user.account
-      expect(report)
-        .to be_action_taken
-      expect(last_action_log.target)
-        .to eq(report)
-    end
-  end
-
-  describe 'Reopening reports' do
-    let!(:report) { Fabricate :report, action_taken_at: 3.days.ago }
-
-    it 'reopens a resolved report' do
-      visit admin_report_path(report)
-      within '.content__heading__actions' do
-        click_on I18n.t('admin.reports.mark_as_unresolved')
-      end
-
-      expect(page)
-        .to have_title(I18n.t('admin.reports.report', id: report.id))
-
-      report.reload
-      expect(report.action_taken_by_account)
-        .to be_nil
-      expect(report)
-        .to_not be_action_taken
-      expect(last_action_log.target)
-        .to eq(report)
-    end
-  end
-
-  describe 'Assigning reports' do
-    let!(:report) { Fabricate :report }
-
-    it 'assigns report to user and then unassigns' do
-      visit admin_report_path(report)
-
-      click_on I18n.t('admin.reports.assign_to_self')
-
-      expect(page)
-        .to have_title(I18n.t('admin.reports.report', id: report.id))
-      report.reload
-      expect(report.assigned_account)
-        .to eq user.account
-      expect(last_action_log.target)
-        .to eq(report)
-
-      click_on I18n.t('admin.reports.unassign')
-      expect(page)
-        .to have_title(I18n.t('admin.reports.report', id: report.id))
-      report.reload
-      expect(report.assigned_account)
-        .to be_nil
-      expect(last_action_log.target)
-        .to eq(report)
-    end
-  end
-
-  private
-
-  def last_action_log
-    Admin::ActionLog.last
-  end
-end
diff --git a/spec/system/admin/reset_spec.rb b/spec/system/admin/reset_spec.rb
index 5cd0c048bb..1e787ea110 100644
--- a/spec/system/admin/reset_spec.rb
+++ b/spec/system/admin/reset_spec.rb
@@ -3,7 +3,7 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Reset' do
-  it 'Resets password for account user', :inline_jobs do
+  it 'Resets password for account user' do
     account = Fabricate :account
     sign_in admin_user
     visit admin_account_path(account.id)
@@ -28,7 +28,7 @@ RSpec.describe 'Admin::Reset' do
   end
 
   def admin_user
-    Fabricate(:admin_user)
+    Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
   end
 
   def submit_reset
diff --git a/spec/system/admin/roles_spec.rb b/spec/system/admin/roles_spec.rb
deleted file mode 100644
index 2a82d80b71..0000000000
--- a/spec/system/admin/roles_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin::Roles' do
-  context 'when user has administrator permissions' do
-    let(:user_role) { Fabricate(:user_role, permissions: UserRole::FLAGS[:administrator], position: 10) }
-
-    before { sign_in Fabricate(:user, role: user_role) }
-
-    it 'creates new user role' do
-      visit new_admin_role_path
-
-      fill_in 'user_role_name', with: 'Baz'
-      fill_in 'user_role_position', with: '1'
-      check 'user_role_permissions_as_keys_manage_reports'
-      check 'user_role_permissions_as_keys_manage_roles'
-
-      expect { click_on I18n.t('admin.roles.add_new') }
-        .to change(UserRole, :count)
-      expect(page)
-        .to have_title(I18n.t('admin.roles.title'))
-    end
-  end
-
-  context 'when user has permissions to manage roles' do
-    let(:user_role) { Fabricate(:user_role, permissions: UserRole::FLAGS[:manage_roles], position: 10) }
-
-    before { sign_in Fabricate(:user, role: user_role) }
-
-    it 'Creates user roles' do
-      visit admin_roles_path
-      expect(page)
-        .to have_title(I18n.t('admin.roles.title'))
-
-      click_on I18n.t('admin.roles.add_new')
-      expect(page)
-        .to have_title(I18n.t('admin.roles.add_new'))
-
-      # Position too high
-      fill_in 'user_role_name', with: 'Baz'
-      fill_in 'user_role_position', with: '100'
-      expect { click_on I18n.t('admin.roles.add_new') }
-        .to_not change(UserRole, :count)
-      expect(page)
-        .to have_content(I18n.t('activerecord.errors.models.user_role.attributes.position.elevated'))
-
-      # Valid submission
-      fill_in 'user_role_name', with: 'Baz'
-      fill_in 'user_role_position', with: '5' # Lower than user
-      check 'user_role_permissions_as_keys_manage_roles' # User has permission
-      expect { click_on I18n.t('admin.roles.add_new') }
-        .to change(UserRole, :count)
-      expect(page)
-        .to have_title(I18n.t('admin.roles.title'))
-    end
-
-    it 'Manages existing user roles' do
-      role = Fabricate :user_role, name: 'Baz'
-
-      visit edit_admin_role_path(role)
-      expect(page)
-        .to have_title(I18n.t('admin.roles.edit', name: 'Baz'))
-
-      # Update role attribute
-      fill_in 'user_role_position', with: '5' # Lower than user
-      expect { click_on submit_button }
-        .to(change { role.reload.position })
-
-      # Destroy the role
-      visit edit_admin_role_path(role)
-      expect { click_on I18n.t('admin.roles.delete') }
-        .to change(UserRole, :count).by(-1)
-      expect(page)
-        .to have_title(I18n.t('admin.roles.title'))
-    end
-  end
-end
diff --git a/spec/system/admin/rules_spec.rb b/spec/system/admin/rules_spec.rb
deleted file mode 100644
index 95391ba33d..0000000000
--- a/spec/system/admin/rules_spec.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Rules' do
-  describe 'Managing rules' do
-    before { sign_in Fabricate(:admin_user) }
-
-    describe 'Viewing rules' do
-      let!(:rule) { Fabricate :rule, text: 'This is a rule' }
-
-      it 'lists existing records' do
-        visit admin_rules_path
-
-        expect(page)
-          .to have_content(I18n.t('admin.rules.title'))
-          .and have_content(rule.text)
-
-        click_on(rule.text)
-        expect(page)
-          .to have_content(I18n.t('admin.rules.title'))
-      end
-    end
-
-    describe 'Creating a new rule' do
-      it 'creates new record with valid attributes' do
-        visit admin_rules_path
-
-        # Invalid submission
-        fill_in 'rule_text', with: ''
-        expect { submit_form }
-          .to_not change(Rule, :count)
-        expect(page)
-          .to have_content(/error below/)
-
-        # Valid submission
-        fill_in 'rule_text', with: 'No yelling on the bus!'
-        expect { submit_form }
-          .to change(Rule, :count).by(1)
-        expect(page)
-          .to have_content(I18n.t('admin.rules.title'))
-      end
-
-      def submit_form
-        click_on I18n.t('admin.rules.add_new')
-      end
-    end
-
-    describe 'Editing an existing rule' do
-      let!(:rule) { Fabricate :rule, text: 'Rule text' }
-
-      it 'updates with valid attributes' do
-        visit admin_rules_path
-
-        # Invalid submission
-        click_on rule.text
-        fill_in 'rule_text', with: ''
-        expect { submit_form }
-          .to_not change(rule.reload, :updated_at)
-
-        # Valid update
-        fill_in 'rule_text', with: 'What day is this?'
-        expect { submit_form }
-          .to(change { rule.reload.text })
-      end
-
-      def submit_form
-        click_on(submit_button)
-      end
-    end
-
-    describe 'Destroy a rule' do
-      let!(:rule) { Fabricate :rule }
-
-      it 'removes the record' do
-        visit admin_rules_path
-
-        expect { click_on I18n.t('admin.rules.delete') }
-          .to change { rule.reload.discarded? }.to(true)
-      end
-    end
-  end
-end
diff --git a/spec/system/admin/settings/about_spec.rb b/spec/system/admin/settings/about_spec.rb
index 93ee3f6864..c7405a8d5a 100644
--- a/spec/system/admin/settings/about_spec.rb
+++ b/spec/system/admin/settings/about_spec.rb
@@ -3,14 +3,9 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Settings::About' do
-  let(:admin_user) { Fabricate(:admin_user) }
-
-  before { sign_in(admin_user) }
-
   it 'Saves changes to about settings' do
+    sign_in admin_user
     visit admin_settings_about_path
-    expect(page)
-      .to have_title(I18n.t('admin.settings.about.title'))
 
     fill_in extended_description_field,
             with: 'new site description'
diff --git a/spec/system/admin/settings/appearance_spec.rb b/spec/system/admin/settings/appearance_spec.rb
index 2f6e67979e..56af58c812 100644
--- a/spec/system/admin/settings/appearance_spec.rb
+++ b/spec/system/admin/settings/appearance_spec.rb
@@ -3,14 +3,9 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Settings::Appearance' do
-  let(:admin_user) { Fabricate(:admin_user) }
-
-  before { sign_in(admin_user) }
-
   it 'Saves changes to appearance settings' do
+    sign_in admin_user
     visit admin_settings_appearance_path
-    expect(page)
-      .to have_title(I18n.t('admin.settings.appearance.title'))
 
     fill_in custom_css_field,
             with: 'html { display: inline; }'
diff --git a/spec/system/admin/settings/branding_spec.rb b/spec/system/admin/settings/branding_spec.rb
index 78364669e9..5cd9319ce0 100644
--- a/spec/system/admin/settings/branding_spec.rb
+++ b/spec/system/admin/settings/branding_spec.rb
@@ -3,14 +3,9 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Settings::Branding' do
-  let(:admin_user) { Fabricate(:admin_user) }
-
-  before { sign_in(admin_user) }
-
   it 'Saves changes to branding settings' do
+    sign_in admin_user
     visit admin_settings_branding_path
-    expect(page)
-      .to have_title(I18n.t('admin.settings.branding.title'))
 
     fill_in short_description_field,
             with: 'new key value'
diff --git a/spec/system/admin/settings/content_retention_spec.rb b/spec/system/admin/settings/content_retention_spec.rb
index b813c4fa5b..f788f8eea0 100644
--- a/spec/system/admin/settings/content_retention_spec.rb
+++ b/spec/system/admin/settings/content_retention_spec.rb
@@ -3,14 +3,9 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Settings::ContentRetention' do
-  let(:admin_user) { Fabricate(:admin_user) }
-
-  before { sign_in(admin_user) }
-
   it 'Saves changes to content retention settings' do
+    sign_in admin_user
     visit admin_settings_content_retention_path
-    expect(page)
-      .to have_title(I18n.t('admin.settings.content_retention.title'))
 
     fill_in media_cache_retention_period_field,
             with: '2'
diff --git a/spec/system/admin/settings/discovery_spec.rb b/spec/system/admin/settings/discovery_spec.rb
index f6909da9be..f000d18370 100644
--- a/spec/system/admin/settings/discovery_spec.rb
+++ b/spec/system/admin/settings/discovery_spec.rb
@@ -3,14 +3,9 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Settings::Discovery' do
-  let(:admin_user) { Fabricate(:admin_user) }
-
-  before { sign_in(admin_user) }
-
   it 'Saves changes to discovery settings' do
+    sign_in admin_user
     visit admin_settings_discovery_path
-    expect(page)
-      .to have_title(I18n.t('admin.settings.discovery.title'))
 
     check trends_box
 
diff --git a/spec/system/admin/settings/registrations_spec.rb b/spec/system/admin/settings/registrations_spec.rb
index 9b8bef4172..d026b07c85 100644
--- a/spec/system/admin/settings/registrations_spec.rb
+++ b/spec/system/admin/settings/registrations_spec.rb
@@ -3,14 +3,9 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Settings::Registrations' do
-  let(:admin_user) { Fabricate(:admin_user) }
-
-  before { sign_in(admin_user) }
-
   it 'Saves changes to registrations settings' do
+    sign_in admin_user
     visit admin_settings_registrations_path
-    expect(page)
-      .to have_title(I18n.t('admin.settings.registrations.title'))
 
     select open_mode_option,
            from: registrations_mode_field
diff --git a/spec/system/admin/site_uploads_spec.rb b/spec/system/admin/site_uploads_spec.rb
deleted file mode 100644
index 5cbd8d275c..0000000000
--- a/spec/system/admin/site_uploads_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin SiteUploads' do
-  let(:user) { Fabricate(:admin_user) }
-
-  before { sign_in(user) }
-
-  describe 'Removing a site upload' do
-    let!(:site_upload) { Fabricate(:site_upload, var: 'thumbnail') }
-
-    it 'removes the upload and redirects' do
-      visit admin_settings_branding_path
-      expect(page)
-        .to have_title(I18n.t('admin.settings.branding.title'))
-
-      expect { click_on I18n.t('admin.site_uploads.delete') }
-        .to change(SiteUpload, :count).by(-1)
-      expect { site_upload.reload }
-        .to raise_error(ActiveRecord::RecordNotFound)
-      expect(page)
-        .to have_content(I18n.t('admin.site_uploads.destroyed_msg'))
-        .and have_title(I18n.t('admin.settings.branding.title'))
-    end
-  end
-end
diff --git a/spec/system/admin/software_updates_spec.rb b/spec/system/admin/software_updates_spec.rb
index e62b6a8cfe..77e9f16684 100644
--- a/spec/system/admin/software_updates_spec.rb
+++ b/spec/system/admin/software_updates_spec.rb
@@ -5,9 +5,8 @@ require 'rails_helper'
 RSpec.describe 'finding software updates through the admin interface' do
   before do
     Fabricate(:software_update, version: '99.99.99', type: 'major', urgent: true, release_notes: 'https://github.com/mastodon/mastodon/releases/v99')
-    Fabricate(:software_update, version: '3.5.0', type: 'major', urgent: true, release_notes: 'https://github.com/mastodon/mastodon/releases/v3.5.0')
 
-    sign_in Fabricate(:owner_user), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Owner')), scope: :user
   end
 
   it 'shows a link to the software updates page, which links to release notes' do
@@ -17,7 +16,6 @@ RSpec.describe 'finding software updates through the admin interface' do
     expect(page).to have_title(I18n.t('admin.software_updates.title'))
 
     expect(page).to have_content('99.99.99')
-                .and have_no_content('3.5.0')
 
     click_on I18n.t('admin.software_updates.release_notes')
     expect(page).to have_current_path('https://github.com/mastodon/mastodon/releases/v99', url: true)
diff --git a/spec/system/admin/statuses_spec.rb b/spec/system/admin/statuses_spec.rb
index 998ffc89df..bb76a2963d 100644
--- a/spec/system/admin/statuses_spec.rb
+++ b/spec/system/admin/statuses_spec.rb
@@ -3,7 +3,7 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Statuses' do
-  let(:current_user) { Fabricate(:admin_user) }
+  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
   before do
     sign_in current_user
diff --git a/spec/system/admin/tags_spec.rb b/spec/system/admin/tags_spec.rb
index 654fac3340..a3eca80d13 100644
--- a/spec/system/admin/tags_spec.rb
+++ b/spec/system/admin/tags_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe 'Admin Tags' do
   describe 'Tag interaction' do
     let!(:tag) { Fabricate(:tag, name: 'test') }
 
-    before { sign_in Fabricate(:admin_user) }
+    before { sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     it 'allows tags listing and editing' do
       visit admin_tags_path
@@ -28,7 +28,7 @@ RSpec.describe 'Admin Tags' do
     end
 
     def display_name_field
-      form_label('defaults.display_name')
+      I18n.t('simple_form.labels.defaults.display_name')
     end
 
     def match_error_text
diff --git a/spec/system/admin/terms_of_service/distributions_spec.rb b/spec/system/admin/terms_of_service/distributions_spec.rb
deleted file mode 100644
index ba525d09c0..0000000000
--- a/spec/system/admin/terms_of_service/distributions_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin TermsOfService Distributions' do
-  let(:user) { Fabricate(:admin_user) }
-  let(:terms_of_service) { Fabricate(:terms_of_service, notification_sent_at: nil) }
-
-  before { sign_in(user) }
-
-  describe 'Sending a TOS change notification', :inline_jobs do
-    it 'marks the TOS as notified and sends the email' do
-      visit admin_terms_of_service_preview_path(terms_of_service)
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.preview.title'))
-
-      emails = capture_emails do
-        expect { click_on I18n.t('admin.terms_of_service.preview.send_to_all', count: 1, display_count: 1) }
-          .to(change { terms_of_service.reload.notification_sent_at })
-      end
-      expect(emails.first)
-        .to be_present
-        .and(deliver_to(user.email))
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.title'))
-    end
-  end
-end
diff --git a/spec/system/admin/terms_of_service/drafts_spec.rb b/spec/system/admin/terms_of_service/drafts_spec.rb
deleted file mode 100644
index cf4c10ce00..0000000000
--- a/spec/system/admin/terms_of_service/drafts_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin TermsOfService Drafts' do
-  let(:admin_user) { Fabricate(:admin_user) }
-
-  before { sign_in(admin_user) }
-
-  describe 'Managing TOS drafts' do
-    let!(:terms) { Fabricate :terms_of_service, published_at: nil }
-
-    it 'saves and publishes TOS drafts' do
-      visit admin_terms_of_service_draft_path
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.title'))
-
-      # Invalid submission
-      expect { click_on I18n.t('admin.terms_of_service.save_draft') }
-        .to_not(change { terms.reload.published_at })
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.title'))
-
-      # Valid submission with draft button
-      fill_in 'terms_of_service_text', with: 'new'
-      expect { click_on I18n.t('admin.terms_of_service.save_draft') }
-        .to not_change { terms.reload.published_at }.from(nil)
-        .and not_change(Admin::ActionLog, :count)
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.title'))
-
-      # Valid with publish button
-      fill_in 'terms_of_service_text', with: 'newer'
-      expect { click_on I18n.t('admin.terms_of_service.publish') }
-        .to change { terms.reload.published_at }.from(nil)
-        .and change(Admin::ActionLog, :count)
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.title'))
-    end
-  end
-end
diff --git a/spec/system/admin/terms_of_service/generates_spec.rb b/spec/system/admin/terms_of_service/generates_spec.rb
deleted file mode 100644
index 431084314c..0000000000
--- a/spec/system/admin/terms_of_service/generates_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin TermsOfService Generates' do
-  let(:admin_user) { Fabricate(:admin_user) }
-
-  before { sign_in(admin_user) }
-
-  describe 'Generating a TOS policy' do
-    it 'saves a new TOS from values' do
-      visit admin_terms_of_service_generate_path
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.generates.title'))
-
-      # Invalid form submission
-      fill_in 'terms_of_service_generator_admin_email', with: 'what the'
-      expect { submit_form }
-        .to_not change(TermsOfService, :count)
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.generates.title'))
-
-      # Valid submission
-      fill_in 'terms_of_service_generator_admin_email', with: 'test@host.example'
-      fill_in 'terms_of_service_generator_arbitration_address', with: '123 Main Street'
-      fill_in 'terms_of_service_generator_arbitration_website', with: 'https://host.example'
-      fill_in 'terms_of_service_generator_dmca_address', with: '123 DMCA Ave'
-      fill_in 'terms_of_service_generator_dmca_email', with: 'dmca@host.example'
-      fill_in 'terms_of_service_generator_domain', with: 'host.example'
-      fill_in 'terms_of_service_generator_jurisdiction', with: 'Europe'
-      fill_in 'terms_of_service_generator_choice_of_law', with: 'New York'
-      fill_in 'terms_of_service_generator_min_age', with: '16'
-
-      expect { submit_form }
-        .to change(TermsOfService, :count).by(1)
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.title'))
-    end
-
-    def submit_form
-      click_on I18n.t('admin.terms_of_service.generates.action')
-    end
-  end
-end
diff --git a/spec/system/admin/terms_of_service/histories_spec.rb b/spec/system/admin/terms_of_service/histories_spec.rb
deleted file mode 100644
index aa59550d09..0000000000
--- a/spec/system/admin/terms_of_service/histories_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Terms of Service Histories' do
-  let(:current_user) { Fabricate(:admin_user) }
-
-  before { sign_in(current_user) }
-
-  describe 'Viewing TOS histories' do
-    before { Fabricate :terms_of_service, changelog: 'The changelog notes from v1 are here' }
-
-    it 'shows previous terms versions' do
-      visit admin_terms_of_service_history_path
-
-      expect(page)
-        .to have_content(I18n.t('admin.terms_of_service.history'))
-        .and have_content(/changelog notes from v1/)
-    end
-  end
-end
diff --git a/spec/system/admin/terms_of_service/previews_spec.rb b/spec/system/admin/terms_of_service/previews_spec.rb
deleted file mode 100644
index df72e601dc..0000000000
--- a/spec/system/admin/terms_of_service/previews_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin TermsOfService Previews' do
-  let(:terms_of_service) { Fabricate(:terms_of_service, notification_sent_at: nil) }
-  let(:admin_user) { Fabricate(:admin_user) }
-
-  before { sign_in(admin_user) }
-
-  describe 'Viewing TOS previews' do
-    it 'shows the TOS preview page' do
-      visit admin_terms_of_service_preview_path(terms_of_service)
-
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.preview.title'))
-    end
-  end
-end
diff --git a/spec/system/admin/terms_of_service/tests_spec.rb b/spec/system/admin/terms_of_service/tests_spec.rb
deleted file mode 100644
index 3fc7d4e75d..0000000000
--- a/spec/system/admin/terms_of_service/tests_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin TermsOfService Tests' do
-  let(:user) { Fabricate(:admin_user) }
-  let(:terms_of_service) { Fabricate(:terms_of_service, notification_sent_at: nil) }
-
-  before { sign_in(user) }
-
-  describe 'Sending test TOS email', :inline_jobs do
-    it 'generates the test email' do
-      visit admin_terms_of_service_preview_path(terms_of_service)
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.preview.title'))
-
-      emails = capture_emails { click_on I18n.t('admin.terms_of_service.preview.send_preview', email: user.email) }
-      expect(emails.first)
-        .to be_present
-        .and(deliver_to(user.email))
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.preview.title'))
-    end
-  end
-end
diff --git a/spec/system/admin/terms_of_service_spec.rb b/spec/system/admin/terms_of_service_spec.rb
deleted file mode 100644
index 0842138034..0000000000
--- a/spec/system/admin/terms_of_service_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Terms of services' do
-  describe 'Viewing terms of services index page' do
-    let!(:terms) { Fabricate :terms_of_service, text: 'Test terms' }
-
-    before { sign_in Fabricate(:admin_user) }
-
-    it 'allows tags listing and editing' do
-      visit admin_terms_of_service_index_path
-
-      expect(page)
-        .to have_title(I18n.t('admin.terms_of_service.title'))
-
-      expect(page)
-        .to have_content(terms.text)
-    end
-  end
-end
diff --git a/spec/system/admin/trends/links/preview_card_providers_spec.rb b/spec/system/admin/trends/links/preview_card_providers_spec.rb
index 4636ca86b2..16343a6891 100644
--- a/spec/system/admin/trends/links/preview_card_providers_spec.rb
+++ b/spec/system/admin/trends/links/preview_card_providers_spec.rb
@@ -3,53 +3,22 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Trends::Links::PreviewCardProviders' do
-  let(:current_user) { Fabricate(:admin_user) }
+  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
-  before { sign_in current_user }
+  before do
+    sign_in current_user
+  end
 
   describe 'Performing batch updates' do
+    before do
+      visit admin_trends_links_preview_card_providers_path
+    end
+
     context 'without selecting any records' do
       it 'displays a notice about selection' do
-        visit admin_trends_links_preview_card_providers_path
-        expect(page)
-          .to have_title(I18n.t('admin.trends.preview_card_providers.title'))
-
         click_on button_for_allow
 
-        expect(page)
-          .to have_content(selection_error_text)
-      end
-    end
-
-    context 'with providers that are not trendable' do
-      let!(:provider) { Fabricate :preview_card_provider, trendable: false }
-
-      it 'allows the providers' do
-        visit admin_trends_links_preview_card_providers_path
-
-        check_item
-
-        expect { click_on button_for_allow }
-          .to change { provider.reload.trendable? }.from(false).to(true)
-      end
-    end
-
-    context 'with providers that are trendable' do
-      let!(:provider) { Fabricate :preview_card_provider, trendable: true }
-
-      it 'disallows the providers' do
-        visit admin_trends_links_preview_card_providers_path
-
-        check_item
-
-        expect { click_on button_for_disallow }
-          .to change { provider.reload.trendable? }.from(true).to(false)
-      end
-    end
-
-    def check_item
-      within '.batch-table__row' do
-        find('input[type=checkbox]').check
+        expect(page).to have_content(selection_error_text)
       end
     end
 
@@ -57,10 +26,6 @@ RSpec.describe 'Admin::Trends::Links::PreviewCardProviders' do
       I18n.t('admin.trends.allow')
     end
 
-    def button_for_disallow
-      I18n.t('admin.trends.disallow')
-    end
-
     def selection_error_text
       I18n.t('admin.trends.links.publishers.no_publisher_selected')
     end
diff --git a/spec/system/admin/trends/links_spec.rb b/spec/system/admin/trends/links_spec.rb
index 6140ea8154..7a51c337c9 100644
--- a/spec/system/admin/trends/links_spec.rb
+++ b/spec/system/admin/trends/links_spec.rb
@@ -3,81 +3,22 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Trends::Links' do
-  let(:current_user) { Fabricate(:admin_user) }
+  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
-  before { sign_in current_user }
+  before do
+    sign_in current_user
+  end
 
   describe 'Performing batch updates' do
+    before do
+      visit admin_trends_links_path
+    end
+
     context 'without selecting any records' do
       it 'displays a notice about selection' do
-        visit admin_trends_links_path
-        expect(page)
-          .to have_title(I18n.t('admin.trends.links.title'))
-
         click_on button_for_allow
 
-        expect(page)
-          .to have_content(selection_error_text)
-      end
-    end
-
-    context 'with links that are not trendable' do
-      let!(:preview_card_trend) { Fabricate :preview_card_trend, preview_card: Fabricate(:preview_card, trendable: false) }
-
-      it 'allows the links' do
-        visit admin_trends_links_path
-
-        check_item
-
-        expect { click_on button_for_allow }
-          .to change { preview_card_trend.preview_card.reload.trendable? }.from(false).to(true)
-      end
-    end
-
-    context 'with links whose providers are not trendable' do
-      let(:preview_card_provider) { Fabricate :preview_card_provider, trendable: false }
-      let!(:preview_card_trend) { Fabricate :preview_card_trend, preview_card: Fabricate(:preview_card, url: "https://#{preview_card_provider.domain}/page") }
-
-      it 'allows the providers of the links' do
-        visit admin_trends_links_path
-
-        check_item
-
-        expect { click_on button_for_allow_providers }
-          .to change { preview_card_trend.preview_card.provider.reload.trendable? }.from(false).to(true)
-      end
-    end
-
-    context 'with links that are trendable' do
-      let!(:preview_card_trend) { Fabricate :preview_card_trend, preview_card: Fabricate(:preview_card, trendable: true) }
-
-      it 'disallows the links' do
-        visit admin_trends_links_path
-
-        check_item
-
-        expect { click_on button_for_disallow }
-          .to change { preview_card_trend.preview_card.reload.trendable? }.from(true).to(false)
-      end
-    end
-
-    context 'with links whose providers are trendable' do
-      let(:preview_card_provider) { Fabricate :preview_card_provider, trendable: true }
-      let!(:preview_card_trend) { Fabricate :preview_card_trend, preview_card: Fabricate(:preview_card, url: "https://#{preview_card_provider.domain}/page") }
-
-      it 'disallows the links' do
-        visit admin_trends_links_path
-
-        check_item
-
-        expect { click_on button_for_disallow_providers }
-          .to change { preview_card_trend.preview_card.provider.reload.trendable? }.from(true).to(false)
-      end
-    end
-
-    def check_item
-      within '.batch-table__row' do
-        find('input[type=checkbox]').check
+        expect(page).to have_content(selection_error_text)
       end
     end
 
@@ -85,18 +26,6 @@ RSpec.describe 'Admin::Trends::Links' do
       I18n.t('admin.trends.links.allow')
     end
 
-    def button_for_allow_providers
-      I18n.t('admin.trends.links.allow_provider')
-    end
-
-    def button_for_disallow
-      I18n.t('admin.trends.links.disallow')
-    end
-
-    def button_for_disallow_providers
-      I18n.t('admin.trends.links.disallow_provider')
-    end
-
     def selection_error_text
       I18n.t('admin.trends.links.no_link_selected')
     end
diff --git a/spec/system/admin/trends/statuses_spec.rb b/spec/system/admin/trends/statuses_spec.rb
index 6e1aa17b7d..13fc966dfd 100644
--- a/spec/system/admin/trends/statuses_spec.rb
+++ b/spec/system/admin/trends/statuses_spec.rb
@@ -3,79 +3,22 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Trends::Statuses' do
-  let(:current_user) { Fabricate(:admin_user) }
+  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
-  before { sign_in current_user }
+  before do
+    sign_in current_user
+  end
 
   describe 'Performing batch updates' do
+    before do
+      visit admin_trends_statuses_path
+    end
+
     context 'without selecting any records' do
       it 'displays a notice about selection' do
-        visit admin_trends_statuses_path
-        expect(page)
-          .to have_title(I18n.t('admin.trends.statuses.title'))
-
         click_on button_for_allow
 
-        expect(page)
-          .to have_content(selection_error_text)
-      end
-    end
-
-    context 'with statuses that are not trendable' do
-      let!(:status_trend) { Fabricate :status_trend, status: Fabricate(:status, trendable: false) }
-
-      it 'allows the statuses' do
-        visit admin_trends_statuses_path
-
-        check_item
-
-        expect { click_on button_for_allow }
-          .to change { status_trend.status.reload.trendable? }.from(false).to(true)
-      end
-    end
-
-    context 'with statuses whose accounts are not trendable' do
-      let!(:status_trend) { Fabricate :status_trend, status: Fabricate(:status, account: Fabricate(:account, trendable: false)) }
-
-      it 'allows the accounts of the statuses' do
-        visit admin_trends_statuses_path
-
-        check_item
-
-        expect { click_on button_for_allow_accounts }
-          .to change { status_trend.status.account.reload.trendable? }.from(false).to(true)
-      end
-    end
-
-    context 'with statuses that are trendable' do
-      let!(:status_trend) { Fabricate :status_trend, status: Fabricate(:status, trendable: true) }
-
-      it 'disallows the statuses' do
-        visit admin_trends_statuses_path
-
-        check_item
-
-        expect { click_on button_for_disallow }
-          .to change { status_trend.status.reload.trendable? }.from(true).to(false)
-      end
-    end
-
-    context 'with statuses whose accounts are trendable' do
-      let!(:status_trend) { Fabricate :status_trend, status: Fabricate(:status, account: Fabricate(:account, trendable: true)) }
-
-      it 'disallows the statuses' do
-        visit admin_trends_statuses_path
-
-        check_item
-
-        expect { click_on button_for_disallow_accounts }
-          .to change { status_trend.status.reload.trendable? }.from(true).to(false)
-      end
-    end
-
-    def check_item
-      within '.batch-table__row' do
-        find('input[type=checkbox]').check
+        expect(page).to have_content(selection_error_text)
       end
     end
 
@@ -83,18 +26,6 @@ RSpec.describe 'Admin::Trends::Statuses' do
       I18n.t('admin.trends.statuses.allow')
     end
 
-    def button_for_allow_accounts
-      I18n.t('admin.trends.statuses.allow_account')
-    end
-
-    def button_for_disallow
-      I18n.t('admin.trends.statuses.disallow')
-    end
-
-    def button_for_disallow_accounts
-      I18n.t('admin.trends.statuses.disallow_account')
-    end
-
     def selection_error_text
       I18n.t('admin.trends.statuses.no_status_selected')
     end
diff --git a/spec/system/admin/trends/tags_spec.rb b/spec/system/admin/trends/tags_spec.rb
index a7f00c0232..d914badbd4 100644
--- a/spec/system/admin/trends/tags_spec.rb
+++ b/spec/system/admin/trends/tags_spec.rb
@@ -3,63 +3,29 @@
 require 'rails_helper'
 
 RSpec.describe 'Admin::Trends::Tags' do
-  let(:current_user) { Fabricate(:admin_user) }
+  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
-  before { sign_in current_user }
+  before do
+    sign_in current_user
+  end
 
   describe 'Performing batch updates' do
+    before do
+      visit admin_trends_tags_path
+    end
+
     context 'without selecting any records' do
       it 'displays a notice about selection' do
-        visit admin_trends_tags_path
-        expect(page)
-          .to have_title(I18n.t('admin.trends.tags.title'))
-
         click_on button_for_allow
 
         expect(page).to have_content(selection_error_text)
       end
     end
 
-    context 'with tags that are not trendable' do
-      let!(:tag_trend) { Fabricate :tag_trend, tag: Fabricate(:tag, trendable: false) }
-
-      it 'allows the tags' do
-        visit admin_trends_tags_path
-
-        check_item
-
-        expect { click_on button_for_allow }
-          .to change { tag_trend.tag.reload.trendable? }.from(false).to(true)
-      end
-    end
-
-    context 'with tags that are trendable' do
-      let!(:tag_trend) { Fabricate :tag_trend, tag: Fabricate(:tag, trendable: true) }
-
-      it 'disallows the tags' do
-        visit admin_trends_tags_path
-
-        check_item
-
-        expect { click_on button_for_disallow }
-          .to change { tag_trend.tag.reload.trendable? }.from(true).to(false)
-      end
-    end
-
-    def check_item
-      within '.batch-table__row' do
-        find('input[type=checkbox]').check
-      end
-    end
-
     def button_for_allow
       I18n.t('admin.trends.allow')
     end
 
-    def button_for_disallow
-      I18n.t('admin.trends.disallow')
-    end
-
     def selection_error_text
       I18n.t('admin.trends.tags.no_tag_selected')
     end
diff --git a/spec/system/admin/users/roles_spec.rb b/spec/system/admin/users/roles_spec.rb
deleted file mode 100644
index 8b163c4d79..0000000000
--- a/spec/system/admin/users/roles_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Users Roles' do
-  let(:current_role) { UserRole.create(name: 'Foo', permissions: UserRole::FLAGS[:manage_roles], position: 10) }
-  let(:current_user) { Fabricate(:user, role: current_role) }
-
-  let(:previous_role) { nil }
-  let(:user) { Fabricate(:user, role: previous_role) }
-
-  before do
-    sign_in current_user, scope: :user
-  end
-
-  describe 'Managing user roles' do
-    let!(:too_high_role) { UserRole.create(name: 'TooHigh', permissions: UserRole::FLAGS[:administrator], position: 100) }
-    let!(:usable_role) { UserRole.create(name: 'Usable', permissions: UserRole::FLAGS[:manage_roles], position: 1) }
-
-    it 'selects and updates user roles' do
-      visit admin_user_role_path(user)
-      expect(page)
-        .to have_title I18n.t('admin.accounts.change_role.title', username: user.account.username)
-
-      # Fails to assign not allowed role
-      select too_high_role.name, from: 'user_role_id'
-      expect { click_on submit_button }
-        .to_not(change { user.reload.role_id })
-      expect(page)
-        .to have_title I18n.t('admin.accounts.change_role.title', username: user.account.username)
-
-      # Assigns allowed role
-      select usable_role.name, from: 'user_role_id'
-      expect { click_on submit_button }
-        .to(change { user.reload.role_id }.to(usable_role.id))
-    end
-  end
-end
diff --git a/spec/system/admin/users/two_factor_authentications_spec.rb b/spec/system/admin/users/two_factor_authentications_spec.rb
deleted file mode 100644
index e09bc437b4..0000000000
--- a/spec/system/admin/users/two_factor_authentications_spec.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-require 'webauthn/fake_client'
-
-RSpec.describe 'Admin Users TwoFactorAuthentications' do
-  let(:user) { Fabricate(:user) }
-
-  before { sign_in Fabricate(:admin_user) }
-
-  describe 'Disabling 2FA for users' do
-    before { stub_webauthn_credential }
-
-    let(:fake_client) { WebAuthn::FakeClient.new('http://test.host') }
-
-    context 'when user has OTP enabled' do
-      before { user.update(otp_required_for_login: true) }
-
-      it 'disables OTP and redirects to admin account page' do
-        visit admin_account_path(user.account.id)
-
-        expect { disable_two_factor }
-          .to change { user.reload.otp_enabled? }.to(false)
-        expect(page)
-          .to have_title(user.account.pretty_acct)
-      end
-    end
-
-    context 'when user has OTP and WebAuthn enabled' do
-      before { user.update(otp_required_for_login: true, webauthn_id: WebAuthn.generate_user_id) }
-
-      it 'disables OTP and webauthn and redirects to admin account page' do
-        visit admin_account_path(user.account.id)
-
-        expect { disable_two_factor }
-          .to change { user.reload.otp_enabled? }.to(false)
-          .and(change { user.reload.webauthn_enabled? }.to(false))
-        expect(page)
-          .to have_title(user.account.pretty_acct)
-      end
-    end
-
-    def disable_two_factor
-      click_on I18n.t('admin.accounts.disable_two_factor_authentication')
-    end
-
-    def stub_webauthn_credential
-      public_key_credential = WebAuthn::Credential.from_create(fake_client.create)
-      Fabricate(
-        :webauthn_credential,
-        external_id: public_key_credential.id,
-        nickname: 'Security Key',
-        public_key: public_key_credential.public_key,
-        user_id: user.id
-      )
-    end
-  end
-end
diff --git a/spec/system/admin/warning_presets_spec.rb b/spec/system/admin/warning_presets_spec.rb
deleted file mode 100644
index 88c697b743..0000000000
--- a/spec/system/admin/warning_presets_spec.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Warning Presets' do
-  describe 'Managing warning presets' do
-    before { sign_in Fabricate(:admin_user) }
-
-    describe 'Viewing warning presets' do
-      let!(:account_warning_preset) { Fabricate :account_warning_preset, text: 'This is a preset' }
-
-      it 'lists existing records' do
-        visit admin_warning_presets_path
-
-        expect(page)
-          .to have_content(I18n.t('admin.warning_presets.title'))
-          .and have_content(account_warning_preset.text)
-      end
-    end
-
-    describe 'Creating a new account warning preset' do
-      it 'creates new record with valid attributes' do
-        visit admin_warning_presets_path
-
-        # Invalid submission
-        fill_in 'account_warning_preset_text', with: ''
-        expect { submit_form }
-          .to_not change(AccountWarningPreset, :count)
-        expect(page)
-          .to have_content(/error below/)
-
-        # Valid submission
-        fill_in 'account_warning_preset_text', with: 'You cant do that here'
-        expect { submit_form }
-          .to change(AccountWarningPreset, :count).by(1)
-        expect(page)
-          .to have_content(I18n.t('admin.warning_presets.title'))
-      end
-
-      def submit_form
-        click_on I18n.t('admin.warning_presets.add_new')
-      end
-    end
-
-    describe 'Editing an existing account warning preset' do
-      let!(:account_warning_preset) { Fabricate :account_warning_preset, text: 'Preset text' }
-
-      it 'updates with valid attributes' do
-        visit admin_warning_presets_path
-
-        # Invalid submission
-        click_on account_warning_preset.text
-        fill_in 'account_warning_preset_text', with: ''
-        expect { submit_form }
-          .to_not change(account_warning_preset.reload, :updated_at)
-
-        # Valid update
-        fill_in 'account_warning_preset_text', with: 'Updated text'
-        expect { submit_form }
-          .to(change { account_warning_preset.reload.text })
-      end
-
-      def submit_form
-        click_on(submit_button)
-      end
-    end
-
-    describe 'Destroy an account warning preset' do
-      let!(:account_warning_preset) { Fabricate :account_warning_preset }
-
-      it 'removes the record' do
-        visit admin_warning_presets_path
-
-        expect { click_on I18n.t('admin.warning_presets.delete') }
-          .to change(AccountWarningPreset, :count).by(-1)
-        expect { account_warning_preset.reload }
-          .to raise_error(ActiveRecord::RecordNotFound)
-      end
-    end
-  end
-end
diff --git a/spec/system/admin/webhooks_spec.rb b/spec/system/admin/webhooks_spec.rb
deleted file mode 100644
index 709752dc9c..0000000000
--- a/spec/system/admin/webhooks_spec.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Admin Webhooks' do
-  describe 'Managing webhooks' do
-    before { sign_in Fabricate(:admin_user) }
-
-    describe 'Viewing webhooks' do
-      let!(:webhook) { Fabricate :webhook }
-
-      it 'lists existing records' do
-        visit admin_webhooks_path
-
-        expect(page)
-          .to have_content(I18n.t('admin.webhooks.title'))
-          .and have_content(webhook.url)
-
-        click_on(webhook.url)
-        expect(page)
-          .to have_content(I18n.t('admin.webhooks.title'))
-      end
-    end
-
-    describe 'Creating a new webhook' do
-      it 'creates new record with valid attributes' do
-        visit admin_webhooks_path
-
-        # Visit new page
-        click_on I18n.t('admin.webhooks.add_new')
-        expect(page)
-          .to have_content(I18n.t('admin.webhooks.new'))
-
-        # Invalid submission (missing url, no events selected)
-        fill_in 'webhook_url', with: ''
-        expect { submit_form }
-          .to_not change(Webhook, :count)
-        expect(page)
-          .to have_content(/errors below/)
-
-        # Valid submission
-        fill_in 'webhook_url', with: 'https://host.example/hooks/123'
-        check Webhook::EVENTS.first
-        expect { submit_form }
-          .to change(Webhook, :count).by(1)
-        expect(page)
-          .to have_content(I18n.t('admin.webhooks.title'))
-      end
-
-      it 'fails to create with no events selected' do
-        visit new_admin_webhook_path
-
-        fill_in 'webhook_url', with: 'https://host.example/hooks/123'
-        expect { submit_form }
-          .to_not change(Webhook, :count)
-        expect(page)
-          .to have_content(/errors below/)
-      end
-
-      def submit_form
-        click_on I18n.t('admin.webhooks.add_new')
-      end
-    end
-
-    describe 'Editing an existing webhook' do
-      let!(:webhook) { Fabricate :webhook, events: [Webhook::EVENTS.first] }
-
-      it 'updates with valid attributes' do
-        visit admin_webhook_path(webhook)
-
-        # Invalid submission (missing url)
-        click_on I18n.t('admin.webhooks.edit')
-        fill_in 'webhook_url', with: ''
-        expect { submit_form }
-          .to_not change(webhook.reload, :updated_at)
-
-        # Valid update
-        fill_in 'webhook_url', with: 'https://host.example/new/value/123'
-        expect { submit_form }
-          .to(change { webhook.reload.url })
-      end
-
-      def submit_form
-        click_on(submit_button)
-      end
-    end
-
-    describe 'Rotate a webhook secret' do
-      let!(:webhook) { Fabricate :webhook, events: [Webhook::EVENTS.first] }
-
-      it 'rotates secret and returns to page' do
-        visit admin_webhook_path(webhook)
-
-        expect { click_on I18n.t('admin.webhooks.rotate_secret') }
-          .to(change { webhook.reload.secret })
-        expect(page)
-          .to have_title(I18n.t('admin.webhooks.title'))
-      end
-    end
-
-    describe 'Destroy a webhook' do
-      let!(:webhook) { Fabricate :webhook, events: [Webhook::EVENTS.first] }
-
-      it 'removes the record' do
-        visit admin_webhooks_path
-
-        expect { click_on I18n.t('admin.webhooks.delete') }
-          .to change(Webhook, :count).by(-1)
-        expect { webhook.reload }
-          .to raise_error(ActiveRecord::RecordNotFound)
-      end
-    end
-
-    describe 'Toggle state of webhook' do
-      let!(:webhook) { Fabricate :webhook, events: [Webhook::EVENTS.first], enabled: true }
-
-      it 'switches enabled and disabled as requested' do
-        visit admin_webhook_path(webhook)
-
-        # Disable the initially enabled record
-        expect { click_on I18n.t('admin.webhooks.disable') }
-          .to change { webhook.reload.enabled? }.to(false)
-
-        # Re-enable the record
-        expect { click_on I18n.t('admin.webhooks.enable') }
-          .to change { webhook.reload.enabled? }.to(true)
-      end
-    end
-  end
-end
diff --git a/spec/system/auth/passwords_spec.rb b/spec/system/auth/passwords_spec.rb
deleted file mode 100644
index 42733b2521..0000000000
--- a/spec/system/auth/passwords_spec.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Auth Passwords' do
-  let(:user) { Fabricate :user }
-  let!(:session_activation) { Fabricate(:session_activation, user: user) }
-  let!(:access_token) { Fabricate(:access_token, resource_owner_id: user.id) }
-  let!(:web_push_subscription) { Fabricate(:web_push_subscription, access_token: access_token) }
-
-  describe 'Resetting a password', :inline_jobs do
-    let(:new_password) { 'New.Pass.123' }
-
-    before { allow(Devise).to receive(:pam_authentication).and_return(false) } # Avoid the "seamless external" path
-
-    it 'initiates reset, sends link, resets password from form, clears data' do
-      visit new_user_password_path
-      expect(page)
-        .to have_title(I18n.t('auth.reset_password'))
-
-      submit_email_reset
-      expect(page)
-        .to have_title(I18n.t('auth.set_new_password'))
-
-      set_new_password
-      expect(page)
-        .to have_title(I18n.t('auth.login'))
-
-      # Change password
-      expect(User.find(user.id))
-        .to be_present
-        .and be_valid_password(new_password)
-
-      # Deactivate session
-      expect(user_session_count)
-        .to eq(0)
-      expect { session_activation.reload }
-        .to raise_error(ActiveRecord::RecordNotFound)
-
-      # Revoke tokens
-      expect(user_token_count)
-        .to eq(0)
-
-      # Remove push subs
-      expect(push_subs_count)
-        .to eq(0)
-      expect { web_push_subscription.reload }
-        .to raise_error(ActiveRecord::RecordNotFound)
-    end
-
-    def submit_email_reset
-      fill_in 'user_email', with: user.email
-      click_on I18n.t('auth.reset_password')
-      open_last_email
-      visit_in_email(I18n.t('devise.mailer.reset_password_instructions.action'))
-    end
-
-    def set_new_password
-      fill_in 'user_password', with: new_password
-      fill_in 'user_password_confirmation', with: new_password
-      click_on I18n.t('auth.set_new_password')
-    end
-
-    def user_session_count
-      user
-        .session_activations
-        .count
-    end
-
-    def user_token_count
-      Doorkeeper::AccessToken
-        .active_for(user)
-        .count
-    end
-
-    def push_subs_count
-      Web::PushSubscription
-        .where(user: user)
-        .or(Web::PushSubscription.where(access_token: access_token))
-        .count
-    end
-  end
-end
diff --git a/spec/system/auth/setup_spec.rb b/spec/system/auth/setup_spec.rb
deleted file mode 100644
index 154f8cd5fa..0000000000
--- a/spec/system/auth/setup_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Auth Setup' do
-  context 'with an unconfirmed signed in user' do
-    let(:user) { Fabricate(:user, confirmed_at: nil) }
-
-    before { sign_in(user) }
-
-    it 'can update email address' do
-      visit auth_setup_path
-
-      expect(page)
-        .to have_content(I18n.t('auth.setup.title'))
-
-      find('summary.lead').click
-      fill_in 'user_email', with: 'new-email@example.host'
-
-      expect { submit_form }
-        .to(change { user.reload.unconfirmed_email })
-      expect(page)
-        .to have_content(I18n.t('auth.setup.new_confirmation_instructions_sent'))
-    end
-
-    def submit_form
-      find('[name=button]').click
-    end
-  end
-end
diff --git a/spec/system/disputes/appeals_spec.rb b/spec/system/disputes/appeals_spec.rb
deleted file mode 100644
index 860b8fcfd1..0000000000
--- a/spec/system/disputes/appeals_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Dispute Appeals' do
-  let(:user) { Fabricate(:user) }
-  let!(:admin) { Fabricate(:admin_user) }
-
-  before { sign_in user }
-
-  describe 'Submitting an appeal', :inline_jobs do
-    let(:strike) { Fabricate(:account_warning, target_account: user.account) }
-
-    it 'Submits the appeal and notifies admins' do
-      visit disputes_strike_path(strike)
-
-      # Invalid with missing attribute
-      fill_in 'appeal_text', with: ''
-      emails = capture_emails do
-        expect { submit_form }
-          .to_not change(Appeal, :count)
-      end
-      expect(emails)
-        .to be_empty
-      expect(page)
-        .to have_content(/can't be blank/)
-
-      # Valid with text
-      fill_in 'appeal_text', with: 'It wasnt me this time!'
-      emails = capture_emails do
-        expect { submit_form }
-          .to change(Appeal, :count).by(1)
-      end
-      expect(emails)
-        .to contain_exactly(
-          have_attributes(
-            to: contain_exactly(admin.email),
-            subject: eq(new_appeal_subject)
-          )
-        )
-      expect(page)
-        .to have_content(I18n.t('disputes.strikes.appealed_msg'))
-    end
-
-    def new_appeal_subject
-      I18n.t('admin_mailer.new_appeal.subject', username: user.account.acct, instance: Rails.configuration.x.local_domain)
-    end
-
-    def submit_form
-      click_on I18n.t('disputes.strikes.appeals.submit')
-    end
-  end
-end
diff --git a/spec/system/disputes/strikes_spec.rb b/spec/system/disputes/strikes_spec.rb
deleted file mode 100644
index d2b6b08c46..0000000000
--- a/spec/system/disputes/strikes_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Disputes Strikes' do
-  before { sign_in(current_user) }
-
-  describe 'viewing strike disputes' do
-    let(:current_user) { Fabricate(:user) }
-    let!(:strike) { Fabricate(:account_warning, target_account: current_user.account) }
-
-    it 'shows a list of strikes and details for each' do
-      visit disputes_strikes_path
-      expect(page)
-        .to have_title(I18n.t('settings.strikes'))
-
-      find('.strike-entry').click
-      expect(page)
-        .to have_title(strike_page_title)
-        .and have_content(strike.text)
-    end
-
-    def strike_page_title
-      I18n.t('disputes.strikes.title', action: I18n.t(strike.action, scope: 'disputes.strikes.title_actions'), date: I18n.l(strike.created_at.to_date))
-    end
-  end
-end
diff --git a/spec/system/filters/statuses_spec.rb b/spec/system/filters/statuses_spec.rb
deleted file mode 100644
index b353bd8674..0000000000
--- a/spec/system/filters/statuses_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Filters Statuses' do
-  describe 'Viewing statuses under a filter' do
-    let(:filter) { Fabricate(:custom_filter, title: 'good filter') }
-
-    context 'with the filter user signed in' do
-      before { sign_in(filter.account.user) }
-
-      it 'returns a page with the status filters' do
-        visit filter_statuses_path(filter)
-
-        expect(page)
-          .to have_private_cache_control
-          .and have_title(/good filter/)
-      end
-    end
-  end
-end
diff --git a/spec/system/filters_spec.rb b/spec/system/filters_spec.rb
index dac04d5b6c..64de384c00 100644
--- a/spec/system/filters_spec.rb
+++ b/spec/system/filters_spec.rb
@@ -8,18 +8,6 @@ RSpec.describe 'Filters' do
 
   before { sign_in(user) }
 
-  describe 'Viewing existing filters' do
-    before { Fabricate :custom_filter, account: user.account, phrase: 'Photography' }
-
-    it 'shows a list of user filters' do
-      visit filters_path
-
-      expect(page)
-        .to have_content('Photography')
-        .and have_private_cache_control
-    end
-  end
-
   describe 'Creating a filter' do
     it 'Populates a new filter from form' do
       navigate_to_filters
@@ -28,24 +16,12 @@ RSpec.describe 'Filters' do
       fill_in_filter_form
       expect(page).to have_content(filter_title)
     end
-
-    it 'Does not save with invalid values' do
-      navigate_to_filters
-      click_on I18n.t('filters.new.title')
-
-      expect { click_on I18n.t('filters.new.save') }
-        .to_not change(CustomFilter, :count)
-      expect(page)
-        .to have_content("can't be blank")
-    end
   end
 
   describe 'Editing an existing filter' do
     let(:new_title) { 'Change title value' }
 
-    let!(:custom_filter) { Fabricate :custom_filter, account: user.account, title: filter_title }
-    let!(:keyword_one) { Fabricate :custom_filter_keyword, custom_filter: custom_filter }
-    let!(:keyword_two) { Fabricate :custom_filter_keyword, custom_filter: custom_filter }
+    before { Fabricate :custom_filter, account: user.account, title: filter_title }
 
     it 'Updates the saved filter' do
       navigate_to_filters
@@ -53,27 +29,10 @@ RSpec.describe 'Filters' do
       click_on filter_title
 
       fill_in filter_title_field, with: new_title
-      fill_in 'custom_filter_keywords_attributes_0_keyword', with: 'New value'
-      fill_in 'custom_filter_keywords_attributes_1_keyword', with: 'Wilderness'
-
-      expect { click_on submit_button }
-        .to change { keyword_one.reload.keyword }.to(/New value/)
-        .and(change { keyword_two.reload.keyword }.to(/Wilderness/))
+      click_on submit_button
 
       expect(page).to have_content(new_title)
     end
-
-    it 'Does not save with invalid values' do
-      navigate_to_filters
-      click_on filter_title
-
-      fill_in filter_title_field, with: ''
-
-      expect { click_on submit_button }
-        .to_not(change { custom_filter.reload.updated_at })
-      expect(page)
-        .to have_content("can't be blank")
-    end
   end
 
   describe 'Destroying an existing filter' do
@@ -108,6 +67,6 @@ RSpec.describe 'Filters' do
   end
 
   def filter_title_field
-    form_label('defaults.title')
+    I18n.t('simple_form.labels.defaults.title')
   end
 end
diff --git a/spec/system/home_spec.rb b/spec/system/home_spec.rb
index 0838b3d8e7..c1ce4e1726 100644
--- a/spec/system/home_spec.rb
+++ b/spec/system/home_spec.rb
@@ -11,7 +11,6 @@ RSpec.describe 'Home page' do
 
       expect(page)
         .to have_css('noscript', text: /Mastodon/)
-        .and have_css('body', class: 'app-body')
     end
   end
 
@@ -21,7 +20,6 @@ RSpec.describe 'Home page' do
 
       expect(page)
         .to have_css('noscript', text: /Mastodon/)
-        .and have_css('body', class: 'app-body')
     end
   end
 end
diff --git a/spec/system/invites_spec.rb b/spec/system/invites_spec.rb
index fa3eb5778a..5a9b5afa2c 100644
--- a/spec/system/invites_spec.rb
+++ b/spec/system/invites_spec.rb
@@ -74,9 +74,9 @@ RSpec.describe 'Invites' do
 
   def fill_invite_form
     select I18n.t('invites.max_uses', count: 100),
-           from: form_label('defaults.max_uses')
+           from: I18n.t('simple_form.labels.defaults.max_uses')
     select I18n.t("invites.expires_in.#{30.minutes.to_i}"),
-           from: form_label('defaults.expires_in')
-    check form_label('defaults.autofollow')
+           from: I18n.t('simple_form.labels.defaults.expires_in')
+    check I18n.t('simple_form.labels.defaults.autofollow')
   end
 end
diff --git a/spec/system/log_in_spec.rb b/spec/system/log_in_spec.rb
index 10869fd240..f8765e8e1c 100644
--- a/spec/system/log_in_spec.rb
+++ b/spec/system/log_in_spec.rb
@@ -34,7 +34,7 @@ RSpec.describe 'Log in' do
     it 'A unconfirmed user is able to log in' do
       fill_in_auth_details(email, password)
 
-      expect(subject).to have_css('.title', text: I18n.t('auth.setup.title'))
+      expect(subject).to have_css('div.admin-wrapper')
     end
   end
 
diff --git a/spec/system/log_out_spec.rb b/spec/system/log_out_spec.rb
index ebbf5a5772..2e52254ca0 100644
--- a/spec/system/log_out_spec.rb
+++ b/spec/system/log_out_spec.rb
@@ -17,9 +17,8 @@ RSpec.describe 'Log out' do
         click_on 'Logout'
       end
 
-      expect(page)
-        .to have_title(I18n.t('auth.login'))
-        .and have_current_path('/auth/sign_in')
+      expect(page).to have_title(I18n.t('auth.login'))
+      expect(page).to have_current_path('/auth/sign_in')
     end
   end
 
@@ -29,8 +28,6 @@ RSpec.describe 'Log out' do
       ignore_js_error(/Failed to load resource: the server responded with a status of 422/)
 
       visit root_path
-      expect(page)
-        .to have_css('body', class: 'app-body')
 
       within '.navigation-bar' do
         click_on 'Menu'
@@ -42,9 +39,8 @@ RSpec.describe 'Log out' do
 
       click_on 'Log out'
 
-      expect(page)
-        .to have_title(I18n.t('auth.login'))
-        .and have_current_path('/auth/sign_in')
+      expect(page).to have_title(I18n.t('auth.login'))
+      expect(page).to have_current_path('/auth/sign_in')
     end
   end
 end
diff --git a/spec/system/new_statuses_spec.rb b/spec/system/new_statuses_spec.rb
index 87b29004b2..1d38b44a15 100644
--- a/spec/system/new_statuses_spec.rb
+++ b/spec/system/new_statuses_spec.rb
@@ -5,6 +5,8 @@ require 'rails_helper'
 RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do
   include ProfileStories
 
+  subject { page }
+
   let(:email)               { 'test@example.com' }
   let(:password)            { 'password' }
   let(:confirmed_at)        { Time.zone.now }
@@ -12,27 +14,33 @@ RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do
 
   before do
     as_a_logged_in_user
+    visit root_path
     page.driver.browser.manage.window.resize_to(1600, 1050)
   end
 
   it 'can be posted' do
-    visit_homepage
+    expect(subject).to have_css('div.app-holder')
+
     status_text = 'This is a new status!'
 
     within('.compose-form') do
-      fill_in frontend_translations('compose_form.placeholder'), with: status_text
+      fill_in "What's on your mind?", with: status_text
       click_on 'Post'
     end
 
-    expect(page)
-      .to have_css('.status__content__text', text: status_text)
+    expect(subject).to have_css('.status__content__text', text: status_text)
   end
 
-  def visit_homepage
-    visit root_path
+  it 'can be posted again' do
+    expect(subject).to have_css('div.app-holder')
 
-    expect(page)
-      .to have_css('div.app-holder')
-      .and have_css('form.compose-form')
+    status_text = 'This is a second status!'
+
+    within('.compose-form') do
+      fill_in "What's on your mind?", with: status_text
+      click_on 'Post'
+    end
+
+    expect(subject).to have_css('.status__content__text', text: status_text)
   end
 end
diff --git a/spec/system/oauth_spec.rb b/spec/system/oauth_spec.rb
index caed5ea9af..64ac75879e 100644
--- a/spec/system/oauth_spec.rb
+++ b/spec/system/oauth_spec.rb
@@ -24,28 +24,28 @@ RSpec.describe 'Using OAuth from an external app' do
       subject
 
       # It presents the user with an authorization page
-      expect(page).to have_content(oauth_authorize_text)
-
-      # It grants the app access to the account
-      expect { click_on oauth_authorize_text }
-        .to change { user_has_grant_with_client_app? }.to(true)
+      expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
 
       # Upon authorizing, it redirects to the apps' callback URL
-      expect(page).to redirect_to_callback_url
+      click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
+      expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
+
+      # It grants the app access to the account
+      expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be true
     end
 
     it 'when rejecting the authorization request' do
       subject
 
       # It presents the user with an authorization page
-      expect(page).to have_content(oauth_deny_text)
-
-      # It does not grant the app access to the account
-      expect { click_on oauth_deny_text }
-        .to_not change { user_has_grant_with_client_app? }.from(false)
+      expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.deny'))
 
       # Upon denying, it redirects to the apps' callback URL
-      expect(page).to redirect_to_callback_url
+      click_on I18n.t('doorkeeper.authorizations.buttons.deny')
+      expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
+
+      # It does not grant the app access to the account
+      expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be false
     end
 
     # The tests in this context ensures that requests without PKCE parameters
@@ -115,8 +115,6 @@ RSpec.describe 'Using OAuth from an external app' do
           subject
 
           within '.form-container .flash-message' do
-            # FIXME: Replace with doorkeeper.errors.messages.invalid_code_challenge_method.one for Doorkeeper > 5.8.0
-            # see: https://github.com/doorkeeper-gem/doorkeeper/pull/1747
             expect(page).to have_content(I18n.t('doorkeeper.errors.messages.invalid_code_challenge_method'))
           end
         end
@@ -135,6 +133,7 @@ RSpec.describe 'Using OAuth from an external app' do
     end
 
     it 'when accepting the authorization request' do
+      params = { client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' }
       visit "/oauth/authorize?#{params.to_query}"
 
       # It presents the user with a log-in page
@@ -146,17 +145,18 @@ RSpec.describe 'Using OAuth from an external app' do
 
       # Logging in redirects to an authorization page
       fill_in_auth_details(email, password)
-      expect(page).to have_content(oauth_authorize_text)
-
-      # It grants the app access to the account
-      expect { click_on oauth_authorize_text }
-        .to change { user_has_grant_with_client_app? }.to(true)
+      expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
 
       # Upon authorizing, it redirects to the apps' callback URL
-      expect(page).to redirect_to_callback_url
+      click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
+      expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
+
+      # It grants the app access to the account
+      expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be true
     end
 
     it 'when rejecting the authorization request' do
+      params = { client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' }
       visit "/oauth/authorize?#{params.to_query}"
 
       # It presents the user with a log-in page
@@ -168,20 +168,21 @@ RSpec.describe 'Using OAuth from an external app' do
 
       # Logging in redirects to an authorization page
       fill_in_auth_details(email, password)
-      expect(page).to have_content(oauth_authorize_text)
-
-      # It does not grant the app access to the account
-      expect { click_on oauth_deny_text }
-        .to_not change { user_has_grant_with_client_app? }.from(false)
+      expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
 
       # Upon denying, it redirects to the apps' callback URL
-      expect(page).to redirect_to_callback_url
+      click_on I18n.t('doorkeeper.authorizations.buttons.deny')
+      expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
+
+      # It does not grant the app access to the account
+      expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be false
     end
 
     context 'when the user has set up TOTP' do
       let(:user) { Fabricate(:user, email: email, password: password, otp_required_for_login: true, otp_secret: User.generate_otp_secret) }
 
       it 'when accepting the authorization request' do
+        params = { client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' }
         visit "/oauth/authorize?#{params.to_query}"
 
         # It presents the user with a log-in page
@@ -201,17 +202,18 @@ RSpec.describe 'Using OAuth from an external app' do
 
         # Filling in the correct TOTP code redirects to an app authorization page
         fill_in_otp_details(user.current_otp)
-        expect(page).to have_content(oauth_authorize_text)
-
-        # It grants the app access to the account
-        expect { click_on oauth_authorize_text }
-          .to change { user_has_grant_with_client_app? }.to(true)
+        expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
 
         # Upon authorizing, it redirects to the apps' callback URL
-        expect(page).to redirect_to_callback_url
+        click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
+        expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
+
+        # It grants the app access to the account
+        expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be true
       end
 
       it 'when rejecting the authorization request' do
+        params = { client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' }
         visit "/oauth/authorize?#{params.to_query}"
 
         # It presents the user with a log-in page
@@ -231,14 +233,14 @@ RSpec.describe 'Using OAuth from an external app' do
 
         # Filling in the correct TOTP code redirects to an app authorization page
         fill_in_otp_details(user.current_otp)
-        expect(page).to have_content(oauth_authorize_text)
-
-        # It does not grant the app access to the account
-        expect { click_on oauth_deny_text }
-          .to_not change { user_has_grant_with_client_app? }.from(false)
+        expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
 
         # Upon denying, it redirects to the apps' callback URL
-        expect(page).to redirect_to_callback_url
+        click_on I18n.t('doorkeeper.authorizations.buttons.deny')
+        expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
+
+        # It does not grant the app access to the account
+        expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be false
       end
     end
     # TODO: external auth
@@ -250,24 +252,4 @@ RSpec.describe 'Using OAuth from an external app' do
     fill_in 'user_otp_attempt', with: value
     click_on I18n.t('auth.login')
   end
-
-  def oauth_authorize_text
-    I18n.t('doorkeeper.authorizations.buttons.authorize')
-  end
-
-  def oauth_deny_text
-    I18n.t('doorkeeper.authorizations.buttons.deny')
-  end
-
-  def redirect_to_callback_url
-    have_current_path(/\A#{client_app.redirect_uri}/, url: true)
-  end
-
-  def user_has_grant_with_client_app?
-    Doorkeeper::AccessGrant
-      .exists?(
-        application: client_app,
-        resource_owner_id: user.id
-      )
-  end
 end
diff --git a/spec/system/ocr_spec.rb b/spec/system/ocr_spec.rb
index 0f9f6575ea..fc816b6dba 100644
--- a/spec/system/ocr_spec.rb
+++ b/spec/system/ocr_spec.rb
@@ -26,8 +26,8 @@ RSpec.describe 'OCR', :attachment_processing, :inline_jobs, :js, :streaming do
       end
     end
 
-    click_on('Add text from image')
+    click_on('Detect text from picture')
 
-    expect(page).to have_css('#description', text: /Hello Mastodon\s*/, wait: 10)
+    expect(page).to have_css('#upload-modal__description', text: /Hello Mastodon\s*/, wait: 10)
   end
 end
diff --git a/spec/system/privacy_spec.rb b/spec/system/privacy_spec.rb
index f2e4d5a993..631440ebb2 100644
--- a/spec/system/privacy_spec.rb
+++ b/spec/system/privacy_spec.rb
@@ -8,6 +8,5 @@ RSpec.describe 'Privacy policy page' do
 
     expect(page)
       .to have_css('noscript', text: /Mastodon/)
-      .and have_css('body', class: 'app-body')
   end
 end
diff --git a/spec/system/profile_spec.rb b/spec/system/profile_spec.rb
index f9449b52c3..7e3cbfd334 100644
--- a/spec/system/profile_spec.rb
+++ b/spec/system/profile_spec.rb
@@ -5,18 +5,19 @@ require 'rails_helper'
 RSpec.describe 'Profile' do
   include ProfileStories
 
+  subject { page }
+
   let(:local_domain) { Rails.configuration.x.local_domain }
 
   before do
     as_a_logged_in_user
-    Fabricate(:user, account: Fabricate(:account, username: 'alice'))
+    with_alice_as_local_user
   end
 
-  it 'I can view public account page for Alice' do
+  it 'I can view Annes public account' do
     visit account_path('alice')
 
-    expect(page)
-      .to have_title("alice (@alice@#{local_domain})")
+    expect(subject).to have_title("alice (@alice@#{local_domain})")
   end
 
   it 'I can change my account' do
@@ -25,31 +26,8 @@ RSpec.describe 'Profile' do
     fill_in 'Display name', with: 'Bob'
     fill_in 'Bio', with: 'Bob is silent'
 
-    fill_in 'account_fields_attributes_0_name', with: 'Personal Website'
-    fill_in 'account_fields_attributes_0_value', with: 'https://host.example/personal'
-
-    fill_in 'account_fields_attributes_1_name', with: 'Professional Biography'
-    fill_in 'account_fields_attributes_1_value', with: 'https://host.example/pro'
-
-    expect { submit_form }
-      .to change { bob.account.reload.display_name }.to('Bob')
-      .and(change_account_fields)
-    expect(page)
-      .to have_content 'Changes successfully saved!'
-  end
-
-  def submit_form
     first('button[type=submit]').click
-  end
 
-  def change_account_fields
-    change { bob.account.reload.fields }
-      .from([])
-      .to(
-        contain_exactly(
-          be_a(Account::Field),
-          be_a(Account::Field)
-        )
-      )
+    expect(subject).to have_content 'Changes successfully saved!'
   end
 end
diff --git a/spec/system/report_interface_spec.rb b/spec/system/report_interface_spec.rb
index 3df6b3714b..257a1cd6fd 100644
--- a/spec/system/report_interface_spec.rb
+++ b/spec/system/report_interface_spec.rb
@@ -28,19 +28,4 @@ RSpec.describe 'report interface', :attachment_processing, :js, :streaming do
     page.scroll_to(page.find('.batch-table__row'))
     expect(page).to have_css('.spoiler-button__overlay__label')
   end
-
-  it 'marks a report resolved from the show page actions area' do
-    visit admin_report_path(report)
-
-    expect { resolve_report }
-      .to change { report.reload.action_taken_at }.to(be_present).from(nil)
-  end
-
-  def resolve_report
-    within '.report-actions' do
-      click_on I18n.t('admin.reports.mark_as_resolved')
-    end
-    expect(page)
-      .to have_content(I18n.t('admin.reports.resolved_msg'))
-  end
 end
diff --git a/spec/system/settings/applications_spec.rb b/spec/system/settings/applications_spec.rb
index 5a8c97dd1e..ee43da3d5d 100644
--- a/spec/system/settings/applications_spec.rb
+++ b/spec/system/settings/applications_spec.rb
@@ -38,9 +38,6 @@ RSpec.describe 'Settings applications page' do
       expect(page)
         .to have_content(I18n.t('doorkeeper.applications.index.title'))
         .and have_content('My new app')
-        .and have_content('read')
-        .and have_content('write')
-        .and have_content('follow')
     end
 
     it 'does not save with invalid form values' do
@@ -76,12 +73,10 @@ RSpec.describe 'Settings applications page' do
 
       fill_in form_app_name_label,
               with: 'My new app name with a new value'
-      check 'push', id: :doorkeeper_application_scopes_push
       submit_form
 
       expect(page)
         .to have_content('My new app name with a new value')
-        .and have_checked_field('push', id: :doorkeeper_application_scopes_push)
     end
 
     it 'does not update with wrong values' do
@@ -96,7 +91,7 @@ RSpec.describe 'Settings applications page' do
     end
 
     def submit_form
-      click_on(submit_button)
+      click_on I18n.t('generic.save_changes')
     end
   end
 
diff --git a/spec/system/settings/deletes_spec.rb b/spec/system/settings/deletes_spec.rb
deleted file mode 100644
index 91f7104252..0000000000
--- a/spec/system/settings/deletes_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Deletes' do
-  describe 'Deleting user from settings area' do
-    let(:user) { Fabricate(:user) }
-
-    before { sign_in(user) }
-
-    it 'requires password and deletes user record', :inline_jobs do
-      visit settings_delete_path
-      expect(page)
-        .to have_title(I18n.t('settings.delete'))
-        .and have_private_cache_control
-
-      # Wrong confirmation value
-      fill_in 'form_delete_confirmation_password', with: 'wrongvalue'
-      click_on I18n.t('deletes.proceed')
-      expect(page)
-        .to have_content(I18n.t('deletes.challenge_not_passed'))
-
-      # Correct confirmation value
-      fill_in 'form_delete_confirmation_password', with: user.password
-      click_on I18n.t('deletes.proceed')
-      expect(page)
-        .to have_content(I18n.t('deletes.success_msg'))
-      expect(page)
-        .to have_title(I18n.t('auth.login'))
-      expect(User.find_by(id: user.id))
-        .to be_nil
-      expect(user.account.reload)
-        .to be_suspended
-      expect(CanonicalEmailBlock.block?(user.email))
-        .to be(false)
-    end
-  end
-end
diff --git a/spec/system/settings/featured_tags_spec.rb b/spec/system/settings/featured_tags_spec.rb
deleted file mode 100644
index 2460817f94..0000000000
--- a/spec/system/settings/featured_tags_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Featured Tags' do
-  let(:user) { Fabricate(:user) }
-
-  before { sign_in(user) }
-
-  describe 'Managing tags' do
-    let(:tag) { Fabricate(:tag) }
-    let(:status) { Fabricate :status, account: user.account }
-
-    before { status.tags << tag }
-
-    it 'Views, adds, and removes featured tags' do
-      visit settings_featured_tags_path
-
-      # Link to existing tag used on a status
-      expect(page.body)
-        .to include(
-          settings_featured_tags_path(featured_tag: { name: tag.name })
-        )
-
-      # Invalid entry
-      fill_in 'featured_tag_name', with: 'test, #foo !bleh'
-      expect { click_on I18n.t('featured_tags.add_new') }
-        .to_not change(user.account.featured_tags, :count)
-
-      # Valid entry
-      fill_in 'featured_tag_name', with: '#friends'
-      expect { click_on I18n.t('featured_tags.add_new') }
-        .to change(user.account.featured_tags, :count).by(1)
-
-      # Delete the created entry
-      expect { click_on I18n.t('filters.index.delete') }
-        .to change(user.account.featured_tags, :count).by(-1)
-      expect(page)
-        .to have_title(I18n.t('settings.featured_tags'))
-    end
-  end
-end
diff --git a/spec/system/settings/login_activities_spec.rb b/spec/system/settings/login_activities_spec.rb
deleted file mode 100644
index ff89c3f37e..0000000000
--- a/spec/system/settings/login_activities_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Login activities page' do
-  let!(:user) { Fabricate :user }
-  let!(:login_activity) { Fabricate :login_activity, user: user }
-
-  context 'when signed in' do
-    before { sign_in user }
-
-    describe 'Viewing the login activities page' do
-      it 'shows the login activity history' do
-        visit edit_user_registration_path
-
-        click_on I18n.t('sessions.view_authentication_history')
-
-        expect(page)
-          .to have_content(browser_description)
-          .and have_content(login_activity.authentication_method)
-          .and have_content(login_activity.ip)
-          .and have_private_cache_control
-      end
-
-      def browser_description
-        I18n.t(
-          'sessions.description',
-          browser: I18n.t("sessions.browsers.#{login_activity.browser}", default: login_activity.browser),
-          platform: I18n.t("sessions.platforms.#{login_activity.platform}", default: login_activity.platform)
-        )
-      end
-    end
-  end
-end
diff --git a/spec/system/settings/migration/redirects_spec.rb b/spec/system/settings/migration/redirects_spec.rb
deleted file mode 100644
index b59be5ac1f..0000000000
--- a/spec/system/settings/migration/redirects_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Migration Redirects' do
-  let!(:user) { Fabricate(:user, password: 'testtest') }
-
-  before { sign_in(user) }
-
-  describe 'Managing redirects' do
-    before { stub_resolver }
-
-    it 'creates and destroys redirects' do
-      visit new_settings_migration_redirect_path
-      expect(page)
-        .to have_title(I18n.t('settings.migrate'))
-
-      # Empty form invalid submission
-      expect { click_on I18n.t('migrations.set_redirect') }
-        .to_not(change { user.account.moved_to_account_id }.from(nil))
-
-      # Valid form submission
-      fill_in 'form_redirect_acct', with: 'new@example.host'
-      fill_in 'form_redirect_current_password', with: 'testtest'
-      expect { click_on I18n.t('migrations.set_redirect') }
-        .to(change { user.reload.account.moved_to_account_id }.from(nil))
-
-      # Delete the account move
-      expect { click_on I18n.t('migrations.cancel') }
-        .to(change { user.reload.account.moved_to_account_id }.to(nil))
-      expect(page)
-        .to have_content(I18n.t('migrations.cancelled_msg'))
-    end
-
-    private
-
-    def stub_resolver
-      resolver = instance_double(ResolveAccountService, call: Fabricate(:account))
-      allow(ResolveAccountService).to receive(:new).and_return(resolver)
-    end
-  end
-end
diff --git a/spec/system/settings/migrations_spec.rb b/spec/system/settings/migrations_spec.rb
deleted file mode 100644
index fecde36f35..0000000000
--- a/spec/system/settings/migrations_spec.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Migrations' do
-  describe 'Viewing settings migrations' do
-    let(:user) { Fabricate(:account, moved_to_account: moved_to_account).user }
-
-    before { sign_in(user) }
-
-    context 'when user does not have moved to account' do
-      let(:moved_to_account) { nil }
-
-      it 'renders show page' do
-        visit settings_migration_path
-
-        expect(page)
-          .to have_content(I18n.t('settings.migrate'))
-      end
-    end
-
-    context 'when user has a moved to account' do
-      let(:moved_to_account) { Fabricate(:account) }
-
-      it 'renders show page and account details' do
-        visit settings_migration_path
-
-        expect(page)
-          .to have_content(I18n.t('settings.migrate'))
-          .and have_content(moved_to_account.pretty_acct)
-      end
-    end
-  end
-
-  describe 'Creating migrations' do
-    let(:user) { Fabricate(:user, password: '12345678') }
-
-    before { sign_in(user) }
-
-    context 'when migration account is changed' do
-      let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) }
-
-      it 'updates moved to account' do
-        visit settings_migration_path
-
-        expect { fill_in_and_submit }
-          .to(change { user.account.reload.moved_to_account_id }.to(acct.id))
-        expect(page)
-          .to have_content(I18n.t('settings.migrate'))
-      end
-    end
-
-    context 'when acct is the current account' do
-      let(:acct) { user.account }
-
-      it 'does not update the moved account', :aggregate_failures do
-        visit settings_migration_path
-
-        expect { fill_in_and_submit }
-          .to_not(change { user.account.reload.moved_to_account_id }.from(nil))
-        expect(page)
-          .to have_content(I18n.t('settings.migrate'))
-      end
-    end
-
-    context 'when target account does not reference the account being moved from' do
-      let(:acct) { Fabricate(:account, also_known_as: []) }
-
-      it 'does not update the moved account', :aggregate_failures do
-        visit settings_migration_path
-
-        expect { fill_in_and_submit }
-          .to_not(change { user.account.reload.moved_to_account_id }.from(nil))
-        expect(page)
-          .to have_content(I18n.t('settings.migrate'))
-      end
-    end
-
-    context 'when a recent migration already exists' do
-      let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) }
-      let(:moved_to) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) }
-
-      before { user.account.migrations.create!(acct: moved_to.acct) }
-
-      it 'can not update the moved account', :aggregate_failures do
-        visit settings_migration_path
-
-        expect(find_by_id('account_migration_acct'))
-          .to be_disabled
-      end
-    end
-
-    def fill_in_and_submit
-      fill_in 'account_migration_acct', with: acct.username
-      fill_in 'account_migration_current_password', with: '12345678'
-      click_on I18n.t('migrations.proceed_with_move')
-    end
-  end
-end
diff --git a/spec/system/settings/preferences/appearance_spec.rb b/spec/system/settings/preferences/appearance_spec.rb
deleted file mode 100644
index e8fb0c5de8..0000000000
--- a/spec/system/settings/preferences/appearance_spec.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings preferences appearance page' do
-  let(:user) { Fabricate :user }
-
-  before { sign_in user }
-
-  it 'Views and updates user prefs' do
-    visit settings_preferences_appearance_path
-
-    expect(page)
-      .to have_private_cache_control
-
-    select 'contrast', from: theme_selection_field
-    check confirm_reblog_field
-    uncheck confirm_delete_field
-
-    check advanced_layout_field
-
-    expect { save_changes }
-      .to change { user.reload.settings.theme }.to('contrast')
-      .and change { user.reload.settings['web.reblog_modal'] }.to(true)
-      .and change { user.reload.settings['web.delete_modal'] }.to(false)
-      .and(change { user.reload.settings['web.advanced_layout'] }.to(true))
-    expect(page)
-      .to have_title(I18n.t('settings.appearance'))
-  end
-
-  def save_changes
-    within('form') { click_on submit_button }
-  end
-
-  def confirm_delete_field
-    form_label('defaults.setting_delete_modal')
-  end
-
-  def confirm_reblog_field
-    form_label('defaults.setting_boost_modal')
-  end
-
-  def theme_selection_field
-    form_label('defaults.setting_theme')
-  end
-
-  def advanced_layout_field
-    form_label('defaults.setting_advanced_layout')
-  end
-end
diff --git a/spec/system/settings/preferences/notifications_spec.rb b/spec/system/settings/preferences/notifications_spec.rb
deleted file mode 100644
index c9d2c4270b..0000000000
--- a/spec/system/settings/preferences/notifications_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings preferences notifications page' do
-  let(:user) { Fabricate :user }
-
-  before { sign_in user }
-
-  it 'Views and updates user prefs' do
-    visit settings_preferences_notifications_path
-
-    expect(page)
-      .to have_private_cache_control
-
-    uncheck notifications_follow_field
-
-    expect { click_on submit_button }
-      .to change { user.reload.settings['notification_emails.follow'] }.to(false)
-    expect(page)
-      .to have_title(I18n.t('settings.notifications'))
-  end
-
-  def notifications_follow_field
-    form_label('notification_emails.follow')
-  end
-end
diff --git a/spec/system/settings/preferences/other_spec.rb b/spec/system/settings/preferences/other_spec.rb
deleted file mode 100644
index d741ec1ad2..0000000000
--- a/spec/system/settings/preferences/other_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings preferences other page' do
-  let(:user) { Fabricate :user }
-
-  before { sign_in user }
-
-  it 'Views and updates user prefs' do
-    visit settings_preferences_other_path
-
-    expect(page)
-      .to have_private_cache_control
-
-    check language_field(:es)
-    check language_field(:fr)
-    check mark_sensitive_field
-
-    expect { save_changes }
-      .to change { user.reload.chosen_languages }.to(%w(es fr))
-      .and(change { user.reload.settings.default_sensitive }.to(true))
-    expect(page)
-      .to have_title(I18n.t('settings.preferences'))
-  end
-
-  def save_changes
-    within('form') { click_on submit_button }
-  end
-
-  def mark_sensitive_field
-    form_label('defaults.setting_default_sensitive')
-  end
-
-  def language_field(key)
-    LanguagesHelper::SUPPORTED_LOCALES[key].last
-  end
-end
diff --git a/spec/system/settings/privacy_spec.rb b/spec/system/settings/privacy_spec.rb
deleted file mode 100644
index 5e1498613e..0000000000
--- a/spec/system/settings/privacy_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Privacy' do
-  let!(:user) { Fabricate(:user) }
-
-  before { sign_in(user) }
-
-  describe 'Managing privacy settings' do
-    before { user.account.update(discoverable: false) }
-
-    context 'with a successful update' do
-      it 'updates user profile information' do
-        # View settings page
-        visit settings_privacy_path
-        expect(page)
-          .to have_content(I18n.t('privacy.title'))
-          .and have_private_cache_control
-
-        # Fill out form and submit
-        check 'account_discoverable'
-        check 'account_indexable'
-        expect { click_on submit_button }
-          .to change { user.account.reload.discoverable }.to(true)
-        expect(page)
-          .to have_content(I18n.t('privacy.title'))
-          .and have_content(success_message)
-        expect(ActivityPub::UpdateDistributionWorker)
-          .to have_enqueued_sidekiq_job(user.account.id)
-      end
-    end
-
-    context 'with a failed update' do
-      before do
-        allow(UpdateAccountService).to receive(:new).and_return(failing_update_service)
-      end
-
-      it 'updates user profile information' do
-        # View settings page
-        visit settings_privacy_path
-        expect(page)
-          .to have_content(I18n.t('privacy.title'))
-          .and have_private_cache_control
-
-        # Fill out form and submit
-        check 'account_discoverable'
-        check 'account_indexable'
-        expect { click_on submit_button }
-          .to_not(change { user.account.reload.discoverable })
-        expect(page)
-          .to have_content(I18n.t('privacy.title'))
-        expect(ActivityPub::UpdateDistributionWorker)
-          .to_not have_enqueued_sidekiq_job(anything)
-      end
-
-      private
-
-      def failing_update_service
-        instance_double(UpdateAccountService, call: false)
-      end
-    end
-  end
-end
diff --git a/spec/system/settings/profiles_spec.rb b/spec/system/settings/profiles_spec.rb
deleted file mode 100644
index 23d9ab2fda..0000000000
--- a/spec/system/settings/profiles_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings profile page' do
-  let(:user) { Fabricate :user }
-  let(:account) { user.account }
-
-  before do
-    sign_in user
-  end
-
-  it 'Views and updates profile information' do
-    visit settings_profile_path
-
-    expect(page)
-      .to have_private_cache_control
-
-    fill_in display_name_field, with: 'New name'
-    attach_file avatar_field, Rails.root.join('spec', 'fixtures', 'files', 'avatar.gif')
-
-    expect { click_on submit_button }
-      .to change { account.reload.display_name }.to('New name')
-      .and(change { account.reload.avatar.instance.avatar_file_name }.from(nil).to(be_present))
-    expect(ActivityPub::UpdateDistributionWorker)
-      .to have_enqueued_sidekiq_job(account.id)
-  end
-
-  def display_name_field
-    form_label('defaults.display_name')
-  end
-
-  def avatar_field
-    form_label('defaults.avatar')
-  end
-end
diff --git a/spec/system/settings/sessions_spec.rb b/spec/system/settings/sessions_spec.rb
deleted file mode 100644
index ffc7a64185..0000000000
--- a/spec/system/settings/sessions_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings Sessions' do
-  let(:user) { Fabricate(:user) }
-  let!(:session_activation) { Fabricate(:session_activation, user: user) }
-
-  before { sign_in(user) }
-
-  describe 'deleting a session' do
-    it 'deletes listed session activation from the auth page' do
-      visit edit_user_registration_path
-      expect(page)
-        .to have_title(I18n.t('settings.account_settings'))
-
-      expect { click_on(I18n.t('sessions.revoke')) }
-        .to change(SessionActivation, :count).by(-1)
-      expect { session_activation.reload }
-        .to raise_error(ActiveRecord::RecordNotFound)
-      expect(page)
-        .to have_content(I18n.t('sessions.revoke_success'))
-    end
-  end
-end
diff --git a/spec/system/settings/two_factor_authentication/recovery_codes_spec.rb b/spec/system/settings/two_factor_authentication/recovery_codes_spec.rb
deleted file mode 100644
index ba8491429c..0000000000
--- a/spec/system/settings/two_factor_authentication/recovery_codes_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings TwoFactorAuthentication RecoveryCodes' do
-  describe 'Generating recovery codes' do
-    let(:user) { Fabricate :user, otp_required_for_login: true }
-    let(:backup_code) { +'147e7284c95bd260b91ed17820860019' }
-
-    before do
-      stub_code_generator
-      sign_in(user)
-    end
-
-    it 'updates the codes and includes them in the view' do
-      # Attempt to generate codes
-      visit settings_two_factor_authentication_methods_path
-      click_on I18n.t('two_factor_authentication.generate_recovery_codes')
-
-      # Fill in challenge password
-      fill_in 'form_challenge_current_password', with: user.password
-
-      expect { click_on I18n.t('challenge.confirm') }
-        .to(change { user.reload.otp_backup_codes })
-
-      expect(page)
-        .to have_content(I18n.t('two_factor_authentication.recovery_codes_regenerated'))
-        .and have_title(I18n.t('settings.two_factor_authentication'))
-        .and have_css('ol.recovery-codes')
-        .and have_content(backup_code)
-    end
-
-    def stub_code_generator
-      allow(SecureRandom).to receive(:hex).and_return(backup_code)
-    end
-  end
-end
diff --git a/spec/system/settings/two_factor_authentication_methods_spec.rb b/spec/system/settings/two_factor_authentication_methods_spec.rb
deleted file mode 100644
index bed226deb5..0000000000
--- a/spec/system/settings/two_factor_authentication_methods_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings TwoFactorAuthenticationMethods' do
-  context 'when signed in' do
-    let(:user) { Fabricate(:user) }
-
-    before { sign_in user }
-
-    describe 'Managing 2FA methods' do
-      before { user.update(otp_required_for_login: true) }
-
-      it 'disables 2FA with challenge confirmation', :inline_jobs do
-        visit settings_two_factor_authentication_methods_path
-        expect(page)
-          .to have_content(I18n.t('settings.two_factor_authentication'))
-          .and have_private_cache_control
-
-        # Attempt to disable
-        click_on I18n.t('two_factor_authentication.disable')
-        expect(page)
-          .to have_title(I18n.t('challenge.prompt'))
-
-        # Fill in challenge form
-        fill_in 'form_challenge_current_password', with: user.password
-        emails = capture_emails do
-          expect { click_on I18n.t('challenge.confirm') }
-            .to change { user.reload.otp_required_for_login }.to(false)
-        end
-
-        expect(page)
-          .to have_content(I18n.t('two_factor_authentication.disabled_success'))
-        expect(emails.first)
-          .to be_present
-          .and(deliver_to(user.email))
-          .and(have_subject(I18n.t('devise.mailer.two_factor_disabled.subject')))
-      end
-    end
-  end
-end
diff --git a/spec/system/settings/verifications_spec.rb b/spec/system/settings/verifications_spec.rb
deleted file mode 100644
index 87220057ed..0000000000
--- a/spec/system/settings/verifications_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Settings verification page' do
-  let(:user) { Fabricate :user }
-
-  before { sign_in user }
-
-  describe 'Viewing the verification page' do
-    it 'shows the page and updates attribution' do
-      visit settings_verification_path
-
-      expect(page)
-        .to have_content(verification_summary)
-        .and have_private_cache_control
-
-      fill_in attribution_field, with: " example.com\n\n  https://example.net"
-
-      expect { click_on submit_button }
-        .to(change { user.account.reload.attribution_domains }.to(['example.com', 'example.net']))
-      expect(page)
-        .to have_content(success_message)
-      expect(find_field(attribution_field).value)
-        .to have_content("example.com\nexample.net")
-    end
-
-    it 'rejects invalid attribution domains' do
-      visit settings_verification_path
-
-      fill_in attribution_field, with: "example.com \n invalid_com"
-
-      expect { click_on submit_button }
-        .to_not(change { user.account.reload.attribution_domains })
-      expect(page)
-        .to have_content(I18n.t('activerecord.errors.messages.invalid_domain_on_line', value: 'invalid_com'))
-      expect(find_field(attribution_field).value)
-        .to have_content("example.com\ninvalid_com")
-    end
-  end
-
-  def verification_summary
-    I18n.t('verification.website_verification')
-  end
-
-  def attribution_field
-    form_label('account.attribution_domains')
-  end
-end
diff --git a/spec/system/share_entrypoint_spec.rb b/spec/system/share_entrypoint_spec.rb
index b55ea31657..7ccfee599a 100644
--- a/spec/system/share_entrypoint_spec.rb
+++ b/spec/system/share_entrypoint_spec.rb
@@ -23,14 +23,24 @@ RSpec.describe 'Share page', :js, :streaming do
     fill_in_form
 
     expect(page)
-      .to have_css('.notification-bar-message', text: frontend_translations('compose.published.body'))
+      .to have_css('.notification-bar-message', text: translations['compose.published.body'])
   end
 
   def fill_in_form
     within('.compose-form') do
-      fill_in frontend_translations('compose_form.placeholder'),
+      fill_in translations['compose_form.placeholder'],
               with: 'This is a new status!'
-      click_on frontend_translations('compose_form.publish')
+      click_on translations['compose_form.publish']
     end
   end
+
+  def translations
+    # TODO: Extract to system spec helper for re-use?
+    JSON.parse(
+      Rails
+        .root
+        .join('app', 'javascript', 'mastodon', 'locales', 'en.json')
+        .read
+    )
+  end
 end
diff --git a/spec/system/statuses_cleanup_spec.rb b/spec/system/statuses_cleanup_spec.rb
deleted file mode 100644
index 524f710af7..0000000000
--- a/spec/system/statuses_cleanup_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Automated post deletion settings' do
-  let(:user) { Fabricate :user }
-  let(:account) { user.account }
-
-  describe 'Updating settings' do
-    before { sign_in user }
-
-    it 'visits the page and updates the policy' do
-      visit statuses_cleanup_path
-      expect(page)
-        .to have_private_cache_control
-
-      check I18n.t('statuses_cleanup.enabled')
-      submit_form
-      expect(account.reload.statuses_cleanup_policy)
-        .to be_enabled
-
-      uncheck I18n.t('statuses_cleanup.keep_pinned')
-      expect { submit_form }
-        .to change { account.reload.statuses_cleanup_policy.keep_pinned? }.to(false)
-      expect(page)
-        .to have_content(I18n.t('settings.statuses_cleanup'))
-    end
-
-    def submit_form
-      click_on submit_button
-    end
-  end
-end
diff --git a/spec/system/statuses_spec.rb b/spec/system/statuses_spec.rb
deleted file mode 100644
index 704cae03f2..0000000000
--- a/spec/system/statuses_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Status page' do
-  let(:status) { Fabricate :status }
-
-  it 'visits the status page and renders the web app' do
-    visit short_account_status_path(account_username: status.account.username, id: status.id)
-
-    expect(page)
-      .to have_css('noscript', text: /Mastodon/)
-      .and have_css('body', class: 'app-body')
-  end
-end
diff --git a/spec/system/tags_spec.rb b/spec/system/tags_spec.rb
index 9da5768c7f..f39c6bf0d8 100644
--- a/spec/system/tags_spec.rb
+++ b/spec/system/tags_spec.rb
@@ -13,7 +13,6 @@ RSpec.describe 'Tags' do
 
       expect(page)
         .to have_css('noscript', text: /Mastodon/)
-        .and have_css('body', class: 'app-body')
         .and have_private_cache_control
     end
   end
diff --git a/spec/system/terms_of_service_spec.rb b/spec/system/terms_of_service_spec.rb
deleted file mode 100644
index 0de12bae36..0000000000
--- a/spec/system/terms_of_service_spec.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'Terms of Service page' do
-  it 'visits the about page and renders the web app' do
-    visit terms_of_service_path
-
-    expect(page)
-      .to have_css('noscript', text: /Mastodon/)
-      .and have_css('body', class: 'app-body')
-  end
-end
diff --git a/spec/validators/date_of_birth_validator_spec.rb b/spec/validators/date_of_birth_validator_spec.rb
deleted file mode 100644
index 33e69e811b..0000000000
--- a/spec/validators/date_of_birth_validator_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe DateOfBirthValidator do
-  let(:record_class) do
-    Class.new do
-      include ActiveModel::Validations
-
-      attr_accessor :date_of_birth
-
-      validates :date_of_birth, date_of_birth: true
-    end
-  end
-
-  let(:record) { record_class.new }
-
-  before do
-    Setting.min_age = 16
-  end
-
-  describe '#validate_each' do
-    context 'with an invalid date' do
-      it 'adds errors' do
-        record.date_of_birth = '76.830.10'
-
-        expect(record).to_not be_valid
-        expect(record.errors.first.attribute).to eq(:date_of_birth)
-        expect(record.errors.first.type).to eq(:invalid)
-      end
-    end
-
-    context 'with a date below age limit' do
-      it 'adds errors' do
-        record.date_of_birth = 13.years.ago.strftime('%d.%m.%Y')
-
-        expect(record).to_not be_valid
-        expect(record.errors.first.attribute).to eq(:date_of_birth)
-        expect(record.errors.first.type).to eq(:below_limit)
-      end
-    end
-
-    context 'with a date above age limit' do
-      it 'does not add errors' do
-        record.date_of_birth = 16.years.ago.strftime('%d.%m.%Y')
-
-        expect(record).to be_valid
-      end
-    end
-  end
-end
diff --git a/spec/validators/lines_validator_spec.rb b/spec/validators/lines_validator_spec.rb
new file mode 100644
index 0000000000..a80dbbaf3e
--- /dev/null
+++ b/spec/validators/lines_validator_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe LinesValidator do
+  let(:record_class) do
+    Class.new do
+      include ActiveModel::Validations
+
+      attr_accessor :text
+
+      validates :text, lines: { maximum: 5 }
+    end
+  end
+
+  let(:record) { record_class.new }
+
+  describe '#validate_each' do
+    context 'with a nil value' do
+      it 'does not add errors' do
+        record.text = nil
+
+        expect(record).to be_valid
+        expect(record.errors).to be_empty
+      end
+    end
+
+    context 'with lines below the limit' do
+      it 'does not add errors' do
+        record.text = "hoge\n" * 5
+
+        expect(record).to be_valid
+        expect(record.errors).to be_empty
+      end
+    end
+
+    context 'with more lines than limit' do
+      it 'adds an error' do
+        record.text = "hoge\n" * 6
+
+        expect(record).to_not be_valid
+        expect(record.errors.where(:text)).to_not be_empty
+      end
+    end
+  end
+end
diff --git a/spec/validators/note_length_validator_spec.rb b/spec/validators/note_length_validator_spec.rb
index c761c95280..3fdb4ae8b9 100644
--- a/spec/validators/note_length_validator_spec.rb
+++ b/spec/validators/note_length_validator_spec.rb
@@ -30,22 +30,6 @@ RSpec.describe NoteLengthValidator do
       expect(account.errors).to have_received(:add)
     end
 
-    it 'counts multi byte emoji as single character' do
-      text = '✨' * 500
-      account = instance_double(Account, note: text, errors: activemodel_errors)
-
-      subject.validate_each(account, 'note', text)
-      expect(account.errors).to_not have_received(:add)
-    end
-
-    it 'counts ZWJ sequence emoji as single character' do
-      text = '🏳️‍⚧️' * 500
-      account = instance_double(Account, note: text, errors: activemodel_errors)
-
-      subject.validate_each(account, 'note', text)
-      expect(account.errors).to_not have_received(:add)
-    end
-
     private
 
     def starting_string
diff --git a/spec/validators/poll_options_validator_spec.rb b/spec/validators/poll_options_validator_spec.rb
index cc03e9d673..9e4ec744db 100644
--- a/spec/validators/poll_options_validator_spec.rb
+++ b/spec/validators/poll_options_validator_spec.rb
@@ -41,31 +41,5 @@ RSpec.describe PollOptionsValidator do
         expect(errors).to have_received(:add)
       end
     end
-
-    describe 'character length of poll options' do
-      context 'when poll has acceptable length options' do
-        let(:options) { %w(test this) }
-
-        it 'has no errors' do
-          expect(errors).to_not have_received(:add)
-        end
-      end
-
-      context 'when poll has multibyte and ZWJ emoji options' do
-        let(:options) { ['✨' * described_class::MAX_OPTION_CHARS, '🏳️‍⚧️' * described_class::MAX_OPTION_CHARS] }
-
-        it 'has no errors' do
-          expect(errors).to_not have_received(:add)
-        end
-      end
-
-      context 'when poll has options that are too long' do
-        let(:options) { ['ok', 'a' * (described_class::MAX_OPTION_CHARS**2)] }
-
-        it 'has errors' do
-          expect(errors).to have_received(:add)
-        end
-      end
-    end
   end
 end
diff --git a/spec/validators/status_length_validator_spec.rb b/spec/validators/status_length_validator_spec.rb
index 050b7500bb..ecbfd4ba37 100644
--- a/spec/validators/status_length_validator_spec.rb
+++ b/spec/validators/status_length_validator_spec.rb
@@ -80,22 +80,6 @@ RSpec.describe StatusLengthValidator do
       subject.validate(status)
       expect(status.errors).to have_received(:add)
     end
-
-    it 'counts multi byte emoji as single character' do
-      text = '✨' * 500
-      status = status_double(text: text)
-
-      subject.validate(status)
-      expect(status.errors).to_not have_received(:add)
-    end
-
-    it 'counts ZWJ sequence emoji as single character' do
-      text = '🏳️‍⚧️' * 500
-      status = status_double(text: text)
-
-      subject.validate(status)
-      expect(status.errors).to_not have_received(:add)
-    end
   end
 
   private
diff --git a/spec/workers/account_refresh_worker_spec.rb b/spec/workers/account_refresh_worker_spec.rb
index 4408ef77a9..3e88e8db28 100644
--- a/spec/workers/account_refresh_worker_spec.rb
+++ b/spec/workers/account_refresh_worker_spec.rb
@@ -7,7 +7,9 @@ RSpec.describe AccountRefreshWorker do
   let(:service) { instance_double(ResolveAccountService, call: true) }
 
   describe '#perform' do
-    before { stub_service }
+    before do
+      allow(ResolveAccountService).to receive(:new).and_return(service)
+    end
 
     context 'when account does not exist' do
       it 'returns immediately without processing' do
@@ -46,11 +48,5 @@ RSpec.describe AccountRefreshWorker do
         (Account::BACKGROUND_REFRESH_INTERVAL + 3.days).ago
       end
     end
-
-    def stub_service
-      allow(ResolveAccountService)
-        .to receive(:new)
-        .and_return(service)
-    end
   end
 end
diff --git a/spec/workers/activitypub/delivery_worker_spec.rb b/spec/workers/activitypub/delivery_worker_spec.rb
index 9e6805c68b..3dfbef31a4 100644
--- a/spec/workers/activitypub/delivery_worker_spec.rb
+++ b/spec/workers/activitypub/delivery_worker_spec.rb
@@ -5,45 +5,26 @@ require 'rails_helper'
 RSpec.describe ActivityPub::DeliveryWorker do
   include RoutingHelper
 
-  let(:sender) { Fabricate(:account) }
+  subject { described_class.new }
+
+  let(:sender)  { Fabricate(:account) }
   let(:payload) { 'test' }
-  let(:url) { 'https://example.com/api' }
 
   before do
-    allow(sender).to receive(:remote_followers_hash).with(url).and_return('somehash')
+    allow(sender).to receive(:remote_followers_hash).with('https://example.com/api').and_return('somehash')
     allow(Account).to receive(:find).with(sender.id).and_return(sender)
   end
 
   describe 'perform' do
-    context 'with successful request' do
-      before { stub_request(:post, url).to_return(status: 200) }
-
-      it 'performs a request to synchronize collection' do
-        subject.perform(payload, sender.id, url, { synchronize_followers: true })
-
-        expect(request_to_url)
-          .to have_been_made.once
-      end
-
-      def request_to_url
-        a_request(:post, url)
-          .with(
-            headers: {
-              'Collection-Synchronization' => <<~VALUES.squish,
-                collectionId="#{account_followers_url(sender)}", digest="somehash", url="#{account_followers_synchronization_url(sender)}"
-              VALUES
-            }
-          )
-      end
+    it 'performs a request' do
+      stub_request(:post, 'https://example.com/api').to_return(status: 200)
+      subject.perform(payload, sender.id, 'https://example.com/api', { synchronize_followers: true })
+      expect(a_request(:post, 'https://example.com/api').with(headers: { 'Collection-Synchronization' => "collectionId=\"#{account_followers_url(sender)}\", digest=\"somehash\", url=\"#{account_followers_synchronization_url(sender)}\"" })).to have_been_made.once
     end
 
-    context 'with failing request' do
-      before { stub_request(:post, url).to_return(status: 500) }
-
-      it 'raises error' do
-        expect { subject.perform(payload, sender.id, url) }
-          .to raise_error Mastodon::UnexpectedResponseError
-      end
+    it 'raises when request fails' do
+      stub_request(:post, 'https://example.com/api').to_return(status: 500)
+      expect { subject.perform(payload, sender.id, 'https://example.com/api') }.to raise_error Mastodon::UnexpectedResponseError
     end
   end
 end
diff --git a/spec/workers/activitypub/fetch_all_replies_worker_spec.rb b/spec/workers/activitypub/fetch_all_replies_worker_spec.rb
deleted file mode 100644
index 4746d742d0..0000000000
--- a/spec/workers/activitypub/fetch_all_replies_worker_spec.rb
+++ /dev/null
@@ -1,281 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ActivityPub::FetchAllRepliesWorker do
-  subject { described_class.new }
-
-  let(:top_items) do
-    [
-      'http://example.com/self-reply-1',
-      'http://other.com/other-reply-2',
-      'http://example.com/self-reply-3',
-    ]
-  end
-
-  let(:top_items_paged) do
-    [
-      'http://example.com/self-reply-4',
-      'http://other.com/other-reply-5',
-      'http://example.com/self-reply-6',
-    ]
-  end
-
-  let(:nested_items) do
-    [
-      'http://example.com/nested-self-reply-1',
-      'http://other.com/nested-other-reply-2',
-      'http://example.com/nested-self-reply-3',
-    ]
-  end
-
-  let(:nested_items_paged) do
-    [
-      'http://example.com/nested-self-reply-4',
-      'http://other.com/nested-other-reply-5',
-      'http://example.com/nested-self-reply-6',
-    ]
-  end
-
-  let(:all_items) do
-    top_items + top_items_paged + nested_items + nested_items_paged
-  end
-
-  let(:top_note_uri) do
-    'http://example.com/top-post'
-  end
-
-  let(:top_collection_uri) do
-    'http://example.com/top-post/replies'
-  end
-
-  # The reply uri that has the nested replies under it
-  let(:reply_note_uri) do
-    'http://other.com/other-reply-2'
-  end
-
-  # The collection uri of nested replies
-  let(:reply_collection_uri) do
-    'http://other.com/other-reply-2/replies'
-  end
-
-  let(:replies_top) do
-    {
-      '@context': 'https://www.w3.org/ns/activitystreams',
-      id: top_collection_uri,
-      type: 'Collection',
-      items: top_items + top_items_paged,
-    }
-  end
-
-  let(:replies_nested) do
-    {
-      '@context': 'https://www.w3.org/ns/activitystreams',
-      id: reply_collection_uri,
-      type: 'Collection',
-      items: nested_items + nested_items_paged,
-    }
-  end
-
-  # The status resource for the top post
-  let(:top_object) do
-    {
-      '@context': 'https://www.w3.org/ns/activitystreams',
-      id: top_note_uri,
-      type: 'Note',
-      content: 'Lorem ipsum',
-      replies: replies_top,
-      attributedTo: 'https://example.com',
-    }
-  end
-
-  # The status resource that has the uri to the replies collection
-  let(:reply_object) do
-    {
-      '@context': 'https://www.w3.org/ns/activitystreams',
-      id: reply_note_uri,
-      type: 'Note',
-      content: 'Lorem ipsum',
-      replies: replies_nested,
-      attributedTo: 'https://other.com',
-    }
-  end
-
-  let(:empty_object) do
-    {
-      '@context': 'https://www.w3.org/ns/activitystreams',
-      id: 'https://example.com/empty',
-      type: 'Note',
-      content: 'Lorem ipsum',
-      replies: [],
-      attributedTo: 'https://example.com',
-    }
-  end
-
-  let(:account) { Fabricate(:account, domain: 'example.com') }
-  let(:status) do
-    Fabricate(
-      :status,
-      account: account,
-      uri: top_note_uri,
-      created_at: 1.day.ago - Status::FetchRepliesConcern::FETCH_REPLIES_INITIAL_WAIT_MINUTES
-    )
-  end
-
-  before do
-    stub_const('Status::FetchRepliesConcern::FETCH_REPLIES_ENABLED', true)
-    allow(FetchReplyWorker).to receive(:push_bulk)
-    all_items.each do |item|
-      next if [top_note_uri, reply_note_uri].include? item
-
-      stub_request(:get, item).to_return(status: 200, body: Oj.dump(empty_object), headers: { 'Content-Type': 'application/activity+json' })
-    end
-
-    stub_request(:get, top_note_uri).to_return(status: 200, body: Oj.dump(top_object), headers: { 'Content-Type': 'application/activity+json' })
-    stub_request(:get, reply_note_uri).to_return(status: 200, body: Oj.dump(reply_object), headers: { 'Content-Type': 'application/activity+json' })
-  end
-
-  shared_examples 'fetches all replies' do
-    it 'fetches statuses recursively' do
-      got_uris = subject.perform(status.id)
-      expect(got_uris).to match_array(all_items)
-    end
-
-    it 'respects the maximum limits set by not recursing after the max is reached' do
-      stub_const('ActivityPub::FetchAllRepliesWorker::MAX_REPLIES', 5)
-      got_uris = subject.perform(status.id)
-      expect(got_uris).to match_array(top_items + top_items_paged)
-    end
-  end
-
-  describe 'perform' do
-    context 'when the payload is a Note with replies as a Collection of inlined replies' do
-      it_behaves_like 'fetches all replies'
-    end
-
-    context 'when the payload is a Note with replies as a URI to a Collection' do
-      let(:top_object) do
-        {
-          '@context': 'https://www.w3.org/ns/activitystreams',
-          id: top_note_uri,
-          type: 'Note',
-          content: 'Lorem ipsum',
-          replies: top_collection_uri,
-          attributedTo: 'https://example.com',
-        }
-      end
-      let(:reply_object) do
-        {
-          '@context': 'https://www.w3.org/ns/activitystreams',
-          id: reply_note_uri,
-          type: 'Note',
-          content: 'Lorem ipsum',
-          replies: reply_collection_uri,
-          attributedTo: 'https://other.com',
-        }
-      end
-
-      before do
-        stub_request(:get, top_collection_uri).to_return(status: 200, body: Oj.dump(replies_top), headers: { 'Content-Type': 'application/activity+json' })
-        stub_request(:get, reply_collection_uri).to_return(status: 200, body: Oj.dump(replies_nested), headers: { 'Content-Type': 'application/activity+json' })
-      end
-
-      it_behaves_like 'fetches all replies'
-    end
-
-    context 'when the payload is a Note with replies as a paginated collection' do
-      let(:top_page_2_uri) do
-        "#{top_collection_uri}/2"
-      end
-
-      let(:reply_page_2_uri) do
-        "#{reply_collection_uri}/2"
-      end
-
-      let(:top_object) do
-        {
-          '@context': 'https://www.w3.org/ns/activitystreams',
-          id: top_note_uri,
-          type: 'Note',
-          content: 'Lorem ipsum',
-          replies: {
-            type: 'Collection',
-            id: top_collection_uri,
-            first: {
-              type: 'CollectionPage',
-              partOf: top_collection_uri,
-              items: top_items,
-              next: top_page_2_uri,
-            },
-          },
-          attributedTo: 'https://example.com',
-        }
-      end
-      let(:reply_object) do
-        {
-          '@context': 'https://www.w3.org/ns/activitystreams',
-          id: reply_note_uri,
-          type: 'Note',
-          content: 'Lorem ipsum',
-          replies: {
-            type: 'Collection',
-            id: reply_collection_uri,
-            first: {
-              type: 'CollectionPage',
-              partOf: reply_collection_uri,
-              items: nested_items,
-              next: reply_page_2_uri,
-            },
-          },
-          attributedTo: 'https://other.com',
-        }
-      end
-
-      let(:top_page_two) do
-        {
-          type: 'CollectionPage',
-          id: top_page_2_uri,
-          partOf: top_collection_uri,
-          items: top_items_paged,
-        }
-      end
-
-      let(:reply_page_two) do
-        {
-          type: 'CollectionPage',
-          id: reply_page_2_uri,
-          partOf: reply_collection_uri,
-          items: nested_items_paged,
-        }
-      end
-
-      before do
-        stub_request(:get, top_page_2_uri).to_return(status: 200, body: Oj.dump(top_page_two), headers: { 'Content-Type': 'application/activity+json' })
-        stub_request(:get, reply_page_2_uri).to_return(status: 200, body: Oj.dump(reply_page_two), headers: { 'Content-Type': 'application/activity+json' })
-      end
-
-      it_behaves_like 'fetches all replies'
-
-      it 'limits by max pages' do
-        stub_const('ActivityPub::FetchAllRepliesWorker::MAX_PAGES', 3)
-        got_uris = subject.perform(status.id)
-        expect(got_uris).to match_array(top_items + top_items_paged + nested_items)
-      end
-    end
-
-    context 'when replies should not be fetched' do
-      # ensure that we should not fetch by setting the status to be created in the debounce window
-      let(:status) { Fabricate(:status, account: account, uri: top_note_uri, created_at: DateTime.now) }
-
-      before do
-        stub_const('Status::FetchRepliesConcern::FETCH_REPLIES_INITIAL_WAIT_MINUTES', 1.week)
-      end
-
-      it 'returns nil without fetching' do
-        got_uris = subject.perform(status.id)
-        expect(got_uris).to be_nil
-        assert_not_requested :get, top_note_uri
-      end
-    end
-  end
-end
diff --git a/spec/workers/activitypub/followers_synchronization_worker_spec.rb b/spec/workers/activitypub/followers_synchronization_worker_spec.rb
deleted file mode 100644
index 0847b247e3..0000000000
--- a/spec/workers/activitypub/followers_synchronization_worker_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ActivityPub::FollowersSynchronizationWorker do
-  let(:worker) { described_class.new }
-  let(:service) { instance_double(ActivityPub::SynchronizeFollowersService, call: true) }
-
-  describe '#perform' do
-    before { stub_service }
-
-    let(:account) { Fabricate(:account, domain: 'host.example') }
-    let(:url) { 'https://sync.url' }
-
-    it 'sends the status to the service' do
-      worker.perform(account.id, url)
-
-      expect(service).to have_received(:call).with(account, url)
-    end
-
-    it 'returns nil for non-existent record' do
-      result = worker.perform(123_123_123, url)
-
-      expect(result).to be(true)
-    end
-  end
-
-  def stub_service
-    allow(ActivityPub::SynchronizeFollowersService)
-      .to receive(:new)
-      .and_return(service)
-  end
-end
diff --git a/spec/workers/admin/distribute_announcement_notification_worker_spec.rb b/spec/workers/admin/distribute_announcement_notification_worker_spec.rb
deleted file mode 100644
index 0e618418b0..0000000000
--- a/spec/workers/admin/distribute_announcement_notification_worker_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Admin::DistributeAnnouncementNotificationWorker do
-  let(:worker) { described_class.new }
-
-  describe '#perform' do
-    context 'with missing record' do
-      it 'runs without error' do
-        expect { worker.perform(nil) }.to_not raise_error
-      end
-    end
-
-    context 'with valid announcement' do
-      let(:announcement) { Fabricate(:announcement) }
-      let!(:user) { Fabricate :user, confirmed_at: 3.days.ago }
-
-      it 'sends the announcement via email', :inline_jobs do
-        emails = capture_emails { worker.perform(announcement.id) }
-
-        expect(emails.size)
-          .to eq(1)
-        expect(emails.first)
-          .to have_attributes(
-            to: [user.email],
-            subject: I18n.t('user_mailer.announcement_published.subject')
-          )
-      end
-    end
-  end
-end
diff --git a/spec/workers/admin/distribute_terms_of_service_notification_worker_spec.rb b/spec/workers/admin/distribute_terms_of_service_notification_worker_spec.rb
deleted file mode 100644
index 27ddfb28bc..0000000000
--- a/spec/workers/admin/distribute_terms_of_service_notification_worker_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Admin::DistributeTermsOfServiceNotificationWorker do
-  let(:worker) { described_class.new }
-
-  describe '#perform' do
-    context 'with missing record' do
-      it 'runs without error' do
-        expect { worker.perform(nil) }.to_not raise_error
-      end
-    end
-
-    context 'with valid terms' do
-      let(:terms) { Fabricate(:terms_of_service) }
-      let!(:user) { Fabricate :user, confirmed_at: 3.days.ago }
-
-      it 'sends the terms update via email', :inline_jobs do
-        emails = capture_emails { worker.perform(terms.id) }
-
-        expect(emails.size)
-          .to eq(1)
-        expect(emails.first)
-          .to have_attributes(
-            to: [user.email],
-            subject: I18n.t('user_mailer.terms_of_service_changed.subject')
-          )
-      end
-    end
-  end
-end
diff --git a/spec/workers/import_worker_spec.rb b/spec/workers/import_worker_spec.rb
new file mode 100644
index 0000000000..1d34aafe86
--- /dev/null
+++ b/spec/workers/import_worker_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe ImportWorker do
+  let(:worker) { described_class.new }
+  let(:service) { instance_double(ImportService, call: true) }
+
+  describe '#perform' do
+    before do
+      allow(ImportService).to receive(:new).and_return(service)
+    end
+
+    let(:import) { Fabricate(:import) }
+
+    it 'sends the import to the service' do
+      worker.perform(import.id)
+
+      expect(service).to have_received(:call).with(import)
+      expect { import.reload }.to raise_error(ActiveRecord::RecordNotFound)
+    end
+  end
+end
diff --git a/spec/workers/poll_expiration_notify_worker_spec.rb b/spec/workers/poll_expiration_notify_worker_spec.rb
index 190630608c..b3ccdd3d77 100644
--- a/spec/workers/poll_expiration_notify_worker_spec.rb
+++ b/spec/workers/poll_expiration_notify_worker_spec.rb
@@ -33,11 +33,15 @@ RSpec.describe PollExpirationNotifyWorker do
       end
 
       context 'when poll is local' do
-        it 'notifies voters, owner, and local voters' do
+        it 'notifies voters' do
           expect(ActivityPub::DistributePollUpdateWorker).to have_enqueued_sidekiq_job(poll.status.id)
+        end
 
+        it 'notifies owner' do
           expect(LocalNotificationWorker).to have_enqueued_sidekiq_job(poll.account.id, poll.id, 'Poll', 'poll')
+        end
 
+        it 'notifies local voters' do
           expect(LocalNotificationWorker).to have_enqueued_sidekiq_job(poll_vote.account.id, poll.id, 'Poll', 'poll')
         end
       end
@@ -45,11 +49,15 @@ RSpec.describe PollExpirationNotifyWorker do
       context 'when poll is remote' do
         let(:remote?) { true }
 
-        it 'does not notify remote voters or owner, does notify local voters' do
+        it 'does not notify remote voters' do
           expect(ActivityPub::DistributePollUpdateWorker).to_not have_enqueued_sidekiq_job(poll.status.id)
+        end
 
+        it 'does not notify owner' do
           expect(LocalNotificationWorker).to_not have_enqueued_sidekiq_job(poll.account.id, poll.id, 'Poll', 'poll')
+        end
 
+        it 'notifies local voters' do
           expect(LocalNotificationWorker).to have_enqueued_sidekiq_job(poll_vote.account.id, poll.id, 'Poll', 'poll')
         end
       end
diff --git a/spec/workers/publish_scheduled_status_worker_spec.rb b/spec/workers/publish_scheduled_status_worker_spec.rb
index a91e665965..35e510d253 100644
--- a/spec/workers/publish_scheduled_status_worker_spec.rb
+++ b/spec/workers/publish_scheduled_status_worker_spec.rb
@@ -12,22 +12,12 @@ RSpec.describe PublishScheduledStatusWorker do
       subject.perform(scheduled_status.id)
     end
 
-    context 'when the account is not disabled' do
-      it 'creates a status and removes scheduled record' do
-        expect(scheduled_status.account.statuses.first.text).to eq 'Hello world, future!'
-
-        expect(ScheduledStatus.find_by(id: scheduled_status.id)).to be_nil
-      end
+    it 'creates a status' do
+      expect(scheduled_status.account.statuses.first.text).to eq 'Hello world, future!'
     end
 
-    context 'when the account is disabled' do
-      let(:scheduled_status) { Fabricate(:scheduled_status, account: Fabricate(:account, user: Fabricate(:user, disabled: true))) }
-
-      it 'does not create a status and removes scheduled record' do
-        expect(Status.count).to eq 0
-
-        expect(ScheduledStatus.find_by(id: scheduled_status.id)).to be_nil
-      end
+    it 'removes the scheduled status' do
+      expect(ScheduledStatus.find_by(id: scheduled_status.id)).to be_nil
     end
   end
 end
diff --git a/spec/workers/push_conversation_worker_spec.rb b/spec/workers/push_conversation_worker_spec.rb
index a98c603c17..d651059c9a 100644
--- a/spec/workers/push_conversation_worker_spec.rb
+++ b/spec/workers/push_conversation_worker_spec.rb
@@ -6,30 +6,8 @@ RSpec.describe PushConversationWorker do
   let(:worker) { described_class.new }
 
   describe 'perform' do
-    context 'with missing values' do
-      it 'runs without error' do
-        expect { worker.perform(nil) }
-          .to_not raise_error
-      end
-    end
-
-    context 'with valid records' do
-      let(:account_conversation) { Fabricate :account_conversation }
-
-      before { allow(redis).to receive(:publish) }
-
-      it 'pushes message to timeline' do
-        expect { worker.perform(account_conversation.id) }
-          .to_not raise_error
-
-        expect(redis)
-          .to have_received(:publish)
-          .with(redis_key, anything)
-      end
-
-      def redis_key
-        "timeline:direct:#{account_conversation.account_id}"
-      end
+    it 'runs without error for missing record' do
+      expect { worker.perform(nil) }.to_not raise_error
     end
   end
 end
diff --git a/spec/workers/push_update_worker_spec.rb b/spec/workers/push_update_worker_spec.rb
index f3e0a128df..6206ab5986 100644
--- a/spec/workers/push_update_worker_spec.rb
+++ b/spec/workers/push_update_worker_spec.rb
@@ -6,31 +6,11 @@ RSpec.describe PushUpdateWorker do
   let(:worker) { described_class.new }
 
   describe 'perform' do
-    context 'with missing values' do
-      it 'runs without error' do
-        expect { worker.perform(nil, nil) }
-          .to_not raise_error
-      end
-    end
+    it 'runs without error for missing record' do
+      account_id = nil
+      status_id = nil
 
-    context 'with valid records' do
-      let(:account) { Fabricate :account }
-      let(:status) { Fabricate :status }
-
-      before { allow(redis).to receive(:publish) }
-
-      it 'pushes message to timeline' do
-        expect { worker.perform(account.id, status.id) }
-          .to_not raise_error
-
-        expect(redis)
-          .to have_received(:publish)
-          .with(redis_key, anything)
-      end
-
-      def redis_key
-        "timeline:#{account.id}"
-      end
+      expect { worker.perform(account_id, status_id) }.to_not raise_error
     end
   end
 end
diff --git a/spec/workers/remote_account_refresh_worker_spec.rb b/spec/workers/remote_account_refresh_worker_spec.rb
deleted file mode 100644
index 9938d971b9..0000000000
--- a/spec/workers/remote_account_refresh_worker_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe RemoteAccountRefreshWorker do
-  let(:worker) { described_class.new }
-  let(:service) { instance_double(ActivityPub::FetchRemoteAccountService, call: true) }
-
-  describe '#perform' do
-    before { stub_service }
-
-    let(:account) { Fabricate(:account, domain: 'host.example') }
-
-    it 'sends the status to the service' do
-      worker.perform(account.id)
-
-      expect(service).to have_received(:call).with(account.uri)
-    end
-
-    it 'returns nil for non-existent record' do
-      result = worker.perform(123_123_123)
-
-      expect(result).to be_nil
-    end
-
-    it 'returns nil for a local record' do
-      account = Fabricate :account, domain: nil
-      result = worker.perform(account)
-      expect(result).to be_nil
-    end
-
-    def stub_service
-      allow(ActivityPub::FetchRemoteAccountService)
-        .to receive(:new)
-        .and_return(service)
-    end
-  end
-end
diff --git a/spec/workers/remove_featured_tag_worker_spec.rb b/spec/workers/remove_featured_tag_worker_spec.rb
index a8a5bcea81..7866824ee7 100644
--- a/spec/workers/remove_featured_tag_worker_spec.rb
+++ b/spec/workers/remove_featured_tag_worker_spec.rb
@@ -4,35 +4,12 @@ require 'rails_helper'
 
 RSpec.describe RemoveFeaturedTagWorker do
   let(:worker) { described_class.new }
-  let(:service) { instance_double(RemoveFeaturedTagService, call: true) }
 
   describe 'perform' do
-    context 'with missing values' do
-      it 'runs without error' do
-        expect { worker.perform(nil, nil) }
-          .to_not raise_error
-      end
-    end
-
-    context 'with real records' do
-      before { stub_service }
-
-      let(:account) { Fabricate :account }
-      let(:featured_tag) { Fabricate :featured_tag }
-
-      it 'calls the service for processing' do
-        worker.perform(account.id, featured_tag.id)
-
-        expect(service)
-          .to have_received(:call)
-          .with(be_an(Account), be_an(FeaturedTag))
-      end
-
-      def stub_service
-        allow(RemoveFeaturedTagService)
-          .to receive(:new)
-          .and_return(service)
-      end
+    it 'runs without error for missing record' do
+      account_id = nil
+      featured_tag_id = nil
+      expect { worker.perform(account_id, featured_tag_id) }.to_not raise_error
     end
   end
 end
diff --git a/spec/workers/resolve_account_worker_spec.rb b/spec/workers/resolve_account_worker_spec.rb
index 58c2c12129..de349adacb 100644
--- a/spec/workers/resolve_account_worker_spec.rb
+++ b/spec/workers/resolve_account_worker_spec.rb
@@ -4,34 +4,10 @@ require 'rails_helper'
 
 RSpec.describe ResolveAccountWorker do
   let(:worker) { described_class.new }
-  let(:service) { instance_double(ResolveAccountService, call: true) }
 
   describe 'perform' do
-    context 'with missing values' do
-      it 'runs without error' do
-        expect { worker.perform(nil) }
-          .to_not raise_error
-      end
-    end
-
-    context 'with a URI' do
-      before { stub_service }
-
-      let(:uri) { 'https://host/path/value' }
-
-      it 'initiates account resolution' do
-        worker.perform(uri)
-
-        expect(service)
-          .to have_received(:call)
-          .with(uri)
-      end
-
-      def stub_service
-        allow(ResolveAccountService)
-          .to receive(:new)
-          .and_return(service)
-      end
+    it 'runs without error for missing record' do
+      expect { worker.perform(nil) }.to_not raise_error
     end
   end
 end
diff --git a/spec/workers/scheduler/auto_close_registrations_scheduler_spec.rb b/spec/workers/scheduler/auto_close_registrations_scheduler_spec.rb
index af3c3fba34..d9355248ba 100644
--- a/spec/workers/scheduler/auto_close_registrations_scheduler_spec.rb
+++ b/spec/workers/scheduler/auto_close_registrations_scheduler_spec.rb
@@ -9,8 +9,8 @@ RSpec.describe Scheduler::AutoCloseRegistrationsScheduler do
     let(:moderator_activity_date) { Time.now.utc }
 
     before do
-      Fabricate(:owner_user, current_sign_in_at: 10.years.ago)
-      Fabricate(:moderator_user, current_sign_in_at: moderator_activity_date)
+      Fabricate(:user, role: UserRole.find_by(name: 'Owner'), current_sign_in_at: 10.years.ago)
+      Fabricate(:user, role: UserRole.find_by(name: 'Moderator'), current_sign_in_at: moderator_activity_date)
     end
 
     context 'when registrations are open' do
diff --git a/spec/workers/unfilter_notifications_worker_spec.rb b/spec/workers/unfilter_notifications_worker_spec.rb
index 2fd130301f..464a4520ff 100644
--- a/spec/workers/unfilter_notifications_worker_spec.rb
+++ b/spec/workers/unfilter_notifications_worker_spec.rb
@@ -5,7 +5,6 @@ require 'rails_helper'
 RSpec.describe UnfilterNotificationsWorker do
   let(:recipient) { Fabricate(:account) }
   let(:sender) { Fabricate(:account) }
-  let(:worker) { described_class.new }
 
   before do
     # Populate multiple kinds of filtered notifications
@@ -68,22 +67,23 @@ RSpec.describe UnfilterNotificationsWorker do
   end
 
   describe '#perform' do
-    context 'with recipient and sender' do
-      subject { worker.perform(recipient.id, sender.id) }
+    context 'with single argument (prerelease behavior)' do
+      subject { described_class.new.perform(notification_request.id) }
+
+      let(:notification_request) { Fabricate(:notification_request, from_account: sender, account: recipient) }
+
+      it_behaves_like 'shared behavior'
+
+      it 'destroys the notification request' do
+        expect { subject }
+          .to change { NotificationRequest.exists?(notification_request.id) }.to(false)
+      end
+    end
+
+    context 'with two arguments' do
+      subject { described_class.new.perform(recipient.id, sender.id) }
 
       it_behaves_like 'shared behavior'
     end
-
-    context 'with missing records' do
-      it 'runs without error for missing sender' do
-        expect { worker.perform(recipient.id, nil) }
-          .to_not raise_error
-      end
-
-      it 'runs without error for missing recipient' do
-        expect { worker.perform(nil, sender.id) }
-          .to_not raise_error
-      end
-    end
   end
 end
diff --git a/spec/workers/unfollow_follow_worker_spec.rb b/spec/workers/unfollow_follow_worker_spec.rb
index 2e05d1ca78..7b9d49b902 100644
--- a/spec/workers/unfollow_follow_worker_spec.rb
+++ b/spec/workers/unfollow_follow_worker_spec.rb
@@ -18,11 +18,14 @@ RSpec.describe UnfollowFollowWorker do
     let(:show_reblogs) { true }
 
     describe 'perform' do
-      it 'unfollows source account and follows target account and preserves show_reblogs' do
+      it 'unfollows source account and follows target account' do
         subject.perform(local_follower.id, source_account.id, target_account.id)
         expect(local_follower.following?(source_account)).to be false
         expect(local_follower.following?(target_account)).to be true
+      end
 
+      it 'preserves show_reblogs' do
+        subject.perform(local_follower.id, source_account.id, target_account.id)
         expect(Follow.find_by(account: local_follower, target_account: target_account).show_reblogs?).to be show_reblogs
       end
     end
@@ -32,11 +35,14 @@ RSpec.describe UnfollowFollowWorker do
     let(:show_reblogs) { false }
 
     describe 'perform' do
-      it 'unfollows source account and follows target account and preserves show_reblogs' do
+      it 'unfollows source account and follows target account' do
         subject.perform(local_follower.id, source_account.id, target_account.id)
         expect(local_follower.following?(source_account)).to be false
         expect(local_follower.following?(target_account)).to be true
+      end
 
+      it 'preserves show_reblogs' do
+        subject.perform(local_follower.id, source_account.id, target_account.id)
         expect(Follow.find_by(account: local_follower, target_account: target_account).show_reblogs?).to be show_reblogs
       end
     end
diff --git a/spec/workers/web/push_notification_worker_spec.rb b/spec/workers/web/push_notification_worker_spec.rb
index 6ee8ae53f8..7f836d99e4 100644
--- a/spec/workers/web/push_notification_worker_spec.rb
+++ b/spec/workers/web/push_notification_worker_spec.rb
@@ -5,36 +5,21 @@ require 'rails_helper'
 RSpec.describe Web::PushNotificationWorker do
   subject { described_class.new }
 
+  let(:p256dh) { 'BN4GvZtEZiZuqFxSKVZfSfluwKBD7UxHNBmWkfiZfCtgDE8Bwh-_MtLXbBxTBAWH9r7IPKL0lhdcaqtL1dfxU5E=' }
+  let(:auth) { 'Q2BoAjC09xH3ywDLNJr-dA==' }
   let(:endpoint) { 'https://updates.push.services.mozilla.com/push/v1/subscription-id' }
   let(:user) { Fabricate(:user) }
   let(:notification) { Fabricate(:notification) }
+  let(:subscription) { Fabricate(:web_push_subscription, user_id: user.id, key_p256dh: p256dh, key_auth: auth, endpoint: endpoint, data: { alerts: { notification.type => true } }) }
   let(:vapid_public_key) { 'BB37UCyc8LLX4PNQSe-04vSFvpUWGrENubUaslVFM_l5TxcGVMY0C3RXPeUJAQHKYlcOM2P4vTYmkoo0VZGZTM4=' }
   let(:vapid_private_key) { 'OPrw1Sum3gRoL4-DXfSCC266r-qfFSRZrnj8MgIhRHg=' }
   let(:vapid_key) { Webpush::VapidKey.from_keys(vapid_public_key, vapid_private_key) }
   let(:contact_email) { 'sender@example.com' }
-
-  # Legacy values
-  let(:p256dh) { 'BN4GvZtEZiZuqFxSKVZfSfluwKBD7UxHNBmWkfiZfCtgDE8Bwh-_MtLXbBxTBAWH9r7IPKL0lhdcaqtL1dfxU5E=' }
-  let(:auth) { 'Q2BoAjC09xH3ywDLNJr-dA==' }
-  let(:legacy_subscription) { Fabricate(:web_push_subscription, user_id: user.id, key_p256dh: p256dh, key_auth: auth, endpoint: endpoint, data: { alerts: { notification.type => true } }) }
-  let(:legacy_payload) do
-    {
-      ciphertext: "+\xB8\xDBT}\x13\xB6\xDD.\xF9\xB0\xA7\xC8\xD2\x80\xFD\x99#\xF7\xAC\x83\xA4\xDB,\x1F\xB5\xB9w\x85>\xF7\xADr",
-      salt: "X\x97\x953\xE4X\xF8_w\xE7T\x95\xC51q\xFE",
-      server_public_key: "\x04\b-RK9w\xDD$\x16lFz\xF9=\xB4~\xC6\x12k\xF3\xF40t\xA9\xC1\fR\xC3\x81\x80\xAC\f\x7F\xE4\xCC\x8E\xC2\x88 n\x8BB\xF1\x9C\x14\a\xFA\x8D\xC9\x80\xA1\xDDyU\\&c\x01\x88#\x118Ua",
-      shared_secret: "\t\xA7&\x85\t\xC5m\b\xA8\xA7\xF8B{1\xADk\xE1y'm\xEDE\xEC\xDD\xEDj\xB3$s\xA9\xDA\xF0",
-    }
-  end
-
-  # Standard values, from RFC8291
-  let(:std_p256dh) { 'BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4' }
-  let(:std_auth) { 'BTBZMqHH6r4Tts7J_aSIgg' }
-  let(:std_as_public) { 'BP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A8' }
-  let(:std_as_private) { 'yfWPiYE-n46HLnH0KqZOF1fJJU3MYrct3AELtAQ-oRw' }
-  let(:std_salt) { 'DGv6ra1nlYgDCS1FRnbzlw' }
-  let(:std_subscription) { Fabricate(:web_push_subscription, user_id: user.id, key_p256dh: std_p256dh, key_auth: std_auth, endpoint: endpoint, standard: true, data: { alerts: { notification.type => true } }) }
-  let(:std_input) { 'When I grow up, I want to be a watermelon' }
-  let(:std_ciphertext) { 'DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A_yl95bQpu6cVPTpK4Mqgkf1CXztLVBSt2Ks3oZwbuwXPXLWyouBWLVWGNWQexSgSxsj_Qulcy4a-fN' }
+  let(:ciphertext) { "+\xB8\xDBT}\x13\xB6\xDD.\xF9\xB0\xA7\xC8\xD2\x80\xFD\x99#\xF7\xAC\x83\xA4\xDB,\x1F\xB5\xB9w\x85>\xF7\xADr" }
+  let(:salt) { "X\x97\x953\xE4X\xF8_w\xE7T\x95\xC51q\xFE" }
+  let(:server_public_key) { "\x04\b-RK9w\xDD$\x16lFz\xF9=\xB4~\xC6\x12k\xF3\xF40t\xA9\xC1\fR\xC3\x81\x80\xAC\f\x7F\xE4\xCC\x8E\xC2\x88 n\x8BB\xF1\x9C\x14\a\xFA\x8D\xC9\x80\xA1\xDDyU\\&c\x01\x88#\x118Ua" }
+  let(:shared_secret) { "\t\xA7&\x85\t\xC5m\b\xA8\xA7\xF8B{1\xADk\xE1y'm\xEDE\xEC\xDD\xEDj\xB3$s\xA9\xDA\xF0" }
+  let(:payload) { { ciphertext: ciphertext, salt: salt, server_public_key: server_public_key, shared_secret: shared_secret } }
 
   describe 'perform' do
     around do |example|
@@ -50,40 +35,20 @@ RSpec.describe Web::PushNotificationWorker do
     before do
       Setting.site_contact_email = contact_email
 
+      allow(Webpush::Encryption).to receive(:encrypt).and_return(payload)
       allow(JWT).to receive(:encode).and_return('jwt.encoded.payload')
 
       stub_request(:post, endpoint).to_return(status: 201, body: '')
     end
 
-    it 'Legacy push calls the relevant service with the legacy headers' do
-      allow(Webpush::Legacy::Encryption).to receive(:encrypt).and_return(legacy_payload)
+    it 'calls the relevant service with the correct headers' do
+      subject.perform(subscription.id, notification.id)
 
-      subject.perform(legacy_subscription.id, notification.id)
-
-      expect(legacy_web_push_endpoint_request)
+      expect(web_push_endpoint_request)
         .to have_been_made
     end
 
-    # We allow subject stub to encrypt the same input than the RFC8291 example
-    # rubocop:disable RSpec/SubjectStub
-    it 'Standard push calls the relevant service with the standard headers' do
-      # Mock server keys to match RFC example
-      allow(OpenSSL::PKey::EC).to receive(:generate).and_return(std_as_keys)
-      # Mock the random salt to match RFC example
-      rand = Random.new
-      allow(Random).to receive(:new).and_return(rand)
-      allow(rand).to receive(:bytes).and_return(Webpush.decode64(std_salt))
-      # Mock input to match RFC example
-      allow(subject).to receive(:push_notification_json).and_return(std_input)
-
-      subject.perform(std_subscription.id, notification.id)
-
-      expect(standard_web_push_endpoint_request)
-        .to have_been_made
-    end
-    # rubocop:enable RSpec/SubjectStub
-
-    def legacy_web_push_endpoint_request
+    def web_push_endpoint_request
       a_request(
         :post,
         endpoint
@@ -96,33 +61,9 @@ RSpec.describe Web::PushNotificationWorker do
           'Ttl' => '172800',
           'Urgency' => 'normal',
           'Authorization' => 'WebPush jwt.encoded.payload',
-          'Unsubscribe-URL' => %r{/api/web/push_subscriptions/},
         },
         body: "+\xB8\xDBT}\u0013\xB6\xDD.\xF9\xB0\xA7\xC8Ҁ\xFD\x99#\xF7\xAC\x83\xA4\xDB,\u001F\xB5\xB9w\x85>\xF7\xADr"
       )
     end
-
-    def standard_web_push_endpoint_request
-      a_request(
-        :post,
-        endpoint
-      ).with(
-        headers: {
-          'Content-Encoding' => 'aes128gcm',
-          'Content-Type' => 'application/octet-stream',
-          'Ttl' => '172800',
-          'Urgency' => 'normal',
-          'Authorization' => "vapid t=jwt.encoded.payload,k=#{vapid_public_key.delete('=')}",
-          'Unsubscribe-URL' => %r{/api/web/push_subscriptions/},
-        },
-        body: Webpush.decode64(std_ciphertext)
-      )
-    end
-
-    def std_as_keys
-      # VapidKey contains a method to retrieve EC keypair from
-      # B64 raw keys, the keypair is stored in curve field
-      Webpush::VapidKey.from_keys(std_as_public, std_as_private).curve
-    end
   end
 end
diff --git a/streaming/.eslintrc.cjs b/streaming/.eslintrc.cjs
new file mode 100644
index 0000000000..e25cff7df0
--- /dev/null
+++ b/streaming/.eslintrc.cjs
@@ -0,0 +1,43 @@
+/* eslint-disable import/no-commonjs */
+
+// @ts-check
+
+// @ts-ignore - This needs to be a CJS file (eslint does not yet support ESM configs), and TS is complaining we use require
+const { defineConfig } = require('eslint-define-config');
+
+module.exports = defineConfig({
+  extends: ['../.eslintrc.js'],
+  env: {
+    browser: false,
+  },
+  parserOptions: {
+    project: true,
+    tsconfigRootDir: __dirname,
+    ecmaFeatures: {
+      jsx: false,
+    },
+    ecmaVersion: 2021,
+  },
+  rules: {
+    // In the streaming server we need to delete some variables to ensure
+    // garbage collection takes place on the values referenced by those objects;
+    // The alternative is to declare the variable as nullable, but then we need
+    // to assert it's in existence before every use, which becomes much harder
+    // to maintain.
+    'no-delete-var': 'off',
+
+    // This overrides the base configuration for this rule to pick up
+    // dependencies for the streaming server from the correct package.json file.
+    'import/no-extraneous-dependencies': [
+      'error',
+      {
+        devDependencies: ['streaming/.eslintrc.cjs'],
+        optionalDependencies: false,
+        peerDependencies: false,
+        includeTypes: true,
+        packageDir: __dirname,
+      },
+    ],
+    'import/extensions': ['error', 'always'],
+  },
+});
diff --git a/streaming/Dockerfile b/streaming/Dockerfile
index 14f2d3c7e7..938f1655d1 100644
--- a/streaming/Dockerfile
+++ b/streaming/Dockerfile
@@ -1,4 +1,4 @@
-# syntax=docker/dockerfile:1.12
+# syntax=docker/dockerfile:1.9
 
 # Please see https://docs.docker.com/engine/reference/builder for information about
 # the extended buildx capabilities used in this file.
@@ -6,15 +6,14 @@
 # See: https://docs.docker.com/build/building/multi-platform/
 ARG TARGETPLATFORM=${TARGETPLATFORM}
 ARG BUILDPLATFORM=${BUILDPLATFORM}
-ARG BASE_REGISTRY="docker.io"
 
 # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
 # renovate: datasource=node-version depName=node
-ARG NODE_MAJOR_VERSION="22"
+ARG NODE_MAJOR_VERSION="20"
 # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
 ARG DEBIAN_VERSION="bookworm"
 # Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
-FROM ${BASE_REGISTRY}/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS streaming
+FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS streaming
 
 # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA
 # Example: v4.3.0-nightly.2023.11.09+pr-123456
diff --git a/streaming/database.js b/streaming/database.js
index 553c9149cc..9f1d742143 100644
--- a/streaming/database.js
+++ b/streaming/database.js
@@ -49,7 +49,7 @@ export function configFromEnv(env, environment) {
     if (typeof parsedUrl.password === 'string') baseConfig.password = parsedUrl.password;
     if (typeof parsedUrl.host === 'string') baseConfig.host = parsedUrl.host;
     if (typeof parsedUrl.user === 'string') baseConfig.user = parsedUrl.user;
-    if (typeof parsedUrl.port === 'string' && parsedUrl.port) {
+    if (typeof parsedUrl.port === 'string') {
       const parsedPort = parseInt(parsedUrl.port, 10);
       if (isNaN(parsedPort)) {
         throw new Error('Invalid port specified in DATABASE_URL environment variable');
@@ -116,44 +116,13 @@ let pool;
 /**
  *
  * @param {pg.PoolConfig} config
- * @param {string} environment
- * @param {import('pino').Logger} logger
  * @returns {pg.Pool}
  */
-export function getPool(config, environment, logger) {
+export function getPool(config) {
   if (pool) {
     return pool;
   }
 
   pool = new pg.Pool(config);
-
-  // Setup logging on pool.query and client.query for checked out clients:
-  // This is taken from: https://node-postgres.com/guides/project-structure
-  if (environment === 'development') {
-    const logQuery = (originalQuery) => {
-      return async (queryTextOrConfig, values, ...rest) => {
-        const start = process.hrtime();
-
-        const result = await originalQuery.apply(pool, [queryTextOrConfig, values, ...rest]);
-
-        const duration = process.hrtime(start);
-        const durationInMs = (duration[0] * 1000000000 + duration[1]) / 1000000;
-
-        logger.debug({
-          query: queryTextOrConfig,
-          values,
-          duration: durationInMs
-        }, 'Executed database query');
-
-        return result;
-      };
-    };
-
-    pool.on('connect', (client) => {
-      const originalQuery = client.query.bind(client);
-      client.query = logQuery(originalQuery);
-    });
-  }
-
   return pool;
 }
diff --git a/streaming/eslint.config.mjs b/streaming/eslint.config.mjs
deleted file mode 100644
index 51a6551514..0000000000
--- a/streaming/eslint.config.mjs
+++ /dev/null
@@ -1,45 +0,0 @@
-// @ts-check
-
-import globals from 'globals';
-import tseslint from 'typescript-eslint';
-
-// eslint-disable-next-line import/no-relative-packages -- Must import from the root
-import { baseConfig } from '../eslint.config.mjs';
-
-export default tseslint.config([
-  baseConfig,
-  {
-    languageOptions: {
-      globals: globals.node,
-
-      parser: tseslint.parser,
-      ecmaVersion: 2021,
-      sourceType: 'module',
-    },
-
-    settings: {
-      'import/ignore': ['node_modules', '\\.(json)$'],
-      'import/resolver': {
-        typescript: {},
-      },
-    },
-
-    rules: {
-      // In the streaming server we need to delete some variables to ensure
-      // garbage collection takes place on the values referenced by those objects;
-      // The alternative is to declare the variable as nullable, but then we need
-      // to assert it's in existence before every use, which becomes much harder
-      // to maintain.
-      'no-delete-var': 'off',
-
-      'import/no-extraneous-dependencies': [
-        'error',
-        {
-          devDependencies: ['**/*.config.mjs'],
-        },
-      ],
-
-      'import/extensions': ['error', 'always'],
-    },
-  },
-]);
diff --git a/streaming/index.js b/streaming/index.js
index ff3ef1c7ba..952e7da6d7 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -101,8 +101,7 @@ const CHANNEL_NAMES = [
 ];
 
 const startServer = async () => {
-  const pgConfig = Database.configFromEnv(process.env, environment);
-  const pgPool = Database.getPool(pgConfig, environment, logger);
+  const pgPool = Database.getPool(Database.configFromEnv(process.env, environment));
 
   const metrics = setupMetrics(CHANNEL_NAMES, pgPool);
 
@@ -784,8 +783,9 @@ const startServer = async () => {
                     // custom_filters.action database column, it is an integer
                     // representing a value in an enum defined by Ruby on Rails:
                     //
-                    // enum { warn: 0, hide: 1 }
-                    filter_action: ['warn', 'hide'][filter.filter_action],
+                    // enum { warn: 0, hide: 1, half_warn: 2 }
+                    filter_action: filter.filter_action === 2 ? 'warn' : ['warn', 'hide', 'half_warn'][filter.filter_action],
+                    filter_action_ex: ['warn', 'hide', 'half_warn'][filter.filter_action],
                     with_quote: filter.with_quote,
                     withAccountName: filter.with_profile,
                     excludeFollows: filter.exclude_follows,
diff --git a/streaming/lint-staged.config.mjs b/streaming/lint-staged.config.mjs
deleted file mode 100644
index 430a999b9a..0000000000
--- a/streaming/lint-staged.config.mjs
+++ /dev/null
@@ -1,7 +0,0 @@
-const config = {
-  '*': 'prettier --ignore-unknown --write',
-  '*.{js,ts}': 'eslint --fix',
-  '**/*.ts': () => 'tsc -p tsconfig.json --noEmit',
-};
-
-export default config;
diff --git a/streaming/package.json b/streaming/package.json
index fa33b575db..d573c9b284 100644
--- a/streaming/package.json
+++ b/streaming/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@mastodon/streaming",
   "license": "AGPL-3.0-or-later",
-  "packageManager": "yarn@4.9.1",
+  "packageManager": "yarn@4.5.0",
   "engines": {
     "node": ">=18"
   },
@@ -21,26 +21,24 @@
     "dotenv": "^16.0.3",
     "express": "^4.18.2",
     "ioredis": "^5.3.2",
-    "jsdom": "^26.0.0",
+    "jsdom": "^25.0.0",
     "pg": "^8.5.0",
     "pg-connection-string": "^2.6.0",
     "pino": "^9.0.0",
     "pino-http": "^10.0.0",
     "prom-client": "^15.0.0",
-    "uuid": "^11.0.0",
+    "uuid": "^10.0.0",
     "ws": "^8.12.1"
   },
   "devDependencies": {
-    "@eslint/js": "^9.23.0",
     "@types/cors": "^2.8.16",
     "@types/express": "^4.17.17",
     "@types/pg": "^8.6.6",
     "@types/uuid": "^10.0.0",
     "@types/ws": "^8.5.9",
-    "globals": "^16.0.0",
-    "pino-pretty": "^13.0.0",
-    "typescript": "~5.7.3",
-    "typescript-eslint": "^8.28.0"
+    "eslint-define-config": "^2.0.0",
+    "pino-pretty": "^11.0.0",
+    "typescript": "^5.0.4"
   },
   "optionalDependencies": {
     "bufferutil": "^4.0.7",
diff --git a/streaming/tsconfig.json b/streaming/tsconfig.json
index 2f892dc3b6..37e9a7fee0 100644
--- a/streaming/tsconfig.json
+++ b/streaming/tsconfig.json
@@ -8,5 +8,5 @@
     "tsBuildInfoFile": "../tmp/cache/streaming/tsconfig.tsbuildinfo",
     "paths": {}
   },
-  "include": ["./*.js"]
+  "include": ["./*.js", "./.eslintrc.cjs"]
 }
diff --git a/yarn.lock b/yarn.lock
index fb750e3e11..65bba72fbd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -42,151 +42,133 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@asamuzakjp/css-color@npm:^2.8.2":
-  version: 2.8.2
-  resolution: "@asamuzakjp/css-color@npm:2.8.2"
+"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/code-frame@npm:7.24.7"
   dependencies:
-    "@csstools/css-calc": "npm:^2.1.1"
-    "@csstools/css-color-parser": "npm:^3.0.7"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
-    lru-cache: "npm:^11.0.2"
-  checksum: 10c0/352b91ca7741876e459cd3cb350a969e842da1e532577157d38365a6da89b7d6e6944249489366ee61b8a225ede1b521e7ab305b70ad4c688b01404061eecca8
-  languageName: node
-  linkType: hard
-
-"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.26.2":
-  version: 7.26.2
-  resolution: "@babel/code-frame@npm:7.26.2"
-  dependencies:
-    "@babel/helper-validator-identifier": "npm:^7.25.9"
-    js-tokens: "npm:^4.0.0"
+    "@babel/highlight": "npm:^7.24.7"
     picocolors: "npm:^1.0.0"
-  checksum: 10c0/7d79621a6849183c415486af99b1a20b84737e8c11cd55b6544f688c51ce1fd710e6d869c3dd21232023da272a79b91efb3e83b5bc2dc65c1187c5fcd1b72ea8
+  checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6
   languageName: node
   linkType: hard
 
-"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.26.5, @babel/compat-data@npm:^7.26.8":
-  version: 7.26.8
-  resolution: "@babel/compat-data@npm:7.26.8"
-  checksum: 10c0/66408a0388c3457fff1c2f6c3a061278dd7b3d2f0455ea29bb7b187fa52c60ae8b4054b3c0a184e21e45f0eaac63cf390737bc7504d1f4a088a6e7f652c068ca
+"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.2, @babel/compat-data@npm:^7.25.4":
+  version: 7.25.4
+  resolution: "@babel/compat-data@npm:7.25.4"
+  checksum: 10c0/50d79734d584a28c69d6f5b99adfaa064d0f41609a378aef04eb06accc5b44f8520e68549eba3a082478180957b7d5783f1bfb1672e4ae8574e797ce8bae79fa
   languageName: node
   linkType: hard
 
-"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.22.1, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.10":
-  version: 7.26.10
-  resolution: "@babel/core@npm:7.26.10"
+"@babel/core@npm:^7.10.4, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.22.1, @babel/core@npm:^7.24.4":
+  version: 7.25.2
+  resolution: "@babel/core@npm:7.25.2"
   dependencies:
     "@ampproject/remapping": "npm:^2.2.0"
-    "@babel/code-frame": "npm:^7.26.2"
-    "@babel/generator": "npm:^7.26.10"
-    "@babel/helper-compilation-targets": "npm:^7.26.5"
-    "@babel/helper-module-transforms": "npm:^7.26.0"
-    "@babel/helpers": "npm:^7.26.10"
-    "@babel/parser": "npm:^7.26.10"
-    "@babel/template": "npm:^7.26.9"
-    "@babel/traverse": "npm:^7.26.10"
-    "@babel/types": "npm:^7.26.10"
+    "@babel/code-frame": "npm:^7.24.7"
+    "@babel/generator": "npm:^7.25.0"
+    "@babel/helper-compilation-targets": "npm:^7.25.2"
+    "@babel/helper-module-transforms": "npm:^7.25.2"
+    "@babel/helpers": "npm:^7.25.0"
+    "@babel/parser": "npm:^7.25.0"
+    "@babel/template": "npm:^7.25.0"
+    "@babel/traverse": "npm:^7.25.2"
+    "@babel/types": "npm:^7.25.2"
     convert-source-map: "npm:^2.0.0"
     debug: "npm:^4.1.0"
     gensync: "npm:^1.0.0-beta.2"
     json5: "npm:^2.2.3"
     semver: "npm:^6.3.1"
-  checksum: 10c0/e046e0e988ab53841b512ee9d263ca409f6c46e2a999fe53024688b92db394346fa3aeae5ea0866331f62133982eee05a675d22922a4603c3f603aa09a581d62
+  checksum: 10c0/a425fa40e73cb72b6464063a57c478bc2de9dbcc19c280f1b55a3d88b35d572e87e8594e7d7b4880331addb6faef641bbeb701b91b41b8806cd4deae5d74f401
   languageName: node
   linkType: hard
 
-"@babel/generator@npm:^7.26.10, @babel/generator@npm:^7.27.0":
-  version: 7.27.0
-  resolution: "@babel/generator@npm:7.27.0"
+"@babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.4, @babel/generator@npm:^7.7.2":
+  version: 7.25.4
+  resolution: "@babel/generator@npm:7.25.4"
   dependencies:
-    "@babel/parser": "npm:^7.27.0"
-    "@babel/types": "npm:^7.27.0"
+    "@babel/types": "npm:^7.25.4"
     "@jridgewell/gen-mapping": "npm:^0.3.5"
     "@jridgewell/trace-mapping": "npm:^0.3.25"
-    jsesc: "npm:^3.0.2"
-  checksum: 10c0/7cb10693d2b365c278f109a745dc08856cae139d262748b77b70ce1d97da84627f79648cab6940d847392c0e5d180441669ed958b3aee98d9c7d274b37c553bd
+    jsesc: "npm:^2.5.1"
+  checksum: 10c0/a2d8cc39e759214740f836360c8d9c17aa93e16e41afe73368a9e7ccd1d5c3303a420ce3aca1c9a31fdb93d1899de471d5aac97d1c64f741f8750a25a6e91fbc
   languageName: node
   linkType: hard
 
-"@babel/generator@npm:^7.7.2":
-  version: 7.26.9
-  resolution: "@babel/generator@npm:7.26.9"
+"@babel/helper-annotate-as-pure@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/helper-annotate-as-pure@npm:7.24.7"
   dependencies:
-    "@babel/parser": "npm:^7.26.9"
-    "@babel/types": "npm:^7.26.9"
-    "@jridgewell/gen-mapping": "npm:^0.3.5"
-    "@jridgewell/trace-mapping": "npm:^0.3.25"
-    jsesc: "npm:^3.0.2"
-  checksum: 10c0/6b78872128205224a9a9761b9ea7543a9a7902a04b82fc2f6801ead4de8f59056bab3fd17b1f834ca7b049555fc4c79234b9a6230dd9531a06525306050becad
+    "@babel/types": "npm:^7.24.7"
+  checksum: 10c0/4679f7df4dffd5b3e26083ae65228116c3da34c3fff2c11ae11b259a61baec440f51e30fd236f7a0435b9d471acd93d0bc5a95df8213cbf02b1e083503d81b9a
   languageName: node
   linkType: hard
 
-"@babel/helper-annotate-as-pure@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-annotate-as-pure@npm:7.25.9"
+"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.24.7"
   dependencies:
-    "@babel/types": "npm:^7.25.9"
-  checksum: 10c0/095b6ba50489d797733abebc4596a81918316a99e3632755c9f02508882912b00c2ae5e468532a25a5c2108d109ddbe9b7da78333ee7cc13817fc50c00cf06fe
+    "@babel/traverse": "npm:^7.24.7"
+    "@babel/types": "npm:^7.24.7"
+  checksum: 10c0/0ed84abf848c79fb1cd4c1ddac12c771d32c1904d87fc3087f33cfdeb0c2e0db4e7892b74b407d9d8d0c000044f3645a7391a781f788da8410c290bb123a1f13
   languageName: node
   linkType: hard
 
-"@babel/helper-builder-react-jsx@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-builder-react-jsx@npm:7.25.9"
+"@babel/helper-builder-react-jsx@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/helper-builder-react-jsx@npm:7.24.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.25.9"
-    "@babel/types": "npm:^7.25.9"
-  checksum: 10c0/99d6e87eede0971f25b5e638220f5f966c56c03f6a6278a693c73ac0a31acddb86110208a89d948aa337c8cf7998fb317c00e8baf2e9fa0a42b9207b977dc9c6
+    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
+    "@babel/types": "npm:^7.24.7"
+  checksum: 10c0/c95c8856c67c57060461f39b669707b2dca3501149bcc54b7c3e49c95069afce52179fc7c2bd809981acfbfdf0860ef1035dd7512cdba46c83bdb1ad6f6dc53f
   languageName: node
   linkType: hard
 
-"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.25.9, @babel/helper-compilation-targets@npm:^7.26.5":
-  version: 7.26.5
-  resolution: "@babel/helper-compilation-targets@npm:7.26.5"
+"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.24.7, @babel/helper-compilation-targets@npm:^7.24.8, @babel/helper-compilation-targets@npm:^7.25.2":
+  version: 7.25.2
+  resolution: "@babel/helper-compilation-targets@npm:7.25.2"
   dependencies:
-    "@babel/compat-data": "npm:^7.26.5"
-    "@babel/helper-validator-option": "npm:^7.25.9"
-    browserslist: "npm:^4.24.0"
+    "@babel/compat-data": "npm:^7.25.2"
+    "@babel/helper-validator-option": "npm:^7.24.8"
+    browserslist: "npm:^4.23.1"
     lru-cache: "npm:^5.1.1"
     semver: "npm:^6.3.1"
-  checksum: 10c0/9da5c77e5722f1a2fcb3e893049a01d414124522bbf51323bb1a0c9dcd326f15279836450fc36f83c9e8a846f3c40e88be032ed939c5a9840922bed6073edfb4
+  checksum: 10c0/de10e986b5322c9f807350467dc845ec59df9e596a5926a3b5edbb4710d8e3b8009d4396690e70b88c3844fe8ec4042d61436dd4b92d1f5f75655cf43ab07e99
   languageName: node
   linkType: hard
 
-"@babel/helper-create-class-features-plugin@npm:^7.25.9":
-  version: 7.27.0
-  resolution: "@babel/helper-create-class-features-plugin@npm:7.27.0"
+"@babel/helper-create-class-features-plugin@npm:^7.24.7, @babel/helper-create-class-features-plugin@npm:^7.25.4":
+  version: 7.25.4
+  resolution: "@babel/helper-create-class-features-plugin@npm:7.25.4"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.25.9"
-    "@babel/helper-member-expression-to-functions": "npm:^7.25.9"
-    "@babel/helper-optimise-call-expression": "npm:^7.25.9"
-    "@babel/helper-replace-supers": "npm:^7.26.5"
-    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.27.0"
+    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
+    "@babel/helper-member-expression-to-functions": "npm:^7.24.8"
+    "@babel/helper-optimise-call-expression": "npm:^7.24.7"
+    "@babel/helper-replace-supers": "npm:^7.25.0"
+    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7"
+    "@babel/traverse": "npm:^7.25.4"
     semver: "npm:^6.3.1"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/c4945903136d934050e070f69a4d72ec425f1f70634e0ddf14ad36695f935125a6df559f8d5b94cc1ed49abd4ce9c5be8ef3ba033fa8d09c5dd78d1a9b97d8cc
+  checksum: 10c0/a765d9e0482e13cf96642fa8aa28e6f7d4d7d39f37840d6246e5e10a7c47f47c52d52522edd3073f229449d17ec0db6f9b7b5e398bff6bb0b4994d65957a164c
   languageName: node
   linkType: hard
 
-"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.9"
+"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7, @babel/helper-create-regexp-features-plugin@npm:^7.25.0, @babel/helper-create-regexp-features-plugin@npm:^7.25.2":
+  version: 7.25.2
+  resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.2"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.25.9"
-    regexpu-core: "npm:^6.1.1"
+    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
+    regexpu-core: "npm:^5.3.1"
     semver: "npm:^6.3.1"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/3adc60a758febbf07d65a15eaccab1f7b9fcc55e7141e59122f13c9f81fc0d1cce4525b7f4af50285d27c93b34c859fd2c39c39820c5fb92211898c3bbdc77ef
+  checksum: 10c0/85a7e3639c118856fb1113f54fb7e3bf7698171ddfd0cd6fccccd5426b3727bc1434fe7f69090441dcde327feef9de917e00d35e47ab820047057518dd675317
   languageName: node
   linkType: hard
 
-"@babel/helper-define-polyfill-provider@npm:^0.6.1, @babel/helper-define-polyfill-provider@npm:^0.6.2, @babel/helper-define-polyfill-provider@npm:^0.6.3":
-  version: 0.6.3
-  resolution: "@babel/helper-define-polyfill-provider@npm:0.6.3"
+"@babel/helper-define-polyfill-provider@npm:^0.6.1, @babel/helper-define-polyfill-provider@npm:^0.6.2":
+  version: 0.6.2
+  resolution: "@babel/helper-define-polyfill-provider@npm:0.6.2"
   dependencies:
     "@babel/helper-compilation-targets": "npm:^7.22.6"
     "@babel/helper-plugin-utils": "npm:^7.22.5"
@@ -195,215 +177,227 @@ __metadata:
     resolve: "npm:^1.14.2"
   peerDependencies:
     "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0
-  checksum: 10c0/4320e3527645e98b6a0d5626fef815680e3b2b03ec36045de5e909b0f01546ab3674e96f50bf3bc8413f8c9037e5ee1a5f560ebdf8210426dad1c2c03c96184a
+  checksum: 10c0/f777fe0ee1e467fdaaac059c39ed203bdc94ef2465fb873316e9e1acfc511a276263724b061e3b0af2f6d7ad3ff174f2bb368fde236a860e0f650fda43d7e022
   languageName: node
   linkType: hard
 
-"@babel/helper-member-expression-to-functions@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-member-expression-to-functions@npm:7.25.9"
+"@babel/helper-member-expression-to-functions@npm:^7.24.8":
+  version: 7.24.8
+  resolution: "@babel/helper-member-expression-to-functions@npm:7.24.8"
   dependencies:
-    "@babel/traverse": "npm:^7.25.9"
-    "@babel/types": "npm:^7.25.9"
-  checksum: 10c0/e08c7616f111e1fb56f398365e78858e26e466d4ac46dff25921adc5ccae9b232f66e952a2f4162bbe336627ba336c7fd9eca4835b6548935973d3380d77eaff
+    "@babel/traverse": "npm:^7.24.8"
+    "@babel/types": "npm:^7.24.8"
+  checksum: 10c0/7e14a5acc91f6cd26305a4441b82eb6f616bd70b096a4d2099a968f16b26d50207eec0b9ebfc466fefd62bd91587ac3be878117cdfec819b7151911183cb0e5a
   languageName: node
   linkType: hard
 
-"@babel/helper-module-imports@npm:^7.0.0-beta.49, @babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-module-imports@npm:7.25.9"
+"@babel/helper-module-imports@npm:^7.0.0-beta.49, @babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/helper-module-imports@npm:7.24.7"
   dependencies:
-    "@babel/traverse": "npm:^7.25.9"
-    "@babel/types": "npm:^7.25.9"
-  checksum: 10c0/078d3c2b45d1f97ffe6bb47f61961be4785d2342a4156d8b42c92ee4e1b7b9e365655dd6cb25329e8fe1a675c91eeac7e3d04f0c518b67e417e29d6e27b6aa70
+    "@babel/traverse": "npm:^7.24.7"
+    "@babel/types": "npm:^7.24.7"
+  checksum: 10c0/97c57db6c3eeaea31564286e328a9fb52b0313c5cfcc7eee4bc226aebcf0418ea5b6fe78673c0e4a774512ec6c86e309d0f326e99d2b37bfc16a25a032498af0
   languageName: node
   linkType: hard
 
-"@babel/helper-module-transforms@npm:^7.25.9, @babel/helper-module-transforms@npm:^7.26.0":
-  version: 7.26.0
-  resolution: "@babel/helper-module-transforms@npm:7.26.0"
+"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.25.0, @babel/helper-module-transforms@npm:^7.25.2":
+  version: 7.25.2
+  resolution: "@babel/helper-module-transforms@npm:7.25.2"
   dependencies:
-    "@babel/helper-module-imports": "npm:^7.25.9"
-    "@babel/helper-validator-identifier": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.25.9"
+    "@babel/helper-module-imports": "npm:^7.24.7"
+    "@babel/helper-simple-access": "npm:^7.24.7"
+    "@babel/helper-validator-identifier": "npm:^7.24.7"
+    "@babel/traverse": "npm:^7.25.2"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/ee111b68a5933481d76633dad9cdab30c41df4479f0e5e1cc4756dc9447c1afd2c9473b5ba006362e35b17f4ebddd5fca090233bef8dfc84dca9d9127e56ec3a
+  checksum: 10c0/adaa15970ace0aee5934b5a633789b5795b6229c6a9cf3e09a7e80aa33e478675eee807006a862aa9aa517935d81f88a6db8a9f5936e3a2a40ec75f8062bc329
   languageName: node
   linkType: hard
 
-"@babel/helper-optimise-call-expression@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-optimise-call-expression@npm:7.25.9"
+"@babel/helper-optimise-call-expression@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/helper-optimise-call-expression@npm:7.24.7"
   dependencies:
-    "@babel/types": "npm:^7.25.9"
-  checksum: 10c0/90203e6607edeadd2a154940803fd616c0ed92c1013d6774c4b8eb491f1a5a3448b68faae6268141caa5c456e55e3ee49a4ed2bd7ddaf2365daea321c435914c
+    "@babel/types": "npm:^7.24.7"
+  checksum: 10c0/ca6a9884705dea5c95a8b3ce132d1e3f2ae951ff74987d400d1d9c215dae9c0f9e29924d8f8e131e116533d182675bc261927be72f6a9a2968eaeeaa51eb1d0f
   languageName: node
   linkType: hard
 
-"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.26.5, @babel/helper-plugin-utils@npm:^7.8.0":
-  version: 7.26.5
-  resolution: "@babel/helper-plugin-utils@npm:7.26.5"
-  checksum: 10c0/cdaba71d4b891aa6a8dfbe5bac2f94effb13e5fa4c2c487667fdbaa04eae059b78b28d85a885071f45f7205aeb56d16759e1bed9c118b94b16e4720ef1ab0f65
+"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3":
+  version: 7.24.8
+  resolution: "@babel/helper-plugin-utils@npm:7.24.8"
+  checksum: 10c0/0376037f94a3bfe6b820a39f81220ac04f243eaee7193774b983e956c1750883ff236b30785795abbcda43fac3ece74750566830c2daa4d6e3870bb0dff34c2d
   languageName: node
   linkType: hard
 
-"@babel/helper-remap-async-to-generator@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-remap-async-to-generator@npm:7.25.9"
+"@babel/helper-remap-async-to-generator@npm:^7.24.7, @babel/helper-remap-async-to-generator@npm:^7.25.0":
+  version: 7.25.0
+  resolution: "@babel/helper-remap-async-to-generator@npm:7.25.0"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.25.9"
-    "@babel/helper-wrap-function": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.25.9"
+    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
+    "@babel/helper-wrap-function": "npm:^7.25.0"
+    "@babel/traverse": "npm:^7.25.0"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/6798b562f2788210980f29c5ee96056d90dc73458c88af5bd32f9c82e28e01975588aa2a57bb866c35556bd9b76bac937e824ee63ba472b6430224b91b4879e9
+  checksum: 10c0/0d17b5f7bb6a607edc9cc62fff8056dd9f341bf2f919884f97b99170d143022a5e7ae57922c4891e4fc360ad291e708d2f8cd8989f1d3cd7a17600159984f5a6
   languageName: node
   linkType: hard
 
-"@babel/helper-replace-supers@npm:^7.25.9, @babel/helper-replace-supers@npm:^7.26.5":
-  version: 7.26.5
-  resolution: "@babel/helper-replace-supers@npm:7.26.5"
+"@babel/helper-replace-supers@npm:^7.24.7, @babel/helper-replace-supers@npm:^7.25.0":
+  version: 7.25.0
+  resolution: "@babel/helper-replace-supers@npm:7.25.0"
   dependencies:
-    "@babel/helper-member-expression-to-functions": "npm:^7.25.9"
-    "@babel/helper-optimise-call-expression": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.26.5"
+    "@babel/helper-member-expression-to-functions": "npm:^7.24.8"
+    "@babel/helper-optimise-call-expression": "npm:^7.24.7"
+    "@babel/traverse": "npm:^7.25.0"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/b19b1245caf835207aaaaac3a494f03a16069ae55e76a2e1350b5acd560e6a820026997a8160e8ebab82ae873e8208759aa008eb8422a67a775df41f0a4633d4
+  checksum: 10c0/b4b6650ab3d56c39a259367cd97f8df2f21c9cebb3716fea7bca40a150f8847bfb82f481e98927c7c6579b48a977b5a8f77318a1c6aeb497f41ecd6dbc3fdfef
   languageName: node
   linkType: hard
 
-"@babel/helper-skip-transparent-expression-wrappers@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.25.9"
+"@babel/helper-simple-access@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/helper-simple-access@npm:7.24.7"
   dependencies:
-    "@babel/traverse": "npm:^7.25.9"
-    "@babel/types": "npm:^7.25.9"
-  checksum: 10c0/09ace0c6156961624ac9524329ce7f45350bab94bbe24335cbe0da7dfaa1448e658771831983cb83fe91cf6635b15d0a3cab57c03b92657480bfb49fb56dd184
+    "@babel/traverse": "npm:^7.24.7"
+    "@babel/types": "npm:^7.24.7"
+  checksum: 10c0/7230e419d59a85f93153415100a5faff23c133d7442c19e0cd070da1784d13cd29096ee6c5a5761065c44e8164f9f80e3a518c41a0256df39e38f7ad6744fed7
   languageName: node
   linkType: hard
 
-"@babel/helper-string-parser@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-string-parser@npm:7.25.9"
-  checksum: 10c0/7244b45d8e65f6b4338a6a68a8556f2cb161b782343e97281a5f2b9b93e420cad0d9f5773a59d79f61d0c448913d06f6a2358a87f2e203cf112e3c5b53522ee6
-  languageName: node
-  linkType: hard
-
-"@babel/helper-validator-identifier@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-validator-identifier@npm:7.25.9"
-  checksum: 10c0/4fc6f830177b7b7e887ad3277ddb3b91d81e6c4a24151540d9d1023e8dc6b1c0505f0f0628ae653601eb4388a8db45c1c14b2c07a9173837aef7e4116456259d
-  languageName: node
-  linkType: hard
-
-"@babel/helper-validator-option@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-validator-option@npm:7.25.9"
-  checksum: 10c0/27fb195d14c7dcb07f14e58fe77c44eea19a6a40a74472ec05c441478fa0bb49fa1c32b2d64be7a38870ee48ef6601bdebe98d512f0253aea0b39756c4014f3e
-  languageName: node
-  linkType: hard
-
-"@babel/helper-wrap-function@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/helper-wrap-function@npm:7.25.9"
+"@babel/helper-skip-transparent-expression-wrappers@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.24.7"
   dependencies:
-    "@babel/template": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.25.9"
-    "@babel/types": "npm:^7.25.9"
-  checksum: 10c0/b6627d83291e7b80df020f8ee2890c52b8d49272962cac0114ef90f189889c90f1027985873d1b5261a4e986e109b2754292dc112392f0b1fcbfc91cc08bd003
+    "@babel/traverse": "npm:^7.24.7"
+    "@babel/types": "npm:^7.24.7"
+  checksum: 10c0/e3a9b8ac9c262ac976a1bcb5fe59694db5e6f0b4f9e7bdba5c7693b8b5e28113c23bdaa60fe8d3ec32a337091b67720b2053bcb3d5655f5406536c3d0584242b
   languageName: node
   linkType: hard
 
-"@babel/helpers@npm:^7.26.10":
-  version: 7.26.10
-  resolution: "@babel/helpers@npm:7.26.10"
-  dependencies:
-    "@babel/template": "npm:^7.26.9"
-    "@babel/types": "npm:^7.26.10"
-  checksum: 10c0/f99e1836bcffce96db43158518bb4a24cf266820021f6461092a776cba2dc01d9fc8b1b90979d7643c5c2ab7facc438149064463a52dd528b21c6ab32509784f
+"@babel/helper-string-parser@npm:^7.24.8":
+  version: 7.24.8
+  resolution: "@babel/helper-string-parser@npm:7.24.8"
+  checksum: 10c0/6361f72076c17fabf305e252bf6d580106429014b3ab3c1f5c4eb3e6d465536ea6b670cc0e9a637a77a9ad40454d3e41361a2909e70e305116a23d68ce094c08
   languageName: node
   linkType: hard
 
-"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.26.9":
-  version: 7.26.9
-  resolution: "@babel/parser@npm:7.26.9"
+"@babel/helper-validator-identifier@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/helper-validator-identifier@npm:7.24.7"
+  checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651
+  languageName: node
+  linkType: hard
+
+"@babel/helper-validator-option@npm:^7.24.7, @babel/helper-validator-option@npm:^7.24.8":
+  version: 7.24.8
+  resolution: "@babel/helper-validator-option@npm:7.24.8"
+  checksum: 10c0/73db93a34ae89201351288bee7623eed81a54000779462a986105b54ffe82069e764afd15171a428b82e7c7a9b5fec10b5d5603b216317a414062edf5c67a21f
+  languageName: node
+  linkType: hard
+
+"@babel/helper-wrap-function@npm:^7.25.0":
+  version: 7.25.0
+  resolution: "@babel/helper-wrap-function@npm:7.25.0"
   dependencies:
-    "@babel/types": "npm:^7.26.9"
+    "@babel/template": "npm:^7.25.0"
+    "@babel/traverse": "npm:^7.25.0"
+    "@babel/types": "npm:^7.25.0"
+  checksum: 10c0/d54601a98384c191cbc1ff07b03a19e288ef8d5c6bfafe270b2a303d96e7304eb296002921ed464cc1b105a547d1db146eb86b0be617924dee1ba1b379cdc216
+  languageName: node
+  linkType: hard
+
+"@babel/helpers@npm:^7.25.0":
+  version: 7.25.0
+  resolution: "@babel/helpers@npm:7.25.0"
+  dependencies:
+    "@babel/template": "npm:^7.25.0"
+    "@babel/types": "npm:^7.25.0"
+  checksum: 10c0/b7fe007fc4194268abf70aa3810365085e290e6528dcb9fbbf7a765d43c74b6369ce0f99c5ccd2d44c413853099daa449c9a0123f0b212ac8d18643f2e8174b8
+  languageName: node
+  linkType: hard
+
+"@babel/highlight@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/highlight@npm:7.24.7"
+  dependencies:
+    "@babel/helper-validator-identifier": "npm:^7.24.7"
+    chalk: "npm:^2.4.2"
+    js-tokens: "npm:^4.0.0"
+    picocolors: "npm:^1.0.0"
+  checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a
+  languageName: node
+  linkType: hard
+
+"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.4":
+  version: 7.25.4
+  resolution: "@babel/parser@npm:7.25.4"
+  dependencies:
+    "@babel/types": "npm:^7.25.4"
   bin:
     parser: ./bin/babel-parser.js
-  checksum: 10c0/4b9ef3c9a0d4c328e5e5544f50fe8932c36f8a2c851e7f14a85401487cd3da75cad72c2e1bcec1eac55599a6bbb2fdc091f274c4fcafa6bdd112d4915ff087fc
+  checksum: 10c0/bdada5662f15d1df11a7266ec3bc9bb769bf3637ecf3d051eafcfc8f576dcf5a3ac1007c5e059db4a1e1387db9ae9caad239fc4f79e4c2200930ed610e779993
   languageName: node
   linkType: hard
 
-"@babel/parser@npm:^7.26.10, @babel/parser@npm:^7.27.0":
-  version: 7.27.0
-  resolution: "@babel/parser@npm:7.27.0"
+"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.3":
+  version: 7.25.3
+  resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.3"
   dependencies:
-    "@babel/types": "npm:^7.27.0"
-  bin:
-    parser: ./bin/babel-parser.js
-  checksum: 10c0/ba2ed3f41735826546a3ef2a7634a8d10351df221891906e59b29b0a0cd748f9b0e7a6f07576858a9de8e77785aad925c8389ddef146de04ea2842047c9d2859
-  languageName: node
-  linkType: hard
-
-"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.9"
-  dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/traverse": "npm:^7.25.3"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/7aab47fcbb8c1ddc195a3cd66609edcad54c5022f018db7de40185f0182950389690e953e952f117a1737b72f665ff02ad30de6c02b49b97f1d8f4ccdffedc34
+  checksum: 10c0/814b4d3f102e7556a5053d1acf57ef601cfcff39a2c81b8cdc6a5c842e3cb9838f5925d1466a5f1e6416e74c9c83586a3c07fbd7fb8610a396c2becdf9ae5790
   languageName: node
   linkType: hard
 
-"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.25.9"
+"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.25.0":
+  version: 7.25.0
+  resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.25.0"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/3a652b3574ca62775c5f101f8457950edc540c3581226579125da535d67765f41ad7f0e6327f8efeb2540a5dad5bb0c60a89fb934af3f67472e73fb63612d004
+  checksum: 10c0/9645a1f47b3750acadb1353c02e71cc712d072aafe5ce115ed3a886bc14c5d9200cfb0b5b5e60e813baa549b800cf798f8714019fd246c699053cf68c428e426
   languageName: node
   linkType: hard
 
-"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.25.9"
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.25.0":
+  version: 7.25.0
+  resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.25.0"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/18fc9004104a150f9f5da9f3307f361bc3104d16778bb593b7523d5110f04a8df19a2587e6bdd5e726fb1d397191add45223f4f731bb556c33f14f2779d596e8
+  checksum: 10c0/ed1ce1c90cac46c01825339fd0f2a96fa071b016fb819d8dfaf8e96300eae30e74870cb47e4dc80d4ce2fb287869f102878b4f3b35bc927fec8b1d0d76bcf612
   languageName: node
   linkType: hard
 
-"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.25.9"
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9"
-    "@babel/plugin-transform-optional-chaining": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7"
+    "@babel/plugin-transform-optional-chaining": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.13.0
-  checksum: 10c0/3f6c8781a2f7aa1791a31d2242399ca884df2ab944f90c020b6f112fb19f05fa6dad5be143d274dad1377e40415b63d24d5489faf5060b9c4a99e55d8f0c317c
+  checksum: 10c0/aeb6e7aa363a47f815cf956ea1053c5dd8b786a17799f065c9688ba4b0051fe7565d258bbe9400bfcbfb3114cb9fda66983e10afe4d750bc70ff75403e15dd36
   languageName: node
   linkType: hard
 
-"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.25.9"
+"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.25.0":
+  version: 7.25.0
+  resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.25.0"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/traverse": "npm:^7.25.0"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/02b365f0cc4df8b8b811c68697c93476da387841e5f153fe42766f34241b685503ea51110d5ed6df7132759820b93e48d9fa3743cffc091eed97c19f7e5fe272
+  checksum: 10c0/45988025537a9d4a27b610fd696a18fd9ba9336621a69b4fb40560eeb10c79657f85c92a37f30c7c8fb29c22970eea0b373315795a891f1a05549a6cfe5a6bfe
   languageName: node
   linkType: hard
 
@@ -438,7 +432,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-class-properties@npm:^7.8.3":
+"@babel/plugin-syntax-class-properties@npm:^7.12.13, @babel/plugin-syntax-class-properties@npm:^7.8.3":
   version: 7.12.13
   resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13"
   dependencies:
@@ -449,29 +443,62 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-import-assertions@npm:^7.26.0":
-  version: 7.26.0
-  resolution: "@babel/plugin-syntax-import-assertions@npm:7.26.0"
+"@babel/plugin-syntax-class-static-block@npm:^7.14.5":
+  version: 7.14.5
+  resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.14.5"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/525b174e60b210d96c1744c1575fc2ddedcc43a479cba64a5344cf77bd0541754fc58120b5a11ff832ba098437bb05aa80900d1f49bb3d888c5e349a4a3a356e
+  checksum: 10c0/4464bf9115f4a2d02ce1454411baf9cfb665af1da53709c5c56953e5e2913745b0fcce82982a00463d6facbdd93445c691024e310b91431a1e2f024b158f6371
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-import-attributes@npm:^7.26.0":
-  version: 7.26.0
-  resolution: "@babel/plugin-syntax-import-attributes@npm:7.26.0"
+"@babel/plugin-syntax-dynamic-import@npm:^7.8.3":
+  version: 7.8.3
+  resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.8.0"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/e594c185b12bfe0bbe7ca78dfeebe870e6d569a12128cac86f3164a075fe0ff70e25ddbd97fd0782906b91f65560c9dc6957716b7b4a68aba2516c9b7455e352
+  checksum: 10c0/9c50927bf71adf63f60c75370e2335879402648f468d0172bc912e303c6a3876927d8eb35807331b57f415392732ed05ab9b42c68ac30a936813ab549e0246c5
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-import-meta@npm:^7.8.3":
+"@babel/plugin-syntax-export-namespace-from@npm:^7.8.3":
+  version: 7.8.3
+  resolution: "@babel/plugin-syntax-export-namespace-from@npm:7.8.3"
+  dependencies:
+    "@babel/helper-plugin-utils": "npm:^7.8.3"
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 10c0/5100d658ba563829700cd8d001ddc09f4c0187b1a13de300d729c5b3e87503f75a6d6c99c1794182f7f1a9f546ee009df4f15a0ce36376e206ed0012fa7cdc24
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-syntax-import-assertions@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-syntax-import-assertions@npm:7.24.7"
+  dependencies:
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 10c0/b82c53e095274ee71c248551352d73441cf65b3b3fc0107258ba4e9aef7090772a425442b3ed1c396fa207d0efafde8929c87a17d3c885b3ca2021316e87e246
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-syntax-import-attributes@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-syntax-import-attributes@npm:7.24.7"
+  dependencies:
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 10c0/eccc54d0f03c96d0eec7a6e2fa124dadbc7298345b62ffc4238f173308c4325b5598f139695ff05a95cf78412ef6903599e4b814496612bf39aad4715a16375b
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-syntax-import-meta@npm:^7.10.4, @babel/plugin-syntax-import-meta@npm:^7.8.3":
   version: 7.10.4
   resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4"
   dependencies:
@@ -493,18 +520,18 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-jsx@npm:^7.25.9, @babel/plugin-syntax-jsx@npm:^7.7.2":
-  version: 7.25.9
-  resolution: "@babel/plugin-syntax-jsx@npm:7.25.9"
+"@babel/plugin-syntax-jsx@npm:7, @babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.7.2":
+  version: 7.24.7
+  resolution: "@babel/plugin-syntax-jsx@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/d56597aff4df39d3decda50193b6dfbe596ca53f437ff2934622ce19a743bf7f43492d3fb3308b0289f5cee2b825d99ceb56526a2b9e7b68bf04901546c5618c
+  checksum: 10c0/f44d927a9ae8d5ef016ff5b450e1671e56629ddc12e56b938e41fd46e141170d9dfc9a53d6cb2b9a20a7dd266a938885e6a3981c60c052a2e1daed602ac80e51
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3":
+"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4, @babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3":
   version: 7.10.4
   resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4"
   dependencies:
@@ -526,7 +553,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-numeric-separator@npm:^7.8.3":
+"@babel/plugin-syntax-numeric-separator@npm:^7.10.4, @babel/plugin-syntax-numeric-separator@npm:^7.8.3":
   version: 7.10.4
   resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4"
   dependencies:
@@ -570,7 +597,18 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-top-level-await@npm:^7.8.3":
+"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5":
+  version: 7.14.5
+  resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5"
+  dependencies:
+    "@babel/helper-plugin-utils": "npm:^7.14.5"
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 10c0/69822772561706c87f0a65bc92d0772cea74d6bc0911537904a676d5ff496a6d3ac4e05a166d8125fce4a16605bace141afc3611074e170a994e66e5397787f3
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-syntax-top-level-await@npm:^7.14.5, @babel/plugin-syntax-top-level-await@npm:^7.8.3":
   version: 7.14.5
   resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5"
   dependencies:
@@ -581,14 +619,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-typescript@npm:^7.25.9, @babel/plugin-syntax-typescript@npm:^7.7.2":
-  version: 7.25.9
-  resolution: "@babel/plugin-syntax-typescript@npm:7.25.9"
+"@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.7.2":
+  version: 7.24.7
+  resolution: "@babel/plugin-syntax-typescript@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/5192ebe11bd46aea68b7a60fd9555465c59af7e279e71126788e59121b86e00b505816685ab4782abe159232b0f73854e804b54449820b0d950b397ee158caa2
+  checksum: 10c0/cdabd2e8010fb0ad15b49c2c270efc97c4bfe109ead36c7bbcf22da7a74bc3e49702fc4f22f12d2d6049e8e22a5769258df1fd05f0420ae45e11bdd5bc07805a
   languageName: node
   linkType: hard
 
@@ -604,452 +642,466 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-arrow-functions@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-arrow-functions@npm:7.25.9"
+"@babel/plugin-transform-arrow-functions@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-arrow-functions@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/851fef9f58be60a80f46cc0ce1e46a6f7346a6f9d50fa9e0fa79d46ec205320069d0cc157db213e2bea88ef5b7d9bd7618bb83f0b1996a836e2426c3a3a1f622
+  checksum: 10c0/6ac05a54e5582f34ac6d5dc26499e227227ec1c7fa6fc8de1f3d40c275f140d3907f79bbbd49304da2d7008a5ecafb219d0b71d78ee3290ca22020d878041245
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-async-generator-functions@npm:^7.26.8":
-  version: 7.26.8
-  resolution: "@babel/plugin-transform-async-generator-functions@npm:7.26.8"
+"@babel/plugin-transform-async-generator-functions@npm:^7.25.4":
+  version: 7.25.4
+  resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.4"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.26.5"
-    "@babel/helper-remap-async-to-generator": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.26.8"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-remap-async-to-generator": "npm:^7.25.0"
+    "@babel/plugin-syntax-async-generators": "npm:^7.8.4"
+    "@babel/traverse": "npm:^7.25.4"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/f6fefce963fe2e6268dde1958975d7adbce65fba94ca6f4bc554c90da03104ad1dd2e66d03bc0462da46868498428646e30b03a218ef0e5a84bfc87a7e375cec
+  checksum: 10c0/efed6f6be90b25ad77c15a622a0dc0b22dbf5d45599c207ab8fbc4e959aef21f574fa467d9cf872e45de664a46c32334e78dee2332d82f5f27e26249a34a0920
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-async-to-generator@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-async-to-generator@npm:7.25.9"
+"@babel/plugin-transform-async-to-generator@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-async-to-generator@npm:7.24.7"
   dependencies:
-    "@babel/helper-module-imports": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/helper-remap-async-to-generator": "npm:^7.25.9"
+    "@babel/helper-module-imports": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-remap-async-to-generator": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/c443d9e462ddef733ae56360064f32fc800105803d892e4ff32d7d6a6922b3765fa97b9ddc9f7f1d3f9d8c2d95721d85bef9dbf507804214c6cf6466b105c168
+  checksum: 10c0/83c82e243898875af8457972a26ab29baf8a2078768ee9f35141eb3edff0f84b165582a2ff73e90a9e08f5922bf813dbf15a85c1213654385198f4591c0dc45d
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-block-scoped-functions@npm:^7.26.5":
-  version: 7.26.5
-  resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.26.5"
+"@babel/plugin-transform-block-scoped-functions@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.26.5"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/2f3060800ead46b09971dd7bf830d66383b7bc61ced9945633b4ef9bf87787956ea83fcf49b387cecb377812588c6b81681714c760f9cf89ecba45edcbab1192
+  checksum: 10c0/113e86de4612ae91773ff5cb6b980f01e1da7e26ae6f6012127415d7ae144e74987bc23feb97f63ba4bc699331490ddea36eac004d76a20d5369e4cc6a7f61cd
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-block-scoping@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-block-scoping@npm:7.25.9"
+"@babel/plugin-transform-block-scoping@npm:^7.25.0":
+  version: 7.25.0
+  resolution: "@babel/plugin-transform-block-scoping@npm:7.25.0"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/a76e30becb6c75b4d87a2cd53556fddb7c88ddd56bfadb965287fd944810ac159aa8eb5705366fc37336041f63154ed9fab3862fb10482a45bf5ede63fd55fda
+  checksum: 10c0/382931c75a5d0ea560387e76cb57b03461300527e4784efcb2fb62f36c1eb0ab331327b6034def256baa0cad9050925a61f9c0d56261b6afd6a29c3065fb0bd4
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-class-properties@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-class-properties@npm:7.25.9"
+"@babel/plugin-transform-class-properties@npm:^7.25.4":
+  version: 7.25.4
+  resolution: "@babel/plugin-transform-class-properties@npm:7.25.4"
   dependencies:
-    "@babel/helper-create-class-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-create-class-features-plugin": "npm:^7.25.4"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/f0603b6bd34d8ba62c03fc0572cb8bbc75874d097ac20cc7c5379e001081210a84dba1749e7123fca43b978382f605bb9973c99caf2c5b4c492d5c0a4a441150
+  checksum: 10c0/0b41bc8a5920d3d17c7c06220b601cf43e0a32ac34f05f05cd0cdf08915e4521b1b707cb1e60942b4fc68a5dfac09f0444a8720e0c72ce76fb039e8ec5263115
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-class-static-block@npm:^7.26.0":
-  version: 7.26.0
-  resolution: "@babel/plugin-transform-class-static-block@npm:7.26.0"
+"@babel/plugin-transform-class-static-block@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-class-static-block@npm:7.24.7"
   dependencies:
-    "@babel/helper-create-class-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-create-class-features-plugin": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-class-static-block": "npm:^7.14.5"
   peerDependencies:
     "@babel/core": ^7.12.0
-  checksum: 10c0/cdcf5545ae6514ed75fbd73cccfa209c6a5dfdf0c2bb7bb62c0fb4ec334a32281bcf1bc16ace494d9dbe93feb8bdc0bd3cf9d9ccb6316e634a67056fa13b741b
+  checksum: 10c0/b0ade39a3d09dce886f79dbd5907c3d99b48167eddb6b9bbde24a0598129654d7017e611c20494cdbea48b07ac14397cd97ea34e3754bbb2abae4e698128eccb
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-classes@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-classes@npm:7.25.9"
+"@babel/plugin-transform-classes@npm:^7.25.4":
+  version: 7.25.4
+  resolution: "@babel/plugin-transform-classes@npm:7.25.4"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.25.9"
-    "@babel/helper-compilation-targets": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/helper-replace-supers": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.25.9"
+    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
+    "@babel/helper-compilation-targets": "npm:^7.25.2"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-replace-supers": "npm:^7.25.0"
+    "@babel/traverse": "npm:^7.25.4"
     globals: "npm:^11.1.0"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/02742ea7cd25be286c982e672619effca528d7a931626a6f3d6cea11852951b7ee973276127eaf6418ac0e18c4d749a16b520709c707e86a67012bd23ff2927d
+  checksum: 10c0/c68424d9dd64860825111aa4a4ed5caf29494b7a02ddb9c36351d768c41e8e05127d89274795cdfcade032d9d299e6c677418259df58c71e68f1741583dcf467
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-computed-properties@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-computed-properties@npm:7.25.9"
+"@babel/plugin-transform-computed-properties@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-computed-properties@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/template": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/template": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/948c0ae3ce0ba2375241d122a9bc7cda4a7ac8110bd8a62cd804bc46a5fdb7a7a42c7799c4cd972e14e0a579d2bd0999b92e53177b73f240bb0d4b09972c758b
+  checksum: 10c0/25636dbc1f605c0b8bc60aa58628a916b689473d11551c9864a855142e36742fe62d4a70400ba3b74902338e77fb3d940376c0a0ba154b6b7ec5367175233b49
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-destructuring@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-destructuring@npm:7.25.9"
+"@babel/plugin-transform-destructuring@npm:^7.24.8":
+  version: 7.24.8
+  resolution: "@babel/plugin-transform-destructuring@npm:7.24.8"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/7beec5fda665d108f69d5023aa7c298a1e566b973dd41290faa18aeea70f6f571295c1ece0a058f3ceb6c6c96de76de7cd34f5a227fbf09a1b8d8a735d28ca49
+  checksum: 10c0/804968c1d5f5072c717505296c1e5d5ec33e90550423de66de82bbcb78157156e8470bbe77a04ab8c710a88a06360a30103cf223ac7eff4829adedd6150de5ce
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-dotall-regex@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-dotall-regex@npm:7.25.9"
+"@babel/plugin-transform-dotall-regex@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-dotall-regex@npm:7.24.7"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/7c3471ae5cf7521fd8da5b03e137e8d3733fc5ee4524ce01fb0c812f0bb77cb2c9657bc8a6253186be3a15bb4caa8974993c7ddc067f554ecc6a026f0a3b5e12
+  checksum: 10c0/793f14c9494972d294b7e7b97b747f47874b6d57d7804d3443c701becf5db192c9311be6a1835c07664486df1f5c60d33196c36fb7e11a53015e476b4c145b33
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-duplicate-keys@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-duplicate-keys@npm:7.25.9"
+"@babel/plugin-transform-duplicate-keys@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-duplicate-keys@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/d0c74894b9bf6ff2a04189afffb9cd43d87ebd7b7943e51a827c92d2aaa40fa89ac81565a2fd6fbeabf9e38413a9264c45862eee2b017f1d49046cc3c8ff06b4
+  checksum: 10c0/75ff7ec1117ac500e77bf20a144411d39c0fdd038f108eec061724123ce6d1bb8d5bd27968e466573ee70014f8be0043361cdb0ef388f8a182d1d97ad67e51b9
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.25.9"
+"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.25.0":
+  version: 7.25.0
+  resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.25.0"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.0"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/a8039a6d2b90e011c7b30975edee47b5b1097cf3c2f95ec1f5ddd029898d783a995f55f7d6eb8d6bb8873c060fb64f9f1ccba938dfe22d118d09cf68e0cd3bf6
+  checksum: 10c0/1c9b57ddd9b33696e88911d0e7975e1573ebc46219c4b30eb1dc746cbb71aedfac6f6dab7fdfdec54dd58f31468bf6ab56b157661ea4ffe58f906d71f89544c8
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-dynamic-import@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-dynamic-import@npm:7.25.9"
+"@babel/plugin-transform-dynamic-import@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-dynamic-import@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/5e643a8209072b668350f5788f23c64e9124f81f958b595c80fecca6561086d8ef346c04391b9e5e4cad8b8cbe22c258f0cd5f4ea89b97e74438e7d1abfd98cf
+  checksum: 10c0/eeda48372efd0a5103cb22dadb13563c975bce18ae85daafbb47d57bb9665d187da9d4fe8d07ac0a6e1288afcfcb73e4e5618bf75ff63fddf9736bfbf225203b
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-exponentiation-operator@npm:^7.26.3":
-  version: 7.26.3
-  resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.26.3"
+"@babel/plugin-transform-exponentiation-operator@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-builder-binary-assignment-operator-visitor": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/cac922e851c6a0831fdd2e3663564966916015aeff7f4485825fc33879cbc3a313ceb859814c9200248e2875d65bb13802a723e5d7d7b40a2e90da82a5a1e15c
+  checksum: 10c0/ace3e11c94041b88848552ba8feb39ae4d6cad3696d439ff51445bd2882d8b8775d85a26c2c0edb9b5e38c9e6013cc11b0dea89ec8f93c7d9d7ee95e3645078c
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-export-namespace-from@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-export-namespace-from@npm:7.25.9"
+"@babel/plugin-transform-export-namespace-from@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-export-namespace-from@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/f291ea2ec5f36de9028a00cbd5b32f08af281b8183bf047200ff001f4cb260be56f156b2449f42149448a4a033bd6e86a3a7f06d0c2825532eb0ae6b03058dfb
+  checksum: 10c0/4e144d7f1c57bc63b4899dbbbdfed0880f2daa75ea9c7251c7997f106e4b390dc362175ab7830f11358cb21f6b972ca10a43a2e56cd789065f7606b082674c0c
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-for-of@npm:^7.26.9":
-  version: 7.26.9
-  resolution: "@babel/plugin-transform-for-of@npm:7.26.9"
+"@babel/plugin-transform-for-of@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-for-of@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.26.5"
-    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/e28a521521cf9f84ddd69ca8da7c89fb9f7aa38e4dea35742fe973e4e1d7c23f9cee1a4861a2fdd9e9f18ff945886a44d7335cea1c603b96bfcb1c7c8791ef09
+  checksum: 10c0/77629b1173e55d07416f05ba7353caa09d2c2149da2ca26721ab812209b63689d1be45116b68eadc011c49ced59daf5320835b15245eb7ae93ae0c5e8277cfc0
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-function-name@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-function-name@npm:7.25.9"
+"@babel/plugin-transform-function-name@npm:^7.25.1":
+  version: 7.25.1
+  resolution: "@babel/plugin-transform-function-name@npm:7.25.1"
   dependencies:
-    "@babel/helper-compilation-targets": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.25.9"
+    "@babel/helper-compilation-targets": "npm:^7.24.8"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/traverse": "npm:^7.25.1"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/8e67fbd1dd367927b8b6afdf0a6e7cb3a3fd70766c52f700ca77428b6d536f6c9d7ec643e7762d64b23093233765c66bffa40e31aabe6492682879bcb45423e1
+  checksum: 10c0/e74912174d5e33d1418b840443c2e226a7b76cc017c1ed20ee30a566e4f1794d4a123be03180da046241576e8b692731807ba1f52608922acf1cb2cb6957593f
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-json-strings@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-json-strings@npm:7.25.9"
+"@babel/plugin-transform-json-strings@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-json-strings@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-json-strings": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/00bc2d4751dfc9d44ab725be16ee534de13cfd7e77dfb386e5dac9e48101ce8fcbc5971df919dc25b3f8a0fa85d6dc5f2a0c3cf7ec9d61c163d9823c091844f0
+  checksum: 10c0/17c72cd5bf3e90e722aabd333559275f3309e3fa0b9cea8c2944ab83ae01502c71a2be05da5101edc02b3fc8df15a8dbb9b861cbfcc8a52bf5e797cf01d3a40a
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-literals@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-literals@npm:7.25.9"
+"@babel/plugin-transform-literals@npm:^7.25.2":
+  version: 7.25.2
+  resolution: "@babel/plugin-transform-literals@npm:7.25.2"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/00b14e9c14cf1e871c1f3781bf6334cac339c360404afd6aba63d2f6aca9270854d59a2b40abff1c4c90d4ffdca614440842d3043316c2f0ceb155fdf7726b3b
+  checksum: 10c0/0796883217b0885d37e7f6d350773be349e469a812b6bf11ccf862a6edf65103d3e7c849529d65381b441685c12e756751d8c2489a0fd3f8139bb5ef93185f58
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-logical-assignment-operators@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.25.9"
+"@babel/plugin-transform-logical-assignment-operators@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/6e2051e10b2d6452980fc4bdef9da17c0d6ca48f81b8529e8804b031950e4fff7c74a7eb3de4a2b6ad22ffb631d0b67005425d232cce6e2b29ce861c78ed04f5
+  checksum: 10c0/dbe882eb9053931f2ab332c50fc7c2a10ef507d6421bd9831adbb4cb7c9f8e1e5fbac4fbd2e007f6a1bf1df1843547559434012f118084dc0bf42cda3b106272
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-member-expression-literals@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-member-expression-literals@npm:7.25.9"
+"@babel/plugin-transform-member-expression-literals@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-member-expression-literals@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/91d17b451bcc5ea9f1c6f8264144057ade3338d4b92c0b248366e4db3a7790a28fd59cc56ac433a9627a9087a17a5684e53f4995dd6ae92831cb72f1bd540b54
+  checksum: 10c0/e789ae359bdf2d20e90bedef18dfdbd965c9ebae1cee398474a0c349590fda7c8b874e1a2ceee62e47e5e6ec1730e76b0f24e502164357571854271fc12cc684
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-amd@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-modules-amd@npm:7.25.9"
+"@babel/plugin-transform-modules-amd@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-modules-amd@npm:7.24.7"
   dependencies:
-    "@babel/helper-module-transforms": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-module-transforms": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/849957d9484d0a2d93331226ed6cf840cee7d57454549534c447c93f8b839ef8553eae9877f8f550e3c39f14d60992f91244b2e8e7502a46064b56c5d68ba855
+  checksum: 10c0/6df7de7fce34117ca4b2fa07949b12274c03668cbfe21481c4037b6300796d50ae40f4f170527b61b70a67f26db906747797e30dbd0d9809a441b6e220b5728f
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-commonjs@npm:^7.25.9, @babel/plugin-transform-modules-commonjs@npm:^7.26.3":
-  version: 7.26.3
-  resolution: "@babel/plugin-transform-modules-commonjs@npm:7.26.3"
+"@babel/plugin-transform-modules-commonjs@npm:^7.24.7, @babel/plugin-transform-modules-commonjs@npm:^7.24.8":
+  version: 7.24.8
+  resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.8"
   dependencies:
-    "@babel/helper-module-transforms": "npm:^7.26.0"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-module-transforms": "npm:^7.24.8"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-simple-access": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/82e59708f19f36da29531a64a7a94eabbf6ff46a615e0f5d9b49f3f59e8ef10e2bac607d749091508d3fa655146c9e5647c3ffeca781060cdabedb4c7a33c6f2
+  checksum: 10c0/f1cf552307ebfced20d3907c1dd8be941b277f0364aa655e2b5fee828c84c54065745183104dae86f1f93ea0406db970a463ef7ceaaed897623748e99640e5a7
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-systemjs@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-modules-systemjs@npm:7.25.9"
+"@babel/plugin-transform-modules-systemjs@npm:^7.25.0":
+  version: 7.25.0
+  resolution: "@babel/plugin-transform-modules-systemjs@npm:7.25.0"
   dependencies:
-    "@babel/helper-module-transforms": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/helper-validator-identifier": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.25.9"
+    "@babel/helper-module-transforms": "npm:^7.25.0"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-validator-identifier": "npm:^7.24.7"
+    "@babel/traverse": "npm:^7.25.0"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/8299e3437542129c2684b86f98408c690df27db4122a79edded4782cf04e755d6ecb05b1e812c81a34224a81e664303392d5f3c36f3d2d51fdc99bb91c881e9a
+  checksum: 10c0/fca6198da71237e4bb1274b3b67a0c81d56013c9535361242b6bfa87d70a9597854aadb45d4d8203369be4a655e158be2a5d20af0040b1f8d1bfc47db3ad7b68
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-umd@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-modules-umd@npm:7.25.9"
+"@babel/plugin-transform-modules-umd@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-modules-umd@npm:7.24.7"
   dependencies:
-    "@babel/helper-module-transforms": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-module-transforms": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/fa11a621f023e2ac437b71d5582f819e667c94306f022583d77da9a8f772c4128861a32bbb63bef5cba581a70cd7dbe87a37238edaafcfacf889470c395e7076
+  checksum: 10c0/7791d290121db210e4338b94b4a069a1a79e4c7a8d7638d8159a97b281851bbed3048dac87a4ae718ad963005e6c14a5d28e6db2eeb2b04e031cee92fb312f85
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.25.9"
+"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.24.7"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/32b14fda5c885d1706863f8af2ee6c703d39264355b57482d3a24fce7f6afbd4c7a0896e501c0806ed2b0759beb621bf7f3f7de1fbbc82026039a98d961e78ef
+  checksum: 10c0/41a0b0f2d0886318237440aa3b489f6d0305361d8671121777d9ff89f9f6de9d0c02ce93625049061426c8994064ef64deae8b819d1b14c00374a6a2336fb5d9
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-new-target@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-new-target@npm:7.25.9"
+"@babel/plugin-transform-new-target@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-new-target@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/7b5f1b7998f1cf183a7fa646346e2f3742e5805b609f28ad5fee22d666a15010f3e398b7e1ab78cddb7901841a3d3f47135929af23d54e8bf4ce69b72051f71e
+  checksum: 10c0/2540808a35e1a978e537334c43dab439cf24c93e7beb213a2e71902f6710e60e0184316643790c0a6644e7a8021e52f7ab8165e6b3e2d6651be07bdf517b67df
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.3, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.26.6":
-  version: 7.26.6
-  resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.26.6"
+"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.3, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.26.5"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/574d6db7cbc5c092db5d1dece8ce26195e642b9c40dbfeaf3082058a78ad7959c1c333471cdd45f38b784ec488850548075d527b178c5010ee9bff7aa527cc7a
+  checksum: 10c0/7243c8ff734ed5ef759dd8768773c4b443c12e792727e759a1aec2c7fa2bfdd24f1ecb42e292a7b3d8bd3d7f7b861cf256a8eb4ba144fc9cc463892c303083d9
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-numeric-separator@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-numeric-separator@npm:7.25.9"
+"@babel/plugin-transform-numeric-separator@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-numeric-separator@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/ad63ad341977844b6f9535fcca15ca0d6d6ad112ed9cc509d4f6b75e9bf4b1b1a96a0bcb1986421a601505d34025373608b5f76d420d924b4e21f86b1a1f2749
+  checksum: 10c0/e18e09ca5a6342645d00ede477731aa6e8714ff357efc9d7cda5934f1703b3b6fb7d3298dce3ce3ba53e9ff1158eab8f1aadc68874cc21a6099d33a1ca457789
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-object-rest-spread@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-object-rest-spread@npm:7.25.9"
+"@babel/plugin-transform-object-rest-spread@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-object-rest-spread@npm:7.24.7"
   dependencies:
-    "@babel/helper-compilation-targets": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/plugin-transform-parameters": "npm:^7.25.9"
+    "@babel/helper-compilation-targets": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3"
+    "@babel/plugin-transform-parameters": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/02077d8abd83bf6a48ff0b59e98d7561407cf75b591cffd3fdc5dc5e9a13dec1c847a7a690983762a3afecddb244831e897e0515c293e7c653b262c30cd614af
+  checksum: 10c0/9ad64bc003f583030f9da50614b485852f8edac93f8faf5d1cd855201a4852f37c5255ae4daf70dd4375bdd4874e16e39b91f680d4668ec219ba05441ce286eb
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-object-super@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-object-super@npm:7.25.9"
+"@babel/plugin-transform-object-super@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-object-super@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/helper-replace-supers": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-replace-supers": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/0348d00e76f1f15ada44481a76e8c923d24cba91f6e49ee9b30d6861eb75344e7f84d62a18df8a6f9e9a7eacf992f388174b7f9cc4ce48287bcefca268c07600
+  checksum: 10c0/770cebb4b4e1872c216b17069db9a13b87dfee747d359dc56d9fcdd66e7544f92dc6ab1861a4e7e0528196aaff2444e4f17dc84efd8eaf162d542b4ba0943869
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-optional-catch-binding@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.25.9"
+"@babel/plugin-transform-optional-catch-binding@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/722fd5ee12ab905309d4e84421584fce4b6d9e6b639b06afb20b23fa809e6ab251e908a8d5e8b14d066a28186b8ef8f58d69fd6eca9ce1b9ef7af08333378f6c
+  checksum: 10c0/1e2f10a018f7d03b3bde6c0b70d063df8d5dd5209861d4467726cf834f5e3d354e2276079dc226aa8e6ece35f5c9b264d64b8229a8bb232829c01e561bcfb07a
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-optional-chaining@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-optional-chaining@npm:7.25.9"
+"@babel/plugin-transform-optional-chaining@npm:^7.24.7, @babel/plugin-transform-optional-chaining@npm:^7.24.8":
+  version: 7.24.8
+  resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.8"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7"
+    "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/041ad2beae5affb8e68a0bcb6882a2dadb758db3c629a0e012f57488ab43a822ac1ea17a29db8ef36560a28262a5dfa4dbbbf06ed6e431db55abe024b7cd3961
+  checksum: 10c0/4ffbe1aad7dec7c9aa2bf6ceb4b2f91f96815b2784f2879bde80e46934f59d64a12cb2c6262e40897c4754d77d2c35d8a5cfed63044fdebf94978b1ed3d14b17
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-parameters@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-parameters@npm:7.25.9"
+"@babel/plugin-transform-parameters@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-parameters@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/aecb446754b9e09d6b6fa95fd09e7cf682f8aaeed1d972874ba24c0a30a7e803ad5f014bb1fffc7bfeed22f93c0d200947407894ea59bf7687816f2f464f8df3
+  checksum: 10c0/53bf190d6926771545d5184f1f5f3f5144d0f04f170799ad46a43f683a01fab8d5fe4d2196cf246774530990c31fe1f2b9f0def39f0a5ddbb2340b924f5edf01
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-private-methods@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-private-methods@npm:7.25.9"
+"@babel/plugin-transform-private-methods@npm:^7.25.4":
+  version: 7.25.4
+  resolution: "@babel/plugin-transform-private-methods@npm:7.25.4"
   dependencies:
-    "@babel/helper-create-class-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-create-class-features-plugin": "npm:^7.25.4"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/64bd71de93d39daefa3e6c878d6f2fd238ed7d4ecfb13b0e771ddbbc131487def3ceb405b62b534a5cbb5043046b504e1b189b0a45229cc75af979a9fbcaa7bd
+  checksum: 10c0/7abdb427c3984a2c8a2e9d806297d8509b02f78a3501b7760e544be532446e9df328b876daa8fc38718f3dce7ccc45083016ee7aeaab169b81c142bc18700794
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-private-property-in-object@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-private-property-in-object@npm:7.25.9"
+"@babel/plugin-transform-private-property-in-object@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-private-property-in-object@npm:7.24.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.25.9"
-    "@babel/helper-create-class-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
+    "@babel/helper-create-class-features-plugin": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/d4965de19d9f204e692cc74dbc39f0bb469e5f29df96dd4457ea23c5e5596fba9d5af76eaa96f9d48a9fc20ec5f12a94c679285e36b8373406868ea228109e27
+  checksum: 10c0/c6fa7defb90b1b0ed46f24ff94ff2e77f44c1f478d1090e81712f33cf992dda5ba347016f030082a2f770138bac6f4a9c2c1565e9f767a125901c77dd9c239ba
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-property-literals@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-property-literals@npm:7.25.9"
+"@babel/plugin-transform-property-literals@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-property-literals@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/1639e35b2438ccf3107af760d34e6a8e4f9acdd3ae6186ae771a6e3029bd59dfe778e502d67090f1185ecda5c16addfed77561e39c518a3f51ff10d41790e106
+  checksum: 10c0/52564b58f3d111dc02d241d5892a4b01512e98dfdf6ef11b0ed62f8b11b0acacccef0fc229b44114fe8d1a57a8b70780b11bdd18b807d3754a781a07d8f57433
   languageName: node
   linkType: hard
 
@@ -1064,312 +1116,313 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-display-name@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-react-display-name@npm:7.25.9"
+"@babel/plugin-transform-react-display-name@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-react-display-name@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/63a0f962d64e71baf87c212755419e25c637d2d95ea6fdc067df26b91e606ae186442ae815b99a577eca9bf5404d9577ecad218a3cf42d0e9e286ca7b003a992
+  checksum: 10c0/c14a07a9e75723c96f1a0a306b8a8e899ff1c6a0cc3d62bcda79bb1b54e4319127b258651c513a1a47da152cdc22e16525525a30ae5933a2980c7036fd0b4d24
   languageName: node
   linkType: hard
 
 "@babel/plugin-transform-react-inline-elements@npm:^7.21.0":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-react-inline-elements@npm:7.25.9"
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-react-inline-elements@npm:7.24.7"
   dependencies:
-    "@babel/helper-builder-react-jsx": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-builder-react-jsx": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/d745bcfa8e51acc497b5ea2ff2fc4215cd39ebdb54703f2973962e3818a4829081ebc9e7818db28f90d1295e0b02e140986509ba48eef8aaa9de8c09b9434646
+  checksum: 10c0/affe44efc641e5dc4de077db74c80e3dee896a5dfea04491cbd8167ac40dcbb6eaa1ad0ba599e4a85071acdaa08732e7f5d9cf3236a2aa635406c232c8f08236
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-jsx-development@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-react-jsx-development@npm:7.25.9"
+"@babel/plugin-transform-react-jsx-development@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-react-jsx-development@npm:7.24.7"
   dependencies:
-    "@babel/plugin-transform-react-jsx": "npm:^7.25.9"
+    "@babel/plugin-transform-react-jsx": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/c0b92ff9eb029620abf320ff74aae182cea87524723d740fb48a4373d0d16bddf5edbe1116e7ba341332a5337e55c2ceaee8b8cad5549e78af7f4b3cfe77debb
+  checksum: 10c0/fce647db50f90a5291681f0f97865d9dc76981262dff71d6d0332e724b85343de5860c26f9e9a79e448d61e1d70916b07ce91e8c7f2b80dceb4b16aee41794d8
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-jsx@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-react-jsx@npm:7.25.9"
+"@babel/plugin-transform-react-jsx@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-react-jsx@npm:7.24.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.25.9"
-    "@babel/helper-module-imports": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/plugin-syntax-jsx": "npm:^7.25.9"
-    "@babel/types": "npm:^7.25.9"
+    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
+    "@babel/helper-module-imports": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-jsx": "npm:^7.24.7"
+    "@babel/types": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/5c9947e8ed141f7606f54da3e05eea1074950c5b8354c39df69cb7f43cb5a83c6c9d7973b24bc3d89341c8611f8ad50830a98ab10d117d850e6bdd8febdce221
+  checksum: 10c0/5c46d2c1c06a30e6bde084839df9cc689bf9c9cb0292105d61c225ca731f64247990724caee7dfc7f817dc964c062e8319e7f05394209590c476b65d75373435
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-pure-annotations@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.25.9"
+"@babel/plugin-transform-react-pure-annotations@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.24.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/7c8eac04644ad19dcd71bb8e949b0ae22b9e548fa4a58e545d3d0342f647fb89db7f8789a7c5b8074d478ce6d3d581eaf47dd4b36027e16fd68211c383839abc
+  checksum: 10c0/fae517d293d9c93b7b920458c3e4b91cb0400513889af41ba184a5f3acc8bfef27242cc262741bb8f87870df376f1733a0d0f52b966d342e2aaaf5607af8f73d
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-regenerator@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-regenerator@npm:7.25.9"
+"@babel/plugin-transform-regenerator@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-regenerator@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
     regenerator-transform: "npm:^0.15.2"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/eef3ffc19f7d291b863635f32b896ad7f87806d9219a0d3404a470219abcfc5b43aabecd691026c48e875b965760d9c16abee25e6447272233f30cd07f453ec7
+  checksum: 10c0/d2dc2c788fdae9d97217e70d46ba8ca9db0035c398dc3e161552b0c437113719a75c04f201f9c91ddc8d28a1da60d0b0853f616dead98a396abb9c845c44892b
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-regexp-modifiers@npm:^7.26.0":
-  version: 7.26.0
-  resolution: "@babel/plugin-transform-regexp-modifiers@npm:7.26.0"
+"@babel/plugin-transform-reserved-words@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-reserved-words@npm:7.24.7"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-  peerDependencies:
-    "@babel/core": ^7.0.0
-  checksum: 10c0/4abc1db6c964efafc7a927cda814c7275275afa4b530483e0936fd614de23cb5802f7ca43edaa402008a723d4e7eac282b6f5283aa2eeb3b27da6d6c1dd7f8ed
-  languageName: node
-  linkType: hard
-
-"@babel/plugin-transform-reserved-words@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-reserved-words@npm:7.25.9"
-  dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/8b028b80d1983e3e02f74e21924323cc66ba930e5c5758909a122aa7d80e341b8b0f42e1698e42b50d47a6ba911332f584200b28e1a4e2104b7514d9dc011e96
+  checksum: 10c0/2229de2768615e7f5dc0bbc55bc121b5678fd6d2febd46c74a58e42bb894d74cd5955c805880f4e02d0e1cf94f6886270eda7fafc1be9305a1ec3b9fd1d063f5
   languageName: node
   linkType: hard
 
 "@babel/plugin-transform-runtime@npm:^7.22.4":
-  version: 7.26.9
-  resolution: "@babel/plugin-transform-runtime@npm:7.26.9"
+  version: 7.25.4
+  resolution: "@babel/plugin-transform-runtime@npm:7.25.4"
   dependencies:
-    "@babel/helper-module-imports": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.26.5"
+    "@babel/helper-module-imports": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
     babel-plugin-polyfill-corejs2: "npm:^0.4.10"
     babel-plugin-polyfill-corejs3: "npm:^0.10.6"
     babel-plugin-polyfill-regenerator: "npm:^0.6.1"
     semver: "npm:^6.3.1"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/2c4d77d0671badc7fd53dcd7015df5db892712436c7e9740ffb2f5b85e8591e5bfe208f78dff402b4ee2d55d0f7a3c0a1102c683f333f4ee0cfa62f68ea68842
+  checksum: 10c0/c08698276596d58bf49e222ead3c414c35d099a7e5a6174b11e2db9b74420e94783ada596820437622c3eccc8852c0e750ad053bd8e775f0050839479ba76e6a
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-shorthand-properties@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-shorthand-properties@npm:7.25.9"
+"@babel/plugin-transform-shorthand-properties@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-shorthand-properties@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/05a20d45f0fb62567644c507ccd4e379c1a74dacf887d2b2cac70247415e3f6d7d3bf4850c8b336053144715fedb6200fc38f7130c4b76c94eec9b9c0c2a8e9b
+  checksum: 10c0/41b155bdbb3be66618358488bf7731b3b2e8fff2de3dbfd541847720a9debfcec14db06a117abedd03c9cd786db20a79e2a86509a4f19513f6e1b610520905cf
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-spread@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-spread@npm:7.25.9"
+"@babel/plugin-transform-spread@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-spread@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/996c8fed238efc30e0664f9f58bd7ec8c148f4659f84425f68923a094fe891245711d26eb10d1f815f50c124434e076e860dbe9662240844d1b77cd09907dcdf
+  checksum: 10c0/facba1553035f76b0d2930d4ada89a8cd0f45b79579afd35baefbfaf12e3b86096995f4b0c402cf9ee23b3f2ea0a4460c3b1ec0c192d340962c948bb223d4e66
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-sticky-regex@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-sticky-regex@npm:7.25.9"
+"@babel/plugin-transform-sticky-regex@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-sticky-regex@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/e9612b0615dab4c4fba1c560769616a9bd7b9226c73191ef84b6c3ee185c8b719b4f887cdd8336a0a13400ce606ab4a0d33bc8fa6b4fcdb53e2896d07f2568f6
+  checksum: 10c0/5a74ed2ed0a3ab51c3d15fcaf09d9e2fe915823535c7a4d7b019813177d559b69677090e189ec3d5d08b619483eb5ad371fbcfbbff5ace2a76ba33ee566a1109
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-template-literals@npm:^7.26.8":
-  version: 7.26.8
-  resolution: "@babel/plugin-transform-template-literals@npm:7.26.8"
+"@babel/plugin-transform-template-literals@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-template-literals@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.26.5"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/205a938ded9554857a604416d369023a961334b6c20943bd861b45f0e5dbbeca1cf6fda1c2049126e38a0d18865993433fdc78eae3028e94836b3b643c08ba0d
+  checksum: 10c0/3630f966257bcace122f04d3157416a09d40768c44c3a800855da81146b009187daa21859d1c3b7d13f4e19e8888e60613964b175b2275d451200fb6d8d6cfe6
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-typeof-symbol@npm:^7.26.7":
-  version: 7.26.7
-  resolution: "@babel/plugin-transform-typeof-symbol@npm:7.26.7"
+"@babel/plugin-transform-typeof-symbol@npm:^7.24.8":
+  version: 7.24.8
+  resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.8"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.26.5"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/d5640e3457637e6eee1d7205d255602ccca124ed30e4de10ec75ba179d167e0a826ceeab424e119921f5c995dfddf39ef1f2c91efd2dcbf3f0dc1e7931dfd1d1
+  checksum: 10c0/2f570a4fbbdc5fd85f48165a97452826560051e3b8efb48c3bb0a0a33ee8485633439e7b71bfe3ef705583a1df43f854f49125bd759abdedc195b2cf7e60012a
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-typescript@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-typescript@npm:7.25.9"
+"@babel/plugin-transform-typescript@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-typescript@npm:7.24.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.25.9"
-    "@babel/helper-create-class-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9"
-    "@babel/plugin-syntax-typescript": "npm:^7.25.9"
+    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
+    "@babel/helper-create-class-features-plugin": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/plugin-syntax-typescript": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/c607ddb45f7e33cfcb928aad05cb1b18b1ecb564d2329d8f8e427f75192511aa821dee42d26871f1bdffbd883853e150ba81436664646c6e6b13063e65ce1475
+  checksum: 10c0/e8dacdc153a4c4599014b66eb01b94e3dc933d58d4f0cc3039c1a8f432e77b9df14f34a61964e014b975bf466f3fefd8c4768b3e887d3da1be9dc942799bdfdf
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-unicode-escapes@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-unicode-escapes@npm:7.25.9"
+"@babel/plugin-transform-unicode-escapes@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-unicode-escapes@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/615c84d7c53e1575d54ba9257e753e0b98c5de1e3225237d92f55226eaab8eb5bceb74df43f50f4aa162b0bbcc934ed11feafe2b60b8ec4934ce340fad4b8828
+  checksum: 10c0/8b18e2e66af33471a6971289492beff5c240e56727331db1d34c4338a6a368a82a7ed6d57ec911001b6d65643aed76531e1e7cac93265fb3fb2717f54d845e69
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-unicode-property-regex@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.25.9"
+"@babel/plugin-transform-unicode-property-regex@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.24.7"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/1685836fc38af4344c3d2a9edbd46f7c7b28d369b63967d5b83f2f6849ec45b97223461cea3d14cc3f0be6ebb284938e637a5ca3955c0e79c873d62f593d615c
+  checksum: 10c0/bc57656eb94584d1b74a385d378818ac2b3fca642e3f649fead8da5fb3f9de22f8461185936915dfb33d5a9104e62e7a47828331248b09d28bb2d59e9276de3e
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-unicode-regex@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-unicode-regex@npm:7.25.9"
+"@babel/plugin-transform-unicode-regex@npm:^7.24.7":
+  version: 7.24.7
+  resolution: "@babel/plugin-transform-unicode-regex@npm:7.24.7"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/448004f978279e726af26acd54f63f9002c9e2582ecd70d1c5c4436f6de490fcd817afb60016d11c52f5ef17dbaac2590e8cc7bfaf4e91b58c452cf188c7920f
+  checksum: 10c0/83f72a345b751566b601dc4d07e9f2c8f1bc0e0c6f7abb56ceb3095b3c9d304de73f85f2f477a09f8cc7edd5e65afd0ff9e376cdbcbea33bc0c28f3705b38fd9
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.9":
-  version: 7.25.9
-  resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.9"
+"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.4":
+  version: 7.25.4
+  resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.4"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9"
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.2"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/56ee04fbe236b77cbcd6035cbf0be7566d1386b8349154ac33244c25f61170c47153a9423cd1d92855f7d6447b53a4a653d9e8fd1eaeeee14feb4b2baf59bd9f
+  checksum: 10c0/f65749835a98d8d6242e961f9276bdcdb09020e791d151ccc145acaca9a66f025b2c7cb761104f139180d35eb066a429596ee6edece81f5fd9244e0edb97d7ec
   languageName: node
   linkType: hard
 
 "@babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.12.1, @babel/preset-env@npm:^7.22.4":
-  version: 7.26.9
-  resolution: "@babel/preset-env@npm:7.26.9"
+  version: 7.25.4
+  resolution: "@babel/preset-env@npm:7.25.4"
   dependencies:
-    "@babel/compat-data": "npm:^7.26.8"
-    "@babel/helper-compilation-targets": "npm:^7.26.5"
-    "@babel/helper-plugin-utils": "npm:^7.26.5"
-    "@babel/helper-validator-option": "npm:^7.25.9"
-    "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.25.9"
-    "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.25.9"
-    "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.25.9"
-    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.25.9"
-    "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.9"
+    "@babel/compat-data": "npm:^7.25.4"
+    "@babel/helper-compilation-targets": "npm:^7.25.2"
+    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-validator-option": "npm:^7.24.8"
+    "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.25.3"
+    "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.25.0"
+    "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.25.0"
+    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.24.7"
+    "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.0"
     "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2"
-    "@babel/plugin-syntax-import-assertions": "npm:^7.26.0"
-    "@babel/plugin-syntax-import-attributes": "npm:^7.26.0"
+    "@babel/plugin-syntax-async-generators": "npm:^7.8.4"
+    "@babel/plugin-syntax-class-properties": "npm:^7.12.13"
+    "@babel/plugin-syntax-class-static-block": "npm:^7.14.5"
+    "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3"
+    "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3"
+    "@babel/plugin-syntax-import-assertions": "npm:^7.24.7"
+    "@babel/plugin-syntax-import-attributes": "npm:^7.24.7"
+    "@babel/plugin-syntax-import-meta": "npm:^7.10.4"
+    "@babel/plugin-syntax-json-strings": "npm:^7.8.3"
+    "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4"
+    "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3"
+    "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4"
+    "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3"
+    "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3"
+    "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3"
+    "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5"
+    "@babel/plugin-syntax-top-level-await": "npm:^7.14.5"
     "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6"
-    "@babel/plugin-transform-arrow-functions": "npm:^7.25.9"
-    "@babel/plugin-transform-async-generator-functions": "npm:^7.26.8"
-    "@babel/plugin-transform-async-to-generator": "npm:^7.25.9"
-    "@babel/plugin-transform-block-scoped-functions": "npm:^7.26.5"
-    "@babel/plugin-transform-block-scoping": "npm:^7.25.9"
-    "@babel/plugin-transform-class-properties": "npm:^7.25.9"
-    "@babel/plugin-transform-class-static-block": "npm:^7.26.0"
-    "@babel/plugin-transform-classes": "npm:^7.25.9"
-    "@babel/plugin-transform-computed-properties": "npm:^7.25.9"
-    "@babel/plugin-transform-destructuring": "npm:^7.25.9"
-    "@babel/plugin-transform-dotall-regex": "npm:^7.25.9"
-    "@babel/plugin-transform-duplicate-keys": "npm:^7.25.9"
-    "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.9"
-    "@babel/plugin-transform-dynamic-import": "npm:^7.25.9"
-    "@babel/plugin-transform-exponentiation-operator": "npm:^7.26.3"
-    "@babel/plugin-transform-export-namespace-from": "npm:^7.25.9"
-    "@babel/plugin-transform-for-of": "npm:^7.26.9"
-    "@babel/plugin-transform-function-name": "npm:^7.25.9"
-    "@babel/plugin-transform-json-strings": "npm:^7.25.9"
-    "@babel/plugin-transform-literals": "npm:^7.25.9"
-    "@babel/plugin-transform-logical-assignment-operators": "npm:^7.25.9"
-    "@babel/plugin-transform-member-expression-literals": "npm:^7.25.9"
-    "@babel/plugin-transform-modules-amd": "npm:^7.25.9"
-    "@babel/plugin-transform-modules-commonjs": "npm:^7.26.3"
-    "@babel/plugin-transform-modules-systemjs": "npm:^7.25.9"
-    "@babel/plugin-transform-modules-umd": "npm:^7.25.9"
-    "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.25.9"
-    "@babel/plugin-transform-new-target": "npm:^7.25.9"
-    "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.26.6"
-    "@babel/plugin-transform-numeric-separator": "npm:^7.25.9"
-    "@babel/plugin-transform-object-rest-spread": "npm:^7.25.9"
-    "@babel/plugin-transform-object-super": "npm:^7.25.9"
-    "@babel/plugin-transform-optional-catch-binding": "npm:^7.25.9"
-    "@babel/plugin-transform-optional-chaining": "npm:^7.25.9"
-    "@babel/plugin-transform-parameters": "npm:^7.25.9"
-    "@babel/plugin-transform-private-methods": "npm:^7.25.9"
-    "@babel/plugin-transform-private-property-in-object": "npm:^7.25.9"
-    "@babel/plugin-transform-property-literals": "npm:^7.25.9"
-    "@babel/plugin-transform-regenerator": "npm:^7.25.9"
-    "@babel/plugin-transform-regexp-modifiers": "npm:^7.26.0"
-    "@babel/plugin-transform-reserved-words": "npm:^7.25.9"
-    "@babel/plugin-transform-shorthand-properties": "npm:^7.25.9"
-    "@babel/plugin-transform-spread": "npm:^7.25.9"
-    "@babel/plugin-transform-sticky-regex": "npm:^7.25.9"
-    "@babel/plugin-transform-template-literals": "npm:^7.26.8"
-    "@babel/plugin-transform-typeof-symbol": "npm:^7.26.7"
-    "@babel/plugin-transform-unicode-escapes": "npm:^7.25.9"
-    "@babel/plugin-transform-unicode-property-regex": "npm:^7.25.9"
-    "@babel/plugin-transform-unicode-regex": "npm:^7.25.9"
-    "@babel/plugin-transform-unicode-sets-regex": "npm:^7.25.9"
+    "@babel/plugin-transform-arrow-functions": "npm:^7.24.7"
+    "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4"
+    "@babel/plugin-transform-async-to-generator": "npm:^7.24.7"
+    "@babel/plugin-transform-block-scoped-functions": "npm:^7.24.7"
+    "@babel/plugin-transform-block-scoping": "npm:^7.25.0"
+    "@babel/plugin-transform-class-properties": "npm:^7.25.4"
+    "@babel/plugin-transform-class-static-block": "npm:^7.24.7"
+    "@babel/plugin-transform-classes": "npm:^7.25.4"
+    "@babel/plugin-transform-computed-properties": "npm:^7.24.7"
+    "@babel/plugin-transform-destructuring": "npm:^7.24.8"
+    "@babel/plugin-transform-dotall-regex": "npm:^7.24.7"
+    "@babel/plugin-transform-duplicate-keys": "npm:^7.24.7"
+    "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.0"
+    "@babel/plugin-transform-dynamic-import": "npm:^7.24.7"
+    "@babel/plugin-transform-exponentiation-operator": "npm:^7.24.7"
+    "@babel/plugin-transform-export-namespace-from": "npm:^7.24.7"
+    "@babel/plugin-transform-for-of": "npm:^7.24.7"
+    "@babel/plugin-transform-function-name": "npm:^7.25.1"
+    "@babel/plugin-transform-json-strings": "npm:^7.24.7"
+    "@babel/plugin-transform-literals": "npm:^7.25.2"
+    "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7"
+    "@babel/plugin-transform-member-expression-literals": "npm:^7.24.7"
+    "@babel/plugin-transform-modules-amd": "npm:^7.24.7"
+    "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8"
+    "@babel/plugin-transform-modules-systemjs": "npm:^7.25.0"
+    "@babel/plugin-transform-modules-umd": "npm:^7.24.7"
+    "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7"
+    "@babel/plugin-transform-new-target": "npm:^7.24.7"
+    "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7"
+    "@babel/plugin-transform-numeric-separator": "npm:^7.24.7"
+    "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7"
+    "@babel/plugin-transform-object-super": "npm:^7.24.7"
+    "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7"
+    "@babel/plugin-transform-optional-chaining": "npm:^7.24.8"
+    "@babel/plugin-transform-parameters": "npm:^7.24.7"
+    "@babel/plugin-transform-private-methods": "npm:^7.25.4"
+    "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7"
+    "@babel/plugin-transform-property-literals": "npm:^7.24.7"
+    "@babel/plugin-transform-regenerator": "npm:^7.24.7"
+    "@babel/plugin-transform-reserved-words": "npm:^7.24.7"
+    "@babel/plugin-transform-shorthand-properties": "npm:^7.24.7"
+    "@babel/plugin-transform-spread": "npm:^7.24.7"
+    "@babel/plugin-transform-sticky-regex": "npm:^7.24.7"
+    "@babel/plugin-transform-template-literals": "npm:^7.24.7"
+    "@babel/plugin-transform-typeof-symbol": "npm:^7.24.8"
+    "@babel/plugin-transform-unicode-escapes": "npm:^7.24.7"
+    "@babel/plugin-transform-unicode-property-regex": "npm:^7.24.7"
+    "@babel/plugin-transform-unicode-regex": "npm:^7.24.7"
+    "@babel/plugin-transform-unicode-sets-regex": "npm:^7.25.4"
     "@babel/preset-modules": "npm:0.1.6-no-external-plugins"
     babel-plugin-polyfill-corejs2: "npm:^0.4.10"
-    babel-plugin-polyfill-corejs3: "npm:^0.11.0"
+    babel-plugin-polyfill-corejs3: "npm:^0.10.6"
     babel-plugin-polyfill-regenerator: "npm:^0.6.1"
-    core-js-compat: "npm:^3.40.0"
+    core-js-compat: "npm:^3.37.1"
     semver: "npm:^6.3.1"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/6812ca76bd38165a58fe8354bab5e7204e1aa17d8b9270bd8f8babb08cc7fa94cd29525fe41b553f2ba0e84033d566f10da26012b8ee0f81897005c5225d0051
+  checksum: 10c0/ed210a1974b5a1e7f80a933c87253907ec869457cea900bc97892642fa9a690c47627a9bac08a7c9495deb992a2b15f308ffca2741e1876ba47172c96fa27e14
   languageName: node
   linkType: hard
 
@@ -1387,33 +1440,40 @@ __metadata:
   linkType: hard
 
 "@babel/preset-react@npm:^7.12.5, @babel/preset-react@npm:^7.22.3":
-  version: 7.26.3
-  resolution: "@babel/preset-react@npm:7.26.3"
+  version: 7.24.7
+  resolution: "@babel/preset-react@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/helper-validator-option": "npm:^7.25.9"
-    "@babel/plugin-transform-react-display-name": "npm:^7.25.9"
-    "@babel/plugin-transform-react-jsx": "npm:^7.25.9"
-    "@babel/plugin-transform-react-jsx-development": "npm:^7.25.9"
-    "@babel/plugin-transform-react-pure-annotations": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-validator-option": "npm:^7.24.7"
+    "@babel/plugin-transform-react-display-name": "npm:^7.24.7"
+    "@babel/plugin-transform-react-jsx": "npm:^7.24.7"
+    "@babel/plugin-transform-react-jsx-development": "npm:^7.24.7"
+    "@babel/plugin-transform-react-pure-annotations": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/b470dcba11032ef6c832066f4af5c75052eaed49feb0f445227231ef1b5c42aacd6e216988c0bd469fd5728cd27b6b059ca307c9ecaa80c6bb5da4bf1c833e12
+  checksum: 10c0/9658b685b25cedaadd0b65c4e663fbc7f57394b5036ddb4c99b1a75b0711fb83292c1c625d605c05b73413fc7a6dc20e532627f6a39b6dc8d4e00415479b054c
   languageName: node
   linkType: hard
 
 "@babel/preset-typescript@npm:^7.21.5":
-  version: 7.26.0
-  resolution: "@babel/preset-typescript@npm:7.26.0"
+  version: 7.24.7
+  resolution: "@babel/preset-typescript@npm:7.24.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.25.9"
-    "@babel/helper-validator-option": "npm:^7.25.9"
-    "@babel/plugin-syntax-jsx": "npm:^7.25.9"
-    "@babel/plugin-transform-modules-commonjs": "npm:^7.25.9"
-    "@babel/plugin-transform-typescript": "npm:^7.25.9"
+    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-validator-option": "npm:^7.24.7"
+    "@babel/plugin-syntax-jsx": "npm:^7.24.7"
+    "@babel/plugin-transform-modules-commonjs": "npm:^7.24.7"
+    "@babel/plugin-transform-typescript": "npm:^7.24.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/20d86bc45d2bbfde2f84fc7d7b38746fa6481d4bde6643039ad4b1ff0b804c6d210ee43e6830effd8571f2ff43fa7ffd27369f42f2b3a2518bb92dc86c780c61
+  checksum: 10c0/986bc0978eedb4da33aba8e1e13a3426dd1829515313b7e8f4ba5d8c18aff1663b468939d471814e7acf4045d326ae6cff37239878d169ac3fe53a8fde71f8ee
+  languageName: node
+  linkType: hard
+
+"@babel/regjsgen@npm:^0.8.0":
+  version: 0.8.0
+  resolution: "@babel/regjsgen@npm:0.8.0"
+  checksum: 10c0/4f3ddd8c7c96d447e05c8304c1d5ba3a83fcabd8a716bc1091c2f31595cdd43a3a055fff7cb5d3042b8cb7d402d78820fcb4e05d896c605a7d8bcf30f2424c4a
   languageName: node
   linkType: hard
 
@@ -1427,68 +1487,48 @@ __metadata:
   linkType: hard
 
 "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.23.7, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
-  version: 7.26.9
-  resolution: "@babel/runtime@npm:7.26.9"
+  version: 7.25.6
+  resolution: "@babel/runtime@npm:7.25.6"
   dependencies:
     regenerator-runtime: "npm:^0.14.0"
-  checksum: 10c0/e8517131110a6ec3a7360881438b85060e49824e007f4a64b5dfa9192cf2bb5c01e84bfc109f02d822c7edb0db926928dd6b991e3ee460b483fb0fac43152d9b
+  checksum: 10c0/d6143adf5aa1ce79ed374e33fdfd74fa975055a80bc6e479672ab1eadc4e4bfd7484444e17dd063a1d180e051f3ec62b357c7a2b817e7657687b47313158c3d2
   languageName: node
   linkType: hard
 
-"@babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.3.3":
-  version: 7.26.9
-  resolution: "@babel/template@npm:7.26.9"
+"@babel/template@npm:^7.24.7, @babel/template@npm:^7.25.0, @babel/template@npm:^7.3.3":
+  version: 7.25.0
+  resolution: "@babel/template@npm:7.25.0"
   dependencies:
-    "@babel/code-frame": "npm:^7.26.2"
-    "@babel/parser": "npm:^7.26.9"
-    "@babel/types": "npm:^7.26.9"
-  checksum: 10c0/019b1c4129cc01ad63e17529089c2c559c74709d225f595eee017af227fee11ae8a97a6ab19ae6768b8aa22d8d75dcb60a00b28f52e9fa78140672d928bc1ae9
+    "@babel/code-frame": "npm:^7.24.7"
+    "@babel/parser": "npm:^7.25.0"
+    "@babel/types": "npm:^7.25.0"
+  checksum: 10c0/4e31afd873215744c016e02b04f43b9fa23205d6d0766fb2e93eb4091c60c1b88897936adb895fb04e3c23de98dfdcbe31bc98daaa1a4e0133f78bb948e1209b
   languageName: node
   linkType: hard
 
-"@babel/template@npm:^7.27.0":
-  version: 7.27.0
-  resolution: "@babel/template@npm:7.27.0"
+"@babel/traverse@npm:7, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.25.4":
+  version: 7.25.4
+  resolution: "@babel/traverse@npm:7.25.4"
   dependencies:
-    "@babel/code-frame": "npm:^7.26.2"
-    "@babel/parser": "npm:^7.27.0"
-    "@babel/types": "npm:^7.27.0"
-  checksum: 10c0/13af543756127edb5f62bf121f9b093c09a2b6fe108373887ccffc701465cfbcb17e07cf48aa7f440415b263f6ec006e9415c79dfc2e8e6010b069435f81f340
-  languageName: node
-  linkType: hard
-
-"@babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.26.5, @babel/traverse@npm:^7.26.8, @babel/traverse@npm:^7.27.0":
-  version: 7.27.0
-  resolution: "@babel/traverse@npm:7.27.0"
-  dependencies:
-    "@babel/code-frame": "npm:^7.26.2"
-    "@babel/generator": "npm:^7.27.0"
-    "@babel/parser": "npm:^7.27.0"
-    "@babel/template": "npm:^7.27.0"
-    "@babel/types": "npm:^7.27.0"
+    "@babel/code-frame": "npm:^7.24.7"
+    "@babel/generator": "npm:^7.25.4"
+    "@babel/parser": "npm:^7.25.4"
+    "@babel/template": "npm:^7.25.0"
+    "@babel/types": "npm:^7.25.4"
     debug: "npm:^4.3.1"
     globals: "npm:^11.1.0"
-  checksum: 10c0/c7af29781960dacaae51762e8bc6c4b13d6ab4b17312990fbca9fc38e19c4ad7fecaae24b1cf52fb844e8e6cdc76c70ad597f90e496bcb3cc0a1d66b41a0aa5b
+  checksum: 10c0/37c9b49b277e051fe499ef5f6f217370c4f648d6370564d70b5e6beb2da75bfda6d7dab1d39504d89e9245448f8959bc1a5880d2238840cdc3979b35338ed0f5
   languageName: node
   linkType: hard
 
-"@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.12.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
-  version: 7.27.0
-  resolution: "@babel/types@npm:7.27.0"
+"@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
+  version: 7.25.4
+  resolution: "@babel/types@npm:7.25.4"
   dependencies:
-    "@babel/helper-string-parser": "npm:^7.25.9"
-    "@babel/helper-validator-identifier": "npm:^7.25.9"
-  checksum: 10c0/6f1592eabe243c89a608717b07b72969be9d9d2fce1dee21426238757ea1fa60fdfc09b29de9e48d8104311afc6e6fb1702565a9cc1e09bc1e76f2b2ddb0f6e1
-  languageName: node
-  linkType: hard
-
-"@babel/types@npm:^7.26.9":
-  version: 7.26.9
-  resolution: "@babel/types@npm:7.26.9"
-  dependencies:
-    "@babel/helper-string-parser": "npm:^7.25.9"
-    "@babel/helper-validator-identifier": "npm:^7.25.9"
-  checksum: 10c0/999c56269ba00e5c57aa711fbe7ff071cd6990bafd1b978341ea7572cc78919986e2aa6ee51dacf4b6a7a6fa63ba4eb3f1a03cf55eee31b896a56d068b895964
+    "@babel/helper-string-parser": "npm:^7.24.8"
+    "@babel/helper-validator-identifier": "npm:^7.24.7"
+    to-fast-properties: "npm:^2.0.0"
+  checksum: 10c0/9aa25dfcd89cc4e4dde3188091c34398a005a49e2c2b069d0367b41e1122c91e80fd92998c52a90f2fb500f7e897b6090ec8be263d9cb53d0d75c756f44419f2
   languageName: node
   linkType: hard
 
@@ -1499,138 +1539,138 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@csstools/cascade-layer-name-parser@npm:^2.0.4":
-  version: 2.0.4
-  resolution: "@csstools/cascade-layer-name-parser@npm:2.0.4"
+"@csstools/cascade-layer-name-parser@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "@csstools/cascade-layer-name-parser@npm:2.0.1"
   peerDependencies:
-    "@csstools/css-parser-algorithms": ^3.0.4
-    "@csstools/css-tokenizer": ^3.0.3
-  checksum: 10c0/774f2bcc96a576183853191bdfd31df15e22c51901ee01678ee47f1d1afcb4ab0e6d9a78e08f7383ac089c7e0b390013633f45ff1f1d577c9aefd252589bcced
+    "@csstools/css-parser-algorithms": ^3.0.1
+    "@csstools/css-tokenizer": ^3.0.1
+  checksum: 10c0/e1f9030285d1a16ca0424018289e5288e58dee2d6f6cc392e27d9e8eca0deeea4ced9b749eef09a8322746cb15b6304bc7e2d04bb9dc7405c29b3717ec80e736
   languageName: node
   linkType: hard
 
-"@csstools/color-helpers@npm:^5.0.2":
-  version: 5.0.2
-  resolution: "@csstools/color-helpers@npm:5.0.2"
-  checksum: 10c0/bebaddb28b9eb58b0449edd5d0c0318fa88f3cb079602ee27e88c9118070d666dcc4e09a5aa936aba2fde6ba419922ade07b7b506af97dd7051abd08dfb2959b
-  languageName: node
-  linkType: hard
-
-"@csstools/css-calc@npm:^2.1.1, @csstools/css-calc@npm:^2.1.2":
-  version: 2.1.2
-  resolution: "@csstools/css-calc@npm:2.1.2"
-  peerDependencies:
-    "@csstools/css-parser-algorithms": ^3.0.4
-    "@csstools/css-tokenizer": ^3.0.3
-  checksum: 10c0/34ced30553968ef5d5f9e00e3b90b48c47480cf130e282e99d57ec9b09f803aab8bc06325683e72a1518b5e7180a3da8b533f1b462062757c21989a53b482e1a
-  languageName: node
-  linkType: hard
-
-"@csstools/css-color-parser@npm:^3.0.7, @csstools/css-color-parser@npm:^3.0.8":
-  version: 3.0.8
-  resolution: "@csstools/css-color-parser@npm:3.0.8"
-  dependencies:
-    "@csstools/color-helpers": "npm:^5.0.2"
-    "@csstools/css-calc": "npm:^2.1.2"
-  peerDependencies:
-    "@csstools/css-parser-algorithms": ^3.0.4
-    "@csstools/css-tokenizer": ^3.0.3
-  checksum: 10c0/90722c5a62ca94e9d578ddf59be604a76400b932bd3d4bd23cb1ae9b7ace8fcf83c06995d2b31f96f4afef24a7cefba79beb11ed7ee4999d7ecfec3869368359
-  languageName: node
-  linkType: hard
-
-"@csstools/css-parser-algorithms@npm:^3.0.4":
-  version: 3.0.4
-  resolution: "@csstools/css-parser-algorithms@npm:3.0.4"
-  peerDependencies:
-    "@csstools/css-tokenizer": ^3.0.3
-  checksum: 10c0/d411f07765e14eede17bccc6bd4f90ff303694df09aabfede3fd104b2dfacfd4fe3697cd25ddad14684c850328f3f9420ebfa9f78380892492974db24ae47dbd
-  languageName: node
-  linkType: hard
-
-"@csstools/css-tokenizer@npm:^3.0.3":
-  version: 3.0.3
-  resolution: "@csstools/css-tokenizer@npm:3.0.3"
-  checksum: 10c0/c31bf410e1244b942e71798e37c54639d040cb59e0121b21712b40015fced2b0fb1ffe588434c5f8923c9cd0017cfc1c1c8f3921abc94c96edf471aac2eba5e5
-  languageName: node
-  linkType: hard
-
-"@csstools/media-query-list-parser@npm:^4.0.2":
-  version: 4.0.2
-  resolution: "@csstools/media-query-list-parser@npm:4.0.2"
-  peerDependencies:
-    "@csstools/css-parser-algorithms": ^3.0.4
-    "@csstools/css-tokenizer": ^3.0.3
-  checksum: 10c0/5d008a70f5d4fd96224066a433f5cdefa76cfd78a74416a20d6d5b2bb1bc8282b140e8373015d807d4dadb91daf3deb73eb13f853ec4e0479d0cb92e80c6f20d
-  languageName: node
-  linkType: hard
-
-"@csstools/postcss-cascade-layers@npm:^5.0.1":
+"@csstools/color-helpers@npm:^5.0.1":
   version: 5.0.1
-  resolution: "@csstools/postcss-cascade-layers@npm:5.0.1"
-  dependencies:
-    "@csstools/selector-specificity": "npm:^5.0.0"
-    postcss-selector-parser: "npm:^7.0.0"
-  peerDependencies:
-    postcss: ^8.4
-  checksum: 10c0/5cc3c6f220d9216f7ab16e716a20d6db845f127c917521e6236342bfa871accd63eb662a04c1e24a28e396412dcb47b1c4abccc490b88e4010cd704d14a702f1
+  resolution: "@csstools/color-helpers@npm:5.0.1"
+  checksum: 10c0/77fa3b7236eaa3f36dea24708ac0d5e53168903624ac5aed54615752a0730cd20773fda50e742ce868012eca8c000cc39688e05869e79f34714230ab6968d1e6
   languageName: node
   linkType: hard
 
-"@csstools/postcss-color-function@npm:^4.0.8":
-  version: 4.0.8
-  resolution: "@csstools/postcss-color-function@npm:4.0.8"
+"@csstools/css-calc@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "@csstools/css-calc@npm:2.0.1"
+  peerDependencies:
+    "@csstools/css-parser-algorithms": ^3.0.1
+    "@csstools/css-tokenizer": ^3.0.1
+  checksum: 10c0/84c0ba3dac51466c9ac22c3540360f6058cf351a3676d71d4412584b165a91abc7c69a4ddf5a5dacc6e6082806d2317cf50ddbc0a0562275b2aedaee41cb87a9
+  languageName: node
+  linkType: hard
+
+"@csstools/css-color-parser@npm:^3.0.2":
+  version: 3.0.2
+  resolution: "@csstools/css-color-parser@npm:3.0.2"
   dependencies:
-    "@csstools/css-color-parser": "npm:^3.0.8"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/color-helpers": "npm:^5.0.1"
+    "@csstools/css-calc": "npm:^2.0.1"
+  peerDependencies:
+    "@csstools/css-parser-algorithms": ^3.0.1
+    "@csstools/css-tokenizer": ^3.0.1
+  checksum: 10c0/31f42cc22426c937f5c6999889f72b40aec8504189a6badf3319552f27a5af73dd5f46be9ebc54eb87e526468eb2546ffbd60a6879fe599efa6c98e51d6cfa3d
+  languageName: node
+  linkType: hard
+
+"@csstools/css-parser-algorithms@npm:^3.0.1":
+  version: 3.0.1
+  resolution: "@csstools/css-parser-algorithms@npm:3.0.1"
+  peerDependencies:
+    "@csstools/css-tokenizer": ^3.0.1
+  checksum: 10c0/064c6d519197b5af43bbf5efe8f4cdbd361b006113aa82160d637e925b50c643a52d33d512ca01c63042d952d723a2a10798231a714668356b76668fb11294e3
+  languageName: node
+  linkType: hard
+
+"@csstools/css-tokenizer@npm:^3.0.1":
+  version: 3.0.1
+  resolution: "@csstools/css-tokenizer@npm:3.0.1"
+  checksum: 10c0/c9ed4373e5731b5375ea9791590081019c04e95f08b46b272977e5e7b8c3d560affc62e82263cb8def1df1e57f0673140e7e16a14a5e7be04e6a234be088d1d3
+  languageName: node
+  linkType: hard
+
+"@csstools/media-query-list-parser@npm:^3.0.1":
+  version: 3.0.1
+  resolution: "@csstools/media-query-list-parser@npm:3.0.1"
+  peerDependencies:
+    "@csstools/css-parser-algorithms": ^3.0.1
+    "@csstools/css-tokenizer": ^3.0.1
+  checksum: 10c0/fca1935cabf9fb94128da87f72c34aa2cfce8eb0beba4c78d685c7b42aaba3521067710afc6905b7347fc41fe53947536ce15a7ef3387b48763d8f7d71778d5e
+  languageName: node
+  linkType: hard
+
+"@csstools/postcss-cascade-layers@npm:^5.0.0":
+  version: 5.0.0
+  resolution: "@csstools/postcss-cascade-layers@npm:5.0.0"
+  dependencies:
+    "@csstools/selector-specificity": "npm:^4.0.0"
+    postcss-selector-parser: "npm:^6.1.0"
+  peerDependencies:
+    postcss: ^8.4
+  checksum: 10c0/b608c69c12671682676598e451dcd79bfc6f5030a4e17b4d1bf9659e531f1daf03526be023f9aafdc952ecc87c87b04f379a763309e3eadb2140572cd4aa5b60
+  languageName: node
+  linkType: hard
+
+"@csstools/postcss-color-function@npm:^4.0.2":
+  version: 4.0.2
+  resolution: "@csstools/postcss-color-function@npm:4.0.2"
+  dependencies:
+    "@csstools/css-color-parser": "npm:^3.0.2"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/d52c65bb4ed28f62b3fc9c0b2ce068e58395345dcead797ed8f7e4f5f469a9311607d39dd409c571ccc94d6c5c84171aff62d51d4f53fdcf6e1cca23fc31d4f1
+  checksum: 10c0/c987ccc7ab326668895396d3fe69c05087cf6e245be6f70e8c33da0cdeb56f8ac3d117cfad984191ec57be9691ab56d427aaa28c61c4a7446521972993dd5039
   languageName: node
   linkType: hard
 
-"@csstools/postcss-color-mix-function@npm:^3.0.8":
-  version: 3.0.8
-  resolution: "@csstools/postcss-color-mix-function@npm:3.0.8"
+"@csstools/postcss-color-mix-function@npm:^3.0.2":
+  version: 3.0.2
+  resolution: "@csstools/postcss-color-mix-function@npm:3.0.2"
   dependencies:
-    "@csstools/css-color-parser": "npm:^3.0.8"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-color-parser": "npm:^3.0.2"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/3fe7093b38f2b469462fa942af5a54a1ad68b07cd33267288e5c9e865d3a871c04774463136e4af24955316f40560dda1371d02cfd5595475a742afae13a37ba
+  checksum: 10c0/cfdc641f504f9d02b9a7b53d6bd933d4e767ecaee5f3d2df45d897f69b8a38b5b79b538d307b16fb56dcb7c19dc7e107518c356772e89771e28e04fd846d9035
   languageName: node
   linkType: hard
 
-"@csstools/postcss-content-alt-text@npm:^2.0.4":
-  version: 2.0.4
-  resolution: "@csstools/postcss-content-alt-text@npm:2.0.4"
+"@csstools/postcss-content-alt-text@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "@csstools/postcss-content-alt-text@npm:2.0.1"
   dependencies:
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/84caccedd8a519df434babd58b14104c5a92cd326057ce509bdbaa2a4bb3130afb1c1456caf30235ba14da52d1628a5411ea4f5d2fb558d603d234f795538017
+  checksum: 10c0/693e4cfa9a30a9c384120bd24846a7cd5ba1e1ebf975eb81b0e0131a21ac28a0301c7dcfa13706bc7d8b5343536fb43a46de636c3257d0fd05041c7255366e87
   languageName: node
   linkType: hard
 
-"@csstools/postcss-exponential-functions@npm:^2.0.7":
-  version: 2.0.7
-  resolution: "@csstools/postcss-exponential-functions@npm:2.0.7"
+"@csstools/postcss-exponential-functions@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "@csstools/postcss-exponential-functions@npm:2.0.1"
   dependencies:
-    "@csstools/css-calc": "npm:^2.1.2"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-calc": "npm:^2.0.1"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/9d02076135ee9bf82bf911f577c9fda42bf00347f3c519fa83e32e83f5b8a98649b97e13ba3a42ed906467729d7b69574595556dfb9e865c86d3bbae5ffbc918
+  checksum: 10c0/ddcaedfa48cc0cf93611c8d2ed5a75d56c1d196a97015db644b45881adabb47f3255242acaef6ea869a1e5ba66328725d254bf6d29eb5e988cde8b040bc5f55d
   languageName: node
   linkType: hard
 
@@ -1646,46 +1686,46 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@csstools/postcss-gamut-mapping@npm:^2.0.8":
-  version: 2.0.8
-  resolution: "@csstools/postcss-gamut-mapping@npm:2.0.8"
+"@csstools/postcss-gamut-mapping@npm:^2.0.2":
+  version: 2.0.2
+  resolution: "@csstools/postcss-gamut-mapping@npm:2.0.2"
   dependencies:
-    "@csstools/css-color-parser": "npm:^3.0.8"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-color-parser": "npm:^3.0.2"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/81daaba0e774ed3ab97e2c7c93dcae16d1e8447a27f0e82ddf8a176e8f1e93b444f463284105fd312c6234d4210372d6d69d96efcfb05bc5b6adfba6fcfd6f44
+  checksum: 10c0/8b6504f81c5036e3c2a9f9516c371f48a283112469b746546c8c7f6f0da2467c915d4dac6dfe8bb05d7dab3a7503911391eb9e666cb7632e09a032e801c029f5
   languageName: node
   linkType: hard
 
-"@csstools/postcss-gradients-interpolation-method@npm:^5.0.8":
-  version: 5.0.8
-  resolution: "@csstools/postcss-gradients-interpolation-method@npm:5.0.8"
+"@csstools/postcss-gradients-interpolation-method@npm:^5.0.2":
+  version: 5.0.2
+  resolution: "@csstools/postcss-gradients-interpolation-method@npm:5.0.2"
   dependencies:
-    "@csstools/css-color-parser": "npm:^3.0.8"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-color-parser": "npm:^3.0.2"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/832bfb663b334be9783f49c354cbeec3cede1830a576b91a101456db33207e9651f97624f0df92e5d01a39b68a215ad4b20621ee229b92b51607e889093bc590
+  checksum: 10c0/4fa27437ad9861b1457c28228f2503c17bcc2f77dcb38da1314a4a5d38fd010e7e5d11b5f9d69e0a2cb2999bbfeca1e99ce2f59422bda5b382658dcb03f7326e
   languageName: node
   linkType: hard
 
-"@csstools/postcss-hwb-function@npm:^4.0.8":
-  version: 4.0.8
-  resolution: "@csstools/postcss-hwb-function@npm:4.0.8"
+"@csstools/postcss-hwb-function@npm:^4.0.2":
+  version: 4.0.2
+  resolution: "@csstools/postcss-hwb-function@npm:4.0.2"
   dependencies:
-    "@csstools/css-color-parser": "npm:^3.0.8"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-color-parser": "npm:^3.0.2"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/d6196e2acfc0a6fd61fe254385049fb784abb862c724543940dbba8ffe29bbdbedd83985a517132a21073435445486f918da170fb0f710dbe40a798b9abc41e7
+  checksum: 10c0/46dc9596e37830de4c38f70764d6da9f2fc7bc339217b4291eced75daa8998c4e05fb743c271701e44818df4ac111c285019b7bb3a728e8b61d86899bbeb74eb
   languageName: node
   linkType: hard
 
@@ -1702,38 +1742,38 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@csstools/postcss-initial@npm:^2.0.1":
-  version: 2.0.1
-  resolution: "@csstools/postcss-initial@npm:2.0.1"
+"@csstools/postcss-initial@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "@csstools/postcss-initial@npm:2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/dbff7084ef4f1c4647efe2b147001daf172003c15b5e22689f0540d03c8d362f2a332cd9cf136e6c8dcda7564ee30492a4267ea188f72cb9c1000fb9bcfbfef8
+  checksum: 10c0/44c443cba84cc66367f2082bf20db06c8437338c02c244c38798c5bf5342932d89fed0dd13e4409f084ecf7fce47ae6394e9a7a006fd98a973decfa24ab1eb04
   languageName: node
   linkType: hard
 
-"@csstools/postcss-is-pseudo-class@npm:^5.0.1":
-  version: 5.0.1
-  resolution: "@csstools/postcss-is-pseudo-class@npm:5.0.1"
+"@csstools/postcss-is-pseudo-class@npm:^5.0.0":
+  version: 5.0.0
+  resolution: "@csstools/postcss-is-pseudo-class@npm:5.0.0"
   dependencies:
-    "@csstools/selector-specificity": "npm:^5.0.0"
-    postcss-selector-parser: "npm:^7.0.0"
+    "@csstools/selector-specificity": "npm:^4.0.0"
+    postcss-selector-parser: "npm:^6.1.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/3aaab18ebb2dcf5565efa79813eaa987d40de1e086765358524392a09631c68ad1ee952e6aff8f42513b2c18ab84891787e065fe287f696128498fc641520b6c
+  checksum: 10c0/738eb84728b24bfe19ca06ccf6ff773a423552df2f31c87704ce79da4abfd2ccf2a45d5d6d3e11e71e42cc3d92eb35a856209b9cd6116c879acf15ac75454683
   languageName: node
   linkType: hard
 
-"@csstools/postcss-light-dark-function@npm:^2.0.7":
-  version: 2.0.7
-  resolution: "@csstools/postcss-light-dark-function@npm:2.0.7"
+"@csstools/postcss-light-dark-function@npm:^2.0.4":
+  version: 2.0.4
+  resolution: "@csstools/postcss-light-dark-function@npm:2.0.4"
   dependencies:
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/c116bfd2d3f4d0caabdedf8954c2a25908ffb29f9bbe2c57d44a2974277c7e46ee79862eea848385dc040275d343f2330350394a2095ec30f0aa17f72e2f4e39
+  checksum: 10c0/0176422ad9747953964b1ceff002df1ecb1952ebc481db6192070d68777135b582ea6fd32ae819b9c64c96cb9170bd6907c647c85b48daa4984b7ed3d7f9bccb
   languageName: node
   linkType: hard
 
@@ -1775,42 +1815,42 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@csstools/postcss-logical-viewport-units@npm:^3.0.3":
-  version: 3.0.3
-  resolution: "@csstools/postcss-logical-viewport-units@npm:3.0.3"
+"@csstools/postcss-logical-viewport-units@npm:^3.0.1":
+  version: 3.0.1
+  resolution: "@csstools/postcss-logical-viewport-units@npm:3.0.1"
   dependencies:
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/8ec746598d7ce8697c3dafd83cb3a319a90079ad755dd78e3ec92f4ba9ad849c4cdaba33b16e9dcbac1e9489b3d7c48262030110c20ce1d88cdacbe9f5987cec
+  checksum: 10c0/f54f91ec4d308562371576e82131c3cc1ff461a951c9a38f0b42b783c26f37a93cc846fcd025d3c4a8437b55a4fff1192ebfac8ccf84abb6478b2c515d232552
   languageName: node
   linkType: hard
 
-"@csstools/postcss-media-minmax@npm:^2.0.7":
-  version: 2.0.7
-  resolution: "@csstools/postcss-media-minmax@npm:2.0.7"
+"@csstools/postcss-media-minmax@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "@csstools/postcss-media-minmax@npm:2.0.1"
   dependencies:
-    "@csstools/css-calc": "npm:^2.1.2"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
-    "@csstools/media-query-list-parser": "npm:^4.0.2"
+    "@csstools/css-calc": "npm:^2.0.1"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
+    "@csstools/media-query-list-parser": "npm:^3.0.1"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/03b7a5603437d5be17e9c0d951ca0b7b3b6f437fd4e24e3ac3f70ed9d573ef67641821fe209b5764c54aa36e841c830a5d8cf3a3dd97fd2fa774b7ceba7ba038
+  checksum: 10c0/23c1fb0c3ed8bf82f3223f161d0d65ba62045b917bc19624581f64aaa5d678485d22c23af2591a3f634ba02030ccb419c2b30209aa22f9fd2baa1a6474af810a
   languageName: node
   linkType: hard
 
-"@csstools/postcss-media-queries-aspect-ratio-number-values@npm:^3.0.4":
-  version: 3.0.4
-  resolution: "@csstools/postcss-media-queries-aspect-ratio-number-values@npm:3.0.4"
+"@csstools/postcss-media-queries-aspect-ratio-number-values@npm:^3.0.1":
+  version: 3.0.1
+  resolution: "@csstools/postcss-media-queries-aspect-ratio-number-values@npm:3.0.1"
   dependencies:
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
-    "@csstools/media-query-list-parser": "npm:^4.0.2"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
+    "@csstools/media-query-list-parser": "npm:^3.0.1"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/27dc9419b0f4315774647588f599348e7cc593984f59b414c51c910066501fd087cbe232deb762907c18bd21dd4184e7b6e0e0b730e5c72341ab9cc696c75739
+  checksum: 10c0/e491cb149fb4fff85b2f03191511e43654ae00716e3c1ea9f1dc22ec4e7042c35f034d372082a69d3621c86cafbe46e8f419872fa36f4a534f145f584d655768
   languageName: node
   linkType: hard
 
@@ -1837,18 +1877,18 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@csstools/postcss-oklab-function@npm:^4.0.8":
-  version: 4.0.8
-  resolution: "@csstools/postcss-oklab-function@npm:4.0.8"
+"@csstools/postcss-oklab-function@npm:^4.0.2":
+  version: 4.0.2
+  resolution: "@csstools/postcss-oklab-function@npm:4.0.2"
   dependencies:
-    "@csstools/css-color-parser": "npm:^3.0.8"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-color-parser": "npm:^3.0.2"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/8a62f3875bb9026c95758a0b834e876a8f07dd1a5ba36c3967e230565fbd9afd21ec714c8590cb4ea594fd214e68f2ccf58456ed6e919a47d2ed17d5b63a925a
+  checksum: 10c0/3209a7cec6d3577544a7ef41f2d5cca25f77891d4ec0d7f39d32f9a79a6c9a9b0ee6b54b2937a2d995548ad11c39966b07d4b9f58e907ffbe1a4b454f2d277f3
   languageName: node
   linkType: hard
 
@@ -1863,93 +1903,67 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@csstools/postcss-random-function@npm:^1.0.3":
-  version: 1.0.3
-  resolution: "@csstools/postcss-random-function@npm:1.0.3"
+"@csstools/postcss-relative-color-syntax@npm:^3.0.2":
+  version: 3.0.2
+  resolution: "@csstools/postcss-relative-color-syntax@npm:3.0.2"
   dependencies:
-    "@csstools/css-calc": "npm:^2.1.2"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
-  peerDependencies:
-    postcss: ^8.4
-  checksum: 10c0/c3bf319a6f79c0e372e4754e7888a4cd3a97b81e480662b1d1cb193949670bbcd5995c42483390a996e66d6dd81c9ad753836cc617aac2e3acbd542faa56f907
-  languageName: node
-  linkType: hard
-
-"@csstools/postcss-relative-color-syntax@npm:^3.0.8":
-  version: 3.0.8
-  resolution: "@csstools/postcss-relative-color-syntax@npm:3.0.8"
-  dependencies:
-    "@csstools/css-color-parser": "npm:^3.0.8"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-color-parser": "npm:^3.0.2"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/fcd14fb1c3f103dbaaf88afa2540f9946313d48515fa24fffcde4200e7dc4aa767d186ecf2e12bb0501dd946a824f118cd4ad5d44899c8d6d9d8d9d9b99a123e
+  checksum: 10c0/34a8c999e08c6e7833484ee2fb91e7fcc25235d6c361712fed581e44a5a29f1ceb95415b6f4260de53809ac13f5da5415d1905c2971477cf5d45e5196081c663
   languageName: node
   linkType: hard
 
-"@csstools/postcss-scope-pseudo-class@npm:^4.0.1":
+"@csstools/postcss-scope-pseudo-class@npm:^4.0.0":
+  version: 4.0.0
+  resolution: "@csstools/postcss-scope-pseudo-class@npm:4.0.0"
+  dependencies:
+    postcss-selector-parser: "npm:^6.1.0"
+  peerDependencies:
+    postcss: ^8.4
+  checksum: 10c0/a6f562df1417c6f257c0fec95babf4cea99a25622573a2dbcfb416e2fcb8e1e53561127f5e7277d19fcb2a4603bdbc64dd6a4c416429623503c604050c99229a
+  languageName: node
+  linkType: hard
+
+"@csstools/postcss-stepped-value-functions@npm:^4.0.1":
   version: 4.0.1
-  resolution: "@csstools/postcss-scope-pseudo-class@npm:4.0.1"
+  resolution: "@csstools/postcss-stepped-value-functions@npm:4.0.1"
   dependencies:
-    postcss-selector-parser: "npm:^7.0.0"
+    "@csstools/css-calc": "npm:^2.0.1"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/6a0ca50fae655f4498200d1ce298ca794c85fbe2e3fd5d6419843254f055df5007a973e09b5f1e78e376c02b54278e411516c8d824300c68b265d3e5b311d7ee
+  checksum: 10c0/7e65969b124fce603675ca17c2ffa2bb456677866e54bc9fbdc4da0945be1593fde2abb0730d3d03190776ad2022b394a1f9d4834c5b1f4c7ec497929fd35f8f
   languageName: node
   linkType: hard
 
-"@csstools/postcss-sign-functions@npm:^1.1.2":
-  version: 1.1.2
-  resolution: "@csstools/postcss-sign-functions@npm:1.1.2"
+"@csstools/postcss-text-decoration-shorthand@npm:^4.0.1":
+  version: 4.0.1
+  resolution: "@csstools/postcss-text-decoration-shorthand@npm:4.0.1"
   dependencies:
-    "@csstools/css-calc": "npm:^2.1.2"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
-  peerDependencies:
-    postcss: ^8.4
-  checksum: 10c0/15a1c434c3059ab884634d32374d53265c0ea5b5d1f6cb979dcfef18903edbafbf334fcbabd5b24869356db93792adfe95d88efef998b7d6b4c6f4b8393faca1
-  languageName: node
-  linkType: hard
-
-"@csstools/postcss-stepped-value-functions@npm:^4.0.7":
-  version: 4.0.7
-  resolution: "@csstools/postcss-stepped-value-functions@npm:4.0.7"
-  dependencies:
-    "@csstools/css-calc": "npm:^2.1.2"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
-  peerDependencies:
-    postcss: ^8.4
-  checksum: 10c0/1e664f0b169abe0e8ad832844ff06b219702ba7e6af795801109bd2e90403295d5cdb2e27c17f92e60d9704b30726b4564da79e0bf66dec852d50704a8813053
-  languageName: node
-  linkType: hard
-
-"@csstools/postcss-text-decoration-shorthand@npm:^4.0.2":
-  version: 4.0.2
-  resolution: "@csstools/postcss-text-decoration-shorthand@npm:4.0.2"
-  dependencies:
-    "@csstools/color-helpers": "npm:^5.0.2"
+    "@csstools/color-helpers": "npm:^5.0.1"
     postcss-value-parser: "npm:^4.2.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/01e2f3717e7a42224dc1a746491c55a381cf208cb7588f0308eeefe730675be4c7bb56c0cc557e75999c981e67da7d0b0bb68610635752c89ef251ee435b9cac
+  checksum: 10c0/81950e248d6019c0066353895e0fa2a5c684b754c9af349218cb919534f5ebf79e5e9c7a10b3af1e9c56de2f246968de3b87a00d8c4102e5f88e0f05c04f9889
   languageName: node
   linkType: hard
 
-"@csstools/postcss-trigonometric-functions@npm:^4.0.7":
-  version: 4.0.7
-  resolution: "@csstools/postcss-trigonometric-functions@npm:4.0.7"
+"@csstools/postcss-trigonometric-functions@npm:^4.0.1":
+  version: 4.0.1
+  resolution: "@csstools/postcss-trigonometric-functions@npm:4.0.1"
   dependencies:
-    "@csstools/css-calc": "npm:^2.1.2"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-calc": "npm:^2.0.1"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/2b01608a9f7dba6f73febfdd75269f6f88eb2a653de38a0adc6e81de57de4248bedd39b3e8b219cc49ce73b99118e285a870711953a553ddddb0bd5b2f9a5852
+  checksum: 10c0/59e017ebb9f4f8f027e134024e3322b5e202cc96073e0bb0d45733a829c8eadc7f4f7ce57ce8360a748a677595af9ea95da1779684699b48b911b73b4017ac8b
   languageName: node
   linkType: hard
 
@@ -1962,30 +1976,21 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@csstools/selector-resolve-nested@npm:^3.0.0":
-  version: 3.0.0
-  resolution: "@csstools/selector-resolve-nested@npm:3.0.0"
+"@csstools/selector-resolve-nested@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "@csstools/selector-resolve-nested@npm:2.0.0"
   peerDependencies:
-    postcss-selector-parser: ^7.0.0
-  checksum: 10c0/2b01c36b3fa81388d5bddd8db962766465d76b021a815c8bb5a48c3a42c530154cc155fc496707ade627dbba6745eb8ecd9fa840c1972133c0f7d8811e0a959d
+    postcss-selector-parser: ^6.1.0
+  checksum: 10c0/10516fd1db5e0a3963063caa57d24eeb1d1f69fcb63f0b5aec5d7a44f8b36ff07b1eded3012e8a9b92fc1f484e1a5a9def0cf57d788aa7f944ee79877837cc77
   languageName: node
   linkType: hard
 
-"@csstools/selector-specificity@npm:^5.0.0":
-  version: 5.0.0
-  resolution: "@csstools/selector-specificity@npm:5.0.0"
+"@csstools/selector-specificity@npm:^4.0.0":
+  version: 4.0.0
+  resolution: "@csstools/selector-specificity@npm:4.0.0"
   peerDependencies:
-    postcss-selector-parser: ^7.0.0
-  checksum: 10c0/186b444cabcdcdeb553bfe021f80c58bfe9ef38dcc444f2b1f34a5aab9be063ab4e753022b2d5792049c041c28cfbb78e4b707ec398459300e402030d35c07eb
-  languageName: node
-  linkType: hard
-
-"@csstools/stylelint-formatter-github@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "@csstools/stylelint-formatter-github@npm:1.0.0"
-  peerDependencies:
-    stylelint: ^16.6.0
-  checksum: 10c0/2052c4e4d89656b2b4176a6d07508ef73278d33c24a7408a3555d07f26ec853f85da95525590c51751fb3150a2ebb5e3083d8200dc6597af2cd8e93198695269
+    postcss-selector-parser: ^6.1.0
+  checksum: 10c0/6f4d4ecfdcd37f950100de8ffe0b4c1b1cc8c004aab2c2ebaa5c3e2bca2412d15b17d4628435f47a62d2c56db41bcbf985cb9c69e74b89964d48e421e93e75ba
   languageName: node
   linkType: hard
 
@@ -2005,41 +2010,41 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@dnd-kit/accessibility@npm:^3.1.1":
-  version: 3.1.1
-  resolution: "@dnd-kit/accessibility@npm:3.1.1"
+"@dnd-kit/accessibility@npm:^3.1.0":
+  version: 3.1.0
+  resolution: "@dnd-kit/accessibility@npm:3.1.0"
   dependencies:
     tslib: "npm:^2.0.0"
   peerDependencies:
     react: ">=16.8.0"
-  checksum: 10c0/be0bf41716dc58f9386bc36906ec1ce72b7b42b6d1d0e631d347afe9bd8714a829bd6f58a346dd089b1519e93918ae2f94497411a61a4f5e4d9247c6cfd1fef8
+  checksum: 10c0/4f9d24e801d66d4fbb551ec389ed90424dd4c5bbdf527000a618e9abb9833cbd84d9a79e362f470ccbccfbd6d00217a9212c92f3cef66e01c951c7f79625b9d7
   languageName: node
   linkType: hard
 
 "@dnd-kit/core@npm:^6.1.0":
-  version: 6.3.1
-  resolution: "@dnd-kit/core@npm:6.3.1"
+  version: 6.1.0
+  resolution: "@dnd-kit/core@npm:6.1.0"
   dependencies:
-    "@dnd-kit/accessibility": "npm:^3.1.1"
+    "@dnd-kit/accessibility": "npm:^3.1.0"
     "@dnd-kit/utilities": "npm:^3.2.2"
     tslib: "npm:^2.0.0"
   peerDependencies:
     react: ">=16.8.0"
     react-dom: ">=16.8.0"
-  checksum: 10c0/196db95d81096d9dc248983533eab91ba83591770fa5c894b1ac776f42af0d99522b3fd5bb3923411470e4733fcfa103e6ee17adc17b9b7eb54c7fbec5ff7c52
+  checksum: 10c0/c793eb97cb59285ca8937ebcdfcd27cff09d750ae06722e36ca5ed07925e41abc36a38cff98f9f6056f7a07810878d76909826142a2968330e7e22060e6be584
   languageName: node
   linkType: hard
 
-"@dnd-kit/sortable@npm:^10.0.0":
-  version: 10.0.0
-  resolution: "@dnd-kit/sortable@npm:10.0.0"
+"@dnd-kit/sortable@npm:^8.0.0":
+  version: 8.0.0
+  resolution: "@dnd-kit/sortable@npm:8.0.0"
   dependencies:
     "@dnd-kit/utilities": "npm:^3.2.2"
     tslib: "npm:^2.0.0"
   peerDependencies:
-    "@dnd-kit/core": ^6.3.0
+    "@dnd-kit/core": ^6.1.0
     react: ">=16.8.0"
-  checksum: 10c0/37ee48bc6789fb512dc0e4c374a96d19abe5b2b76dc34856a5883aaa96c3297891b94cc77bbc409e074dcce70967ebcb9feb40cd9abadb8716fc280b4c7f99af
+  checksum: 10c0/a6066c652b892c6a11320c7d8f5c18fdf723e721e8eea37f4ab657dee1ac5e7ca710ac32ce0712a57fe968bc07c13bcea5d5599d90dfdd95619e162befd4d2fb
   languageName: node
   linkType: hard
 
@@ -2061,41 +2066,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@emnapi/core@npm:^1.3.1":
-  version: 1.3.1
-  resolution: "@emnapi/core@npm:1.3.1"
-  dependencies:
-    "@emnapi/wasi-threads": "npm:1.0.1"
-    tslib: "npm:^2.4.0"
-  checksum: 10c0/d3be1044ad704e2c486641bc18908523490f28c7d38bd12d9c1d4ce37d39dae6c4aecd2f2eaf44c6e3bd90eaf04e0591acc440b1b038cdf43cce078a355a0ea0
-  languageName: node
-  linkType: hard
-
-"@emnapi/runtime@npm:^1.3.1":
-  version: 1.3.1
-  resolution: "@emnapi/runtime@npm:1.3.1"
-  dependencies:
-    tslib: "npm:^2.4.0"
-  checksum: 10c0/060ffede50f1b619c15083312b80a9e62a5b0c87aa8c1b54854c49766c9d69f8d1d3d87bd963a647071263a320db41b25eaa50b74d6a80dcc763c23dbeaafd6c
-  languageName: node
-  linkType: hard
-
-"@emnapi/wasi-threads@npm:1.0.1":
-  version: 1.0.1
-  resolution: "@emnapi/wasi-threads@npm:1.0.1"
-  dependencies:
-    tslib: "npm:^2.4.0"
-  checksum: 10c0/1e0c8036b8d53e9b07cc9acf021705ef6c86ab6b13e1acda7fffaf541a2d3565072afb92597419173ced9ea14f6bf32fce149106e669b5902b825e8b499e5c6c
-  languageName: node
-  linkType: hard
-
-"@emoji-mart/data@npm:1.2.1":
-  version: 1.2.1
-  resolution: "@emoji-mart/data@npm:1.2.1"
-  checksum: 10c0/6784b97bf49a0d3ff110d8447bbd3b0449fcbc497294be3d1c3a6cb1609308776895c7520200be604cbecaa5e172c76927e47f34419c72ba8a76fd4e5a53674b
-  languageName: node
-  linkType: hard
-
 "@emotion/babel-plugin@npm:^11.11.0":
   version: 11.11.0
   resolution: "@emotion/babel-plugin@npm:11.11.0"
@@ -2213,14 +2183,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@es-joy/jsdoccomment@npm:~0.49.0":
-  version: 0.49.0
-  resolution: "@es-joy/jsdoccomment@npm:0.49.0"
+"@es-joy/jsdoccomment@npm:~0.48.0":
+  version: 0.48.0
+  resolution: "@es-joy/jsdoccomment@npm:0.48.0"
   dependencies:
     comment-parser: "npm:1.4.1"
     esquery: "npm:^1.6.0"
     jsdoc-type-pratt-parser: "npm:~4.1.0"
-  checksum: 10c0/16717507d557d37e7b59456fedeefbe0a3bc93aa2d9c043d5db91e24e076509b6fcb10ee6fd1dafcb0c5bbe50ae329b45de5b83541cb5994a98c9e862a45641e
+  checksum: 10c0/8d87c7c0426fade009c30ab429d4ede53fd253d40b55079c02bdacdaa4c0fe904aaea5e3084cd98052f2bed6b3030c381d84f4a3251b343a71fee6f681a08bee
   languageName: node
   linkType: hard
 
@@ -2235,78 +2205,34 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1":
-  version: 4.12.1
-  resolution: "@eslint-community/regexpp@npm:4.12.1"
-  checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6
+"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.6.1":
+  version: 4.10.0
+  resolution: "@eslint-community/regexpp@npm:4.10.0"
+  checksum: 10c0/c5f60ef1f1ea7649fa7af0e80a5a79f64b55a8a8fa5086de4727eb4c86c652aedee407a9c143b8995d2c0b2d75c1222bec9ba5d73dbfc1f314550554f0979ef4
   languageName: node
   linkType: hard
 
-"@eslint/config-array@npm:^0.19.2":
-  version: 0.19.2
-  resolution: "@eslint/config-array@npm:0.19.2"
-  dependencies:
-    "@eslint/object-schema": "npm:^2.1.6"
-    debug: "npm:^4.3.1"
-    minimatch: "npm:^3.1.2"
-  checksum: 10c0/dd68da9abb32d336233ac4fe0db1e15a0a8d794b6e69abb9e57545d746a97f6f542496ff9db0d7e27fab1438546250d810d90b1904ac67677215b8d8e7573f3d
-  languageName: node
-  linkType: hard
-
-"@eslint/config-helpers@npm:^0.2.0":
-  version: 0.2.0
-  resolution: "@eslint/config-helpers@npm:0.2.0"
-  checksum: 10c0/743a64653e13177029108f57ab47460ded08e3412c86216a14b7e8ab2dc79c2b64be45bf55c5ef29f83692a707dc34cf1e9217e4b8b4b272a0d9b691fdaf6a2a
-  languageName: node
-  linkType: hard
-
-"@eslint/core@npm:^0.12.0":
-  version: 0.12.0
-  resolution: "@eslint/core@npm:0.12.0"
-  dependencies:
-    "@types/json-schema": "npm:^7.0.15"
-  checksum: 10c0/d032af81195bb28dd800c2b9617548c6c2a09b9490da3c5537fd2a1201501666d06492278bb92cfccac1f7ac249e58601dd87f813ec0d6a423ef0880434fa0c3
-  languageName: node
-  linkType: hard
-
-"@eslint/eslintrc@npm:^3.3.1":
-  version: 3.3.1
-  resolution: "@eslint/eslintrc@npm:3.3.1"
+"@eslint/eslintrc@npm:^2.1.4":
+  version: 2.1.4
+  resolution: "@eslint/eslintrc@npm:2.1.4"
   dependencies:
     ajv: "npm:^6.12.4"
     debug: "npm:^4.3.2"
-    espree: "npm:^10.0.1"
-    globals: "npm:^14.0.0"
+    espree: "npm:^9.6.0"
+    globals: "npm:^13.19.0"
     ignore: "npm:^5.2.0"
     import-fresh: "npm:^3.2.1"
     js-yaml: "npm:^4.1.0"
     minimatch: "npm:^3.1.2"
     strip-json-comments: "npm:^3.1.1"
-  checksum: 10c0/b0e63f3bc5cce4555f791a4e487bf999173fcf27c65e1ab6e7d63634d8a43b33c3693e79f192cbff486d7df1be8ebb2bd2edc6e70ddd486cbfa84a359a3e3b41
+  checksum: 10c0/32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573
   languageName: node
   linkType: hard
 
-"@eslint/js@npm:9.23.0, @eslint/js@npm:^9.23.0":
-  version: 9.23.0
-  resolution: "@eslint/js@npm:9.23.0"
-  checksum: 10c0/4e70869372b6325389e0ab51cac6d3062689807d1cef2c3434857571422ce11dde3c62777af85c382b9f94d937127598d605d2086787f08611351bf99faded81
-  languageName: node
-  linkType: hard
-
-"@eslint/object-schema@npm:^2.1.6":
-  version: 2.1.6
-  resolution: "@eslint/object-schema@npm:2.1.6"
-  checksum: 10c0/b8cdb7edea5bc5f6a96173f8d768d3554a628327af536da2fc6967a93b040f2557114d98dbcdbf389d5a7b290985ad6a9ce5babc547f36fc1fde42e674d11a56
-  languageName: node
-  linkType: hard
-
-"@eslint/plugin-kit@npm:^0.2.7":
-  version: 0.2.7
-  resolution: "@eslint/plugin-kit@npm:0.2.7"
-  dependencies:
-    "@eslint/core": "npm:^0.12.0"
-    levn: "npm:^0.4.1"
-  checksum: 10c0/0a1aff1ad63e72aca923217e556c6dfd67d7cd121870eb7686355d7d1475d569773528a8b2111b9176f3d91d2ea81f7413c34600e8e5b73d59e005d70780b633
+"@eslint/js@npm:8.57.0":
+  version: 8.57.0
+  resolution: "@eslint/js@npm:8.57.0"
+  checksum: 10c0/9a518bb8625ba3350613903a6d8c622352ab0c6557a59fe6ff6178bf882bf57123f9d92aa826ee8ac3ee74b9c6203fe630e9ee00efb03d753962dcf65ee4bd94
   languageName: node
   linkType: hard
 
@@ -2337,15 +2263,15 @@ __metadata:
   linkType: hard
 
 "@formatjs/cli@npm:^6.1.1":
-  version: 6.3.14
-  resolution: "@formatjs/cli@npm:6.3.14"
+  version: 6.2.12
+  resolution: "@formatjs/cli@npm:6.2.12"
   peerDependencies:
     "@glimmer/env": ^0.1.7
-    "@glimmer/reference": ^0.91.1 || ^0.92.0 || ^0.93.0
-    "@glimmer/syntax": ^0.92.0 || ^0.93.0
-    "@glimmer/validator": ^0.92.0 || ^0.93.0
+    "@glimmer/reference": ^0.91.1 || ^0.92.0
+    "@glimmer/syntax": ^0.92.0
+    "@glimmer/validator": ^0.92.0
     "@vue/compiler-core": ^3.4.0
-    content-tag: ^2.0.1 || ^3.0.0
+    content-tag: ^2.0.1
     ember-template-recast: ^6.1.4
     vue: ^3.4.0
   peerDependenciesMeta:
@@ -2367,108 +2293,129 @@ __metadata:
       optional: true
   bin:
     formatjs: bin/formatjs
-  checksum: 10c0/b4c83ed7fdc8dcd48b2f48fa9cca65b52472fb096eb028517a872f8a71ed3964f4b0a6bbc607f821a9504f396fe7341ef4d9ad44a381a37f280ed7547de66f41
+  checksum: 10c0/3bd05a9fad6c837e22988e6638f426c128efa46ab80ff88cf2ad81fb3bc10cf4f228907577fc01e24c2d7d505cfabfaa69f0496d2ec8f0ab2d6b5eaccb5e475c
   languageName: node
   linkType: hard
 
-"@formatjs/ecma402-abstract@npm:2.3.4":
-  version: 2.3.4
-  resolution: "@formatjs/ecma402-abstract@npm:2.3.4"
+"@formatjs/ecma402-abstract@npm:2.0.0":
+  version: 2.0.0
+  resolution: "@formatjs/ecma402-abstract@npm:2.0.0"
   dependencies:
-    "@formatjs/fast-memoize": "npm:2.2.7"
-    "@formatjs/intl-localematcher": "npm:0.6.1"
-    decimal.js: "npm:^10.4.3"
-    tslib: "npm:^2.8.0"
-  checksum: 10c0/2644bc618a34dc610ef9691281eeb45ae6175e6982cf19f1bd140672fc95c748747ce3c85b934649ea7e4a304f7ae0060625fd53d5df76f92ca3acf743e1eb0a
+    "@formatjs/intl-localematcher": "npm:0.5.4"
+    tslib: "npm:^2.4.0"
+  checksum: 10c0/94cba291aeadffa3ca416087c2c2352c8a741bb4dcb7f47f15c247b1f043ffcef1af5b20a1b7578fbba9e704fc5f1c079923f3537a273d50162be62f8037625c
   languageName: node
   linkType: hard
 
-"@formatjs/fast-memoize@npm:2.2.7":
-  version: 2.2.7
-  resolution: "@formatjs/fast-memoize@npm:2.2.7"
+"@formatjs/fast-memoize@npm:2.2.0":
+  version: 2.2.0
+  resolution: "@formatjs/fast-memoize@npm:2.2.0"
   dependencies:
-    tslib: "npm:^2.8.0"
-  checksum: 10c0/f5eabb0e4ab7162297df8252b4cfde194b23248120d9df267592eae2be2d2f7c4f670b5a70523d91b4ecdc35d40e65823bb8eeba8dd79fbf8601a972bf3b8866
+    tslib: "npm:^2.4.0"
+  checksum: 10c0/ae88c5a93b96235aba4bd9b947d0310d2ec013687a99133413361b24122b5cdea8c9bf2e04a4a2a8b61f1f4ee5419ef6416ca4796554226b5050e05a9ce6ef49
   languageName: node
   linkType: hard
 
-"@formatjs/icu-messageformat-parser@npm:2.11.2":
-  version: 2.11.2
-  resolution: "@formatjs/icu-messageformat-parser@npm:2.11.2"
+"@formatjs/icu-messageformat-parser@npm:2.7.8":
+  version: 2.7.8
+  resolution: "@formatjs/icu-messageformat-parser@npm:2.7.8"
   dependencies:
-    "@formatjs/ecma402-abstract": "npm:2.3.4"
-    "@formatjs/icu-skeleton-parser": "npm:1.8.14"
-    tslib: "npm:^2.8.0"
-  checksum: 10c0/a121f2d2c6b36a1632ffd64c3545e2500c8ee0f7fee5db090318c035d635c430ab123faedb5d000f18d9423a7b55fbf670b84e2e2dd72cc307a38aed61d3b2e0
+    "@formatjs/ecma402-abstract": "npm:2.0.0"
+    "@formatjs/icu-skeleton-parser": "npm:1.8.2"
+    tslib: "npm:^2.4.0"
+  checksum: 10c0/a3b759a825fb22ffd7b906f6a07b1a079bbc34f72c745de2c2514e439c4bb75bc1a9442eba1bac7ff3ea3010e12076374cd755ad12116b1d066cc90da5fbcbc9
   languageName: node
   linkType: hard
 
-"@formatjs/icu-skeleton-parser@npm:1.8.14":
-  version: 1.8.14
-  resolution: "@formatjs/icu-skeleton-parser@npm:1.8.14"
+"@formatjs/icu-skeleton-parser@npm:1.8.2":
+  version: 1.8.2
+  resolution: "@formatjs/icu-skeleton-parser@npm:1.8.2"
   dependencies:
-    "@formatjs/ecma402-abstract": "npm:2.3.4"
-    tslib: "npm:^2.8.0"
-  checksum: 10c0/a1807ed6e90b8a2e8d0e5b5125e6f9a2c057d3cff377fb031d2333af7cfaa6de4ed3a15c23da7294d4c3557f8b28b2163246434a19720f26b5db0497d97e9b58
+    "@formatjs/ecma402-abstract": "npm:2.0.0"
+    tslib: "npm:^2.4.0"
+  checksum: 10c0/9b15013acc47b8d560b52942e3dab2abaaa9c5a4410bbd1d490a4b22bf5ca36fdd88b71f241d05479bddf856d0d1d57b7ecc9e79738497ac518616aa6d4d0015
   languageName: node
   linkType: hard
 
-"@formatjs/intl-localematcher@npm:0.6.1":
-  version: 0.6.1
-  resolution: "@formatjs/intl-localematcher@npm:0.6.1"
+"@formatjs/intl-displaynames@npm:6.6.8":
+  version: 6.6.8
+  resolution: "@formatjs/intl-displaynames@npm:6.6.8"
   dependencies:
-    tslib: "npm:^2.8.0"
-  checksum: 10c0/bacbedd508519c1bb5ca2620e89dc38f12101be59439aa14aa472b222915b462cb7d679726640f6dcf52a05dd218b5aa27ccd60f2e5010bb96f1d4929848cde0
+    "@formatjs/ecma402-abstract": "npm:2.0.0"
+    "@formatjs/intl-localematcher": "npm:0.5.4"
+    tslib: "npm:^2.4.0"
+  checksum: 10c0/1a03e7644022741c1bcf10fcd07da88c434416a13603ace693a038114010463307b4130d3a3f53ad5665bd27fca9a6b19ac8e5bf58e17598b1ea84db173fdfbb
   languageName: node
   linkType: hard
 
-"@formatjs/intl-pluralrules@npm:^5.4.4":
-  version: 5.4.4
-  resolution: "@formatjs/intl-pluralrules@npm:5.4.4"
+"@formatjs/intl-listformat@npm:7.5.7":
+  version: 7.5.7
+  resolution: "@formatjs/intl-listformat@npm:7.5.7"
   dependencies:
-    "@formatjs/ecma402-abstract": "npm:2.3.4"
-    "@formatjs/intl-localematcher": "npm:0.6.1"
-    decimal.js: "npm:^10.4.3"
-    tslib: "npm:^2.8.0"
-  checksum: 10c0/8a8ec9f2fad40d9fa654a68de06fb18aaa6f0eafa908f41397f057366740625c12da627c6de68e0396fcd67ceaaa2c5c20a4b102f71ac8694abd9e76cceca949
+    "@formatjs/ecma402-abstract": "npm:2.0.0"
+    "@formatjs/intl-localematcher": "npm:0.5.4"
+    tslib: "npm:^2.4.0"
+  checksum: 10c0/5d0478752d669d87c82aa80880df464d64a1c8974fcb6136bc854567f570a1696e5468005ffa266cfcb623adb7c7299b839c06ea33897f55d35dab6a7575cc84
   languageName: node
   linkType: hard
 
-"@formatjs/intl@npm:3.1.6":
-  version: 3.1.6
-  resolution: "@formatjs/intl@npm:3.1.6"
+"@formatjs/intl-localematcher@npm:0.5.4":
+  version: 0.5.4
+  resolution: "@formatjs/intl-localematcher@npm:0.5.4"
   dependencies:
-    "@formatjs/ecma402-abstract": "npm:2.3.4"
-    "@formatjs/fast-memoize": "npm:2.2.7"
-    "@formatjs/icu-messageformat-parser": "npm:2.11.2"
-    intl-messageformat: "npm:10.7.16"
-    tslib: "npm:^2.8.0"
+    tslib: "npm:^2.4.0"
+  checksum: 10c0/c9ff5d34ca8b6fe59f8f303a3cc31a92d343e095a6987e273e5cc23f0fe99feb557a392a05da95931c7d24106acb6988e588d00ddd05b0934005aafd7fdbafe6
+  languageName: node
+  linkType: hard
+
+"@formatjs/intl-pluralrules@npm:^5.2.2":
+  version: 5.2.14
+  resolution: "@formatjs/intl-pluralrules@npm:5.2.14"
+  dependencies:
+    "@formatjs/ecma402-abstract": "npm:2.0.0"
+    "@formatjs/intl-localematcher": "npm:0.5.4"
+    tslib: "npm:^2.4.0"
+  checksum: 10c0/3c00109c8d4c8b221c2b9af38a38d31cd6293a0a412a1f2cdae2b8ef81bd71626c9ff4a647389682cb27ae5c223bd6f64ef54d03e3f6f19c372e0c6194b76b38
+  languageName: node
+  linkType: hard
+
+"@formatjs/intl@npm:2.10.5":
+  version: 2.10.5
+  resolution: "@formatjs/intl@npm:2.10.5"
+  dependencies:
+    "@formatjs/ecma402-abstract": "npm:2.0.0"
+    "@formatjs/fast-memoize": "npm:2.2.0"
+    "@formatjs/icu-messageformat-parser": "npm:2.7.8"
+    "@formatjs/intl-displaynames": "npm:6.6.8"
+    "@formatjs/intl-listformat": "npm:7.5.7"
+    intl-messageformat: "npm:10.5.14"
+    tslib: "npm:^2.4.0"
   peerDependencies:
-    typescript: ^5.6.0
+    typescript: ^4.7 || 5
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: 10c0/a31f8d2569c9f2384f67a76f1cc2c8bfc2721c97a7dee0e971b6cfc0f223449bab0cfdc29140e3b71d74b04573c20ee8600909d256293e296a809da69a141530
+  checksum: 10c0/d00ef00e41200947ed22895b73a0863283de4762ec238b4a81e2252e642e30a309cd9c73174e4917b6c675ab6f148eda5a4e3345c3caeef64e090fc8374d27c4
   languageName: node
   linkType: hard
 
-"@formatjs/ts-transformer@npm:3.13.34":
-  version: 3.13.34
-  resolution: "@formatjs/ts-transformer@npm:3.13.34"
+"@formatjs/ts-transformer@npm:3.13.14":
+  version: 3.13.14
+  resolution: "@formatjs/ts-transformer@npm:3.13.14"
   dependencies:
-    "@formatjs/icu-messageformat-parser": "npm:2.11.2"
-    "@types/json-stable-stringify": "npm:^1.1.0"
-    "@types/node": "npm:^22.0.0"
-    chalk: "npm:^4.1.2"
-    json-stable-stringify: "npm:^1.1.1"
-    tslib: "npm:^2.8.0"
-    typescript: "npm:^5.6.0"
+    "@formatjs/icu-messageformat-parser": "npm:2.7.8"
+    "@types/json-stable-stringify": "npm:^1.0.32"
+    "@types/node": "npm:14 || 16 || 17"
+    chalk: "npm:^4.0.0"
+    json-stable-stringify: "npm:^1.0.1"
+    tslib: "npm:^2.4.0"
+    typescript: "npm:5"
   peerDependencies:
-    ts-jest: ^29
+    ts-jest: ">=27"
   peerDependenciesMeta:
     ts-jest:
       optional: true
-  checksum: 10c0/2e53af5a53cab71be0ba2fc16ba856c95bf336c063cc835486cd3a68d01013c5c08026b34667f4bdf99422e74faa8eb1f26d5fe8006f3a1ae9c77e065599362e
+  checksum: 10c0/38450cfce3ec5132f3548c1e9ab098909ca8d2db2b8b6b4b5bb87aa59a4ca1a19bbf6d339ace39bcc931fa80d9946b4c7cf039c9574069b317ae015cd6963bd3
   languageName: node
   linkType: hard
 
@@ -2513,20 +2460,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@humanfs/core@npm:^0.19.1":
-  version: 0.19.1
-  resolution: "@humanfs/core@npm:0.19.1"
-  checksum: 10c0/aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67
-  languageName: node
-  linkType: hard
-
-"@humanfs/node@npm:^0.16.6":
-  version: 0.16.6
-  resolution: "@humanfs/node@npm:0.16.6"
+"@humanwhocodes/config-array@npm:^0.11.14":
+  version: 0.11.14
+  resolution: "@humanwhocodes/config-array@npm:0.11.14"
   dependencies:
-    "@humanfs/core": "npm:^0.19.1"
-    "@humanwhocodes/retry": "npm:^0.3.0"
-  checksum: 10c0/8356359c9f60108ec204cbd249ecd0356667359b2524886b357617c4a7c3b6aace0fd5a369f63747b926a762a88f8a25bc066fa1778508d110195ce7686243e1
+    "@humanwhocodes/object-schema": "npm:^2.0.2"
+    debug: "npm:^4.3.1"
+    minimatch: "npm:^3.0.5"
+  checksum: 10c0/66f725b4ee5fdd8322c737cb5013e19fac72d4d69c8bf4b7feb192fcb83442b035b92186f8e9497c220e58b2d51a080f28a73f7899bc1ab288c3be172c467541
   languageName: node
   linkType: hard
 
@@ -2537,17 +2478,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@humanwhocodes/retry@npm:^0.3.0":
-  version: 0.3.1
-  resolution: "@humanwhocodes/retry@npm:0.3.1"
-  checksum: 10c0/f0da1282dfb45e8120480b9e2e275e2ac9bbe1cf016d046fdad8e27cc1285c45bb9e711681237944445157b430093412b4446c1ab3fc4bb037861b5904101d3b
-  languageName: node
-  linkType: hard
-
-"@humanwhocodes/retry@npm:^0.4.2":
-  version: 0.4.2
-  resolution: "@humanwhocodes/retry@npm:0.4.2"
-  checksum: 10c0/0235525d38f243bee3bf8b25ed395fbf957fb51c08adae52787e1325673071abe856c7e18e530922ed2dd3ce12ed82ba01b8cee0279ac52a3315fcdc3a69ef0c
+"@humanwhocodes/object-schema@npm:^2.0.2":
+  version: 2.0.2
+  resolution: "@humanwhocodes/object-schema@npm:2.0.2"
+  checksum: 10c0/6fd83dc320231d71c4541d0244051df61f301817e9f9da9fd4cb7e44ec8aacbde5958c1665b0c419401ab935114fdf532a6ad5d4e7294b1af2f347dd91a6983f
   languageName: node
   linkType: hard
 
@@ -2886,28 +2820,23 @@ __metadata:
     "@babel/preset-react": "npm:^7.22.3"
     "@babel/preset-typescript": "npm:^7.21.5"
     "@babel/runtime": "npm:^7.23.7"
-    "@csstools/stylelint-formatter-github": "npm:^1.0.0"
     "@dnd-kit/core": "npm:^6.1.0"
-    "@dnd-kit/sortable": "npm:^10.0.0"
+    "@dnd-kit/sortable": "npm:^8.0.0"
     "@dnd-kit/utilities": "npm:^3.2.2"
-    "@emoji-mart/data": "npm:1.2.1"
-    "@eslint/js": "npm:^9.23.0"
     "@formatjs/cli": "npm:^6.1.1"
-    "@formatjs/intl-pluralrules": "npm:^5.4.4"
+    "@formatjs/intl-pluralrules": "npm:^5.2.2"
     "@gamestdio/websocket": "npm:^0.3.2"
     "@github/webauthn-json": "npm:^2.1.1"
     "@hello-pangea/dnd": "npm:^17.0.0"
-    "@rails/ujs": "npm:7.1.501"
-    "@react-spring/web": "npm:^9.7.5"
+    "@rails/ujs": "npm:7.1.401"
     "@reduxjs/toolkit": "npm:^2.0.1"
     "@svgr/webpack": "npm:^5.5.0"
     "@testing-library/dom": "npm:^10.2.0"
     "@testing-library/jest-dom": "npm:^6.0.0"
     "@testing-library/react": "npm:^16.0.0"
     "@types/babel__core": "npm:^7.20.1"
-    "@types/emoji-mart": "npm:3.0.14"
+    "@types/emoji-mart": "npm:^3.0.9"
     "@types/escape-html": "npm:^1.0.2"
-    "@types/eslint-plugin-jsx-a11y": "npm:^6"
     "@types/hoist-non-react-statics": "npm:^3.3.1"
     "@types/http-link-header": "npm:^1.0.3"
     "@types/intl": "npm:^1.2.0"
@@ -2922,6 +2851,7 @@ __metadata:
     "@types/react-dom": "npm:^18.2.4"
     "@types/react-helmet": "npm:^6.1.6"
     "@types/react-immutable-proptypes": "npm:^2.1.0"
+    "@types/react-motion": "npm:^0.0.40"
     "@types/react-router": "npm:^5.1.20"
     "@types/react-router-dom": "npm:^5.3.3"
     "@types/react-sparklines": "npm:^1.7.2"
@@ -2932,13 +2862,14 @@ __metadata:
     "@types/requestidlecallback": "npm:^0.3.5"
     "@types/webpack": "npm:^4.41.33"
     "@types/webpack-env": "npm:^1.18.4"
-    "@use-gesture/react": "npm:^10.3.1"
+    "@typescript-eslint/eslint-plugin": "npm:^8.0.0"
+    "@typescript-eslint/parser": "npm:^8.0.0"
     arrow-key-navigation: "npm:^1.2.0"
     async-mutex: "npm:^0.5.0"
     axios: "npm:^1.4.0"
     babel-jest: "npm:^29.5.0"
     babel-loader: "npm:^8.3.0"
-    babel-plugin-formatjs: "npm:^10.5.37"
+    babel-plugin-formatjs: "npm:^10.5.1"
     babel-plugin-lodash: "patch:babel-plugin-lodash@npm%3A3.3.4#~/.yarn/patches/babel-plugin-lodash-npm-3.3.4-c7161075b6.patch"
     babel-plugin-preval: "npm:^5.1.0"
     babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24"
@@ -2955,30 +2886,29 @@ __metadata:
     detect-passive-events: "npm:^2.0.3"
     emoji-mart: "npm:emoji-mart-lazyload@latest"
     escape-html: "npm:^1.0.3"
-    eslint: "npm:^9.23.0"
-    eslint-import-resolver-typescript: "npm:^4.2.5"
-    eslint-plugin-formatjs: "npm:^5.3.1"
-    eslint-plugin-import: "npm:~2.31.0"
-    eslint-plugin-jsdoc: "npm:^50.6.9"
-    eslint-plugin-jsx-a11y: "npm:~6.10.2"
-    eslint-plugin-promise: "npm:~7.2.1"
-    eslint-plugin-react: "npm:^7.37.4"
-    eslint-plugin-react-hooks: "npm:^5.2.0"
+    eslint: "npm:^8.41.0"
+    eslint-define-config: "npm:^2.0.0"
+    eslint-import-resolver-typescript: "npm:^3.5.5"
+    eslint-plugin-formatjs: "npm:^4.10.1"
+    eslint-plugin-import: "npm:~2.30.0"
+    eslint-plugin-jsdoc: "npm:^50.0.0"
+    eslint-plugin-jsx-a11y: "npm:~6.10.0"
+    eslint-plugin-promise: "npm:~7.1.0"
+    eslint-plugin-react: "npm:^7.33.2"
+    eslint-plugin-react-hooks: "npm:^4.6.0"
     file-loader: "npm:^6.2.0"
     fuzzysort: "npm:^3.0.0"
     glob: "npm:^10.2.6"
-    globals: "npm:^16.0.0"
     history: "npm:^4.10.1"
     hoist-non-react-statics: "npm:^3.3.2"
     http-link-header: "npm:^1.1.1"
     husky: "npm:^9.0.11"
     immutable: "npm:^4.3.0"
     imports-loader: "npm:^1.2.0"
-    intl-messageformat: "npm:^10.7.16"
+    intl-messageformat: "npm:^10.3.5"
     jest: "npm:^29.5.0"
     jest-environment-jsdom: "npm:^29.5.0"
     js-yaml: "npm:^4.1.0"
-    lande: "npm:^1.0.10"
     lint-staged: "npm:^15.0.0"
     lodash: "npm:^4.17.21"
     mark-loader: "npm:^0.1.6"
@@ -2997,7 +2927,9 @@ __metadata:
     react-hotkeys: "npm:^1.1.4"
     react-immutable-proptypes: "npm:^2.2.0"
     react-immutable-pure-component: "npm:^2.2.2"
-    react-intl: "npm:^7.1.10"
+    react-intl: "npm:^6.4.2"
+    react-motion: "npm:^0.5.2"
+    react-notification: "npm:^6.8.5"
     react-overlays: "npm:^5.2.1"
     react-redux: "npm:^9.0.4"
     react-redux-loading-bar: "npm:^5.0.8"
@@ -3017,16 +2949,15 @@ __metadata:
     sass-loader: "npm:^10.2.0"
     stacktrace-js: "npm:^2.0.2"
     stringz: "npm:^2.1.0"
-    stylelint: "npm:^16.11.0"
+    stylelint: "npm:^16.0.2"
     stylelint-config-prettier-scss: "npm:^1.0.0"
-    stylelint-config-standard-scss: "npm:^14.0.0"
+    stylelint-config-standard-scss: "npm:^13.0.0"
     substring-trie: "npm:^1.0.2"
     terser-webpack-plugin: "npm:^4.2.3"
-    tesseract.js: "npm:^6.0.0"
+    tesseract.js: "npm:^2.1.5"
     tiny-queue: "npm:^0.2.1"
     twitter-text: "npm:3.1.0"
-    typescript: "npm:~5.7.3"
-    typescript-eslint: "npm:^8.28.0"
+    typescript: "npm:^5.0.4"
     use-debounce: "npm:^10.0.0"
     webpack: "npm:^4.47.0"
     webpack-assets-manifest: "npm:^4.0.6"
@@ -3055,7 +2986,6 @@ __metadata:
   version: 0.0.0-use.local
   resolution: "@mastodon/streaming@workspace:streaming"
   dependencies:
-    "@eslint/js": "npm:^9.23.0"
     "@types/cors": "npm:^2.8.16"
     "@types/express": "npm:^4.17.17"
     "@types/pg": "npm:^8.6.6"
@@ -3064,20 +2994,19 @@ __metadata:
     bufferutil: "npm:^4.0.7"
     cors: "npm:^2.8.5"
     dotenv: "npm:^16.0.3"
+    eslint-define-config: "npm:^2.0.0"
     express: "npm:^4.18.2"
-    globals: "npm:^16.0.0"
     ioredis: "npm:^5.3.2"
-    jsdom: "npm:^26.0.0"
+    jsdom: "npm:^25.0.0"
     pg: "npm:^8.5.0"
     pg-connection-string: "npm:^2.6.0"
     pino: "npm:^9.0.0"
     pino-http: "npm:^10.0.0"
-    pino-pretty: "npm:^13.0.0"
+    pino-pretty: "npm:^11.0.0"
     prom-client: "npm:^15.0.0"
-    typescript: "npm:~5.7.3"
-    typescript-eslint: "npm:^8.28.0"
+    typescript: "npm:^5.0.4"
     utf-8-validate: "npm:^6.0.3"
-    uuid: "npm:^11.0.0"
+    uuid: "npm:^10.0.0"
     ws: "npm:^8.12.1"
   dependenciesMeta:
     bufferutil:
@@ -3087,17 +3016,6 @@ __metadata:
   languageName: unknown
   linkType: soft
 
-"@napi-rs/wasm-runtime@npm:^0.2.7":
-  version: 0.2.7
-  resolution: "@napi-rs/wasm-runtime@npm:0.2.7"
-  dependencies:
-    "@emnapi/core": "npm:^1.3.1"
-    "@emnapi/runtime": "npm:^1.3.1"
-    "@tybys/wasm-util": "npm:^0.9.0"
-  checksum: 10c0/04a5edd79144bfa4e821a373fb6d4939f10c578c5f3633b5e67a57d0f5e36a593f595834d26654ea757bba7cd80b6c42d0d1405d6a8460c5d774e8cd5c9548a4
-  languageName: node
-  linkType: hard
-
 "@nodelib/fs.scandir@npm:2.1.5":
   version: 2.1.5
   resolution: "@nodelib/fs.scandir@npm:2.1.5"
@@ -3115,7 +3033,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@nodelib/fs.walk@npm:^1.2.3":
+"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8":
   version: 1.2.8
   resolution: "@nodelib/fs.walk@npm:1.2.8"
   dependencies:
@@ -3125,6 +3043,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@nolyfill/is-core-module@npm:1.0.39":
+  version: 1.0.39
+  resolution: "@nolyfill/is-core-module@npm:1.0.39"
+  checksum: 10c0/34ab85fdc2e0250879518841f74a30c276bca4f6c3e13526d2d1fe515e1adf6d46c25fcd5989d22ea056d76f7c39210945180b4859fc83b050e2da411aa86289
+  languageName: node
+  linkType: hard
+
 "@npmcli/agent@npm:^2.0.0":
   version: 2.2.0
   resolution: "@npmcli/agent@npm:2.2.0"
@@ -3174,150 +3099,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@parcel/watcher-android-arm64@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-android-arm64@npm:2.5.0"
-  conditions: os=android & cpu=arm64
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-darwin-arm64@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-darwin-arm64@npm:2.5.0"
-  conditions: os=darwin & cpu=arm64
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-darwin-x64@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-darwin-x64@npm:2.5.0"
-  conditions: os=darwin & cpu=x64
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-freebsd-x64@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-freebsd-x64@npm:2.5.0"
-  conditions: os=freebsd & cpu=x64
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-linux-arm-glibc@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-linux-arm-glibc@npm:2.5.0"
-  conditions: os=linux & cpu=arm & libc=glibc
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-linux-arm-musl@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-linux-arm-musl@npm:2.5.0"
-  conditions: os=linux & cpu=arm & libc=musl
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-linux-arm64-glibc@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-linux-arm64-glibc@npm:2.5.0"
-  conditions: os=linux & cpu=arm64 & libc=glibc
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-linux-arm64-musl@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-linux-arm64-musl@npm:2.5.0"
-  conditions: os=linux & cpu=arm64 & libc=musl
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-linux-x64-glibc@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-linux-x64-glibc@npm:2.5.0"
-  conditions: os=linux & cpu=x64 & libc=glibc
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-linux-x64-musl@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-linux-x64-musl@npm:2.5.0"
-  conditions: os=linux & cpu=x64 & libc=musl
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-win32-arm64@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-win32-arm64@npm:2.5.0"
-  conditions: os=win32 & cpu=arm64
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-win32-ia32@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-win32-ia32@npm:2.5.0"
-  conditions: os=win32 & cpu=ia32
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher-win32-x64@npm:2.5.0":
-  version: 2.5.0
-  resolution: "@parcel/watcher-win32-x64@npm:2.5.0"
-  conditions: os=win32 & cpu=x64
-  languageName: node
-  linkType: hard
-
-"@parcel/watcher@npm:^2.4.1":
-  version: 2.5.0
-  resolution: "@parcel/watcher@npm:2.5.0"
-  dependencies:
-    "@parcel/watcher-android-arm64": "npm:2.5.0"
-    "@parcel/watcher-darwin-arm64": "npm:2.5.0"
-    "@parcel/watcher-darwin-x64": "npm:2.5.0"
-    "@parcel/watcher-freebsd-x64": "npm:2.5.0"
-    "@parcel/watcher-linux-arm-glibc": "npm:2.5.0"
-    "@parcel/watcher-linux-arm-musl": "npm:2.5.0"
-    "@parcel/watcher-linux-arm64-glibc": "npm:2.5.0"
-    "@parcel/watcher-linux-arm64-musl": "npm:2.5.0"
-    "@parcel/watcher-linux-x64-glibc": "npm:2.5.0"
-    "@parcel/watcher-linux-x64-musl": "npm:2.5.0"
-    "@parcel/watcher-win32-arm64": "npm:2.5.0"
-    "@parcel/watcher-win32-ia32": "npm:2.5.0"
-    "@parcel/watcher-win32-x64": "npm:2.5.0"
-    detect-libc: "npm:^1.0.3"
-    is-glob: "npm:^4.0.3"
-    micromatch: "npm:^4.0.5"
-    node-addon-api: "npm:^7.0.0"
-    node-gyp: "npm:latest"
-  dependenciesMeta:
-    "@parcel/watcher-android-arm64":
-      optional: true
-    "@parcel/watcher-darwin-arm64":
-      optional: true
-    "@parcel/watcher-darwin-x64":
-      optional: true
-    "@parcel/watcher-freebsd-x64":
-      optional: true
-    "@parcel/watcher-linux-arm-glibc":
-      optional: true
-    "@parcel/watcher-linux-arm-musl":
-      optional: true
-    "@parcel/watcher-linux-arm64-glibc":
-      optional: true
-    "@parcel/watcher-linux-arm64-musl":
-      optional: true
-    "@parcel/watcher-linux-x64-glibc":
-      optional: true
-    "@parcel/watcher-linux-x64-musl":
-      optional: true
-    "@parcel/watcher-win32-arm64":
-      optional: true
-    "@parcel/watcher-win32-ia32":
-      optional: true
-    "@parcel/watcher-win32-x64":
-      optional: true
-  checksum: 10c0/9bad727d8b11e5d150ec47459254544c583adaa47d047b8ef65e1c74aede1a0767dc7fc6b8997649dae07318d6ef39caba6a1c405d306398d5bcd47074ec5d29
-  languageName: node
-  linkType: hard
-
 "@pkgjs/parseargs@npm:^0.11.0":
   version: 0.11.0
   resolution: "@pkgjs/parseargs@npm:0.11.0"
@@ -3346,96 +3127,30 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@rails/ujs@npm:7.1.501":
-  version: 7.1.501
-  resolution: "@rails/ujs@npm:7.1.501"
-  checksum: 10c0/b75a30f36ff219264e0926da1ffcd14c2a5d6aee5be29da4dc81f9a45843875da79ac19cf7ed9a3f11a39084398d0ae4a75a8edb28ba94907db3081572af62b0
-  languageName: node
-  linkType: hard
-
-"@react-spring/animated@npm:~9.7.5":
-  version: 9.7.5
-  resolution: "@react-spring/animated@npm:9.7.5"
-  dependencies:
-    "@react-spring/shared": "npm:~9.7.5"
-    "@react-spring/types": "npm:~9.7.5"
-  peerDependencies:
-    react: ^16.8.0 || ^17.0.0 || ^18.0.0
-  checksum: 10c0/f8c2473c60f39a878c7dd0fdfcfcdbc720521e1506aa3f63c9de64780694a0a73d5ccc535a5ccec3520ddb70a71cf43b038b32c18e99531522da5388c510ecd7
-  languageName: node
-  linkType: hard
-
-"@react-spring/core@npm:~9.7.5":
-  version: 9.7.5
-  resolution: "@react-spring/core@npm:9.7.5"
-  dependencies:
-    "@react-spring/animated": "npm:~9.7.5"
-    "@react-spring/shared": "npm:~9.7.5"
-    "@react-spring/types": "npm:~9.7.5"
-  peerDependencies:
-    react: ^16.8.0 || ^17.0.0 || ^18.0.0
-  checksum: 10c0/5bfd83dfe248cd91889f215f015d908c7714ef445740fd5afa054b27ebc7d5a456abf6c309e2459d9b5b436e78d6fda16b62b9601f96352e9130552c02270830
-  languageName: node
-  linkType: hard
-
-"@react-spring/rafz@npm:~9.7.5":
-  version: 9.7.5
-  resolution: "@react-spring/rafz@npm:9.7.5"
-  checksum: 10c0/8bdad180feaa9a0e870a513043a5e98a4e9b7292a9f887575b7e6fadab2677825bc894b7ff16c38511b35bfe6cc1072df5851c5fee64448d67551559578ca759
-  languageName: node
-  linkType: hard
-
-"@react-spring/shared@npm:~9.7.5":
-  version: 9.7.5
-  resolution: "@react-spring/shared@npm:9.7.5"
-  dependencies:
-    "@react-spring/rafz": "npm:~9.7.5"
-    "@react-spring/types": "npm:~9.7.5"
-  peerDependencies:
-    react: ^16.8.0 || ^17.0.0 || ^18.0.0
-  checksum: 10c0/0207eacccdedd918a2fc55e78356ce937f445ce27ad9abd5d3accba8f9701a39349b55115641dc2b39bb9d3a155b058c185b411d292dc8cc5686bfa56f73b94f
-  languageName: node
-  linkType: hard
-
-"@react-spring/types@npm:~9.7.5":
-  version: 9.7.5
-  resolution: "@react-spring/types@npm:9.7.5"
-  checksum: 10c0/85c05121853cacb64f7cf63a4855e9044635e1231f70371cd7b8c78bc10be6f4dd7c68f592f92a2607e8bb68051540989b4677a2ccb525dba937f5cd95dc8bc1
-  languageName: node
-  linkType: hard
-
-"@react-spring/web@npm:^9.7.5":
-  version: 9.7.5
-  resolution: "@react-spring/web@npm:9.7.5"
-  dependencies:
-    "@react-spring/animated": "npm:~9.7.5"
-    "@react-spring/core": "npm:~9.7.5"
-    "@react-spring/shared": "npm:~9.7.5"
-    "@react-spring/types": "npm:~9.7.5"
-  peerDependencies:
-    react: ^16.8.0 || ^17.0.0 || ^18.0.0
-    react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-  checksum: 10c0/bcd1e052e1b16341a12a19bf4515f153ca09d1fa86ff7752a5d02d7c4db58e8baf80e6283e64411f1e388c65340dce2254b013083426806b5dbae38bd151e53e
+"@rails/ujs@npm:7.1.401":
+  version: 7.1.401
+  resolution: "@rails/ujs@npm:7.1.401"
+  checksum: 10c0/08eae084c80e837e47cc01d0be25a431495f7dea381dcaaa4ce39a3217fac46bf87d169b3dfcf304ae16e0714de7435c2b8c5eb8d5052e3ba70ef3050a72fa3c
   languageName: node
   linkType: hard
 
 "@reduxjs/toolkit@npm:^2.0.1":
-  version: 2.6.1
-  resolution: "@reduxjs/toolkit@npm:2.6.1"
+  version: 2.2.7
+  resolution: "@reduxjs/toolkit@npm:2.2.7"
   dependencies:
     immer: "npm:^10.0.3"
     redux: "npm:^5.0.1"
     redux-thunk: "npm:^3.1.0"
     reselect: "npm:^5.1.0"
   peerDependencies:
-    react: ^16.9.0 || ^17.0.0 || ^18 || ^19
+    react: ^16.9.0 || ^17.0.0 || ^18
     react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0
   peerDependenciesMeta:
     react:
       optional: true
     react-redux:
       optional: true
-  checksum: 10c0/6ae5db267f2b0a9da8b59080797c5adb1b92b50c0f2bdd933470206285d684b79ed2b1c4e2d7a0fb4aa9e3772d0e215fdcd3a8e4a4a2aac81400fcd790dbd6cd
+  checksum: 10c0/7761a91adac2b5e1d50a8163ba5441480bb86a3a80b7583037c27a88463394b132dd7592862fc2be03aa7ab98a6e1710549889986dc0d3f033c169a3ba2cb02e
   languageName: node
   linkType: hard
 
@@ -3735,8 +3450,8 @@ __metadata:
   linkType: hard
 
 "@testing-library/jest-dom@npm:^6.0.0":
-  version: 6.6.3
-  resolution: "@testing-library/jest-dom@npm:6.6.3"
+  version: 6.5.0
+  resolution: "@testing-library/jest-dom@npm:6.5.0"
   dependencies:
     "@adobe/css-tools": "npm:^4.4.0"
     aria-query: "npm:^5.0.0"
@@ -3745,27 +3460,27 @@ __metadata:
     dom-accessibility-api: "npm:^0.6.3"
     lodash: "npm:^4.17.21"
     redent: "npm:^3.0.0"
-  checksum: 10c0/5566b6c0b7b0709bc244aec3aa3dc9e5f4663e8fb2b99d8cd456fc07279e59db6076cbf798f9d3099a98fca7ef4cd50e4e1f4c4dec5a60a8fad8d24a638a5bf6
+  checksum: 10c0/fd5936a547f04608d8de15a7de3ae26516f21023f8f45169b10c8c8847015fd20ec259b7309f08aa1031bcbc37c6e5e6f532d1bb85ef8f91bad654193ec66a4c
   languageName: node
   linkType: hard
 
 "@testing-library/react@npm:^16.0.0":
-  version: 16.1.0
-  resolution: "@testing-library/react@npm:16.1.0"
+  version: 16.0.1
+  resolution: "@testing-library/react@npm:16.0.1"
   dependencies:
     "@babel/runtime": "npm:^7.12.5"
   peerDependencies:
     "@testing-library/dom": ^10.0.0
-    "@types/react": ^18.0.0 || ^19.0.0
-    "@types/react-dom": ^18.0.0 || ^19.0.0
-    react: ^18.0.0 || ^19.0.0
-    react-dom: ^18.0.0 || ^19.0.0
+    "@types/react": ^18.0.0
+    "@types/react-dom": ^18.0.0
+    react: ^18.0.0
+    react-dom: ^18.0.0
   peerDependenciesMeta:
     "@types/react":
       optional: true
     "@types/react-dom":
       optional: true
-  checksum: 10c0/8451dcc76ba0d4f3504af78f2a4aacc13117691f4b7a3c279f3e047d5ea817ff686496ad53e7f65f6183112aef2be3f318af609b1f5d666eed42b1014d1c68d5
+  checksum: 10c0/67d05dec5ad5a2e6f92b6a3234af785435c7bb62bdbf12f3bfc89c9bca0c871a189e88c4ba023ed4cea504704c87c6ac7e86e24a3962df6c521ae89b62f48ff7
   languageName: node
   linkType: hard
 
@@ -3783,15 +3498,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@tybys/wasm-util@npm:^0.9.0":
-  version: 0.9.0
-  resolution: "@tybys/wasm-util@npm:0.9.0"
-  dependencies:
-    tslib: "npm:^2.4.0"
-  checksum: 10c0/f9fde5c554455019f33af6c8215f1a1435028803dc2a2825b077d812bed4209a1a64444a4ca0ce2ea7e1175c8d88e2f9173a36a33c199e8a5c671aa31de8242d
-  languageName: node
-  linkType: hard
-
 "@types/aria-query@npm:^5.0.1":
   version: 5.0.4
   resolution: "@types/aria-query@npm:5.0.4"
@@ -3799,7 +3505,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/babel__core@npm:*, @types/babel__core@npm:^7.1.12, @types/babel__core@npm:^7.1.14, @types/babel__core@npm:^7.20.1, @types/babel__core@npm:^7.20.5":
+"@types/babel__core@npm:*, @types/babel__core@npm:^7.1.12, @types/babel__core@npm:^7.1.14, @types/babel__core@npm:^7.1.7, @types/babel__core@npm:^7.20.1":
   version: 7.20.5
   resolution: "@types/babel__core@npm:7.20.5"
   dependencies:
@@ -3821,12 +3527,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/babel__helper-plugin-utils@npm:^7.10.3":
-  version: 7.10.3
-  resolution: "@types/babel__helper-plugin-utils@npm:7.10.3"
+"@types/babel__helper-plugin-utils@npm:^7.10.0":
+  version: 7.10.2
+  resolution: "@types/babel__helper-plugin-utils@npm:7.10.2"
   dependencies:
     "@types/babel__core": "npm:*"
-  checksum: 10c0/c22f68e8019c1e75e42fccc6eaca94a269fa177c4544599aa084b216b879b626f63f89755a4ac2dc9054b6e9ed4e0fab1e3460d36ce20767c99aef4a3c81fce3
+  checksum: 10c0/1c544e33b30ccfc02254b9fe090c52880c8bb05e5406f525bfdce71ce34e93677276f788b93e9fa5e1a16f6317b97e59a07d8a92bf4726d8f4639a0045ebb590
   languageName: node
   linkType: hard
 
@@ -3840,12 +3546,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6, @types/babel__traverse@npm:^7.20.6":
-  version: 7.20.6
-  resolution: "@types/babel__traverse@npm:7.20.6"
+"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6, @types/babel__traverse@npm:^7.1.7":
+  version: 7.20.3
+  resolution: "@types/babel__traverse@npm:7.20.3"
   dependencies:
     "@babel/types": "npm:^7.20.7"
-  checksum: 10c0/7ba7db61a53e28cac955aa99af280d2600f15a8c056619c05b6fc911cbe02c61aa4f2823299221b23ce0cce00b294c0e5f618ec772aa3f247523c2e48cf7b888
+  checksum: 10c0/295ed9b837e62e17ee43be0df45d90fff5208986bd43af593c9020d152d3b2c55328e038c2f8585926b63cc22f887f28bf3f4c805aa881e2dd0bdd5ead92ece0
   languageName: node
   linkType: hard
 
@@ -3877,7 +3583,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/emoji-mart@npm:3.0.14":
+"@types/emoji-mart@npm:^3.0.9":
   version: 3.0.14
   resolution: "@types/emoji-mart@npm:3.0.14"
   dependencies:
@@ -3893,29 +3599,20 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/eslint-plugin-jsx-a11y@npm:^6":
-  version: 6.10.0
-  resolution: "@types/eslint-plugin-jsx-a11y@npm:6.10.0"
-  dependencies:
-    "@types/eslint": "npm:*"
-  checksum: 10c0/ec494e0ea56a0a10140316a74463eb8a6ac5ed02d7408ef91444f7c0c9bc875866a9d0be7a761f1631ea543c2413b1db947bf7cb1df9d936171e5d82f71c6772
-  languageName: node
-  linkType: hard
-
-"@types/eslint@npm:*, @types/eslint@npm:^9.6.1":
-  version: 9.6.1
-  resolution: "@types/eslint@npm:9.6.1"
+"@types/eslint@npm:7 || 8":
+  version: 8.56.10
+  resolution: "@types/eslint@npm:8.56.10"
   dependencies:
     "@types/estree": "npm:*"
     "@types/json-schema": "npm:*"
-  checksum: 10c0/69ba24fee600d1e4c5abe0df086c1a4d798abf13792d8cfab912d76817fe1a894359a1518557d21237fbaf6eda93c5ab9309143dee4c59ef54336d1b3570420e
+  checksum: 10c0/674349d6c342c3864d70f4d5a9965f96fb253801532752c8c500ad6a1c2e8b219e01ccff5dc8791dcb58b5483012c495708bb9f3ff929f5c9322b3da126c15d3
   languageName: node
   linkType: hard
 
-"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6":
-  version: 1.0.7
-  resolution: "@types/estree@npm:1.0.7"
-  checksum: 10c0/be815254316882f7c40847336cd484c3bc1c3e34f710d197160d455dc9d6d050ffbf4c3bc76585dba86f737f020ab20bdb137ebe0e9116b0c86c7c0342221b8c
+"@types/estree@npm:*, @types/estree@npm:^1.0.0":
+  version: 1.0.5
+  resolution: "@types/estree@npm:1.0.5"
+  checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d
   languageName: node
   linkType: hard
 
@@ -3977,12 +3674,12 @@ __metadata:
   linkType: hard
 
 "@types/hoist-non-react-statics@npm:^3.3.1":
-  version: 3.3.6
-  resolution: "@types/hoist-non-react-statics@npm:3.3.6"
+  version: 3.3.5
+  resolution: "@types/hoist-non-react-statics@npm:3.3.5"
   dependencies:
     "@types/react": "npm:*"
     hoist-non-react-statics: "npm:^3.3.0"
-  checksum: 10c0/149a4c217d81f21f8a1e152160a59d5b99b6a9aa6d354385d5f5bc02760cbf1e170a8442ba92eb653befff44b0c5bc2234bb77ce33e0d11a65f779e8bab5c321
+  checksum: 10c0/2a3b64bf3d9817d7830afa60ee314493c475fb09570a64e7737084cd482d2177ebdddf888ce837350bac51741278b077683facc9541f052d4bbe8487b4e3e618
   languageName: node
   linkType: hard
 
@@ -4035,12 +3732,12 @@ __metadata:
   linkType: hard
 
 "@types/jest@npm:^29.5.2":
-  version: 29.5.14
-  resolution: "@types/jest@npm:29.5.14"
+  version: 29.5.13
+  resolution: "@types/jest@npm:29.5.13"
   dependencies:
     expect: "npm:^29.0.0"
     pretty-format: "npm:^29.0.0"
-  checksum: 10c0/18e0712d818890db8a8dab3d91e9ea9f7f19e3f83c2e50b312f557017dc81466207a71f3ed79cf4428e813ba939954fa26ffa0a9a7f153181ba174581b1c2aed
+  checksum: 10c0/9c31af0b155387b9860908830de63c6b79011d7c87c8b61b39da124e26e55423dd51b006749aafe4f0ef3a065016619a1f93ef4b055157d43727f448e67824b7
   languageName: node
   linkType: hard
 
@@ -4062,17 +3759,17 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.8":
+"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.12, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.8":
   version: 7.0.15
   resolution: "@types/json-schema@npm:7.0.15"
   checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db
   languageName: node
   linkType: hard
 
-"@types/json-stable-stringify@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "@types/json-stable-stringify@npm:1.1.0"
-  checksum: 10c0/8f69944701510243cd3a83aa44363a8a4d366f11a659b258f69fb3ad0f94ab1e2533206a2c929ac7fd18784d201b663b3f02a45934f545c926f051d8cb4df095
+"@types/json-stable-stringify@npm:^1.0.32":
+  version: 1.0.35
+  resolution: "@types/json-stable-stringify@npm:1.0.35"
+  checksum: 10c0/67b50a86478f932c932ba21515b80700a8844d55e0656b3abe2c32c41c1c405c70fcd10b30ff8f38d1869871af2deae63ffdea54ce72cc2ee92e94a9d1cf2b6f
   languageName: node
   linkType: hard
 
@@ -4084,9 +3781,9 @@ __metadata:
   linkType: hard
 
 "@types/lodash@npm:^4.14.195":
-  version: 4.17.16
-  resolution: "@types/lodash@npm:4.17.16"
-  checksum: 10c0/cf017901b8ab1d7aabc86d5189d9288f4f99f19a75caf020c0e2c77b8d4cead4db0d0b842d009b029339f92399f49f34377dd7c2721053388f251778b4c23534
+  version: 4.17.7
+  resolution: "@types/lodash@npm:4.17.7"
+  checksum: 10c0/40c965b5ffdcf7ff5c9105307ee08b782da228c01b5c0529122c554c64f6b7168fc8f11dc79aa7bae4e67e17efafaba685dc3a47e294dbf52a65ed2b67100561
   languageName: node
   linkType: hard
 
@@ -4111,12 +3808,19 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/node@npm:*, @types/node@npm:^22.0.0":
-  version: 22.13.14
-  resolution: "@types/node@npm:22.13.14"
+"@types/node@npm:*":
+  version: 20.8.10
+  resolution: "@types/node@npm:20.8.10"
   dependencies:
-    undici-types: "npm:~6.20.0"
-  checksum: 10c0/fa2ab5b8277bfbcc86c42e46a3ea9871b0d559894cc9d955685d17178c9499f0b1bf03d1d1ea8d92ef2dda818988f4035acb8abf9dc15423a998fa56173ab804
+    undici-types: "npm:~5.26.4"
+  checksum: 10c0/caaa3ae9294f1bfdacb029a916c64af63cbcea613a52f53ea86f93c91779859af177b2b68113ef835194519f5e76cadda08559929b68297f1a8a568c207f9f66
+  languageName: node
+  linkType: hard
+
+"@types/node@npm:14 || 16 || 17":
+  version: 17.0.45
+  resolution: "@types/node@npm:17.0.45"
+  checksum: 10c0/0db377133d709b33a47892581a21a41cd7958f22723a3cc6c71d55ac018121382de42fbfc7970d5ae3e7819dbe5f40e1c6a5174aedf7e7964e9cb8fa72b580b0
   languageName: node
   linkType: hard
 
@@ -4135,27 +3839,27 @@ __metadata:
   linkType: hard
 
 "@types/pg@npm:^8.6.6":
-  version: 8.11.11
-  resolution: "@types/pg@npm:8.11.11"
+  version: 8.11.10
+  resolution: "@types/pg@npm:8.11.10"
   dependencies:
     "@types/node": "npm:*"
     pg-protocol: "npm:*"
     pg-types: "npm:^4.0.1"
-  checksum: 10c0/18c2585e1ba7a5dd5f849d49410d53fdfe9a6c3cbc4ae46c51fd728264d6ecf9a84a5cd82d89cb1f870a74383bad88effce1eed888f16accbcbde56a53d23a69
+  checksum: 10c0/c8800d0ab2c6424308e6c6b40c73f19583ee1aed758462bd07694844b0a551b5841442205a4ee05207b80109ba502f33f20241b1bd9b4902e713611fb9e08f6c
   languageName: node
   linkType: hard
 
-"@types/picomatch@npm:^3":
-  version: 3.0.2
-  resolution: "@types/picomatch@npm:3.0.2"
-  checksum: 10c0/f35d16fe10a6e13ead6499dd7d7d317e4fd78e48260398104e837e5ca83d393024bdc6f432cb644c0a69b0726a071fcc6eb09befbbcfafb3c3c5f71dbbfde487
+"@types/picomatch@npm:^2.3.0":
+  version: 2.3.2
+  resolution: "@types/picomatch@npm:2.3.2"
+  checksum: 10c0/91445cfc0d07fe2a44c16ee284ab2e2a279da3f6df9c62ad61e7bc50343e47bef541369aff6110c4e51bd8fe501fc9c564deefbb4c03e392254889de6b46f237
   languageName: node
   linkType: hard
 
 "@types/prop-types@npm:*, @types/prop-types@npm:^15.7.5":
-  version: 15.7.14
-  resolution: "@types/prop-types@npm:15.7.14"
-  checksum: 10c0/1ec775160bfab90b67a782d735952158c7e702ca4502968aa82565bd8e452c2de8601c8dfe349733073c31179116cf7340710160d3836aa8a1ef76d1532893b1
+  version: 15.7.13
+  resolution: "@types/prop-types@npm:15.7.13"
+  checksum: 10c0/1b20fc67281902c6743379960247bc161f3f0406ffc0df8e7058745a85ea1538612109db0406290512947f9632fe9e10e7337bf0ce6338a91d6c948df16a7c61
   languageName: node
   linkType: hard
 
@@ -4195,11 +3899,11 @@ __metadata:
   linkType: hard
 
 "@types/react-dom@npm:^18.2.4":
-  version: 18.3.5
-  resolution: "@types/react-dom@npm:18.3.5"
-  peerDependencies:
-    "@types/react": ^18.0.0
-  checksum: 10c0/b163d35a6b32a79f5782574a7aeb12a31a647e248792bf437e6d596e2676961c394c5e3c6e91d1ce44ae90441dbaf93158efb4f051c0d61e2612f1cb04ce4faa
+  version: 18.3.0
+  resolution: "@types/react-dom@npm:18.3.0"
+  dependencies:
+    "@types/react": "npm:*"
+  checksum: 10c0/6c90d2ed72c5a0e440d2c75d99287e4b5df3e7b011838cdc03ae5cd518ab52164d86990e73246b9d812eaf02ec351d74e3b4f5bd325bf341e13bf980392fd53b
   languageName: node
   linkType: hard
 
@@ -4222,6 +3926,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/react-motion@npm:^0.0.40":
+  version: 0.0.40
+  resolution: "@types/react-motion@npm:0.0.40"
+  dependencies:
+    "@types/react": "npm:*"
+  checksum: 10c0/8a560051be917833fdbe051185b53aeafbe8657968ac8e073ac874b9a55c6f16e3793748b13cfb9bd6d9a3d27aba116d6f8f296ec1950f4175dc94d17c5e8470
+  languageName: node
+  linkType: hard
+
 "@types/react-router-dom@npm:^5.3.3":
   version: 5.3.3
   resolution: "@types/react-router-dom@npm:5.3.3"
@@ -4253,20 +3966,20 @@ __metadata:
   linkType: hard
 
 "@types/react-swipeable-views@npm:^0.13.1":
-  version: 0.13.6
-  resolution: "@types/react-swipeable-views@npm:0.13.6"
+  version: 0.13.5
+  resolution: "@types/react-swipeable-views@npm:0.13.5"
   dependencies:
     "@types/react": "npm:*"
-  checksum: 10c0/a26879146748417234bb7f44c5a71e6bab2b76c0b34c72f0493c18403487a5d77021510e8665bd8bd22786904fbbd90d6db55c8dd2bf983c32421139de851c94
+  checksum: 10c0/d1dcc78d862f37d30a43d79d915fdb388e05dce0b2ac07462ca4f1b00e0eef37cb41d75997f5685dec79bcce1ffee0dfbc744f20d5266dd3090658def5b4e193
   languageName: node
   linkType: hard
 
 "@types/react-test-renderer@npm:^18.0.0":
-  version: 18.3.1
-  resolution: "@types/react-test-renderer@npm:18.3.1"
+  version: 18.3.0
+  resolution: "@types/react-test-renderer@npm:18.3.0"
   dependencies:
-    "@types/react": "npm:^18"
-  checksum: 10c0/9fc8467ff1a3f14be6cc3498a75fc788d2c92c0fffa7bf21269ed5d9d82db9195bf2178ddc42ea16a0836995c1b77601c6be8abb27bd1864668c418c6d0e5a3b
+    "@types/react": "npm:*"
+  checksum: 10c0/3c9748be52e8e659e7adf91dea6939486463264e6f633bf21c4cb116de18af7bef0595568a1e588160420b2f65289473075dda1cb417c2875df8cf7a09f5d913
   languageName: node
   linkType: hard
 
@@ -4288,13 +4001,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/react@npm:^18.2.7":
-  version: 18.3.18
-  resolution: "@types/react@npm:18.3.18"
+"@types/react@npm:*, @types/react@npm:16 || 17 || 18, @types/react@npm:>=16.9.11, @types/react@npm:^18.2.7":
+  version: 18.3.8
+  resolution: "@types/react@npm:18.3.8"
   dependencies:
     "@types/prop-types": "npm:*"
     csstype: "npm:^3.0.2"
-  checksum: 10c0/8fb2b00672072135d0858dc9db07873ea107cc238b6228aaa2a9afd1ef7a64a7074078250db38afbeb19064be8ea6af5eac32d404efdd5f45e093cc4829d87f8
+  checksum: 10c0/367312c9fe276639ecb142265e090a4dd04bb39f8d718cbab546de3f1ddcfddeff415e1147d0fc40f734badaa7420b7b109d511bd4304b2c4c9c36164612fdf8
   languageName: node
   linkType: hard
 
@@ -4322,6 +4035,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/semver@npm:^7.5.0":
+  version: 7.5.8
+  resolution: "@types/semver@npm:7.5.8"
+  checksum: 10c0/8663ff927234d1c5fcc04b33062cb2b9fcfbe0f5f351ed26c4d1e1581657deebd506b41ff7fdf89e787e3d33ce05854bc01686379b89e9c49b564c4cfa988efa
+  languageName: node
+  linkType: hard
+
 "@types/send@npm:*":
   version: 0.17.4
   resolution: "@types/send@npm:0.17.4"
@@ -4387,10 +4107,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/use-sync-external-store@npm:^0.0.6":
-  version: 0.0.6
-  resolution: "@types/use-sync-external-store@npm:0.0.6"
-  checksum: 10c0/77c045a98f57488201f678b181cccd042279aff3da34540ad242f893acc52b358bd0a8207a321b8ac09adbcef36e3236944390e2df4fcedb556ce7bb2a88f2a8
+"@types/use-sync-external-store@npm:^0.0.3":
+  version: 0.0.3
+  resolution: "@types/use-sync-external-store@npm:0.0.3"
+  checksum: 10c0/82824c1051ba40a00e3d47964cdf4546a224e95f172e15a9c62aa3f118acee1c7518b627a34f3aa87298a2039f982e8509f92bfcc18bea7c255c189c293ba547
   languageName: node
   linkType: hard
 
@@ -4409,9 +4129,9 @@ __metadata:
   linkType: hard
 
 "@types/webpack-env@npm:^1.18.4":
-  version: 1.18.8
-  resolution: "@types/webpack-env@npm:1.18.8"
-  checksum: 10c0/527a5d1eb75c5243e4f3665d956c7c340f899955dd25d16c9fd9750406f32e95a3a17d207640295038e8235c0c2a2daf084f420e088e58b965d82fc74f6012d7
+  version: 1.18.5
+  resolution: "@types/webpack-env@npm:1.18.5"
+  checksum: 10c0/b9e4876e8c7cae419896249f9ed795db283c008fe1d38efa679cbbf05194fc2eea2a5bfb4ff4393d109e3a9895416dadf5f3ddd5c22931b678062230f860454e
   languageName: node
   linkType: hard
 
@@ -4427,8 +4147,8 @@ __metadata:
   linkType: hard
 
 "@types/webpack@npm:^4.41.33":
-  version: 4.41.40
-  resolution: "@types/webpack@npm:4.41.40"
+  version: 4.41.39
+  resolution: "@types/webpack@npm:4.41.39"
   dependencies:
     "@types/node": "npm:*"
     "@types/tapable": "npm:^1"
@@ -4436,16 +4156,16 @@ __metadata:
     "@types/webpack-sources": "npm:*"
     anymatch: "npm:^3.0.0"
     source-map: "npm:^0.6.0"
-  checksum: 10c0/ecd530e5db4c21ec61795eec538026f96c126323836249a83e72805afd1d0b1141fc781f14d4a59d77f877523384b4c5d79dc391cfb901e7a781a9aa085f8198
+  checksum: 10c0/740420d092abb80b70263b02609bde209801b060d8e6f3a399a129945cb09182c2ce63dc816908bfbcdb123b35dc4c4fb51367aac2b5974537694cac2631db21
   languageName: node
   linkType: hard
 
 "@types/ws@npm:^8.5.9":
-  version: 8.5.14
-  resolution: "@types/ws@npm:8.5.14"
+  version: 8.5.12
+  resolution: "@types/ws@npm:8.5.12"
   dependencies:
     "@types/node": "npm:*"
-  checksum: 10c0/be88a0b6252f939cb83340bd1b4d450287f752c19271195cd97564fd94047259a9bb8c31c585a61b69d8a1b069a99df9dd804db0132d3359c54d3890c501416a
+  checksum: 10c0/3fd77c9e4e05c24ce42bfc7647f7506b08c40a40fe2aea236ef6d4e96fc7cb4006a81ed1b28ec9c457e177a74a72924f4768b7b4652680b42dfd52bc380e15f9
   languageName: node
   linkType: hard
 
@@ -4465,240 +4185,189 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@typescript-eslint/eslint-plugin@npm:8.28.0":
-  version: 8.28.0
-  resolution: "@typescript-eslint/eslint-plugin@npm:8.28.0"
+"@typescript-eslint/eslint-plugin@npm:^8.0.0":
+  version: 8.1.0
+  resolution: "@typescript-eslint/eslint-plugin@npm:8.1.0"
   dependencies:
     "@eslint-community/regexpp": "npm:^4.10.0"
-    "@typescript-eslint/scope-manager": "npm:8.28.0"
-    "@typescript-eslint/type-utils": "npm:8.28.0"
-    "@typescript-eslint/utils": "npm:8.28.0"
-    "@typescript-eslint/visitor-keys": "npm:8.28.0"
+    "@typescript-eslint/scope-manager": "npm:8.1.0"
+    "@typescript-eslint/type-utils": "npm:8.1.0"
+    "@typescript-eslint/utils": "npm:8.1.0"
+    "@typescript-eslint/visitor-keys": "npm:8.1.0"
     graphemer: "npm:^1.4.0"
     ignore: "npm:^5.3.1"
     natural-compare: "npm:^1.4.0"
-    ts-api-utils: "npm:^2.0.1"
+    ts-api-utils: "npm:^1.3.0"
   peerDependencies:
     "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0
     eslint: ^8.57.0 || ^9.0.0
-    typescript: ">=4.8.4 <5.9.0"
-  checksum: 10c0/f01b7d231b01ec2c1cc7c40599ddceb329532f2876664a39dec9d25c0aed4cfdbef3ec07f26bac357df000d798f652af6fdb6a2481b6120e43bfa38f7c7a7c48
+  peerDependenciesMeta:
+    typescript:
+      optional: true
+  checksum: 10c0/7bbeae588f859b59c34d6a76cac06ef0fa605921b40c5d3b65b94829984280ea84c4dd3f5cb9ce2eb326f5563e9abb4c90ebff05c47f83f4def296c2ea1fa86c
   languageName: node
   linkType: hard
 
-"@typescript-eslint/parser@npm:8.28.0":
-  version: 8.28.0
-  resolution: "@typescript-eslint/parser@npm:8.28.0"
+"@typescript-eslint/parser@npm:^8.0.0":
+  version: 8.1.0
+  resolution: "@typescript-eslint/parser@npm:8.1.0"
   dependencies:
-    "@typescript-eslint/scope-manager": "npm:8.28.0"
-    "@typescript-eslint/types": "npm:8.28.0"
-    "@typescript-eslint/typescript-estree": "npm:8.28.0"
-    "@typescript-eslint/visitor-keys": "npm:8.28.0"
+    "@typescript-eslint/scope-manager": "npm:8.1.0"
+    "@typescript-eslint/types": "npm:8.1.0"
+    "@typescript-eslint/typescript-estree": "npm:8.1.0"
+    "@typescript-eslint/visitor-keys": "npm:8.1.0"
     debug: "npm:^4.3.4"
   peerDependencies:
     eslint: ^8.57.0 || ^9.0.0
-    typescript: ">=4.8.4 <5.9.0"
-  checksum: 10c0/4bde6887bbf3fe031c01e46db90f9f384a8cac2e67c2972b113a62d607db75e01db943601279aac847b9187960a038981814042cb02fd5aa27ea4613028f9313
+  peerDependenciesMeta:
+    typescript:
+      optional: true
+  checksum: 10c0/b94b2d3ab5ca505484d100701fad6a04a5dc8d595029bac1b9f5b8a4a91d80fd605b0f65d230b36a97ab7e5d55eeb0c28af2ab63929a3e4ab8fdefd2a548c36b
   languageName: node
   linkType: hard
 
-"@typescript-eslint/scope-manager@npm:8.28.0":
-  version: 8.28.0
-  resolution: "@typescript-eslint/scope-manager@npm:8.28.0"
+"@typescript-eslint/scope-manager@npm:6.21.0":
+  version: 6.21.0
+  resolution: "@typescript-eslint/scope-manager@npm:6.21.0"
   dependencies:
-    "@typescript-eslint/types": "npm:8.28.0"
-    "@typescript-eslint/visitor-keys": "npm:8.28.0"
-  checksum: 10c0/f3bd76b3f54e60f1efe108b233b2d818e44ecf0dc6422cc296542f784826caf3c66d51b8acc83d8c354980bd201e1d9aa1ea01011de96e0613d320c00e40ccfd
+    "@typescript-eslint/types": "npm:6.21.0"
+    "@typescript-eslint/visitor-keys": "npm:6.21.0"
+  checksum: 10c0/eaf868938d811cbbea33e97e44ba7050d2b6892202cea6a9622c486b85ab1cf801979edf78036179a8ba4ac26f1dfdf7fcc83a68c1ff66be0b3a8e9a9989b526
   languageName: node
   linkType: hard
 
-"@typescript-eslint/type-utils@npm:8.28.0":
-  version: 8.28.0
-  resolution: "@typescript-eslint/type-utils@npm:8.28.0"
+"@typescript-eslint/scope-manager@npm:8.1.0":
+  version: 8.1.0
+  resolution: "@typescript-eslint/scope-manager@npm:8.1.0"
   dependencies:
-    "@typescript-eslint/typescript-estree": "npm:8.28.0"
-    "@typescript-eslint/utils": "npm:8.28.0"
+    "@typescript-eslint/types": "npm:8.1.0"
+    "@typescript-eslint/visitor-keys": "npm:8.1.0"
+  checksum: 10c0/2bcf8cd176a1819bddcae16c572e7da8fba821b995a91cd53d64d8d6b85a17f5a895522f281ba57e34929574bddd4d6684ee3e545ec4e8096be4c3198e253a9a
+  languageName: node
+  linkType: hard
+
+"@typescript-eslint/type-utils@npm:8.1.0":
+  version: 8.1.0
+  resolution: "@typescript-eslint/type-utils@npm:8.1.0"
+  dependencies:
+    "@typescript-eslint/typescript-estree": "npm:8.1.0"
+    "@typescript-eslint/utils": "npm:8.1.0"
     debug: "npm:^4.3.4"
-    ts-api-utils: "npm:^2.0.1"
-  peerDependencies:
-    eslint: ^8.57.0 || ^9.0.0
-    typescript: ">=4.8.4 <5.9.0"
-  checksum: 10c0/b8936edc2153bf794efba39bfb06393a228217830051767360f4b691fed7c82f3831c4fc6deac6d78b90a58596e61f866c17eaee9dd793c3efda3ebdcf5a71d8
+    ts-api-utils: "npm:^1.3.0"
+  peerDependenciesMeta:
+    typescript:
+      optional: true
+  checksum: 10c0/62753941c4136e8d2daa72fe0410dea48e5317a6f12ece6382ca85e29912bd1b3f739b61d1060fc0a1f8c488dfc905beab4c8b8497951a21c3138a659c7271ec
   languageName: node
   linkType: hard
 
-"@typescript-eslint/types@npm:8.28.0":
-  version: 8.28.0
-  resolution: "@typescript-eslint/types@npm:8.28.0"
-  checksum: 10c0/1f95895e20dac1cf063dc93c99142fd1871e53be816bcbbee93f22a05e6b2a82ca83c20ce3a551f65555910aa0956443a23268edbb004369d0d5cb282d13c377
+"@typescript-eslint/types@npm:6.21.0":
+  version: 6.21.0
+  resolution: "@typescript-eslint/types@npm:6.21.0"
+  checksum: 10c0/020631d3223bbcff8a0da3efbdf058220a8f48a3de221563996ad1dcc30d6c08dadc3f7608cc08830d21c0d565efd2db19b557b9528921c78aabb605eef2d74d
   languageName: node
   linkType: hard
 
-"@typescript-eslint/typescript-estree@npm:8.28.0":
-  version: 8.28.0
-  resolution: "@typescript-eslint/typescript-estree@npm:8.28.0"
+"@typescript-eslint/types@npm:8.1.0":
+  version: 8.1.0
+  resolution: "@typescript-eslint/types@npm:8.1.0"
+  checksum: 10c0/ceade44455f45974e68956016c4d1c6626580732f7f9675e14ffa63db80b551752b0df596b20473dae9f0dc6ed966e17417dc2cf36e1a82b6ab0edc97c5eaa50
+  languageName: node
+  linkType: hard
+
+"@typescript-eslint/typescript-estree@npm:6.21.0":
+  version: 6.21.0
+  resolution: "@typescript-eslint/typescript-estree@npm:6.21.0"
   dependencies:
-    "@typescript-eslint/types": "npm:8.28.0"
-    "@typescript-eslint/visitor-keys": "npm:8.28.0"
+    "@typescript-eslint/types": "npm:6.21.0"
+    "@typescript-eslint/visitor-keys": "npm:6.21.0"
     debug: "npm:^4.3.4"
-    fast-glob: "npm:^3.3.2"
+    globby: "npm:^11.1.0"
+    is-glob: "npm:^4.0.3"
+    minimatch: "npm:9.0.3"
+    semver: "npm:^7.5.4"
+    ts-api-utils: "npm:^1.0.1"
+  peerDependenciesMeta:
+    typescript:
+      optional: true
+  checksum: 10c0/af1438c60f080045ebb330155a8c9bb90db345d5069cdd5d01b67de502abb7449d6c75500519df829f913a6b3f490ade3e8215279b6bdc63d0fb0ae61034df5f
+  languageName: node
+  linkType: hard
+
+"@typescript-eslint/typescript-estree@npm:8.1.0":
+  version: 8.1.0
+  resolution: "@typescript-eslint/typescript-estree@npm:8.1.0"
+  dependencies:
+    "@typescript-eslint/types": "npm:8.1.0"
+    "@typescript-eslint/visitor-keys": "npm:8.1.0"
+    debug: "npm:^4.3.4"
+    globby: "npm:^11.1.0"
     is-glob: "npm:^4.0.3"
     minimatch: "npm:^9.0.4"
     semver: "npm:^7.6.0"
-    ts-api-utils: "npm:^2.0.1"
-  peerDependencies:
-    typescript: ">=4.8.4 <5.9.0"
-  checksum: 10c0/97a91c95b1295926098c12e2d2c2abaa68994dc879da132dcce1e75ec9d7dee8187695eaa5241d09cbc42b5e633917b6d35c624e78e3d3ee9bda42d1318080b6
+    ts-api-utils: "npm:^1.3.0"
+  peerDependenciesMeta:
+    typescript:
+      optional: true
+  checksum: 10c0/a7bc8275df1c79c4cb14ef086c56674316dd4907efec53eddca35d0b5220428b69c82178ce2d95138da2e398269c8bd0764cae8020a36417e411e35c3c47bc4b
   languageName: node
   linkType: hard
 
-"@typescript-eslint/utils@npm:8.28.0, @typescript-eslint/utils@npm:^8.27.0":
-  version: 8.28.0
-  resolution: "@typescript-eslint/utils@npm:8.28.0"
+"@typescript-eslint/utils@npm:8.1.0":
+  version: 8.1.0
+  resolution: "@typescript-eslint/utils@npm:8.1.0"
   dependencies:
     "@eslint-community/eslint-utils": "npm:^4.4.0"
-    "@typescript-eslint/scope-manager": "npm:8.28.0"
-    "@typescript-eslint/types": "npm:8.28.0"
-    "@typescript-eslint/typescript-estree": "npm:8.28.0"
+    "@typescript-eslint/scope-manager": "npm:8.1.0"
+    "@typescript-eslint/types": "npm:8.1.0"
+    "@typescript-eslint/typescript-estree": "npm:8.1.0"
   peerDependencies:
     eslint: ^8.57.0 || ^9.0.0
-    typescript: ">=4.8.4 <5.9.0"
-  checksum: 10c0/d3425be7f86c1245a11f0ea39136af681027797417348d8e666d38c76646945eaed7b35eb8db66372b067dee8b02a855caf2c24c040ec9c31e59681ab223b59d
+  checksum: 10c0/c95503a6bdcd98b1ff04d1adbf46377b2036b1c510d90a4a056401f996f775f06c3108c95fb81cd6babc9c97b73b91b8e848f0337bc508de8a49c993582f0e75
   languageName: node
   linkType: hard
 
-"@typescript-eslint/visitor-keys@npm:8.28.0":
-  version: 8.28.0
-  resolution: "@typescript-eslint/visitor-keys@npm:8.28.0"
+"@typescript-eslint/utils@npm:^6.18.1":
+  version: 6.21.0
+  resolution: "@typescript-eslint/utils@npm:6.21.0"
   dependencies:
-    "@typescript-eslint/types": "npm:8.28.0"
-    eslint-visitor-keys: "npm:^4.2.0"
-  checksum: 10c0/245a78ed983fe95fbd1b0f2d4cb9e9d1d964bddc0aa3e3d6ab10c19c4273855bfb27d840bb1fd55deb7ae3078b52f26592472baf6fd2c7019a5aa3b1da974f35
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-darwin-arm64@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.3.2"
-  conditions: os=darwin & cpu=arm64
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-darwin-x64@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-darwin-x64@npm:1.3.2"
-  conditions: os=darwin & cpu=x64
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-freebsd-x64@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.3.2"
-  conditions: os=freebsd & cpu=x64
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.3.2"
-  conditions: os=linux & cpu=arm
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.3.2"
-  conditions: os=linux & cpu=arm
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-linux-arm64-gnu@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.3.2"
-  conditions: os=linux & cpu=arm64 & libc=glibc
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-linux-arm64-musl@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.3.2"
-  conditions: os=linux & cpu=arm64 & libc=musl
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.3.2"
-  conditions: os=linux & cpu=ppc64 & libc=glibc
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-linux-s390x-gnu@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.3.2"
-  conditions: os=linux & cpu=s390x & libc=glibc
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-linux-x64-gnu@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.3.2"
-  conditions: os=linux & cpu=x64 & libc=glibc
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-linux-x64-musl@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.3.2"
-  conditions: os=linux & cpu=x64 & libc=musl
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-wasm32-wasi@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.3.2"
-  dependencies:
-    "@napi-rs/wasm-runtime": "npm:^0.2.7"
-  conditions: cpu=wasm32
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-win32-arm64-msvc@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.3.2"
-  conditions: os=win32 & cpu=arm64
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-win32-ia32-msvc@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.3.2"
-  conditions: os=win32 & cpu=ia32
-  languageName: node
-  linkType: hard
-
-"@unrs/resolver-binding-win32-x64-msvc@npm:1.3.2":
-  version: 1.3.2
-  resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.3.2"
-  conditions: os=win32 & cpu=x64
-  languageName: node
-  linkType: hard
-
-"@use-gesture/core@npm:10.3.1":
-  version: 10.3.1
-  resolution: "@use-gesture/core@npm:10.3.1"
-  checksum: 10c0/2e3b5c0f7fe26cdb47be3a9c2a58a6a9edafc5b2895b07d2898eda9ab5a2b29fb0098b15597baa0856907b593075cd44cc69bba4785c9cfb7b6fabaa3b52cd3e
-  languageName: node
-  linkType: hard
-
-"@use-gesture/react@npm:^10.3.1":
-  version: 10.3.1
-  resolution: "@use-gesture/react@npm:10.3.1"
-  dependencies:
-    "@use-gesture/core": "npm:10.3.1"
+    "@eslint-community/eslint-utils": "npm:^4.4.0"
+    "@types/json-schema": "npm:^7.0.12"
+    "@types/semver": "npm:^7.5.0"
+    "@typescript-eslint/scope-manager": "npm:6.21.0"
+    "@typescript-eslint/types": "npm:6.21.0"
+    "@typescript-eslint/typescript-estree": "npm:6.21.0"
+    semver: "npm:^7.5.4"
   peerDependencies:
-    react: ">= 16.8.0"
-  checksum: 10c0/978da66e4e7c424866ad52eba8fdf0ce93a4c8fc44f8837c7043e68c6a6107cd67e817fffb27f7db2ae871ef2f6addb0c8ddf1586f24c67b7e6aef1646c668cf
+    eslint: ^7.0.0 || ^8.0.0
+  checksum: 10c0/ab2df3833b2582d4e5467a484d08942b4f2f7208f8e09d67de510008eb8001a9b7460f2f9ba11c12086fd3cdcac0c626761c7995c2c6b5657d5fa6b82030a32d
+  languageName: node
+  linkType: hard
+
+"@typescript-eslint/visitor-keys@npm:6.21.0":
+  version: 6.21.0
+  resolution: "@typescript-eslint/visitor-keys@npm:6.21.0"
+  dependencies:
+    "@typescript-eslint/types": "npm:6.21.0"
+    eslint-visitor-keys: "npm:^3.4.1"
+  checksum: 10c0/7395f69739cfa1cb83c1fb2fad30afa2a814756367302fb4facd5893eff66abc807e8d8f63eba94ed3b0fe0c1c996ac9a1680bcbf0f83717acedc3f2bb724fbf
+  languageName: node
+  linkType: hard
+
+"@typescript-eslint/visitor-keys@npm:8.1.0":
+  version: 8.1.0
+  resolution: "@typescript-eslint/visitor-keys@npm:8.1.0"
+  dependencies:
+    "@typescript-eslint/types": "npm:8.1.0"
+    eslint-visitor-keys: "npm:^3.4.3"
+  checksum: 10c0/b7544dbb0eec1ddbfcd95c04b51b9a739c2e768c16d1c88508f976a2b0d1bc02fefb7491930e06e48073a5c07c6f488cd8403bba3a8b918888b93a88d5ac3869
+  languageName: node
+  linkType: hard
+
+"@ungap/structured-clone@npm:^1.2.0":
+  version: 1.2.0
+  resolution: "@ungap/structured-clone@npm:1.2.0"
+  checksum: 10c0/8209c937cb39119f44eb63cf90c0b73e7c754209a6411c707be08e50e29ee81356dca1a848a405c8bdeebfe2f5e4f831ad310ae1689eeef65e7445c090c6657d
   languageName: node
   linkType: hard
 
@@ -4911,6 +4580,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"abort-controller@npm:^3.0.0":
+  version: 3.0.0
+  resolution: "abort-controller@npm:3.0.0"
+  dependencies:
+    event-target-shim: "npm:^5.0.0"
+  checksum: 10c0/90ccc50f010250152509a344eb2e71977fbf8db0ab8f1061197e3275ddf6c61a41a6edfd7b9409c664513131dd96e962065415325ef23efa5db931b382d24ca5
+  languageName: node
+  linkType: hard
+
 "accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.8":
   version: 1.3.8
   resolution: "accepts@npm:1.3.8"
@@ -4956,16 +4634,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"acorn@npm:^8.0.4, acorn@npm:^8.1.0, acorn@npm:^8.14.0, acorn@npm:^8.8.1, acorn@npm:^8.8.2":
-  version: 8.14.1
-  resolution: "acorn@npm:8.14.1"
-  bin:
-    acorn: bin/acorn
-  checksum: 10c0/dbd36c1ed1d2fa3550140000371fcf721578095b18777b85a79df231ca093b08edc6858d75d6e48c73e431c174dcf9214edbd7e6fa5911b93bd8abfa54e47123
-  languageName: node
-  linkType: hard
-
-"acorn@npm:^8.12.0":
+"acorn@npm:^8.0.4, acorn@npm:^8.1.0, acorn@npm:^8.12.0, acorn@npm:^8.8.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0":
   version: 8.12.1
   resolution: "acorn@npm:8.12.1"
   bin:
@@ -4983,10 +4652,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.2":
-  version: 7.1.3
-  resolution: "agent-base@npm:7.1.3"
-  checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11
+"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0":
+  version: 7.1.0
+  resolution: "agent-base@npm:7.1.0"
+  dependencies:
+    debug: "npm:^4.3.4"
+  checksum: 10c0/fc974ab57ffdd8421a2bc339644d312a9cca320c20c3393c9d8b1fd91731b9bbabdb985df5fc860f5b79d81c3e350daa3fcb31c5c07c0bb385aafc817df004ce
   languageName: node
   linkType: hard
 
@@ -5179,7 +4850,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"aria-query@npm:5.3.0":
+"aria-query@npm:5.3.0, aria-query@npm:^5.0.0":
   version: 5.3.0
   resolution: "aria-query@npm:5.3.0"
   dependencies:
@@ -5188,10 +4859,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"aria-query@npm:^5.0.0, aria-query@npm:^5.3.2":
-  version: 5.3.2
-  resolution: "aria-query@npm:5.3.2"
-  checksum: 10c0/003c7e3e2cff5540bf7a7893775fc614de82b0c5dde8ae823d47b7a28a9d4da1f7ed85f340bdb93d5649caa927755f0e31ecc7ab63edfdfc00c8ef07e505e03e
+"aria-query@npm:~5.1.3":
+  version: 5.1.3
+  resolution: "aria-query@npm:5.1.3"
+  dependencies:
+    deep-equal: "npm:^2.0.5"
+  checksum: 10c0/edcbc8044c4663d6f88f785e983e6784f98cb62b4ba1e9dd8d61b725d0203e4cfca38d676aee984c31f354103461102a3d583aa4fbe4fd0a89b679744f4e5faf
   languageName: node
   linkType: hard
 
@@ -5216,13 +4889,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2":
-  version: 1.0.2
-  resolution: "array-buffer-byte-length@npm:1.0.2"
+"array-buffer-byte-length@npm:^1.0.0, array-buffer-byte-length@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "array-buffer-byte-length@npm:1.0.1"
   dependencies:
-    call-bound: "npm:^1.0.3"
-    is-array-buffer: "npm:^3.0.5"
-  checksum: 10c0/74e1d2d996941c7a1badda9cabb7caab8c449db9086407cad8a1b71d2604cc8abf105db8ca4e02c04579ec58b7be40279ddb09aea4784832984485499f48432d
+    call-bind: "npm:^1.0.5"
+    is-array-buffer: "npm:^3.0.4"
+  checksum: 10c0/f5cdf54527cd18a3d2852ddf73df79efec03829e7373a8322ef5df2b4ef546fb365c19c71d6b42d641cb6bfe0f1a2f19bc0ece5b533295f86d7c3d522f228917
   languageName: node
   linkType: hard
 
@@ -5324,15 +4997,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"array.prototype.flatmap@npm:^1.3.2, array.prototype.flatmap@npm:^1.3.3":
-  version: 1.3.3
-  resolution: "array.prototype.flatmap@npm:1.3.3"
+"array.prototype.flatmap@npm:^1.3.2":
+  version: 1.3.2
+  resolution: "array.prototype.flatmap@npm:1.3.2"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    define-properties: "npm:^1.2.1"
-    es-abstract: "npm:^1.23.5"
-    es-shim-unscopables: "npm:^1.0.2"
-  checksum: 10c0/ba899ea22b9dc9bf276e773e98ac84638ed5e0236de06f13d63a90b18ca9e0ec7c97d622d899796e3773930b946cd2413d098656c0c5d8cc58c6f25c21e6bd54
+    call-bind: "npm:^1.0.2"
+    define-properties: "npm:^1.2.0"
+    es-abstract: "npm:^1.22.1"
+    es-shim-unscopables: "npm:^1.0.0"
+  checksum: 10c0/67b3f1d602bb73713265145853128b1ad77cc0f9b833c7e1e056b323fbeac41a4ff1c9c99c7b9445903caea924d9ca2450578d9011913191aa88cc3c3a4b54f4
   languageName: node
   linkType: hard
 
@@ -5362,18 +5035,19 @@ __metadata:
   languageName: node
   linkType: hard
 
-"arraybuffer.prototype.slice@npm:^1.0.4":
-  version: 1.0.4
-  resolution: "arraybuffer.prototype.slice@npm:1.0.4"
+"arraybuffer.prototype.slice@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "arraybuffer.prototype.slice@npm:1.0.3"
   dependencies:
     array-buffer-byte-length: "npm:^1.0.1"
-    call-bind: "npm:^1.0.8"
+    call-bind: "npm:^1.0.5"
     define-properties: "npm:^1.2.1"
-    es-abstract: "npm:^1.23.5"
-    es-errors: "npm:^1.3.0"
-    get-intrinsic: "npm:^1.2.6"
+    es-abstract: "npm:^1.22.3"
+    es-errors: "npm:^1.2.1"
+    get-intrinsic: "npm:^1.2.3"
     is-array-buffer: "npm:^3.0.4"
-  checksum: 10c0/2f2459caa06ae0f7f615003f9104b01f6435cc803e11bd2a655107d52a1781dc040532dc44d93026b694cc18793993246237423e13a5337e86b43ed604932c06
+    is-shared-array-buffer: "npm:^1.0.2"
+  checksum: 10c0/d32754045bcb2294ade881d45140a5e52bda2321b9e98fa514797b7f0d252c4c5ab0d1edb34112652c62fa6a9398def568da63a4d7544672229afea283358c36
   languageName: node
   linkType: hard
 
@@ -5531,13 +5205,13 @@ __metadata:
   linkType: hard
 
 "axios@npm:^1.4.0":
-  version: 1.8.2
-  resolution: "axios@npm:1.8.2"
+  version: 1.7.7
+  resolution: "axios@npm:1.7.7"
   dependencies:
     follow-redirects: "npm:^1.15.6"
     form-data: "npm:^4.0.0"
     proxy-from-env: "npm:^1.1.0"
-  checksum: 10c0/d8c2969e4642dc6d39555ac58effe06c051ba7aac2bd40cad7a9011c019fb2f16ee011c5a6906cb25b8a4f87258c359314eb981f852e60ad445ecaeb793c7aa2
+  checksum: 10c0/4499efc89e86b0b49ffddc018798de05fab26e3bf57913818266be73279a6418c3ce8f9e934c7d2d707ab8c095e837fc6c90608fb7715b94d357720b5f568af7
   languageName: node
   linkType: hard
 
@@ -5580,22 +5254,22 @@ __metadata:
   languageName: node
   linkType: hard
 
-"babel-plugin-formatjs@npm:^10.5.37":
-  version: 10.5.37
-  resolution: "babel-plugin-formatjs@npm:10.5.37"
+"babel-plugin-formatjs@npm:^10.5.1":
+  version: 10.5.16
+  resolution: "babel-plugin-formatjs@npm:10.5.16"
   dependencies:
-    "@babel/core": "npm:^7.26.10"
-    "@babel/helper-plugin-utils": "npm:^7.26.5"
-    "@babel/plugin-syntax-jsx": "npm:^7.25.9"
-    "@babel/traverse": "npm:^7.26.10"
-    "@babel/types": "npm:^7.26.10"
-    "@formatjs/icu-messageformat-parser": "npm:2.11.2"
-    "@formatjs/ts-transformer": "npm:3.13.34"
-    "@types/babel__core": "npm:^7.20.5"
-    "@types/babel__helper-plugin-utils": "npm:^7.10.3"
-    "@types/babel__traverse": "npm:^7.20.6"
-    tslib: "npm:^2.8.0"
-  checksum: 10c0/e206ff1a8ad3cbcb3db2d2735d8821701df9d54c8aeb5e8b2861c945af91d4662b9cd37b1ff9d7e17954cdd31aec81788a3d044a1cd9f3e7e8e4f93177097b83
+    "@babel/core": "npm:^7.10.4"
+    "@babel/helper-plugin-utils": "npm:^7.10.4"
+    "@babel/plugin-syntax-jsx": "npm:7"
+    "@babel/traverse": "npm:7"
+    "@babel/types": "npm:^7.12.11"
+    "@formatjs/icu-messageformat-parser": "npm:2.7.8"
+    "@formatjs/ts-transformer": "npm:3.13.14"
+    "@types/babel__core": "npm:^7.1.7"
+    "@types/babel__helper-plugin-utils": "npm:^7.10.0"
+    "@types/babel__traverse": "npm:^7.1.7"
+    tslib: "npm:^2.4.0"
+  checksum: 10c0/03d9d2b0b9cdc05c011bfb417a43e5c0f557868ed84d83acbc3cb9072b7fa98f5219473d0bd61f02741c151d6f2162da363bd337522c80af14721ae37f6da86b
   languageName: node
   linkType: hard
 
@@ -5686,18 +5360,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"babel-plugin-polyfill-corejs3@npm:^0.11.0":
-  version: 0.11.1
-  resolution: "babel-plugin-polyfill-corejs3@npm:0.11.1"
-  dependencies:
-    "@babel/helper-define-polyfill-provider": "npm:^0.6.3"
-    core-js-compat: "npm:^3.40.0"
-  peerDependencies:
-    "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0
-  checksum: 10c0/025f754b6296d84b20200aff63a3c1acdd85e8c621781f2bd27fe2512d0060526192d02329326947c6b29c27cf475fbcfaaff8c51eab1d2bfc7b79086bb64229
-  languageName: node
-  linkType: hard
-
 "babel-plugin-polyfill-regenerator@npm:^0.6.1":
   version: 0.6.1
   resolution: "babel-plugin-polyfill-regenerator@npm:0.6.1"
@@ -5776,7 +5438,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"base64-js@npm:^1.0.2":
+"base64-js@npm:^1.0.2, base64-js@npm:^1.3.1":
   version: 1.5.1
   resolution: "base64-js@npm:1.5.1"
   checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf
@@ -5842,6 +5504,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"blueimp-load-image@npm:^3.0.0":
+  version: 3.0.0
+  resolution: "blueimp-load-image@npm:3.0.0"
+  checksum: 10c0/e860da4113afd8e58bc026fb17240007e15dc155287a70fb57b3048fc8f0aa5f7dbd052efed8bff19d1208eeab4d058dc6788684a721c50ccd08b68d836a8d18
+  languageName: node
+  linkType: hard
+
 "blurhash@npm:^2.0.5":
   version: 2.0.5
   resolution: "blurhash@npm:2.0.5"
@@ -6037,17 +5706,17 @@ __metadata:
   languageName: node
   linkType: hard
 
-"browserslist@npm:^4.0.0, browserslist@npm:^4.23.0, browserslist@npm:^4.23.3, browserslist@npm:^4.24.0, browserslist@npm:^4.24.3, browserslist@npm:^4.24.4":
-  version: 4.24.4
-  resolution: "browserslist@npm:4.24.4"
+"browserslist@npm:^4.0.0, browserslist@npm:^4.23.0, browserslist@npm:^4.23.1, browserslist@npm:^4.23.3":
+  version: 4.23.3
+  resolution: "browserslist@npm:4.23.3"
   dependencies:
-    caniuse-lite: "npm:^1.0.30001688"
-    electron-to-chromium: "npm:^1.5.73"
-    node-releases: "npm:^2.0.19"
-    update-browserslist-db: "npm:^1.1.1"
+    caniuse-lite: "npm:^1.0.30001646"
+    electron-to-chromium: "npm:^1.5.4"
+    node-releases: "npm:^2.0.18"
+    update-browserslist-db: "npm:^1.1.0"
   bin:
     browserslist: cli.js
-  checksum: 10c0/db7ebc1733cf471e0b490b4f47e3e2ea2947ce417192c9246644e92c667dd56a71406cc58f62ca7587caf828364892e9952904a02b7aead752bc65b62a37cfe9
+  checksum: 10c0/3063bfdf812815346447f4796c8f04601bf5d62003374305fd323c2a463e42776475bcc5309264e39bcf9a8605851e53560695991a623be988138b3ff8c66642
   languageName: node
   linkType: hard
 
@@ -6092,13 +5761,23 @@ __metadata:
   languageName: node
   linkType: hard
 
+"buffer@npm:^6.0.3":
+  version: 6.0.3
+  resolution: "buffer@npm:6.0.3"
+  dependencies:
+    base64-js: "npm:^1.3.1"
+    ieee754: "npm:^1.2.1"
+  checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0
+  languageName: node
+  linkType: hard
+
 "bufferutil@npm:^4.0.7":
-  version: 4.0.9
-  resolution: "bufferutil@npm:4.0.9"
+  version: 4.0.8
+  resolution: "bufferutil@npm:4.0.8"
   dependencies:
     node-gyp: "npm:latest"
     node-gyp-build: "npm:^4.3.0"
-  checksum: 10c0/f8a93279fc9bdcf32b42eba97edc672b39ca0fe5c55a8596099886cffc76ea9dd78e0f6f51ecee3b5ee06d2d564aa587036b5d4ea39b8b5ac797262a363cdf7d
+  checksum: 10c0/36cdc5b53a38d9f61f89fdbe62029a2ebcd020599862253fefebe31566155726df9ff961f41b8c97b02b4c12b391ef97faf94e2383392654cf8f0ed68f76e47c
   languageName: node
   linkType: hard
 
@@ -6193,35 +5872,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2":
-  version: 1.0.2
-  resolution: "call-bind-apply-helpers@npm:1.0.2"
+"call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7":
+  version: 1.0.7
+  resolution: "call-bind@npm:1.0.7"
   dependencies:
+    es-define-property: "npm:^1.0.0"
     es-errors: "npm:^1.3.0"
     function-bind: "npm:^1.1.2"
-  checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938
-  languageName: node
-  linkType: hard
-
-"call-bind@npm:^1.0.2, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8":
-  version: 1.0.8
-  resolution: "call-bind@npm:1.0.8"
-  dependencies:
-    call-bind-apply-helpers: "npm:^1.0.0"
-    es-define-property: "npm:^1.0.0"
     get-intrinsic: "npm:^1.2.4"
-    set-function-length: "npm:^1.2.2"
-  checksum: 10c0/a13819be0681d915144467741b69875ae5f4eba8961eb0bf322aab63ec87f8250eb6d6b0dcbb2e1349876412a56129ca338592b3829ef4343527f5f18a0752d4
-  languageName: node
-  linkType: hard
-
-"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3":
-  version: 1.0.4
-  resolution: "call-bound@npm:1.0.4"
-  dependencies:
-    call-bind-apply-helpers: "npm:^1.0.2"
-    get-intrinsic: "npm:^1.3.0"
-  checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644
+    set-function-length: "npm:^1.2.1"
+  checksum: 10c0/a3ded2e423b8e2a265983dba81c27e125b48eefb2655e7dfab6be597088da3d47c47976c24bc51b8fd9af1061f8f87b4ab78a314f3c77784b2ae2ba535ad8b8d
   languageName: node
   linkType: hard
 
@@ -6258,10 +5918,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001599, caniuse-lite@npm:^1.0.30001688":
-  version: 1.0.30001699
-  resolution: "caniuse-lite@npm:1.0.30001699"
-  checksum: 10c0/e87b3a0602c3124131f6a21f1eb262378e17a2ee3089e3c472ac8b9caa85cf7d6a219655379302c29c6f10a74051f2a712639d7f98ee0444c73fefcbaf25d519
+"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001599, caniuse-lite@npm:^1.0.30001646":
+  version: 1.0.30001651
+  resolution: "caniuse-lite@npm:1.0.30001651"
+  checksum: 10c0/7821278952a6dbd17358e5d08083d258f092e2a530f5bc1840657cb140fbbc5ec44293bc888258c44a18a9570cde149ed05819ac8320b9710cf22f699891e6ad
   languageName: node
   linkType: hard
 
@@ -6286,7 +5946,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"chalk@npm:^4.0, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.2":
+"chalk@npm:^4.0, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0":
   version: 4.1.2
   resolution: "chalk@npm:4.1.2"
   dependencies:
@@ -6588,6 +6248,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"colors@npm:^1.4.0":
+  version: 1.4.0
+  resolution: "colors@npm:1.4.0"
+  checksum: 10c0/9af357c019da3c5a098a301cf64e3799d27549d8f185d86f79af23069e4f4303110d115da98483519331f6fb71c8568d5688fa1c6523600044fd4a54e97c4efb
+  languageName: node
+  linkType: hard
+
 "combined-stream@npm:^1.0.8":
   version: 1.0.8
   resolution: "combined-stream@npm:1.0.8"
@@ -6750,10 +6417,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"cookie@npm:0.7.1":
-  version: 0.7.1
-  resolution: "cookie@npm:0.7.1"
-  checksum: 10c0/5de60c67a410e7c8dc8a46a4b72eb0fe925871d057c9a5d2c0e8145c4270a4f81076de83410c4d397179744b478e33cd80ccbcc457abf40a9409ad27dcd21dde
+"cookie@npm:0.6.0":
+  version: 0.6.0
+  resolution: "cookie@npm:0.6.0"
+  checksum: 10c0/f2318b31af7a31b4ddb4a678d024514df5e705f9be5909a192d7f116cfb6d45cbacf96a473fa733faa95050e7cff26e7832bb3ef94751592f1387b71c8956686
   languageName: node
   linkType: hard
 
@@ -6764,12 +6431,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"core-js-compat@npm:^3.38.0, core-js-compat@npm:^3.40.0":
-  version: 3.40.0
-  resolution: "core-js-compat@npm:3.40.0"
+"core-js-compat@npm:^3.37.1, core-js-compat@npm:^3.38.0":
+  version: 3.38.1
+  resolution: "core-js-compat@npm:3.38.1"
   dependencies:
-    browserslist: "npm:^4.24.3"
-  checksum: 10c0/44f6e88726fe266a5be9581a79766800478a8d5c492885f2d4c2a4e2babd9b06bc1689d5340d3a61ae7332f990aff2e83b6203ff8773137a627cfedfbeefabeb
+    browserslist: "npm:^4.23.3"
+  checksum: 10c0/d8bc8a35591fc5fbf3e376d793f298ec41eb452619c7ef9de4ea59b74be06e9fda799e0dcbf9ba59880dae87e3b41fb191d744ffc988315642a1272bb9442b31
   languageName: node
   linkType: hard
 
@@ -6781,9 +6448,9 @@ __metadata:
   linkType: hard
 
 "core-js@npm:^3.30.2":
-  version: 3.41.0
-  resolution: "core-js@npm:3.41.0"
-  checksum: 10c0/a29ed0b7fe81acf49d04ce5c17a1947166b1c15197327a5d12f95bbe84b46d60c3c13de701d808f41da06fa316285f3f55ce5903abc8d5642afc1eac4457afc8
+  version: 3.38.1
+  resolution: "core-js@npm:3.38.1"
+  checksum: 10c0/7df063b6f13a54e46515817ac3e235c6c598a4d3de65cd188a061fc250642be313b895fb9fb2f36e1e31890a1bb4ef61d82666a340413f540b7ce3c65689739b
   languageName: node
   linkType: hard
 
@@ -6913,14 +6580,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6":
-  version: 7.0.6
-  resolution: "cross-spawn@npm:7.0.6"
+"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
+  version: 7.0.3
+  resolution: "cross-spawn@npm:7.0.3"
   dependencies:
     path-key: "npm:^3.1.0"
     shebang-command: "npm:^2.0.0"
     which: "npm:^2.0.1"
-  checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1
+  checksum: 10c0/5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750
   languageName: node
   linkType: hard
 
@@ -6950,14 +6617,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"css-blank-pseudo@npm:^7.0.1":
-  version: 7.0.1
-  resolution: "css-blank-pseudo@npm:7.0.1"
+"css-blank-pseudo@npm:^7.0.0":
+  version: 7.0.0
+  resolution: "css-blank-pseudo@npm:7.0.0"
   dependencies:
-    postcss-selector-parser: "npm:^7.0.0"
+    postcss-selector-parser: "npm:^6.1.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/46c3d3a611972fdb0c264db7c0b34fe437bc4300961d11945145cf04962f52a545a6ef55bc8ff4afd82b605bd692b4970f2b54582616dea00441105e725d4618
+  checksum: 10c0/74c6c0af773a8d2c8c5a53bcfc2b2c06f9c3fd4a8bd756b7aafc102b91a1060b179a4f0aa21475b54685b62bfd9724fee90778dd992b42e0cd3ea3698132af92
   languageName: node
   linkType: hard
 
@@ -6979,23 +6646,23 @@ __metadata:
   languageName: node
   linkType: hard
 
-"css-functions-list@npm:^3.2.3":
-  version: 3.2.3
-  resolution: "css-functions-list@npm:3.2.3"
-  checksum: 10c0/03f9ed34eeed310d2b1cf0e524eea02bc5f87854a4de85f8957ea432ab1036841a3fb00879590519f7bb8fda40d992ce7a72fa9b61696ca1dc53b90064858f96
+"css-functions-list@npm:^3.2.2":
+  version: 3.2.2
+  resolution: "css-functions-list@npm:3.2.2"
+  checksum: 10c0/8638a63d0cf1bdc50d4a752ec1c94a57e9953c3b03eace4f5526db20bec3c061e95089f905dbb4999c44b9780ce777ba856967560f6d15119a303f6030901c10
   languageName: node
   linkType: hard
 
-"css-has-pseudo@npm:^7.0.2":
-  version: 7.0.2
-  resolution: "css-has-pseudo@npm:7.0.2"
+"css-has-pseudo@npm:^7.0.0":
+  version: 7.0.0
+  resolution: "css-has-pseudo@npm:7.0.0"
   dependencies:
-    "@csstools/selector-specificity": "npm:^5.0.0"
-    postcss-selector-parser: "npm:^7.0.0"
+    "@csstools/selector-specificity": "npm:^4.0.0"
+    postcss-selector-parser: "npm:^6.1.0"
     postcss-value-parser: "npm:^4.2.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/456e9ce1eec8a535683c329956acfe53ce5a208345d7f2fcbe662626be8b3c98681e9041d7f4980316714397b0c1c3defde25653d629c396df17803d599c4edf
+  checksum: 10c0/2c72602ca9bcdb3afe2cce3b014e7dd17548658904c17560042ebf4bd6727b1ed8706961b1f44bff43bbdb8dc932c30a0b29f536c353df858e300e68e163b872
   languageName: node
   linkType: hard
 
@@ -7090,16 +6757,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"css-tree@npm:^3.0.1":
-  version: 3.0.1
-  resolution: "css-tree@npm:3.0.1"
-  dependencies:
-    mdn-data: "npm:2.12.1"
-    source-map-js: "npm:^1.0.1"
-  checksum: 10c0/9f117f3067e68e9edb0b3db0134f420db1a62bede3e84d8835767ecfaa6f8ced5e87989cf39b65ffe65d788c134c8ea9abd7393d7c35838a9da84326adf57a9b
-  languageName: node
-  linkType: hard
-
 "css-tree@npm:~2.2.0":
   version: 2.2.1
   resolution: "css-tree@npm:2.2.1"
@@ -7131,10 +6788,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"cssdb@npm:^8.2.3":
-  version: 8.2.3
-  resolution: "cssdb@npm:8.2.3"
-  checksum: 10c0/17c3ca6432ed02431db6b44bed74649ccef7d7b7b900ccbc7297525f030722c441dd67c71f28aef3cfa0814ba7b254a24adfb0dcd5728937da179ff437cdcd0c
+"cssdb@npm:^8.1.1":
+  version: 8.1.1
+  resolution: "cssdb@npm:8.1.1"
+  checksum: 10c0/d60facfad3bca70e21100fc35b9205cb9d3d0ac642f44f0a687e54bf787f21c43d28ce2d17fcd405f67950fb4709516108fe1f3cb15df570eff1007b5fbbc787
   languageName: node
   linkType: hard
 
@@ -7249,13 +6906,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"cssstyle@npm:^4.2.1":
-  version: 4.2.1
-  resolution: "cssstyle@npm:4.2.1"
+"cssstyle@npm:^4.1.0":
+  version: 4.1.0
+  resolution: "cssstyle@npm:4.1.0"
   dependencies:
-    "@asamuzakjp/css-color": "npm:^2.8.2"
-    rrweb-cssom: "npm:^0.8.0"
-  checksum: 10c0/02ba8c47c0caaab57acadacb3eb6c0f5f009000f55d61f6563670e07d389b26edefeed497e6c1847fcd2e6bbe0b6974c2d4291f97fa0c6ec6add13a7fa926d84
+    rrweb-cssom: "npm:^0.7.1"
+  checksum: 10c0/05c6597e5d3e0ec6b15221f2c0ce9a0443a46cc50a6089a3ba9ee1ac27f83ff86a445a8f95435137dadd859f091fc61b6d342abaf396d3c910471b5b33cfcbfa
   languageName: node
   linkType: hard
 
@@ -7294,36 +6950,36 @@ __metadata:
   languageName: node
   linkType: hard
 
-"data-view-buffer@npm:^1.0.2":
-  version: 1.0.2
-  resolution: "data-view-buffer@npm:1.0.2"
-  dependencies:
-    call-bound: "npm:^1.0.3"
-    es-errors: "npm:^1.3.0"
-    is-data-view: "npm:^1.0.2"
-  checksum: 10c0/7986d40fc7979e9e6241f85db8d17060dd9a71bd53c894fa29d126061715e322a4cd47a00b0b8c710394854183d4120462b980b8554012acc1c0fa49df7ad38c
-  languageName: node
-  linkType: hard
-
-"data-view-byte-length@npm:^1.0.2":
-  version: 1.0.2
-  resolution: "data-view-byte-length@npm:1.0.2"
-  dependencies:
-    call-bound: "npm:^1.0.3"
-    es-errors: "npm:^1.3.0"
-    is-data-view: "npm:^1.0.2"
-  checksum: 10c0/f8a4534b5c69384d95ac18137d381f18a5cfae1f0fc1df0ef6feef51ef0d568606d970b69e02ea186c6c0f0eac77fe4e6ad96fec2569cc86c3afcc7475068c55
-  languageName: node
-  linkType: hard
-
-"data-view-byte-offset@npm:^1.0.1":
+"data-view-buffer@npm:^1.0.1":
   version: 1.0.1
-  resolution: "data-view-byte-offset@npm:1.0.1"
+  resolution: "data-view-buffer@npm:1.0.1"
   dependencies:
-    call-bound: "npm:^1.0.2"
+    call-bind: "npm:^1.0.6"
     es-errors: "npm:^1.3.0"
     is-data-view: "npm:^1.0.1"
-  checksum: 10c0/fa7aa40078025b7810dcffc16df02c480573b7b53ef1205aa6a61533011005c1890e5ba17018c692ce7c900212b547262d33279fde801ad9843edc0863bf78c4
+  checksum: 10c0/8984119e59dbed906a11fcfb417d7d861936f16697a0e7216fe2c6c810f6b5e8f4a5281e73f2c28e8e9259027190ac4a33e2a65fdd7fa86ac06b76e838918583
+  languageName: node
+  linkType: hard
+
+"data-view-byte-length@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "data-view-byte-length@npm:1.0.1"
+  dependencies:
+    call-bind: "npm:^1.0.7"
+    es-errors: "npm:^1.3.0"
+    is-data-view: "npm:^1.0.1"
+  checksum: 10c0/b7d9e48a0cf5aefed9ab7d123559917b2d7e0d65531f43b2fd95b9d3a6b46042dd3fca597c42bba384e66b70d7ad66ff23932f8367b241f53d93af42cfe04ec2
+  languageName: node
+  linkType: hard
+
+"data-view-byte-offset@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "data-view-byte-offset@npm:1.0.0"
+  dependencies:
+    call-bind: "npm:^1.0.6"
+    es-errors: "npm:^1.3.0"
+    is-data-view: "npm:^1.0.1"
+  checksum: 10c0/21b0d2e53fd6e20cc4257c873bf6d36d77bd6185624b84076c0a1ddaa757b49aaf076254006341d35568e89f52eecd1ccb1a502cfb620f2beca04f48a6a62a8f
   languageName: node
   linkType: hard
 
@@ -7350,15 +7006,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:~4.4.0":
-  version: 4.4.0
-  resolution: "debug@npm:4.4.0"
+"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:~4.3.6":
+  version: 4.3.6
+  resolution: "debug@npm:4.3.6"
   dependencies:
-    ms: "npm:^2.1.3"
+    ms: "npm:2.1.2"
   peerDependenciesMeta:
     supports-color:
       optional: true
-  checksum: 10c0/db94f1a182bf886f57b4755f85b3a74c39b5114b9377b7ab375dc2cfa3454f09490cc6c30f829df3fc8042bc8b8995f6567ce5cd96f3bc3688bd24027197d9de
+  checksum: 10c0/3293416bff072389c101697d4611c402a6bacd1900ac20c0492f61a9cdd6b3b29750fc7f5e299f8058469ef60ff8fb79b86395a30374fbd2490113c1c7112285
   languageName: node
   linkType: hard
 
@@ -7378,10 +7034,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"decimal.js@npm:^10.4.2, decimal.js@npm:^10.4.3, decimal.js@npm:^10.5.0":
-  version: 10.5.0
-  resolution: "decimal.js@npm:10.5.0"
-  checksum: 10c0/785c35279df32762143914668df35948920b6c1c259b933e0519a69b7003fc0a5ed2a766b1e1dda02574450c566b21738a45f15e274b47c2ac02072c0d1f3ac3
+"decimal.js@npm:^10.4.2, decimal.js@npm:^10.4.3":
+  version: 10.4.3
+  resolution: "decimal.js@npm:10.4.3"
+  checksum: 10c0/6d60206689ff0911f0ce968d40f163304a6c1bc739927758e6efc7921cfa630130388966f16bf6ef6b838cb33679fbe8e7a78a2f3c478afce841fd55ac8fb8ee
   languageName: node
   linkType: hard
 
@@ -7418,6 +7074,32 @@ __metadata:
   languageName: node
   linkType: hard
 
+"deep-equal@npm:^2.0.5":
+  version: 2.2.3
+  resolution: "deep-equal@npm:2.2.3"
+  dependencies:
+    array-buffer-byte-length: "npm:^1.0.0"
+    call-bind: "npm:^1.0.5"
+    es-get-iterator: "npm:^1.1.3"
+    get-intrinsic: "npm:^1.2.2"
+    is-arguments: "npm:^1.1.1"
+    is-array-buffer: "npm:^3.0.2"
+    is-date-object: "npm:^1.0.5"
+    is-regex: "npm:^1.1.4"
+    is-shared-array-buffer: "npm:^1.0.2"
+    isarray: "npm:^2.0.5"
+    object-is: "npm:^1.1.5"
+    object-keys: "npm:^1.1.1"
+    object.assign: "npm:^4.1.4"
+    regexp.prototype.flags: "npm:^1.5.1"
+    side-channel: "npm:^1.0.4"
+    which-boxed-primitive: "npm:^1.0.2"
+    which-collection: "npm:^1.0.1"
+    which-typed-array: "npm:^1.1.13"
+  checksum: 10c0/a48244f90fa989f63ff5ef0cc6de1e4916b48ea0220a9c89a378561960814794a5800c600254482a2c8fd2e49d6c2e196131dc983976adb024c94a42dfe4949f
+  languageName: node
+  linkType: hard
+
 "deep-is@npm:^0.1.3":
   version: 0.1.4
   resolution: "deep-is@npm:0.1.4"
@@ -7442,7 +7124,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4":
+"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.2, define-data-property@npm:^1.1.4":
   version: 1.1.4
   resolution: "define-data-property@npm:1.1.4"
   dependencies:
@@ -7573,15 +7255,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"detect-libc@npm:^1.0.3":
-  version: 1.0.3
-  resolution: "detect-libc@npm:1.0.3"
-  bin:
-    detect-libc: ./bin/detect-libc.js
-  checksum: 10c0/4da0deae9f69e13bc37a0902d78bf7169480004b1fed3c19722d56cff578d16f0e11633b7fbf5fb6249181236c72e90024cbd68f0b9558ae06e281f47326d50d
-  languageName: node
-  linkType: hard
-
 "detect-newline@npm:^3.0.0":
   version: 3.1.0
   resolution: "detect-newline@npm:3.1.0"
@@ -7667,6 +7340,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"doctrine@npm:^3.0.0":
+  version: 3.0.0
+  resolution: "doctrine@npm:3.0.0"
+  dependencies:
+    esutils: "npm:^2.0.2"
+  checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520
+  languageName: node
+  linkType: hard
+
 "dom-accessibility-api@npm:^0.5.9":
   version: 0.5.16
   resolution: "dom-accessibility-api@npm:0.5.16"
@@ -7782,20 +7464,9 @@ __metadata:
   linkType: hard
 
 "dotenv@npm:^16.0.3":
-  version: 16.4.7
-  resolution: "dotenv@npm:16.4.7"
-  checksum: 10c0/be9f597e36a8daf834452daa1f4cc30e5375a5968f98f46d89b16b983c567398a330580c88395069a77473943c06b877d1ca25b4afafcdd6d4adb549e8293462
-  languageName: node
-  linkType: hard
-
-"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "dunder-proto@npm:1.0.1"
-  dependencies:
-    call-bind-apply-helpers: "npm:^1.0.1"
-    es-errors: "npm:^1.3.0"
-    gopd: "npm:^1.2.0"
-  checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031
+  version: 16.4.5
+  resolution: "dotenv@npm:16.4.5"
+  checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f
   languageName: node
   linkType: hard
 
@@ -7831,10 +7502,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"electron-to-chromium@npm:^1.5.73":
-  version: 1.5.96
-  resolution: "electron-to-chromium@npm:1.5.96"
-  checksum: 10c0/827d480f35abe8b0d01a4311fc3180089a406edfcd016d8433712b03ec6e56618ad6f9757e35403092de5c2e163372f9ec90eb8e8334f6f26119a21b568d9bf9
+"electron-to-chromium@npm:^1.5.4":
+  version: 1.5.7
+  resolution: "electron-to-chromium@npm:1.5.7"
+  checksum: 10c0/be4460bbe3d2186a16d53a03da67fde6fd06ad41943553ce517a45d52e03424732a982f75528e8a2d5fb042d6afde64186aa482caec0fb925daa5a74cf5ef060
   languageName: node
   linkType: hard
 
@@ -7873,7 +7544,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"emoji-regex@npm:10.3.0, emoji-regex@npm:^10.3.0":
+"emoji-regex@npm:10.3.0, emoji-regex@npm:^10.2.1, emoji-regex@npm:^10.3.0":
   version: 10.3.0
   resolution: "emoji-regex@npm:10.3.0"
   checksum: 10c0/b4838e8dcdceb44cf47f59abe352c25ff4fe7857acaf5fb51097c427f6f75b44d052eb907a7a3b86f86bc4eae3a93f5c2b7460abe79c407307e6212d65c91163
@@ -7951,6 +7622,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"enhanced-resolve@npm:^5.15.0":
+  version: 5.17.1
+  resolution: "enhanced-resolve@npm:5.17.1"
+  dependencies:
+    graceful-fs: "npm:^4.2.4"
+    tapable: "npm:^2.2.0"
+  checksum: 10c0/81a0515675eca17efdba2cf5bad87abc91a528fc1191aad50e275e74f045b41506167d420099022da7181c8d787170ea41e4a11a0b10b7a16f6237daecb15370
+  languageName: node
+  linkType: hard
+
 "entities@npm:^2.0.0":
   version: 2.2.0
   resolution: "entities@npm:2.2.0"
@@ -7958,7 +7639,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"entities@npm:^4.2.0, entities@npm:^4.5.0":
+"entities@npm:^4.2.0, entities@npm:^4.4.0":
   version: 4.5.0
   resolution: "entities@npm:4.5.0"
   checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250
@@ -8015,62 +7696,57 @@ __metadata:
   languageName: node
   linkType: hard
 
-"es-abstract@npm:^1.17.2, es-abstract@npm:^1.17.5, es-abstract@npm:^1.20.4, es-abstract@npm:^1.21.2, es-abstract@npm:^1.22.1, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9":
-  version: 1.23.9
-  resolution: "es-abstract@npm:1.23.9"
+"es-abstract@npm:^1.17.2, es-abstract@npm:^1.17.5, es-abstract@npm:^1.20.4, es-abstract@npm:^1.21.2, es-abstract@npm:^1.22.1, es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3":
+  version: 1.23.3
+  resolution: "es-abstract@npm:1.23.3"
   dependencies:
-    array-buffer-byte-length: "npm:^1.0.2"
-    arraybuffer.prototype.slice: "npm:^1.0.4"
+    array-buffer-byte-length: "npm:^1.0.1"
+    arraybuffer.prototype.slice: "npm:^1.0.3"
     available-typed-arrays: "npm:^1.0.7"
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.3"
-    data-view-buffer: "npm:^1.0.2"
-    data-view-byte-length: "npm:^1.0.2"
-    data-view-byte-offset: "npm:^1.0.1"
-    es-define-property: "npm:^1.0.1"
+    call-bind: "npm:^1.0.7"
+    data-view-buffer: "npm:^1.0.1"
+    data-view-byte-length: "npm:^1.0.1"
+    data-view-byte-offset: "npm:^1.0.0"
+    es-define-property: "npm:^1.0.0"
     es-errors: "npm:^1.3.0"
     es-object-atoms: "npm:^1.0.0"
-    es-set-tostringtag: "npm:^2.1.0"
-    es-to-primitive: "npm:^1.3.0"
-    function.prototype.name: "npm:^1.1.8"
-    get-intrinsic: "npm:^1.2.7"
-    get-proto: "npm:^1.0.0"
-    get-symbol-description: "npm:^1.1.0"
-    globalthis: "npm:^1.0.4"
-    gopd: "npm:^1.2.0"
+    es-set-tostringtag: "npm:^2.0.3"
+    es-to-primitive: "npm:^1.2.1"
+    function.prototype.name: "npm:^1.1.6"
+    get-intrinsic: "npm:^1.2.4"
+    get-symbol-description: "npm:^1.0.2"
+    globalthis: "npm:^1.0.3"
+    gopd: "npm:^1.0.1"
     has-property-descriptors: "npm:^1.0.2"
-    has-proto: "npm:^1.2.0"
-    has-symbols: "npm:^1.1.0"
+    has-proto: "npm:^1.0.3"
+    has-symbols: "npm:^1.0.3"
     hasown: "npm:^2.0.2"
-    internal-slot: "npm:^1.1.0"
-    is-array-buffer: "npm:^3.0.5"
+    internal-slot: "npm:^1.0.7"
+    is-array-buffer: "npm:^3.0.4"
     is-callable: "npm:^1.2.7"
-    is-data-view: "npm:^1.0.2"
-    is-regex: "npm:^1.2.1"
-    is-shared-array-buffer: "npm:^1.0.4"
-    is-string: "npm:^1.1.1"
-    is-typed-array: "npm:^1.1.15"
-    is-weakref: "npm:^1.1.0"
-    math-intrinsics: "npm:^1.1.0"
-    object-inspect: "npm:^1.13.3"
+    is-data-view: "npm:^1.0.1"
+    is-negative-zero: "npm:^2.0.3"
+    is-regex: "npm:^1.1.4"
+    is-shared-array-buffer: "npm:^1.0.3"
+    is-string: "npm:^1.0.7"
+    is-typed-array: "npm:^1.1.13"
+    is-weakref: "npm:^1.0.2"
+    object-inspect: "npm:^1.13.1"
     object-keys: "npm:^1.1.1"
-    object.assign: "npm:^4.1.7"
-    own-keys: "npm:^1.0.1"
-    regexp.prototype.flags: "npm:^1.5.3"
-    safe-array-concat: "npm:^1.1.3"
-    safe-push-apply: "npm:^1.0.0"
-    safe-regex-test: "npm:^1.1.0"
-    set-proto: "npm:^1.0.0"
-    string.prototype.trim: "npm:^1.2.10"
-    string.prototype.trimend: "npm:^1.0.9"
+    object.assign: "npm:^4.1.5"
+    regexp.prototype.flags: "npm:^1.5.2"
+    safe-array-concat: "npm:^1.1.2"
+    safe-regex-test: "npm:^1.0.3"
+    string.prototype.trim: "npm:^1.2.9"
+    string.prototype.trimend: "npm:^1.0.8"
     string.prototype.trimstart: "npm:^1.0.8"
-    typed-array-buffer: "npm:^1.0.3"
-    typed-array-byte-length: "npm:^1.0.3"
-    typed-array-byte-offset: "npm:^1.0.4"
-    typed-array-length: "npm:^1.0.7"
-    unbox-primitive: "npm:^1.1.0"
-    which-typed-array: "npm:^1.1.18"
-  checksum: 10c0/1de229c9e08fe13c17fe5abaec8221545dfcd57e51f64909599a6ae896df84b8fd2f7d16c60cb00d7bf495b9298ca3581aded19939d4b7276854a4b066f8422b
+    typed-array-buffer: "npm:^1.0.2"
+    typed-array-byte-length: "npm:^1.0.1"
+    typed-array-byte-offset: "npm:^1.0.2"
+    typed-array-length: "npm:^1.0.6"
+    unbox-primitive: "npm:^1.0.2"
+    which-typed-array: "npm:^1.1.15"
+  checksum: 10c0/d27e9afafb225c6924bee9971a7f25f20c314f2d6cb93a63cada4ac11dcf42040896a6c22e5fb8f2a10767055ed4ddf400be3b1eb12297d281726de470b75666
   languageName: node
   linkType: hard
 
@@ -8081,41 +7757,58 @@ __metadata:
   languageName: node
   linkType: hard
 
-"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "es-define-property@npm:1.0.1"
-  checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c
+"es-define-property@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "es-define-property@npm:1.0.0"
+  dependencies:
+    get-intrinsic: "npm:^1.2.4"
+  checksum: 10c0/6bf3191feb7ea2ebda48b577f69bdfac7a2b3c9bcf97307f55fd6ef1bbca0b49f0c219a935aca506c993d8c5d8bddd937766cb760cd5e5a1071351f2df9f9aa4
   languageName: node
   linkType: hard
 
-"es-errors@npm:^1.3.0":
+"es-errors@npm:^1.2.1, es-errors@npm:^1.3.0":
   version: 1.3.0
   resolution: "es-errors@npm:1.3.0"
   checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85
   languageName: node
   linkType: hard
 
-"es-iterator-helpers@npm:^1.2.1":
-  version: 1.2.1
-  resolution: "es-iterator-helpers@npm:1.2.1"
+"es-get-iterator@npm:^1.1.3":
+  version: 1.1.3
+  resolution: "es-get-iterator@npm:1.1.3"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.3"
+    call-bind: "npm:^1.0.2"
+    get-intrinsic: "npm:^1.1.3"
+    has-symbols: "npm:^1.0.3"
+    is-arguments: "npm:^1.1.1"
+    is-map: "npm:^2.0.2"
+    is-set: "npm:^2.0.2"
+    is-string: "npm:^1.0.7"
+    isarray: "npm:^2.0.5"
+    stop-iteration-iterator: "npm:^1.0.0"
+  checksum: 10c0/ebd11effa79851ea75d7f079405f9d0dc185559fd65d986c6afea59a0ff2d46c2ed8675f19f03dce7429d7f6c14ff9aede8d121fbab78d75cfda6a263030bac0
+  languageName: node
+  linkType: hard
+
+"es-iterator-helpers@npm:^1.0.19":
+  version: 1.0.19
+  resolution: "es-iterator-helpers@npm:1.0.19"
+  dependencies:
+    call-bind: "npm:^1.0.7"
     define-properties: "npm:^1.2.1"
-    es-abstract: "npm:^1.23.6"
+    es-abstract: "npm:^1.23.3"
     es-errors: "npm:^1.3.0"
     es-set-tostringtag: "npm:^2.0.3"
     function-bind: "npm:^1.1.2"
-    get-intrinsic: "npm:^1.2.6"
-    globalthis: "npm:^1.0.4"
-    gopd: "npm:^1.2.0"
+    get-intrinsic: "npm:^1.2.4"
+    globalthis: "npm:^1.0.3"
     has-property-descriptors: "npm:^1.0.2"
-    has-proto: "npm:^1.2.0"
-    has-symbols: "npm:^1.1.0"
-    internal-slot: "npm:^1.1.0"
-    iterator.prototype: "npm:^1.1.4"
-    safe-array-concat: "npm:^1.1.3"
-  checksum: 10c0/97e3125ca472d82d8aceea11b790397648b52c26d8768ea1c1ee6309ef45a8755bb63225a43f3150c7591cffc17caf5752459f1e70d583b4184370a8f04ebd2f
+    has-proto: "npm:^1.0.3"
+    has-symbols: "npm:^1.0.3"
+    internal-slot: "npm:^1.0.7"
+    iterator.prototype: "npm:^1.1.2"
+    safe-array-concat: "npm:^1.1.2"
+  checksum: 10c0/ae8f0241e383b3d197383b9842c48def7fce0255fb6ed049311b686ce295595d9e389b466f6a1b7d4e7bb92d82f5e716d6fae55e20c1040249bf976743b038c5
   languageName: node
   linkType: hard
 
@@ -8126,24 +7819,23 @@ __metadata:
   languageName: node
   linkType: hard
 
-"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1":
-  version: 1.1.1
-  resolution: "es-object-atoms@npm:1.1.1"
+"es-object-atoms@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "es-object-atoms@npm:1.0.0"
   dependencies:
     es-errors: "npm:^1.3.0"
-  checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c
+  checksum: 10c0/1fed3d102eb27ab8d983337bb7c8b159dd2a1e63ff833ec54eea1311c96d5b08223b433060ba240541ca8adba9eee6b0a60cdbf2f80634b784febc9cc8b687b4
   languageName: node
   linkType: hard
 
-"es-set-tostringtag@npm:^2.0.3, es-set-tostringtag@npm:^2.1.0":
-  version: 2.1.0
-  resolution: "es-set-tostringtag@npm:2.1.0"
+"es-set-tostringtag@npm:^2.0.3":
+  version: 2.0.3
+  resolution: "es-set-tostringtag@npm:2.0.3"
   dependencies:
-    es-errors: "npm:^1.3.0"
-    get-intrinsic: "npm:^1.2.6"
+    get-intrinsic: "npm:^1.2.4"
     has-tostringtag: "npm:^1.0.2"
-    hasown: "npm:^2.0.2"
-  checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af
+    hasown: "npm:^2.0.1"
+  checksum: 10c0/f22aff1585eb33569c326323f0b0d175844a1f11618b86e193b386f8be0ea9474cfbe46df39c45d959f7aa8f6c06985dc51dd6bce5401645ec5a74c4ceaa836a
   languageName: node
   linkType: hard
 
@@ -8156,21 +7848,21 @@ __metadata:
   languageName: node
   linkType: hard
 
-"es-to-primitive@npm:^1.3.0":
-  version: 1.3.0
-  resolution: "es-to-primitive@npm:1.3.0"
+"es-to-primitive@npm:^1.2.1":
+  version: 1.2.1
+  resolution: "es-to-primitive@npm:1.2.1"
   dependencies:
-    is-callable: "npm:^1.2.7"
-    is-date-object: "npm:^1.0.5"
-    is-symbol: "npm:^1.0.4"
-  checksum: 10c0/c7e87467abb0b438639baa8139f701a06537d2b9bc758f23e8622c3b42fd0fdb5bde0f535686119e446dd9d5e4c0f238af4e14960f4771877cf818d023f6730b
+    is-callable: "npm:^1.1.4"
+    is-date-object: "npm:^1.0.1"
+    is-symbol: "npm:^1.0.2"
+  checksum: 10c0/0886572b8dc075cb10e50c0af62a03d03a68e1e69c388bd4f10c0649ee41b1fbb24840a1b7e590b393011b5cdbe0144b776da316762653685432df37d6de60f1
   languageName: node
   linkType: hard
 
-"escalade@npm:^3.1.1, escalade@npm:^3.2.0":
-  version: 3.2.0
-  resolution: "escalade@npm:3.2.0"
-  checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65
+"escalade@npm:^3.1.1, escalade@npm:^3.1.2":
+  version: 3.1.2
+  resolution: "escalade@npm:3.1.2"
+  checksum: 10c0/6b4adafecd0682f3aa1cd1106b8fff30e492c7015b178bc81b2d2f75106dabea6c6d6e8508fc491bd58e597c74abb0e8e2368f943ecb9393d4162e3c2f3cf287
   languageName: node
   linkType: hard
 
@@ -8220,6 +7912,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"eslint-define-config@npm:^2.0.0":
+  version: 2.1.0
+  resolution: "eslint-define-config@npm:2.1.0"
+  checksum: 10c0/034bd6bfbfec2db6c720a51815de6b072efeef7afbf99d90c23a1871f9cd741bb77f9d34e0bc2465262298c6110c5c45b704714d8575c6567fd2df963fb792ea
+  languageName: node
+  linkType: hard
+
 "eslint-import-resolver-node@npm:^0.3.9":
   version: 0.3.9
   resolution: "eslint-import-resolver-node@npm:0.3.9"
@@ -8231,16 +7930,18 @@ __metadata:
   languageName: node
   linkType: hard
 
-"eslint-import-resolver-typescript@npm:^4.2.5":
-  version: 4.2.5
-  resolution: "eslint-import-resolver-typescript@npm:4.2.5"
+"eslint-import-resolver-typescript@npm:^3.5.5":
+  version: 3.6.3
+  resolution: "eslint-import-resolver-typescript@npm:3.6.3"
   dependencies:
-    debug: "npm:^4.4.0"
-    get-tsconfig: "npm:^4.10.0"
-    is-bun-module: "npm:^2.0.0"
-    stable-hash: "npm:^0.0.5"
-    tinyglobby: "npm:^0.2.12"
-    unrs-resolver: "npm:^1.3.2"
+    "@nolyfill/is-core-module": "npm:1.0.39"
+    debug: "npm:^4.3.5"
+    enhanced-resolve: "npm:^5.15.0"
+    eslint-module-utils: "npm:^2.8.1"
+    fast-glob: "npm:^3.3.2"
+    get-tsconfig: "npm:^4.7.5"
+    is-bun-module: "npm:^1.0.2"
+    is-glob: "npm:^4.0.3"
   peerDependencies:
     eslint: "*"
     eslint-plugin-import: "*"
@@ -8250,44 +7951,46 @@ __metadata:
       optional: true
     eslint-plugin-import-x:
       optional: true
-  checksum: 10c0/9134c4dd6e8b3cf1356d6bff68939153c81255c0ac7f694e829c3c7f5e785936591cfe43209d866c8a3b379d3a8dcd203651ec49bd99361fcb54dc0c2b9ce8fc
+  checksum: 10c0/5933b00791b7b077725b9ba9a85327d2e2dc7c8944c18a868feb317a0bf0e1e77aed2254c9c5e24dcc49360d119331d2c15281837f4269592965ace380a75111
   languageName: node
   linkType: hard
 
-"eslint-module-utils@npm:^2.12.0":
-  version: 2.12.0
-  resolution: "eslint-module-utils@npm:2.12.0"
+"eslint-module-utils@npm:^2.8.1, eslint-module-utils@npm:^2.9.0":
+  version: 2.9.0
+  resolution: "eslint-module-utils@npm:2.9.0"
   dependencies:
     debug: "npm:^3.2.7"
   peerDependenciesMeta:
     eslint:
       optional: true
-  checksum: 10c0/4d8b46dcd525d71276f9be9ffac1d2be61c9d54cc53c992e6333cf957840dee09381842b1acbbb15fc6b255ebab99cd481c5007ab438e5455a14abe1a0468558
+  checksum: 10c0/7c45c5b54402a969e99315890c10e9bf8c8bee16c7890573343af05dfa04566d61546585678c413e5228af0550e39461be47e35a8ff0d1863e113bdbb28d1d29
   languageName: node
   linkType: hard
 
-"eslint-plugin-formatjs@npm:^5.3.1":
-  version: 5.3.1
-  resolution: "eslint-plugin-formatjs@npm:5.3.1"
+"eslint-plugin-formatjs@npm:^4.10.1":
+  version: 4.13.3
+  resolution: "eslint-plugin-formatjs@npm:4.13.3"
   dependencies:
-    "@formatjs/icu-messageformat-parser": "npm:2.11.2"
-    "@formatjs/ts-transformer": "npm:3.13.34"
-    "@types/eslint": "npm:^9.6.1"
-    "@types/picomatch": "npm:^3"
-    "@typescript-eslint/utils": "npm:^8.27.0"
+    "@formatjs/icu-messageformat-parser": "npm:2.7.8"
+    "@formatjs/ts-transformer": "npm:3.13.14"
+    "@types/eslint": "npm:7 || 8"
+    "@types/picomatch": "npm:^2.3.0"
+    "@typescript-eslint/utils": "npm:^6.18.1"
+    emoji-regex: "npm:^10.2.1"
     magic-string: "npm:^0.30.0"
-    picomatch: "npm:2 || 3 || 4"
-    tslib: "npm:^2.8.0"
+    picomatch: "npm:^2.3.1"
+    tslib: "npm:2.6.2"
+    typescript: "npm:5"
     unicode-emoji-utils: "npm:^1.2.0"
   peerDependencies:
-    eslint: ^9.23.0
-  checksum: 10c0/fb8ba06e0718cd098f2393aea04eb4a6037ca3ead1c9450bd38926d0adecba4cefdebfb661c56c36685e0f003331520c3330544c45803f397b827713ab5e1d7d
+    eslint: 7 || 8
+  checksum: 10c0/5e98f487a097189e3bdc64b678d19f4c83502c32d7c89a8959eda4ed9cb664bf16f13ad8871be89ca192cb39c1007d6a158c39bbf5b23c56962d949dbe9abfab
   languageName: node
   linkType: hard
 
-"eslint-plugin-import@npm:~2.31.0":
-  version: 2.31.0
-  resolution: "eslint-plugin-import@npm:2.31.0"
+"eslint-plugin-import@npm:~2.30.0":
+  version: 2.30.0
+  resolution: "eslint-plugin-import@npm:2.30.0"
   dependencies:
     "@rtsao/scc": "npm:^1.1.0"
     array-includes: "npm:^3.1.8"
@@ -8297,7 +8000,7 @@ __metadata:
     debug: "npm:^3.2.7"
     doctrine: "npm:^2.1.0"
     eslint-import-resolver-node: "npm:^0.3.9"
-    eslint-module-utils: "npm:^2.12.0"
+    eslint-module-utils: "npm:^2.9.0"
     hasown: "npm:^2.0.2"
     is-core-module: "npm:^2.15.1"
     is-glob: "npm:^4.0.3"
@@ -8306,19 +8009,18 @@ __metadata:
     object.groupby: "npm:^1.0.3"
     object.values: "npm:^1.2.0"
     semver: "npm:^6.3.1"
-    string.prototype.trimend: "npm:^1.0.8"
     tsconfig-paths: "npm:^3.15.0"
   peerDependencies:
-    eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
-  checksum: 10c0/e21d116ddd1900e091ad120b3eb68c5dd5437fe2c930f1211781cd38b246f090a6b74d5f3800b8255a0ed29782591521ad44eb21c5534960a8f1fb4040fd913a
+    eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+  checksum: 10c0/4c9dcb1f27505c4d5dd891d2b551f56c70786d136aa3992a77e785bdc67c9f60200a2c7fb0ce55b7647fe550b12bc433d5dfa59e2c00ab44227791c5ab86badf
   languageName: node
   linkType: hard
 
-"eslint-plugin-jsdoc@npm:^50.6.9":
-  version: 50.6.9
-  resolution: "eslint-plugin-jsdoc@npm:50.6.9"
+"eslint-plugin-jsdoc@npm:^50.0.0":
+  version: 50.2.2
+  resolution: "eslint-plugin-jsdoc@npm:50.2.2"
   dependencies:
-    "@es-joy/jsdoccomment": "npm:~0.49.0"
+    "@es-joy/jsdoccomment": "npm:~0.48.0"
     are-docs-informative: "npm:^0.0.2"
     comment-parser: "npm:1.4.1"
     debug: "npm:^4.3.6"
@@ -8331,15 +8033,15 @@ __metadata:
     synckit: "npm:^0.9.1"
   peerDependencies:
     eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
-  checksum: 10c0/cad199d262c2e889a3af4e402f6adc624e4273b3d5ca1940e7227b37d87af8090ca3444f7fff57f58dab9a827faed8722fc2f5d4daf31ec085eb00e9f5a338a7
+  checksum: 10c0/f41d30246f6a4b6acb55e8cd75cf4fc256315e141ab25f7740fa6fa58cdd24e08cb672b4a350da93aeb126d210bd25981310a50f97cfb108f6a7ce8668b6b90a
   languageName: node
   linkType: hard
 
-"eslint-plugin-jsx-a11y@npm:~6.10.2":
-  version: 6.10.2
-  resolution: "eslint-plugin-jsx-a11y@npm:6.10.2"
+"eslint-plugin-jsx-a11y@npm:~6.10.0":
+  version: 6.10.0
+  resolution: "eslint-plugin-jsx-a11y@npm:6.10.0"
   dependencies:
-    aria-query: "npm:^5.3.2"
+    aria-query: "npm:~5.1.3"
     array-includes: "npm:^3.1.8"
     array.prototype.flatmap: "npm:^1.3.2"
     ast-types-flow: "npm:^0.0.8"
@@ -8347,64 +8049,63 @@ __metadata:
     axobject-query: "npm:^4.1.0"
     damerau-levenshtein: "npm:^1.0.8"
     emoji-regex: "npm:^9.2.2"
+    es-iterator-helpers: "npm:^1.0.19"
     hasown: "npm:^2.0.2"
     jsx-ast-utils: "npm:^3.3.5"
     language-tags: "npm:^1.0.9"
     minimatch: "npm:^3.1.2"
     object.fromentries: "npm:^2.0.8"
     safe-regex-test: "npm:^1.0.3"
-    string.prototype.includes: "npm:^2.0.1"
+    string.prototype.includes: "npm:^2.0.0"
   peerDependencies:
     eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
-  checksum: 10c0/d93354e03b0cf66f018d5c50964e074dffe4ddf1f9b535fa020d19c4ae45f89c1a16e9391ca61ac3b19f7042c751ac0d361a056a65cbd1de24718a53ff8daa6e
+  checksum: 10c0/9f8e29a3317fb6a82e2ecd333fe0fab3a69fff786d087eb65dc723d6e954473ab681d14a252d7cb2971f5e7f68816cb6f7731766558e1833a77bd73af1b5ab34
   languageName: node
   linkType: hard
 
-"eslint-plugin-promise@npm:~7.2.1":
-  version: 7.2.1
-  resolution: "eslint-plugin-promise@npm:7.2.1"
-  dependencies:
-    "@eslint-community/eslint-utils": "npm:^4.4.0"
+"eslint-plugin-promise@npm:~7.1.0":
+  version: 7.1.0
+  resolution: "eslint-plugin-promise@npm:7.1.0"
   peerDependencies:
     eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
-  checksum: 10c0/d494982faeeafbd2aa5fae9cbceca546169a8399000f72d5d940fa5c4ba554612903bcafbb8033647179e5d21ccf1d621b433d089695f7f47ce3d9fcf4cd0abf
+  checksum: 10c0/bbc3406139715dfa5f48d04f6d5b5e82f68929d954b0fa3821eb8cd6dc381b210512cedd2d874e5de5381005d316566f4ae046a4750ce3f5f5cbf28a14cc0ab2
   languageName: node
   linkType: hard
 
-"eslint-plugin-react-hooks@npm:^5.2.0":
-  version: 5.2.0
-  resolution: "eslint-plugin-react-hooks@npm:5.2.0"
+"eslint-plugin-react-hooks@npm:^4.6.0":
+  version: 4.6.2
+  resolution: "eslint-plugin-react-hooks@npm:4.6.2"
   peerDependencies:
-    eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
-  checksum: 10c0/1c8d50fa5984c6dea32470651807d2922cc3934cf3425e78f84a24c2dfd972e7f019bee84aefb27e0cf2c13fea0ac1d4473267727408feeb1c56333ca1489385
+    eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+  checksum: 10c0/4844e58c929bc05157fb70ba1e462e34f1f4abcbc8dd5bbe5b04513d33e2699effb8bca668297976ceea8e7ebee4e8fc29b9af9d131bcef52886feaa2308b2cc
   languageName: node
   linkType: hard
 
-"eslint-plugin-react@npm:^7.37.4":
-  version: 7.37.4
-  resolution: "eslint-plugin-react@npm:7.37.4"
+"eslint-plugin-react@npm:^7.33.2":
+  version: 7.35.2
+  resolution: "eslint-plugin-react@npm:7.35.2"
   dependencies:
     array-includes: "npm:^3.1.8"
     array.prototype.findlast: "npm:^1.2.5"
-    array.prototype.flatmap: "npm:^1.3.3"
+    array.prototype.flatmap: "npm:^1.3.2"
     array.prototype.tosorted: "npm:^1.1.4"
     doctrine: "npm:^2.1.0"
-    es-iterator-helpers: "npm:^1.2.1"
+    es-iterator-helpers: "npm:^1.0.19"
     estraverse: "npm:^5.3.0"
     hasown: "npm:^2.0.2"
     jsx-ast-utils: "npm:^2.4.1 || ^3.0.0"
     minimatch: "npm:^3.1.2"
     object.entries: "npm:^1.1.8"
     object.fromentries: "npm:^2.0.8"
-    object.values: "npm:^1.2.1"
+    object.values: "npm:^1.2.0"
     prop-types: "npm:^15.8.1"
     resolve: "npm:^2.0.0-next.5"
     semver: "npm:^6.3.1"
-    string.prototype.matchall: "npm:^4.0.12"
+    string.prototype.matchall: "npm:^4.0.11"
     string.prototype.repeat: "npm:^1.0.0"
   peerDependencies:
     eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
-  checksum: 10c0/4acbbdb19669dfa9a162ed8847c3ad1918f6aea1ceb675ee320b5d903b4e463fdef25e15233295b6d0a726fef2ea8b015c527da769c7690932ddc52d5b82ba12
+  checksum: 10c0/5f891f5a77e902a0ca8d10b23d0b800e90a09400187febe5986c5078d6277baa4b974d6acdbba25baae065dbcf12eb9241b5f5782527d0780314c2ee5006a8af
   languageName: node
   linkType: hard
 
@@ -8418,88 +8119,75 @@ __metadata:
   languageName: node
   linkType: hard
 
-"eslint-scope@npm:^8.3.0":
-  version: 8.3.0
-  resolution: "eslint-scope@npm:8.3.0"
+"eslint-scope@npm:^7.2.2":
+  version: 7.2.2
+  resolution: "eslint-scope@npm:7.2.2"
   dependencies:
     esrecurse: "npm:^4.3.0"
     estraverse: "npm:^5.2.0"
-  checksum: 10c0/23bf54345573201fdf06d29efa345ab508b355492f6c6cc9e2b9f6d02b896f369b6dd5315205be94b8853809776c4d13353b85c6b531997b164ff6c3328ecf5b
+  checksum: 10c0/613c267aea34b5a6d6c00514e8545ef1f1433108097e857225fed40d397dd6b1809dffd11c2fde23b37ca53d7bf935fe04d2a18e6fc932b31837b6ad67e1c116
   languageName: node
   linkType: hard
 
-"eslint-visitor-keys@npm:^3.3.0":
+"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3":
   version: 3.4.3
   resolution: "eslint-visitor-keys@npm:3.4.3"
   checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820
   languageName: node
   linkType: hard
 
-"eslint-visitor-keys@npm:^4.0.0, eslint-visitor-keys@npm:^4.2.0":
-  version: 4.2.0
-  resolution: "eslint-visitor-keys@npm:4.2.0"
-  checksum: 10c0/2ed81c663b147ca6f578312919483eb040295bbab759e5a371953456c636c5b49a559883e2677112453728d66293c0a4c90ab11cab3428cf02a0236d2e738269
+"eslint-visitor-keys@npm:^4.0.0":
+  version: 4.0.0
+  resolution: "eslint-visitor-keys@npm:4.0.0"
+  checksum: 10c0/76619f42cf162705a1515a6868e6fc7567e185c7063a05621a8ac4c3b850d022661262c21d9f1fc1d144ecf0d5d64d70a3f43c15c3fc969a61ace0fb25698cf5
   languageName: node
   linkType: hard
 
-"eslint@npm:^9.23.0":
-  version: 9.23.0
-  resolution: "eslint@npm:9.23.0"
+"eslint@npm:^8.41.0":
+  version: 8.57.0
+  resolution: "eslint@npm:8.57.0"
   dependencies:
     "@eslint-community/eslint-utils": "npm:^4.2.0"
-    "@eslint-community/regexpp": "npm:^4.12.1"
-    "@eslint/config-array": "npm:^0.19.2"
-    "@eslint/config-helpers": "npm:^0.2.0"
-    "@eslint/core": "npm:^0.12.0"
-    "@eslint/eslintrc": "npm:^3.3.1"
-    "@eslint/js": "npm:9.23.0"
-    "@eslint/plugin-kit": "npm:^0.2.7"
-    "@humanfs/node": "npm:^0.16.6"
+    "@eslint-community/regexpp": "npm:^4.6.1"
+    "@eslint/eslintrc": "npm:^2.1.4"
+    "@eslint/js": "npm:8.57.0"
+    "@humanwhocodes/config-array": "npm:^0.11.14"
     "@humanwhocodes/module-importer": "npm:^1.0.1"
-    "@humanwhocodes/retry": "npm:^0.4.2"
-    "@types/estree": "npm:^1.0.6"
-    "@types/json-schema": "npm:^7.0.15"
+    "@nodelib/fs.walk": "npm:^1.2.8"
+    "@ungap/structured-clone": "npm:^1.2.0"
     ajv: "npm:^6.12.4"
     chalk: "npm:^4.0.0"
-    cross-spawn: "npm:^7.0.6"
+    cross-spawn: "npm:^7.0.2"
     debug: "npm:^4.3.2"
+    doctrine: "npm:^3.0.0"
     escape-string-regexp: "npm:^4.0.0"
-    eslint-scope: "npm:^8.3.0"
-    eslint-visitor-keys: "npm:^4.2.0"
-    espree: "npm:^10.3.0"
-    esquery: "npm:^1.5.0"
+    eslint-scope: "npm:^7.2.2"
+    eslint-visitor-keys: "npm:^3.4.3"
+    espree: "npm:^9.6.1"
+    esquery: "npm:^1.4.2"
     esutils: "npm:^2.0.2"
     fast-deep-equal: "npm:^3.1.3"
-    file-entry-cache: "npm:^8.0.0"
+    file-entry-cache: "npm:^6.0.1"
     find-up: "npm:^5.0.0"
     glob-parent: "npm:^6.0.2"
+    globals: "npm:^13.19.0"
+    graphemer: "npm:^1.4.0"
     ignore: "npm:^5.2.0"
     imurmurhash: "npm:^0.1.4"
     is-glob: "npm:^4.0.0"
+    is-path-inside: "npm:^3.0.3"
+    js-yaml: "npm:^4.1.0"
     json-stable-stringify-without-jsonify: "npm:^1.0.1"
+    levn: "npm:^0.4.1"
     lodash.merge: "npm:^4.6.2"
     minimatch: "npm:^3.1.2"
     natural-compare: "npm:^1.4.0"
     optionator: "npm:^0.9.3"
-  peerDependencies:
-    jiti: "*"
-  peerDependenciesMeta:
-    jiti:
-      optional: true
+    strip-ansi: "npm:^6.0.1"
+    text-table: "npm:^0.2.0"
   bin:
     eslint: bin/eslint.js
-  checksum: 10c0/9616c308dfa8d09db8ae51019c87d5d05933742214531b077bd6ab618baab3bec7938256c14dcad4dc47f5ba93feb0bc5e089f68799f076374ddea21b6a9be45
-  languageName: node
-  linkType: hard
-
-"espree@npm:^10.0.1, espree@npm:^10.3.0":
-  version: 10.3.0
-  resolution: "espree@npm:10.3.0"
-  dependencies:
-    acorn: "npm:^8.14.0"
-    acorn-jsx: "npm:^5.3.2"
-    eslint-visitor-keys: "npm:^4.2.0"
-  checksum: 10c0/272beeaca70d0a1a047d61baff64db04664a33d7cfb5d144f84bc8a5c6194c6c8ebe9cc594093ca53add88baa23e59b01e69e8a0160ab32eac570482e165c462
+  checksum: 10c0/00bb96fd2471039a312435a6776fe1fd557c056755eaa2b96093ef3a8508c92c8775d5f754768be6b1dddd09fdd3379ddb231eeb9b6c579ee17ea7d68000a529
   languageName: node
   linkType: hard
 
@@ -8514,6 +8202,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"espree@npm:^9.6.0, espree@npm:^9.6.1":
+  version: 9.6.1
+  resolution: "espree@npm:9.6.1"
+  dependencies:
+    acorn: "npm:^8.9.0"
+    acorn-jsx: "npm:^5.3.2"
+    eslint-visitor-keys: "npm:^3.4.1"
+  checksum: 10c0/1a2e9b4699b715347f62330bcc76aee224390c28bb02b31a3752e9d07549c473f5f986720483c6469cf3cfb3c9d05df612ffc69eb1ee94b54b739e67de9bb460
+  languageName: node
+  linkType: hard
+
 "esprima@npm:^4.0.0, esprima@npm:^4.0.1":
   version: 4.0.1
   resolution: "esprima@npm:4.0.1"
@@ -8524,7 +8223,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"esquery@npm:^1.5.0, esquery@npm:^1.6.0":
+"esquery@npm:^1.4.2, esquery@npm:^1.6.0":
   version: 1.6.0
   resolution: "esquery@npm:1.6.0"
   dependencies:
@@ -8584,6 +8283,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"event-target-shim@npm:^5.0.0":
+  version: 5.0.1
+  resolution: "event-target-shim@npm:5.0.1"
+  checksum: 10c0/0255d9f936215fd206156fd4caa9e8d35e62075d720dc7d847e89b417e5e62cf1ce6c9b4e0a1633a9256de0efefaf9f8d26924b1f3c8620cffb9db78e7d3076b
+  languageName: node
+  linkType: hard
+
 "eventemitter3@npm:^4.0.0":
   version: 4.0.7
   resolution: "eventemitter3@npm:4.0.7"
@@ -8598,7 +8304,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"events@npm:^3.0.0":
+"events@npm:^3.0.0, events@npm:^3.3.0":
   version: 3.3.0
   resolution: "events@npm:3.3.0"
   checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6
@@ -8724,15 +8430,15 @@ __metadata:
   linkType: hard
 
 "express@npm:^4.17.1, express@npm:^4.18.2":
-  version: 4.21.2
-  resolution: "express@npm:4.21.2"
+  version: 4.21.0
+  resolution: "express@npm:4.21.0"
   dependencies:
     accepts: "npm:~1.3.8"
     array-flatten: "npm:1.1.1"
     body-parser: "npm:1.20.3"
     content-disposition: "npm:0.5.4"
     content-type: "npm:~1.0.4"
-    cookie: "npm:0.7.1"
+    cookie: "npm:0.6.0"
     cookie-signature: "npm:1.0.6"
     debug: "npm:2.6.9"
     depd: "npm:2.0.0"
@@ -8746,7 +8452,7 @@ __metadata:
     methods: "npm:~1.1.2"
     on-finished: "npm:2.4.1"
     parseurl: "npm:~1.3.3"
-    path-to-regexp: "npm:0.1.12"
+    path-to-regexp: "npm:0.1.10"
     proxy-addr: "npm:~2.0.7"
     qs: "npm:6.13.0"
     range-parser: "npm:~1.2.1"
@@ -8758,7 +8464,7 @@ __metadata:
     type-is: "npm:~1.6.18"
     utils-merge: "npm:1.0.1"
     vary: "npm:~1.1.2"
-  checksum: 10c0/38168fd0a32756600b56e6214afecf4fc79ec28eca7f7a91c2ab8d50df4f47562ca3f9dee412da7f5cea6b1a1544b33b40f9f8586dbacfbdada0fe90dbb10a1f
+  checksum: 10c0/4cf7ca328f3fdeb720f30ccb2ea7708bfa7d345f9cc460b64a82bf1b2c91e5b5852ba15a9a11b2a165d6089acf83457fc477dc904d59cd71ed34c7a91762c6cc
   languageName: node
   linkType: hard
 
@@ -8886,33 +8592,21 @@ __metadata:
   languageName: node
   linkType: hard
 
-"fdir@npm:^6.4.3":
-  version: 6.4.3
-  resolution: "fdir@npm:6.4.3"
-  peerDependencies:
-    picomatch: ^3 || ^4
-  peerDependenciesMeta:
-    picomatch:
-      optional: true
-  checksum: 10c0/d13c10120e9625adf21d8d80481586200759928c19405a816b77dd28eaeb80e7c59c5def3e2941508045eb06d34eb47fad865ccc8bf98e6ab988bb0ed160fb6f
-  languageName: node
-  linkType: hard
-
-"file-entry-cache@npm:^8.0.0":
-  version: 8.0.0
-  resolution: "file-entry-cache@npm:8.0.0"
+"file-entry-cache@npm:^6.0.1":
+  version: 6.0.1
+  resolution: "file-entry-cache@npm:6.0.1"
   dependencies:
-    flat-cache: "npm:^4.0.0"
-  checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638
+    flat-cache: "npm:^3.0.4"
+  checksum: 10c0/58473e8a82794d01b38e5e435f6feaf648e3f36fdb3a56e98f417f4efae71ad1c0d4ebd8a9a7c50c3ad085820a93fc7494ad721e0e4ebc1da3573f4e1c3c7cdd
   languageName: node
   linkType: hard
 
-"file-entry-cache@npm:^9.1.0":
-  version: 9.1.0
-  resolution: "file-entry-cache@npm:9.1.0"
+"file-entry-cache@npm:^9.0.0":
+  version: 9.0.0
+  resolution: "file-entry-cache@npm:9.0.0"
   dependencies:
     flat-cache: "npm:^5.0.0"
-  checksum: 10c0/4b4dbc1e972f50202b1a4430d30fd99378ef6e2a64857176abdc65c5e4730a948fb37e274478520a7bacbc70f3abba455a4b9d2c1915c53f30d11dc85d3fef5e
+  checksum: 10c0/07b0a4f062dc0aa258f3e1b06ac083ea25313f5e289943e146fafdaf3315dcc031635545eea7fe98fe5598b91d6c7f48dba7a251dd7ac20108a6ebf7d00b0b1c
   languageName: node
   linkType: hard
 
@@ -8928,6 +8622,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"file-type@npm:^12.4.1":
+  version: 12.4.2
+  resolution: "file-type@npm:12.4.2"
+  checksum: 10c0/26a307262a2a0b41ea83136550fbe83d8b502d080778b6577e0336fbfe9e919e1f871a286a6eb59f668425f60ebb19402fcb6c0443af58446d33c63362554e1d
+  languageName: node
+  linkType: hard
+
 "file-uri-to-path@npm:1.0.0":
   version: 1.0.0
   resolution: "file-uri-to-path@npm:1.0.0"
@@ -9039,13 +8740,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"flat-cache@npm:^4.0.0":
-  version: 4.0.1
-  resolution: "flat-cache@npm:4.0.1"
+"flat-cache@npm:^3.0.4":
+  version: 3.2.0
+  resolution: "flat-cache@npm:3.2.0"
   dependencies:
     flatted: "npm:^3.2.9"
-    keyv: "npm:^4.5.4"
-  checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc
+    keyv: "npm:^4.5.3"
+    rimraf: "npm:^3.0.2"
+  checksum: 10c0/b76f611bd5f5d68f7ae632e3ae503e678d205cf97a17c6ab5b12f6ca61188b5f1f7464503efae6dc18683ed8f0b41460beb48ac4b9ac63fe6201296a91ba2f75
   languageName: node
   linkType: hard
 
@@ -9112,13 +8814,13 @@ __metadata:
   linkType: hard
 
 "form-data@npm:^4.0.0":
-  version: 4.0.1
-  resolution: "form-data@npm:4.0.1"
+  version: 4.0.0
+  resolution: "form-data@npm:4.0.0"
   dependencies:
     asynckit: "npm:^0.4.0"
     combined-stream: "npm:^1.0.8"
     mime-types: "npm:^2.1.12"
-  checksum: 10c0/bb102d570be8592c23f4ea72d7df9daa50c7792eb0cf1c5d7e506c1706e7426a4e4ae48a35b109e91c85f1c0ec63774a21ae252b66f4eb981cb8efef7d0463c8
+  checksum: 10c0/cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e
   languageName: node
   linkType: hard
 
@@ -9236,17 +8938,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8":
-  version: 1.1.8
-  resolution: "function.prototype.name@npm:1.1.8"
+"function.prototype.name@npm:^1.1.5, function.prototype.name@npm:^1.1.6":
+  version: 1.1.6
+  resolution: "function.prototype.name@npm:1.1.6"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.3"
-    define-properties: "npm:^1.2.1"
+    call-bind: "npm:^1.0.2"
+    define-properties: "npm:^1.2.0"
+    es-abstract: "npm:^1.22.1"
     functions-have-names: "npm:^1.2.3"
-    hasown: "npm:^2.0.2"
-    is-callable: "npm:^1.2.7"
-  checksum: 10c0/e920a2ab52663005f3cbe7ee3373e3c71c1fb5558b0b0548648cdf3e51961085032458e26c71ff1a8c8c20e7ee7caeb03d43a5d1fa8610c459333323a2e71253
+  checksum: 10c0/9eae11294905b62cb16874adb4fc687927cda3162285e0ad9612e6a1d04934005d46907362ea9cdb7428edce05a2f2c3dabc3b2d21e9fd343e9bb278230ad94b
   languageName: node
   linkType: hard
 
@@ -9258,9 +8958,9 @@ __metadata:
   linkType: hard
 
 "fuzzysort@npm:^3.0.0":
-  version: 3.1.0
-  resolution: "fuzzysort@npm:3.1.0"
-  checksum: 10c0/da9bb32de16f2a5c2c000b99031d9f4f8a01380c12d5d3b67296443a1152c55987ce3c4ddbfe97481b0e9b6f2fb77d61dceba29a93ad36ee23ef5bab6a31afb8
+  version: 3.0.2
+  resolution: "fuzzysort@npm:3.0.2"
+  checksum: 10c0/c6cdbd092a8e91ed822aeac6d4fb95559759c10602cb29f27307c1cabd01fdd384fa399f7757722435b595244efb000cd63f144104c41b8551b2faff123279cb
   languageName: node
   linkType: hard
 
@@ -9285,21 +8985,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0":
-  version: 1.3.0
-  resolution: "get-intrinsic@npm:1.3.0"
+"get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4":
+  version: 1.2.4
+  resolution: "get-intrinsic@npm:1.2.4"
   dependencies:
-    call-bind-apply-helpers: "npm:^1.0.2"
-    es-define-property: "npm:^1.0.1"
     es-errors: "npm:^1.3.0"
-    es-object-atoms: "npm:^1.1.1"
     function-bind: "npm:^1.1.2"
-    get-proto: "npm:^1.0.1"
-    gopd: "npm:^1.2.0"
-    has-symbols: "npm:^1.1.0"
-    hasown: "npm:^2.0.2"
-    math-intrinsics: "npm:^1.1.0"
-  checksum: 10c0/52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a
+    has-proto: "npm:^1.0.1"
+    has-symbols: "npm:^1.0.3"
+    hasown: "npm:^2.0.0"
+  checksum: 10c0/0a9b82c16696ed6da5e39b1267104475c47e3a9bdbe8b509dfe1710946e38a87be70d759f4bb3cda042d76a41ef47fe769660f3b7c0d1f68750299344ffb15b7
   languageName: node
   linkType: hard
 
@@ -9317,16 +9012,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "get-proto@npm:1.0.1"
-  dependencies:
-    dunder-proto: "npm:^1.0.1"
-    es-object-atoms: "npm:^1.0.0"
-  checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c
-  languageName: node
-  linkType: hard
-
 "get-stream@npm:^4.0.0":
   version: 4.1.0
   resolution: "get-stream@npm:4.1.0"
@@ -9350,23 +9035,23 @@ __metadata:
   languageName: node
   linkType: hard
 
-"get-symbol-description@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "get-symbol-description@npm:1.1.0"
+"get-symbol-description@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "get-symbol-description@npm:1.0.2"
   dependencies:
-    call-bound: "npm:^1.0.3"
+    call-bind: "npm:^1.0.5"
     es-errors: "npm:^1.3.0"
-    get-intrinsic: "npm:^1.2.6"
-  checksum: 10c0/d6a7d6afca375779a4b307738c9e80dbf7afc0bdbe5948768d54ab9653c865523d8920e670991a925936eb524b7cb6a6361d199a760b21d0ca7620194455aa4b
+    get-intrinsic: "npm:^1.2.4"
+  checksum: 10c0/867be6d63f5e0eb026cb3b0ef695ec9ecf9310febb041072d2e142f260bd91ced9eeb426b3af98791d1064e324e653424afa6fd1af17dee373bea48ae03162bc
   languageName: node
   linkType: hard
 
-"get-tsconfig@npm:^4.10.0":
-  version: 4.10.0
-  resolution: "get-tsconfig@npm:4.10.0"
+"get-tsconfig@npm:^4.7.5":
+  version: 4.8.0
+  resolution: "get-tsconfig@npm:4.8.0"
   dependencies:
     resolve-pkg-maps: "npm:^1.0.0"
-  checksum: 10c0/c9b5572c5118923c491c04285c73bd55b19e214992af957c502a3be0fc0043bb421386ffd45ca3433c0a7fba81221ca300479e8393960acf15d0ed4563f38a86
+  checksum: 10c0/943721c996d9a77351aa7c07956de77baece97f997bd30f3247f46907e4b743f7b9da02c7b3692a36f0884d3724271faeb88ed1c3aca3aba2afe3f27d6c4aeb3
   languageName: node
   linkType: hard
 
@@ -9486,27 +9171,21 @@ __metadata:
   languageName: node
   linkType: hard
 
-"globals@npm:^14.0.0":
-  version: 14.0.0
-  resolution: "globals@npm:14.0.0"
-  checksum: 10c0/b96ff42620c9231ad468d4c58ff42afee7777ee1c963013ff8aabe095a451d0ceeb8dcd8ef4cbd64d2538cef45f787a78ba3a9574f4a634438963e334471302d
-  languageName: node
-  linkType: hard
-
-"globals@npm:^16.0.0":
-  version: 16.0.0
-  resolution: "globals@npm:16.0.0"
-  checksum: 10c0/8906d5f01838df64a81d6c2a7b7214312e2216cf65c5ed1546dc9a7d0febddf55ffa906cf04efd5b01eec2534d6f14859a89535d1a68241832810e41ef3fd5bb
-  languageName: node
-  linkType: hard
-
-"globalthis@npm:^1.0.4":
-  version: 1.0.4
-  resolution: "globalthis@npm:1.0.4"
+"globals@npm:^13.19.0":
+  version: 13.23.0
+  resolution: "globals@npm:13.23.0"
   dependencies:
-    define-properties: "npm:^1.2.1"
-    gopd: "npm:^1.0.1"
-  checksum: 10c0/9d156f313af79d80b1566b93e19285f481c591ad6d0d319b4be5e03750d004dde40a39a0f26f7e635f9007a3600802f53ecd85a759b86f109e80a5f705e01846
+    type-fest: "npm:^0.20.2"
+  checksum: 10c0/fc05e184b3be59bffa2580f28551a12a758c3a18df4be91444202982c76f13f52821ad54ffaf7d3f2a4d2498fdf54aeaca8d4540fd9e860a9edb09d34ef4c507
+  languageName: node
+  linkType: hard
+
+"globalthis@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "globalthis@npm:1.0.3"
+  dependencies:
+    define-properties: "npm:^1.1.3"
+  checksum: 10c0/0db6e9af102a5254630351557ac15e6909bc7459d3e3f6b001e59fe784c96d31108818f032d9095739355a88467459e6488ff16584ee6250cd8c27dec05af4b0
   languageName: node
   linkType: hard
 
@@ -9544,14 +9223,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"gopd@npm:^1.0.1, gopd@npm:^1.2.0":
-  version: 1.2.0
-  resolution: "gopd@npm:1.2.0"
-  checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead
+"gopd@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "gopd@npm:1.0.1"
+  dependencies:
+    get-intrinsic: "npm:^1.1.3"
+  checksum: 10c0/505c05487f7944c552cee72087bf1567debb470d4355b1335f2c262d218ebbff805cd3715448fe29b4b380bae6912561d0467233e4165830efd28da241418c63
   languageName: node
   linkType: hard
 
-"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9":
+"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9":
   version: 4.2.11
   resolution: "graceful-fs@npm:4.2.11"
   checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2
@@ -9581,7 +9262,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"has-bigints@npm:^1.0.2":
+"has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2":
   version: 1.0.2
   resolution: "has-bigints@npm:1.0.2"
   checksum: 10c0/724eb1485bfa3cdff6f18d95130aa190561f00b3fcf9f19dc640baf8176b5917c143b81ec2123f8cddb6c05164a198c94b13e1377c497705ccc8e1a80306e83b
@@ -9602,7 +9283,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2":
+"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.1, has-property-descriptors@npm:^1.0.2":
   version: 1.0.2
   resolution: "has-property-descriptors@npm:1.0.2"
   dependencies:
@@ -9611,19 +9292,17 @@ __metadata:
   languageName: node
   linkType: hard
 
-"has-proto@npm:^1.2.0":
-  version: 1.2.0
-  resolution: "has-proto@npm:1.2.0"
-  dependencies:
-    dunder-proto: "npm:^1.0.0"
-  checksum: 10c0/46538dddab297ec2f43923c3d35237df45d8c55a6fc1067031e04c13ed8a9a8f94954460632fd4da84c31a1721eefee16d901cbb1ae9602bab93bb6e08f93b95
+"has-proto@npm:^1.0.1, has-proto@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "has-proto@npm:1.0.3"
+  checksum: 10c0/35a6989f81e9f8022c2f4027f8b48a552de714938765d019dbea6bb547bd49ce5010a3c7c32ec6ddac6e48fc546166a3583b128f5a7add8b058a6d8b4afec205
   languageName: node
   linkType: hard
 
-"has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "has-symbols@npm:1.1.0"
-  checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e
+"has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "has-symbols@npm:1.0.3"
+  checksum: 10c0/e6922b4345a3f37069cdfe8600febbca791c94988c01af3394d86ca3360b4b93928bbf395859158f88099cb10b19d98e3bbab7c9ff2c1bd09cf665ee90afa2c3
   languageName: node
   linkType: hard
 
@@ -9696,7 +9375,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"hasown@npm:^2.0.0, hasown@npm:^2.0.2":
+"hasown@npm:^2.0.0, hasown@npm:^2.0.1, hasown@npm:^2.0.2":
   version: 2.0.2
   resolution: "hasown@npm:2.0.2"
   dependencies:
@@ -9920,13 +9599,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.6":
-  version: 7.0.6
-  resolution: "https-proxy-agent@npm:7.0.6"
+"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5":
+  version: 7.0.5
+  resolution: "https-proxy-agent@npm:7.0.5"
   dependencies:
-    agent-base: "npm:^7.1.2"
+    agent-base: "npm:^7.0.2"
     debug: "npm:4"
-  checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac
+  checksum: 10c0/2490e3acec397abeb88807db52cac59102d5ed758feee6df6112ab3ccd8325e8a1ce8bce6f4b66e5470eca102d31e425ace904242e4fa28dbe0c59c4bafa7b2c
   languageName: node
   linkType: hard
 
@@ -9945,11 +9624,11 @@ __metadata:
   linkType: hard
 
 "husky@npm:^9.0.11":
-  version: 9.1.7
-  resolution: "husky@npm:9.1.7"
+  version: 9.1.6
+  resolution: "husky@npm:9.1.6"
   bin:
     husky: bin.js
-  checksum: 10c0/35bb110a71086c48906aa7cd3ed4913fb913823715359d65e32e0b964cb1e255593b0ae8014a5005c66a68e6fa66c38dcfa8056dbbdfb8b0187c0ffe7ee3a58f
+  checksum: 10c0/705673db4a247c1febd9c5df5f6a3519106cf0335845027bb50a15fba9b1f542cb2610932ede96fd08008f6d9f49db0f15560509861808b0031cdc0e7c798bac
   languageName: node
   linkType: hard
 
@@ -9980,10 +9659,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"idb-keyval@npm:^6.2.0":
-  version: 6.2.1
-  resolution: "idb-keyval@npm:6.2.1"
-  checksum: 10c0/9f0c83703a365e00bd0b4ed6380ce509a06dedfc6ec39b2ba5740085069fd2f2ff5c14ba19356488e3612a2f9c49985971982d836460a982a5d0b4019eeba48a
+"idb-keyval@npm:^3.2.0":
+  version: 3.2.0
+  resolution: "idb-keyval@npm:3.2.0"
+  checksum: 10c0/9b1f65d5f08630ef444a89334370c394175b1543f157621b36a3bc5e5208946f3f0ab5d5e24c74e81f2ef54b55b742b4e5b439c561f62695ffb69a06b0bce8e1
   languageName: node
   linkType: hard
 
@@ -9994,27 +9673,20 @@ __metadata:
   languageName: node
   linkType: hard
 
-"ieee754@npm:^1.1.4":
+"ieee754@npm:^1.1.4, ieee754@npm:^1.2.1":
   version: 1.2.1
   resolution: "ieee754@npm:1.2.1"
   checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb
   languageName: node
   linkType: hard
 
-"ignore@npm:^5.2.0, ignore@npm:^5.3.1":
+"ignore@npm:^5.2.0, ignore@npm:^5.3.1, ignore@npm:^5.3.2":
   version: 5.3.2
   resolution: "ignore@npm:5.3.2"
   checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337
   languageName: node
   linkType: hard
 
-"ignore@npm:^6.0.2":
-  version: 6.0.2
-  resolution: "ignore@npm:6.0.2"
-  checksum: 10c0/9a38feac1861906a78ba0f03e8ef3cd6b0526dce2a1a84e1009324b557763afeb9c3ebcc04666b21f7bbf71adda45e76781bb9e2eaa0903d45dcaded634454f5
-  languageName: node
-  linkType: hard
-
 "immer@npm:^10.0.3":
   version: 10.0.3
   resolution: "immer@npm:10.0.3"
@@ -10029,20 +9701,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"immutable@npm:^4.0.0-rc.1, immutable@npm:^4.3.0":
+"immutable@npm:^4.0.0, immutable@npm:^4.0.0-rc.1, immutable@npm:^4.3.0":
   version: 4.3.7
   resolution: "immutable@npm:4.3.7"
   checksum: 10c0/9b099197081b22f6433003e34929da8ecddbbdc1474cdc8aa3b7669dee4adda349c06143de22def36016d1b6de5322b043eccd7a11db1dad2ca85dad4fff5435
   languageName: node
   linkType: hard
 
-"immutable@npm:^5.0.2":
-  version: 5.0.3
-  resolution: "immutable@npm:5.0.3"
-  checksum: 10c0/3269827789e1026cd25c2ea97f0b2c19be852ffd49eda1b674b20178f73d84fa8d945ad6f5ac5bc4545c2b4170af9f6e1f77129bc1cae7974a4bf9b04a9cdfb9
-  languageName: node
-  linkType: hard
-
 "import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0":
   version: 3.3.0
   resolution: "import-fresh@npm:3.3.0"
@@ -10160,14 +9825,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"internal-slot@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "internal-slot@npm:1.1.0"
+"internal-slot@npm:^1.0.4, internal-slot@npm:^1.0.7":
+  version: 1.0.7
+  resolution: "internal-slot@npm:1.0.7"
   dependencies:
     es-errors: "npm:^1.3.0"
-    hasown: "npm:^2.0.2"
-    side-channel: "npm:^1.1.0"
-  checksum: 10c0/03966f5e259b009a9bf1a78d60da920df198af4318ec004f57b8aef1dd3fe377fbc8cce63a96e8c810010302654de89f9e19de1cd8ad0061d15be28a695465c7
+    hasown: "npm:^2.0.0"
+    side-channel: "npm:^1.0.4"
+  checksum: 10c0/f8b294a4e6ea3855fc59551bbf35f2b832cf01fd5e6e2a97f5c201a071cc09b49048f856e484b67a6c721da5e55736c5b6ddafaf19e2dbeb4a3ff1821680de6c
   languageName: node
   linkType: hard
 
@@ -10185,15 +9850,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"intl-messageformat@npm:10.7.16, intl-messageformat@npm:^10.7.16":
-  version: 10.7.16
-  resolution: "intl-messageformat@npm:10.7.16"
+"intl-messageformat@npm:10.5.14, intl-messageformat@npm:^10.3.5":
+  version: 10.5.14
+  resolution: "intl-messageformat@npm:10.5.14"
   dependencies:
-    "@formatjs/ecma402-abstract": "npm:2.3.4"
-    "@formatjs/fast-memoize": "npm:2.2.7"
-    "@formatjs/icu-messageformat-parser": "npm:2.11.2"
-    tslib: "npm:^2.8.0"
-  checksum: 10c0/537735bf6439f0560f132895d117df6839957ac04cdd58d861f6da86803d40bfc19059e3d341ddb8de87214b73a6329b57f9acdb512bb0f745dcf08729507b9b
+    "@formatjs/ecma402-abstract": "npm:2.0.0"
+    "@formatjs/fast-memoize": "npm:2.2.0"
+    "@formatjs/icu-messageformat-parser": "npm:2.7.8"
+    tslib: "npm:^2.4.0"
+  checksum: 10c0/8ec0a60539f67039356e211bcc8d81cf1bd9d62190a72ab0e94504da92f0242fe2f94ffb512b97cc6f63782b7891874d4038536ce04631e59d762c3441c60b4b
   languageName: node
   linkType: hard
 
@@ -10207,8 +9872,8 @@ __metadata:
   linkType: hard
 
 "ioredis@npm:^5.3.2":
-  version: 5.6.1
-  resolution: "ioredis@npm:5.6.1"
+  version: 5.4.1
+  resolution: "ioredis@npm:5.4.1"
   dependencies:
     "@ioredis/commands": "npm:^1.1.1"
     cluster-key-slot: "npm:^1.1.0"
@@ -10219,7 +9884,7 @@ __metadata:
     redis-errors: "npm:^1.2.0"
     redis-parser: "npm:^3.0.0"
     standard-as-callback: "npm:^2.1.0"
-  checksum: 10c0/26ae49cf448e807e454a9bdea5a9dfdcf669e2fdbf2df341900a0fb693c5662fea7e39db3227ce8972d1bda0ba7da9b7410e5163b12d8878a579548d847220ac
+  checksum: 10c0/5d28b7c89a3cab5b76d75923d7d4ce79172b3a1ca9be690133f6e8e393a7a4b4ffd55513e618bbb5504fed80d9e1395c9d9531a7c5c5c84aa4c4e765cca75456
   languageName: node
   linkType: hard
 
@@ -10276,7 +9941,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-arguments@npm:^1.0.4":
+"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1":
   version: 1.1.1
   resolution: "is-arguments@npm:1.1.1"
   dependencies:
@@ -10286,14 +9951,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5":
-  version: 3.0.5
-  resolution: "is-array-buffer@npm:3.0.5"
+"is-array-buffer@npm:^3.0.2, is-array-buffer@npm:^3.0.4":
+  version: 3.0.4
+  resolution: "is-array-buffer@npm:3.0.4"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.3"
-    get-intrinsic: "npm:^1.2.6"
-  checksum: 10c0/c5c9f25606e86dbb12e756694afbbff64bc8b348d1bc989324c037e1068695131930199d6ad381952715dad3a9569333817f0b1a72ce5af7f883ce802e49c83d
+    call-bind: "npm:^1.0.2"
+    get-intrinsic: "npm:^1.2.1"
+  checksum: 10c0/42a49d006cc6130bc5424eae113e948c146f31f9d24460fc0958f855d9d810e6fd2e4519bf19aab75179af9c298ea6092459d8cafdec523cd19e529b26eab860
   languageName: node
   linkType: hard
 
@@ -10313,12 +9977,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-bigint@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "is-bigint@npm:1.1.0"
+"is-bigint@npm:^1.0.1":
+  version: 1.0.4
+  resolution: "is-bigint@npm:1.0.4"
   dependencies:
-    has-bigints: "npm:^1.0.2"
-  checksum: 10c0/f4f4b905ceb195be90a6ea7f34323bf1c18e3793f18922e3e9a73c684c29eeeeff5175605c3a3a74cc38185fe27758f07efba3dbae812e5c5afbc0d2316b40e4
+    has-bigints: "npm:^1.0.1"
+  checksum: 10c0/eb9c88e418a0d195ca545aff2b715c9903d9b0a5033bc5922fec600eb0c3d7b1ee7f882dbf2e0d5a6e694e42391be3683e4368737bd3c4a77f8ac293e7773696
   languageName: node
   linkType: hard
 
@@ -10340,13 +10004,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-boolean-object@npm:^1.2.1":
-  version: 1.2.2
-  resolution: "is-boolean-object@npm:1.2.2"
+"is-boolean-object@npm:^1.1.0":
+  version: 1.1.2
+  resolution: "is-boolean-object@npm:1.1.2"
   dependencies:
-    call-bound: "npm:^1.0.3"
-    has-tostringtag: "npm:^1.0.2"
-  checksum: 10c0/36ff6baf6bd18b3130186990026f5a95c709345c39cd368468e6c1b6ab52201e9fd26d8e1f4c066357b4938b0f0401e1a5000e08257787c1a02f3a719457001e
+    call-bind: "npm:^1.0.2"
+    has-tostringtag: "npm:^1.0.0"
+  checksum: 10c0/6090587f8a8a8534c0f816da868bc94f32810f08807aa72fa7e79f7e11c466d281486ffe7a788178809c2aa71fe3e700b167fe80dd96dad68026bfff8ebf39f7
   languageName: node
   linkType: hard
 
@@ -10359,16 +10023,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-bun-module@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "is-bun-module@npm:2.0.0"
+"is-bun-module@npm:^1.0.2":
+  version: 1.1.0
+  resolution: "is-bun-module@npm:1.1.0"
   dependencies:
-    semver: "npm:^7.7.1"
-  checksum: 10c0/7d27a0679cfa5be1f5052650391f9b11040cd70c48d45112e312c56bc6b6ca9c9aea70dcce6cc40b1e8947bfff8567a5c5715d3b066fb478522dab46ea379240
+    semver: "npm:^7.6.3"
+  checksum: 10c0/17cae968c3fe08e2bd66f8477e4d5a166d6299b5e7ce5c7558355551c50267f77dd386297fada6b68e4a32f01ce8920b0423e4d258242ea463b45901ec474beb
   languageName: node
   linkType: hard
 
-"is-callable@npm:^1.1.3, is-callable@npm:^1.2.7":
+"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7":
   version: 1.2.7
   resolution: "is-callable@npm:1.2.7"
   checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f
@@ -10402,24 +10066,21 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2":
-  version: 1.0.2
-  resolution: "is-data-view@npm:1.0.2"
+"is-data-view@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "is-data-view@npm:1.0.1"
   dependencies:
-    call-bound: "npm:^1.0.2"
-    get-intrinsic: "npm:^1.2.6"
     is-typed-array: "npm:^1.1.13"
-  checksum: 10c0/ef3548a99d7e7f1370ce21006baca6d40c73e9f15c941f89f0049c79714c873d03b02dae1c64b3f861f55163ecc16da06506c5b8a1d4f16650b3d9351c380153
+  checksum: 10c0/a3e6ec84efe303da859107aed9b970e018e2bee7ffcb48e2f8096921a493608134240e672a2072577e5f23a729846241d9634806e8a0e51d9129c56d5f65442d
   languageName: node
   linkType: hard
 
-"is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.5, is-date-object@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "is-date-object@npm:1.1.0"
+"is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.5":
+  version: 1.0.5
+  resolution: "is-date-object@npm:1.0.5"
   dependencies:
-    call-bound: "npm:^1.0.2"
-    has-tostringtag: "npm:^1.0.2"
-  checksum: 10c0/1a4d199c8e9e9cac5128d32e6626fa7805175af9df015620ac0d5d45854ccf348ba494679d872d37301032e35a54fc7978fba1687e8721b2139aea7870cafa2f
+    has-tostringtag: "npm:^1.0.0"
+  checksum: 10c0/eed21e5dcc619c48ccef804dfc83a739dbb2abee6ca202838ee1bd5f760fe8d8a93444f0d49012ad19bb7c006186e2884a1b92f6e1c056da7fd23d0a9ad5992e
   languageName: node
   linkType: hard
 
@@ -10445,6 +10106,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"is-electron@npm:^2.2.0":
+  version: 2.2.2
+  resolution: "is-electron@npm:2.2.2"
+  checksum: 10c0/327bb373f7be01b16cdff3998b5ddaa87d28f576092affaa7fe0659571b3306fdd458afbf0683a66841e7999af13f46ad0e1b51647b469526cd05a4dd736438a
+  languageName: node
+  linkType: hard
+
 "is-extendable@npm:^0.1.0, is-extendable@npm:^0.1.1":
   version: 0.1.1
   resolution: "is-extendable@npm:0.1.1"
@@ -10468,12 +10136,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-finalizationregistry@npm:^1.1.0":
-  version: 1.1.1
-  resolution: "is-finalizationregistry@npm:1.1.1"
+"is-finalizationregistry@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "is-finalizationregistry@npm:1.0.2"
   dependencies:
-    call-bound: "npm:^1.0.3"
-  checksum: 10c0/818dff679b64f19e228a8205a1e2d09989a98e98def3a817f889208cfcbf918d321b251aadf2c05918194803ebd2eb01b14fc9d0b2bea53d984f4137bfca5e97
+    call-bind: "npm:^1.0.2"
+  checksum: 10c0/81caecc984d27b1a35c68741156fc651fb1fa5e3e6710d21410abc527eb226d400c0943a167922b2e920f6b3e58b0dede9aa795882b038b85f50b3a4b877db86
   languageName: node
   linkType: hard
 
@@ -10548,7 +10216,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-map@npm:^2.0.3":
+"is-map@npm:^2.0.1, is-map@npm:^2.0.2":
   version: 2.0.3
   resolution: "is-map@npm:2.0.3"
   checksum: 10c0/2c4d431b74e00fdda7162cd8e4b763d6f6f217edf97d4f8538b94b8702b150610e2c64961340015fe8df5b1fcee33ccd2e9b62619c4a8a3a155f8de6d6d355fc
@@ -10562,13 +10230,19 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-number-object@npm:^1.1.1":
-  version: 1.1.1
-  resolution: "is-number-object@npm:1.1.1"
+"is-negative-zero@npm:^2.0.3":
+  version: 2.0.3
+  resolution: "is-negative-zero@npm:2.0.3"
+  checksum: 10c0/bcdcf6b8b9714063ffcfa9929c575ac69bfdabb8f4574ff557dfc086df2836cf07e3906f5bbc4f2a5c12f8f3ba56af640c843cdfc74da8caed86c7c7d66fd08e
+  languageName: node
+  linkType: hard
+
+"is-number-object@npm:^1.0.4":
+  version: 1.0.7
+  resolution: "is-number-object@npm:1.0.7"
   dependencies:
-    call-bound: "npm:^1.0.3"
-    has-tostringtag: "npm:^1.0.2"
-  checksum: 10c0/97b451b41f25135ff021d85c436ff0100d84a039bb87ffd799cbcdbea81ef30c464ced38258cdd34f080be08fc3b076ca1f472086286d2aa43521d6ec6a79f53
+    has-tostringtag: "npm:^1.0.0"
+  checksum: 10c0/aad266da1e530f1804a2b7bd2e874b4869f71c98590b3964f9d06cc9869b18f8d1f4778f838ecd2a11011bce20aeecb53cb269ba916209b79c24580416b74b1b
   languageName: node
   linkType: hard
 
@@ -10620,6 +10294,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"is-path-inside@npm:^3.0.3":
+  version: 3.0.3
+  resolution: "is-path-inside@npm:3.0.3"
+  checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05
+  languageName: node
+  linkType: hard
+
 "is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4":
   version: 2.0.4
   resolution: "is-plain-object@npm:2.0.4"
@@ -10643,15 +10324,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-regex@npm:^1.0.4, is-regex@npm:^1.2.1":
-  version: 1.2.1
-  resolution: "is-regex@npm:1.2.1"
+"is-regex@npm:^1.0.4, is-regex@npm:^1.1.4":
+  version: 1.1.4
+  resolution: "is-regex@npm:1.1.4"
   dependencies:
-    call-bound: "npm:^1.0.2"
-    gopd: "npm:^1.2.0"
-    has-tostringtag: "npm:^1.0.2"
-    hasown: "npm:^2.0.2"
-  checksum: 10c0/1d3715d2b7889932349241680032e85d0b492cfcb045acb75ffc2c3085e8d561184f1f7e84b6f8321935b4aea39bc9c6ba74ed595b57ce4881a51dfdbc214e04
+    call-bind: "npm:^1.0.2"
+    has-tostringtag: "npm:^1.0.0"
+  checksum: 10c0/bb72aae604a69eafd4a82a93002058c416ace8cde95873589a97fc5dac96a6c6c78a9977d487b7b95426a8f5073969124dd228f043f9f604f041f32fcc465fc1
   languageName: node
   linkType: hard
 
@@ -10662,19 +10341,19 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-set@npm:^2.0.3":
+"is-set@npm:^2.0.1, is-set@npm:^2.0.2":
   version: 2.0.3
   resolution: "is-set@npm:2.0.3"
   checksum: 10c0/f73732e13f099b2dc879c2a12341cfc22ccaca8dd504e6edae26484bd5707a35d503fba5b4daad530a9b088ced1ae6c9d8200fd92e09b428fe14ea79ce8080b7
   languageName: node
   linkType: hard
 
-"is-shared-array-buffer@npm:^1.0.4":
-  version: 1.0.4
-  resolution: "is-shared-array-buffer@npm:1.0.4"
+"is-shared-array-buffer@npm:^1.0.2, is-shared-array-buffer@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "is-shared-array-buffer@npm:1.0.3"
   dependencies:
-    call-bound: "npm:^1.0.3"
-  checksum: 10c0/65158c2feb41ff1edd6bbd6fd8403a69861cf273ff36077982b5d4d68e1d59278c71691216a4a64632bd76d4792d4d1d2553901b6666d84ade13bba5ea7bc7db
+    call-bind: "npm:^1.0.7"
+  checksum: 10c0/adc11ab0acbc934a7b9e5e9d6c588d4ec6682f6fea8cda5180721704fa32927582ede5b123349e32517fdadd07958973d24716c80e7ab198970c47acc09e59c7
   languageName: node
   linkType: hard
 
@@ -10699,33 +10378,30 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-string@npm:^1.0.7, is-string@npm:^1.1.1":
-  version: 1.1.1
-  resolution: "is-string@npm:1.1.1"
+"is-string@npm:^1.0.5, is-string@npm:^1.0.7":
+  version: 1.0.7
+  resolution: "is-string@npm:1.0.7"
   dependencies:
-    call-bound: "npm:^1.0.3"
-    has-tostringtag: "npm:^1.0.2"
-  checksum: 10c0/2f518b4e47886bb81567faba6ffd0d8a8333cf84336e2e78bf160693972e32ad00fe84b0926491cc598dee576fdc55642c92e62d0cbe96bf36f643b6f956f94d
+    has-tostringtag: "npm:^1.0.0"
+  checksum: 10c0/905f805cbc6eedfa678aaa103ab7f626aac9ebbdc8737abb5243acaa61d9820f8edc5819106b8fcd1839e33db21de9f0116ae20de380c8382d16dc2a601921f6
   languageName: node
   linkType: hard
 
-"is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1":
-  version: 1.1.1
-  resolution: "is-symbol@npm:1.1.1"
+"is-symbol@npm:^1.0.2, is-symbol@npm:^1.0.3":
+  version: 1.0.4
+  resolution: "is-symbol@npm:1.0.4"
   dependencies:
-    call-bound: "npm:^1.0.2"
-    has-symbols: "npm:^1.1.0"
-    safe-regex-test: "npm:^1.1.0"
-  checksum: 10c0/f08f3e255c12442e833f75a9e2b84b2d4882fdfd920513cf2a4a2324f0a5b076c8fd913778e3ea5d258d5183e9d92c0cd20e04b03ab3df05316b049b2670af1e
+    has-symbols: "npm:^1.0.2"
+  checksum: 10c0/9381dd015f7c8906154dbcbf93fad769de16b4b961edc94f88d26eb8c555935caa23af88bda0c93a18e65560f6d7cca0fd5a3f8a8e1df6f1abbb9bead4502ef7
   languageName: node
   linkType: hard
 
-"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15":
-  version: 1.1.15
-  resolution: "is-typed-array@npm:1.1.15"
+"is-typed-array@npm:^1.1.13":
+  version: 1.1.13
+  resolution: "is-typed-array@npm:1.1.13"
   dependencies:
-    which-typed-array: "npm:^1.1.16"
-  checksum: 10c0/415511da3669e36e002820584e264997ffe277ff136643a3126cc949197e6ca3334d0f12d084e83b1994af2e9c8141275c741cf2b7da5a2ff62dd0cac26f76c4
+    which-typed-array: "npm:^1.1.14"
+  checksum: 10c0/fa5cb97d4a80e52c2cc8ed3778e39f175a1a2ae4ddf3adae3187d69586a1fd57cfa0b095db31f66aa90331e9e3da79184cea9c6abdcd1abc722dc3c3edd51cca
   languageName: node
   linkType: hard
 
@@ -10736,29 +10412,29 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-weakmap@npm:^2.0.2":
+"is-weakmap@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "is-weakmap@npm:2.0.1"
+  checksum: 10c0/9c9fec9efa7bf5030a4a927f33fff2a6976b93646259f92b517d3646c073cc5b98283a162ce75c412b060a46de07032444b530f0a4c9b6e012ef8f1741c3a987
+  languageName: node
+  linkType: hard
+
+"is-weakref@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "is-weakref@npm:1.0.2"
+  dependencies:
+    call-bind: "npm:^1.0.2"
+  checksum: 10c0/1545c5d172cb690c392f2136c23eec07d8d78a7f57d0e41f10078aa4f5daf5d7f57b6513a67514ab4f073275ad00c9822fc8935e00229d0a2089e1c02685d4b1
+  languageName: node
+  linkType: hard
+
+"is-weakset@npm:^2.0.1":
   version: 2.0.2
-  resolution: "is-weakmap@npm:2.0.2"
-  checksum: 10c0/443c35bb86d5e6cc5929cd9c75a4024bb0fff9586ed50b092f94e700b89c43a33b186b76dbc6d54f3d3d09ece689ab38dcdc1af6a482cbe79c0f2da0a17f1299
-  languageName: node
-  linkType: hard
-
-"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.0":
-  version: 1.1.1
-  resolution: "is-weakref@npm:1.1.1"
+  resolution: "is-weakset@npm:2.0.2"
   dependencies:
-    call-bound: "npm:^1.0.3"
-  checksum: 10c0/8e0a9c07b0c780949a100e2cab2b5560a48ecd4c61726923c1a9b77b6ab0aa0046c9e7fb2206042296817045376dee2c8ab1dabe08c7c3dfbf195b01275a085b
-  languageName: node
-  linkType: hard
-
-"is-weakset@npm:^2.0.3":
-  version: 2.0.4
-  resolution: "is-weakset@npm:2.0.4"
-  dependencies:
-    call-bound: "npm:^1.0.3"
-    get-intrinsic: "npm:^1.2.6"
-  checksum: 10c0/6491eba08acb8dc9532da23cb226b7d0192ede0b88f16199e592e4769db0a077119c1f5d2283d1e0d16d739115f70046e887e477eb0e66cd90e1bb29f28ba647
+    call-bind: "npm:^1.0.2"
+    get-intrinsic: "npm:^1.1.1"
+  checksum: 10c0/ef5136bd446ae4603229b897f73efd0720c6ab3ec6cc05c8d5c4b51aa9f95164713c4cad0a22ff1fedf04865ff86cae4648bc1d5eead4b6388e1150525af1cc1
   languageName: node
   linkType: hard
 
@@ -10892,17 +10568,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"iterator.prototype@npm:^1.1.4":
-  version: 1.1.5
-  resolution: "iterator.prototype@npm:1.1.5"
+"iterator.prototype@npm:^1.1.2":
+  version: 1.1.2
+  resolution: "iterator.prototype@npm:1.1.2"
   dependencies:
-    define-data-property: "npm:^1.1.4"
-    es-object-atoms: "npm:^1.0.0"
-    get-intrinsic: "npm:^1.2.6"
-    get-proto: "npm:^1.0.0"
-    has-symbols: "npm:^1.1.0"
-    set-function-name: "npm:^2.0.2"
-  checksum: 10c0/f7a262808e1b41049ab55f1e9c29af7ec1025a000d243b83edf34ce2416eedd56079b117fa59376bb4a724110690f13aa8427f2ee29a09eec63a7e72367626d0
+    define-properties: "npm:^1.2.1"
+    get-intrinsic: "npm:^1.2.1"
+    has-symbols: "npm:^1.0.3"
+    reflect.getprototypeof: "npm:^1.0.4"
+    set-function-name: "npm:^2.0.1"
+  checksum: 10c0/a32151326095e916f306990d909f6bbf23e3221999a18ba686419535dcd1749b10ded505e89334b77dc4c7a58a8508978f0eb16c2c8573e6d412eb7eb894ea79
   languageName: node
   linkType: hard
 
@@ -11411,6 +11086,28 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jpeg-autorotate@npm:^7.1.1":
+  version: 7.1.1
+  resolution: "jpeg-autorotate@npm:7.1.1"
+  dependencies:
+    colors: "npm:^1.4.0"
+    glob: "npm:^7.1.6"
+    jpeg-js: "npm:^0.4.2"
+    piexifjs: "npm:^1.0.6"
+    yargs-parser: "npm:^20.2.1"
+  bin:
+    jpeg-autorotate: src/cli.js
+  checksum: 10c0/75328e15b7abcaf8b36c980495cb0b37ffabeb8921e8576312deac8139a9e8a66f85d9196f314120e4633c76623d1b595e65ca7a87b679511ffb804f880a1644
+  languageName: node
+  linkType: hard
+
+"jpeg-js@npm:^0.4.2":
+  version: 0.4.4
+  resolution: "jpeg-js@npm:0.4.4"
+  checksum: 10c0/4d0d5097f8e55d8bbce6f1dc32ffaf3f43f321f6222e4e6490734fdc6d005322e3bd6fb992c2df7f5b587343b1441a1c333281dc3285bc9116e369fd2a2b43a7
+  languageName: node
+  linkType: hard
+
 "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
   version: 4.0.0
   resolution: "js-tokens@npm:4.0.0"
@@ -11487,45 +11184,55 @@ __metadata:
   languageName: node
   linkType: hard
 
-"jsdom@npm:^26.0.0":
-  version: 26.1.0
-  resolution: "jsdom@npm:26.1.0"
+"jsdom@npm:^25.0.0":
+  version: 25.0.1
+  resolution: "jsdom@npm:25.0.1"
   dependencies:
-    cssstyle: "npm:^4.2.1"
+    cssstyle: "npm:^4.1.0"
     data-urls: "npm:^5.0.0"
-    decimal.js: "npm:^10.5.0"
+    decimal.js: "npm:^10.4.3"
+    form-data: "npm:^4.0.0"
     html-encoding-sniffer: "npm:^4.0.0"
     http-proxy-agent: "npm:^7.0.2"
-    https-proxy-agent: "npm:^7.0.6"
+    https-proxy-agent: "npm:^7.0.5"
     is-potential-custom-element-name: "npm:^1.0.1"
-    nwsapi: "npm:^2.2.16"
-    parse5: "npm:^7.2.1"
-    rrweb-cssom: "npm:^0.8.0"
+    nwsapi: "npm:^2.2.12"
+    parse5: "npm:^7.1.2"
+    rrweb-cssom: "npm:^0.7.1"
     saxes: "npm:^6.0.0"
     symbol-tree: "npm:^3.2.4"
-    tough-cookie: "npm:^5.1.1"
+    tough-cookie: "npm:^5.0.0"
     w3c-xmlserializer: "npm:^5.0.0"
     webidl-conversions: "npm:^7.0.0"
     whatwg-encoding: "npm:^3.1.1"
     whatwg-mimetype: "npm:^4.0.0"
-    whatwg-url: "npm:^14.1.1"
+    whatwg-url: "npm:^14.0.0"
     ws: "npm:^8.18.0"
     xml-name-validator: "npm:^5.0.0"
   peerDependencies:
-    canvas: ^3.0.0
+    canvas: ^2.11.2
   peerDependenciesMeta:
     canvas:
       optional: true
-  checksum: 10c0/5b14a5bc32ce077a06fb42d1ab95b1191afa5cbbce8859e3b96831c5143becbbcbf0511d4d4934e922d2901443ced2cdc3b734c1cf30b5f73b3e067ce457d0f4
+  checksum: 10c0/6bda32a6dfe4e37a30568bf51136bdb3ba9c0b72aadd6356280404275a34c9e097c8c25b5eb3c742e602623741e172da977ff456684befd77c9042ed9bf8c2b4
   languageName: node
   linkType: hard
 
-"jsesc@npm:^3.0.2, jsesc@npm:~3.0.2":
-  version: 3.0.2
-  resolution: "jsesc@npm:3.0.2"
+"jsesc@npm:^2.5.1":
+  version: 2.5.2
+  resolution: "jsesc@npm:2.5.2"
   bin:
     jsesc: bin/jsesc
-  checksum: 10c0/ef22148f9e793180b14d8a145ee6f9f60f301abf443288117b4b6c53d0ecd58354898dc506ccbb553a5f7827965cd38bc5fb726575aae93c5e8915e2de8290e1
+  checksum: 10c0/dbf59312e0ebf2b4405ef413ec2b25abb5f8f4d9bc5fb8d9f90381622ebca5f2af6a6aa9a8578f65903f9e33990a6dc798edd0ce5586894bf0e9e31803a1de88
+  languageName: node
+  linkType: hard
+
+"jsesc@npm:~0.5.0":
+  version: 0.5.0
+  resolution: "jsesc@npm:0.5.0"
+  bin:
+    jsesc: bin/jsesc
+  checksum: 10c0/f93792440ae1d80f091b65f8ceddf8e55c4bb7f1a09dee5dcbdb0db5612c55c0f6045625aa6b7e8edb2e0a4feabd80ee48616dbe2d37055573a84db3d24f96d9
   languageName: node
   linkType: hard
 
@@ -11578,16 +11285,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"json-stable-stringify@npm:^1.1.1":
-  version: 1.2.1
-  resolution: "json-stable-stringify@npm:1.2.1"
+"json-stable-stringify@npm:^1.0.1":
+  version: 1.0.2
+  resolution: "json-stable-stringify@npm:1.0.2"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.3"
-    isarray: "npm:^2.0.5"
     jsonify: "npm:^0.0.1"
-    object-keys: "npm:^1.1.1"
-  checksum: 10c0/e623e7ce89282f089d56454087edb717357e8572089b552fbc6980fb7814dc3943f7d0e4f1a19429a36ce9f4428b6c8ee6883357974457aaaa98daba5adebeea
+  checksum: 10c0/502d021c3c59c09587faa40d7693d77c00460fd6c68bae95d6e35804909ec8c4aec71b136d3a09df61a7ebf803eb6e820f23ede76b77e74b8b02c76afb2ada8c
   languageName: node
   linkType: hard
 
@@ -11657,7 +11360,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"keyv@npm:^4.5.4":
+"keyv@npm:^4.5.3, keyv@npm:^4.5.4":
   version: 4.5.4
   resolution: "keyv@npm:4.5.4"
   dependencies:
@@ -11694,19 +11397,17 @@ __metadata:
   languageName: node
   linkType: hard
 
-"known-css-properties@npm:^0.35.0":
-  version: 0.35.0
-  resolution: "known-css-properties@npm:0.35.0"
-  checksum: 10c0/04a4a2859d62670bb25b5b28091a1f03f6f0d3298a5ed3e7476397c5287b98c434f6dd9c004a0c67a53b7f21acc93f83c972e98c122f568d4d0bd21fd2b90fb6
+"known-css-properties@npm:^0.29.0":
+  version: 0.29.0
+  resolution: "known-css-properties@npm:0.29.0"
+  checksum: 10c0/f66e9992097b8f54e97dbe729943d4a11b8d3ba15f68dbb3deb8bb0122cb89c22c90c9221ecb1e3f2e236838fe3c0faae319b43908c81b6e254ac43cafde2906
   languageName: node
   linkType: hard
 
-"lande@npm:^1.0.10":
-  version: 1.0.10
-  resolution: "lande@npm:1.0.10"
-  dependencies:
-    toygrad: "npm:^2.6.0"
-  checksum: 10c0/27300be5937b6b9e245a7ea7a8216a0dcf5286a3b7ae38886c10c5c75b83fbfa1a69cd6754ab26bb38c6bd18aa8a2dcb62dea873506accb245cf82084acfee71
+"known-css-properties@npm:^0.34.0":
+  version: 0.34.0
+  resolution: "known-css-properties@npm:0.34.0"
+  checksum: 10c0/8549969f02b1858554e89faf4548ece37625d0d21b42e8d54fa53184e68e1512ef2531bb15941575ad816361ab7447b598c1b18c1b96ce0a868333d1a68f2e2c
   languageName: node
   linkType: hard
 
@@ -11743,10 +11444,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"lilconfig@npm:^3.1.2, lilconfig@npm:~3.1.3":
-  version: 3.1.3
-  resolution: "lilconfig@npm:3.1.3"
-  checksum: 10c0/f5604e7240c5c275743561442fbc5abf2a84ad94da0f5adc71d25e31fa8483048de3dcedcb7a44112a942fed305fd75841cdf6c9681c7f640c63f1049e9a5dcc
+"lilconfig@npm:^3.1.2, lilconfig@npm:~3.1.2":
+  version: 3.1.2
+  resolution: "lilconfig@npm:3.1.2"
+  checksum: 10c0/f059630b1a9bddaeba83059db00c672b64dc14074e9f232adce32b38ca1b5686ab737eb665c5ba3c32f147f0002b4bee7311ad0386a9b98547b5623e87071fbe
   languageName: node
   linkType: hard
 
@@ -11758,28 +11459,28 @@ __metadata:
   linkType: hard
 
 "lint-staged@npm:^15.0.0":
-  version: 15.2.11
-  resolution: "lint-staged@npm:15.2.11"
+  version: 15.2.10
+  resolution: "lint-staged@npm:15.2.10"
   dependencies:
     chalk: "npm:~5.3.0"
     commander: "npm:~12.1.0"
-    debug: "npm:~4.4.0"
+    debug: "npm:~4.3.6"
     execa: "npm:~8.0.1"
-    lilconfig: "npm:~3.1.3"
-    listr2: "npm:~8.2.5"
+    lilconfig: "npm:~3.1.2"
+    listr2: "npm:~8.2.4"
     micromatch: "npm:~4.0.8"
     pidtree: "npm:~0.6.0"
     string-argv: "npm:~0.3.2"
-    yaml: "npm:~2.6.1"
+    yaml: "npm:~2.5.0"
   bin:
     lint-staged: bin/lint-staged.js
-  checksum: 10c0/28e2ad08b90460cc18398a023eaf93954d7753f958c2b889ead2d9305407d7b4ef0ee007875410d6ce1df758007fda77e079c82eb79c9ce684fba71e6f7d0452
+  checksum: 10c0/6ad7b41f5e87a84fa2eb1990080ea3c68a2f2031b4e81edcdc2a458cc878538eedb310e6f98ffd878a1287e1a52ac968e540ee8a0e96c247e04b0cbc36421cdd
   languageName: node
   linkType: hard
 
-"listr2@npm:~8.2.5":
-  version: 8.2.5
-  resolution: "listr2@npm:8.2.5"
+"listr2@npm:~8.2.4":
+  version: 8.2.4
+  resolution: "listr2@npm:8.2.4"
   dependencies:
     cli-truncate: "npm:^4.0.0"
     colorette: "npm:^2.0.20"
@@ -11787,7 +11488,7 @@ __metadata:
     log-update: "npm:^6.1.0"
     rfdc: "npm:^1.4.1"
     wrap-ansi: "npm:^9.0.0"
-  checksum: 10c0/f5a9599514b00c27d7eb32d1117c83c61394b2a985ec20e542c798bf91cf42b19340215701522736f5b7b42f557e544afeadec47866e35e5d4f268f552729671
+  checksum: 10c0/df5b129e9767de1997973cec6103cd4bd6fc3b3367685b7c23048d12b61d5b7e44fecd8a3d3534c0e1c963bd5ac43ca501d14712f46fa101050037be323a5c16
   languageName: node
   linkType: hard
 
@@ -11993,13 +11694,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"lru-cache@npm:^11.0.2":
-  version: 11.0.2
-  resolution: "lru-cache@npm:11.0.2"
-  checksum: 10c0/c993b8e06ead0b24b969c1dbb5b301716aed66e320e9014a80012f5febe280b438f28ff50046b2c55ff404e889351ccb332ff91f8dd175a21f5eae80e3fb155f
-  languageName: node
-  linkType: hard
-
 "lru-cache@npm:^5.1.1":
   version: 5.1.1
   resolution: "lru-cache@npm:5.1.1"
@@ -12115,16 +11809,9 @@ __metadata:
   linkType: hard
 
 "marky@npm:^1.2.5":
-  version: 1.3.0
-  resolution: "marky@npm:1.3.0"
-  checksum: 10c0/6619cdb132fdc4f7cd3e2bed6eebf81a38e50ff4b426bbfb354db68731e4adfebf35ebfd7c8e5a6e846cbf9b872588c4f76db25782caee8c1529ec9d483bf98b
-  languageName: node
-  linkType: hard
-
-"math-intrinsics@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "math-intrinsics@npm:1.1.0"
-  checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f
+  version: 1.2.5
+  resolution: "marky@npm:1.2.5"
+  checksum: 10c0/ca8a011f287dab1ac3291df720fc32b366c4cd767347b63722966650405ce71ec6566f71d1e22e1768bf6461a7fd689b9038e7df0fcfb62eacf3a5a6dcac249e
   languageName: node
   linkType: hard
 
@@ -12174,20 +11861,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"mdn-data@npm:2.12.1":
-  version: 2.12.1
-  resolution: "mdn-data@npm:2.12.1"
-  checksum: 10c0/1a09f441bdd423f2b0ab712665a1a3329fe7b15e9a2dad8c1c10c521ddb204ed186e7ac91052fd53a5ae0a07ac6eae53b5bcbb59ba8a1fb654268611297eea4a
-  languageName: node
-  linkType: hard
-
-"mdn-data@npm:^2.12.2":
-  version: 2.13.0
-  resolution: "mdn-data@npm:2.13.0"
-  checksum: 10c0/7f4cbba78ded58d63e28b7be68dc93a97cc4859d474b08e4570a9adb6cc57106c6b860a749b465a39be18eb8b010ad3b042b5d4d475f78fe1b3ea6156926bfa0
-  languageName: node
-  linkType: hard
-
 "media-typer@npm:0.3.0":
   version: 0.3.0
   resolution: "media-typer@npm:0.3.0"
@@ -12278,7 +11951,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:^4.0.8, micromatch@npm:~4.0.8":
+"micromatch@npm:^4.0.4, micromatch@npm:^4.0.8, micromatch@npm:~4.0.8":
   version: 4.0.8
   resolution: "micromatch@npm:4.0.8"
   dependencies:
@@ -12389,7 +12062,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
+"minimatch@npm:9.0.3":
+  version: 9.0.3
+  resolution: "minimatch@npm:9.0.3"
+  dependencies:
+    brace-expansion: "npm:^2.0.1"
+  checksum: 10c0/85f407dcd38ac3e180f425e86553911d101455ca3ad5544d6a7cec16286657e4f8a9aa6695803025c55e31e35a91a2252b5dc8e7d527211278b8b65b4dbd5eac
+  languageName: node
+  linkType: hard
+
+"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
   version: 3.1.2
   resolution: "minimatch@npm:3.1.2"
   dependencies:
@@ -12558,7 +12240,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"ms@npm:2.1.3, ms@npm:^2.1.1, ms@npm:^2.1.3":
+"ms@npm:2.1.2":
+  version: 2.1.2
+  resolution: "ms@npm:2.1.2"
+  checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc
+  languageName: node
+  linkType: hard
+
+"ms@npm:2.1.3, ms@npm:^2.1.1":
   version: 2.1.3
   resolution: "ms@npm:2.1.3"
   checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48
@@ -12593,12 +12282,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"nanoid@npm:^3.3.8":
-  version: 3.3.8
-  resolution: "nanoid@npm:3.3.8"
+"nanoid@npm:^3.3.7":
+  version: 3.3.7
+  resolution: "nanoid@npm:3.3.7"
   bin:
     nanoid: bin/nanoid.cjs
-  checksum: 10c0/4b1bb29f6cfebf3be3bc4ad1f1296fb0a10a3043a79f34fbffe75d1621b4318319211cd420549459018ea3592f0d2f159247a6f874911d6d26eaaadda2478120
+  checksum: 10c0/e3fb661aa083454f40500473bb69eedb85dc160e763150b9a2c567c7e9ff560ce028a9f833123b618a6ea742e311138b591910e795614a629029e86e180660f3
   languageName: node
   linkType: hard
 
@@ -12649,18 +12338,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"node-addon-api@npm:^7.0.0":
-  version: 7.1.1
-  resolution: "node-addon-api@npm:7.1.1"
-  dependencies:
-    node-gyp: "npm:latest"
-  checksum: 10c0/fb32a206276d608037fa1bcd7e9921e177fe992fc610d098aa3128baca3c0050fc1e014fa007e9b3874cf865ddb4f5bd9f43ccb7cbbbe4efaff6a83e920b17e9
-  languageName: node
-  linkType: hard
-
-"node-fetch@npm:^2.6.9":
-  version: 2.7.0
-  resolution: "node-fetch@npm:2.7.0"
+"node-fetch@npm:^2.6.0":
+  version: 2.6.11
+  resolution: "node-fetch@npm:2.6.11"
   dependencies:
     whatwg-url: "npm:^5.0.0"
   peerDependencies:
@@ -12668,7 +12348,7 @@ __metadata:
   peerDependenciesMeta:
     encoding:
       optional: true
-  checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8
+  checksum: 10c0/3ec847ca43f678d07b80abfd85bdf06523c2554ee9a494c992c5fc61f5d9cde9f9f16aa33ff09a62f19eee9d54813b8850d7f054cdfee8b2daf789c57f8eeaea
   languageName: node
   linkType: hard
 
@@ -12748,10 +12428,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"node-releases@npm:^2.0.19":
-  version: 2.0.19
-  resolution: "node-releases@npm:2.0.19"
-  checksum: 10c0/52a0dbd25ccf545892670d1551690fe0facb6a471e15f2cfa1b20142a5b255b3aa254af5f59d6ecb69c2bec7390bc643c43aa63b13bf5e64b6075952e716b1aa
+"node-releases@npm:^2.0.18":
+  version: 2.0.18
+  resolution: "node-releases@npm:2.0.18"
+  checksum: 10c0/786ac9db9d7226339e1dc84bbb42007cb054a346bd9257e6aa154d294f01bc6a6cddb1348fa099f079be6580acbb470e3c048effd5f719325abd0179e566fd27
   languageName: node
   linkType: hard
 
@@ -12834,10 +12514,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"nwsapi@npm:^2.2.16, nwsapi@npm:^2.2.2":
-  version: 2.2.16
-  resolution: "nwsapi@npm:2.2.16"
-  checksum: 10c0/0aa0637f4d51043d0183d994e08336bae996b03b42984381bf09ebdf3ff4909c018eda6b2a8aba0a08f3ea8303db8a0dad0608b38dc0bff15fd87017286ae21a
+"nwsapi@npm:^2.2.12, nwsapi@npm:^2.2.2":
+  version: 2.2.12
+  resolution: "nwsapi@npm:2.2.12"
+  checksum: 10c0/95e9623d63df111405503df8c5d800e26f71675d319e2c9c70cddfa31e5ace1d3f8b6d98d354544fc156a1506d920ec291e303fab761e4f99296868e199a466e
   languageName: node
   linkType: hard
 
@@ -12859,14 +12539,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"object-inspect@npm:^1.13.3":
-  version: 1.13.4
-  resolution: "object-inspect@npm:1.13.4"
-  checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692
+"object-inspect@npm:^1.13.1":
+  version: 1.13.1
+  resolution: "object-inspect@npm:1.13.1"
+  checksum: 10c0/fad603f408e345c82e946abdf4bfd774260a5ed3e5997a0b057c44153ac32c7271ff19e3a5ae39c858da683ba045ccac2f65245c12763ce4e8594f818f4a648d
   languageName: node
   linkType: hard
 
-"object-is@npm:^1.0.1":
+"object-is@npm:^1.0.1, object-is@npm:^1.1.5":
   version: 1.1.6
   resolution: "object-is@npm:1.1.6"
   dependencies:
@@ -12892,17 +12572,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"object.assign@npm:^4.1.4, object.assign@npm:^4.1.7":
-  version: 4.1.7
-  resolution: "object.assign@npm:4.1.7"
+"object.assign@npm:^4.1.4, object.assign@npm:^4.1.5":
+  version: 4.1.5
+  resolution: "object.assign@npm:4.1.5"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.3"
+    call-bind: "npm:^1.0.5"
     define-properties: "npm:^1.2.1"
-    es-object-atoms: "npm:^1.0.0"
-    has-symbols: "npm:^1.1.0"
+    has-symbols: "npm:^1.0.3"
     object-keys: "npm:^1.1.1"
-  checksum: 10c0/3b2732bd860567ea2579d1567525168de925a8d852638612846bd8082b3a1602b7b89b67b09913cbb5b9bd6e95923b2ae73580baa9d99cb4e990564e8cbf5ddc
+  checksum: 10c0/60108e1fa2706f22554a4648299b0955236c62b3685c52abf4988d14fffb0e7731e00aa8c6448397e3eb63d087dcc124a9f21e1980f36d0b2667f3c18bacd469
   languageName: node
   linkType: hard
 
@@ -12962,15 +12640,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"object.values@npm:^1.1.0, object.values@npm:^1.1.6, object.values@npm:^1.2.0, object.values@npm:^1.2.1":
-  version: 1.2.1
-  resolution: "object.values@npm:1.2.1"
+"object.values@npm:^1.1.0, object.values@npm:^1.1.6, object.values@npm:^1.2.0":
+  version: 1.2.0
+  resolution: "object.values@npm:1.2.0"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.3"
+    call-bind: "npm:^1.0.7"
     define-properties: "npm:^1.2.1"
     es-object-atoms: "npm:^1.0.0"
-  checksum: 10c0/3c47814fdc64842ae3d5a74bc9d06bdd8d21563c04d9939bf6716a9c00596a4ebc342552f8934013d1ec991c74e3671b26710a0c51815f0b603795605ab6b2c9
+  checksum: 10c0/15809dc40fd6c5529501324fec5ff08570b7d70fb5ebbe8e2b3901afec35cf2b3dc484d1210c6c642cd3e7e0a5e18dd1d6850115337fef46bdae14ab0cb18ac3
   languageName: node
   linkType: hard
 
@@ -13040,7 +12717,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"opencollective-postinstall@npm:^2.0.3":
+"opencollective-postinstall@npm:^2.0.2":
   version: 2.0.3
   resolution: "opencollective-postinstall@npm:2.0.3"
   bin:
@@ -13088,17 +12765,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"own-keys@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "own-keys@npm:1.0.1"
-  dependencies:
-    get-intrinsic: "npm:^1.2.6"
-    object-keys: "npm:^1.1.1"
-    safe-push-apply: "npm:^1.0.0"
-  checksum: 10c0/6dfeb3455bff92ec3f16a982d4e3e65676345f6902d9f5ded1d8265a6318d0200ce461956d6d1c70053c7fe9f9fe65e552faac03f8140d37ef0fdd108e67013a
-  languageName: node
-  linkType: hard
-
 "p-finally@npm:^1.0.0":
   version: 1.0.0
   resolution: "p-finally@npm:1.0.0"
@@ -13248,12 +12914,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"parse5@npm:^7.0.0, parse5@npm:^7.1.1, parse5@npm:^7.2.1":
-  version: 7.2.1
-  resolution: "parse5@npm:7.2.1"
+"parse5@npm:^7.0.0, parse5@npm:^7.1.1, parse5@npm:^7.1.2":
+  version: 7.1.2
+  resolution: "parse5@npm:7.1.2"
   dependencies:
-    entities: "npm:^4.5.0"
-  checksum: 10c0/829d37a0c709215a887e410a7118d754f8e1afd7edb529db95bc7bbf8045fb0266a7b67801331d8e8d9d073ea75793624ec27ce9ff3b96862c3b9008f4d68e80
+    entities: "npm:^4.4.0"
+  checksum: 10c0/297d7af8224f4b5cb7f6617ecdae98eeaed7f8cbd78956c42785e230505d5a4f07cef352af10d3006fa5c1544b76b57784d3a22d861ae071bbc460c649482bf4
   languageName: node
   linkType: hard
 
@@ -13358,10 +13024,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"path-to-regexp@npm:0.1.12":
-  version: 0.1.12
-  resolution: "path-to-regexp@npm:0.1.12"
-  checksum: 10c0/1c6ff10ca169b773f3bba943bbc6a07182e332464704572962d277b900aeee81ac6aa5d060ff9e01149636c30b1f63af6e69dd7786ba6e0ddb39d4dee1f0645b
+"path-to-regexp@npm:0.1.10":
+  version: 0.1.10
+  resolution: "path-to-regexp@npm:0.1.10"
+  checksum: 10c0/34196775b9113ca6df88e94c8d83ba82c0e1a2063dd33bfe2803a980da8d49b91db8104f49d5191b44ea780d46b8670ce2b7f4a5e349b0c48c6779b653f1afe4
   languageName: node
   linkType: hard
 
@@ -13394,6 +13060,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"performance-now@npm:^0.2.0":
+  version: 0.2.0
+  resolution: "performance-now@npm:0.2.0"
+  checksum: 10c0/d7f3824e443491208f7124b45d3280dbff889f8f048c3aee507109c24644d51a226eb07fd7ac51dd0eef144639590c57410c2d167bd4fdf0c5caa0101a449c3d
+  languageName: node
+  linkType: hard
+
+"performance-now@npm:^2.1.0":
+  version: 2.1.0
+  resolution: "performance-now@npm:2.1.0"
+  checksum: 10c0/22c54de06f269e29f640e0e075207af57de5052a3d15e360c09b9a8663f393f6f45902006c1e71aa8a5a1cdfb1a47fe268826f8496d6425c362f00f5bc3e85d9
+  languageName: node
+  linkType: hard
+
 "pg-cloudflare@npm:^1.1.1":
   version: 1.1.1
   resolution: "pg-cloudflare@npm:1.1.1"
@@ -13422,26 +13102,19 @@ __metadata:
   languageName: node
   linkType: hard
 
-"pg-pool@npm:^3.7.1":
-  version: 3.7.1
-  resolution: "pg-pool@npm:3.7.1"
+"pg-pool@npm:^3.7.0":
+  version: 3.7.0
+  resolution: "pg-pool@npm:3.7.0"
   peerDependencies:
     pg: ">=8.0"
-  checksum: 10c0/65bff013102684774f4cfdffbfe0a2b0614252234561d391608f7fd915477e44f5fd0e1968ecc2f42032dcf8bac241d2f724c4f3df90384567d22df37dd3b6ba
+  checksum: 10c0/9128673cf941f288c0cb1a74ca959a9b4f6075ef73b2cc7dece5d4db3dd7043784869e7c12bce2e69ca0df22132a419cc45c2050b4373632856fe8bae9eb94b5
   languageName: node
   linkType: hard
 
-"pg-protocol@npm:*":
-  version: 1.8.0
-  resolution: "pg-protocol@npm:1.8.0"
-  checksum: 10c0/2be784955599d84b564795952cee52cc2b8eab0be43f74fc1061506353801e282c1d52c9e0691a9b72092c1f3fde370e9b181e80fef6bb82a9b8d1618bfa91e6
-  languageName: node
-  linkType: hard
-
-"pg-protocol@npm:^1.7.1":
-  version: 1.7.1
-  resolution: "pg-protocol@npm:1.7.1"
-  checksum: 10c0/3168d407ddc4c0fa2403eb9b49205399d4bc53dadbafdfcc5d25fa61b860a31c25df25704cf14c8140c80f0a41061d586e5fd5ce9bf800dfb91e9ce810bc2c37
+"pg-protocol@npm:*, pg-protocol@npm:^1.7.0":
+  version: 1.7.0
+  resolution: "pg-protocol@npm:1.7.0"
+  checksum: 10c0/c4af854d9b843c808231c0040fed89f2b9101006157df8da2bb2f62a7dde702de748d852228dc22df41cc7ffddfb526af3bcb34b278b581e9f76a060789186c1
   languageName: node
   linkType: hard
 
@@ -13474,13 +13147,13 @@ __metadata:
   linkType: hard
 
 "pg@npm:^8.5.0":
-  version: 8.13.3
-  resolution: "pg@npm:8.13.3"
+  version: 8.13.0
+  resolution: "pg@npm:8.13.0"
   dependencies:
     pg-cloudflare: "npm:^1.1.1"
     pg-connection-string: "npm:^2.7.0"
-    pg-pool: "npm:^3.7.1"
-    pg-protocol: "npm:^1.7.1"
+    pg-pool: "npm:^3.7.0"
+    pg-protocol: "npm:^1.7.0"
     pg-types: "npm:^2.1.0"
     pgpass: "npm:1.x"
   peerDependencies:
@@ -13491,7 +13164,7 @@ __metadata:
   peerDependenciesMeta:
     pg-native:
       optional: true
-  checksum: 10c0/7296f0e5930b35faef471be2673210cda553b30f1b8e9d176fcc286aa43248e17e09336032bf5a6bba55d2cc2d03afb8a407b5a6e6bc56ebb331c02d1a7ccc05
+  checksum: 10c0/1521189063d2293d62f3fac61e797a3096a62a69668c223827d00b83c17a320805f31f0b5316feb80f8d9eed0c6c32f95146d8aca866af05816a66fd2ba8e32a
   languageName: node
   linkType: hard
 
@@ -13504,17 +13177,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1":
-  version: 1.1.1
-  resolution: "picocolors@npm:1.1.1"
-  checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58
-  languageName: node
-  linkType: hard
-
-"picomatch@npm:2 || 3 || 4, picomatch@npm:^4.0.2":
-  version: 4.0.2
-  resolution: "picomatch@npm:4.0.2"
-  checksum: 10c0/7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc
+"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.0":
+  version: 1.1.0
+  resolution: "picocolors@npm:1.1.0"
+  checksum: 10c0/86946f6032148801ef09c051c6fb13b5cf942eaf147e30ea79edb91dd32d700934edebe782a1078ff859fb2b816792e97ef4dab03d7f0b804f6b01a0df35e023
   languageName: node
   linkType: hard
 
@@ -13534,6 +13200,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"piexifjs@npm:^1.0.6":
+  version: 1.0.6
+  resolution: "piexifjs@npm:1.0.6"
+  checksum: 10c0/69a10fe09c08f1e67e653844ac79e720324a7fa34689b020359d60d98b3a601c070e1759df8f2d97d022298bd2f5b79eed4c92de86c5f215300c8a63adf947b1
+  languageName: node
+  linkType: hard
+
 "pify@npm:^2.0.0":
   version: 2.3.0
   resolution: "pify@npm:2.3.0"
@@ -13564,30 +13237,31 @@ __metadata:
   languageName: node
   linkType: hard
 
-"pino-abstract-transport@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "pino-abstract-transport@npm:2.0.0"
+"pino-abstract-transport@npm:^1.0.0, pino-abstract-transport@npm:^1.2.0":
+  version: 1.2.0
+  resolution: "pino-abstract-transport@npm:1.2.0"
   dependencies:
+    readable-stream: "npm:^4.0.0"
     split2: "npm:^4.0.0"
-  checksum: 10c0/02c05b8f2ffce0d7c774c8e588f61e8b77de8ccb5f8125afd4a7325c9ea0e6af7fb78168999657712ae843e4462bb70ac550dfd6284f930ee57f17f486f25a9f
+  checksum: 10c0/b4ab59529b7a91f488440147fc58ee0827a6c1c5ca3627292339354b1381072c1a6bfa9b46d03ad27872589e8477ecf74da12cf286e1e6b665ac64a3b806bf07
   languageName: node
   linkType: hard
 
 "pino-http@npm:^10.0.0":
-  version: 10.4.0
-  resolution: "pino-http@npm:10.4.0"
+  version: 10.3.0
+  resolution: "pino-http@npm:10.3.0"
   dependencies:
     get-caller-file: "npm:^2.0.5"
     pino: "npm:^9.0.0"
     pino-std-serializers: "npm:^7.0.0"
     process-warning: "npm:^4.0.0"
-  checksum: 10c0/64144e2c94e939070f56ad82dfb012b6a98d21582e0660cf821e7cee64d4e06f7724aa40bc5bf9cd1254d58ab7cbd972dec287b7989eba647d384f6edd8d95fd
+  checksum: 10c0/da95d93e1176c02201f9b9bb0af53ad737105c5772acbb077dcad0f52ebce2438e0e9fc8216cd96396d1305d0ecf1f1d23142c7a50110a701ea093b2ee999ea7
   languageName: node
   linkType: hard
 
-"pino-pretty@npm:^13.0.0":
-  version: 13.0.0
-  resolution: "pino-pretty@npm:13.0.0"
+"pino-pretty@npm:^11.0.0":
+  version: 11.2.2
+  resolution: "pino-pretty@npm:11.2.2"
   dependencies:
     colorette: "npm:^2.0.7"
     dateformat: "npm:^4.6.3"
@@ -13597,14 +13271,15 @@ __metadata:
     joycon: "npm:^3.1.1"
     minimist: "npm:^1.2.6"
     on-exit-leak-free: "npm:^2.1.0"
-    pino-abstract-transport: "npm:^2.0.0"
+    pino-abstract-transport: "npm:^1.0.0"
     pump: "npm:^3.0.0"
+    readable-stream: "npm:^4.0.0"
     secure-json-parse: "npm:^2.4.0"
     sonic-boom: "npm:^4.0.1"
     strip-json-comments: "npm:^3.1.1"
   bin:
     pino-pretty: bin.js
-  checksum: 10c0/015dac25006c1b9820b9e01fccb8a392a019e12b30e6bfc3f3f61ecca8dbabcd000a8f3f64410b620b7f5d08579ba85e6ef137f7fbeaad70d46397a97a5f75ea
+  checksum: 10c0/3ce1769907886a5584f6c8123d9bc987712ad10a375797733a0fe95a238df587dac8e2b709bab291c4e30d41b0cf65808c708c96f8eb98b2778b6df60afa7e66
   languageName: node
   linkType: hard
 
@@ -13616,13 +13291,13 @@ __metadata:
   linkType: hard
 
 "pino@npm:^9.0.0":
-  version: 9.6.0
-  resolution: "pino@npm:9.6.0"
+  version: 9.4.0
+  resolution: "pino@npm:9.4.0"
   dependencies:
     atomic-sleep: "npm:^1.0.0"
     fast-redact: "npm:^3.1.1"
     on-exit-leak-free: "npm:^2.1.0"
-    pino-abstract-transport: "npm:^2.0.0"
+    pino-abstract-transport: "npm:^1.2.0"
     pino-std-serializers: "npm:^7.0.0"
     process-warning: "npm:^4.0.0"
     quick-format-unescaped: "npm:^4.0.3"
@@ -13632,7 +13307,7 @@ __metadata:
     thread-stream: "npm:^3.0.0"
   bin:
     pino: bin.js
-  checksum: 10c0/bcd1e9d9b301bea13b95689ca9ad7105ae9451928fb6c0b67b3e58c5fe37cea1d40665f3d6641e3da00be0bbc17b89031e67abbc8ea6aac6164f399309fd78e7
+  checksum: 10c0/12a3d74968964d92b18ca7d6095a3c5b86478dc22264a37486d64e102085ed08820fcbe75e640acc3542fdf2937a34e5050b624f98e6ac62dd10f5e1328058a2
   languageName: node
   linkType: hard
 
@@ -13686,14 +13361,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss-attribute-case-insensitive@npm:^7.0.1":
-  version: 7.0.1
-  resolution: "postcss-attribute-case-insensitive@npm:7.0.1"
+"postcss-attribute-case-insensitive@npm:^7.0.0":
+  version: 7.0.0
+  resolution: "postcss-attribute-case-insensitive@npm:7.0.0"
   dependencies:
-    postcss-selector-parser: "npm:^7.0.0"
+    postcss-selector-parser: "npm:^6.1.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/48945abe2024e2d2e4c37d30b8c1aaf37af720f24f6a996f7ea7e7ed33621f5c22cf247ed22028c0c922de040c58c0802729bc39b903cb1693f4b63c0b49da34
+  checksum: 10c0/ce2a96bc29f59a6113953f1f72bfa2d4d34e54b194ff4d49aad0d548aa738835afaf479f22060a2605952b842f63aeae278b44e41f8f3a05731df28d08e2df97
   languageName: node
   linkType: hard
 
@@ -13720,18 +13395,18 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss-color-functional-notation@npm:^7.0.8":
-  version: 7.0.8
-  resolution: "postcss-color-functional-notation@npm:7.0.8"
+"postcss-color-functional-notation@npm:^7.0.2":
+  version: 7.0.2
+  resolution: "postcss-color-functional-notation@npm:7.0.2"
   dependencies:
-    "@csstools/css-color-parser": "npm:^3.0.8"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-color-parser": "npm:^3.0.2"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/4180e2f6ee9c925d6c47e727cfc50de2186d4a5cfda6e1ccf28f60e5536b418ddd90f9cc5f9cbcd1900f74098101bca8f844867e16b591e66760300e34257e47
+  checksum: 10c0/e89c0bff94558b0c978ac36f7e02f7f516291f90fd43169d39c63ad2bb0415e3b1c4b3c2469280d578727e850fdf15a557230cb28275f3f0676f0f73187f2867
   languageName: node
   linkType: hard
 
@@ -13785,57 +13460,57 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss-custom-media@npm:^11.0.5":
-  version: 11.0.5
-  resolution: "postcss-custom-media@npm:11.0.5"
+"postcss-custom-media@npm:^11.0.1":
+  version: 11.0.1
+  resolution: "postcss-custom-media@npm:11.0.1"
   dependencies:
-    "@csstools/cascade-layer-name-parser": "npm:^2.0.4"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
-    "@csstools/media-query-list-parser": "npm:^4.0.2"
+    "@csstools/cascade-layer-name-parser": "npm:^2.0.1"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
+    "@csstools/media-query-list-parser": "npm:^3.0.1"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/5ba1ca0383818e83d5f6f398a2b0c12cfda066b5d552adfc0e030a2c5f8690c2cc6224f9a1832a9c780dae3fd8d00d78c4a5c88eb36b731da1752f0c3917d488
+  checksum: 10c0/771b281a28f105370ede7c4a86f9e3dd8d9ec3bf2d2883d4f2cfe9c42b5ec1bf88f713458b356870315d0ba3a285fbeb7bb514a1203d1c4fb113bd9044369bf2
   languageName: node
   linkType: hard
 
-"postcss-custom-properties@npm:^14.0.4":
-  version: 14.0.4
-  resolution: "postcss-custom-properties@npm:14.0.4"
+"postcss-custom-properties@npm:^14.0.1":
+  version: 14.0.1
+  resolution: "postcss-custom-properties@npm:14.0.1"
   dependencies:
-    "@csstools/cascade-layer-name-parser": "npm:^2.0.4"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/cascade-layer-name-parser": "npm:^2.0.1"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/utilities": "npm:^2.0.0"
     postcss-value-parser: "npm:^4.2.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/5b101ee71289657cc2e5a16f4912009c10441052e2c54bd9e4f3d4d72b652bab56adb662ddaa96881413e375cf9852e2159b3c778d953442ce86efb781c3b2bf
+  checksum: 10c0/12180a7f4a4fe2d528387346a1810b82ed870081756dcf6be226c839716ab3f6f4d6ca4c7208a07d7d84bf2c986beef6473e29964e7c2572066fca5d3b000ed5
   languageName: node
   linkType: hard
 
-"postcss-custom-selectors@npm:^8.0.4":
-  version: 8.0.4
-  resolution: "postcss-custom-selectors@npm:8.0.4"
+"postcss-custom-selectors@npm:^8.0.1":
+  version: 8.0.1
+  resolution: "postcss-custom-selectors@npm:8.0.1"
   dependencies:
-    "@csstools/cascade-layer-name-parser": "npm:^2.0.4"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
-    postcss-selector-parser: "npm:^7.0.0"
+    "@csstools/cascade-layer-name-parser": "npm:^2.0.1"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
+    postcss-selector-parser: "npm:^6.1.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/09d494d2580d0a99f57684f79793d03358286c32460b61a84063c33bdde24865771cb1205efe9a8e26a508be24eba4fb93fc7f1e96ba21ca96a5d17fadb24863
+  checksum: 10c0/b867233b3d68fbab90afca8a776eb74196ebc3fac8988175d95118a47993c793138fec6cc580272bb35d9bd31086acbdd33ff86da0cab83ef2f08bfc1c23ecd6
   languageName: node
   linkType: hard
 
-"postcss-dir-pseudo-class@npm:^9.0.1":
-  version: 9.0.1
-  resolution: "postcss-dir-pseudo-class@npm:9.0.1"
+"postcss-dir-pseudo-class@npm:^9.0.0":
+  version: 9.0.0
+  resolution: "postcss-dir-pseudo-class@npm:9.0.0"
   dependencies:
-    postcss-selector-parser: "npm:^7.0.0"
+    postcss-selector-parser: "npm:^6.1.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/da9d3387648c5c3161a653d354c8f3e70a299108df3977e8aa65cf10793e4dd58a2711b3426cd63716245b13584ca8d95adcd6e10e3c9adbc61d08743e2d8690
+  checksum: 10c0/debae71bf508c0e494ebb1892ce6b3c1c4eeb6b23231180151a93920a12fec771815510cdec54db54605e090ae56af9f07c68ef6a61260d0c837adc719f9e1e4
   languageName: node
   linkType: hard
 
@@ -13890,25 +13565,25 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss-focus-visible@npm:^10.0.1":
-  version: 10.0.1
-  resolution: "postcss-focus-visible@npm:10.0.1"
+"postcss-focus-visible@npm:^10.0.0":
+  version: 10.0.0
+  resolution: "postcss-focus-visible@npm:10.0.0"
   dependencies:
-    postcss-selector-parser: "npm:^7.0.0"
+    postcss-selector-parser: "npm:^6.1.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/c5ecc8536a708a49a99d0abd68a88a160664e6c832c808db8edd9f0221e7017a258daa87e49daf2cb098cb037005d46cf492403c8c9c92ad8835d30adaccf665
+  checksum: 10c0/b86b825bac597092b300127c2686c0669ce0766165716ecda42f298f21ca69dda721e44917732cbcb2611a4ab650f1231bf8c5d4d07c9daefef815329251ae8a
   languageName: node
   linkType: hard
 
-"postcss-focus-within@npm:^9.0.1":
-  version: 9.0.1
-  resolution: "postcss-focus-within@npm:9.0.1"
+"postcss-focus-within@npm:^9.0.0":
+  version: 9.0.0
+  resolution: "postcss-focus-within@npm:9.0.0"
   dependencies:
-    postcss-selector-parser: "npm:^7.0.0"
+    postcss-selector-parser: "npm:^6.1.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/d6ab49d2a7f33485a9e137dc77ec92c5619a3ec92e1e672734fc604853ff1f3c0c189085c12461614be4fcb03ea0347d91791a45986a18d50b5228d161eda57a
+  checksum: 10c0/1d6f1b4f4d12e23a2824f394652d520942f00fd582d3016c933a492fe0ba38aaf26bc1855576878aaeaeda1d6fc38da39bb51e8e6470c50ef03f3ea9a286b3d1
   languageName: node
   linkType: hard
 
@@ -13942,18 +13617,18 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss-lab-function@npm:^7.0.8":
-  version: 7.0.8
-  resolution: "postcss-lab-function@npm:7.0.8"
+"postcss-lab-function@npm:^7.0.2":
+  version: 7.0.2
+  resolution: "postcss-lab-function@npm:7.0.2"
   dependencies:
-    "@csstools/css-color-parser": "npm:^3.0.8"
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
+    "@csstools/css-color-parser": "npm:^3.0.2"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
     "@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/5f7b6f95cb3d1aa099c16dcdd89c575f112387600f30949f74c205e0846c9303ca851be794fad9fd56825859d38ac811f972cc34bbc2dfcf71371c640165ddfb
+  checksum: 10c0/6b2be7e762b4ccb58ea9051723d390f6732ad78bb30bfef9499139cf5e2ac160c3de31b2b005fcc30e9fced4abe1685df6cb76c99d548896bae6746105ac8520
   languageName: node
   linkType: hard
 
@@ -13973,14 +13648,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss-logical@npm:^8.1.0":
-  version: 8.1.0
-  resolution: "postcss-logical@npm:8.1.0"
+"postcss-logical@npm:^8.0.0":
+  version: 8.0.0
+  resolution: "postcss-logical@npm:8.0.0"
   dependencies:
     postcss-value-parser: "npm:^4.2.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/0e2e9e901d8a550db7f682d46b1f7e4f363c1ada061dc8e4548e2b563c5e39f3684a2d7c3f11fe061188782bca37874e34967fc6179fa6d98a49ff66a0076d27
+  checksum: 10c0/2caa04e45227ab9dec728416ccde47514e1c347ee72aac58e13ecee3bc7fbc8b53e3fe4f1e2e4396432feb1d54e70a1f06ec5a74d60e84bafff05ab82f196475
   languageName: node
   linkType: hard
 
@@ -14110,16 +13785,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss-nesting@npm:^13.0.1":
-  version: 13.0.1
-  resolution: "postcss-nesting@npm:13.0.1"
+"postcss-nesting@npm:^13.0.0":
+  version: 13.0.0
+  resolution: "postcss-nesting@npm:13.0.0"
   dependencies:
-    "@csstools/selector-resolve-nested": "npm:^3.0.0"
-    "@csstools/selector-specificity": "npm:^5.0.0"
-    postcss-selector-parser: "npm:^7.0.0"
+    "@csstools/selector-resolve-nested": "npm:^2.0.0"
+    "@csstools/selector-specificity": "npm:^4.0.0"
+    postcss-selector-parser: "npm:^6.1.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/549307c272cdd4cb5105d8fbcd582f15a1cb74e5bba240b05b27f77fe0422730be966699a49a9ad15fd9d1bc551c1edbaefb21a69686a9b131b585dbc9d90ebf
+  checksum: 10c0/b82a3c7010f7c6097bd3f3fe6c03f3f3af9a63a58e255f120dadd506a0ea9444aeeaba994f2a3fa34fb26e666dc72032edf5786c5fbdade790b02ad07d91ef95
   languageName: node
   linkType: hard
 
@@ -14274,86 +13949,84 @@ __metadata:
   linkType: hard
 
 "postcss-preset-env@npm:^10.0.0":
-  version: 10.1.5
-  resolution: "postcss-preset-env@npm:10.1.5"
+  version: 10.0.5
+  resolution: "postcss-preset-env@npm:10.0.5"
   dependencies:
-    "@csstools/postcss-cascade-layers": "npm:^5.0.1"
-    "@csstools/postcss-color-function": "npm:^4.0.8"
-    "@csstools/postcss-color-mix-function": "npm:^3.0.8"
-    "@csstools/postcss-content-alt-text": "npm:^2.0.4"
-    "@csstools/postcss-exponential-functions": "npm:^2.0.7"
+    "@csstools/postcss-cascade-layers": "npm:^5.0.0"
+    "@csstools/postcss-color-function": "npm:^4.0.2"
+    "@csstools/postcss-color-mix-function": "npm:^3.0.2"
+    "@csstools/postcss-content-alt-text": "npm:^2.0.1"
+    "@csstools/postcss-exponential-functions": "npm:^2.0.1"
     "@csstools/postcss-font-format-keywords": "npm:^4.0.0"
-    "@csstools/postcss-gamut-mapping": "npm:^2.0.8"
-    "@csstools/postcss-gradients-interpolation-method": "npm:^5.0.8"
-    "@csstools/postcss-hwb-function": "npm:^4.0.8"
+    "@csstools/postcss-gamut-mapping": "npm:^2.0.2"
+    "@csstools/postcss-gradients-interpolation-method": "npm:^5.0.2"
+    "@csstools/postcss-hwb-function": "npm:^4.0.2"
     "@csstools/postcss-ic-unit": "npm:^4.0.0"
-    "@csstools/postcss-initial": "npm:^2.0.1"
-    "@csstools/postcss-is-pseudo-class": "npm:^5.0.1"
-    "@csstools/postcss-light-dark-function": "npm:^2.0.7"
+    "@csstools/postcss-initial": "npm:^2.0.0"
+    "@csstools/postcss-is-pseudo-class": "npm:^5.0.0"
+    "@csstools/postcss-light-dark-function": "npm:^2.0.4"
     "@csstools/postcss-logical-float-and-clear": "npm:^3.0.0"
     "@csstools/postcss-logical-overflow": "npm:^2.0.0"
     "@csstools/postcss-logical-overscroll-behavior": "npm:^2.0.0"
     "@csstools/postcss-logical-resize": "npm:^3.0.0"
-    "@csstools/postcss-logical-viewport-units": "npm:^3.0.3"
-    "@csstools/postcss-media-minmax": "npm:^2.0.7"
-    "@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^3.0.4"
+    "@csstools/postcss-logical-viewport-units": "npm:^3.0.1"
+    "@csstools/postcss-media-minmax": "npm:^2.0.1"
+    "@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^3.0.1"
     "@csstools/postcss-nested-calc": "npm:^4.0.0"
     "@csstools/postcss-normalize-display-values": "npm:^4.0.0"
-    "@csstools/postcss-oklab-function": "npm:^4.0.8"
+    "@csstools/postcss-oklab-function": "npm:^4.0.2"
     "@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
-    "@csstools/postcss-random-function": "npm:^1.0.3"
-    "@csstools/postcss-relative-color-syntax": "npm:^3.0.8"
-    "@csstools/postcss-scope-pseudo-class": "npm:^4.0.1"
-    "@csstools/postcss-sign-functions": "npm:^1.1.2"
-    "@csstools/postcss-stepped-value-functions": "npm:^4.0.7"
-    "@csstools/postcss-text-decoration-shorthand": "npm:^4.0.2"
-    "@csstools/postcss-trigonometric-functions": "npm:^4.0.7"
+    "@csstools/postcss-relative-color-syntax": "npm:^3.0.2"
+    "@csstools/postcss-scope-pseudo-class": "npm:^4.0.0"
+    "@csstools/postcss-stepped-value-functions": "npm:^4.0.1"
+    "@csstools/postcss-text-decoration-shorthand": "npm:^4.0.1"
+    "@csstools/postcss-trigonometric-functions": "npm:^4.0.1"
     "@csstools/postcss-unset-value": "npm:^4.0.0"
     autoprefixer: "npm:^10.4.19"
-    browserslist: "npm:^4.24.4"
-    css-blank-pseudo: "npm:^7.0.1"
-    css-has-pseudo: "npm:^7.0.2"
+    browserslist: "npm:^4.23.1"
+    css-blank-pseudo: "npm:^7.0.0"
+    css-has-pseudo: "npm:^7.0.0"
     css-prefers-color-scheme: "npm:^10.0.0"
-    cssdb: "npm:^8.2.3"
-    postcss-attribute-case-insensitive: "npm:^7.0.1"
+    cssdb: "npm:^8.1.1"
+    postcss-attribute-case-insensitive: "npm:^7.0.0"
     postcss-clamp: "npm:^4.1.0"
-    postcss-color-functional-notation: "npm:^7.0.8"
+    postcss-color-functional-notation: "npm:^7.0.2"
     postcss-color-hex-alpha: "npm:^10.0.0"
     postcss-color-rebeccapurple: "npm:^10.0.0"
-    postcss-custom-media: "npm:^11.0.5"
-    postcss-custom-properties: "npm:^14.0.4"
-    postcss-custom-selectors: "npm:^8.0.4"
-    postcss-dir-pseudo-class: "npm:^9.0.1"
+    postcss-custom-media: "npm:^11.0.1"
+    postcss-custom-properties: "npm:^14.0.1"
+    postcss-custom-selectors: "npm:^8.0.1"
+    postcss-dir-pseudo-class: "npm:^9.0.0"
     postcss-double-position-gradients: "npm:^6.0.0"
-    postcss-focus-visible: "npm:^10.0.1"
-    postcss-focus-within: "npm:^9.0.1"
+    postcss-focus-visible: "npm:^10.0.0"
+    postcss-focus-within: "npm:^9.0.0"
     postcss-font-variant: "npm:^5.0.0"
     postcss-gap-properties: "npm:^6.0.0"
     postcss-image-set-function: "npm:^7.0.0"
-    postcss-lab-function: "npm:^7.0.8"
-    postcss-logical: "npm:^8.1.0"
-    postcss-nesting: "npm:^13.0.1"
+    postcss-lab-function: "npm:^7.0.2"
+    postcss-logical: "npm:^8.0.0"
+    postcss-nesting: "npm:^13.0.0"
     postcss-opacity-percentage: "npm:^3.0.0"
     postcss-overflow-shorthand: "npm:^6.0.0"
     postcss-page-break: "npm:^3.0.4"
     postcss-place: "npm:^10.0.0"
-    postcss-pseudo-class-any-link: "npm:^10.0.1"
+    postcss-pseudo-class-any-link: "npm:^10.0.0"
     postcss-replace-overflow-wrap: "npm:^4.0.0"
-    postcss-selector-not: "npm:^8.0.1"
+    postcss-selector-not: "npm:^8.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/5ed5aeb7c9718230742a56d9b49e05a90135bc4bb77f97d9978bdb0b999d36a2d6175d99360c966cb7a307c9efe4b8792f4c0b79ec99a233f9e1c1ebae4244f0
+  checksum: 10c0/db5eb1175cb26bed3f1a4c47acc67935ffc784520321470520e59de366ac6f91be1e609fe36056af707ed20f7910721287cff0fae416c437dd3e944de13ffd05
   languageName: node
   linkType: hard
 
-"postcss-pseudo-class-any-link@npm:^10.0.1":
-  version: 10.0.1
-  resolution: "postcss-pseudo-class-any-link@npm:10.0.1"
+"postcss-pseudo-class-any-link@npm:^10.0.0":
+  version: 10.0.0
+  resolution: "postcss-pseudo-class-any-link@npm:10.0.0"
   dependencies:
-    postcss-selector-parser: "npm:^7.0.0"
+    postcss-selector-parser: "npm:^6.1.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/95e883996e87baf14fc09d25f9a763a2e9d599eb3b9c6b736e83a8c3d0b55841bcb886bccdf51b5b7fefc128cbd0187ad8841f59878f85bd1613642e592d7673
+  checksum: 10c0/8357716e0ba0f01c70dba65a1efd268f610249ac2fbd41833e5e87dc19ffa7911c8d5e234d0d7c77d94ba6cdfa04fe7f0f98461c34f64cdbb59abd9737ab7d32
   languageName: node
   linkType: hard
 
@@ -14389,19 +14062,19 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss-resolve-nested-selector@npm:^0.1.6":
+"postcss-resolve-nested-selector@npm:^0.1.1, postcss-resolve-nested-selector@npm:^0.1.6":
   version: 0.1.6
   resolution: "postcss-resolve-nested-selector@npm:0.1.6"
   checksum: 10c0/84213a2bccce481b1569c595a3c547b25c6ef1cca839fbd6c82c12ab407559966e968613c7454b573aa54f38cfd7e900c1fd603f0efc9f51939ab9f93f115455
   languageName: node
   linkType: hard
 
-"postcss-safe-parser@npm:^7.0.1":
-  version: 7.0.1
-  resolution: "postcss-safe-parser@npm:7.0.1"
+"postcss-safe-parser@npm:^7.0.0":
+  version: 7.0.0
+  resolution: "postcss-safe-parser@npm:7.0.0"
   peerDependencies:
     postcss: ^8.4.31
-  checksum: 10c0/6957b10b818bd8d4664ec0e548af967f7549abedfb37f844d389571d36af681340f41f9477b9ccf34bcc7599bdef222d1d72e79c64373001fae77089fba6d965
+  checksum: 10c0/4217afd8ce2809e959dc365e4675f499303cc6b91f94db06c8164422822db2d3b3124df701ee2234db4127ad05619b016bfb9c2bccae9bf9cf898a396f1632c9
   languageName: node
   linkType: hard
 
@@ -14414,18 +14087,18 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss-selector-not@npm:^8.0.1":
-  version: 8.0.1
-  resolution: "postcss-selector-not@npm:8.0.1"
+"postcss-selector-not@npm:^8.0.0":
+  version: 8.0.0
+  resolution: "postcss-selector-not@npm:8.0.0"
   dependencies:
-    postcss-selector-parser: "npm:^7.0.0"
+    postcss-selector-parser: "npm:^6.1.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/491ea3dcc421cd90135be786078521605e2062fb93624ea8813cfd5ba0d35143f931e2e608d5f20effd5ea7d3f4786d2afea2afa42d117779a0288e135f132b6
+  checksum: 10c0/677f2cd9d0cd481d276663b57001b2ba96db94ad5bba397f277e53d560ccb074b27c21792deff44720a9f2d96da85fa34f438bb1d33198305b5866b35f1a4708
   languageName: node
   linkType: hard
 
-"postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.1.2":
+"postcss-selector-parser@npm:^6.0.13, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.1.0, postcss-selector-parser@npm:^6.1.2":
   version: 6.1.2
   resolution: "postcss-selector-parser@npm:6.1.2"
   dependencies:
@@ -14435,16 +14108,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss-selector-parser@npm:^7.0.0":
-  version: 7.0.0
-  resolution: "postcss-selector-parser@npm:7.0.0"
-  dependencies:
-    cssesc: "npm:^3.0.0"
-    util-deprecate: "npm:^1.0.2"
-  checksum: 10c0/e96e096afcce70bf5c97789f5ea09d7415ae5eb701d82b05b5e8532885d31363b484fcb1ca9488c9a331f30508d9e5bb6c3109eb2eb5067ef3d3919f9928cd9d
-  languageName: node
-  linkType: hard
-
 "postcss-svgo@npm:^7.0.1":
   version: 7.0.1
   resolution: "postcss-svgo@npm:7.0.1"
@@ -14475,14 +14138,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss@npm:^8.2.15, postcss@npm:^8.4.24, postcss@npm:^8.4.49":
-  version: 8.5.3
-  resolution: "postcss@npm:8.5.3"
+"postcss@npm:^8.2.15, postcss@npm:^8.4.24, postcss@npm:^8.4.41":
+  version: 8.4.47
+  resolution: "postcss@npm:8.4.47"
   dependencies:
-    nanoid: "npm:^3.3.8"
-    picocolors: "npm:^1.1.1"
+    nanoid: "npm:^3.3.7"
+    picocolors: "npm:^1.1.0"
     source-map-js: "npm:^1.2.1"
-  checksum: 10c0/b75510d7b28c3ab728c8733dd01538314a18c52af426f199a3c9177e63eb08602a3938bfb66b62dc01350b9aed62087eabbf229af97a1659eb8d3513cec823b3
+  checksum: 10c0/929f68b5081b7202709456532cee2a145c1843d391508c5a09de2517e8c4791638f71dd63b1898dba6712f8839d7a6da046c72a5e44c162e908f5911f57b5f44
   languageName: node
   linkType: hard
 
@@ -14561,11 +14224,11 @@ __metadata:
   linkType: hard
 
 "prettier@npm:^3.3.3":
-  version: 3.4.2
-  resolution: "prettier@npm:3.4.2"
+  version: 3.3.3
+  resolution: "prettier@npm:3.3.3"
   bin:
     prettier: bin/prettier.cjs
-  checksum: 10c0/99e076a26ed0aba4ebc043880d0f08bbb8c59a4c6641cdee6cdadf2205bdd87aa1d7823f50c3aea41e015e99878d37c58d7b5f0e663bba0ef047f94e36b96446
+  checksum: 10c0/b85828b08e7505716324e4245549b9205c0cacb25342a030ba8885aba2039a115dbcf75a0b7ca3b37bc9d101ee61fab8113fc69ca3359f2a226f1ecc07ad2e26
   languageName: node
   linkType: hard
 
@@ -14663,7 +14326,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"prop-types@npm:^15.5.10, prop-types@npm:^15.5.4, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
+"prop-types@npm:^15.5.10, prop-types@npm:^15.5.4, prop-types@npm:^15.5.8, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
   version: 15.8.1
   resolution: "prop-types@npm:15.8.1"
   dependencies:
@@ -14801,6 +14464,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"raf@npm:^3.1.0":
+  version: 3.4.1
+  resolution: "raf@npm:3.4.1"
+  dependencies:
+    performance-now: "npm:^2.1.0"
+  checksum: 10c0/337f0853c9e6a77647b0f499beedafea5d6facfb9f2d488a624f88b03df2be72b8a0e7f9118a3ff811377d534912039a3311815700d2b6d2313f82f736f9eb6e
+  languageName: node
+  linkType: hard
+
 "randombytes@npm:^2.0.0, randombytes@npm:^2.0.1, randombytes@npm:^2.0.5, randombytes@npm:^2.1.0":
   version: 2.1.0
   resolution: "randombytes@npm:2.1.0"
@@ -14923,25 +14595,27 @@ __metadata:
   languageName: node
   linkType: hard
 
-"react-intl@npm:^7.1.10":
-  version: 7.1.10
-  resolution: "react-intl@npm:7.1.10"
+"react-intl@npm:^6.4.2":
+  version: 6.7.0
+  resolution: "react-intl@npm:6.7.0"
   dependencies:
-    "@formatjs/ecma402-abstract": "npm:2.3.4"
-    "@formatjs/icu-messageformat-parser": "npm:2.11.2"
-    "@formatjs/intl": "npm:3.1.6"
+    "@formatjs/ecma402-abstract": "npm:2.0.0"
+    "@formatjs/icu-messageformat-parser": "npm:2.7.8"
+    "@formatjs/intl": "npm:2.10.5"
+    "@formatjs/intl-displaynames": "npm:6.6.8"
+    "@formatjs/intl-listformat": "npm:7.5.7"
     "@types/hoist-non-react-statics": "npm:^3.3.1"
-    "@types/react": "npm:16 || 17 || 18 || 19"
+    "@types/react": "npm:16 || 17 || 18"
     hoist-non-react-statics: "npm:^3.3.2"
-    intl-messageformat: "npm:10.7.16"
-    tslib: "npm:^2.8.0"
+    intl-messageformat: "npm:10.5.14"
+    tslib: "npm:^2.4.0"
   peerDependencies:
-    react: 16 || 17 || 18 || 19
-    typescript: ^5.6.0
+    react: ^16.6.0 || 17 || 18
+    typescript: ^4.7 || 5
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: 10c0/7d47ac7e2284eb6d920d26d5cdcfee33f54805eb27284dab969ac931139c5eacb2b24f60c8e7645a623b768d0fdfa5dfc4df63a9270301d57aa356ddf231c484
+  checksum: 10c0/210088bf0e934ad5f09d8e7c6d7d72682bb806583645fb333d4efd8ae55585b675ea8e47bb240140d5143ca15ecc0457c3ddc3e8ca45e9b576bce1fa2f9886b3
   languageName: node
   linkType: hard
 
@@ -14973,6 +14647,30 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react-motion@npm:^0.5.2":
+  version: 0.5.2
+  resolution: "react-motion@npm:0.5.2"
+  dependencies:
+    performance-now: "npm:^0.2.0"
+    prop-types: "npm:^15.5.8"
+    raf: "npm:^3.1.0"
+  peerDependencies:
+    react: ^0.14.9 || ^15.3.0 || ^16.0.0
+  checksum: 10c0/4ea6f1cc7079f0161fd786cc755133a822d87d9c0510369b8fb348d9ad602111efa2e3496dbcc390c967229e39e3eb5f6dd5dd6d3d124289443de31d6035a6c8
+  languageName: node
+  linkType: hard
+
+"react-notification@npm:^6.8.5":
+  version: 6.8.5
+  resolution: "react-notification@npm:6.8.5"
+  dependencies:
+    prop-types: "npm:^15.6.2"
+  peerDependencies:
+    react: ^0.14.0 || ^15.0.0 || ^16.0.0
+  checksum: 10c0/14ffb71a5b18301830699b814d1de2421f4f43f31df5b95efd95cd47548a0d7597ec58abc16a12191958cad398495eba9274193af3294863e2864d32ea79f2c6
+  languageName: node
+  linkType: hard
+
 "react-overlays@npm:^5.2.1":
   version: 5.2.1
   resolution: "react-overlays@npm:5.2.1"
@@ -15008,21 +14706,21 @@ __metadata:
   linkType: hard
 
 "react-redux@npm:^9.0.4, react-redux@npm:^9.1.2":
-  version: 9.2.0
-  resolution: "react-redux@npm:9.2.0"
+  version: 9.1.2
+  resolution: "react-redux@npm:9.1.2"
   dependencies:
-    "@types/use-sync-external-store": "npm:^0.0.6"
-    use-sync-external-store: "npm:^1.4.0"
+    "@types/use-sync-external-store": "npm:^0.0.3"
+    use-sync-external-store: "npm:^1.0.0"
   peerDependencies:
-    "@types/react": ^18.2.25 || ^19
-    react: ^18.0 || ^19
+    "@types/react": ^18.2.25
+    react: ^18.0
     redux: ^5.0.0
   peerDependenciesMeta:
     "@types/react":
       optional: true
     redux:
       optional: true
-  checksum: 10c0/00d485f9d9219ca1507b4d30dde5f6ff8fb68ba642458f742e0ec83af052f89e65cd668249b99299e1053cc6ad3d2d8ac6cb89e2f70d2ac5585ae0d7fa0ef259
+  checksum: 10c0/56ac98228e011b26e0202346af9c8dd408ad5ea8235d8761c8e05ea0953b8ca801cdf9d1f481fdec7b285d7f30ceef7238b46b3df7636ef77dd5c2ea8c5be5b2
   languageName: node
   linkType: hard
 
@@ -15078,8 +14776,8 @@ __metadata:
   linkType: hard
 
 "react-select@npm:^5.7.3":
-  version: 5.10.1
-  resolution: "react-select@npm:5.10.1"
+  version: 5.8.1
+  resolution: "react-select@npm:5.8.1"
   dependencies:
     "@babel/runtime": "npm:^7.12.0"
     "@emotion/cache": "npm:^11.4.0"
@@ -15089,11 +14787,11 @@ __metadata:
     memoize-one: "npm:^6.0.0"
     prop-types: "npm:^15.6.0"
     react-transition-group: "npm:^4.3.0"
-    use-isomorphic-layout-effect: "npm:^1.2.0"
+    use-isomorphic-layout-effect: "npm:^1.1.2"
   peerDependencies:
-    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
-    react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
-  checksum: 10c0/0d10a249b96150bd648f2575d59c848b8fac7f4d368a97ae84e4aaba5bbc1035deba4cdc82e49a43904b79ec50494505809618b0e98022b2d51e7629551912ed
+    react: ^16.8.0 || ^17.0.0 || ^18.0.0
+    react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+  checksum: 10c0/0fd73e1e472105f980e09c86f0e6adbdc9f2f5c1befa275b08c71653becdd1829f596155a81b5085cb86f18b20bf4f4cc439ab5fe23e68f326e169dcfe00ccf6
   languageName: node
   linkType: hard
 
@@ -15183,15 +14881,15 @@ __metadata:
   linkType: hard
 
 "react-textarea-autosize@npm:^8.4.1":
-  version: 8.5.7
-  resolution: "react-textarea-autosize@npm:8.5.7"
+  version: 8.5.3
+  resolution: "react-textarea-autosize@npm:8.5.3"
   dependencies:
     "@babel/runtime": "npm:^7.20.13"
     use-composed-ref: "npm:^1.3.0"
     use-latest: "npm:^1.2.1"
   peerDependencies:
-    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
-  checksum: 10c0/ff004797ea28faca442460c42b30042d4c34a140f324eeeddee74508688dbc0f98966d21282c945630655006ad28a87edbcb59e6da7f9e762f4f3042c72f9f24
+    react: ^16.8.0 || ^17.0.0 || ^18.0.0
+  checksum: 10c0/33d38a6d96cf584842695b50c341980944ece23a42155bf0bd1958f02396adb185c7720b88678dc677817fe111783059c0ebcdf7761644006892583b10e258ee
   languageName: node
   linkType: hard
 
@@ -15258,6 +14956,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"readable-stream@npm:^4.0.0":
+  version: 4.4.2
+  resolution: "readable-stream@npm:4.4.2"
+  dependencies:
+    abort-controller: "npm:^3.0.0"
+    buffer: "npm:^6.0.3"
+    events: "npm:^3.3.0"
+    process: "npm:^0.11.10"
+    string_decoder: "npm:^1.3.0"
+  checksum: 10c0/cf7cc8daa2b57872d120945a20a1458c13dcb6c6f352505421115827b18ac4df0e483ac1fe195cb1f5cd226e1073fc55b92b569269d8299e8530840bcdbba40c
+  languageName: node
+  linkType: hard
+
 "readdirp@npm:^2.2.1":
   version: 2.2.1
   resolution: "readdirp@npm:2.2.1"
@@ -15352,28 +15063,26 @@ __metadata:
   languageName: node
   linkType: hard
 
-"reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9":
-  version: 1.0.10
-  resolution: "reflect.getprototypeof@npm:1.0.10"
+"reflect.getprototypeof@npm:^1.0.4":
+  version: 1.0.4
+  resolution: "reflect.getprototypeof@npm:1.0.4"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    define-properties: "npm:^1.2.1"
-    es-abstract: "npm:^1.23.9"
-    es-errors: "npm:^1.3.0"
-    es-object-atoms: "npm:^1.0.0"
-    get-intrinsic: "npm:^1.2.7"
-    get-proto: "npm:^1.0.1"
-    which-builtin-type: "npm:^1.2.1"
-  checksum: 10c0/7facec28c8008876f8ab98e80b7b9cb4b1e9224353fd4756dda5f2a4ab0d30fa0a5074777c6df24e1e0af463a2697513b0a11e548d99cf52f21f7bc6ba48d3ac
+    call-bind: "npm:^1.0.2"
+    define-properties: "npm:^1.2.0"
+    es-abstract: "npm:^1.22.1"
+    get-intrinsic: "npm:^1.2.1"
+    globalthis: "npm:^1.0.3"
+    which-builtin-type: "npm:^1.1.3"
+  checksum: 10c0/02104cdd22658b637efe6b1df73658edab539268347327c8250a72d0cb273dcdf280c284e2d94155d22601d022d16be1a816a8616d679e447cbcbde9860d15cb
   languageName: node
   linkType: hard
 
-"regenerate-unicode-properties@npm:^10.2.0":
-  version: 10.2.0
-  resolution: "regenerate-unicode-properties@npm:10.2.0"
+"regenerate-unicode-properties@npm:^10.1.0":
+  version: 10.1.1
+  resolution: "regenerate-unicode-properties@npm:10.1.1"
   dependencies:
     regenerate: "npm:^1.4.2"
-  checksum: 10c0/5510785eeaf56bbfdf4e663d6753f125c08d2a372d4107bc1b756b7bf142e2ed80c2733a8b54e68fb309ba37690e66a0362699b0e21d5c1f0255dea1b00e6460
+  checksum: 10c0/89adb5ee5ba081380c78f9057c02e156a8181969f6fcca72451efc45612e0c3df767b4333f8d8479c274d9c6fe52ec4854f0d8a22ef95dccbe87da8e5f2ac77d
   languageName: node
   linkType: hard
 
@@ -15424,49 +15133,40 @@ __metadata:
   languageName: node
   linkType: hard
 
-"regexp.prototype.flags@npm:^1.2.0, regexp.prototype.flags@npm:^1.5.3":
-  version: 1.5.4
-  resolution: "regexp.prototype.flags@npm:1.5.4"
+"regexp.prototype.flags@npm:^1.2.0, regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.2":
+  version: 1.5.2
+  resolution: "regexp.prototype.flags@npm:1.5.2"
   dependencies:
-    call-bind: "npm:^1.0.8"
+    call-bind: "npm:^1.0.6"
     define-properties: "npm:^1.2.1"
     es-errors: "npm:^1.3.0"
-    get-proto: "npm:^1.0.1"
-    gopd: "npm:^1.2.0"
-    set-function-name: "npm:^2.0.2"
-  checksum: 10c0/83b88e6115b4af1c537f8dabf5c3744032cb875d63bc05c288b1b8c0ef37cbe55353f95d8ca817e8843806e3e150b118bc624e4279b24b4776b4198232735a77
+    set-function-name: "npm:^2.0.1"
+  checksum: 10c0/0f3fc4f580d9c349f8b560b012725eb9c002f36daa0041b3fbf6f4238cb05932191a4d7d5db3b5e2caa336d5150ad0402ed2be81f711f9308fe7e1a9bf9bd552
   languageName: node
   linkType: hard
 
-"regexpu-core@npm:^6.1.1":
-  version: 6.1.1
-  resolution: "regexpu-core@npm:6.1.1"
+"regexpu-core@npm:^5.3.1":
+  version: 5.3.2
+  resolution: "regexpu-core@npm:5.3.2"
   dependencies:
+    "@babel/regjsgen": "npm:^0.8.0"
     regenerate: "npm:^1.4.2"
-    regenerate-unicode-properties: "npm:^10.2.0"
-    regjsgen: "npm:^0.8.0"
-    regjsparser: "npm:^0.11.0"
+    regenerate-unicode-properties: "npm:^10.1.0"
+    regjsparser: "npm:^0.9.1"
     unicode-match-property-ecmascript: "npm:^2.0.0"
     unicode-match-property-value-ecmascript: "npm:^2.1.0"
-  checksum: 10c0/07d49697e20f9b65977535abba4858b7f5171c13f7c366be53ec1886d3d5f69f1b98cc6a6e63cf271adda077c3366a4c851c7473c28bbd69cf5a6b6b008efc3e
+  checksum: 10c0/7945d5ab10c8bbed3ca383d4274687ea825aee4ab93a9c51c6e31e1365edd5ea807f6908f800ba017b66c462944ba68011164e7055207747ab651f8111ef3770
   languageName: node
   linkType: hard
 
-"regjsgen@npm:^0.8.0":
-  version: 0.8.0
-  resolution: "regjsgen@npm:0.8.0"
-  checksum: 10c0/44f526c4fdbf0b29286101a282189e4dbb303f4013cf3fea058668d96d113b9180d3d03d1e13f6d4cbde38b7728bf951aecd9dc199938c080093a9a6f0d7a6bd
-  languageName: node
-  linkType: hard
-
-"regjsparser@npm:^0.11.0":
-  version: 0.11.0
-  resolution: "regjsparser@npm:0.11.0"
+"regjsparser@npm:^0.9.1":
+  version: 0.9.1
+  resolution: "regjsparser@npm:0.9.1"
   dependencies:
-    jsesc: "npm:~3.0.2"
+    jsesc: "npm:~0.5.0"
   bin:
     regjsparser: bin/parser
-  checksum: 10c0/155143a8f2c95e3170df4fff10ddf3f16a351b5d2b8cbb257e9f4a50abb9a980a28af0936b5bf850fee767537ffa8eb77c6b211fe8be19834dbe584dfd950c62
+  checksum: 10c0/fe44fcf19a99fe4f92809b0b6179530e5ef313ff7f87df143b08ce9a2eb3c4b6189b43735d645be6e8f4033bfb015ed1ca54f0583bc7561bed53fd379feb8225
   languageName: node
   linkType: hard
 
@@ -15753,10 +15453,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"rrweb-cssom@npm:^0.8.0":
-  version: 0.8.0
-  resolution: "rrweb-cssom@npm:0.8.0"
-  checksum: 10c0/56f2bfd56733adb92c0b56e274c43f864b8dd48784d6fe946ef5ff8d438234015e59ad837fc2ad54714b6421384141c1add4eb569e72054e350d1f8a50b8ac7b
+"rrweb-cssom@npm:^0.7.1":
+  version: 0.7.1
+  resolution: "rrweb-cssom@npm:0.7.1"
+  checksum: 10c0/127b8ca6c8aac45e2755abbae6138d4a813b1bedc2caabf79466ae83ab3cfc84b5bfab513b7033f0aa4561c7753edf787d0dd01163ceacdee2e8eb1b6bf7237e
   languageName: node
   linkType: hard
 
@@ -15769,16 +15469,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"safe-array-concat@npm:^1.0.0, safe-array-concat@npm:^1.1.3":
-  version: 1.1.3
-  resolution: "safe-array-concat@npm:1.1.3"
+"safe-array-concat@npm:^1.0.0, safe-array-concat@npm:^1.1.2":
+  version: 1.1.2
+  resolution: "safe-array-concat@npm:1.1.2"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.2"
-    get-intrinsic: "npm:^1.2.6"
-    has-symbols: "npm:^1.1.0"
+    call-bind: "npm:^1.0.7"
+    get-intrinsic: "npm:^1.2.4"
+    has-symbols: "npm:^1.0.3"
     isarray: "npm:^2.0.5"
-  checksum: 10c0/43c86ffdddc461fb17ff8a17c5324f392f4868f3c7dd2c6a5d9f5971713bc5fd755667212c80eab9567595f9a7509cc2f83e590ddaebd1bd19b780f9c79f9a8d
+  checksum: 10c0/12f9fdb01c8585e199a347eacc3bae7b5164ae805cdc8c6707199dbad5b9e30001a50a43c4ee24dc9ea32dbb7279397850e9208a7e217f4d8b1cf5d90129dec9
   languageName: node
   linkType: hard
 
@@ -15796,24 +15495,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"safe-push-apply@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "safe-push-apply@npm:1.0.0"
+"safe-regex-test@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "safe-regex-test@npm:1.0.3"
   dependencies:
+    call-bind: "npm:^1.0.6"
     es-errors: "npm:^1.3.0"
-    isarray: "npm:^2.0.5"
-  checksum: 10c0/831f1c9aae7436429e7862c7e46f847dfe490afac20d0ee61bae06108dbf5c745a0de3568ada30ccdd3eeb0864ca8331b2eef703abd69bfea0745b21fd320750
-  languageName: node
-  linkType: hard
-
-"safe-regex-test@npm:^1.0.3, safe-regex-test@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "safe-regex-test@npm:1.1.0"
-  dependencies:
-    call-bound: "npm:^1.0.2"
-    es-errors: "npm:^1.3.0"
-    is-regex: "npm:^1.2.1"
-  checksum: 10c0/f2c25281bbe5d39cddbbce7f86fca5ea9b3ce3354ea6cd7c81c31b006a5a9fff4286acc5450a3b9122c56c33eba69c56b9131ad751457b2b4a585825e6a10665
+    is-regex: "npm:^1.1.4"
+  checksum: 10c0/900bf7c98dc58f08d8523b7012b468e4eb757afa624f198902c0643d7008ba777b0bdc35810ba0b758671ce887617295fb742b3f3968991b178ceca54cb07603
   languageName: node
   linkType: hard
 
@@ -15866,19 +15555,15 @@ __metadata:
   linkType: hard
 
 "sass@npm:^1.62.1":
-  version: 1.85.1
-  resolution: "sass@npm:1.85.1"
+  version: 1.79.3
+  resolution: "sass@npm:1.79.3"
   dependencies:
-    "@parcel/watcher": "npm:^2.4.1"
     chokidar: "npm:^4.0.0"
-    immutable: "npm:^5.0.2"
+    immutable: "npm:^4.0.0"
     source-map-js: "npm:>=0.6.2 <2.0.0"
-  dependenciesMeta:
-    "@parcel/watcher":
-      optional: true
   bin:
     sass: sass.js
-  checksum: 10c0/f843aa1df1dca2f0e9cb2fb247e4939fd514ae4c182cdd1900a0622c0d71b40dfb1c4225f78b78e165a318287ca137ec597695db3e496408bd16a921a2bc2b3f
+  checksum: 10c0/ad171bbbb2d7a789cc47803a59dcf2d0ac92ede34b538bb3fd683b6391a9ac3dc3eabaac264fc9582c770c4e435b85840e011785b7adfc0ac002b51ba91179c9
   languageName: node
   linkType: hard
 
@@ -15991,12 +15676,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3, semver@npm:^7.7.1":
-  version: 7.7.1
-  resolution: "semver@npm:7.7.1"
+"semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3":
+  version: 7.6.3
+  resolution: "semver@npm:7.6.3"
   bin:
     semver: bin/semver.js
-  checksum: 10c0/fd603a6fb9c399c6054015433051bdbe7b99a940a8fb44b85c2b524c4004b023d7928d47cb22154f8d054ea7ee8597f586605e05b52047f048278e4ac56ae958
+  checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf
   languageName: node
   linkType: hard
 
@@ -16073,21 +15758,21 @@ __metadata:
   languageName: node
   linkType: hard
 
-"set-function-length@npm:^1.2.2":
-  version: 1.2.2
-  resolution: "set-function-length@npm:1.2.2"
+"set-function-length@npm:^1.2.1":
+  version: 1.2.1
+  resolution: "set-function-length@npm:1.2.1"
   dependencies:
-    define-data-property: "npm:^1.1.4"
+    define-data-property: "npm:^1.1.2"
     es-errors: "npm:^1.3.0"
     function-bind: "npm:^1.1.2"
-    get-intrinsic: "npm:^1.2.4"
+    get-intrinsic: "npm:^1.2.3"
     gopd: "npm:^1.0.1"
-    has-property-descriptors: "npm:^1.0.2"
-  checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c
+    has-property-descriptors: "npm:^1.0.1"
+  checksum: 10c0/1927e296599f2c04d210c1911f1600430a5e49e04a6d8bb03dca5487b95a574da9968813a2ced9a774bd3e188d4a6208352c8f64b8d4674cdb021dca21e190ca
   languageName: node
   linkType: hard
 
-"set-function-name@npm:^2.0.2":
+"set-function-name@npm:^2.0.1, set-function-name@npm:^2.0.2":
   version: 2.0.2
   resolution: "set-function-name@npm:2.0.2"
   dependencies:
@@ -16099,17 +15784,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"set-proto@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "set-proto@npm:1.0.0"
-  dependencies:
-    dunder-proto: "npm:^1.0.1"
-    es-errors: "npm:^1.3.0"
-    es-object-atoms: "npm:^1.0.0"
-  checksum: 10c0/ca5c3ccbba479d07c30460e367e66337cec825560b11e8ba9c5ebe13a2a0d6021ae34eddf94ff3dfe17a3104dc1f191519cb6c48378b503e5c3f36393938776a
-  languageName: node
-  linkType: hard
-
 "set-value@npm:^2.0.0, set-value@npm:^2.0.1":
   version: 2.0.1
   resolution: "set-value@npm:2.0.1"
@@ -16203,51 +15877,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"side-channel-list@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "side-channel-list@npm:1.0.0"
+"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6":
+  version: 1.0.6
+  resolution: "side-channel@npm:1.0.6"
   dependencies:
+    call-bind: "npm:^1.0.7"
     es-errors: "npm:^1.3.0"
-    object-inspect: "npm:^1.13.3"
-  checksum: 10c0/644f4ac893456c9490ff388bf78aea9d333d5e5bfc64cfb84be8f04bf31ddc111a8d4b83b85d7e7e8a7b845bc185a9ad02c052d20e086983cf59f0be517d9b3d
-  languageName: node
-  linkType: hard
-
-"side-channel-map@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "side-channel-map@npm:1.0.1"
-  dependencies:
-    call-bound: "npm:^1.0.2"
-    es-errors: "npm:^1.3.0"
-    get-intrinsic: "npm:^1.2.5"
-    object-inspect: "npm:^1.13.3"
-  checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672
-  languageName: node
-  linkType: hard
-
-"side-channel-weakmap@npm:^1.0.2":
-  version: 1.0.2
-  resolution: "side-channel-weakmap@npm:1.0.2"
-  dependencies:
-    call-bound: "npm:^1.0.2"
-    es-errors: "npm:^1.3.0"
-    get-intrinsic: "npm:^1.2.5"
-    object-inspect: "npm:^1.13.3"
-    side-channel-map: "npm:^1.0.1"
-  checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185
-  languageName: node
-  linkType: hard
-
-"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "side-channel@npm:1.1.0"
-  dependencies:
-    es-errors: "npm:^1.3.0"
-    object-inspect: "npm:^1.13.3"
-    side-channel-list: "npm:^1.0.0"
-    side-channel-map: "npm:^1.0.1"
-    side-channel-weakmap: "npm:^1.0.2"
-  checksum: 10c0/cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6
+    get-intrinsic: "npm:^1.2.4"
+    object-inspect: "npm:^1.13.1"
+  checksum: 10c0/d2afd163dc733cc0a39aa6f7e39bf0c436293510dbccbff446733daeaf295857dbccf94297092ec8c53e2503acac30f0b78830876f0485991d62a90e9cad305f
   languageName: node
   linkType: hard
 
@@ -16622,13 +16260,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"stable-hash@npm:^0.0.5":
-  version: 0.0.5
-  resolution: "stable-hash@npm:0.0.5"
-  checksum: 10c0/ca670cb6d172f1c834950e4ec661e2055885df32fee3ebf3647c5df94993b7c2666a5dbc1c9a62ee11fc5c24928579ec5e81bb5ad31971d355d5a341aab493b3
-  languageName: node
-  linkType: hard
-
 "stable@npm:^0.1.8":
   version: 0.1.8
   resolution: "stable@npm:0.1.8"
@@ -16713,6 +16344,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"stop-iteration-iterator@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "stop-iteration-iterator@npm:1.0.0"
+  dependencies:
+    internal-slot: "npm:^1.0.4"
+  checksum: 10c0/c4158d6188aac510d9e92925b58709207bd94699e9c31186a040c80932a687f84a51356b5895e6dc72710aad83addb9411c22171832c9ae0e6e11b7d61b0dfb9
+  languageName: node
+  linkType: hard
+
 "stream-browserify@npm:^2.0.1":
   version: 2.0.2
   resolution: "stream-browserify@npm:2.0.2"
@@ -16797,35 +16437,33 @@ __metadata:
   languageName: node
   linkType: hard
 
-"string.prototype.includes@npm:^2.0.1":
-  version: 2.0.1
-  resolution: "string.prototype.includes@npm:2.0.1"
+"string.prototype.includes@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "string.prototype.includes@npm:2.0.0"
   dependencies:
-    call-bind: "npm:^1.0.7"
-    define-properties: "npm:^1.2.1"
-    es-abstract: "npm:^1.23.3"
-  checksum: 10c0/25ce9c9b49128352a2618fbe8758b46f945817a58a4420f4799419e40a8d28f116e176c7590d767d5327a61e75c8f32c86171063f48e389b9fdd325f1bd04ee5
+    define-properties: "npm:^1.1.3"
+    es-abstract: "npm:^1.17.5"
+  checksum: 10c0/32dff118c9e9dcc87e240b05462fa8ee7248d9e335c0015c1442fe18152261508a2146d9bb87ddae56abab69148a83c61dfaea33f53853812a6a2db737689ed2
   languageName: node
   linkType: hard
 
-"string.prototype.matchall@npm:^4.0.12, string.prototype.matchall@npm:^4.0.6":
-  version: 4.0.12
-  resolution: "string.prototype.matchall@npm:4.0.12"
+"string.prototype.matchall@npm:^4.0.11, string.prototype.matchall@npm:^4.0.6":
+  version: 4.0.11
+  resolution: "string.prototype.matchall@npm:4.0.11"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.3"
+    call-bind: "npm:^1.0.7"
     define-properties: "npm:^1.2.1"
-    es-abstract: "npm:^1.23.6"
+    es-abstract: "npm:^1.23.2"
     es-errors: "npm:^1.3.0"
     es-object-atoms: "npm:^1.0.0"
-    get-intrinsic: "npm:^1.2.6"
-    gopd: "npm:^1.2.0"
-    has-symbols: "npm:^1.1.0"
-    internal-slot: "npm:^1.1.0"
-    regexp.prototype.flags: "npm:^1.5.3"
+    get-intrinsic: "npm:^1.2.4"
+    gopd: "npm:^1.0.1"
+    has-symbols: "npm:^1.0.3"
+    internal-slot: "npm:^1.0.7"
+    regexp.prototype.flags: "npm:^1.5.2"
     set-function-name: "npm:^2.0.2"
-    side-channel: "npm:^1.1.0"
-  checksum: 10c0/1a53328ada73f4a77f1fdf1c79414700cf718d0a8ef6672af5603e709d26a24f2181208144aed7e858b1bcc1a0d08567a570abfb45567db4ae47637ed2c2f85c
+    side-channel: "npm:^1.0.6"
+  checksum: 10c0/915a2562ac9ab5e01b7be6fd8baa0b2b233a0a9aa975fcb2ec13cc26f08fb9a3e85d5abdaa533c99c6fc4c5b65b914eba3d80c4aff9792a4c9fed403f28f7d9d
   languageName: node
   linkType: hard
 
@@ -16839,30 +16477,26 @@ __metadata:
   languageName: node
   linkType: hard
 
-"string.prototype.trim@npm:^1.2.10":
-  version: 1.2.10
-  resolution: "string.prototype.trim@npm:1.2.10"
+"string.prototype.trim@npm:^1.2.9":
+  version: 1.2.9
+  resolution: "string.prototype.trim@npm:1.2.9"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.2"
-    define-data-property: "npm:^1.1.4"
+    call-bind: "npm:^1.0.7"
     define-properties: "npm:^1.2.1"
-    es-abstract: "npm:^1.23.5"
+    es-abstract: "npm:^1.23.0"
     es-object-atoms: "npm:^1.0.0"
-    has-property-descriptors: "npm:^1.0.2"
-  checksum: 10c0/8a8854241c4b54a948e992eb7dd6b8b3a97185112deb0037a134f5ba57541d8248dd610c966311887b6c2fd1181a3877bffb14d873ce937a344535dabcc648f8
+  checksum: 10c0/dcef1a0fb61d255778155006b372dff8cc6c4394bc39869117e4241f41a2c52899c0d263ffc7738a1f9e61488c490b05c0427faa15151efad721e1a9fb2663c2
   languageName: node
   linkType: hard
 
-"string.prototype.trimend@npm:^1.0.8, string.prototype.trimend@npm:^1.0.9":
-  version: 1.0.9
-  resolution: "string.prototype.trimend@npm:1.0.9"
+"string.prototype.trimend@npm:^1.0.8":
+  version: 1.0.8
+  resolution: "string.prototype.trimend@npm:1.0.8"
   dependencies:
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.2"
+    call-bind: "npm:^1.0.7"
     define-properties: "npm:^1.2.1"
     es-object-atoms: "npm:^1.0.0"
-  checksum: 10c0/59e1a70bf9414cb4c536a6e31bef5553c8ceb0cf44d8b4d0ed65c9653358d1c64dd0ec203b100df83d0413bbcde38b8c5d49e14bc4b86737d74adc593a0d35b6
+  checksum: 10c0/0a0b54c17c070551b38e756ae271865ac6cc5f60dabf2e7e343cceae7d9b02e1a1120a824e090e79da1b041a74464e8477e2da43e2775c85392be30a6f60963c
   languageName: node
   linkType: hard
 
@@ -16877,7 +16511,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1":
+"string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0":
   version: 1.3.0
   resolution: "string_decoder@npm:1.3.0"
   dependencies:
@@ -17033,122 +16667,120 @@ __metadata:
   languageName: node
   linkType: hard
 
-"stylelint-config-recommended-scss@npm:^14.1.0":
-  version: 14.1.0
-  resolution: "stylelint-config-recommended-scss@npm:14.1.0"
+"stylelint-config-recommended-scss@npm:^14.0.0":
+  version: 14.0.0
+  resolution: "stylelint-config-recommended-scss@npm:14.0.0"
   dependencies:
     postcss-scss: "npm:^4.0.9"
-    stylelint-config-recommended: "npm:^14.0.1"
-    stylelint-scss: "npm:^6.4.0"
+    stylelint-config-recommended: "npm:^14.0.0"
+    stylelint-scss: "npm:^6.0.0"
   peerDependencies:
     postcss: ^8.3.3
-    stylelint: ^16.6.1
+    stylelint: ^16.0.2
   peerDependenciesMeta:
     postcss:
       optional: true
-  checksum: 10c0/0a1c1bb6d9f7a21acea82e12fee1b36a195181ae1dd0d8b59145a56f76232a80d5b706269bc4ca4929680d36f10371bd8a7d0aeeee468fa9119a3b56410b052f
+  checksum: 10c0/9ddc92e7a5fa131b41cee1ab1f69251934ca35c0e2803dc613329cdead7b8b27d8457048a63db29f61a1442e7cdef14207f88a3abce00ec53fdefe0d604f7de3
   languageName: node
   linkType: hard
 
-"stylelint-config-recommended@npm:^14.0.1":
-  version: 14.0.1
-  resolution: "stylelint-config-recommended@npm:14.0.1"
-  peerDependencies:
-    stylelint: ^16.1.0
-  checksum: 10c0/a0a0ecd91f4d193bbe2cc3408228f8a2d8fcb2b2578d77233f86780c9247c796a04e16aad7a91d97cb918e2de34b6a8062bab66ee017c3835d855081d94f4828
-  languageName: node
-  linkType: hard
-
-"stylelint-config-standard-scss@npm:^14.0.0":
+"stylelint-config-recommended@npm:^14.0.0":
   version: 14.0.0
-  resolution: "stylelint-config-standard-scss@npm:14.0.0"
+  resolution: "stylelint-config-recommended@npm:14.0.0"
+  peerDependencies:
+    stylelint: ^16.0.0
+  checksum: 10c0/4ad15c36e8c03291aa7bbe4b672ebfb0f46ab698e7580a0da8d29644046d102d7f31dbf00a2a6eab94b565c390c6fb0d5d528737b83ac3acf6dc2ef085a90b11
+  languageName: node
+  linkType: hard
+
+"stylelint-config-standard-scss@npm:^13.0.0":
+  version: 13.1.0
+  resolution: "stylelint-config-standard-scss@npm:13.1.0"
   dependencies:
-    stylelint-config-recommended-scss: "npm:^14.1.0"
-    stylelint-config-standard: "npm:^36.0.1"
+    stylelint-config-recommended-scss: "npm:^14.0.0"
+    stylelint-config-standard: "npm:^36.0.0"
   peerDependencies:
     postcss: ^8.3.3
-    stylelint: ^16.11.0
+    stylelint: ^16.3.1
   peerDependenciesMeta:
     postcss:
       optional: true
-  checksum: 10c0/b885f02d955060a8e0214fd8dc30bfc6d84cbdeb870d34ce0761b258914857bd22d537ac1c8ee9755bf4cd5b1f3b94f4ad0270c2ff4362df7d5eb8d95b35db5e
+  checksum: 10c0/d07cae806ee8b3e77684f019a8b22cc32642373da8053e6ae7ed716f8ddbe6ea1f7323633a6a1bbc9aa08c6a3dceb1dcf053d83fdd10d076b5a01da6e86801ae
   languageName: node
   linkType: hard
 
-"stylelint-config-standard@npm:^36.0.1":
-  version: 36.0.1
-  resolution: "stylelint-config-standard@npm:36.0.1"
+"stylelint-config-standard@npm:^36.0.0":
+  version: 36.0.0
+  resolution: "stylelint-config-standard@npm:36.0.0"
   dependencies:
-    stylelint-config-recommended: "npm:^14.0.1"
+    stylelint-config-recommended: "npm:^14.0.0"
   peerDependencies:
     stylelint: ^16.1.0
-  checksum: 10c0/7f9b954694358e77be5110418f31335be579ce59dd952bc3c6a9449265297db3170ec520e0905769253b48b99c3109a95c71f5b985bf402e48fd6c89b5364cb2
+  checksum: 10c0/1fc9adddfc5cf0a1d7a443182a0731712a3950ace72a24081b4ede2b0bb6fc1eebd003c009f1d8d06c3a64ba9b31b0ed12512db2f91c8fa549238d8341580e4b
   languageName: node
   linkType: hard
 
-"stylelint-scss@npm:^6.4.0":
-  version: 6.10.0
-  resolution: "stylelint-scss@npm:6.10.0"
+"stylelint-scss@npm:^6.0.0":
+  version: 6.0.0
+  resolution: "stylelint-scss@npm:6.0.0"
   dependencies:
-    css-tree: "npm:^3.0.1"
-    is-plain-object: "npm:^5.0.0"
-    known-css-properties: "npm:^0.35.0"
-    mdn-data: "npm:^2.12.2"
+    known-css-properties: "npm:^0.29.0"
     postcss-media-query-parser: "npm:^0.2.3"
-    postcss-resolve-nested-selector: "npm:^0.1.6"
-    postcss-selector-parser: "npm:^7.0.0"
+    postcss-resolve-nested-selector: "npm:^0.1.1"
+    postcss-selector-parser: "npm:^6.0.13"
     postcss-value-parser: "npm:^4.2.0"
   peerDependencies:
     stylelint: ^16.0.2
-  checksum: 10c0/9086109bc36b46ea5e62aef5c1793debbd973aaecb28ba65cadaaf6761a295db1e52f94e1a6bae7ee884e440fc36463e9686941fc652a5ce79045ee58cae5308
+  checksum: 10c0/f5e971d19ef6879ae5c18cb8fba8033fe7928f241178e6afd80357cc080d2feddfd6f7fe564aaa696008aa10345df5885d9a4471c926b3e266088e015927782e
   languageName: node
   linkType: hard
 
-"stylelint@npm:^16.11.0":
-  version: 16.12.0
-  resolution: "stylelint@npm:16.12.0"
+"stylelint@npm:^16.0.2":
+  version: 16.9.0
+  resolution: "stylelint@npm:16.9.0"
   dependencies:
-    "@csstools/css-parser-algorithms": "npm:^3.0.4"
-    "@csstools/css-tokenizer": "npm:^3.0.3"
-    "@csstools/media-query-list-parser": "npm:^4.0.2"
-    "@csstools/selector-specificity": "npm:^5.0.0"
+    "@csstools/css-parser-algorithms": "npm:^3.0.1"
+    "@csstools/css-tokenizer": "npm:^3.0.1"
+    "@csstools/media-query-list-parser": "npm:^3.0.1"
+    "@csstools/selector-specificity": "npm:^4.0.0"
     "@dual-bundle/import-meta-resolve": "npm:^4.1.0"
     balanced-match: "npm:^2.0.0"
     colord: "npm:^2.9.3"
     cosmiconfig: "npm:^9.0.0"
-    css-functions-list: "npm:^3.2.3"
-    css-tree: "npm:^3.0.1"
-    debug: "npm:^4.3.7"
+    css-functions-list: "npm:^3.2.2"
+    css-tree: "npm:^2.3.1"
+    debug: "npm:^4.3.6"
     fast-glob: "npm:^3.3.2"
     fastest-levenshtein: "npm:^1.0.16"
-    file-entry-cache: "npm:^9.1.0"
+    file-entry-cache: "npm:^9.0.0"
     global-modules: "npm:^2.0.0"
     globby: "npm:^11.1.0"
     globjoin: "npm:^0.1.4"
     html-tags: "npm:^3.3.1"
-    ignore: "npm:^6.0.2"
+    ignore: "npm:^5.3.2"
     imurmurhash: "npm:^0.1.4"
     is-plain-object: "npm:^5.0.0"
-    known-css-properties: "npm:^0.35.0"
+    known-css-properties: "npm:^0.34.0"
     mathml-tag-names: "npm:^2.1.3"
     meow: "npm:^13.2.0"
     micromatch: "npm:^4.0.8"
     normalize-path: "npm:^3.0.0"
-    picocolors: "npm:^1.1.1"
-    postcss: "npm:^8.4.49"
+    picocolors: "npm:^1.0.1"
+    postcss: "npm:^8.4.41"
     postcss-resolve-nested-selector: "npm:^0.1.6"
-    postcss-safe-parser: "npm:^7.0.1"
-    postcss-selector-parser: "npm:^7.0.0"
+    postcss-safe-parser: "npm:^7.0.0"
+    postcss-selector-parser: "npm:^6.1.2"
     postcss-value-parser: "npm:^4.2.0"
     resolve-from: "npm:^5.0.0"
     string-width: "npm:^4.2.3"
+    strip-ansi: "npm:^7.1.0"
     supports-hyperlinks: "npm:^3.1.0"
     svg-tags: "npm:^1.0.0"
-    table: "npm:^6.9.0"
+    table: "npm:^6.8.2"
     write-file-atomic: "npm:^5.0.1"
   bin:
     stylelint: bin/stylelint.mjs
-  checksum: 10c0/d60bc6136f5bdc4e49ec22aee4d82130c4e9c94aa7d249da9dda315a862615e74acaefb24c560529b2102f8c27dde473b148ffcee861f5dab7b0225254765102
+  checksum: 10c0/d3ff9c8945c56b04a2fa16ec33d163325496d5db94b6fcb5adf74c76f7f794ac992888273f9a3317652ba8b6195168b2ffff382ca2a667a241e2ace8c9505ae2
   languageName: node
   linkType: hard
 
@@ -17290,16 +16922,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"table@npm:^6.9.0":
-  version: 6.9.0
-  resolution: "table@npm:6.9.0"
+"table@npm:^6.8.2":
+  version: 6.8.2
+  resolution: "table@npm:6.8.2"
   dependencies:
     ajv: "npm:^8.0.1"
     lodash.truncate: "npm:^4.4.2"
     slice-ansi: "npm:^4.0.0"
     string-width: "npm:^4.2.3"
     strip-ansi: "npm:^6.0.1"
-  checksum: 10c0/35646185712bb65985fbae5975dda46696325844b78735f95faefae83e86df0a265277819a3e67d189de6e858c509b54e66ca3958ffd51bde56ef1118d455bf4
+  checksum: 10c0/f8b348af38ee34e419d8ce7306ba00671ce6f20e861ccff22555f491ba264e8416086063ce278a8d81abfa8d23b736ec2cca7ac4029b5472f63daa4b4688b803
   languageName: node
   linkType: hard
 
@@ -17310,6 +16942,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"tapable@npm:^2.2.0":
+  version: 2.2.1
+  resolution: "tapable@npm:2.2.1"
+  checksum: 10c0/bc40e6efe1e554d075469cedaba69a30eeb373552aaf41caeaaa45bf56ffacc2674261b106245bd566b35d8f3329b52d838e851ee0a852120acae26e622925c9
+  languageName: node
+  linkType: hard
+
 "tar@npm:^6.0.2, tar@npm:^6.1.11, tar@npm:^6.1.2":
   version: 6.2.0
   resolution: "tar@npm:6.2.0"
@@ -17385,27 +17024,31 @@ __metadata:
   languageName: node
   linkType: hard
 
-"tesseract.js-core@npm:^6.0.0":
-  version: 6.0.0
-  resolution: "tesseract.js-core@npm:6.0.0"
-  checksum: 10c0/c04be8bbaa296be658664496754f21e857bdffff84113f08adf02f03a1f84596d68b3542ed2fda4a6dc138abb84b09b30ab07c04ee5950879e780876d343955f
+"tesseract.js-core@npm:^2.2.0":
+  version: 2.2.0
+  resolution: "tesseract.js-core@npm:2.2.0"
+  checksum: 10c0/9ef569529f1ee96f8bf18388ef086d4940d3f02d28b4252df133a9bd36f6a9d140085e77a12ff1963cf9b4cd85bd1c644b61eca266ecfc51bb83adb30a1f11e3
   languageName: node
   linkType: hard
 
-"tesseract.js@npm:^6.0.0":
-  version: 6.0.0
-  resolution: "tesseract.js@npm:6.0.0"
+"tesseract.js@npm:^2.1.5":
+  version: 2.1.5
+  resolution: "tesseract.js@npm:2.1.5"
   dependencies:
+    blueimp-load-image: "npm:^3.0.0"
     bmp-js: "npm:^0.1.0"
-    idb-keyval: "npm:^6.2.0"
+    file-type: "npm:^12.4.1"
+    idb-keyval: "npm:^3.2.0"
+    is-electron: "npm:^2.2.0"
     is-url: "npm:^1.2.4"
-    node-fetch: "npm:^2.6.9"
-    opencollective-postinstall: "npm:^2.0.3"
+    jpeg-autorotate: "npm:^7.1.1"
+    node-fetch: "npm:^2.6.0"
+    opencollective-postinstall: "npm:^2.0.2"
     regenerator-runtime: "npm:^0.13.3"
-    tesseract.js-core: "npm:^6.0.0"
-    wasm-feature-detect: "npm:^1.2.11"
+    resolve-url: "npm:^0.2.1"
+    tesseract.js-core: "npm:^2.2.0"
     zlibjs: "npm:^0.3.1"
-  checksum: 10c0/f65b816eabc16266bfa74ea61db73afa2d21ce0f57041b87b96abdff8954e042ee16637edea20aaf752227bc075052ca12021f4f68d5d25d52f062ebc4c644e1
+  checksum: 10c0/b3aaee9189f3bc7f4217b83e110d0dd4d9afcafc3045b842f72b7ca9beb00bec732bc6b4b00eca14167c16b014c437fcf83dd272a640c9c8b5e1e9b55ea00ff5
   languageName: node
   linkType: hard
 
@@ -17420,6 +17063,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"text-table@npm:^0.2.0":
+  version: 0.2.0
+  resolution: "text-table@npm:0.2.0"
+  checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c
+  languageName: node
+  linkType: hard
+
 "thread-stream@npm:^3.0.0":
   version: 3.0.0
   resolution: "thread-stream@npm:3.0.0"
@@ -17473,16 +17123,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"tinyglobby@npm:^0.2.12":
-  version: 0.2.12
-  resolution: "tinyglobby@npm:0.2.12"
-  dependencies:
-    fdir: "npm:^6.4.3"
-    picomatch: "npm:^4.0.2"
-  checksum: 10c0/7c9be4fd3625630e262dcb19015302aad3b4ba7fc620f269313e688f2161ea8724d6cb4444baab5ef2826eb6bed72647b169a33ec8eea37501832a2526ff540f
-  languageName: node
-  linkType: hard
-
 "tldts-core@npm:^6.1.47":
   version: 6.1.47
   resolution: "tldts-core@npm:6.1.47"
@@ -17515,6 +17155,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"to-fast-properties@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "to-fast-properties@npm:2.0.0"
+  checksum: 10c0/b214d21dbfb4bce3452b6244b336806ffea9c05297148d32ebb428d5c43ce7545bdfc65a1ceb58c9ef4376a65c0cb2854d645f33961658b3e3b4f84910ddcdd7
+  languageName: node
+  linkType: hard
+
 "to-object-path@npm:^0.3.0":
   version: 0.3.0
   resolution: "to-object-path@npm:0.3.0"
@@ -17581,19 +17228,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"tough-cookie@npm:^5.1.1":
-  version: 5.1.2
-  resolution: "tough-cookie@npm:5.1.2"
+"tough-cookie@npm:^5.0.0":
+  version: 5.0.0
+  resolution: "tough-cookie@npm:5.0.0"
   dependencies:
     tldts: "npm:^6.1.32"
-  checksum: 10c0/5f95023a47de0f30a902bba951664b359725597d8adeabc66a0b93a931c3af801e1e697dae4b8c21a012056c0ea88bd2bf4dfe66b2adcf8e2f42cd9796fe0626
-  languageName: node
-  linkType: hard
-
-"toygrad@npm:^2.6.0":
-  version: 2.6.0
-  resolution: "toygrad@npm:2.6.0"
-  checksum: 10c0/96e42ced87431e99cec7d9b446c7827fe7782c2fd82bb5fc8c4a0855679011d809f9967096a60b4c8ceca867a29f1aadd62af447bdb652cb6f7fee279ae743ed
+  checksum: 10c0/4a69c885bf6f45c5a64e60262af99e8c0d58a33bd3d0ce5da62121eeb9c00996d0128a72df8fc4614cbde59cc8b70aa3e21e4c3c98c2bbde137d7aba7fa00124
   languageName: node
   linkType: hard
 
@@ -17615,12 +17255,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"tr46@npm:^5.1.0":
-  version: 5.1.0
-  resolution: "tr46@npm:5.1.0"
+"tr46@npm:^5.0.0":
+  version: 5.0.0
+  resolution: "tr46@npm:5.0.0"
   dependencies:
     punycode: "npm:^2.3.1"
-  checksum: 10c0/d761f7144e0cb296187674ef245c74f761e334d7cf25ca73ef60e4c72c097c75051031c093fa1a2fee04b904977b316716a7915854bcae8fb1a371746513cbe8
+  checksum: 10c0/1521b6e7bbc8adc825c4561480f9fe48eb2276c81335eed9fa610aa4c44a48a3221f78b10e5f18b875769eb3413e30efbf209ed556a17a42aa8d690df44b7bee
   languageName: node
   linkType: hard
 
@@ -17631,12 +17271,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"ts-api-utils@npm:^2.0.1":
-  version: 2.0.1
-  resolution: "ts-api-utils@npm:2.0.1"
+"ts-api-utils@npm:^1.0.1, ts-api-utils@npm:^1.3.0":
+  version: 1.3.0
+  resolution: "ts-api-utils@npm:1.3.0"
   peerDependencies:
-    typescript: ">=4.8.4"
-  checksum: 10c0/23fd56a958b332cac00150a652e4c84730df30571bd2faa1ba6d7b511356d1a61656621492bb6c7f15dd6e18847a1408357a0e406671d358115369a17f5bfedd
+    typescript: ">=4.2.0"
+  checksum: 10c0/f54a0ba9ed56ce66baea90a3fa087a484002e807f28a8ccb2d070c75e76bde64bd0f6dce98b3802834156306050871b67eec325cb4e918015a360a3f0868c77c
   languageName: node
   linkType: hard
 
@@ -17652,10 +17292,24 @@ __metadata:
   languageName: node
   linkType: hard
 
-"tslib@npm:^2.0.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.8.0":
-  version: 2.8.1
-  resolution: "tslib@npm:2.8.1"
-  checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
+"tslib@npm:2.6.2":
+  version: 2.6.2
+  resolution: "tslib@npm:2.6.2"
+  checksum: 10c0/e03a8a4271152c8b26604ed45535954c0a45296e32445b4b87f8a5abdb2421f40b59b4ca437c4346af0f28179780d604094eb64546bee2019d903d01c6c19bdb
+  languageName: node
+  linkType: hard
+
+"tslib@npm:^2.0.0":
+  version: 2.7.0
+  resolution: "tslib@npm:2.7.0"
+  checksum: 10c0/469e1d5bf1af585742128827000711efa61010b699cb040ab1800bcd3ccdd37f63ec30642c9e07c4439c1db6e46345582614275daca3e0f4abae29b0083f04a6
+  languageName: node
+  linkType: hard
+
+"tslib@npm:^2.4.0, tslib@npm:^2.6.2":
+  version: 2.6.3
+  resolution: "tslib@npm:2.6.3"
+  checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a
   languageName: node
   linkType: hard
 
@@ -17708,6 +17362,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"type-fest@npm:^0.20.2":
+  version: 0.20.2
+  resolution: "type-fest@npm:0.20.2"
+  checksum: 10c0/dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3
+  languageName: node
+  linkType: hard
+
 "type-fest@npm:^0.21.3":
   version: 0.21.3
   resolution: "type-fest@npm:0.21.3"
@@ -17725,122 +17386,87 @@ __metadata:
   languageName: node
   linkType: hard
 
-"typed-array-buffer@npm:^1.0.3":
-  version: 1.0.3
-  resolution: "typed-array-buffer@npm:1.0.3"
+"typed-array-buffer@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "typed-array-buffer@npm:1.0.2"
   dependencies:
-    call-bound: "npm:^1.0.3"
+    call-bind: "npm:^1.0.7"
     es-errors: "npm:^1.3.0"
-    is-typed-array: "npm:^1.1.14"
-  checksum: 10c0/1105071756eb248774bc71646bfe45b682efcad93b55532c6ffa4518969fb6241354e4aa62af679ae83899ec296d69ef88f1f3763657cdb3a4d29321f7b83079
+    is-typed-array: "npm:^1.1.13"
+  checksum: 10c0/9e043eb38e1b4df4ddf9dde1aa64919ae8bb909571c1cc4490ba777d55d23a0c74c7d73afcdd29ec98616d91bb3ae0f705fad4421ea147e1daf9528200b562da
   languageName: node
   linkType: hard
 
-"typed-array-byte-length@npm:^1.0.3":
-  version: 1.0.3
-  resolution: "typed-array-byte-length@npm:1.0.3"
-  dependencies:
-    call-bind: "npm:^1.0.8"
-    for-each: "npm:^0.3.3"
-    gopd: "npm:^1.2.0"
-    has-proto: "npm:^1.2.0"
-    is-typed-array: "npm:^1.1.14"
-  checksum: 10c0/6ae083c6f0354f1fce18b90b243343b9982affd8d839c57bbd2c174a5d5dc71be9eb7019ffd12628a96a4815e7afa85d718d6f1e758615151d5f35df841ffb3e
-  languageName: node
-  linkType: hard
-
-"typed-array-byte-offset@npm:^1.0.4":
-  version: 1.0.4
-  resolution: "typed-array-byte-offset@npm:1.0.4"
-  dependencies:
-    available-typed-arrays: "npm:^1.0.7"
-    call-bind: "npm:^1.0.8"
-    for-each: "npm:^0.3.3"
-    gopd: "npm:^1.2.0"
-    has-proto: "npm:^1.2.0"
-    is-typed-array: "npm:^1.1.15"
-    reflect.getprototypeof: "npm:^1.0.9"
-  checksum: 10c0/3d805b050c0c33b51719ee52de17c1cd8e6a571abdf0fffb110e45e8dd87a657e8b56eee94b776b13006d3d347a0c18a730b903cf05293ab6d92e99ff8f77e53
-  languageName: node
-  linkType: hard
-
-"typed-array-length@npm:^1.0.7":
-  version: 1.0.7
-  resolution: "typed-array-length@npm:1.0.7"
+"typed-array-byte-length@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "typed-array-byte-length@npm:1.0.1"
   dependencies:
     call-bind: "npm:^1.0.7"
     for-each: "npm:^0.3.3"
     gopd: "npm:^1.0.1"
+    has-proto: "npm:^1.0.3"
+    is-typed-array: "npm:^1.1.13"
+  checksum: 10c0/fcebeffb2436c9f355e91bd19e2368273b88c11d1acc0948a2a306792f1ab672bce4cfe524ab9f51a0505c9d7cd1c98eff4235c4f6bfef6a198f6cfc4ff3d4f3
+  languageName: node
+  linkType: hard
+
+"typed-array-byte-offset@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "typed-array-byte-offset@npm:1.0.2"
+  dependencies:
+    available-typed-arrays: "npm:^1.0.7"
+    call-bind: "npm:^1.0.7"
+    for-each: "npm:^0.3.3"
+    gopd: "npm:^1.0.1"
+    has-proto: "npm:^1.0.3"
+    is-typed-array: "npm:^1.1.13"
+  checksum: 10c0/d2628bc739732072e39269389a758025f75339de2ed40c4f91357023c5512d237f255b633e3106c461ced41907c1bf9a533c7e8578066b0163690ca8bc61b22f
+  languageName: node
+  linkType: hard
+
+"typed-array-length@npm:^1.0.6":
+  version: 1.0.6
+  resolution: "typed-array-length@npm:1.0.6"
+  dependencies:
+    call-bind: "npm:^1.0.7"
+    for-each: "npm:^0.3.3"
+    gopd: "npm:^1.0.1"
+    has-proto: "npm:^1.0.3"
     is-typed-array: "npm:^1.1.13"
     possible-typed-array-names: "npm:^1.0.0"
-    reflect.getprototypeof: "npm:^1.0.6"
-  checksum: 10c0/e38f2ae3779584c138a2d8adfa8ecf749f494af3cd3cdafe4e688ce51418c7d2c5c88df1bd6be2bbea099c3f7cea58c02ca02ed438119e91f162a9de23f61295
+  checksum: 10c0/74253d7dc488eb28b6b2711cf31f5a9dcefc9c41b0681fd1c178ed0a1681b4468581a3626d39cd4df7aee3d3927ab62be06aa9ca74e5baf81827f61641445b77
   languageName: node
   linkType: hard
 
-"typescript-eslint@npm:^8.28.0":
-  version: 8.28.0
-  resolution: "typescript-eslint@npm:8.28.0"
+"typescript@npm:5, typescript@npm:^5.0.4":
+  version: 5.5.4
+  resolution: "typescript@npm:5.5.4"
+  bin:
+    tsc: bin/tsc
+    tsserver: bin/tsserver
+  checksum: 10c0/422be60f89e661eab29ac488c974b6cc0a660fb2228003b297c3d10c32c90f3bcffc1009b43876a082515a3c376b1eefcce823d6e78982e6878408b9a923199c
+  languageName: node
+  linkType: hard
+
+"typescript@patch:typescript@npm%3A5#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin<compat/typescript>":
+  version: 5.5.4
+  resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin<compat/typescript>::version=5.5.4&hash=379a07"
+  bin:
+    tsc: bin/tsc
+    tsserver: bin/tsserver
+  checksum: 10c0/73409d7b9196a5a1217b3aaad929bf76294d3ce7d6e9766dd880ece296ee91cf7d7db6b16c6c6c630ee5096eccde726c0ef17c7dfa52b01a243e57ae1f09ef07
+  languageName: node
+  linkType: hard
+
+"unbox-primitive@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "unbox-primitive@npm:1.0.2"
   dependencies:
-    "@typescript-eslint/eslint-plugin": "npm:8.28.0"
-    "@typescript-eslint/parser": "npm:8.28.0"
-    "@typescript-eslint/utils": "npm:8.28.0"
-  peerDependencies:
-    eslint: ^8.57.0 || ^9.0.0
-    typescript: ">=4.8.4 <5.9.0"
-  checksum: 10c0/bf1c1e4b2f21a95930758d5b285c39a394a50e3b6983f373413b93b80a6cb5aabc1d741780e60c63cb42ad5d645ea9c1e6d441d98174c5a2884ab88f4ac46df6
-  languageName: node
-  linkType: hard
-
-"typescript@npm:^5.6.0":
-  version: 5.8.2
-  resolution: "typescript@npm:5.8.2"
-  bin:
-    tsc: bin/tsc
-    tsserver: bin/tsserver
-  checksum: 10c0/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6
-  languageName: node
-  linkType: hard
-
-"typescript@npm:~5.7.3":
-  version: 5.7.3
-  resolution: "typescript@npm:5.7.3"
-  bin:
-    tsc: bin/tsc
-    tsserver: bin/tsserver
-  checksum: 10c0/b7580d716cf1824736cc6e628ab4cd8b51877408ba2be0869d2866da35ef8366dd6ae9eb9d0851470a39be17cbd61df1126f9e211d8799d764ea7431d5435afa
-  languageName: node
-  linkType: hard
-
-"typescript@patch:typescript@npm%3A^5.6.0#optional!builtin<compat/typescript>":
-  version: 5.8.2
-  resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin<compat/typescript>::version=5.8.2&hash=5786d5"
-  bin:
-    tsc: bin/tsc
-    tsserver: bin/tsserver
-  checksum: 10c0/5448a08e595cc558ab321e49d4cac64fb43d1fa106584f6ff9a8d8e592111b373a995a1d5c7f3046211c8a37201eb6d0f1566f15cdb7a62a5e3be01d087848e2
-  languageName: node
-  linkType: hard
-
-"typescript@patch:typescript@npm%3A~5.7.3#optional!builtin<compat/typescript>":
-  version: 5.7.3
-  resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin<compat/typescript>::version=5.7.3&hash=5786d5"
-  bin:
-    tsc: bin/tsc
-    tsserver: bin/tsserver
-  checksum: 10c0/6fd7e0ed3bf23a81246878c613423730c40e8bdbfec4c6e4d7bf1b847cbb39076e56ad5f50aa9d7ebd89877999abaee216002d3f2818885e41c907caaa192cc4
-  languageName: node
-  linkType: hard
-
-"unbox-primitive@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "unbox-primitive@npm:1.1.0"
-  dependencies:
-    call-bound: "npm:^1.0.3"
+    call-bind: "npm:^1.0.2"
     has-bigints: "npm:^1.0.2"
-    has-symbols: "npm:^1.1.0"
-    which-boxed-primitive: "npm:^1.1.1"
-  checksum: 10c0/7dbd35ab02b0e05fe07136c72cb9355091242455473ec15057c11430129bab38b7b3624019b8778d02a881c13de44d63cd02d122ee782fb519e1de7775b5b982
+    has-symbols: "npm:^1.0.3"
+    which-boxed-primitive: "npm:^1.0.2"
+  checksum: 10c0/81ca2e81134167cc8f75fa79fbcc8a94379d6c61de67090986a2273850989dd3bae8440c163121b77434b68263e34787a675cbdcb34bb2f764c6b9c843a11b66
   languageName: node
   linkType: hard
 
@@ -17858,10 +17484,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"undici-types@npm:~6.20.0":
-  version: 6.20.0
-  resolution: "undici-types@npm:6.20.0"
-  checksum: 10c0/68e659a98898d6a836a9a59e6adf14a5d799707f5ea629433e025ac90d239f75e408e2e5ff086afc3cace26f8b26ee52155293564593fbb4a2f666af57fc59bf
+"undici-types@npm:~5.26.4":
+  version: 5.26.5
+  resolution: "undici-types@npm:5.26.5"
+  checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501
   languageName: node
   linkType: hard
 
@@ -17990,60 +17616,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"unrs-resolver@npm:^1.3.2":
-  version: 1.3.2
-  resolution: "unrs-resolver@npm:1.3.2"
-  dependencies:
-    "@unrs/resolver-binding-darwin-arm64": "npm:1.3.2"
-    "@unrs/resolver-binding-darwin-x64": "npm:1.3.2"
-    "@unrs/resolver-binding-freebsd-x64": "npm:1.3.2"
-    "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.3.2"
-    "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.3.2"
-    "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.3.2"
-    "@unrs/resolver-binding-linux-arm64-musl": "npm:1.3.2"
-    "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.3.2"
-    "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.3.2"
-    "@unrs/resolver-binding-linux-x64-gnu": "npm:1.3.2"
-    "@unrs/resolver-binding-linux-x64-musl": "npm:1.3.2"
-    "@unrs/resolver-binding-wasm32-wasi": "npm:1.3.2"
-    "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.3.2"
-    "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.3.2"
-    "@unrs/resolver-binding-win32-x64-msvc": "npm:1.3.2"
-  dependenciesMeta:
-    "@unrs/resolver-binding-darwin-arm64":
-      optional: true
-    "@unrs/resolver-binding-darwin-x64":
-      optional: true
-    "@unrs/resolver-binding-freebsd-x64":
-      optional: true
-    "@unrs/resolver-binding-linux-arm-gnueabihf":
-      optional: true
-    "@unrs/resolver-binding-linux-arm-musleabihf":
-      optional: true
-    "@unrs/resolver-binding-linux-arm64-gnu":
-      optional: true
-    "@unrs/resolver-binding-linux-arm64-musl":
-      optional: true
-    "@unrs/resolver-binding-linux-ppc64-gnu":
-      optional: true
-    "@unrs/resolver-binding-linux-s390x-gnu":
-      optional: true
-    "@unrs/resolver-binding-linux-x64-gnu":
-      optional: true
-    "@unrs/resolver-binding-linux-x64-musl":
-      optional: true
-    "@unrs/resolver-binding-wasm32-wasi":
-      optional: true
-    "@unrs/resolver-binding-win32-arm64-msvc":
-      optional: true
-    "@unrs/resolver-binding-win32-ia32-msvc":
-      optional: true
-    "@unrs/resolver-binding-win32-x64-msvc":
-      optional: true
-  checksum: 10c0/f9b6d18193bcaae7ef9e284a74c85d4cb3d8c833851f1b23254a947297e672826223d82798dbff818455fefeda02084340aca904300fd5060468c2f243767cc1
-  languageName: node
-  linkType: hard
-
 "unset-value@npm:^1.0.0":
   version: 1.0.0
   resolution: "unset-value@npm:1.0.0"
@@ -18061,17 +17633,17 @@ __metadata:
   languageName: node
   linkType: hard
 
-"update-browserslist-db@npm:^1.1.1":
-  version: 1.1.2
-  resolution: "update-browserslist-db@npm:1.1.2"
+"update-browserslist-db@npm:^1.1.0":
+  version: 1.1.0
+  resolution: "update-browserslist-db@npm:1.1.0"
   dependencies:
-    escalade: "npm:^3.2.0"
-    picocolors: "npm:^1.1.1"
+    escalade: "npm:^3.1.2"
+    picocolors: "npm:^1.0.1"
   peerDependencies:
     browserslist: ">= 4.21.0"
   bin:
     update-browserslist-db: cli.js
-  checksum: 10c0/9cb353998d6d7d6ba1e46b8fa3db888822dd972212da4eda609d185eb5c3557a93fd59780ceb757afd4d84240518df08542736969e6a5d6d6ce2d58e9363aac6
+  checksum: 10c0/a7452de47785842736fb71547651c5bbe5b4dc1e3722ccf48a704b7b34e4dcf633991eaa8e4a6a517ffb738b3252eede3773bef673ef9021baa26b056d63a5b9
   languageName: node
   linkType: hard
 
@@ -18121,23 +17693,23 @@ __metadata:
   linkType: hard
 
 "use-debounce@npm:^10.0.0":
-  version: 10.0.4
-  resolution: "use-debounce@npm:10.0.4"
+  version: 10.0.3
+  resolution: "use-debounce@npm:10.0.3"
   peerDependencies:
     react: "*"
-  checksum: 10c0/73494fc44b2bd58a7ec799a528fc20077c45fe2e94fedff6dcd88d136f7a39f417d77f584d5613aac615ed32aeb2ea393797ae1f7d5b2645eab57cb497a6d0cb
+  checksum: 10c0/351b62c565d6dce5a21ecc21fe3e1f8db74f70c81c8f7d9dbdfc2da1cb82d883578589f6146e684d91dac534bc3c8b145ab1a36fbf4d44cbb4113827508b39aa
   languageName: node
   linkType: hard
 
-"use-isomorphic-layout-effect@npm:^1.1.1, use-isomorphic-layout-effect@npm:^1.2.0":
-  version: 1.2.0
-  resolution: "use-isomorphic-layout-effect@npm:1.2.0"
+"use-isomorphic-layout-effect@npm:^1.1.1, use-isomorphic-layout-effect@npm:^1.1.2":
+  version: 1.1.2
+  resolution: "use-isomorphic-layout-effect@npm:1.1.2"
   peerDependencies:
-    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+    react: ^16.8.0 || ^17.0.0 || ^18.0.0
   peerDependenciesMeta:
     "@types/react":
       optional: true
-  checksum: 10c0/2e4bdee68d65893b37e716ebdcc111550775189c80e662eda87d6f5b54dc431d3383a18914ea01a893ee5478902a878012713eaebcacbb6611ab88c463accb83
+  checksum: 10c0/d8deea8b85e55ac6daba237a889630bfdbf0ebf60e9e22b6a78a78c26fabe6025e04ada7abef1e444e6786227d921e648b2707db8b3564daf757264a148a6e23
   languageName: node
   linkType: hard
 
@@ -18164,12 +17736,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"use-sync-external-store@npm:^1.4.0":
-  version: 1.4.0
-  resolution: "use-sync-external-store@npm:1.4.0"
+"use-sync-external-store@npm:^1.0.0":
+  version: 1.2.0
+  resolution: "use-sync-external-store@npm:1.2.0"
   peerDependencies:
-    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
-  checksum: 10c0/ec011a5055962c0f6b509d6e78c0b143f8cd069890ae370528753053c55e3b360d3648e76cfaa854faa7a59eb08d6c5fb1015e60ffde9046d32f5b2a295acea5
+    react: ^16.8.0 || ^17.0.0 || ^18.0.0
+  checksum: 10c0/ac4814e5592524f242921157e791b022efe36e451fe0d4fd4d204322d5433a4fc300d63b0ade5185f8e0735ded044c70bcf6d2352db0f74d097a238cebd2da02
   languageName: node
   linkType: hard
 
@@ -18181,12 +17753,12 @@ __metadata:
   linkType: hard
 
 "utf-8-validate@npm:^6.0.3":
-  version: 6.0.5
-  resolution: "utf-8-validate@npm:6.0.5"
+  version: 6.0.4
+  resolution: "utf-8-validate@npm:6.0.4"
   dependencies:
     node-gyp: "npm:latest"
     node-gyp-build: "npm:^4.3.0"
-  checksum: 10c0/6dc63c513adb001e47a51819072cdd414158430091c49c21d4947ea99f16df5167b671f680df8fb2b6f2ae6a7f30264b4ec111bd3e573720dfe371da1ab99a81
+  checksum: 10c0/f7042d94aec6ca02461b64e725bdc7262266610dbb787331e5bbd49374ef6f75fe9900600df3fc63d97906c23614a965c8989b4bf95d70bf35dc617da99215e7
   languageName: node
   linkType: hard
 
@@ -18234,12 +17806,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"uuid@npm:^11.0.0":
-  version: 11.1.0
-  resolution: "uuid@npm:11.1.0"
+"uuid@npm:^10.0.0":
+  version: 10.0.0
+  resolution: "uuid@npm:10.0.0"
   bin:
-    uuid: dist/esm/bin/uuid
-  checksum: 10c0/34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3
+    uuid: dist/bin/uuid
+  checksum: 10c0/eab18c27fe4ab9fb9709a5d5f40119b45f2ec8314f8d4cf12ce27e4c6f4ffa4a6321dc7db6c515068fa373c075b49691ba969f0010bf37f44c37ca40cd6bf7fe
   languageName: node
   linkType: hard
 
@@ -18345,13 +17917,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"wasm-feature-detect@npm:^1.2.11":
-  version: 1.8.0
-  resolution: "wasm-feature-detect@npm:1.8.0"
-  checksum: 10c0/2cb43e91bbf7aa7c121bc76b3133de3ab6dc4f482acc1d2dc46c528e8adb7a51c72df5c2aacf1d219f113c04efd1706f18274d5790542aa5dd49e0644e3ee665
-  languageName: node
-  linkType: hard
-
 "watchpack-chokidar2@npm:^2.0.1":
   version: 2.0.1
   resolution: "watchpack-chokidar2@npm:2.0.1"
@@ -18664,13 +18229,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"whatwg-url@npm:^14.0.0, whatwg-url@npm:^14.1.1":
-  version: 14.2.0
-  resolution: "whatwg-url@npm:14.2.0"
+"whatwg-url@npm:^14.0.0":
+  version: 14.0.0
+  resolution: "whatwg-url@npm:14.0.0"
   dependencies:
-    tr46: "npm:^5.1.0"
+    tr46: "npm:^5.0.0"
     webidl-conversions: "npm:^7.0.0"
-  checksum: 10c0/f746fc2f4c906607d09537de1227b13f9494c171141e5427ed7d2c0dd0b6a48b43d8e71abaae57d368d0c06b673fd8ec63550b32ad5ed64990c7b0266c2b4272
+  checksum: 10c0/ac32e9ba9d08744605519bbe9e1371174d36229689ecc099157b6ba102d4251a95e81d81f3d80271eb8da182eccfa65653f07f0ab43ea66a6934e643fd091ba9
   languageName: node
   linkType: hard
 
@@ -18695,49 +18260,48 @@ __metadata:
   languageName: node
   linkType: hard
 
-"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1":
-  version: 1.1.1
-  resolution: "which-boxed-primitive@npm:1.1.1"
+"which-boxed-primitive@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "which-boxed-primitive@npm:1.0.2"
   dependencies:
-    is-bigint: "npm:^1.1.0"
-    is-boolean-object: "npm:^1.2.1"
-    is-number-object: "npm:^1.1.1"
-    is-string: "npm:^1.1.1"
-    is-symbol: "npm:^1.1.1"
-  checksum: 10c0/aceea8ede3b08dede7dce168f3883323f7c62272b49801716e8332ff750e7ae59a511ae088840bc6874f16c1b7fd296c05c949b0e5b357bfe3c431b98c417abe
+    is-bigint: "npm:^1.0.1"
+    is-boolean-object: "npm:^1.1.0"
+    is-number-object: "npm:^1.0.4"
+    is-string: "npm:^1.0.5"
+    is-symbol: "npm:^1.0.3"
+  checksum: 10c0/0a62a03c00c91dd4fb1035b2f0733c341d805753b027eebd3a304b9cb70e8ce33e25317add2fe9b5fea6f53a175c0633ae701ff812e604410ddd049777cd435e
   languageName: node
   linkType: hard
 
-"which-builtin-type@npm:^1.2.1":
-  version: 1.2.1
-  resolution: "which-builtin-type@npm:1.2.1"
+"which-builtin-type@npm:^1.1.3":
+  version: 1.1.3
+  resolution: "which-builtin-type@npm:1.1.3"
   dependencies:
-    call-bound: "npm:^1.0.2"
-    function.prototype.name: "npm:^1.1.6"
-    has-tostringtag: "npm:^1.0.2"
+    function.prototype.name: "npm:^1.1.5"
+    has-tostringtag: "npm:^1.0.0"
     is-async-function: "npm:^2.0.0"
-    is-date-object: "npm:^1.1.0"
-    is-finalizationregistry: "npm:^1.1.0"
+    is-date-object: "npm:^1.0.5"
+    is-finalizationregistry: "npm:^1.0.2"
     is-generator-function: "npm:^1.0.10"
-    is-regex: "npm:^1.2.1"
+    is-regex: "npm:^1.1.4"
     is-weakref: "npm:^1.0.2"
     isarray: "npm:^2.0.5"
-    which-boxed-primitive: "npm:^1.1.0"
-    which-collection: "npm:^1.0.2"
-    which-typed-array: "npm:^1.1.16"
-  checksum: 10c0/8dcf323c45e5c27887800df42fbe0431d0b66b1163849bb7d46b5a730ad6a96ee8bfe827d078303f825537844ebf20c02459de41239a0a9805e2fcb3cae0d471
+    which-boxed-primitive: "npm:^1.0.2"
+    which-collection: "npm:^1.0.1"
+    which-typed-array: "npm:^1.1.9"
+  checksum: 10c0/2b7b234df3443b52f4fbd2b65b731804de8d30bcc4210ec84107ef377a81923cea7f2763b7fb78b394175cea59118bf3c41b9ffd2d643cb1d748ef93b33b6bd4
   languageName: node
   linkType: hard
 
-"which-collection@npm:^1.0.2":
-  version: 1.0.2
-  resolution: "which-collection@npm:1.0.2"
+"which-collection@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "which-collection@npm:1.0.1"
   dependencies:
-    is-map: "npm:^2.0.3"
-    is-set: "npm:^2.0.3"
-    is-weakmap: "npm:^2.0.2"
-    is-weakset: "npm:^2.0.3"
-  checksum: 10c0/3345fde20964525a04cdf7c4a96821f85f0cc198f1b2ecb4576e08096746d129eb133571998fe121c77782ac8f21cbd67745a3d35ce100d26d4e684c142ea1f2
+    is-map: "npm:^2.0.1"
+    is-set: "npm:^2.0.1"
+    is-weakmap: "npm:^2.0.1"
+    is-weakset: "npm:^2.0.1"
+  checksum: 10c0/249f913e1758ed2f06f00706007d87dc22090a80591a56917376e70ecf8fc9ab6c41d98e1c87208bb9648676f65d4b09c0e4d23c56c7afb0f0a73a27d701df5d
   languageName: node
   linkType: hard
 
@@ -18748,17 +18312,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18":
-  version: 1.1.18
-  resolution: "which-typed-array@npm:1.1.18"
+"which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15, which-typed-array@npm:^1.1.9":
+  version: 1.1.15
+  resolution: "which-typed-array@npm:1.1.15"
   dependencies:
     available-typed-arrays: "npm:^1.0.7"
-    call-bind: "npm:^1.0.8"
-    call-bound: "npm:^1.0.3"
+    call-bind: "npm:^1.0.7"
     for-each: "npm:^0.3.3"
-    gopd: "npm:^1.2.0"
+    gopd: "npm:^1.0.1"
     has-tostringtag: "npm:^1.0.2"
-  checksum: 10c0/0412f4a91880ca1a2a63056187c2e3de6b129b2b5b6c17bc3729f0f7041047ae48fb7424813e51506addb2c97320003ee18b8c57469d2cde37983ef62126143c
+  checksum: 10c0/4465d5348c044032032251be54d8988270e69c6b7154f8fcb2a47ff706fe36f7624b3a24246b8d9089435a8f4ec48c1c1025c5d6b499456b9e5eff4f48212983
   languageName: node
   linkType: hard
 
@@ -18809,28 +18372,28 @@ __metadata:
   languageName: node
   linkType: hard
 
-"workbox-background-sync@npm:7.3.0":
-  version: 7.3.0
-  resolution: "workbox-background-sync@npm:7.3.0"
+"workbox-background-sync@npm:7.1.0":
+  version: 7.1.0
+  resolution: "workbox-background-sync@npm:7.1.0"
   dependencies:
     idb: "npm:^7.0.1"
-    workbox-core: "npm:7.3.0"
-  checksum: 10c0/cc982d62702847fb16c4ef372a8bd243348a80c2d5da1649a860b0187b45060a799a65582c2d36f1a32e31d5d68dedcb037698c41d3b2f171ea5d54d73453cf1
+    workbox-core: "npm:7.1.0"
+  checksum: 10c0/9538c49a377d8eb06acee3848fbca09bac1940a2ca9e904fed765c39aa32f77c20d72c3ba6fa1eb47bee81289b1d527556a1cd3e02728960a4c40400ce6d0e91
   languageName: node
   linkType: hard
 
-"workbox-broadcast-update@npm:7.3.0":
-  version: 7.3.0
-  resolution: "workbox-broadcast-update@npm:7.3.0"
+"workbox-broadcast-update@npm:7.1.0":
+  version: 7.1.0
+  resolution: "workbox-broadcast-update@npm:7.1.0"
   dependencies:
-    workbox-core: "npm:7.3.0"
-  checksum: 10c0/25007acd3e845b5ca1f4c9ac9888ce661431723f7419cfa56b3029b6c56cbeca24902dae015c42a2d6f554f956274743e331d03ceeb4b0e3879cb7b908d0e82f
+    workbox-core: "npm:7.1.0"
+  checksum: 10c0/4a6e201cedcbc11b9d2f63f63477ba4564a35ce07bd54640198db6ff6a3b8347a65e0b4973c8f8463e8a622fd1ad93d7b3bab42338608811d23c7db01fef475e
   languageName: node
   linkType: hard
 
-"workbox-build@npm:7.3.0":
-  version: 7.3.0
-  resolution: "workbox-build@npm:7.3.0"
+"workbox-build@npm:7.1.0":
+  version: 7.1.0
+  resolution: "workbox-build@npm:7.1.0"
   dependencies:
     "@apideck/better-ajv-errors": "npm:^0.3.1"
     "@babel/core": "npm:^7.24.4"
@@ -18854,163 +18417,163 @@ __metadata:
     strip-comments: "npm:^2.0.1"
     tempy: "npm:^0.6.0"
     upath: "npm:^1.2.0"
-    workbox-background-sync: "npm:7.3.0"
-    workbox-broadcast-update: "npm:7.3.0"
-    workbox-cacheable-response: "npm:7.3.0"
-    workbox-core: "npm:7.3.0"
-    workbox-expiration: "npm:7.3.0"
-    workbox-google-analytics: "npm:7.3.0"
-    workbox-navigation-preload: "npm:7.3.0"
-    workbox-precaching: "npm:7.3.0"
-    workbox-range-requests: "npm:7.3.0"
-    workbox-recipes: "npm:7.3.0"
-    workbox-routing: "npm:7.3.0"
-    workbox-strategies: "npm:7.3.0"
-    workbox-streams: "npm:7.3.0"
-    workbox-sw: "npm:7.3.0"
-    workbox-window: "npm:7.3.0"
-  checksum: 10c0/cb396f9c2a53429d1e11b4c1da2e21c9e1c98473ce15f20ae53277e47bd7ccbcb3f1f843694e588bb70b12d9332faafd098ca05b93abb0293d373f38a8de3ca8
+    workbox-background-sync: "npm:7.1.0"
+    workbox-broadcast-update: "npm:7.1.0"
+    workbox-cacheable-response: "npm:7.1.0"
+    workbox-core: "npm:7.1.0"
+    workbox-expiration: "npm:7.1.0"
+    workbox-google-analytics: "npm:7.1.0"
+    workbox-navigation-preload: "npm:7.1.0"
+    workbox-precaching: "npm:7.1.0"
+    workbox-range-requests: "npm:7.1.0"
+    workbox-recipes: "npm:7.1.0"
+    workbox-routing: "npm:7.1.0"
+    workbox-strategies: "npm:7.1.0"
+    workbox-streams: "npm:7.1.0"
+    workbox-sw: "npm:7.1.0"
+    workbox-window: "npm:7.1.0"
+  checksum: 10c0/c482fde713bad582bd7d4861113d7367ab4722eba9c102864c71048815792c623e9117a8f79957e0388d0c08e8303962d1fb23931456da73909e87d06638d101
   languageName: node
   linkType: hard
 
-"workbox-cacheable-response@npm:7.3.0":
-  version: 7.3.0
-  resolution: "workbox-cacheable-response@npm:7.3.0"
+"workbox-cacheable-response@npm:7.1.0":
+  version: 7.1.0
+  resolution: "workbox-cacheable-response@npm:7.1.0"
   dependencies:
-    workbox-core: "npm:7.3.0"
-  checksum: 10c0/192c8a8878c53a205c55398bac78f2c32c0f36e55c95cab282d8a716ddf2fa72563afaed690d34d3438cc8df5fb0df4d98dcb2d93cc6d67c69a9ae592f7bf246
+    workbox-core: "npm:7.1.0"
+  checksum: 10c0/52ea73bb184c9ef9280cc8f00a1ab7d103d495e12a7a6378fae02fd0aa1a9b893aac5d8074f14ed8c198527123e4401f4703fbfd2be98e184ca783b9216cb4c5
   languageName: node
   linkType: hard
 
-"workbox-core@npm:7.3.0":
-  version: 7.3.0
-  resolution: "workbox-core@npm:7.3.0"
-  checksum: 10c0/b7dce640cd9665ed207f65f5b08a50e2e24e5599790c6ea4fec987539b9d2ef81765d8c5f94acfee3a8a45d5ade8e1a4ebd0b8847a1471302ef75a5b93c7bd04
+"workbox-core@npm:7.1.0":
+  version: 7.1.0
+  resolution: "workbox-core@npm:7.1.0"
+  checksum: 10c0/fb0b6e23a52e085da00b7a74b1f1854f06c695eb2bd4c244aa335165f59156a4febb4f116b9893b9fb7e0e8bac092d32eecceb4d00f930a93f64737cb2be9531
   languageName: node
   linkType: hard
 
-"workbox-expiration@npm:7.3.0, workbox-expiration@npm:^7.0.0":
-  version: 7.3.0
-  resolution: "workbox-expiration@npm:7.3.0"
+"workbox-expiration@npm:7.1.0, workbox-expiration@npm:^7.0.0":
+  version: 7.1.0
+  resolution: "workbox-expiration@npm:7.1.0"
   dependencies:
     idb: "npm:^7.0.1"
-    workbox-core: "npm:7.3.0"
-  checksum: 10c0/6040d72122ece901becfcc59974586e9cc9b6309840b83b652c9f9aafe32ff89783404a431cadf6f888f80e5371252820e425ced499742964d6d68687f6fad1a
+    workbox-core: "npm:7.1.0"
+  checksum: 10c0/669d76f87c1550ce9b425232c3202a26fdea4c4c9bdc1b71c1cee741a5d011423098994452e508576174d3c0b4bec0f4b35012b6d7257e300684c87fdddb7949
   languageName: node
   linkType: hard
 
-"workbox-google-analytics@npm:7.3.0":
-  version: 7.3.0
-  resolution: "workbox-google-analytics@npm:7.3.0"
+"workbox-google-analytics@npm:7.1.0":
+  version: 7.1.0
+  resolution: "workbox-google-analytics@npm:7.1.0"
   dependencies:
-    workbox-background-sync: "npm:7.3.0"
-    workbox-core: "npm:7.3.0"
-    workbox-routing: "npm:7.3.0"
-    workbox-strategies: "npm:7.3.0"
-  checksum: 10c0/5317a4bcc01f1aa87480f9708d7d382c15fb37d6119e71e0a2909dfd683f6060b5cc4f7b016a81fc67098f51a5d0cfd1cda20e228f2f3778ee3caf649b59996b
+    workbox-background-sync: "npm:7.1.0"
+    workbox-core: "npm:7.1.0"
+    workbox-routing: "npm:7.1.0"
+    workbox-strategies: "npm:7.1.0"
+  checksum: 10c0/4178d94fb7f3f7b789f117c104b2ff33945256dc550418b0e9c81130c1e2c2bcd72ec6a1661d91326c04de360e6592edd505f0e2142e8e1043fe0c45f9c1a3fe
   languageName: node
   linkType: hard
 
-"workbox-navigation-preload@npm:7.3.0":
-  version: 7.3.0
-  resolution: "workbox-navigation-preload@npm:7.3.0"
+"workbox-navigation-preload@npm:7.1.0":
+  version: 7.1.0
+  resolution: "workbox-navigation-preload@npm:7.1.0"
   dependencies:
-    workbox-core: "npm:7.3.0"
-  checksum: 10c0/69e4d43c68c06889987e9fa437995378b0632c83bad8c7044b4ed812b05b94b3a4aa8700ea4c26b2ecf68ee6858e94ff41dfa3279815c1bc385ac19c0edfb200
+    workbox-core: "npm:7.1.0"
+  checksum: 10c0/b667a3ba0cae4d43a53a6e211f0f33f6ebc1d9fec6cbb93de83f72a37b81cc39d887b969db9b1cd5c396a1ce34636c89c3b157cc64a5265635d0b274e362db0e
   languageName: node
   linkType: hard
 
-"workbox-precaching@npm:7.3.0, workbox-precaching@npm:^7.0.0":
-  version: 7.3.0
-  resolution: "workbox-precaching@npm:7.3.0"
+"workbox-precaching@npm:7.1.0, workbox-precaching@npm:^7.0.0":
+  version: 7.1.0
+  resolution: "workbox-precaching@npm:7.1.0"
   dependencies:
-    workbox-core: "npm:7.3.0"
-    workbox-routing: "npm:7.3.0"
-    workbox-strategies: "npm:7.3.0"
-  checksum: 10c0/15c4c5cf5dfec684711ce3536bbfa6873f7af16b712d02ded81d3ff490ea4097e46602705548f5872c49f06e3516fd69f17e72a7fc60631ff6d68460e48f7648
+    workbox-core: "npm:7.1.0"
+    workbox-routing: "npm:7.1.0"
+    workbox-strategies: "npm:7.1.0"
+  checksum: 10c0/53b2d0a658109b4d83ee2b1913f884ee1c757a12b8931a7102272bd1e228d29f9430e7d060f328f465bca2aa24bf0719d026eef4f4d21395fa1f678f8d6a3c06
   languageName: node
   linkType: hard
 
-"workbox-range-requests@npm:7.3.0":
-  version: 7.3.0
-  resolution: "workbox-range-requests@npm:7.3.0"
+"workbox-range-requests@npm:7.1.0":
+  version: 7.1.0
+  resolution: "workbox-range-requests@npm:7.1.0"
   dependencies:
-    workbox-core: "npm:7.3.0"
-  checksum: 10c0/d48e1484866442864d66b1891c4965b71e997a83a7634f11452ec1a73a30a5e642e6a95d5cff45578bef4dec7a5f57bc598aeedb6189d17ca210e2c5f2898244
+    workbox-core: "npm:7.1.0"
+  checksum: 10c0/bf4aa597d04cbb533796af64f4006a1f472f8a14ea91f96fe37b2d5e63ffe86dcb944dab9a41317e69d368d83bee20f03ff32b339ae5addef50f325703ad4b77
   languageName: node
   linkType: hard
 
-"workbox-recipes@npm:7.3.0":
-  version: 7.3.0
-  resolution: "workbox-recipes@npm:7.3.0"
+"workbox-recipes@npm:7.1.0":
+  version: 7.1.0
+  resolution: "workbox-recipes@npm:7.1.0"
   dependencies:
-    workbox-cacheable-response: "npm:7.3.0"
-    workbox-core: "npm:7.3.0"
-    workbox-expiration: "npm:7.3.0"
-    workbox-precaching: "npm:7.3.0"
-    workbox-routing: "npm:7.3.0"
-    workbox-strategies: "npm:7.3.0"
-  checksum: 10c0/c8146ece4247cbcbefba36a14f2cb65b5f74b2412f64cfc7955ff75ff653857161a1f1d94c987fbae4812f5b770eedcf99af965e512cc375fbc7fb5421bdc99c
+    workbox-cacheable-response: "npm:7.1.0"
+    workbox-core: "npm:7.1.0"
+    workbox-expiration: "npm:7.1.0"
+    workbox-precaching: "npm:7.1.0"
+    workbox-routing: "npm:7.1.0"
+    workbox-strategies: "npm:7.1.0"
+  checksum: 10c0/5a8c2444f6338c6092be87cc6fd69c8b0cbb413bfc0a11a8f10961bfb2b8059359c4be0264ffa0c01deff3ab5dba15bbcf61d4dedbc93d8bfe1f8a2841b1657c
   languageName: node
   linkType: hard
 
-"workbox-routing@npm:7.3.0, workbox-routing@npm:^7.0.0":
-  version: 7.3.0
-  resolution: "workbox-routing@npm:7.3.0"
+"workbox-routing@npm:7.1.0, workbox-routing@npm:^7.0.0":
+  version: 7.1.0
+  resolution: "workbox-routing@npm:7.1.0"
   dependencies:
-    workbox-core: "npm:7.3.0"
-  checksum: 10c0/8ac1824211d0fbe0e916ecb2c2427bcb0ef8783f9225d8114fe22e6c326f2d8a040a089bead58064e8b096ec95abe070c04cd7353dd8830dba3ab8d608a053aa
+    workbox-core: "npm:7.1.0"
+  checksum: 10c0/efd630fff594bd50276770840bce274660972587e79c097a9f1a84e8347351736aac13f11c6d7655ff550b13195d370d5c3b81a075bf452f358fc144ee868ad9
   languageName: node
   linkType: hard
 
-"workbox-strategies@npm:7.3.0, workbox-strategies@npm:^7.0.0":
-  version: 7.3.0
-  resolution: "workbox-strategies@npm:7.3.0"
+"workbox-strategies@npm:7.1.0, workbox-strategies@npm:^7.0.0":
+  version: 7.1.0
+  resolution: "workbox-strategies@npm:7.1.0"
   dependencies:
-    workbox-core: "npm:7.3.0"
-  checksum: 10c0/50f3c28b46b54885a9461ad6559010d9abb2a7e35e0128d05c268f3ea0a96b1a747934758121d0e821f7af63946d9db8f4d2d7e0146f12555fb05c768e6b82bb
+    workbox-core: "npm:7.1.0"
+  checksum: 10c0/b08712a69b1b13e354345cc228c29f0c759043f7ca7cf6ce9b82fe79c9d423142bfa4a118f91f1a57054047a730127fa4474d59d9306fb2ed42fe9ef568be01a
   languageName: node
   linkType: hard
 
-"workbox-streams@npm:7.3.0":
-  version: 7.3.0
-  resolution: "workbox-streams@npm:7.3.0"
+"workbox-streams@npm:7.1.0":
+  version: 7.1.0
+  resolution: "workbox-streams@npm:7.1.0"
   dependencies:
-    workbox-core: "npm:7.3.0"
-    workbox-routing: "npm:7.3.0"
-  checksum: 10c0/2ae541343d187eb7a50da2cfd74051f15771d1ddd1cad6856ffd530f7cccdb8eed9a8af94ff7540b710fef73eeec37d652123ae42b0206fbbd0679dc25e66ff4
+    workbox-core: "npm:7.1.0"
+    workbox-routing: "npm:7.1.0"
+  checksum: 10c0/1d75c046fcb7b25e1cf85457e3610309dd5513f68752ef333529fcf155df2114b72f3d6f416bb68393e51b5396e3f6df7171e8e2889d0e9e1805e315754b771e
   languageName: node
   linkType: hard
 
-"workbox-sw@npm:7.3.0":
-  version: 7.3.0
-  resolution: "workbox-sw@npm:7.3.0"
-  checksum: 10c0/9ae275e31dd5ec51245773b6d90fda16d0b7f70d59f3a71aec732814b5aedf08aedc7fcce57739e7e89d9e1479ef97e3a202a542a511d732cf5e8b5d1c293870
+"workbox-sw@npm:7.1.0":
+  version: 7.1.0
+  resolution: "workbox-sw@npm:7.1.0"
+  checksum: 10c0/2084f1b58c8509d7ca53ce8a13d93e57d1f13307e0279fedc87942e83c8cb96bc2e5ed3992a89af6245ad2a66897a92908cb60d0717fb90492056eb6fbf20dc6
   languageName: node
   linkType: hard
 
 "workbox-webpack-plugin@npm:^7.0.0":
-  version: 7.3.0
-  resolution: "workbox-webpack-plugin@npm:7.3.0"
+  version: 7.1.0
+  resolution: "workbox-webpack-plugin@npm:7.1.0"
   dependencies:
     fast-json-stable-stringify: "npm:^2.1.0"
     pretty-bytes: "npm:^5.4.1"
     upath: "npm:^1.2.0"
     webpack-sources: "npm:^1.4.3"
-    workbox-build: "npm:7.3.0"
+    workbox-build: "npm:7.1.0"
   peerDependencies:
     webpack: ^4.4.0 || ^5.91.0
-  checksum: 10c0/dd3625544fe08b099fd2b783584c6c2c5da3f0e0c3096fc1a86a0b96a26df5055dd178d3c60ab4cde4099474ab23d51c292356c6910dfa16a974c8a95f351c93
+  checksum: 10c0/516fa68a6a6958ee1560299dd1146032dda68474a2ab01643cbde78fc65b75a3157aef60cb45dcc1984cc458ce44d4e3090cda08dd7cefd0952351270e963a00
   languageName: node
   linkType: hard
 
-"workbox-window@npm:7.3.0, workbox-window@npm:^7.0.0":
-  version: 7.3.0
-  resolution: "workbox-window@npm:7.3.0"
+"workbox-window@npm:7.1.0, workbox-window@npm:^7.0.0":
+  version: 7.1.0
+  resolution: "workbox-window@npm:7.1.0"
   dependencies:
     "@types/trusted-types": "npm:^2.0.2"
-    workbox-core: "npm:7.3.0"
-  checksum: 10c0/dbda33c4761ec40051cfe6e3f1701b2381b4f3b191f7a249c32f683503ea35cf8b42d1f99df5ba3b693fac78705d8ed0c191488bdd178c525d1291d0161ec8ff
+    workbox-core: "npm:7.1.0"
+  checksum: 10c0/c989a6e3a0488f049eead3892f8249387604fb04898aa79d0cf14cd7b684f0758f1edf1996745f4755bd30c31c449f628803e507d39b2ea91cc9c36f7d5e9c72
   languageName: node
   linkType: hard
 
@@ -19110,8 +18673,8 @@ __metadata:
   linkType: hard
 
 "ws@npm:^8.11.0, ws@npm:^8.12.1, ws@npm:^8.18.0":
-  version: 8.18.1
-  resolution: "ws@npm:8.18.1"
+  version: 8.18.0
+  resolution: "ws@npm:8.18.0"
   peerDependencies:
     bufferutil: ^4.0.1
     utf-8-validate: ">=5.0.2"
@@ -19120,7 +18683,7 @@ __metadata:
       optional: true
     utf-8-validate:
       optional: true
-  checksum: 10c0/e498965d6938c63058c4310ffb6967f07d4fa06789d3364829028af380d299fe05762961742971c764973dce3d1f6a2633fe8b2d9410c9b52e534b4b882a99fa
+  checksum: 10c0/25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06
   languageName: node
   linkType: hard
 
@@ -19187,12 +18750,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"yaml@npm:~2.6.1":
-  version: 2.6.1
-  resolution: "yaml@npm:2.6.1"
+"yaml@npm:~2.5.0":
+  version: 2.5.0
+  resolution: "yaml@npm:2.5.0"
   bin:
     yaml: bin.mjs
-  checksum: 10c0/aebf07f61c72b38c74d2b60c3a3ccf89ee4da45bcd94b2bfb7899ba07a5257625a7c9f717c65a6fc511563d48001e01deb1d9e55f0133f3e2edf86039c8c1be7
+  checksum: 10c0/771a1df083c8217cf04ef49f87244ae2dd7d7457094425e793b8f056159f167602ce172aa32d6bca21f787d24ec724aee3cecde938f6643564117bd151452631
   languageName: node
   linkType: hard
 
@@ -19206,6 +18769,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"yargs-parser@npm:^20.2.1":
+  version: 20.2.9
+  resolution: "yargs-parser@npm:20.2.9"
+  checksum: 10c0/0685a8e58bbfb57fab6aefe03c6da904a59769bd803a722bb098bd5b0f29d274a1357762c7258fb487512811b8063fb5d2824a3415a0a4540598335b3b086c72
+  languageName: node
+  linkType: hard
+
 "yargs-parser@npm:^21.1.1":
   version: 21.1.1
   resolution: "yargs-parser@npm:21.1.1"